diff --git a/.agents/pm/history/pm-github-m8vj.jsonl b/.agents/pm/history/pm-github-m8vj.jsonl new file mode 100644 index 0000000..2562915 --- /dev/null +++ b/.agents/pm/history/pm-github-m8vj.jsonl @@ -0,0 +1,8 @@ +{"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"}}}} +{"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 new file mode 100644 index 0000000..74d06fe --- /dev/null +++ b/.agents/pm/issues/pm-github-m8vj.toon @@ -0,0 +1,21 @@ +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-24T10:05:45.234Z" +author: "harness:claude-code" +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 + 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/.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", diff --git a/scripts/verify-release-changelog-date.sh b/scripts/verify-release-changelog-date.sh new file mode 100755 index 0000000..e776951 --- /dev/null +++ b/scripts/verify-release-changelog-date.sh @@ -0,0 +1,68 @@ +#!/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 -- 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 +# 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)" +# 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. +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