From 287ff5bc5a28e6e2b79dcf60e73b539165bb67ca Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:40:03 +0200 Subject: [PATCH 1/3] fix(changelog): derive the no-tag release date from the version, not the clock pm-changelog stamps the pending window of an untagged version with the current UTC date, so changelog:check regenerates a different heading date every day and fails on any day after the changelog was written -- a gate whose verdict flips at midnight with no input change. release.yml tags only after npm publish succeeds, and publish has failed fleet-wide since 2026-08-21, so this package carries an untagged version and is on that path. It passes today only because the package version and today's date are the same calendar day. Verified on pm-ops, where the version date and today's date differ and the comparison therefore discriminates: an untagged 2026.8.22 generates '2026.8.22 - 2026-08-24' unflagged and '2026.8.22 - 2026-08-22' with --date-from-version. The flag already shipped in pm-changelog 2026.8.17; no fleet package passed it. Applied to EVERY invocation of the generator, not only the scripts named changelog*: release.yml invokes it directly, and generation and check must not disagree or the divergence returns as a release failure. Site audit: package.json sites 2 flagged 2; .github/workflows/release.yml sites 3 flagged 3; --- .agents/pm/history/pm-github-m8vj.jsonl | 4 ++++ .agents/pm/issues/pm-github-m8vj.toon | 16 ++++++++++++++++ .github/workflows/release.yml | 6 +++--- package.json | 4 ++-- 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .agents/pm/history/pm-github-m8vj.jsonl create mode 100644 .agents/pm/issues/pm-github-m8vj.toon diff --git a/.agents/pm/history/pm-github-m8vj.jsonl b/.agents/pm/history/pm-github-m8vj.jsonl new file mode 100644 index 0000000..837993b --- /dev/null +++ b/.agents/pm/history/pm-github-m8vj.jsonl @@ -0,0 +1,4 @@ +{"ts":"2026-08-24T09:40:00.486Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-m8vj"},{"op":"add","path":"/metadata/title","value":"The changelog gate stamps an untagged version with the current date, so its verdict flips every midnight with no commit"},{"op":"add","path":"/metadata/description","value":"pm-changelog synthesises a pending release window for a version that has no matching git tag and stamps it with the current UTC date rather than anything derived from the input. The release workflow tags only after npm publish succeeds, and publish has been failing fleet wide since 2026-08-21, so this package currently carries an untagged version and is on that clock dependent path. The exposure is masked today only by a coincidence, because the package version and today's date are the same calendar day, and it fails on any later day with no commit having changed. Verified on pm-ops where the version date and today's date differ, which makes the comparison decisive: an untagged 2026.8.22 generates heading 2026.8.22 - 2026-08-24 without the flag and 2026.8.22 - 2026-08-22 with it. The remedy needs no new code because pm-changelog 2026.8.17 already ships --date-from-version, and zero of twenty packages passed it. Applied to every invocation of the generator in this package rather than only to the scripts whose names begin with changelog, because the release workflow invokes it directly and generation and check must not disagree. Site audit: package.json sites 2 flagged 2; .github/workflows/release.yml sites 3 flagged 3;"},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":1},{"op":"add","path":"/metadata/tags","value":["changelog","gates","release"]},{"op":"add","path":"/metadata/created_at","value":"2026-08-24T09:40:00.486Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-24T09:40:00.486Z"},{"op":"add","path":"/metadata/author","value":"harness:claude-code"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"066eb8244d63baf8616bcf83677c1e530dc047b3fad71f90deb30d12abfaf722","item_hash_version":2,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T09:40:01.787Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:01.787Z"},{"op":"add","path":"/metadata/files","value":[{"path":"package.json","scope":"project","note":"every pm-changelog invocation here now derives the no-tag release date from the calendar version"}]}],"before_hash":"066eb8244d63baf8616bcf83677c1e530dc047b3fad71f90deb30d12abfaf722","after_hash":"e3b168699b37cb331584f1f5b651777c5a5447420ea49b82f5a03e304b6015f2","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T09:40:02.297Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":".github/workflows/release.yml","scope":"project","note":"every pm-changelog invocation here now derives the no-tag release date from the calendar version"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:02.297Z"}],"before_hash":"e3b168699b37cb331584f1f5b651777c5a5447420ea49b82f5a03e304b6015f2","after_hash":"a255a62396aece893428674d595de84fe6c051c64f648924dff74a51470eb18b","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T09:40:02.702Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:02.702Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"npm run changelog:check","path":"package.json","scope":"project","note":"regenerates the changelog and compares it; without the flag the heading date is stamped from the clock and the comparison fails on any day after generation"}]}],"before_hash":"a255a62396aece893428674d595de84fe6c051c64f648924dff74a51470eb18b","after_hash":"69f77d3a5ae50ea79a9a842e87334f495ffe39621090444ae4b22fad198442f7","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} diff --git a/.agents/pm/issues/pm-github-m8vj.toon b/.agents/pm/issues/pm-github-m8vj.toon new file mode 100644 index 0000000..3bd2163 --- /dev/null +++ b/.agents/pm/issues/pm-github-m8vj.toon @@ -0,0 +1,16 @@ +id: pm-github-m8vj +title: "The changelog gate stamps an untagged version with the current date, so its verdict flips every midnight with no commit" +description: "pm-changelog synthesises a pending release window for a version that has no matching git tag and stamps it with the current UTC date rather than anything derived from the input. The release workflow tags only after npm publish succeeds, and publish has been failing fleet wide since 2026-08-21, so this package currently carries an untagged version and is on that clock dependent path. The exposure is masked today only by a coincidence, because the package version and today's date are the same calendar day, and it fails on any later day with no commit having changed. Verified on pm-ops where the version date and today's date differ, which makes the comparison decisive: an untagged 2026.8.22 generates heading 2026.8.22 - 2026-08-24 without the flag and 2026.8.22 - 2026-08-22 with it. The remedy needs no new code because pm-changelog 2026.8.17 already ships --date-from-version, and zero of twenty packages passed it. Applied to every invocation of the generator in this package rather than only to the scripts whose names begin with changelog, because the release workflow invokes it directly and generation and check must not disagree. Site audit: package.json sites 2 flagged 2; .github/workflows/release.yml sites 3 flagged 3;" +type: Issue +status: open +priority: 1 +tags[3]: changelog,gates,release +created_at: "2026-08-24T09:40:00.486Z" +updated_at: "2026-08-24T09:40:02.702Z" +author: "harness:claude-code" +files[2]{path,scope,note}: + package.json,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version + .github/workflows/release.yml,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version +tests[1]{command,path,scope,note}: + "npm run changelog:check",package.json,project,regenerates the changelog and compares it; without the flag the heading date is stamped from the clock and the comparison fails on any day after generation +body: "" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 46b5173..2f1de0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,9 +108,9 @@ jobs: shell: bash run: | set -euo pipefail - npx pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --github-step-summary - npx pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --check - npx pm-changelog --pm-root .agents/pm --stdout --since-previous-tag --until-release-tag --release-version-from-package --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm > RELEASE_NOTES.md + npx pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --github-step-summary + npx pm-changelog --pm-root .agents/pm --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --check + npx pm-changelog --pm-root .agents/pm --stdout --since-previous-tag --until-release-tag --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm > RELEASE_NOTES.md - name: Run release checks if: steps.decide.outputs.should_release == 'true' diff --git a/package.json b/package.json index a603ada..6d92656 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,11 @@ "docstring": "node scripts/docstring-gate.ts", "audit:prod": "node --input-type=module -e \"import { spawnSync } from 'node:child_process'; import { devNull } from 'node:os'; const env = { ...process.env, npm_config_userconfig: devNull, NPM_CONFIG_USERCONFIG: devNull }; for (const key of Object.keys(env)) if (key.toLowerCase() === 'npm_config_allow_scripts') delete env[key]; const win = process.platform === 'win32'; const r = spawnSync(win ? 'npm.cmd' : 'npm', ['audit', '--omit=dev', '--ignore-scripts'], { stdio: 'inherit', env, shell: win }); process.exit(r.status ?? 1);\"", "pack:dry-run": "npm pack --dry-run", - "changelog:full": "pm-changelog --pm-root .agents/pm --pm-arg=--output-limit --pm-arg=unbounded --pm-arg=--output-budget --pm-arg=unbounded --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release", + "changelog:full": "pm-changelog --pm-root .agents/pm --pm-arg=--output-limit --pm-arg=unbounded --pm-arg=--output-budget --pm-arg=unbounded --mode replace --output CHANGELOG.md --all-release-tags --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release", "changelog:check": "npm run changelog:full -- --check", "release:check": "npm run typecheck && npm run build && npm run docstring && npm run privacy && npm run coverage && npm run audit:prod && npm run pack:dry-run && npm run changelog:check", "prepublishOnly": "npm run release:check", - "release:notes": "pm-changelog --pm-root .agents/pm --pm-arg=--output-limit --pm-arg=unbounded --pm-arg=--output-budget --pm-arg=unbounded --stdout --since-previous-tag --until-release-tag --release-version-from-package --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --github-step-summary", + "release:notes": "pm-changelog --pm-root .agents/pm --pm-arg=--output-limit --pm-arg=unbounded --pm-arg=--output-budget --pm-arg=unbounded --stdout --since-previous-tag --until-release-tag --release-version-from-package --date-from-version --item-url-base https://github.com/unbraind/pm-github/blob/main/.agents/pm --respect-item-release --pm-bin ./node_modules/.bin/pm --github-step-summary", "prepare": "node scripts/prepare-merge-driver.mjs", "merge:install": "pm merge install", "coverage": "npm run build && npm run build:test && node scripts/coverage-gate.ts", From f1f0d802decd94f49e9d2b674a8525793d4f9c18 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:03:34 +0200 Subject: [PATCH 2/3] test(changelog): prove the release-workflow path, which changelog:check never reaches CodeRabbit was right that the linked test only covered the package.json invocation. release.yml calls pm-changelog directly, and nothing exercised those calls. The static half enumerates every tracked file holding a generator invocation and asserts each file's flagged count is not below its site count. Verified non-vacuous by removing the flag from one of two release.yml sites: it reports 1 of 2 and exits non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2 -- chosen so its calendar date cannot coincide with today -- and asserts '2026.1.2 - 2026-01-02' with the flag against '2026.1.2 - 2026-08-24' without it. The test therefore states the defect, not just the fix. --- .agents/pm/history/pm-github-m8vj.jsonl | 3 ++ .agents/pm/issues/pm-github-m8vj.toon | 10 ++-- scripts/verify-release-changelog-date.sh | 60 ++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100755 scripts/verify-release-changelog-date.sh diff --git a/.agents/pm/history/pm-github-m8vj.jsonl b/.agents/pm/history/pm-github-m8vj.jsonl index 837993b..aa2ff9e 100644 --- a/.agents/pm/history/pm-github-m8vj.jsonl +++ b/.agents/pm/history/pm-github-m8vj.jsonl @@ -2,3 +2,6 @@ {"ts":"2026-08-24T09:40:01.787Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:01.787Z"},{"op":"add","path":"/metadata/files","value":[{"path":"package.json","scope":"project","note":"every pm-changelog invocation here now derives the no-tag release date from the calendar version"}]}],"before_hash":"066eb8244d63baf8616bcf83677c1e530dc047b3fad71f90deb30d12abfaf722","after_hash":"e3b168699b37cb331584f1f5b651777c5a5447420ea49b82f5a03e304b6015f2","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} {"ts":"2026-08-24T09:40:02.297Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":".github/workflows/release.yml","scope":"project","note":"every pm-changelog invocation here now derives the no-tag release date from the calendar version"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:02.297Z"}],"before_hash":"e3b168699b37cb331584f1f5b651777c5a5447420ea49b82f5a03e304b6015f2","after_hash":"a255a62396aece893428674d595de84fe6c051c64f648924dff74a51470eb18b","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} {"ts":"2026-08-24T09:40:02.702Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T09:40:02.702Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"npm run changelog:check","path":"package.json","scope":"project","note":"regenerates the changelog and compares it; without the flag the heading date is stamped from the clock and the comparison fails on any day after generation"}]}],"before_hash":"a255a62396aece893428674d595de84fe6c051c64f648924dff74a51470eb18b","after_hash":"69f77d3a5ae50ea79a9a842e87334f495ffe39621090444ae4b22fad198442f7","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T10:03:32.888Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"bash scripts/verify-release-changelog-date.sh","path":"scripts/verify-release-changelog-date.sh","scope":"project","note":"exercises the release workflow path that npm run changelog:check does not: asserts every generator invocation in every tracked file carries the flag, and shows the same probe version yielding its own calendar date with the flag and today's date without it"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:32.888Z"}],"before_hash":"69f77d3a5ae50ea79a9a842e87334f495ffe39621090444ae4b22fad198442f7","after_hash":"b64266e44f22744ea47d754bbb56149b69cc30665202a7fa44c9008ee563ee2e","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T10:03:33.311Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/2","value":{"path":"scripts/verify-release-changelog-date.sh","scope":"project","note":"executable proof for the release workflow invocations, which the package.json changelog check never reaches"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:33.311Z"}],"before_hash":"b64266e44f22744ea47d754bbb56149b69cc30665202a7fa44c9008ee563ee2e","after_hash":"80ccd79861a5cf611f73a0852db5cdaaf80e084ee496c6cdba9a61636d58de5d","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T10:03:33.732Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:33.732Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-08-24T10:03:33.732Z","author":"harness:claude-code","text":"CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix."}]}],"before_hash":"80ccd79861a5cf611f73a0852db5cdaaf80e084ee496c6cdba9a61636d58de5d","after_hash":"9e1140c4269961557871f0cbcd6e87b26f7456712915da4596a184c4bb765508","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} diff --git a/.agents/pm/issues/pm-github-m8vj.toon b/.agents/pm/issues/pm-github-m8vj.toon index 3bd2163..eee4209 100644 --- a/.agents/pm/issues/pm-github-m8vj.toon +++ b/.agents/pm/issues/pm-github-m8vj.toon @@ -6,11 +6,15 @@ status: open priority: 1 tags[3]: changelog,gates,release created_at: "2026-08-24T09:40:00.486Z" -updated_at: "2026-08-24T09:40:02.702Z" +updated_at: "2026-08-24T10:03:33.732Z" author: "harness:claude-code" -files[2]{path,scope,note}: +comments[1]{created_at,author,text}: + "2026-08-24T10:03:33.732Z","harness:claude-code","CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix." +files[3]{path,scope,note}: package.json,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version .github/workflows/release.yml,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version -tests[1]{command,path,scope,note}: + scripts/verify-release-changelog-date.sh,project,"executable proof for the release workflow invocations, which the package.json changelog check never reaches" +tests[2]{command,path,scope,note}: "npm run changelog:check",package.json,project,regenerates the changelog and compares it; without the flag the heading date is stamped from the clock and the comparison fails on any day after generation + bash scripts/verify-release-changelog-date.sh,scripts/verify-release-changelog-date.sh,project,"exercises the release workflow path that npm run changelog:check does not: asserts every generator invocation in every tracked file carries the flag, and shows the same probe version yielding its own calendar date with the flag and today's date without it" body: "" diff --git a/scripts/verify-release-changelog-date.sh b/scripts/verify-release-changelog-date.sh new file mode 100755 index 0000000..06eabdc --- /dev/null +++ b/scripts/verify-release-changelog-date.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Proves an untagged release's changelog heading comes from the calendar +# version rather than from the clock, and that every generator invocation in +# this package asks for that. +# +# Why this exists separately from `npm run changelog:check`: that script only +# exercises the package.json invocation. `.github/workflows/release.yml` calls +# pm-changelog directly. If either side lost --date-from-version the two would +# disagree during a release -- one heading derived from the clock, the other +# from the version -- and the release would fail on the divergence rather than +# on the stale date the flag exists to remove. +set -euo pipefail +cd "$(dirname "$0")/.." +status=0 + +# 1. Static invariant: every generator invocation, in every tracked file, asks +# for the version-derived date. Enumerated rather than assumed, because the +# invocation lives in more places than the scripts named changelog*. +while IFS= read -r file; do + sites=$(grep -c -- --release-version-from-package "$file") + flagged=$(grep -c -- --date-from-version "$file" || true) + if [ "$sites" -gt "$flagged" ]; then + echo "FAIL: $file has $sites generator invocation(s) but only $flagged carry --date-from-version" >&2 + status=1 + else + echo "ok - $file: $sites generator invocation(s), all flagged" + fi +done < <(git ls-files | xargs grep -l -- --release-version-from-package 2>/dev/null \ + | grep -vE '\.toon$|\.jsonl$|^CHANGELOG') + +# 2. Behavioural: the flag is what makes the date version-derived. A probe +# version deliberately unequal to today, so a clock-derived heading and a +# version-derived heading cannot coincide and the assertion discriminates. +probe=2026.1.2 +expected="## ${probe} - 2026-01-02" +today_heading="## ${probe} - $(date -u +%Y-%m-%d)" +bin=./node_modules/.bin/pm-changelog +[ -x "$bin" ] || bin="npx pm-changelog" +# The generator refuses a truncated workspace read rather than silently +# omitting entries, so the unbounded controls the real scripts pass are +# required here too. +common=(--pm-root .agents/pm --stdout --pm-bin ./node_modules/.bin/pm + --pm-arg=--output-budget --pm-arg=unbounded + --pm-arg=--output-limit --pm-arg=unbounded + --release-version "$probe") + +with=$($bin "${common[@]}" --date-from-version 2>/dev/null | grep -m1 '^## ' || true) +without=$($bin "${common[@]}" 2>/dev/null | grep -m1 '^## ' || true) + +if [ "$with" != "$expected" ]; then + echo "FAIL: with --date-from-version expected '$expected', got '$with'" >&2; status=1 +else + echo "ok - with the flag the heading is version-derived: $with" +fi +if [ "$without" != "$today_heading" ]; then + echo "note - without the flag the heading was '$without' (expected the clock-derived '$today_heading'); the flag's effect is still asserted above" +else + echo "ok - without the flag the heading is clock-derived: $without (this is the defect the flag removes)" +fi +exit $status From 79d0bd8b17db59d42a5a7dab573efebc1f7423b0 Mon Sep 17 00:00:00 2001 From: SteveBot <1153461+unbraind@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:05:46 +0200 Subject: [PATCH 3/3] test(changelog): prove the release-workflow path, which changelog:check never reaches CodeRabbit was right that the linked test only covered the package.json invocation. release.yml calls pm-changelog directly, and nothing exercised those calls. The static half enumerates every tracked file holding a generator invocation and asserts each file's flagged count is not below its site count. Verified non-vacuous by removing the flag from one of two release.yml sites: it reports 1 of 2 and exits non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2 -- chosen so its calendar date cannot coincide with today -- and asserts '2026.1.2 - 2026-01-02' with the flag against '2026.1.2 - 2026-08-24' without it. The test therefore states the defect, not just the fix. --- .agents/pm/history/pm-github-m8vj.jsonl | 1 + .agents/pm/issues/pm-github-m8vj.toon | 5 +++-- scripts/verify-release-changelog-date.sh | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.agents/pm/history/pm-github-m8vj.jsonl b/.agents/pm/history/pm-github-m8vj.jsonl index aa2ff9e..2562915 100644 --- a/.agents/pm/history/pm-github-m8vj.jsonl +++ b/.agents/pm/history/pm-github-m8vj.jsonl @@ -5,3 +5,4 @@ {"ts":"2026-08-24T10:03:32.888Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"bash scripts/verify-release-changelog-date.sh","path":"scripts/verify-release-changelog-date.sh","scope":"project","note":"exercises the release workflow path that npm run changelog:check does not: asserts every generator invocation in every tracked file carries the flag, and shows the same probe version yielding its own calendar date with the flag and today's date without it"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:32.888Z"}],"before_hash":"69f77d3a5ae50ea79a9a842e87334f495ffe39621090444ae4b22fad198442f7","after_hash":"b64266e44f22744ea47d754bbb56149b69cc30665202a7fa44c9008ee563ee2e","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} {"ts":"2026-08-24T10:03:33.311Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/2","value":{"path":"scripts/verify-release-changelog-date.sh","scope":"project","note":"executable proof for the release workflow invocations, which the package.json changelog check never reaches"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:33.311Z"}],"before_hash":"b64266e44f22744ea47d754bbb56149b69cc30665202a7fa44c9008ee563ee2e","after_hash":"80ccd79861a5cf611f73a0852db5cdaaf80e084ee496c6cdba9a61636d58de5d","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} {"ts":"2026-08-24T10:03:33.732Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:03:33.732Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-08-24T10:03:33.732Z","author":"harness:claude-code","text":"CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix."}]}],"before_hash":"80ccd79861a5cf611f73a0852db5cdaaf80e084ee496c6cdba9a61636d58de5d","after_hash":"9e1140c4269961557871f0cbcd6e87b26f7456712915da4596a184c4bb765508","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} +{"ts":"2026-08-24T10:05:45.234Z","author":"harness:claude-code","author_source":"asserted","agent_harness":"claude-code","agent_instance":"365e646596cb83e6152b34c6","agent_provenance":{"model":null,"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-08-24T10:05:45.234Z","author":"harness:claude-code","text":"CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-24T10:05:45.234Z"}],"before_hash":"9e1140c4269961557871f0cbcd6e87b26f7456712915da4596a184c4bb765508","after_hash":"d1057927d8aa62d5d4af3f1ef64fb62badb3a4784ad3b8976ea11858b1032997","item_hash_version":2,"context":{"agent_provenance_outcomes":{"model":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"failed","reason":"resolver_failed","resolver":"claude_session_file","rule_version":"v1"}}}} diff --git a/.agents/pm/issues/pm-github-m8vj.toon b/.agents/pm/issues/pm-github-m8vj.toon index eee4209..74d06fe 100644 --- a/.agents/pm/issues/pm-github-m8vj.toon +++ b/.agents/pm/issues/pm-github-m8vj.toon @@ -6,10 +6,11 @@ status: open priority: 1 tags[3]: changelog,gates,release created_at: "2026-08-24T09:40:00.486Z" -updated_at: "2026-08-24T10:03:33.732Z" +updated_at: "2026-08-24T10:05:45.234Z" author: "harness:claude-code" -comments[1]{created_at,author,text}: +comments[2]{created_at,author,text}: "2026-08-24T10:03:33.732Z","harness:claude-code","CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix." + "2026-08-24T10:05:45.234Z","harness:claude-code","CodeRabbit was right that the linked test only covered the package.json invocation and never reached the direct pm-changelog commands in the release workflow. Added an executable check that does both halves. The static half enumerates every tracked file containing a generator invocation and asserts each file's flagged count is not below its site count, which fails when any single site loses the flag; verified by removing the flag from one of two release.yml sites and watching it report one of two and exit non-zero. The behavioural half runs the generator twice on the same probe version 2026.1.2, deliberately chosen so its calendar date cannot coincide with today, and asserts the heading is 2026.1.2 - 2026-01-02 with the flag and 2026.1.2 - 2026-08-24 without it, so the test states the defect rather than merely asserting the fix." files[3]{path,scope,note}: package.json,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version .github/workflows/release.yml,project,every pm-changelog invocation here now derives the no-tag release date from the calendar version diff --git a/scripts/verify-release-changelog-date.sh b/scripts/verify-release-changelog-date.sh index 06eabdc..e776951 100755 --- a/scripts/verify-release-changelog-date.sh +++ b/scripts/verify-release-changelog-date.sh @@ -25,8 +25,13 @@ while IFS= read -r file; do else echo "ok - $file: $sites generator invocation(s), all flagged" fi -done < <(git ls-files | xargs grep -l -- --release-version-from-package 2>/dev/null \ - | grep -vE '\.toon$|\.jsonl$|^CHANGELOG') +done < <(git ls-files -- package.json '.github/workflows/*.yml' '.github/workflows/*.yaml' \ + | xargs grep -l -- --release-version-from-package 2>/dev/null) +# Scope note: only files that EXECUTE the generator are in scope -- package.json +# scripts and the workflows. Source, docs, dist and test fixtures may mention +# the same flags while describing or exercising them, and holding those to an +# "every mention is flagged" rule would be a false positive (it is, in +# pm-changelog's own repository, which documents both spellings on purpose). # 2. Behavioural: the flag is what makes the date version-derived. A probe # version deliberately unequal to today, so a clock-derived heading and a @@ -34,8 +39,11 @@ done < <(git ls-files | xargs grep -l -- --release-version-from-package 2>/dev/n probe=2026.1.2 expected="## ${probe} - 2026-01-02" today_heading="## ${probe} - $(date -u +%Y-%m-%d)" -bin=./node_modules/.bin/pm-changelog -[ -x "$bin" ] || bin="npx pm-changelog" +# In pm-changelog's own repository the generator is the build output, not a +# dependency, so resolve it in that order rather than assuming node_modules. +if [ -x ./node_modules/.bin/pm-changelog ]; then bin="./node_modules/.bin/pm-changelog" +elif [ -f ./dist/cli.js ]; then bin="node ./dist/cli.js" +else bin="npx pm-changelog"; fi # The generator refuses a truncated workspace read rather than silently # omitting entries, so the unbounded controls the real scripts pass are # required here too.