diff --git a/.agents/pm/chores/pm-context-h04z.toon b/.agents/pm/chores/pm-context-h04z.toon new file mode 100644 index 0000000..9f5ee23 --- /dev/null +++ b/.agents/pm/chores/pm-context-h04z.toon @@ -0,0 +1,26 @@ +id: pm-context-h04z +title: Certify pm CLI 2026.9.23 and adopt the guarded pm-ops merge-driver launcher +description: "Fleet wave 2026-09-25 (companion epic pm-cli-website-5s6z, launcher rollout pm-cli-website-xy19). Pins the toolchain to 2026.9.23 and replaces the prepare hook with the launcher template pm-ops ships: it imports nothing from pm-ops, so a production install of a clone (npm ci --omit=dev) skips with one notice instead of failing, while a stale or broken pm-ops still fails the install. A test keeps the copy byte-identical to the pinned template, whose branches pm-ops covers with real fixtures." +type: Chore +status: closed +priority: 1 +tags[4]: certify,merge-driver,multi-agent,pm-cli-2026.9.23 +created_at: "2026-09-25T08:11:20.572Z" +updated_at: "2026-09-25T09:10:04.997Z" +closed_at: "2026-09-25T08:13:54.197Z" +completed_at: "2026-09-25T08:13:54.197Z" +author: fleet-wave-script +acceptance_criteria: "package.json and package-lock.json pin pm-cli 2026.9.23 and pm-ops 2026.9.23 (pm-changelog 2026.9.23 where used); scripts/prepare-merge-driver.ts is byte-identical to the pinned pm-ops template, enforced by a test; CI runs pm health --strict-exit --require-merge-drivers and release:check exits 0" +comments[2]{created_at,author,text}: + "2026-09-25T08:31:53.027Z",fleet-wave-script,"Review round 1 (Sourcery and Greptile on the wave PRs): the launcher test now builds isolated git-init checkouts, so drivers registered by this repository's own npm ci cannot mask a launcher that registers none; it asserts the full driver set .gitattributes declares, and it covers the omit-dev skip, stale pm-ops, failing and killed installer branches. The launcher stays in the coverage sources where it was, covered through the child processes as in pm-ops." + "2026-09-25T09:10:04.997Z",fleet-wave-script,"Review round 1 (Greptile): the README now describes the guarded launcher as it behaves, handing over to pm-ops's installer, skipping with one notice when pm-ops is absent and failing on a stale or broken pm-ops." +files[5]{path,scope}: + package-lock.json,project + package.json,project + README.md,project + scripts/prepare-merge-driver.ts,project + test/prepare-merge-driver.test.ts,project +tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + "npm run release:check",project,fleet-wave-script,"2026-09-25T08:11:44.597Z",local_mutation,certify-pm-cli-2026-9-23-and-roll-out-guarded-merge-driver-launcher +close_reason: "Pins: @unbrained/pm-cli 2026.9.21 -> 2026.9.23, pm-ops 2026.9.18 -> 2026.9.23, pm-changelog 2026.9.18 -> 2026.9.23 (package.json and package-lock.json). Launcher: pm-ops template copied unchanged, replacing the static-import launcher; test/prepare-merge-driver.test.ts proves byte identity and a real driver install. git config lists the pm merge drivers; the CI health block runs green with --require-merge-drivers; release:check exits 0." +body: "" diff --git a/.agents/pm/history/pm-context-h04z.jsonl b/.agents/pm/history/pm-context-h04z.jsonl new file mode 100644 index 0000000..8f8ca12 --- /dev/null +++ b/.agents/pm/history/pm-context-h04z.jsonl @@ -0,0 +1,13 @@ +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:20.572Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-context-h04z"},{"op":"add","path":"/metadata/title","value":"Certify pm CLI 2026.9.23 and adopt the guarded pm-ops merge-driver launcher"},{"op":"add","path":"/metadata/description","value":"Fleet wave 2026-09-25 (companion epic pm-cli-website-5s6z, launcher rollout pm-cli-website-xy19). Pins the toolchain to 2026.9.23 and replaces the prepare hook with the launcher template pm-ops ships: it imports nothing from pm-ops, so a production install of a clone (npm ci --omit=dev) skips with one notice instead of failing, while a stale or broken pm-ops still fails the install. A test keeps the copy byte-identical to the pinned template, whose branches pm-ops covers with real fixtures."},{"op":"add","path":"/metadata/type","value":"Chore"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":1},{"op":"add","path":"/metadata/tags","value":["certify","merge-driver","multi-agent","pm-cli-2026.9.23"]},{"op":"add","path":"/metadata/created_at","value":"2026-09-25T08:11:20.572Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-09-25T08:11:20.572Z"},{"op":"add","path":"/metadata/author","value":"fleet-wave-script"},{"op":"add","path":"/metadata/acceptance_criteria","value":"package.json and package-lock.json pin pm-cli 2026.9.23 and pm-ops 2026.9.23 (pm-changelog 2026.9.23 where used); scripts/prepare-merge-driver.ts is byte-identical to the pinned pm-ops template, enforced by a test; CI runs pm health --strict-exit --require-merge-drivers and release:check exits 0"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"7e2adc82b41a42ff2a257530ffa967e30f787f8477664ebc5a7c5e61aeb50581","item_hash_version":3,"message":"","context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"4635da7ee10bf5ab15e40b97c29fb3853706e0ed20e1a2fad5c3c319298af120"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:22.930Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:22.930Z"},{"op":"add","path":"/metadata/assignee","value":"fleet-wave-script"},{"op":"add","path":"/metadata/claim_principal","value":"fleet-wave-script"}],"before_hash":"7e2adc82b41a42ff2a257530ffa967e30f787f8477664ebc5a7c5e61aeb50581","after_hash":"592176ceac843e5dbf6e9dc16bb59466be6dfce29ddf7e925e41739ee94e7fc8","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"8ea2348a3f9a275159eb5e143504a01ddb428854c608fa1668285a80356cf15f"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:25.454Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:25.454Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"592176ceac843e5dbf6e9dc16bb59466be6dfce29ddf7e925e41739ee94e7fc8","after_hash":"60d4e08ecf5248184e6b161b664e69f74af33352831bd2df541de42aa8603106","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"2b0ed412698f081ab7e29c974cc13f4f240e79d0b1841a9a4240b0471bd7887a"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:29.796Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:29.796Z"},{"op":"add","path":"/metadata/files","value":[{"path":"README.md","scope":"project"}]}],"before_hash":"60d4e08ecf5248184e6b161b664e69f74af33352831bd2df541de42aa8603106","after_hash":"5a7a987c27946124e7472f48d3904b51a2b35de66f950c24138f293f17b1a76c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"34bbc708f94d1b26284c06b477f855888f9a20c0da01649434e689c0db8cf6d0"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:32.775Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/0/path","value":"package-lock.json"},{"op":"add","path":"/metadata/files/1","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:32.775Z"}],"before_hash":"5a7a987c27946124e7472f48d3904b51a2b35de66f950c24138f293f17b1a76c","after_hash":"87492661838a81f0944760cc5547abba0096f763a7b41554e083147d19cd323f","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"2e6b6f87f087c9d6ea3fe7f25ed2426409cf5c51e2d34ca2d2b5db6a3d631938"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:35.481Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/path","value":"package.json"},{"op":"add","path":"/metadata/files/2","value":{"path":"README.md","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:35.481Z"}],"before_hash":"87492661838a81f0944760cc5547abba0096f763a7b41554e083147d19cd323f","after_hash":"bdc9ae783dfb9958cf4bac17104bc634a2c90c0bbe11a49c5636d6a3ddcba3af","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"d72eec1ae86f5748d076257f5a554179317e9b58eaf1783b9ed8fea7d0c906ef"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:38.353Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/3","value":{"path":"scripts/prepare-merge-driver.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:38.353Z"}],"before_hash":"bdc9ae783dfb9958cf4bac17104bc634a2c90c0bbe11a49c5636d6a3ddcba3af","after_hash":"9fa4d41e87925a2ea87535c6c7b6e54042e48c4b71b2151bbaf0a5e4f8ac9bb6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"08ebdd4f80d8d106c66b1d5a05a94c38392c6ebdcb37aaddb6531554e294f544"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:41.545Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/4","value":{"path":"test/prepare-merge-driver.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:41.545Z"}],"before_hash":"9fa4d41e87925a2ea87535c6c7b6e54042e48c4b71b2151bbaf0a5e4f8ac9bb6","after_hash":"9c2ca83c485eb60a96f07ea16dac033c0fde5eaf418c079d109b1d4e0a8a9d1c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"dd35bc84136048d8dace9ab5275ebc6e848b8baded3b970335a794a82ab93e76"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:11:44.750Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:11:44.750Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"npm run release:check","scope":"project","provenance":{"author":"fleet-wave-script","created_at":"2026-09-25T08:11:44.597Z","source_kind":"local_mutation","source_ref":"certify-pm-cli-2026-9-23-and-roll-out-guarded-merge-driver-launcher"}}]}],"before_hash":"9c2ca83c485eb60a96f07ea16dac033c0fde5eaf418c079d109b1d4e0a8a9d1c","after_hash":"815142d807ff03fedc1bd664ef1abfdf934bcc1e540bfc6f5bdb3233983a72c3","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"36a7bc7af88b4da0850dfff00856599b9bc67e4456ce299431137b7e6bde6319"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:13:54.261Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:13:54.261Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-09-25T08:13:54.197Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-09-25T08:13:54.197Z"},{"op":"add","path":"/metadata/close_reason","value":"Pins: @unbrained/pm-cli 2026.9.21 -> 2026.9.23, pm-ops 2026.9.18 -> 2026.9.23, pm-changelog 2026.9.18 -> 2026.9.23 (package.json and package-lock.json). Launcher: pm-ops template copied unchanged, replacing the static-import launcher; test/prepare-merge-driver.test.ts proves byte identity and a real driver install. git config lists the pm merge drivers; the CI health block runs green with --require-merge-drivers; release:check exits 0."}],"before_hash":"815142d807ff03fedc1bd664ef1abfdf934bcc1e540bfc6f5bdb3233983a72c3","after_hash":"41af99cd66b3b73b44057feb25bad9fee048e4bc55a6259cc77f8e307cdc82fe","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"aba66b48a0464d938b69fb4bff3ab1e3ee2b5d67476b9ef8db698f1ff481dc49"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:13:55.996Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":{"value":"implementer","source":"argv"},"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:13:55.996Z"}],"before_hash":"41af99cd66b3b73b44057feb25bad9fee048e4bc55a6259cc77f8e307cdc82fe","after_hash":"828c8a577a4481aca2bcdcfa5052475d25279e9e4e180ac377e6f5ce057b3618","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"859485fabd4c79a8e852815a159d2e2fc1ba7375819ef1c7ade75ea6b6aa2830"} +{"hash_algorithm":"sha256","ts":"2026-09-25T08:31:53.027Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T08:31:53.027Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-09-25T08:31:53.027Z","author":"fleet-wave-script","text":"Review round 1 (Sourcery and Greptile on the wave PRs): the launcher test now builds isolated git-init checkouts, so drivers registered by this repository's own npm ci cannot mask a launcher that registers none; it asserts the full driver set .gitattributes declares, and it covers the omit-dev skip, stale pm-ops, failing and killed installer branches. The launcher stays in the coverage sources where it was, covered through the child processes as in pm-ops."}]}],"before_hash":"828c8a577a4481aca2bcdcfa5052475d25279e9e4e180ac377e6f5ce057b3618","after_hash":"78a07136148a74d1ffbe1c9a2fc4a21f22a7a6c96e9762a763126c744246b965","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"47cb25ffed8f42deba73fbc6ba2f4d54c4e1bf5c37995211732ed45534a081e8"} +{"hash_algorithm":"sha256","ts":"2026-09-25T09:10:04.997Z","author":"fleet-wave-script","author_source":"asserted","agent_harness":"claude-code","agent_model":"claude-opus-5-5","agent_model_source":"probe","agent_instance":"e0d15f3cda6eb998be619306","agent_provenance":{"model":{"value":"claude-opus-5-5","source":"probe"},"effort":{"value":"xhigh","source":"environment"},"role":null,"topic":null,"version":{"value":"2.1.282","source":"probe"}},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-09-25T09:10:04.997Z","author":"fleet-wave-script","text":"Review round 1 (Greptile): the README now describes the guarded launcher as it behaves, handing over to pm-ops's installer, skipping with one notice when pm-ops is absent and failing on a stale or broken pm-ops."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-09-25T09:10:04.997Z"}],"before_hash":"78a07136148a74d1ffbe1c9a2fc4a21f22a7a6c96e9762a763126c744246b965","after_hash":"4b95fd0025d96fb75cbe76661db3983bddb7140740d067160cc727d067903fd5","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"},"version":{"status":"resolved","resolver":"claude_session_file","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"a1385096bab0054f550fd93441774a1b2e33257559f662994a81ef963589bc42"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1add7..ecdd154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Other + +- Certify pm CLI 2026.9.23 and adopt the guarded pm-ops merge-driver launcher ([pm-context-h04z](https://github.com/unbraind/pm-context/blob/main/.agents/pm/chores/pm-context-h04z.toon)) + ## 2026.9.22 - 2026-09-22 ### Fixed diff --git a/README.md b/README.md index d8ca217..2902b73 100644 --- a/README.md +++ b/README.md @@ -173,9 +173,7 @@ sent to another agent, or attached to a pull request — and, with This repo tracks its project management in `.agents/pm/` and ships a committed `.gitattributes` that maps those tracker artifacts to pm-cli's field-aware Git merge drivers, so concurrent-branch tracker edits merge cleanly instead of hard-conflicting. The driver **definitions** live in -per-clone Git config; `npm install` / `npm ci` wires them automatically via the `prepare` script (a portable Node guard, `scripts/prepare-merge-driver.ts`, a thin launcher over `pm-ops/merge-driver`: it runs -`pm merge install` only when the `pm` CLI is on `PATH`, and no-ops cleanly when `pm` is absent. Registry installs of this package never run `prepare`; a production install of a clone (`npm ci --omit=dev`) omits `pm-ops` too, so it must pass `--ignore-scripts`; being Node-based it behaves identically -on POSIX shells and Windows `cmd.exe`). To (re)run manually: `npm run merge:install`. +per-clone Git config; `npm install` / `npm ci` wires them automatically via the `prepare` script, `scripts/prepare-merge-driver.ts`: the launcher template pm-ops ships, copied unchanged, which a test compares byte for byte with the pinned template. It runs pm-ops's installer, which calls `pm merge install` when the `pm` CLI is on `PATH` and skips with a notice when it is not. A production install of a clone (`npm ci --omit=dev`) has no `pm-ops`, so the launcher skips with one notice, while a stale or broken `pm-ops` fails the install. Registry installs of this package never run `prepare`. Being Node-based, it behaves identically on POSIX shells and Windows `cmd.exe`. To (re)run manually: `npm run merge:install`. After merging a branch that touched `.agents/pm/`, reconcile any residual history-hash drift with **`pm merge reconcile`** (pm-cli ≥ 2026.7.22): preview with `pm merge reconcile --dry-run`, apply with diff --git a/package-lock.json b/package-lock.json index 3e68dd0..cd1e278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "license": "MIT", "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "2026.9.21", + "@unbrained/pm-cli": "2026.9.23", "c8": "^12.0.0", - "pm-changelog": "2026.9.18", - "pm-ops": "2026.9.18", + "pm-changelog": "2026.9.23", + "pm-ops": "2026.9.23", "typescript": "^7.0.2", "yaml": "2.9.1" }, @@ -726,9 +726,9 @@ } }, "node_modules/@unbrained/pm-cli": { - "version": "2026.9.21", - "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.9.21.tgz", - "integrity": "sha512-HPgGm/pU81Avtty9lVYqYh0jxKzNyQjHnvwQrRjSYNHaVQeJ3xNM/VV9i0CCPL0zLxRjdriYj/PnNDIMC2Ur2Q==", + "version": "2026.9.23", + "resolved": "https://registry.npmjs.org/@unbrained/pm-cli/-/pm-cli-2026.9.23.tgz", + "integrity": "sha512-tL1u+NDa6UkJyJaJR3j7noqi6byFlQdgWSZsFDG/k6k31LTBCQJNfX42EhgBE8kR100O61DleJSaMCtPq+LT9g==", "dev": true, "license": "MIT", "dependencies": { @@ -740,7 +740,7 @@ "fast-json-patch": "^3.1.1", "npm-package-arg": "^13.0.2", "tar": "7.5.22", - "yaml": "2.9.1" + "yaml": "^2.9.1" }, "bin": { "pm": "dist/cli.js", @@ -1431,9 +1431,9 @@ } }, "node_modules/pm-changelog": { - "version": "2026.9.18", - "resolved": "https://registry.npmjs.org/pm-changelog/-/pm-changelog-2026.9.18.tgz", - "integrity": "sha512-UJekN8TZUk/Q9Ri7pMl+EEtPqaGG5ePs/3/hS5JCx7w78dZAamfH7lUA+wUjJtoyKzLMX3XxTkCWpe61CquXdg==", + "version": "2026.9.23", + "resolved": "https://registry.npmjs.org/pm-changelog/-/pm-changelog-2026.9.23.tgz", + "integrity": "sha512-hrHFbRgz/LyiCBN9ic0I1C//SRL/zjU5fxcyCeCAnM2qTrz14A7f9ayMXeiu/ChDkVFMxfz8Ps2j3TuUG6K1QA==", "dev": true, "license": "MIT", "bin": { @@ -1447,9 +1447,9 @@ } }, "node_modules/pm-ops": { - "version": "2026.9.18", - "resolved": "https://registry.npmjs.org/pm-ops/-/pm-ops-2026.9.18.tgz", - "integrity": "sha512-x4KWL0Y9y6Sz2Hw9LDJmZPoZmbruKJ1x9Z9QCq2/W/yAG5Lujl5HiyrE9Er6Ne7gKFWJp3kxywYqUWDnLHEyMQ==", + "version": "2026.9.23", + "resolved": "https://registry.npmjs.org/pm-ops/-/pm-ops-2026.9.23.tgz", + "integrity": "sha512-FglZHOXjsuG8WWf9Ca90/IX1u4ZCxVTvHpwMaiA0TAWBx/lLalM2Ic12gtWZvm3OAJsMMuXvfRBHWxwYLzESsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1465,7 +1465,7 @@ "@unbrained/pm-cli": ">=2026.8.20", "eslint": "^10.10.0", "fast-glob": "^3.3.3", - "jscpd": "^4.3.0" + "jscpd": ">=4.3.0 <6.0.0" }, "peerDependenciesMeta": { "@babel/eslint-parser": { diff --git a/package.json b/package.json index e151660..615f935 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,10 @@ }, "devDependencies": { "@types/node": "^26.1.1", - "@unbrained/pm-cli": "2026.9.21", + "@unbrained/pm-cli": "2026.9.23", "c8": "^12.0.0", - "pm-changelog": "2026.9.18", - "pm-ops": "2026.9.18", + "pm-changelog": "2026.9.23", + "pm-ops": "2026.9.23", "typescript": "^7.0.2", "yaml": "2.9.1" }, diff --git a/scripts/prepare-merge-driver.ts b/scripts/prepare-merge-driver.ts index d513e7f..66e512e 100644 --- a/scripts/prepare-merge-driver.ts +++ b/scripts/prepare-merge-driver.ts @@ -1,10 +1,42 @@ /** - * npm `prepare` hook that installs pm's field-aware Git merge drivers. + * npm `prepare` hook that registers pm's field-aware Git merge drivers. * - * Git never clones `.git/config`, so every clone must register the drivers that - * `.gitattributes` declares. The canonical implementation lives in - * `pm-ops/merge-driver`; this file stays a thin launcher over it. + * Git never clones `.git/config`, so every clone must register the drivers + * `.gitattributes` declares. The installer lives in the devDependency pm-ops, + * which an `npm install --omit=dev` checkout does not have. This launcher + * therefore imports nothing from pm-ops: it resolves the installer entry from + * the package root and runs it in a child process. Only a missing pm-ops package skips, with one + * notice; any other resolution failure (for example a pm-ops too old to export + * the entry) and any installer failure fail the install. + * + * Canonical copy: `pm-ops/templates/prepare-merge-driver.ts`. Copy it + * unchanged to `scripts/prepare-merge-driver.ts`. */ -import { runPrepareMergeDriver } from "pm-ops/merge-driver"; -process.exitCode = runPrepareMergeDriver(); +import { spawnSync } from "node:child_process"; +import { createRequire } from "node:module"; +import { join } from "node:path"; + +// npm runs `prepare` from the package root, so pm-ops is resolved from there. +const resolver = createRequire(join(process.cwd(), "package.json")); +let installer: string | undefined; +try { + installer = resolver.resolve("pm-ops/merge-driver/prepare"); +} catch (error) { + // Only an absent pm-ops package may skip. Probing its package.json tells that + // apart from an installed pm-ops that cannot serve the entry (exports without + // it, no exports map, a missing file): those resolve or fail differently, and + // the original error is rethrown. + let packagePresent = true; + try { + resolver.resolve("pm-ops/package.json"); + } catch (probe) { + packagePresent = !(probe instanceof Error && "code" in probe && probe.code === "MODULE_NOT_FOUND"); + } + if (packagePresent) throw error; +} +if (installer === undefined) { + console.error("pm-ops is not installed (omit-dev install); skipping merge-driver install"); +} else { + process.exitCode = spawnSync(process.execPath, [installer], { stdio: "inherit" }).status ?? 1; +} diff --git a/test/prepare-merge-driver.test.ts b/test/prepare-merge-driver.test.ts new file mode 100644 index 0000000..c3bb230 --- /dev/null +++ b/test/prepare-merge-driver.test.ts @@ -0,0 +1,121 @@ +/** + * Tests for the npm `prepare` hook `scripts/prepare-merge-driver.ts`. + * + * The hook must stay the canonical pm-ops launcher byte for byte, and it is + * exercised the way npm runs it: as the entry point of a child process whose + * working directory is a consumer checkout. Every checkout is a fresh + * `git init` with its own local config, so the drivers this repository's own + * `npm ci` registered cannot mask a launcher that registers none. + */ +import assert from "node:assert/strict"; +import { spawnSync, type SpawnSyncReturns } from "node:child_process"; +import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { delimiter, join } from "node:path"; +import test, { after } from "node:test"; + +// npm runs tests from the package root, which is also where it runs `prepare`. +const root = process.cwd(); +const launcher = join(root, "scripts", "prepare-merge-driver.ts"); +const hostPath = `${join(root, "node_modules", ".bin")}${delimiter}${process.env.PATH ?? ""}`; +const scratch = mkdtempSync(join(tmpdir(), "prepare-merge-driver-")); +after(() => rmSync(scratch, { recursive: true, force: true })); + +// The fixtures use POSIX stub executables and directory symlinks, like pm-ops's own launcher suite. +const posixOnly = { skip: process.platform === "win32" }; + +/** Every merge driver `.gitattributes` asks Git to use, e.g. `pm-history` from `merge=pm-history`. */ +const declaredDrivers = [ + ...new Set([...readFileSync(join(root, ".gitattributes"), "utf8").matchAll(/\bmerge=([\w-]+)/g)].map((match) => match[1])), +].sort(); + +/** + * Create a consumer checkout: a fresh Git repository carrying this + * repository's `.gitattributes` and tracker settings. `pmOps` selects what + * `node_modules/pm-ops` is: absent (an omit-dev install), the pinned package, + * or a stale pm-ops whose exports predate the launcher entry. + */ +function checkout(name: string, pmOps: "absent" | "pinned" | "stale"): string { + const directory = join(scratch, name); + mkdirSync(join(directory, ".agents", "pm"), { recursive: true }); + assert.equal(spawnSync("git", ["init", "-q"], { cwd: directory }).status, 0); + writeFileSync(join(directory, "package.json"), JSON.stringify({ name, type: "module" })); + copyFileSync(join(root, ".gitattributes"), join(directory, ".gitattributes")); + copyFileSync(join(root, ".agents", "pm", "settings.json"), join(directory, ".agents", "pm", "settings.json")); + if (pmOps === "pinned") { + mkdirSync(join(directory, "node_modules")); + symlinkSync(join(root, "node_modules", "pm-ops"), join(directory, "node_modules", "pm-ops"), "dir"); + } + if (pmOps === "stale") { + mkdirSync(join(directory, "node_modules", "pm-ops"), { recursive: true }); + writeFileSync( + join(directory, "node_modules", "pm-ops", "package.json"), + JSON.stringify({ name: "pm-ops", type: "module", exports: { "./merge-driver": "./merge-driver.js" } }), + ); + } + return directory; +} + +/** Put a stub `pm` that runs `body` then exits with `status` alone on a fresh PATH directory. */ +function stubPm(name: string, status: number, body = ""): string { + const bin = join(scratch, `${name}-bin`); + mkdirSync(bin); + writeFileSync(join(bin, "pm"), `#!/bin/sh\n${body}\nexit ${status}\n`); + chmodSync(join(bin, "pm"), 0o755); + return bin; +} + +/** Run the launcher as npm's `prepare` hook would: as the entry point, from `cwd`, with `path` as PATH. */ +function prepare(cwd: string, path: string): SpawnSyncReturns { + return spawnSync(process.execPath, [launcher], { cwd, encoding: "utf8", env: { ...process.env, PATH: path } }); +} + +/** The merge drivers registered in a checkout's LOCAL Git config, by name. */ +function registeredDrivers(cwd: string): string[] { + const config = spawnSync("git", ["config", "--local", "--name-only", "--get-regexp", "^merge\\..*\\.driver$"], { + cwd, + encoding: "utf8", + }); + // git exits 1 when no key matches, which is a genuine "none registered". + assert.ok(config.status === 0 || config.status === 1, config.stderr); + return config.stdout.split("\n").filter(Boolean).map((key) => key.split(".")[1]).sort(); +} + +test("the prepare launcher is the unmodified pm-ops template", () => { + const canonical = readFileSync(join(root, "node_modules", "pm-ops", "templates", "prepare-merge-driver.ts"), "utf8"); + assert.equal(readFileSync(launcher, "utf8"), canonical); +}); + +test("a full install registers every merge driver .gitattributes declares", posixOnly, () => { + const directory = checkout("full", "pinned"); + assert.deepEqual(registeredDrivers(directory), []); + const result = prepare(directory, hostPath); + assert.equal(result.status, 0, result.stderr); + assert.ok(declaredDrivers.length > 0, ".gitattributes declares no pm merge drivers"); + assert.deepEqual(registeredDrivers(directory), declaredDrivers); +}); + +test("an omit-dev checkout without pm-ops skips with one notice and registers nothing", posixOnly, () => { + const directory = checkout("omit-dev", "absent"); + const result = prepare(directory, hostPath); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.stderr, "pm-ops is not installed (omit-dev install); skipping merge-driver install\n"); + assert.deepEqual(registeredDrivers(directory), []); +}); + +test("a pm-ops too old to export the launcher entry fails the install", posixOnly, () => { + const result = prepare(checkout("stale", "stale"), hostPath); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /ERR_PACKAGE_PATH_NOT_EXPORTED/); +}); + +test("a failing pm merge install fails the install with the same status", posixOnly, () => { + const result = prepare(checkout("failing-pm", "pinned"), stubPm("failing-pm", 7)); + assert.equal(result.status, 7, result.stderr); +}); + +test("an installer killed by a signal fails the install instead of reporting success", posixOnly, () => { + // The stub's parent is the pm-ops installer process the launcher spawned. + const result = prepare(checkout("killed", "pinned"), stubPm("killed", 0, "kill -9 $PPID")); + assert.equal(result.status, 1, result.stderr); +});