diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf4ae2..8a749a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ CodeTruss CLI follows semantic versioning. Release artifacts and their SHA-256 checksums are published at . -The current public release is [v0.2.36 on GitHub](https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.36), +The current public release is [v0.2.37 on GitHub](https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.37), distributed from . The npm `latest` tag is still [`@codetruss/cli@0.2.24`](https://www.npmjs.com/package/@codetruss/cli/v/0.2.24): @@ -16,6 +16,35 @@ were superseded before distribution. No unreleased changes. +## 0.2.37 — 2026-08-07 + +- **A process tree whose leader already exited is never force-killed on + Windows.** Verification and local-provider cleanup ran + `taskkill /pid /t /f` from the child's own exit handler — where the + leader is dead by definition — and from the timeout path after the leader had + exited. Windows recycles a freed pid within milliseconds, so that force-kill + could land on an unrelated process that had just inherited the number; it is + what killed a freshly forked vitest worker mid-run in CI. Both call sites now + gate on liveness read from our own `ChildProcess` handle, which pid reuse + cannot misdirect. Nothing is lost by skipping: `taskkill /t` enumerates the + tree from the leader, so a dead leader could not have reached a descendant + anyway. +- The escaped-descendant test closed the same vector in its own cleanup. It + SIGKILLed the pid recorded in a pidfile, and once the deadline had already + reaped the tree that pid could belong to an innocent process — a liveness + probe cannot tell a recycled pid from a live descendant. The descendant now + exits on its own when a sentinel file disappears, so cleanup signals no + recorded pid at all. +- **The release build now reads the changelog it ships.** `pnpm cli:release` + fails unless the version being built has its own `## ` + heading and every release heading forms one unbroken descending chain — each + version exactly once, in order, no gaps, and nothing stranded above the newest + entry. +- Repairs the changelog that guard was written for: CLI 0.2.36 overwrote + `## 0.2.35 — 2026-08-07` with its own heading, leaving the entire local-SAST + release's notes orphaned under 0.2.36 and erasing 0.2.35 from the history. + Both entries are now restored to what each release actually shipped. + ## 0.2.36 — 2026-08-07 - **Indexed file paths are now the same bytes on every platform.** The @@ -32,6 +61,8 @@ No unreleased changes. source fix already makes both sides POSIX, so this is defense in depth against any future caller that hands in a raw platform path. +## 0.2.35 — 2026-08-07 + - **Security analysis now runs locally.** The rule pack and taint solver that previously existed only in hosted scans execute on your machine, offline, over the JavaScript, TypeScript and TSX in your repository — the same engine, not a diff --git a/README.md b/README.md index 8937f66..8048f2d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ To pin an exact version, install the immutable archive directly: ```bash npm install --global --ignore-scripts --no-audit --no-fund \ - https://codetruss.com/downloads/codetruss-cli-0.2.36.tgz + https://codetruss.com/downloads/codetruss-cli-0.2.37.tgz ``` The `@codetruss/cli` package on the npm registry is published as a separate, @@ -313,8 +313,8 @@ clean global install. Verify a downloaded release yourself: ```bash -gh attestation verify codetruss-cli-0.2.36.tgz --repo DeliriumPulse/codetruss-cli -shasum -a 256 -c codetruss-cli-0.2.36.tgz.sha256 +gh attestation verify codetruss-cli-0.2.37.tgz --repo DeliriumPulse/codetruss-cli +shasum -a 256 -c codetruss-cli-0.2.37.tgz.sha256 ``` Maintainers should follow [docs/RELEASE.md](docs/RELEASE.md). Tag-driven GitHub diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index bc18773..1ec3999 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,6 +5,35 @@ checksums are published at /t /f` from the child's own exit handler — where the + leader is dead by definition — and from the timeout path after the leader had + exited. Windows recycles a freed pid within milliseconds, so that force-kill + could land on an unrelated process that had just inherited the number; it is + what killed a freshly forked vitest worker mid-run in CI. Both call sites now + gate on liveness read from our own `ChildProcess` handle, which pid reuse + cannot misdirect. Nothing is lost by skipping: `taskkill /t` enumerates the + tree from the leader, so a dead leader could not have reached a descendant + anyway. +- The escaped-descendant test closed the same vector in its own cleanup. It + SIGKILLed the pid recorded in a pidfile, and once the deadline had already + reaped the tree that pid could belong to an innocent process — a liveness + probe cannot tell a recycled pid from a live descendant. The descendant now + exits on its own when a sentinel file disappears, so cleanup signals no + recorded pid at all. +- **The release build now reads the changelog it ships.** `pnpm cli:release` + fails unless the version being built has its own `## ` + heading and every release heading forms one unbroken descending chain — each + version exactly once, in order, no gaps, and nothing stranded above the newest + entry. +- Repairs the changelog that guard was written for: CLI 0.2.36 overwrote + `## 0.2.35 — 2026-08-07` with its own heading, leaving the entire local-SAST + release's notes orphaned under 0.2.36 and erasing 0.2.35 from the history. + Both entries are now restored to what each release actually shipped. + ## 0.2.36 — 2026-08-07 - **Indexed file paths are now the same bytes on every platform.** The @@ -21,6 +50,8 @@ checksums are published at \d+\.\d+\.\d+) — (?\d{4}-\d{2}-\d{2})(? \(unpublished\))?$/ +const UNRELEASED = /^## Unreleased$/ + +function parseVersion(version) { + const [major, minor, patch] = version.split('.').map(Number) + return { major, minor, patch } +} + +/** + * True when `newer` is the immediate semver successor of `older`: the next patch + * on the same minor, or the `.0` that opens the next minor or major. Anything + * else is a gap (a lost entry) or a reordering. + */ +function succeeds(newer, older) { + if (newer.major === older.major && newer.minor === older.minor) return newer.patch === older.patch + 1 + if (newer.major === older.major) return newer.minor === older.minor + 1 && newer.patch === 0 + return newer.major === older.major + 1 && newer.minor === 0 && newer.patch === 0 +} + +/** + * Assert the changelog records `version` and that every release heading forms one + * unbroken descending chain. `changelog` is the file's raw text. + */ +export function assertChangelogPolicy(changelog, version) { + const lines = changelog.split('\n') + const headings = [] + for (const [index, line] of lines.entries()) { + const match = HEADING.exec(line) + if (match) headings.push({ ...match.groups, line: index + 1, ...parseVersion(match.groups.version) }) + } + if (headings.length === 0) throw new Error('CHANGELOG.md declares no release headings') + + // Nothing may sit between the preamble and the first release except an + // optional, empty `## Unreleased`. Notes stranded above the newest release + // heading are notes no released version claims. + const firstHeadingIndex = headings[0].line - 1 + let sawUnreleased = false + for (let index = 0; index < firstHeadingIndex; index += 1) { + const line = lines[index] + if (UNRELEASED.test(line)) { + sawUnreleased = true + continue + } + if (line.startsWith('## ')) { + throw new Error(`CHANGELOG.md line ${index + 1}: unexpected section before the first release heading: ${line}`) + } + if (sawUnreleased && line.trim() !== '') { + throw new Error( + `CHANGELOG.md line ${index + 1}: "## Unreleased" still has content; move it into the release entry: ${line}`, + ) + } + } + + const seen = new Map() + for (const heading of headings) { + const previous = seen.get(heading.version) + if (previous !== undefined) { + throw new Error(`CHANGELOG.md declares version ${heading.version} twice (lines ${previous} and ${heading.line})`) + } + seen.set(heading.version, heading.line) + } + + for (let index = 1; index < headings.length; index += 1) { + const newer = headings[index - 1] + const older = headings[index] + if (!succeeds(newer, older)) { + throw new Error( + `CHANGELOG.md release chain breaks between ${newer.version} (line ${newer.line}) and ${older.version} ` + + `(line ${older.line}): every released version must appear exactly once, in descending order, with no gaps. ` + + 'A version that was never published still needs its own "(unpublished)" entry.', + ) + } + } + + if (!seen.has(version)) { + throw new Error( + `CHANGELOG.md has no "## ${version} — " heading; the release being built must document itself. ` + + `Newest entry is ${headings[0].version}.`, + ) + } + if (seen.get(version) !== headings[0].line) { + throw new Error(`CHANGELOG.md lists ${version} below a newer entry (${headings[0].version}); it must be the first release heading`) + } +} diff --git a/packages/cli/scripts/test-changelog-policy.mjs b/packages/cli/scripts/test-changelog-policy.mjs new file mode 100644 index 0000000..816bc1a --- /dev/null +++ b/packages/cli/scripts/test-changelog-policy.mjs @@ -0,0 +1,87 @@ +import assert from 'node:assert/strict' +import { readFile } from 'node:fs/promises' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { assertChangelogPolicy } from './changelog-policy.mjs' + +const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..') + +const preamble = [ + '# Changelog', + '', + 'CodeTruss CLI follows semantic versioning.', + '', + '## Unreleased', + '', +].join('\n') + +const changelog = (...entries) => `${preamble}${entries.map((entry) => `${entry}\n`).join('\n')}` +const entry = (version, date = '2026-08-07', body = '- a change') => `## ${version} — ${date}\n\n${body}` + +// The shipped changelog and the version it documents must satisfy the policy. +const shipped = await readFile(join(packageDir, 'CHANGELOG.md'), 'utf8') +const pkg = JSON.parse(await readFile(join(packageDir, 'package.json'), 'utf8')) +assertChangelogPolicy(shipped, pkg.version) + +// A well-formed chain passes, including the roll from one minor to the next. +assertChangelogPolicy(changelog(entry('0.2.1'), entry('0.2.0'), entry('0.1.1')), '0.2.1') +assertChangelogPolicy(changelog(entry('1.0.0'), entry('0.9.3'), entry('0.9.2')), '1.0.0') +// "(unpublished)" entries still count as links in the chain. +assertChangelogPolicy( + changelog(entry('0.2.2'), `## 0.2.1 — 2026-08-06 (unpublished)\n\n- skipped`, entry('0.2.0')), + '0.2.2', +) + +// (a) The version being released must have its own heading. +assert.throws( + () => assertChangelogPolicy(changelog(entry('0.2.1'), entry('0.2.0')), '0.2.2'), + /no "## 0\.2\.2 — " heading/, +) + +// (b) The exact 0.2.36 regression: a release overwrites the previous heading, +// leaving a gap where 0.2.35 used to be. +assert.throws( + () => assertChangelogPolicy(changelog(entry('0.2.36'), entry('0.2.34'), entry('0.2.33')), '0.2.36'), + /release chain breaks between 0\.2\.36 .* and 0\.2\.34/, +) + +// (b) A version listed twice. +assert.throws( + () => assertChangelogPolicy(changelog(entry('0.2.2'), entry('0.2.2'), entry('0.2.1')), '0.2.2'), + /declares version 0\.2\.2 twice/, +) + +// (b) Headings out of descending order. +assert.throws( + () => assertChangelogPolicy(changelog(entry('0.2.0'), entry('0.2.1'), entry('0.1.1')), '0.2.0'), + /release chain breaks between 0\.2\.0 .* and 0\.2\.1/, +) + +// (b) The released version must be the newest entry, not buried mid-file. +assert.throws( + () => assertChangelogPolicy(changelog(entry('0.2.2'), entry('0.2.1'), entry('0.2.0')), '0.2.1'), + /lists 0\.2\.1 below a newer entry/, +) + +// (b) Notes stranded above the newest release heading belong to no version. +assert.throws( + () => assertChangelogPolicy( + `${preamble}- an orphaned bullet\n\n${entry('0.2.1')}\n\n${entry('0.2.0')}\n`, + '0.2.1', + ), + /"## Unreleased" still has content/, +) + +// (b) A non-release section wedged above the first release heading. +assert.throws( + () => assertChangelogPolicy( + `# Changelog\n\n## Notes\n\n${entry('0.2.1')}\n\n${entry('0.2.0')}\n`, + '0.2.1', + ), + /unexpected section before the first release heading/, +) + +// A changelog with no releases at all is not a changelog. +assert.throws(() => assertChangelogPolicy(preamble, '0.2.1'), /declares no release headings/) + +process.stdout.write('changelog policy: chain, uniqueness, ordering, and self-documentation enforced\n') diff --git a/packages/cli/src/git.ts b/packages/cli/src/git.ts index b862002..6b64bf6 100644 --- a/packages/cli/src/git.ts +++ b/packages/cli/src/git.ts @@ -1,4 +1,4 @@ -import { spawn, spawnSync } from 'node:child_process' +import { spawn, spawnSync, type ChildProcess } from 'node:child_process' import { readFile, stat } from 'node:fs/promises' import { devNull } from 'node:os' import { join, resolve } from 'node:path' @@ -450,8 +450,17 @@ function verificationDelay(milliseconds: number): Promise { return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds)) } -async function terminateVerificationProcessTree(pid: number): Promise { +async function terminateVerificationProcessTree(child: ChildProcess): Promise { + const pid = child.pid + if (pid === undefined) return if (process.platform === 'win32') { + // taskkill /t enumerates the tree from the leader pid, so once the leader + // has exited it cannot reach anything — and Windows recycles freed pids + // within milliseconds, so addressing one can force-kill an unrelated + // process. The liveness check uses our own process handle and cannot be + // misdirected; a leader exiting between this check and taskkill's own + // snapshot is the same narrow race every taskkill user carries. + if (child.exitCode !== null || child.signalCode !== null) return spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore', timeout: 2_000, @@ -488,7 +497,7 @@ export async function runVerification( let cleanupPromise: Promise | undefined const cleanup = (): Promise => { - cleanupPromise ??= child.pid === undefined ? Promise.resolve() : terminateVerificationProcessTree(child.pid) + cleanupPromise ??= terminateVerificationProcessTree(child) return cleanupPromise } const finish = (exitCode: number, suffix = ''): void => { diff --git a/packages/cli/src/local-command.ts b/packages/cli/src/local-command.ts index ec8f4f8..a89b890 100644 --- a/packages/cli/src/local-command.ts +++ b/packages/cli/src/local-command.ts @@ -1,4 +1,4 @@ -import { spawn, spawnSync } from 'node:child_process' +import { spawn, spawnSync, type ChildProcess } from 'node:child_process' export const LOCAL_COMMAND_MAX_OUTPUT_BYTES = 2_000_000 @@ -52,8 +52,17 @@ function signalProcessGroup(pid: number, signal: NodeJS.Signals): boolean { } } -async function terminateProcessTree(pid: number): Promise { +async function terminateProcessTree(child: ChildProcess): Promise { + const pid = child.pid + if (pid === undefined) return if (process.platform === 'win32') { + // taskkill /t enumerates the tree from the leader pid, so once the leader + // has exited it cannot reach anything — and Windows recycles freed pids + // within milliseconds, so addressing one can force-kill an unrelated + // process. The liveness check uses our own process handle and cannot be + // misdirected; a leader exiting between this check and taskkill's own + // snapshot is the same narrow race every taskkill user carries. + if (child.exitCode !== null || child.signalCode !== null) return spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore', timeout: 2_000, @@ -97,7 +106,7 @@ export function runLocalCommand(request: LocalCommandRequest): Promise | undefined const cleanup = () => { - cleanupPromise ??= child.pid === undefined ? Promise.resolve() : terminateProcessTree(child.pid) + cleanupPromise ??= terminateProcessTree(child) return cleanupPromise } const fail = (reason: LocalCommandFailureReason) => { diff --git a/packages/cli/test/verification-timeout.test.ts b/packages/cli/test/verification-timeout.test.ts index 52a0f97..c02b51b 100644 --- a/packages/cli/test/verification-timeout.test.ts +++ b/packages/cli/test/verification-timeout.test.ts @@ -55,7 +55,19 @@ describe('verification command lifecycle', () => { const descendantScript = join(root, 'escaped-descendant.cjs') const parentScript = join(root, 'escaped-parent.cjs') const pidFile = join(root, 'escaped-descendant.pid') - await writeFile(descendantScript, 'setInterval(() => {}, 1_000)\n') + const sentinel = join(root, 'escaped-descendant.keepalive') + await writeFile(sentinel, '') + // Cleanup must not signal the recorded pid: when the deadline reaps the + // tree before the leader exits, the descendant is already dead and Windows + // can recycle its pid to an unrelated process within milliseconds — a + // SIGKILL there once terminated a concurrent vitest worker. The descendant + // instead exits on its own when the sentinel file disappears, and the + // afterEach rm (which retries with backoff) absorbs its exit latency. + await writeFile(descendantScript, [ + "const { existsSync } = require('node:fs')", + `const sentinel = ${JSON.stringify(sentinel)}`, + 'setInterval(() => { if (!existsSync(sentinel)) process.exit(0) }, 100)', + ].join('\n')) await writeFile(parentScript, [ "const { spawn } = require('node:child_process')", "const { writeFileSync } = require('node:fs')", @@ -63,20 +75,16 @@ describe('verification command lifecycle', () => { 'child.unref()', `writeFileSync(${JSON.stringify(pidFile)}, String(child.pid))`, ].join(';')) - let descendantPid: number | undefined try { const result = await runVerification(nodeCommand(parentScript), root, 1_024, process.env, 500) expect(result.exitCode).toBe(124) expect(result.output).toContain('CodeTruss verification timed out after 500ms.') expect(result.durationMs).toBeLessThan(2_000) - descendantPid = Number(await readFile(pidFile, 'utf8')) + const descendantPid = Number(await readFile(pidFile, 'utf8')) expect(Number.isSafeInteger(descendantPid)).toBe(true) } finally { - if (descendantPid !== undefined && processExists(descendantPid)) { - try { process.kill(descendantPid, 'SIGKILL') } catch { /* already gone */ } - await expectProcessToExit(descendantPid) - } + await rm(sentinel, { force: true }) } }) diff --git a/public/downloads/codetruss-cli-0.2.37.sbom.cdx.json b/public/downloads/codetruss-cli-0.2.37.sbom.cdx.json new file mode 100644 index 0000000..bd124d9 --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.37.sbom.cdx.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json", + "bomFormat": "CycloneDX", + "serialNumber": "urn:uuid:547ec74a-8f42-5024-930d-23afcefa5478", + "specVersion": "1.6", + "version": 1, + "metadata": { + "component": { + "type": "application", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.37", + "name": "@codetruss/cli", + "version": "0.2.37", + "description": "Local-first scope, quality, and verification receipts for coding agents", + "licenses": [ + { + "license": { + "name": "CodeTruss CLI Proprietary License" + } + } + ], + "purl": "pkg:npm/%40codetruss/cli@0.2.37" + }, + "properties": [ + { + "name": "codetruss:distribution", + "value": "single-file JavaScript bundle" + }, + { + "name": "codetruss:runtimeDependencies", + "value": "0" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "name": "@codetruss/analyzer-engine", + "version": "0.1.0", + "licenses": [ + { + "license": { + "name": "CodeTruss CLI Proprietary License" + } + } + ], + "purl": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/balanced-match@4.0.4", + "name": "balanced-match", + "version": "4.0.4", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:npm/balanced-match@4.0.4", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/brace-expansion@5.0.7", + "name": "brace-expansion", + "version": "5.0.7", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:npm/brace-expansion@5.0.7", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/minimatch@10.2.5", + "name": "minimatch", + "version": "10.2.5", + "licenses": [ + { + "license": { + "id": "BlueOak-1.0.0" + } + } + ], + "purl": "pkg:npm/minimatch@10.2.5", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + }, + { + "type": "library", + "bom-ref": "pkg:npm/yaml@2.9.0", + "name": "yaml", + "version": "2.9.0", + "licenses": [ + { + "license": { + "id": "ISC" + } + } + ], + "purl": "pkg:npm/yaml@2.9.0", + "properties": [ + { + "name": "codetruss:bundled", + "value": "true" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "dependsOn": [] + }, + { + "ref": "pkg:npm/%40codetruss/cli@0.2.37", + "dependsOn": [ + "pkg:npm/%40codetruss/analyzer-engine@0.1.0", + "pkg:npm/minimatch@10.2.5", + "pkg:npm/yaml@2.9.0" + ] + }, + { + "ref": "pkg:npm/balanced-match@4.0.4", + "dependsOn": [] + }, + { + "ref": "pkg:npm/brace-expansion@5.0.7", + "dependsOn": [ + "pkg:npm/balanced-match@4.0.4" + ] + }, + { + "ref": "pkg:npm/minimatch@10.2.5", + "dependsOn": [ + "pkg:npm/brace-expansion@5.0.7" + ] + }, + { + "ref": "pkg:npm/yaml@2.9.0", + "dependsOn": [] + } + ] +} diff --git a/public/downloads/codetruss-cli-0.2.37.tgz b/public/downloads/codetruss-cli-0.2.37.tgz new file mode 100644 index 0000000..1885c3b Binary files /dev/null and b/public/downloads/codetruss-cli-0.2.37.tgz differ diff --git a/public/downloads/codetruss-cli-0.2.37.tgz.sha256 b/public/downloads/codetruss-cli-0.2.37.tgz.sha256 new file mode 100644 index 0000000..0f4b734 --- /dev/null +++ b/public/downloads/codetruss-cli-0.2.37.tgz.sha256 @@ -0,0 +1 @@ +082a03fe5dd2d9c516acc76504e0bb45bc7d3b5a1fa7308fb2d627ce07a3c627 codetruss-cli-0.2.37.tgz diff --git a/public/downloads/codetruss-cli-latest.json b/public/downloads/codetruss-cli-latest.json index 9fdb214..d0e35a7 100644 --- a/public/downloads/codetruss-cli-latest.json +++ b/public/downloads/codetruss-cli-latest.json @@ -1,13 +1,13 @@ { "name": "@codetruss/cli", - "version": "0.2.36", - "url": "/downloads/codetruss-cli-0.2.36.tgz", + "version": "0.2.37", + "url": "/downloads/codetruss-cli-0.2.37.tgz", "latestUrl": "/downloads/codetruss-cli-latest.tgz", - "sha256": "953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814", - "sbomUrl": "/downloads/codetruss-cli-0.2.36.sbom.cdx.json", - "sbomSha256": "41db21a7ada85d04be35aa89858ef486f2d1d74e1161d0235029c7a1b1f8f736", + "sha256": "082a03fe5dd2d9c516acc76504e0bb45bc7d3b5a1fa7308fb2d627ce07a3c627", + "sbomUrl": "/downloads/codetruss-cli-0.2.37.sbom.cdx.json", + "sbomSha256": "deea220e73042fd084a71130d71375353d977baee714900fc5dbe49a28cd4a56", "node": ">=20.9.0", "repository": "https://github.com/DeliriumPulse/codetruss-cli", - "releaseUrl": "https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.36", - "attestationCommand": "gh attestation verify codetruss-cli-0.2.36.tgz --repo DeliriumPulse/codetruss-cli" + "releaseUrl": "https://github.com/DeliriumPulse/codetruss-cli/releases/tag/v0.2.37", + "attestationCommand": "gh attestation verify codetruss-cli-0.2.37.tgz --repo DeliriumPulse/codetruss-cli" } diff --git a/public/downloads/codetruss-cli-latest.sbom.cdx.json b/public/downloads/codetruss-cli-latest.sbom.cdx.json index 1cce3fc..bd124d9 100644 --- a/public/downloads/codetruss-cli-latest.sbom.cdx.json +++ b/public/downloads/codetruss-cli-latest.sbom.cdx.json @@ -1,15 +1,15 @@ { "$schema": "https://cyclonedx.org/schema/bom-1.6.schema.json", "bomFormat": "CycloneDX", - "serialNumber": "urn:uuid:ff01a909-707a-5338-90a8-39a81331538d", + "serialNumber": "urn:uuid:547ec74a-8f42-5024-930d-23afcefa5478", "specVersion": "1.6", "version": 1, "metadata": { "component": { "type": "application", - "bom-ref": "pkg:npm/%40codetruss/cli@0.2.36", + "bom-ref": "pkg:npm/%40codetruss/cli@0.2.37", "name": "@codetruss/cli", - "version": "0.2.36", + "version": "0.2.37", "description": "Local-first scope, quality, and verification receipts for coding agents", "licenses": [ { @@ -18,7 +18,7 @@ } } ], - "purl": "pkg:npm/%40codetruss/cli@0.2.36" + "purl": "pkg:npm/%40codetruss/cli@0.2.37" }, "properties": [ { @@ -139,7 +139,7 @@ "dependsOn": [] }, { - "ref": "pkg:npm/%40codetruss/cli@0.2.36", + "ref": "pkg:npm/%40codetruss/cli@0.2.37", "dependsOn": [ "pkg:npm/%40codetruss/analyzer-engine@0.1.0", "pkg:npm/minimatch@10.2.5", diff --git a/public/downloads/codetruss-cli-latest.tgz b/public/downloads/codetruss-cli-latest.tgz index 3d385f1..1885c3b 100644 Binary files a/public/downloads/codetruss-cli-latest.tgz and b/public/downloads/codetruss-cli-latest.tgz differ diff --git a/public/downloads/codetruss-cli-latest.tgz.sha256 b/public/downloads/codetruss-cli-latest.tgz.sha256 index 48eac5b..4c31281 100644 --- a/public/downloads/codetruss-cli-latest.tgz.sha256 +++ b/public/downloads/codetruss-cli-latest.tgz.sha256 @@ -1 +1 @@ -953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814 codetruss-cli-latest.tgz +082a03fe5dd2d9c516acc76504e0bb45bc7d3b5a1fa7308fb2d627ce07a3c627 codetruss-cli-latest.tgz diff --git a/release-reference.json b/release-reference.json index d32df5a..2b19e05 100644 --- a/release-reference.json +++ b/release-reference.json @@ -1,8 +1,8 @@ { "schemaVersion": 1, - "version": "0.2.36", - "websiteArchive": "https://codetruss.com/downloads/codetruss-cli-0.2.36.tgz", - "archiveSha256": "953e3f48725a7471043b48a55aedd33ef78854615a341dd95881d90eeae3e814", - "sbomSha256": "41db21a7ada85d04be35aa89858ef486f2d1d74e1161d0235029c7a1b1f8f736", - "bundleSha256": "62d99ca35ac4d0e701e4169cb63d7115a814dc5f436fcb9a638ad119a3306381" + "version": "0.2.37", + "websiteArchive": "https://codetruss.com/downloads/codetruss-cli-0.2.37.tgz", + "archiveSha256": "082a03fe5dd2d9c516acc76504e0bb45bc7d3b5a1fa7308fb2d627ce07a3c627", + "sbomSha256": "deea220e73042fd084a71130d71375353d977baee714900fc5dbe49a28cd4a56", + "bundleSha256": "2e5311884651f9c63534bf0ffcd472a994003ca12c81ca82e05400fbb65a875e" }