diff --git a/.agents/pm/extensions/.managed-extensions.json b/.agents/pm/extensions/.managed-extensions.json index 9cb46f445..161d5d0c6 100644 --- a/.agents/pm/extensions/.managed-extensions.json +++ b/.agents/pm/extensions/.managed-extensions.json @@ -1,6 +1,6 @@ { "version": 1, - "updated_at": "2026-07-10T13:19:14.505Z", + "updated_at": "2026-07-10T21:12:15.248Z", "entries": [ { "name": "pm-changelog", diff --git a/.agents/pm/features/pm-114v.toon b/.agents/pm/features/pm-114v.toon index 8e8ee1a58..afb45161b 100644 --- a/.agents/pm/features/pm-114v.toon +++ b/.agents/pm/features/pm-114v.toon @@ -2,20 +2,36 @@ id: pm-114v title: "pm claim --next: atomically claim the next actionable item so parallel agents each get distinct work" description: "Companion to pm-8t5x. pm next + pm claim as separate steps race under parallel agents: all N agents get the same recommendation and N-1 fail their claim. Add an atomic recommend+claim with bounded retry over next-ranked candidates so each parallel agent receives a distinct item. Idea originated in the 2026-07-03 concurrency review (recorded inside pm-8t5x's fix sketch); promoted to its own tracked Feature by the 2026-07-04 metadata review." type: Feature -status: open +status: closed priority: 2 tags[3]: agent-ux,cli,concurrency created_at: "2026-07-04T19:20:15.349Z" -updated_at: "2026-07-10T16:50:47.380Z" +updated_at: "2026-07-10T21:11:59.596Z" +closed_at: "2026-07-10T21:11:58.702Z" author: maintainer-agent estimated_minutes: 240 acceptance_criteria: "pm claim --next claims a distinct item per parallel agent (stress-verified, zero double-grants); bounded candidate retry on already_claimed_by; output includes pm next rationale; wired across MCP/contracts/completion/docs per new-command checklist" parent: pm-dj98 risk: medium confidence: medium -dependencies[2]{id,kind,created_at,author,source_kind}: +resolution: Added atomic ranked claim-next selection with bounded conflict retries and if-available behavior. +expected_result: Parallel agents can claim distinct recommended work through the SDK-backed CLI without a list-then-claim race. +actual_result: "claim --next ranks actionable candidates, retries ownership conflicts, returns recommendation evidence, and passed concurrency and packed-install acceptance tests." +dependencies[1]{id,kind,created_at,author,source_kind}: pm-2muu,related,"2026-07-05T04:07:28.566Z",claude-code-agent,null - pm-8t5x,blocked_by,"2026-07-05T04:07:28.566Z",claude-code-agent,null -comments[1]{created_at,author,text}: +comments[4]{created_at,author,text}: "2026-07-10T16:50:47.379Z",maintainer-agent,"Unblocked as of 2026-07-10 verification: sole blocker pm-8t5x (atomic test-and-set claim) is closed, so the atomic substrate this command needs exists. Actionable now; pairs naturally with the parallel-agent work-distribution story and pm-yl6c/pm-hfg5 pm-next defects when scheduling." + "2026-07-10T17:51:48.842Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:29.986Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." + "2026-07-10T21:11:56.435Z",codex-wide-pm-sdk,"Review evidence: fixed all claim-next findings. --if-available now advances skipped held candidates and returns a non-failing skip only when all are held; explicit --author controls both ranking and mutation; id plus --next is rejected; zsh flags moved from update to claim. Focused suites passed and full coverage is 100/100/100/100 with 5,528 tests." +files[3]{path,scope,note}: + src/cli/commands/claim.ts,project,atomic ranked claim composition + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/commands/claim-command.spec.ts tests/unit/commands/next-command.spec.ts,project,300 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Addressed Greptile and CodeRabbit claim-next contract, author, XOR, and completion findings with full coverage." body: "## Context\nSplit out of pm-8t5x (claim test-and-set): once claim correctly CONFLICTs on held items, N parallel agents that each run `pm next` will all be recommended the SAME top item, all try to claim it, and N-1 will bounce. The missing primitive is an atomic \"give me my own item\".\n\n## Design sketch\n`pm claim --next` (or `pm next --claim`): compute the pm next recommendation and claim it in one operation; on already_claimed_by conflict, retry with the next-ranked candidate (bounded, e.g. 10 candidates) until one claim succeeds; return the claimed item with the same rationale payload pm next emits. Honors --if-available semantics naturally (skip held candidates instead of erroring). Requires pm-8t5x semantics (deps: blocked_by) and benefits from pm-2muu's lock wait.\n\n## Definition of done\nN-parallel `pm claim --next` stress: N distinct items claimed, zero double-grants; recommendation rationale preserved in output; MCP action + contracts + completion + docs wired per the new-command checklist (grep replicate every site); spec covers conflict-retry and exhausted-candidates paths." diff --git a/.agents/pm/history/pm-114v.jsonl b/.agents/pm/history/pm-114v.jsonl index fc23f93a5..cd3422a8c 100644 --- a/.agents/pm/history/pm-114v.jsonl +++ b/.agents/pm/history/pm-114v.jsonl @@ -4,3 +4,21 @@ {"ts":"2026-07-05T04:07:28.609Z","author":"claude-code-agent","op":"update","patch":[{"op":"add","path":"/metadata/dependencies/1/author","value":"claude-code-agent"},{"op":"replace","path":"/metadata/dependencies/1/created_at","value":"2026-07-05T04:07:28.566Z"},{"op":"replace","path":"/metadata/dependencies/1/kind","value":"blocked_by"},{"op":"replace","path":"/metadata/dependencies/1/id","value":"pm-8t5x"},{"op":"add","path":"/metadata/dependencies/0/author","value":"claude-code-agent"},{"op":"replace","path":"/metadata/dependencies/0/created_at","value":"2026-07-05T04:07:28.566Z"},{"op":"replace","path":"/metadata/dependencies/0/id","value":"pm-2muu"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-05T04:07:28.609Z"}],"before_hash":"8bb172bb0b813251ee16d36f68c062c029566bb5d7e907edab68a3cb5b24764d","after_hash":"08ef36d1ee7d87f0898884be76e90eba95c9cc00edcf52a3a1ab1a4a08445351","message":"Normalize dependency ids flagged by PR review"} {"ts":"2026-07-07T14:17:46.978Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-07T14:17:46.978Z"},{"op":"replace","path":"/metadata/description","value":"Companion to pm-8t5x. pm next + pm claim as separate steps race under parallel agents: all N agents get the same recommendation and N-1 fail their claim. Add an atomic recommend+claim with bounded retry over next-ranked candidates so each parallel agent receives a distinct item. Idea originated in the 2026-07-03 concurrency review (recorded inside pm-8t5x's fix sketch); promoted to its own tracked Feature by the 2026-07-04 metadata review."}],"before_hash":"08ef36d1ee7d87f0898884be76e90eba95c9cc00edcf52a3a1ab1a4a08445351","after_hash":"68cce739c3c22f1046d7efcc58004263ae26b1725344ac5bb200c7e66e127e39"} {"ts":"2026-07-10T16:50:47.380Z","author":"maintainer-agent","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T16:50:47.380Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T16:50:47.379Z","author":"maintainer-agent","text":"Unblocked as of 2026-07-10 verification: sole blocker pm-8t5x (atomic test-and-set claim) is closed, so the atomic substrate this command needs exists. Actionable now; pairs naturally with the parallel-agent work-distribution story and pm-yl6c/pm-hfg5 pm-next defects when scheduling."}]}],"before_hash":"68cce739c3c22f1046d7efcc58004263ae26b1725344ac5bb200c7e66e127e39","after_hash":"15ff6239a51ad994b2dee0adabf04bf47aa66c312ef49afa63e129ce90d6dcbf"} +{"ts":"2026-07-10T17:51:46.622Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:46.622Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"15ff6239a51ad994b2dee0adabf04bf47aa66c312ef49afa63e129ce90d6dcbf","after_hash":"a423c135f2fa1a555abd038b682e06ce8debe3f89330e50acf9036ace070fed1"} +{"ts":"2026-07-10T17:51:47.558Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:47.558Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"a423c135f2fa1a555abd038b682e06ce8debe3f89330e50acf9036ace070fed1","after_hash":"1858cb0cd4a709ae0b093ad0ce20db5500467c76dad207d2083775de5d50b4d1","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:48.843Z","author":"codex-root","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T17:51:48.842Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:48.843Z"}],"before_hash":"1858cb0cd4a709ae0b093ad0ce20db5500467c76dad207d2083775de5d50b4d1","after_hash":"8e5746a5411d366199d00986936e01d80b4eca426fa95dee663ec1a7c640b1d3"} +{"ts":"2026-07-10T18:08:23.012Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:23.012Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"8e5746a5411d366199d00986936e01d80b4eca426fa95dee663ec1a7c640b1d3","after_hash":"2a547a46d3c62ad2fa282329971c9ba8e5d71ea20b13411144858c75c05c0a98"} +{"ts":"2026-07-10T18:08:23.460Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:23.460Z"}],"before_hash":"2a547a46d3c62ad2fa282329971c9ba8e5d71ea20b13411144858c75c05c0a98","after_hash":"532dd78e8b1e3d97ade94733d410d547986438c8ab24b2c555ba95c8861f356b"} +{"ts":"2026-07-10T18:08:23.972Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:23.972Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"532dd78e8b1e3d97ade94733d410d547986438c8ab24b2c555ba95c8861f356b","after_hash":"2f1bef1f0e79123898ff157057600bc3d9431082de8c286568136e6ff0c57093"} +{"ts":"2026-07-10T18:08:24.409Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:24.409Z"}],"before_hash":"2f1bef1f0e79123898ff157057600bc3d9431082de8c286568136e6ff0c57093","after_hash":"7986ae7713e5e44eb8a179e7be656a48111da7114d4da6f12f1a7447397da3f1"} +{"ts":"2026-07-10T18:08:38.110Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/note","value":"shared agent work-selection envelope"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/commands/next.ts"},{"op":"replace","path":"/metadata/files/0/note","value":"atomic ranked claim composition"},{"op":"replace","path":"/metadata/files/0/path","value":"src/cli/commands/claim.ts"},{"op":"add","path":"/metadata/files/2","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:38.110Z"}],"before_hash":"7986ae7713e5e44eb8a179e7be656a48111da7114d4da6f12f1a7447397da3f1","after_hash":"fb30d1c384cb82ddd8ac31d904349c24e22b696ab1536f81e9e532ac7f27d4f4"} +{"ts":"2026-07-10T18:08:38.646Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:38.646Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/commands/claim-command.spec.ts tests/unit/commands/next-command.spec.ts","scope":"project","timeout_seconds":300}]}],"before_hash":"fb30d1c384cb82ddd8ac31d904349c24e22b696ab1536f81e9e532ac7f27d4f4","after_hash":"31d582d99c45979d6123409294c3a8fbc94b08f8be816c510a51042a272bebb8"} +{"ts":"2026-07-10T20:32:21.396Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:21.396Z"},{"op":"add","path":"/metadata/resolution","value":"Added atomic ranked claim-next selection with bounded conflict retries and if-available behavior."},{"op":"add","path":"/metadata/expected_result","value":"Parallel agents can claim distinct recommended work through the SDK-backed CLI without a list-then-claim race."},{"op":"add","path":"/metadata/actual_result","value":"claim --next ranks actionable candidates, retries ownership conflicts, returns recommendation evidence, and passed concurrency and packed-install acceptance tests."}],"before_hash":"31d582d99c45979d6123409294c3a8fbc94b08f8be816c510a51042a272bebb8","after_hash":"88310c52b838287bc4f162bb1193adcd9d0686d5b6794d09247834398f53c401","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:29.987Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-07-10T20:32:29.986Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:29.987Z"}],"before_hash":"88310c52b838287bc4f162bb1193adcd9d0686d5b6794d09247834398f53c401","after_hash":"74337624dcfe34a7ae884b3deddb33eb454cec920a0516606c84444fe8c0f7d6"} +{"ts":"2026-07-10T20:32:37.443Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/dependencies/1"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:37.443Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:37.437Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"74337624dcfe34a7ae884b3deddb33eb454cec920a0516606c84444fe8c0f7d6","after_hash":"657ed0dcf52e87cb135cd6376909ce9bf0a4c1a12dab6ba17b902dc6fc83bd3c"} +{"ts":"2026-07-10T20:32:38.402Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:38.402Z"}],"before_hash":"657ed0dcf52e87cb135cd6376909ce9bf0a4c1a12dab6ba17b902dc6fc83bd3c","after_hash":"60afee0e37905fd4da3c8040f6aec24c7d244b40bf8bef6a14712a957bd99ab4"} +{"ts":"2026-07-10T21:02:06.424Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:02:06.424Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"60afee0e37905fd4da3c8040f6aec24c7d244b40bf8bef6a14712a957bd99ab4","after_hash":"ea8d421a9b2a02856166191e00a3d323d0d32a311749f570e8173b4a9656579a","message":"PR review follow-up: honor claim-next if-available exhaustion, align --author selection, enforce id/--next XOR, and correct zsh claim completions."} +{"ts":"2026-07-10T21:02:07.092Z","author":"codex-wide-pm-sdk","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:02:07.092Z"},{"op":"add","path":"/metadata/assignee","value":"codex-wide-pm-sdk"}],"before_hash":"ea8d421a9b2a02856166191e00a3d323d0d32a311749f570e8173b4a9656579a","after_hash":"9bce6bcfb67395bd6b176246e31905894b6cbf601338842a0ff52e4332aaee4d"} +{"ts":"2026-07-10T21:11:56.435Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-07-10T21:11:56.435Z","author":"codex-wide-pm-sdk","text":"Review evidence: fixed all claim-next findings. --if-available now advances skipped held candidates and returns a non-failing skip only when all are held; explicit --author controls both ranking and mutation; id plus --next is rejected; zsh flags moved from update to claim. Focused suites passed and full coverage is 100/100/100/100 with 5,528 tests."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:11:56.435Z"}],"before_hash":"9bce6bcfb67395bd6b176246e31905894b6cbf601338842a0ff52e4332aaee4d","after_hash":"65672e0a58fa3fd21f2042f5d86a4586f4a71e1a3291da11b8dec5712e9047a6"} +{"ts":"2026-07-10T21:11:58.703Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:11:58.703Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T21:11:58.702Z"},{"op":"add","path":"/metadata/close_reason","value":"Addressed Greptile and CodeRabbit claim-next contract, author, XOR, and completion findings with full coverage."}],"before_hash":"65672e0a58fa3fd21f2042f5d86a4586f4a71e1a3291da11b8dec5712e9047a6","after_hash":"93399e4cdc10239426138cf953504944cdd953ecd8a438c74b5a4c00a7aa5ff3"} +{"ts":"2026-07-10T21:11:59.596Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:11:59.596Z"}],"before_hash":"93399e4cdc10239426138cf953504944cdd953ecd8a438c74b5a4c00a7aa5ff3","after_hash":"e9cc9f591fa88f8543a71e3c5c2872c050b00e0f09917d45e47c03cf4acc7dbe"} diff --git a/.agents/pm/history/pm-69nl.jsonl b/.agents/pm/history/pm-69nl.jsonl index 7b4f5650e..5c7bc7d04 100644 --- a/.agents/pm/history/pm-69nl.jsonl +++ b/.agents/pm/history/pm-69nl.jsonl @@ -1 +1,7 @@ {"ts":"2026-07-10T16:39:14.691Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Problem\npm init (tracker-path mode) roots the tracker at the path directly, but workspace discovery for every subsequent command only probes /.agents/pm. The very next command fails with tracker_not_initialized telling the agent to run pm init first — a hard loop: the error's next_steps re-suggest the action that caused the state. pm init . is the natural git/npm-muscle-memory spelling for 'init here', the failure is delayed (init reports ok:true), and the recovery bundle mentions neither the cwd-rooted tracker nor --pm-path. Worst case the agent re-inits and silently splits state across two trackers.\n\n## Repro (pm 2026.7.10)\nmkdir ws && cd ws && pm init . -> ok, mode tracker-path, tracker_root=. pm list --json -> tracker_not_initialized at /.agents/pm. pm --pm-path . list --json works.\n\n## Fix directions (from GH-501, any-of)\n1. Discovery also accepts cwd (or ancestor) as tracker root when it contains a valid settings.json + tracker layout.\n2. Treat pm init . as the workspace-discovery spelling (.agents/pm under it); require an explicit --tracker-path for raw-root mode.\n3. Minimum: tracker_not_initialized probes cwd for a tracker and suggests --pm-path . as an executable next step.\nOption 1+3 together preserve both modes without breaking existing raw-root users; option 2 is a behavior change needing a deprecation note. Related: pm-x26a (GH-488) made path-target init emit tracker-scoped follow-up commands — same mode-split root cause, this is the discovery half.\n\n## Philosophy tie-in\nNever-block: an error whose recovery loop re-suggests its own cause is the worst agent dead-end.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/501"},{"op":"add","path":"/metadata/id","value":"pm-69nl"},{"op":"add","path":"/metadata/title","value":"GH-501: pm init roots a tracker that workspace discovery cannot find — tracker_not_initialized loop right after init"},{"op":"add","path":"/metadata/description","value":""},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["agent-ux","cli","gh-issue","never-block","onboarding"]},{"op":"add","path":"/metadata/created_at","value":"2026-07-10T16:39:14.691Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-10T16:39:14.691Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/estimated_minutes","value":120},{"op":"add","path":"/metadata/acceptance_criteria","value":"After pm init . in an empty dir, the next pm command (e.g. pm list) works without --pm-path; discovery accepts cwd/ancestor as tracker root when it holds a valid tracker layout OR init . is redefined to the workspace-discovery spelling with an explicit flag for raw-root mode (decision recorded); tracker_not_initialized recovery bundle probes for a cwd-rooted tracker and emits an executable --pm-path next step when found; no second tracker can be silently created at .agents/pm when a cwd-rooted tracker exists; e2e test covers the init-then-list round-trip for both init modes; GH-501 closed with a comment linking the fix"},{"op":"add","path":"/metadata/risk","value":"medium"},{"op":"add","path":"/metadata/confidence","value":"high"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-x26a","kind":"related","created_at":"2026-07-10T16:39:14.691Z"}]}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"8dc7db6c0fc6d1e39e7a306ea9c3189639d1a6752be52339a45f6795f4b4adfa","message":""} +{"ts":"2026-07-10T17:51:57.125Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:57.125Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"8dc7db6c0fc6d1e39e7a306ea9c3189639d1a6752be52339a45f6795f4b4adfa","after_hash":"c28761042be36d209fe47a914560ccfcb47bb75098ed8e50695d5d61f232cf10"} +{"ts":"2026-07-10T17:51:57.621Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:57.621Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"c28761042be36d209fe47a914560ccfcb47bb75098ed8e50695d5d61f232cf10","after_hash":"c24bf86b81f9f0aa5bb0cca5fdc67f8603e64cf1c455fd178b255676db8ec00e","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:58.070Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:58.070Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:51:58.069Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"c24bf86b81f9f0aa5bb0cca5fdc67f8603e64cf1c455fd178b255676db8ec00e","after_hash":"aa46913c1afcbce2a4a01876a9b4cc9486374accd884d634414d0be840232458"} +{"ts":"2026-07-10T17:52:50.422Z","author":"codex-root","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T17:52:50.422Z","author":"codex-root","text":"Live verification correction: current main 0f43e921 already resolves cwd-rooted tracker layouts. Fresh temp proof: pm init . --defaults --author probe created tracker-path mode at cwd; the immediately following bare pm list --json exited 0 and returned an empty item list. Root cause was already removed by the root-layout discovery work linked through pm-x26a/GH-495; no code change belongs in this PR."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:50.422Z"}],"before_hash":"aa46913c1afcbce2a4a01876a9b4cc9486374accd884d634414d0be840232458","after_hash":"9e08a23d5d118499981f6ea16be735d27b21de8261cff2a61f0f4cb283fcb3a3"} +{"ts":"2026-07-10T17:52:51.087Z","author":"codex-root","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:51.087Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T17:52:51.082Z"},{"op":"add","path":"/metadata/close_reason","value":"Already fixed on current main; fresh init-then-list temp proof exits 0 with implicit root-layout discovery."}],"before_hash":"9e08a23d5d118499981f6ea16be735d27b21de8261cff2a61f0f4cb283fcb3a3","after_hash":"1cfecab1f90f50cbc8221e270a90c755fd0f31abe507aaa2fd03719059487d02"} +{"ts":"2026-07-10T17:52:51.868Z","author":"codex-root","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:51.868Z"}],"before_hash":"1cfecab1f90f50cbc8221e270a90c755fd0f31abe507aaa2fd03719059487d02","after_hash":"13118a767143c51188f66857006d7690bee68df75accc55e968f41a715f1a2bf"} diff --git a/.agents/pm/history/pm-6y58.jsonl b/.agents/pm/history/pm-6y58.jsonl index 11b78cc8d..36f221a0a 100644 --- a/.agents/pm/history/pm-6y58.jsonl +++ b/.agents/pm/history/pm-6y58.jsonl @@ -1,2 +1,15 @@ {"ts":"2026-07-10T16:38:55.646Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Problem\nWhen a command fails due to a missing boolean flag, the recovery bundle's suggested_retry appends a \"\" placeholder to the flag. Boolean flags take no value, so running the suggested command verbatim fails with a different error (too many arguments). Agents are the primary consumers of suggested_retry and copy it verbatim — an executable-looking suggestion that errors when followed burns a round-trip and erodes trust in the recovery bundle.\n\n## Repro (pm 2026.7.10)\npm close done on an already-terminal item -> missing: --force -> suggested_retry: pm close done --force \"\" -> running it: too many arguments for 'close'.\n\n## Fix direction\nThe suggested_retry renderer must consult each flag's arity from the flag contract: arity-0 flags rendered bare, value flags with a typed placeholder. Related territory: pm-gdi7 owns unknown-option suggestion polish in the same envelope layer; this is the retry-renderer half.\n\n## Philosophy tie-in\nproject management = context management — recovery bundles are agent context; they must be executable as-is.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/500"},{"op":"add","path":"/metadata/id","value":"pm-6y58"},{"op":"add","path":"/metadata/title","value":"GH-500: suggested_retry renders boolean flags with a \"\" placeholder — literal suggestion fails"},{"op":"add","path":"/metadata/description","value":""},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":3},{"op":"add","path":"/metadata/tags","value":["agent-ux","cli","error-recovery","gh-issue"]},{"op":"add","path":"/metadata/created_at","value":"2026-07-10T16:38:55.646Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-10T16:38:55.646Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/estimated_minutes","value":60},{"op":"add","path":"/metadata/acceptance_criteria","value":"suggested_retry renders boolean (arity-0) flags bare (--force) and only value-taking flags with a placeholder; repro from GH-500 (pm close done twice) yields a suggestion that succeeds verbatim; regression test covers boolean and value flags in suggested_retry; GH-500 closed with a comment linking the fix"},{"op":"add","path":"/metadata/risk","value":"low"},{"op":"add","path":"/metadata/confidence","value":"high"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"41ce6790ec07ee89ab265f23521a430cc3a01967fbf5610e1466341b8a3c051b","message":""} {"ts":"2026-07-10T16:39:25.461Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T16:39:25.461Z"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-gdi7","kind":"related","created_at":"2026-07-10T16:39:25.446Z"}]}],"before_hash":"41ce6790ec07ee89ab265f23521a430cc3a01967fbf5610e1466341b8a3c051b","after_hash":"a5d262fe901d8d0e24ba3e35e6f03b533e86140e7ec8c0ef4519442f0476fb92","message":"Link to pm-gdi7: same recovery-envelope layer; gdi7 owns unknown-option suggestions, this owns retry-renderer arity"} +{"ts":"2026-07-10T17:51:58.752Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:58.752Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"a5d262fe901d8d0e24ba3e35e6f03b533e86140e7ec8c0ef4519442f0476fb92","after_hash":"e59e305218920434a3eb1fc7c908969b0e66ba810fba52b1087e7a63179b00ff"} +{"ts":"2026-07-10T17:51:59.288Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:59.288Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"e59e305218920434a3eb1fc7c908969b0e66ba810fba52b1087e7a63179b00ff","after_hash":"31ae90fcbdf11ed7ba234765e49fca79658f248bb791ba95d600606c83a19d7c","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:59.995Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:59.995Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:51:59.995Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"31ae90fcbdf11ed7ba234765e49fca79658f248bb791ba95d600606c83a19d7c","after_hash":"cad4f6617e4fb59e1476fe9280f679c41b8257d8878a811221d1f96723b29db9"} +{"ts":"2026-07-10T18:08:34.091Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:34.091Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"cad4f6617e4fb59e1476fe9280f679c41b8257d8878a811221d1f96723b29db9","after_hash":"415db67ee335afa79db92ae999e7edd1b5ff0944f4438462ebb7d5dd318b4bdf"} +{"ts":"2026-07-10T18:08:34.607Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:34.607Z"}],"before_hash":"415db67ee335afa79db92ae999e7edd1b5ff0944f4438462ebb7d5dd318b4bdf","after_hash":"c903e4e14be40b1d477e56388e0ebd8fffe305a838471dde2db081361c2bafa7"} +{"ts":"2026-07-10T18:08:35.080Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:35.080Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"c903e4e14be40b1d477e56388e0ebd8fffe305a838471dde2db081361c2bafa7","after_hash":"8a5a0e3765eb598eae944baa13cd66af66cef4486afcea47896f1ed89fd3f6ec"} +{"ts":"2026-07-10T18:08:35.560Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:35.560Z"}],"before_hash":"8a5a0e3765eb598eae944baa13cd66af66cef4486afcea47896f1ed89fd3f6ec","after_hash":"f5205f1e0c4c560adb0cc3b7038cb9f486d8c809315e816392c40a915fe08b9d"} +{"ts":"2026-07-10T18:08:44.931Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/note","value":"arity-aware executable recovery retry"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/main.ts"},{"op":"add","path":"/metadata/files/2","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:44.931Z"}],"before_hash":"f5205f1e0c4c560adb0cc3b7038cb9f486d8c809315e816392c40a915fe08b9d","after_hash":"90ef44b5a2295b45105985c4161595a71fb3d93caf2fe872b132fcd369d529ad"} +{"ts":"2026-07-10T18:08:45.574Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:45.574Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/cli/cli-main-errors.spec.ts","scope":"project","timeout_seconds":300}]}],"before_hash":"90ef44b5a2295b45105985c4161595a71fb3d93caf2fe872b132fcd369d529ad","after_hash":"068ba462152147ade8782ac75bd4e895efccae03c6d59a0cd08c949e26f10ed8"} +{"ts":"2026-07-10T20:32:27.119Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:27.119Z"},{"op":"add","path":"/metadata/resolution","value":"Derived recovery placeholders from Commander option arity so boolean flags remain literal."},{"op":"add","path":"/metadata/expected_result","value":"suggested_retry commands are directly runnable for boolean options."},{"op":"add","path":"/metadata/actual_result","value":"Recovery suggestions now render --force without a bogus value placeholder, including commands preceded by global option values."}],"before_hash":"068ba462152147ade8782ac75bd4e895efccae03c6d59a0cd08c949e26f10ed8","after_hash":"52ceb56c7bc464ab2e9a9569795cd12ac46bcd0f5df1ddfab8e38cadb063ccef","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:33.292Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T20:32:33.292Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:33.292Z"}],"before_hash":"52ceb56c7bc464ab2e9a9569795cd12ac46bcd0f5df1ddfab8e38cadb063ccef","after_hash":"300ca79871ad1eecd41f4b7764e04673ad3f35cdd8428b704413394f6f2973cd"} +{"ts":"2026-07-10T20:32:49.333Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:49.333Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:49.329Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"300ca79871ad1eecd41f4b7764e04673ad3f35cdd8428b704413394f6f2973cd","after_hash":"d36f6fbc9aa02a181def903d47bce5da8dc6cae770342711e1b9ec33793bfa8b"} +{"ts":"2026-07-10T20:32:50.588Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:50.588Z"}],"before_hash":"d36f6fbc9aa02a181def903d47bce5da8dc6cae770342711e1b9ec33793bfa8b","after_hash":"e955695ab669dbd05852cf387fe841afcceac19b4599140d25c5e2d7ba081d23"} diff --git a/.agents/pm/history/pm-hfg5.jsonl b/.agents/pm/history/pm-hfg5.jsonl index 2ecedc24e..6684035e4 100644 --- a/.agents/pm/history/pm-hfg5.jsonl +++ b/.agents/pm/history/pm-hfg5.jsonl @@ -1,2 +1,14 @@ {"ts":"2026-07-07T15:24:51.495Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Evidence (2026-07-07)\npm next default (toon) output is 6123 bytes; the recommended block (id/title/type/status/priority/assignee/tags/updated_at/parent/open_blocker_count, ~450 bytes for pm-w89s) is repeated verbatim as ready[0] with every field identical. Same duplication in --json (8559 bytes). Every agent picking work pays the duplicated projection on every call.\n\n## Why it matters\npm next is the highest-frequency read on the agent hot path (every work-pick loop). At thousands of concurrent agents (pm-9rxu scale story) the duplicated projection is pure waste, and it also trains agents to distrust the envelope (same item appears twice with no marker).\n\n## Fix sketch\nOmit the recommended item from ready (documented contract: ready = candidates EXCLUDING recommended), or replace ready[0] with a bare id reference (recommended_ref). Apply in one shared projection site so toon/json/MCP envelopes stay aligned; update SDK contracts + snapshot tests. Small-workspace neutral; strictly fewer tokens."},{"op":"add","path":"/metadata/id","value":"pm-hfg5"},{"op":"add","path":"/metadata/title","value":"pm next repeats the recommended item verbatim as ready[0] — emit an id reference instead"},{"op":"add","path":"/metadata/description","value":"Token-efficiency defect in the pm next envelope: the fully-projected recommended item is duplicated byte-identical as the first entry of the ready list, in both toon and json output. Duplicate check: searched next/duplicate/ready/envelope items — pm-gdi7 (flag hints, list-closed projection), pm-nilh (lean mutation default), pm-a9mc (validate counts-only) are adjacent but none covers the next envelope; no prior item found."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["agent-ux","area:cli","token-efficiency"]},{"op":"add","path":"/metadata/created_at","value":"2026-07-07T15:24:51.495Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-07T15:24:51.495Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"pm next (toon and json) no longer contains two full projections of the same item; the ready-list contract (excludes recommended, or id-ref) is documented in command docs and SDK contracts; contract snapshot tests assert non-duplication; MCP pm_next envelope matches"},{"op":"add","path":"/metadata/parent","value":"pm-mpbb"},{"op":"add","path":"/metadata/risk","value":"low"},{"op":"add","path":"/metadata/confidence","value":"high"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"e156a80a0f6f7a4493d476542d7377cf17cc218f9efc11e26421128f5024c756","message":""} {"ts":"2026-07-07T15:35:23.404Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-07T15:35:23.404Z"},{"op":"add","path":"/metadata/estimated_minutes","value":60}],"before_hash":"e156a80a0f6f7a4493d476542d7377cf17cc218f9efc11e26421128f5024c756","after_hash":"193243baeff93152ed85c24e775372792915eb93802e96e2957fc2dd8668ca6f"} +{"ts":"2026-07-10T17:51:53.060Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:53.060Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"193243baeff93152ed85c24e775372792915eb93802e96e2957fc2dd8668ca6f","after_hash":"5219885f4edc7e6d6e83b4e23f5da3f0c1247a9fb32a3a23dde313fd88c06091"} +{"ts":"2026-07-10T17:51:53.661Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:53.661Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"5219885f4edc7e6d6e83b4e23f5da3f0c1247a9fb32a3a23dde313fd88c06091","after_hash":"e59efe78b480932070d371b76c26294d329757f7f9b45e1a2cdfa0e56e247d15","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:54.496Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:54.496Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:51:54.495Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"e59efe78b480932070d371b76c26294d329757f7f9b45e1a2cdfa0e56e247d15","after_hash":"aff76d1a471d24ff86333eea394edca8502ef359dfbfe0cf668983a006eb95c2"} +{"ts":"2026-07-10T18:08:30.088Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:30.088Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"aff76d1a471d24ff86333eea394edca8502ef359dfbfe0cf668983a006eb95c2","after_hash":"f229ea6e3eac683db17e0e44b31e01b6107278434134bff05dc781a6091c0177"} +{"ts":"2026-07-10T18:08:30.588Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:30.588Z"}],"before_hash":"f229ea6e3eac683db17e0e44b31e01b6107278434134bff05dc781a6091c0177","after_hash":"e1b4e2da6772dfa609fef6ee1d47b5a123eaff816e81d9927b451bb8a9b4f330"} +{"ts":"2026-07-10T18:08:31.068Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:31.068Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"e1b4e2da6772dfa609fef6ee1d47b5a123eaff816e81d9927b451bb8a9b4f330","after_hash":"5da62f878a94f7e4800b513cb0e7b67654a51b3f3ebf0a5f75ae8b6613e40602"} +{"ts":"2026-07-10T18:08:31.554Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:31.554Z"}],"before_hash":"5da62f878a94f7e4800b513cb0e7b67654a51b3f3ebf0a5f75ae8b6613e40602","after_hash":"414897c1bfa1ea62ea93fd2c049d8ed21744a88f01c170ef9b45668dbe4f177a"} +{"ts":"2026-07-10T18:08:43.795Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:43.795Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts","scope":"project","timeout_seconds":240}]}],"before_hash":"414897c1bfa1ea62ea93fd2c049d8ed21744a88f01c170ef9b45668dbe4f177a","after_hash":"b9179c6b3d650b7baa8771b5151fdef733f161d076a275126232e5528f8899a5"} +{"ts":"2026-07-10T20:32:25.145Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:25.145Z"},{"op":"add","path":"/metadata/resolution","value":"Removed the recommended row from the ready tail while preserving evaluation and ranking semantics."},{"op":"add","path":"/metadata/expected_result","value":"pm next emits one canonical recommended object without duplicating it as ready[0]."},{"op":"add","path":"/metadata/actual_result","value":"ready begins after the recommendation and SDK context evaluation treats recommended plus ready as the served ranking."}],"before_hash":"b9179c6b3d650b7baa8771b5151fdef733f161d076a275126232e5528f8899a5","after_hash":"32fa98bcbaf621c2b7182462fe239395bcf50e025bd9ff5f23428e3ffabfb64f","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:31.559Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T20:32:31.559Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:31.559Z"}],"before_hash":"32fa98bcbaf621c2b7182462fe239395bcf50e025bd9ff5f23428e3ffabfb64f","after_hash":"686811bfea63805d2e1211eb480d9e9b23720f78151d380e41f99101c47529d0"} +{"ts":"2026-07-10T20:32:44.318Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:44.318Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:44.317Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"686811bfea63805d2e1211eb480d9e9b23720f78151d380e41f99101c47529d0","after_hash":"c636c46f53e93b0f13fc58ab47586658c9676b27393967ce1833c191291dd258"} +{"ts":"2026-07-10T20:32:45.729Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:45.729Z"}],"before_hash":"c636c46f53e93b0f13fc58ab47586658c9676b27393967ce1833c191291dd258","after_hash":"98ff15892b7fe66a926c3b5fb55433c6618bf2552f955e9e024dc83028a53b2e"} diff --git a/.agents/pm/history/pm-l0bu.jsonl b/.agents/pm/history/pm-l0bu.jsonl index 754ac8aaf..bbe7ebf39 100644 --- a/.agents/pm/history/pm-l0bu.jsonl +++ b/.agents/pm/history/pm-l0bu.jsonl @@ -2,3 +2,15 @@ {"ts":"2026-07-10T10:32:25.997Z","author":"maintainer-agent","op":"append","patch":[{"op":"replace","path":"/body","value":"## Source\nGitHub issue #489 (dogfood, version 2026.7.9).\n\n## Reproduction\n```bash\npm init uxt --defaults --author probe\npm create task \"First\" # -> uxt-A (open)\npm create task \"Second\" --blocked-by uxt-A # -> uxt-B (status: blocked)\npm next\n```\n`pm next` emits `summary.blocked: 0` and no blocked-companion section, while the same envelope's recommended entry says `unblocks 1 item(s): uxt-B` and `pm list-blocked` correctly shows 1 blocked item. A `blocked_limit: 5` filter is even printed, so the companion surface is advertised but never populated.\n\n## Expected\n`summary.blocked` counts blocked items and the blocked companion lists them with their blockers (as `pm next --help` documents).\n\n## Why it matters for agents\n`pm next` is the primary \"what should I work on\" entrypoint. The envelope contradicts itself (unblocks list vs blocked: 0), so an agent trusting the summary concludes no blocked work exists and skips triage. The data is already computed for the unblocks reasons — only the summary/companion aggregation misses it.\n\n## Scope\nFix the aggregation in the pm next summary + blocked companion; add a regression test covering the blocked-companion path in a fresh workspace. Related output-shape defects: pm-hfg5 (ready[0] duplication), pm-yl6c (cross-agent resume recommendation)."},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T10:32:25.997Z"}],"before_hash":"99c1745b8feb09616267ddca4bc4987996d872c9c1f234688c93421118b77082","after_hash":"06d15836daac1b59fbf742d3bc42e0596b63830bc9d3277617744c28040ea6d5"} {"ts":"2026-07-10T10:36:32.481Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T10:36:32.481Z"},{"op":"add","path":"/metadata/acceptance_criteria","value":"pm next summary.blocked equals the true blocked count in a fresh two-item workspace; blocked companion section lists blocked items with their blockers up to blocked_limit; regression test covers the repro from GH-489; envelope no longer contradicts the unblocks reasons"}],"before_hash":"06d15836daac1b59fbf742d3bc42e0596b63830bc9d3277617744c28040ea6d5","after_hash":"67d9dee8273237eb42a1acee101220673b2d6eaa0576c9d48b296f83a8ca3a7b"} {"ts":"2026-07-10T16:57:36.687Z","author":"maintainer-agent","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T16:57:36.687Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T16:57:36.687Z","author":"maintainer-agent","text":"Root-cause narrowing (review 2026-07-10, current main build): the GH-489 repro still fails — fresh workspace with one status=blocked item yields summary.blocked: 0 AND candidates: 1, i.e. the status=blocked item is excluded from the candidate pool entirely, not merely miscounted. Contrast: the real workspace reports blocked: 25 with zero status=blocked items (pm stats), so next's blocked counter is derived from open items with open blockers (graph-blocked). Fix likely needs BOTH paths: include status=blocked items in candidates/summary and populate the companion from the union of status-blocked + graph-blocked. Regression test should assert candidates counts them too."}]}],"before_hash":"67d9dee8273237eb42a1acee101220673b2d6eaa0576c9d48b296f83a8ca3a7b","after_hash":"ffc83e25e967b35e11d6bce16485f6b16b209f0ee8e19393c96118da7ca199a8"} +{"ts":"2026-07-10T17:51:49.524Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:49.524Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"ffc83e25e967b35e11d6bce16485f6b16b209f0ee8e19393c96118da7ca199a8","after_hash":"5db9a327f2c368ead832ee10dea48811d7e1eb81660b3863b1dac9294ec75c75"} +{"ts":"2026-07-10T17:51:50.338Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:50.338Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"5db9a327f2c368ead832ee10dea48811d7e1eb81660b3863b1dac9294ec75c75","after_hash":"bb677d851de615470ea0179938e96231f821d7b00718eb257d96bb7692a81866","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:50.826Z","author":"codex-root","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T17:51:50.816Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:50.826Z"}],"before_hash":"bb677d851de615470ea0179938e96231f821d7b00718eb257d96bb7692a81866","after_hash":"b8d2873a8594e75050d2d5c112cfd6c64d53e1a5babec2429e8e3f4f3e79e615"} +{"ts":"2026-07-10T18:08:24.930Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:24.930Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"b8d2873a8594e75050d2d5c112cfd6c64d53e1a5babec2429e8e3f4f3e79e615","after_hash":"d50bd1d78cf6883a0e6e4b986d43dd4d6ebadb0b33491f6dbc22697a319bae04"} +{"ts":"2026-07-10T18:08:25.434Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:25.434Z"}],"before_hash":"d50bd1d78cf6883a0e6e4b986d43dd4d6ebadb0b33491f6dbc22697a319bae04","after_hash":"7e1813676d0653c8429289bbcf37e27555e79f8c5961ed4816b025ca21b7371b"} +{"ts":"2026-07-10T18:08:25.926Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:25.926Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"7e1813676d0653c8429289bbcf37e27555e79f8c5961ed4816b025ca21b7371b","after_hash":"63b841118c2325adaed10fad99ee360431a66518dd7b2034dcbc4d1bdfef17bc"} +{"ts":"2026-07-10T18:08:26.863Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:26.863Z"}],"before_hash":"63b841118c2325adaed10fad99ee360431a66518dd7b2034dcbc4d1bdfef17bc","after_hash":"4034ada9b7c4ccfac3bce841ebf1d6ee2a8431eecd092f0e6f83fb5d11137f8b"} +{"ts":"2026-07-10T18:08:42.115Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:42.115Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts","scope":"project","timeout_seconds":240}]}],"before_hash":"4034ada9b7c4ccfac3bce841ebf1d6ee2a8431eecd092f0e6f83fb5d11137f8b","after_hash":"b1bfc9cc48959ce371f5163a5c1a42d8f1c28666c8e57d0eef536734e5aee49c"} +{"ts":"2026-07-10T20:32:22.445Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:22.445Z"},{"op":"add","path":"/metadata/resolution","value":"Made pm next preserve lifecycle-blocked and dangling-dependency work in the blocked companion list and summary."},{"op":"add","path":"/metadata/expected_result","value":"Blocked counts and rows remain truthful for graph and lifecycle blocking states."},{"op":"add","path":"/metadata/actual_result","value":"Blocked rows now include lifecycle-only and missing-reference blockers with safe suggestions; regression tests pass."}],"before_hash":"b1bfc9cc48959ce371f5163a5c1a42d8f1c28666c8e57d0eef536734e5aee49c","after_hash":"3175acc8e65a2e072d13ae89c26b3fe38428d704615a9ea3c103940e47bcb933","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:30.510Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-07-10T20:32:30.507Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:30.510Z"}],"before_hash":"3175acc8e65a2e072d13ae89c26b3fe38428d704615a9ea3c103940e47bcb933","after_hash":"f8be321f6de4e8eda37e9d6d73f17aa77274330cb9dc7c98169d05454cd250da"} +{"ts":"2026-07-10T20:32:39.641Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:39.641Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:39.637Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"f8be321f6de4e8eda37e9d6d73f17aa77274330cb9dc7c98169d05454cd250da","after_hash":"8b41a60d40f2371fc5726b2c6f30f13c162b9a38454b2b35a129f06c0e0cae7c"} +{"ts":"2026-07-10T20:32:40.595Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:40.595Z"}],"before_hash":"8b41a60d40f2371fc5726b2c6f30f13c162b9a38454b2b35a129f06c0e0cae7c","after_hash":"2a9034ef9be309478ccdd22a43582ce73bd5cbd743d28d455af8a79c37b05ea7"} diff --git a/.agents/pm/history/pm-ol5v.jsonl b/.agents/pm/history/pm-ol5v.jsonl index 4941a2708..b5222ac0e 100644 --- a/.agents/pm/history/pm-ol5v.jsonl +++ b/.agents/pm/history/pm-ol5v.jsonl @@ -1 +1,24 @@ {"ts":"2026-07-10T17:24:09.633Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Defect\nStructured dependency edges whose `id` resolves to no existing item are invisible to every layer of the system, and the net effect is a SILENT UNBLOCK of work the author intended to be blocked.\n\nVerified in a clean sandbox on v2026.7.10:\n1. `pm create task \"dep to ghost\" --dep \"id=pm-zzzz,kind=blocked_by\"` — accepted, edge stored, no warning.\n2. `pm validate --json` — ok:true, zero referential warnings. Root cause: both graph builders silently drop unresolvable ids — `src/cli/commands/validate.ts:1189` (`!activeItemIds.has(dependencyId) → continue`) for the dependency-cycle graph, and the `canonicalIdByLowercase.get(...)` miss in `buildLifecycleParentGraph` for parents.\n3. `pm next --json` — the ghost-blocked item is counted `ready` and offered as a candidate (summary ready:2 for 2 items). The nonexistent blocker is treated as satisfied.\n\n## Why it matters\n- The pre-PR#499 colon-shorthand bug (pm-zazb) historically wrote malformed `pm-related:*` dep ids in the wild; repos carrying that era's data have exactly these edges, and today nothing will ever tell them.\n- A single-character typo in `--dep id=` inverts the semantics of `blocked_by`: instead of blocking, it releases the item into the actionable pool. For multi-agent fleets picking work via pm next, this dispatches work whose preconditions are unmet — the opposite of the graph's contract, with no error at write time, no warning at validate time, and no trace at read time.\n- Contrast: parent references ARE fail-fast at mutation time (`pm update --parent ` is rejected, pm-z9e9), so deps are the inconsistent surface. A validator-side parent-drift check (stale parent pointing at a since-deleted id) can ride along in the same warning family for symmetry, though the current live graph has zero instances (full-store sweep 2026-07-10: 1708 items, all parent pointers and all structured dep ids resolve except prose mentions).\n\n## Design note for the mutation side\nHard-rejecting nonexistent dep ids at create/update would break legitimate forward-referencing in scripted flows (creating a graph in reverse order). Options: (a) warn on stderr + accept, (b) `--strict-deps` opt-in rejection, (c) accept + rely on validate. The validator warning is the non-negotiable half; the mutation-side choice is the implementer's, but it must be an explicit decision recorded on this item."},{"op":"add","path":"/metadata/id","value":"pm-ol5v"},{"op":"add","path":"/metadata/title","value":"Dangling dependency references: accepted at create/update, skipped by validate, and treated as satisfied by pm next (silent unblock)"},{"op":"add","path":"/metadata/description","value":""},{"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":[]},{"op":"add","path":"/metadata/created_at","value":"2026-07-10T17:24:09.633Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-10T17:24:09.633Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/estimated_minutes","value":90},{"op":"add","path":"/metadata/acceptance_criteria","value":"pm validate emits a dedicated warning (with item ids and the dangling dep ids) for structured dependency edges whose id resolves to no item; a typo'd blocker id can never again yield an actionable pm next candidate without a diagnostic surfacing somewhere; mutation-time behavior for nonexistent dep ids is decided (warn vs reject vs --strict flag) and documented; tests cover create, update, validate, and next paths; remediation hint tells users to repair with --replace-deps"},{"op":"add","path":"/metadata/risk","value":"medium"},{"op":"add","path":"/metadata/confidence","value":"high"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-l0bu","kind":"related","created_at":"2026-07-10T17:24:09.633Z"}]}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"3ea79394ce6320440478ac03820fa375c621996bc31fef813ec5b9cb202f70f8","message":""} +{"ts":"2026-07-10T17:51:55.282Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:55.282Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"3ea79394ce6320440478ac03820fa375c621996bc31fef813ec5b9cb202f70f8","after_hash":"a9463c405bc400b757e7b34c0af96f34554024dc52476f058bd94afdbdaa8f4d"} +{"ts":"2026-07-10T17:51:55.839Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:55.839Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"a9463c405bc400b757e7b34c0af96f34554024dc52476f058bd94afdbdaa8f4d","after_hash":"daac50b1ec7f772c1e1d07e732aac84903c1f60e30038a6234ebc4817eb9d459","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:56.413Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:56.413Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:51:56.413Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"daac50b1ec7f772c1e1d07e732aac84903c1f60e30038a6234ebc4817eb9d459","after_hash":"7177c824442d098616365b5a8f371183379501c9fbf3759277c8b0205779202e"} +{"ts":"2026-07-10T18:08:32.055Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:32.055Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"7177c824442d098616365b5a8f371183379501c9fbf3759277c8b0205779202e","after_hash":"a775d2bede16f3b5352163fff07fae1afa00064a4db26c08325e3e3c289ca261"} +{"ts":"2026-07-10T18:08:32.627Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:32.627Z"}],"before_hash":"a775d2bede16f3b5352163fff07fae1afa00064a4db26c08325e3e3c289ca261","after_hash":"a882e5c7c95f53647ed103c6ae36018cf1235e9931e4200162a482c5cd0e40f8"} +{"ts":"2026-07-10T18:08:33.163Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:33.163Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"a882e5c7c95f53647ed103c6ae36018cf1235e9931e4200162a482c5cd0e40f8","after_hash":"383a09b55fb1d7bfbd7d7e5293d4305c086958b77328ad8adb71dd14872a7606"} +{"ts":"2026-07-10T18:08:33.650Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:33.650Z"}],"before_hash":"383a09b55fb1d7bfbd7d7e5293d4305c086958b77328ad8adb71dd14872a7606","after_hash":"d0f62fa289ed048857543049ddbd83a9ec8e0fb4789b925b8e699a2cf9bddc53"} +{"ts":"2026-07-10T18:08:39.557Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/note","value":"dangling dependency diagnostic"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/commands/validate.ts"},{"op":"add","path":"/metadata/files/2","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:39.557Z"}],"before_hash":"d0f62fa289ed048857543049ddbd83a9ec8e0fb4789b925b8e699a2cf9bddc53","after_hash":"5421105929bb54fdcfa72c8e18ac07d2afed7ae0adb0cc83bc7bacb09aa6ba49"} +{"ts":"2026-07-10T18:08:40.738Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:40.738Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/core/item/actionability.spec.ts tests/unit/commands/validate-command.spec.ts tests/unit/commands/next-command.spec.ts","scope":"project","timeout_seconds":300}]}],"before_hash":"5421105929bb54fdcfa72c8e18ac07d2afed7ae0adb0cc83bc7bacb09aa6ba49","after_hash":"731d7d611b96feee306c64a20b498eb8eeb343a465ece6cb203140df5459ed7f"} +{"ts":"2026-07-10T20:32:26.061Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:26.061Z"},{"op":"add","path":"/metadata/resolution","value":"Kept missing blocker IDs unresolved and added dependency-reference validation with remediation hints."},{"op":"add","path":"/metadata/expected_result","value":"Dangling parent, scalar blocker, and structured dependency references cannot silently unblock work."},{"op":"add","path":"/metadata/actual_result","value":"Actionability blocks dangling references and validate reports sorted affected rows plus replace-deps remediation."}],"before_hash":"731d7d611b96feee306c64a20b498eb8eeb343a465ece6cb203140df5459ed7f","after_hash":"eba5d844c488a13289b8335e28bc51d31c61b479b4884dedc8c1da4381eca7db","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:32.350Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T20:32:32.350Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:32.350Z"}],"before_hash":"eba5d844c488a13289b8335e28bc51d31c61b479b4884dedc8c1da4381eca7db","after_hash":"6b8d6ec3696887eeef3e4145088d2608b05e3631b7e3989f9388d3b49021a9d9"} +{"ts":"2026-07-10T20:32:46.933Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:46.933Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:46.932Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"6b8d6ec3696887eeef3e4145088d2608b05e3631b7e3989f9388d3b49021a9d9","after_hash":"fb4fa2a903f5e2fc06d8ce3f87f34d9e46d167419611e1acb5dee37d9d14966b"} +{"ts":"2026-07-10T20:32:47.937Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:47.937Z"}],"before_hash":"fb4fa2a903f5e2fc06d8ce3f87f34d9e46d167419611e1acb5dee37d9d14966b","after_hash":"98c4caa37d6ff627dce73322e47e9d3b6f5d1a675c3ce0b6d8e5459a96116a78"} +{"ts":"2026-07-10T20:50:59.415Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:50:59.415Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"98c4caa37d6ff627dce73322e47e9d3b6f5d1a675c3ce0b6d8e5459a96116a78","after_hash":"8a13955f325bbc7745d12466f10a88e776fefe77413ab6af84a3dd2d68a976f3","message":"Greptile follow-up: repair invalid second dangling-dependency test fixture and prove both independent rows are retained."} +{"ts":"2026-07-10T20:50:59.910Z","author":"codex-wide-pm-sdk","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:50:59.910Z"},{"op":"add","path":"/metadata/assignee","value":"codex-wide-pm-sdk"}],"before_hash":"8a13955f325bbc7745d12466f10a88e776fefe77413ab6af84a3dd2d68a976f3","after_hash":"f0dcffb972e886d89e8cc53712837a4ccaf95d9bdf748c484a788d465d8b1fe5"} +{"ts":"2026-07-10T20:52:24.944Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-07-10T20:52:24.944Z","author":"codex-wide-pm-sdk","text":"Greptile follow-up resolved: the second fixture used unsupported dependency kind relates_to, so its update command did not persist. Replaced it with a valid blocked_by edge; focused validate suite passes 88/88 and now proves two distinct dangling rows survive sorting and deduplication."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:52:24.944Z"}],"before_hash":"f0dcffb972e886d89e8cc53712837a4ccaf95d9bdf748c484a788d465d8b1fe5","after_hash":"13e4c5928dfbcdaa183307268420a5b1b1421494dcf76f30d551b837e5b8e0f0"} +{"ts":"2026-07-10T20:52:25.723Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:52:25.723Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:52:25.722Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented, verified, and corrected the multi-row dangling-reference regression fixture after Greptile review."}],"before_hash":"13e4c5928dfbcdaa183307268420a5b1b1421494dcf76f30d551b837e5b8e0f0","after_hash":"6304758c050eaff77c228684f4250bba21f72ab1f1782232c7a339f574eeba59"} +{"ts":"2026-07-10T20:52:26.382Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:52:26.382Z"}],"before_hash":"6304758c050eaff77c228684f4250bba21f72ab1f1782232c7a339f574eeba59","after_hash":"e46209e3d562ba94c38890baad074fa9b4ecb710eb21b281ca039f73e628cda5"} +{"ts":"2026-07-10T21:02:07.662Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:02:07.662Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"e46209e3d562ba94c38890baad074fa9b4ecb710eb21b281ca039f73e628cda5","after_hash":"8f856805a45d7549800a555ddc16e431ffd80afbc2b366096f6c4db744988d71","message":"PR review follow-up: generate parent-specific dangling-reference remediation."} +{"ts":"2026-07-10T21:02:08.467Z","author":"codex-wide-pm-sdk","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:02:08.467Z"},{"op":"add","path":"/metadata/assignee","value":"codex-wide-pm-sdk"}],"before_hash":"8f856805a45d7549800a555ddc16e431ffd80afbc2b366096f6c4db744988d71","after_hash":"4809834c7da2904b70a993eec3a864bc9ebb3feecb6ae39dfb2b334241090030"} +{"ts":"2026-07-10T21:11:57.485Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-07-10T21:11:57.482Z","author":"codex-wide-pm-sdk","text":"Review evidence: parent dangling-reference rows now emit the directly runnable pm update --unset parent remediation while dependency rows retain --replace-deps. Focused validation and full 100% coverage passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:11:57.485Z"}],"before_hash":"4809834c7da2904b70a993eec3a864bc9ebb3feecb6ae39dfb2b334241090030","after_hash":"3710c8f47ad2874bff5e25094ceb019fc3a62a4db42c814010b8807821bfebfa"} +{"ts":"2026-07-10T21:12:00.738Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:12:00.738Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T21:12:00.734Z"},{"op":"add","path":"/metadata/close_reason","value":"Addressed Greptile and CodeRabbit parent-remediation finding with full coverage."}],"before_hash":"3710c8f47ad2874bff5e25094ceb019fc3a62a4db42c814010b8807821bfebfa","after_hash":"ec5316b7f56612b9b09f5db30384e988a292ed0a055a7a14660585515803e5ed"} +{"ts":"2026-07-10T21:12:02.286Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T21:12:02.286Z"}],"before_hash":"ec5316b7f56612b9b09f5db30384e988a292ed0a055a7a14660585515803e5ed","after_hash":"b7dcfeae895f929b83922d1068326c5fea4c5469faef2e02591b66779c011b18"} diff --git a/.agents/pm/history/pm-w7jq.jsonl b/.agents/pm/history/pm-w7jq.jsonl index 283572582..995f8b823 100644 --- a/.agents/pm/history/pm-w7jq.jsonl +++ b/.agents/pm/history/pm-w7jq.jsonl @@ -7,3 +7,13 @@ {"ts":"2026-06-10T11:24:10.812Z","author":"codex-impl","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-06-10T11:24:10.812Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/close_reason","value":"Fixed Sentry release gate classifier for structured tracker_not_initialized CommandErrors; regression and live gate passed."}],"before_hash":"74eb83ffa4ae11004a68556cdfa8ba4d6c9ccdcb83f134e860e82546f8df6c2f","after_hash":"14367695f06e755084b9d54a46f6725e817a8debfa330c25bad221ccc820422b"} {"ts":"2026-06-10T11:24:11.050Z","author":"codex-impl","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-06-10T11:24:11.050Z"}],"before_hash":"14367695f06e755084b9d54a46f6725e817a8debfa330c25bad221ccc820422b","after_hash":"fefc745508532ca6940805d5b51453f8a0e55545fe3e780ea1feb20b5a632953"} {"ts":"2026-06-10T11:24:18.536Z","author":"codex-impl","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-06-10T11:24:18.536Z"},{"op":"add","path":"/metadata/resolution","value":"Added tracker_not_initialized to the Sentry expected handled CLI classifier."},{"op":"add","path":"/metadata/expected_result","value":"The release Sentry/telemetry gate treats structured tracker-not-initialized usage errors as expected handled CLI errors."},{"op":"add","path":"/metadata/actual_result","value":"Live gate passes with PM-CLI-1J ignored as expected handled CLI error and zero relevant critical/high Sentry issues."}],"before_hash":"fefc745508532ca6940805d5b51453f8a0e55545fe3e780ea1feb20b5a632953","after_hash":"2203a32d12788bb4b2b59a3a75c0b0f92a52152125f45b3f1c0165b37be7f4db","message":"Backfill close resolution metadata"} +{"ts":"2026-07-10T20:20:14.676Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:14.676Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"2203a32d12788bb4b2b59a3a75c0b0f92a52152125f45b3f1c0165b37be7f4db","after_hash":"e4b7b4cd45b1e154aa28caef6277557a36af77619617c9399a898c101ba601f6","message":"Regression follow-up: live release gate reports PM-CLI-1Y/1Z starter search/context wrappers around tracker-not-initialized CommandErrors; extend classifier without hiding unhandled failures."} +{"ts":"2026-07-10T20:20:15.067Z","author":"codex-wide-pm-sdk","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:15.067Z"},{"op":"add","path":"/metadata/assignee","value":"codex-wide-pm-sdk"}],"before_hash":"e4b7b4cd45b1e154aa28caef6277557a36af77619617c9399a898c101ba601f6","after_hash":"f2561ce92d90adf8c0dab003c7860daff9cc936631fdd12ef4c702d52212631f"} +{"ts":"2026-07-10T20:20:15.657Z","author":"codex-wide-pm-sdk","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:15.657Z"}],"before_hash":"f2561ce92d90adf8c0dab003c7860daff9cc936631fdd12ef4c702d52212631f","after_hash":"318abdfc123ae1c01ae4ebcc3cb20209001c440a2c80850d50854bd6378bdb92"} +{"ts":"2026-07-10T20:20:16.379Z","author":"codex-wide-pm-sdk","op":"files_add","patch":[{"op":"add","path":"/metadata/files/2","value":{"path":"tests/unit/scripts/release/sentry-telemetry-gate.spec.ts","scope":"project","note":"handled versus unhandled starter wrapper coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:16.379Z"}],"before_hash":"318abdfc123ae1c01ae4ebcc3cb20209001c440a2c80850d50854bd6378bdb92","after_hash":"9637755e996af438ca3e560b7c0c4e185d18cd6dd7fe150f33c1423d3be565b0"} +{"ts":"2026-07-10T20:20:17.369Z","author":"codex-wide-pm-sdk","op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node scripts/run-tests.mjs test -- tests/unit/scripts/release/sentry-telemetry-gate.spec.ts","scope":"project","timeout_seconds":240}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:17.369Z"}],"before_hash":"9637755e996af438ca3e560b7c0c4e185d18cd6dd7fe150f33c1423d3be565b0","after_hash":"c63c25f96e9b24936fc0a74aca0f79361475f6a405171b6a92dd79dd827af0a3"} +{"ts":"2026-07-10T20:20:17.949Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-07-10T20:20:17.948Z","author":"codex-wide-pm-sdk","text":"Live regression evidence: pnpm release:gates passed build/static/docs/context/security/100% coverage/packed smoke/88-command dogfood, then blocked only on PM-CLI-1Y and PM-CLI-1Z. Both are CommandError wrappers from pm starter search/context around expected tracker-not-initialized CLI usage failures; telemetry remained healthy at 0.34% finish errors with zero missing error-code rows."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:20:17.949Z"}],"before_hash":"c63c25f96e9b24936fc0a74aca0f79361475f6a405171b6a92dd79dd827af0a3","after_hash":"035da8e6d04dff224b0d52ebb864e5d2ffca4722ba77d02df930aa520cc5e5f9"} +{"ts":"2026-07-10T20:32:29.275Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/actual_result","value":"Live Sentry gate reports zero blocking critical/high issues, classifies PM-CLI-1Y/1Z as expected handled, and telemetry remains healthy at 0.34% with zero missing error codes."},{"op":"replace","path":"/metadata/expected_result","value":"Live release gates ignore handled starter wrapper usage errors without hiding unhandled failures."},{"op":"replace","path":"/metadata/resolution","value":"Extended the expected-handled Sentry classifier for exact starter search/context wrapper prefixes around tracker-not-initialized failures."},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:29.275Z"}],"before_hash":"035da8e6d04dff224b0d52ebb864e5d2ffca4722ba77d02df930aa520cc5e5f9","after_hash":"209905366605c948a399226ed0c9f579b5dfa75ba2234a50d09f5664cb932e77","message":"Record regression resolution"} +{"ts":"2026-07-10T20:32:36.293Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-07-10T20:32:36.293Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:36.293Z"}],"before_hash":"209905366605c948a399226ed0c9f579b5dfa75ba2234a50d09f5664cb932e77","after_hash":"7314e0a6c0677c32048fc39e99734ad47beffded54c26987e3c7987b8e7ff2c0"} +{"ts":"2026-07-10T20:32:54.668Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:54.668Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:54.665Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"7314e0a6c0677c32048fc39e99734ad47beffded54c26987e3c7987b8e7ff2c0","after_hash":"5f783efeae222ab28adb1004fb1a137b941d8f1ce2ff7d8b6946ddd5d40648a3"} +{"ts":"2026-07-10T20:32:55.793Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:55.793Z"}],"before_hash":"5f783efeae222ab28adb1004fb1a137b941d8f1ce2ff7d8b6946ddd5d40648a3","after_hash":"dcd1066170d0d0b48116103d6991edcc7bd2e985cc12d7cf8e2272e31a8d1015"} diff --git a/.agents/pm/history/pm-yl6c.jsonl b/.agents/pm/history/pm-yl6c.jsonl index 022a8403a..002cbd151 100644 --- a/.agents/pm/history/pm-yl6c.jsonl +++ b/.agents/pm/history/pm-yl6c.jsonl @@ -1,3 +1,15 @@ {"ts":"2026-07-07T15:25:15.388Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Evidence (2026-07-07)\nWith four items legitimately in_progress by codex-agent (pm-w89s/pm-x6jf/pm-cbwg under epic pm-usfg, all updated today), pm next invoked by a different caller recommends pm-w89s with reasons including 'in progress — resume to finish' and assignee: codex-agent in the same payload. The envelope tells one agent to resume another agent's active work.\n\n## Why it matters\nClaim-level protection exists (pm-8t5x conflict-on-held, PR#430 ownership semantics) but the recommendation layer routes agents INTO those conflicts: N agents all get told to resume the same foreign WIP, then N-1 bounce at claim time (the exact thundering-herd pm-114v describes) or, worse, mutate without claiming since claims are advisory. At the pm-9rxu scale story (thousands of concurrent agents) recommendation-side ownership awareness is load-bearing, not polish.\n\n## Fix sketch\nResolve caller identity from the same author-resolution chain mutations use (PM_AUTHOR/config author_default; pm-cpja is making this ergonomic). If the recommended candidate has an assignee different from the caller: skip to the next candidate by default, and annotate the skipped one under a new held_by_others section (id + assignee only) so visibility is preserved without a resume instruction. Unassigned or self-assigned WIP keeps today's resume behavior. When caller identity is unresolvable, keep current behavior but replace the reason with 'in progress — assigned to ; coordinate before resuming'. SDK-first: implement in the shared next-ranking primitive so CLI/MCP/SDK agree; relates to pm-114v (claim --next should reuse the same caller-aware ranking)."},{"op":"add","path":"/metadata/id","value":"pm-yl6c"},{"op":"add","path":"/metadata/title","value":"pm next recommends another agent's assigned in_progress item as 'resume to finish' — recommendation must be caller-aware"},{"op":"add","path":"/metadata/description","value":"Coordination defect: pm next run without matching identity recommends an item assigned to a different agent with the reason 'in progress — resume to finish', inviting work-stealing that pm-8t5x/PR#430 claim semantics were built to prevent. Duplicate check: pm-114v (claim --next atomicity), pm-w8q4 (stale-WIP detection), pm-8t5x (claim test-and-set, closed), ADR pm-9x6k (ready-set definition, closed) are adjacent but none makes the RECOMMENDATION side caller-aware; no prior item found."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["agent-ux","area:cli","concurrency"]},{"op":"add","path":"/metadata/created_at","value":"2026-07-07T15:25:15.388Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-07T15:25:15.388Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/acceptance_criteria","value":"pm next with resolvable caller identity never emits a resume reason for an item assigned to a different agent; foreign-assigned candidates are skipped or listed id-only under held_by_others; unresolvable-identity fallback reason names the assignee and drops the resume imperative; behavior covered by unit tests for self/foreign/unassigned/unresolvable cases across toon and json; SDK and MCP envelopes match"},{"op":"add","path":"/metadata/parent","value":"pm-dj98"},{"op":"add","path":"/metadata/risk","value":"medium"},{"op":"add","path":"/metadata/confidence","value":"high"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"21002979411ea7a33a009902cdb0b8143ee02143728653d264fa8beae5e5e2da","message":""} {"ts":"2026-07-07T15:25:27.335Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-07T15:25:27.335Z"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-114v","kind":"related","created_at":"2026-07-07T15:25:27.319Z"}]}],"before_hash":"21002979411ea7a33a009902cdb0b8143ee02143728653d264fa8beae5e5e2da","after_hash":"ff65130f2e7d5661b18a26e054f96bc5f7f7fbf990671cfab5fdf7c375aa73fa","message":"Link claim --next atomicity work; both should share the caller-aware ranking primitive"} {"ts":"2026-07-07T15:35:23.792Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-07T15:35:23.792Z"},{"op":"add","path":"/metadata/estimated_minutes","value":120}],"before_hash":"ff65130f2e7d5661b18a26e054f96bc5f7f7fbf990671cfab5fdf7c375aa73fa","after_hash":"873a6eee778294150cc0e9c73e9b8ee1da911a0ecf5fce18ad937172d767a793"} +{"ts":"2026-07-10T17:51:51.312Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:51.312Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"873a6eee778294150cc0e9c73e9b8ee1da911a0ecf5fce18ad937172d767a793","after_hash":"5a8e6662785b86323b85de165f26770502f8b152f620a556c2e58309092fe926"} +{"ts":"2026-07-10T17:51:51.931Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:51.931Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"5a8e6662785b86323b85de165f26770502f8b152f620a556c2e58309092fe926","after_hash":"1d80863709e6bc298fbe4d421a1688c5e704d77bf477d48bbbd846f949542b0c","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:51:52.538Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:51:52.538Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:51:52.538Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"1d80863709e6bc298fbe4d421a1688c5e704d77bf477d48bbbd846f949542b0c","after_hash":"d60131ac09971bb9aa6dd4c3a4ed6c200fdbe90b9bfc94912f7a004fd7c9bd8c"} +{"ts":"2026-07-10T18:08:27.541Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:27.541Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"d60131ac09971bb9aa6dd4c3a4ed6c200fdbe90b9bfc94912f7a004fd7c9bd8c","after_hash":"0c86df56cb970aaf93ecb4f539754376879d8f9454314e14ee44e4441942d59e"} +{"ts":"2026-07-10T18:08:28.320Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:28.320Z"}],"before_hash":"0c86df56cb970aaf93ecb4f539754376879d8f9454314e14ee44e4441942d59e","after_hash":"24adfd25b2029e36d7fdbce3e92023f46dde9aef3b902187cd5eff12de1e8a4a"} +{"ts":"2026-07-10T18:08:29.069Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:29.069Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"24adfd25b2029e36d7fdbce3e92023f46dde9aef3b902187cd5eff12de1e8a4a","after_hash":"241e27a679ea81733365e19074981e3b8103ec9b68aae8f2457c748a271e8644"} +{"ts":"2026-07-10T18:08:29.604Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:29.604Z"}],"before_hash":"241e27a679ea81733365e19074981e3b8103ec9b68aae8f2457c748a271e8644","after_hash":"8068dfcbadd8e199a2e1782368e05fe87e1c82390089d35d1c910be65c8b5a0a"} +{"ts":"2026-07-10T18:08:43.013Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:43.013Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts","scope":"project","timeout_seconds":240}]}],"before_hash":"8068dfcbadd8e199a2e1782368e05fe87e1c82390089d35d1c910be65c8b5a0a","after_hash":"b88ad653ef1f6de524c4fd53c24af0bca8d5c40500efc7db165601b69c5412f3"} +{"ts":"2026-07-10T20:32:24.114Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:24.114Z"},{"op":"add","path":"/metadata/resolution","value":"Added caller-aware next-work partitioning that excludes foreign-assigned items and reports them as held_by_others."},{"op":"add","path":"/metadata/expected_result","value":"Agents only receive unassigned or caller-owned recommendations."},{"op":"add","path":"/metadata/actual_result","value":"Foreign work is excluded deterministically while attribution remains visible; PM_AUTHOR and fallback branches are fully covered."}],"before_hash":"b88ad653ef1f6de524c4fd53c24af0bca8d5c40500efc7db165601b69c5412f3","after_hash":"1a438b2083eb369141515bc3da30d523401bdce2050a7400cf60cb61d9eb815f","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:31.061Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T20:32:31.060Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:31.061Z"}],"before_hash":"1a438b2083eb369141515bc3da30d523401bdce2050a7400cf60cb61d9eb815f","after_hash":"7d97aead2e90794846a6be070b17386b735cdff3f3ac27686b3eac2b9f60e91e"} +{"ts":"2026-07-10T20:32:42.002Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:42.002Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:42.000Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"7d97aead2e90794846a6be070b17386b735cdff3f3ac27686b3eac2b9f60e91e","after_hash":"4361c227d3dab54a77e08e785b89f46af3d6ca02d2dce356bdd91eeea1aaa391"} +{"ts":"2026-07-10T20:32:43.168Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:43.168Z"}],"before_hash":"4361c227d3dab54a77e08e785b89f46af3d6ca02d2dce356bdd91eeea1aaa391","after_hash":"a1a91ac36a2b050f8ad5863e9431eff1b64c0daec8d2d610ccb5bf728371251a"} diff --git a/.agents/pm/history/pm-z32q.jsonl b/.agents/pm/history/pm-z32q.jsonl index b75cf6da1..e1014ac6b 100644 --- a/.agents/pm/history/pm-z32q.jsonl +++ b/.agents/pm/history/pm-z32q.jsonl @@ -1,3 +1,16 @@ {"ts":"2026-07-10T16:20:02.384Z","author":"maintainer-agent","op":"create","patch":[{"op":"replace","path":"/body","value":"## Problem\n`pm comments --body \"...\"` fails with `Unknown option --body`. gh/glab muscle memory makes --body the reflexive flag for comment text; every fresh agent session pays ~2 wasted tool calls + a few hundred tokens rediscovering --add. Observed in a real session 2026-07-10 (vanilla @unbrained/pm-cli 2026.7.10).\n\n## Proposed fix\n1. Accept `--body ` as a hidden alias for `--add ` on `pm comments` (and mirror for `--edit` replacement text if cheap).\n2. Extend the unknown-option error path to suggest the nearest valid flag — the unknown-COMMAND suggester exists; unknown-OPTION suggestions are the gap (ranking defect on the command side is pm-g543 / GH-490, related edge).\n\n## Scope guard\nAlias only — no behavior change to --add; help output must not grow (hidden alias) per token-efficiency goals.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/498"},{"op":"add","path":"/metadata/id","value":"pm-z32q"},{"op":"add","path":"/metadata/title","value":"GH-498: pm comments rejects --body — accept it as an alias for --add and hint on unknown options"},{"op":"add","path":"/metadata/description","value":"Upstream GH-498 (filed 2026-07-10). Agents trained on gh/glab reach for 'pm comments --body ...' and hit 'Unknown option --body', costing a failed call plus a --help round-trip per fresh session on one of the highest-frequency write paths."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":3},{"op":"add","path":"/metadata/tags","value":["agent-ux","cli","gh-issue","token-efficiency"]},{"op":"add","path":"/metadata/created_at","value":"2026-07-10T16:20:02.384Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-07-10T16:20:02.384Z"},{"op":"add","path":"/metadata/author","value":"maintainer-agent"},{"op":"add","path":"/metadata/estimated_minutes","value":45},{"op":"add","path":"/metadata/acceptance_criteria","value":"pm comments --body behaves exactly like --add (hidden alias, help stays lean); unknown-option errors on comments suggest the nearest valid flag (e.g. --body -> --add); contract/completion/docs sites updated per the new-flag wiring checklist; regression test covers alias and suggestion; GH-498 closed with a comment linking the fix"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-g543","kind":"related","created_at":"2026-07-10T16:20:02.384Z"}]}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"0f7ddd4a3519a916bec643ca7c6b02d254b212409a6e8dd5c0560fae6f052144","message":"Track GH-498 from ecosystem dogfooding"} {"ts":"2026-07-10T16:20:17.327Z","author":"maintainer-agent","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T16:20:17.327Z"},{"op":"add","path":"/metadata/risk","value":"low"},{"op":"add","path":"/metadata/confidence","value":"high"}],"before_hash":"0f7ddd4a3519a916bec643ca7c6b02d254b212409a6e8dd5c0560fae6f052144","after_hash":"96ecf748f072d58fde0fb33ca56d313663f1b145243a4c7a296aaee2b9454220","message":"Set risk/confidence per active-item metadata baseline"} {"ts":"2026-07-10T16:22:00.814Z","author":"maintainer-agent","op":"update","patch":[{"op":"add","path":"/metadata/dependencies/1","value":{"id":"pm-gdi7","kind":"related","created_at":"2026-07-10T16:22:00.795Z"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T16:22:00.814Z"}],"before_hash":"96ecf748f072d58fde0fb33ca56d313663f1b145243a4c7a296aaee2b9454220","after_hash":"7cc85e4694bfe92d82bc22aefda975fcf2b2f576cf91d2369cfa5a11c559be0d","message":"Cross-link: unknown-option suggester half is owned by pm-gdi7; this item's primary deliverable is the comments --body alias"} +{"ts":"2026-07-10T17:52:00.564Z","author":"codex-root","op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:00.564Z"},{"op":"add","path":"/metadata/assignee","value":"codex-root"}],"before_hash":"7cc85e4694bfe92d82bc22aefda975fcf2b2f576cf91d2369cfa5a11c559be0d","after_hash":"f9db801234ed4614b65055cb73c02b971b10e6689d34accaf2f44f62530c838d"} +{"ts":"2026-07-10T17:52:01.620Z","author":"codex-root","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:01.620Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"f9db801234ed4614b65055cb73c02b971b10e6689d34accaf2f44f62530c838d","after_hash":"1806cd1efec8718b23cf0a56991d4d8e866dd9dab06e1e32790f157f820edb1a","message":"Start broad atomic work-selection and agent recovery bundle; duplicate-safe all-status orientation complete"} +{"ts":"2026-07-10T17:52:02.122Z","author":"codex-root","op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T17:52:02.122Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-07-10T17:52:02.121Z","author":"codex-root","text":"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle."}]}],"before_hash":"1806cd1efec8718b23cf0a56991d4d8e866dd9dab06e1e32790f157f820edb1a","after_hash":"a683972332016e9f96ce6a3da906a7113f58906a22ee36b80545fbf3b2049a78"} +{"ts":"2026-07-10T18:08:36.054Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:36.054Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/cli/commands/next.ts","scope":"project","note":"shared agent work-selection envelope"}]}],"before_hash":"a683972332016e9f96ce6a3da906a7113f58906a22ee36b80545fbf3b2049a78","after_hash":"635acbd2b822d0110c63dc826c7cd79632008b5ba9eb53a8492a3cd11be263f1"} +{"ts":"2026-07-10T18:08:36.624Z","author":"codex-root","op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:36.624Z"}],"before_hash":"635acbd2b822d0110c63dc826c7cd79632008b5ba9eb53a8492a3cd11be263f1","after_hash":"10f17007219e7519ea882b9e997b6296dd89d7575c928be3e21be34d302e06d7"} +{"ts":"2026-07-10T18:08:37.121Z","author":"codex-root","op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:37.121Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"agent-facing command contract"}]}],"before_hash":"10f17007219e7519ea882b9e997b6296dd89d7575c928be3e21be34d302e06d7","after_hash":"a5db3e8a75a4aac07eb1c5eef29a093632141569361227c69621241162f7b610"} +{"ts":"2026-07-10T18:08:37.621Z","author":"codex-root","op":"docs_add","patch":[{"op":"add","path":"/metadata/docs/1","value":{"path":"docs/SDK.md","scope":"project","note":"SDK context primitive contract"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:37.621Z"}],"before_hash":"a5db3e8a75a4aac07eb1c5eef29a093632141569361227c69621241162f7b610","after_hash":"f680de607f234e70b0ac3b2b5afc7025850c786d45e588f9cf667b1446fb94d6"} +{"ts":"2026-07-10T18:08:46.333Z","author":"codex-root","op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/note","value":"hidden comments body alias"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/register-mutation.ts"},{"op":"add","path":"/metadata/files/2","value":{"path":"src/core/item/actionability.ts","scope":"project","note":"shared dependency-aware readiness primitive"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:46.333Z"}],"before_hash":"f680de607f234e70b0ac3b2b5afc7025850c786d45e588f9cf667b1446fb94d6","after_hash":"78c78abbcb571c30894d13f20b684cec6807e44426d33ad75debf400e01a5855"} +{"ts":"2026-07-10T18:08:47.027Z","author":"codex-root","op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T18:08:47.027Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/commands/comments-command.spec.ts","scope":"project","timeout_seconds":240}]}],"before_hash":"78c78abbcb571c30894d13f20b684cec6807e44426d33ad75debf400e01a5855","after_hash":"e3855591ac51fbc0da25b40c2b82b857e28f04847d411a8d11d1c6f77f6dc9d0"} +{"ts":"2026-07-10T20:32:28.698Z","author":"codex-wide-pm-sdk","op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:28.698Z"},{"op":"add","path":"/metadata/resolution","value":"Added hidden --body and --comment aliases for comments --add across CLI and runtime contracts."},{"op":"add","path":"/metadata/expected_result","value":"Agent callers can append comments with common body aliases without breaking canonical help."},{"op":"add","path":"/metadata/actual_result","value":"Both aliases normalize to add, remain hidden from concise help, and work from an installed tarball."}],"before_hash":"e3855591ac51fbc0da25b40c2b82b857e28f04847d411a8d11d1c6f77f6dc9d0","after_hash":"a81ef395cfbca3cbc89081331706a7a4c003c4923a8f878457836a1faad709bf","message":"Record implementation resolution"} +{"ts":"2026-07-10T20:32:35.424Z","author":"codex-wide-pm-sdk","op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-07-10T20:32:35.423Z","author":"codex-wide-pm-sdk","text":"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:35.424Z"}],"before_hash":"a81ef395cfbca3cbc89081331706a7a4c003c4923a8f878457836a1faad709bf","after_hash":"6604d88234f359159e30c7e5759f6b019f9a9b5ef23f4ab963aadd3df5cf85a4"} +{"ts":"2026-07-10T20:32:51.917Z","author":"codex-wide-pm-sdk","op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:51.917Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-07-10T20:32:51.911Z"},{"op":"add","path":"/metadata/close_reason","value":"Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle."}],"before_hash":"6604d88234f359159e30c7e5759f6b019f9a9b5ef23f4ab963aadd3df5cf85a4","after_hash":"9b9fa8f058aeba5391603f17141d98cfcb6e7f968e691ab5ac16bcb280a0a903"} +{"ts":"2026-07-10T20:32:53.344Z","author":"codex-wide-pm-sdk","op":"release","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-07-10T20:32:53.344Z"}],"before_hash":"9b9fa8f058aeba5391603f17141d98cfcb6e7f968e691ab5ac16bcb280a0a903","after_hash":"cf97d875a7163f8523df44be302297b6a20b7d123aa210ae9814fd71cc2730e8"} diff --git a/.agents/pm/issues/pm-69nl.toon b/.agents/pm/issues/pm-69nl.toon index 0665d9103..d9d6fae38 100644 --- a/.agents/pm/issues/pm-69nl.toon +++ b/.agents/pm/issues/pm-69nl.toon @@ -2,11 +2,12 @@ id: pm-69nl title: "GH-501: pm init roots a tracker that workspace discovery cannot find — tracker_not_initialized loop right after init" description: "" type: Issue -status: open +status: closed priority: 2 tags[5]: agent-ux,cli,gh-issue,never-block,onboarding created_at: "2026-07-10T16:39:14.691Z" -updated_at: "2026-07-10T16:39:14.691Z" +updated_at: "2026-07-10T17:52:51.868Z" +closed_at: "2026-07-10T17:52:51.082Z" author: maintainer-agent estimated_minutes: 120 acceptance_criteria: "After pm init . in an empty dir, the next pm command (e.g. pm list) works without --pm-path; discovery accepts cwd/ancestor as tracker root when it holds a valid tracker layout OR init . is redefined to the workspace-discovery spelling with an explicit flag for raw-root mode (decision recorded); tracker_not_initialized recovery bundle probes for a cwd-rooted tracker and emits an executable --pm-path next step when found; no second tracker can be silently created at .agents/pm when a cwd-rooted tracker exists; e2e test covers the init-then-list round-trip for both init modes; GH-501 closed with a comment linking the fix" @@ -14,4 +15,8 @@ risk: medium confidence: high dependencies[1]{id,kind,created_at,author,source_kind}: pm-x26a,related,"2026-07-10T16:39:14.691Z",null,null +comments[2]{created_at,author,text}: + "2026-07-10T17:51:58.069Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T17:52:50.422Z",codex-root,"Live verification correction: current main 0f43e921 already resolves cwd-rooted tracker layouts. Fresh temp proof: pm init . --defaults --author probe created tracker-path mode at cwd; the immediately following bare pm list --json exited 0 and returned an empty item list. Root cause was already removed by the root-layout discovery work linked through pm-x26a/GH-495; no code change belongs in this PR." +close_reason: Already fixed on current main; fresh init-then-list temp proof exits 0 with implicit root-layout discovery. body: "## Problem\npm init (tracker-path mode) roots the tracker at the path directly, but workspace discovery for every subsequent command only probes /.agents/pm. The very next command fails with tracker_not_initialized telling the agent to run pm init first — a hard loop: the error's next_steps re-suggest the action that caused the state. pm init . is the natural git/npm-muscle-memory spelling for 'init here', the failure is delayed (init reports ok:true), and the recovery bundle mentions neither the cwd-rooted tracker nor --pm-path. Worst case the agent re-inits and silently splits state across two trackers.\n\n## Repro (pm 2026.7.10)\nmkdir ws && cd ws && pm init . -> ok, mode tracker-path, tracker_root=. pm list --json -> tracker_not_initialized at /.agents/pm. pm --pm-path . list --json works.\n\n## Fix directions (from GH-501, any-of)\n1. Discovery also accepts cwd (or ancestor) as tracker root when it contains a valid settings.json + tracker layout.\n2. Treat pm init . as the workspace-discovery spelling (.agents/pm under it); require an explicit --tracker-path for raw-root mode.\n3. Minimum: tracker_not_initialized probes cwd for a tracker and suggests --pm-path . as an executable next step.\nOption 1+3 together preserve both modes without breaking existing raw-root users; option 2 is a behavior change needing a deprecation note. Related: pm-x26a (GH-488) made path-target init emit tracker-scoped follow-up commands — same mode-split root cause, this is the discovery half.\n\n## Philosophy tie-in\nNever-block: an error whose recovery loop re-suggests its own cause is the worst agent dead-end.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/501" diff --git a/.agents/pm/issues/pm-6y58.toon b/.agents/pm/issues/pm-6y58.toon index 7228d3f98..85684e538 100644 --- a/.agents/pm/issues/pm-6y58.toon +++ b/.agents/pm/issues/pm-6y58.toon @@ -2,16 +2,33 @@ id: pm-6y58 title: "GH-500: suggested_retry renders boolean flags with a \"\" placeholder — literal suggestion fails" description: "" type: Issue -status: open +status: closed priority: 3 tags[4]: agent-ux,cli,error-recovery,gh-issue created_at: "2026-07-10T16:38:55.646Z" -updated_at: "2026-07-10T16:39:25.461Z" +updated_at: "2026-07-10T20:32:50.588Z" +closed_at: "2026-07-10T20:32:49.329Z" author: maintainer-agent estimated_minutes: 60 acceptance_criteria: suggested_retry renders boolean (arity-0) flags bare (--force) and only value-taking flags with a placeholder; repro from GH-500 (pm close done twice) yields a suggestion that succeeds verbatim; regression test covers boolean and value flags in suggested_retry; GH-500 closed with a comment linking the fix risk: low confidence: high +resolution: Derived recovery placeholders from Commander option arity so boolean flags remain literal. +expected_result: suggested_retry commands are directly runnable for boolean options. +actual_result: "Recovery suggestions now render --force without a bogus value placeholder, including commands preceded by global option values." dependencies[1]{id,kind,created_at,author,source_kind}: pm-gdi7,related,"2026-07-10T16:39:25.446Z",null,null +comments[2]{created_at,author,text}: + "2026-07-10T17:51:59.995Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:33.292Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[3]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/cli/main.ts,project,arity-aware executable recovery retry + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/cli/cli-main-errors.spec.ts,project,300 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "## Problem\nWhen a command fails due to a missing boolean flag, the recovery bundle's suggested_retry appends a \"\" placeholder to the flag. Boolean flags take no value, so running the suggested command verbatim fails with a different error (too many arguments). Agents are the primary consumers of suggested_retry and copy it verbatim — an executable-looking suggestion that errors when followed burns a round-trip and erodes trust in the recovery bundle.\n\n## Repro (pm 2026.7.10)\npm close done on an already-terminal item -> missing: --force -> suggested_retry: pm close done --force \"\" -> running it: too many arguments for 'close'.\n\n## Fix direction\nThe suggested_retry renderer must consult each flag's arity from the flag contract: arity-0 flags rendered bare, value flags with a typed placeholder. Related territory: pm-gdi7 owns unknown-option suggestion polish in the same envelope layer; this is the retry-renderer half.\n\n## Philosophy tie-in\nproject management = context management — recovery bundles are agent context; they must be executable as-is.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/500" diff --git a/.agents/pm/issues/pm-hfg5.toon b/.agents/pm/issues/pm-hfg5.toon index e82d44f7d..15661f01f 100644 --- a/.agents/pm/issues/pm-hfg5.toon +++ b/.agents/pm/issues/pm-hfg5.toon @@ -2,15 +2,31 @@ id: pm-hfg5 title: "pm next repeats the recommended item verbatim as ready[0] — emit an id reference instead" description: "Token-efficiency defect in the pm next envelope: the fully-projected recommended item is duplicated byte-identical as the first entry of the ready list, in both toon and json output. Duplicate check: searched next/duplicate/ready/envelope items — pm-gdi7 (flag hints, list-closed projection), pm-nilh (lean mutation default), pm-a9mc (validate counts-only) are adjacent but none covers the next envelope; no prior item found." type: Issue -status: open +status: closed priority: 2 tags[3]: agent-ux,"area:cli",token-efficiency created_at: "2026-07-07T15:24:51.495Z" -updated_at: "2026-07-07T15:35:23.404Z" +updated_at: "2026-07-10T20:32:45.729Z" +closed_at: "2026-07-10T20:32:44.317Z" author: maintainer-agent estimated_minutes: 60 acceptance_criteria: "pm next (toon and json) no longer contains two full projections of the same item; the ready-list contract (excludes recommended, or id-ref) is documented in command docs and SDK contracts; contract snapshot tests assert non-duplication; MCP pm_next envelope matches" parent: pm-mpbb risk: low confidence: high +resolution: Removed the recommended row from the ready tail while preserving evaluation and ranking semantics. +expected_result: "pm next emits one canonical recommended object without duplicating it as ready[0]." +actual_result: ready begins after the recommendation and SDK context evaluation treats recommended plus ready as the served ranking. +comments[2]{created_at,author,text}: + "2026-07-10T17:51:54.495Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:31.559Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[2]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts,project,240 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "## Evidence (2026-07-07)\npm next default (toon) output is 6123 bytes; the recommended block (id/title/type/status/priority/assignee/tags/updated_at/parent/open_blocker_count, ~450 bytes for pm-w89s) is repeated verbatim as ready[0] with every field identical. Same duplication in --json (8559 bytes). Every agent picking work pays the duplicated projection on every call.\n\n## Why it matters\npm next is the highest-frequency read on the agent hot path (every work-pick loop). At thousands of concurrent agents (pm-9rxu scale story) the duplicated projection is pure waste, and it also trains agents to distrust the envelope (same item appears twice with no marker).\n\n## Fix sketch\nOmit the recommended item from ready (documented contract: ready = candidates EXCLUDING recommended), or replace ready[0] with a bare id reference (recommended_ref). Apply in one shared projection site so toon/json/MCP envelopes stay aligned; update SDK contracts + snapshot tests. Small-workspace neutral; strictly fewer tokens." diff --git a/.agents/pm/issues/pm-l0bu.toon b/.agents/pm/issues/pm-l0bu.toon index bb70dd1cb..f39b497d4 100644 --- a/.agents/pm/issues/pm-l0bu.toon +++ b/.agents/pm/issues/pm-l0bu.toon @@ -2,19 +2,34 @@ id: pm-l0bu title: "GH-489: pm next summary reports blocked: 0 while blocked items exist — blocked companion list missing" description: "" type: Issue -status: open +status: closed priority: 2 tags[4]: agent-ux,"area:next",cli,gh-issue created_at: "2026-07-10T10:32:13.106Z" -updated_at: "2026-07-10T16:57:36.687Z" +updated_at: "2026-07-10T20:32:40.595Z" +closed_at: "2026-07-10T20:32:39.637Z" author: maintainer-agent estimated_minutes: 90 acceptance_criteria: pm next summary.blocked equals the true blocked count in a fresh two-item workspace; blocked companion section lists blocked items with their blockers up to blocked_limit; regression test covers the repro from GH-489; envelope no longer contradicts the unblocks reasons risk: medium confidence: high +resolution: Made pm next preserve lifecycle-blocked and dangling-dependency work in the blocked companion list and summary. +expected_result: Blocked counts and rows remain truthful for graph and lifecycle blocking states. +actual_result: Blocked rows now include lifecycle-only and missing-reference blockers with safe suggestions; regression tests pass. dependencies[2]{id,kind,created_at,author,source_kind}: pm-hfg5,related,"2026-07-10T10:32:13.106Z",null,null pm-yl6c,related,"2026-07-10T10:32:13.106Z",null,null -comments[1]{created_at,author,text}: +comments[3]{created_at,author,text}: "2026-07-10T16:57:36.687Z",maintainer-agent,"Root-cause narrowing (review 2026-07-10, current main build): the GH-489 repro still fails — fresh workspace with one status=blocked item yields summary.blocked: 0 AND candidates: 1, i.e. the status=blocked item is excluded from the candidate pool entirely, not merely miscounted. Contrast: the real workspace reports blocked: 25 with zero status=blocked items (pm stats), so next's blocked counter is derived from open items with open blockers (graph-blocked). Fix likely needs BOTH paths: include status=blocked items in candidates/summary and populate the companion from the union of status-blocked + graph-blocked. Regression test should assert candidates counts them too." + "2026-07-10T17:51:50.816Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:30.507Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[2]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts,project,240 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "## Source\nGitHub issue #489 (dogfood, version 2026.7.9).\n\n## Reproduction\n```bash\npm init uxt --defaults --author probe\npm create task \"First\" # -> uxt-A (open)\npm create task \"Second\" --blocked-by uxt-A # -> uxt-B (status: blocked)\npm next\n```\n`pm next` emits `summary.blocked: 0` and no blocked-companion section, while the same envelope's recommended entry says `unblocks 1 item(s): uxt-B` and `pm list-blocked` correctly shows 1 blocked item. A `blocked_limit: 5` filter is even printed, so the companion surface is advertised but never populated.\n\n## Expected\n`summary.blocked` counts blocked items and the blocked companion lists them with their blockers (as `pm next --help` documents).\n\n## Why it matters for agents\n`pm next` is the primary \"what should I work on\" entrypoint. The envelope contradicts itself (unblocks list vs blocked: 0), so an agent trusting the summary concludes no blocked work exists and skips triage. The data is already computed for the unblocks reasons — only the summary/companion aggregation misses it.\n\n## Scope\nFix the aggregation in the pm next summary + blocked companion; add a regression test covering the blocked-companion path in a fresh workspace. Related output-shape defects: pm-hfg5 (ready[0] duplication), pm-yl6c (cross-agent resume recommendation)." diff --git a/.agents/pm/issues/pm-ol5v.toon b/.agents/pm/issues/pm-ol5v.toon index ef2b5b819..5e9e11f91 100644 --- a/.agents/pm/issues/pm-ol5v.toon +++ b/.agents/pm/issues/pm-ol5v.toon @@ -2,16 +2,35 @@ id: pm-ol5v title: "Dangling dependency references: accepted at create/update, skipped by validate, and treated as satisfied by pm next (silent unblock)" description: "" type: Issue -status: open +status: closed priority: 1 tags: [] created_at: "2026-07-10T17:24:09.633Z" -updated_at: "2026-07-10T17:24:09.633Z" +updated_at: "2026-07-10T21:12:02.286Z" +closed_at: "2026-07-10T21:12:00.734Z" author: maintainer-agent estimated_minutes: 90 acceptance_criteria: "pm validate emits a dedicated warning (with item ids and the dangling dep ids) for structured dependency edges whose id resolves to no item; a typo'd blocker id can never again yield an actionable pm next candidate without a diagnostic surfacing somewhere; mutation-time behavior for nonexistent dep ids is decided (warn vs reject vs --strict flag) and documented; tests cover create, update, validate, and next paths; remediation hint tells users to repair with --replace-deps" risk: medium confidence: high +resolution: Kept missing blocker IDs unresolved and added dependency-reference validation with remediation hints. +expected_result: "Dangling parent, scalar blocker, and structured dependency references cannot silently unblock work." +actual_result: Actionability blocks dangling references and validate reports sorted affected rows plus replace-deps remediation. dependencies[1]{id,kind,created_at,author,source_kind}: pm-l0bu,related,"2026-07-10T17:24:09.633Z",null,null +comments[4]{created_at,author,text}: + "2026-07-10T17:51:56.413Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:32.350Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." + "2026-07-10T20:52:24.944Z",codex-wide-pm-sdk,"Greptile follow-up resolved: the second fixture used unsupported dependency kind relates_to, so its update command did not persist. Replaced it with a valid blocked_by edge; focused validate suite passes 88/88 and now proves two distinct dangling rows survive sorting and deduplication." + "2026-07-10T21:11:57.482Z",codex-wide-pm-sdk,"Review evidence: parent dangling-reference rows now emit the directly runnable pm update --unset parent remediation while dependency rows retain --replace-deps. Focused validation and full 100% coverage passed." +files[3]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/cli/commands/validate.ts,project,dangling dependency diagnostic + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/core/item/actionability.spec.ts tests/unit/commands/validate-command.spec.ts tests/unit/commands/next-command.spec.ts,project,300 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: Addressed Greptile and CodeRabbit parent-remediation finding with full coverage. body: "## Defect\nStructured dependency edges whose `id` resolves to no existing item are invisible to every layer of the system, and the net effect is a SILENT UNBLOCK of work the author intended to be blocked.\n\nVerified in a clean sandbox on v2026.7.10:\n1. `pm create task \"dep to ghost\" --dep \"id=pm-zzzz,kind=blocked_by\"` — accepted, edge stored, no warning.\n2. `pm validate --json` — ok:true, zero referential warnings. Root cause: both graph builders silently drop unresolvable ids — `src/cli/commands/validate.ts:1189` (`!activeItemIds.has(dependencyId) → continue`) for the dependency-cycle graph, and the `canonicalIdByLowercase.get(...)` miss in `buildLifecycleParentGraph` for parents.\n3. `pm next --json` — the ghost-blocked item is counted `ready` and offered as a candidate (summary ready:2 for 2 items). The nonexistent blocker is treated as satisfied.\n\n## Why it matters\n- The pre-PR#499 colon-shorthand bug (pm-zazb) historically wrote malformed `pm-related:*` dep ids in the wild; repos carrying that era's data have exactly these edges, and today nothing will ever tell them.\n- A single-character typo in `--dep id=` inverts the semantics of `blocked_by`: instead of blocking, it releases the item into the actionable pool. For multi-agent fleets picking work via pm next, this dispatches work whose preconditions are unmet — the opposite of the graph's contract, with no error at write time, no warning at validate time, and no trace at read time.\n- Contrast: parent references ARE fail-fast at mutation time (`pm update --parent ` is rejected, pm-z9e9), so deps are the inconsistent surface. A validator-side parent-drift check (stale parent pointing at a since-deleted id) can ride along in the same warning family for symmetry, though the current live graph has zero instances (full-store sweep 2026-07-10: 1708 items, all parent pointers and all structured dep ids resolve except prose mentions).\n\n## Design note for the mutation side\nHard-rejecting nonexistent dep ids at create/update would break legitimate forward-referencing in scripted flows (creating a graph in reverse order). Options: (a) warn on stderr + accept, (b) `--strict-deps` opt-in rejection, (c) accept + rely on validate. The validator warning is the non-negotiable half; the mutation-side choice is the implementer's, but it must be an explicit decision recorded on this item." diff --git a/.agents/pm/issues/pm-w7jq.toon b/.agents/pm/issues/pm-w7jq.toon index 5bb6622cb..bdeace7f6 100644 --- a/.agents/pm/issues/pm-w7jq.toon +++ b/.agents/pm/issues/pm-w7jq.toon @@ -6,19 +6,24 @@ status: closed priority: 1 tags: [] created_at: "2026-06-10T11:16:46.777Z" -updated_at: "2026-06-10T11:24:18.536Z" +updated_at: "2026-07-10T20:32:55.793Z" +closed_at: "2026-07-10T20:32:54.665Z" author: codex-impl parent: pm-mpbb -resolution: Added tracker_not_initialized to the Sentry expected handled CLI classifier. -expected_result: The release Sentry/telemetry gate treats structured tracker-not-initialized usage errors as expected handled CLI errors. -actual_result: Live gate passes with PM-CLI-1J ignored as expected handled CLI error and zero relevant critical/high Sentry issues. -comments[2]{created_at,author,text}: +resolution: Extended the expected-handled Sentry classifier for exact starter search/context wrapper prefixes around tracker-not-initialized failures. +expected_result: Live release gates ignore handled starter wrapper usage errors without hiding unhandled failures. +actual_result: "Live Sentry gate reports zero blocking critical/high issues, classifies PM-CLI-1Y/1Z as expected handled, and telemetry remains healthy at 0.34% with zero missing error codes." +comments[4]{created_at,author,text}: "2026-06-10T11:16:46.777Z",codex-impl,"Duplicate check: searched sentry tracker_not_initialized release gate expected CLI error. Related closed lineage includes pm-yohx and pm-flbo for earlier expected CLI classifier refreshes, but no open item covers tracker_not_initialized; creating focused follow-up." "2026-06-10T11:17:44.477Z",codex-impl,"Evidence: added tracker_not_initialized to the Sentry expected handled CLI classifier and covered it in release automation contract tests. Verification passed: node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts (13 tests); pnpm exec tsc --noEmit --pretty false; node scripts/release/sentry-telemetry-gate.mjs --json --telemetry-mode best-effort (ok true, Sentry relevant critical=0 high=0, PM-CLI-1J in ignored expected CLI list, telemetry error rate 1.62%, missing error-code rows 0)." -files[2]{path,scope,note}: + "2026-07-10T20:20:17.948Z",codex-wide-pm-sdk,"Live regression evidence: pnpm release:gates passed build/static/docs/context/security/100% coverage/packed smoke/88-command dogfood, then blocked only on PM-CLI-1Y and PM-CLI-1Z. Both are CommandError wrappers from pm starter search/context around expected tracker-not-initialized CLI usage failures; telemetry remained healthy at 0.34% finish errors with zero missing error-code rows." + "2026-07-10T20:32:36.293Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[3]{path,scope,note}: scripts/release/sentry-telemetry-gate.mjs,project,expected handled CLI classifier tests/integration/release-automation-contract.spec.ts,project,release gate classifier regression -tests[1]{command,path,scope,timeout_seconds,pm_context_mode,env_set,env_clear,shared_host_safe,assert_stdout_contains,assert_stdout_regex,assert_stderr_contains,assert_stderr_regex,assert_stdout_min_lines,assert_json_field_equals,assert_json_field_gte,note}: - node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts,null,project,240,null,null,null,null,null,null,null,null,null,null,null,null -close_reason: Fixed Sentry release gate classifier for structured tracker_not_initialized CommandErrors; regression and live gate passed. + tests/unit/scripts/release/sentry-telemetry-gate.spec.ts,project,handled versus unhandled starter wrapper coverage +tests[2]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/integration/release-automation-contract.spec.ts,project,240 + node scripts/run-tests.mjs test -- tests/unit/scripts/release/sentry-telemetry-gate.spec.ts,project,240 +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "" diff --git a/.agents/pm/issues/pm-yl6c.toon b/.agents/pm/issues/pm-yl6c.toon index e9f2b8605..02c866a8b 100644 --- a/.agents/pm/issues/pm-yl6c.toon +++ b/.agents/pm/issues/pm-yl6c.toon @@ -2,17 +2,33 @@ id: pm-yl6c title: pm next recommends another agent's assigned in_progress item as 'resume to finish' — recommendation must be caller-aware description: "Coordination defect: pm next run without matching identity recommends an item assigned to a different agent with the reason 'in progress — resume to finish', inviting work-stealing that pm-8t5x/PR#430 claim semantics were built to prevent. Duplicate check: pm-114v (claim --next atomicity), pm-w8q4 (stale-WIP detection), pm-8t5x (claim test-and-set, closed), ADR pm-9x6k (ready-set definition, closed) are adjacent but none makes the RECOMMENDATION side caller-aware; no prior item found." type: Issue -status: open +status: closed priority: 2 tags[3]: agent-ux,"area:cli",concurrency created_at: "2026-07-07T15:25:15.388Z" -updated_at: "2026-07-07T15:35:23.792Z" +updated_at: "2026-07-10T20:32:43.168Z" +closed_at: "2026-07-10T20:32:42.000Z" author: maintainer-agent estimated_minutes: 120 acceptance_criteria: pm next with resolvable caller identity never emits a resume reason for an item assigned to a different agent; foreign-assigned candidates are skipped or listed id-only under held_by_others; unresolvable-identity fallback reason names the assignee and drops the resume imperative; behavior covered by unit tests for self/foreign/unassigned/unresolvable cases across toon and json; SDK and MCP envelopes match parent: pm-dj98 risk: medium confidence: high +resolution: Added caller-aware next-work partitioning that excludes foreign-assigned items and reports them as held_by_others. +expected_result: Agents only receive unassigned or caller-owned recommendations. +actual_result: Foreign work is excluded deterministically while attribution remains visible; PM_AUTHOR and fallback branches are fully covered. dependencies[1]{id,kind,created_at,author,source_kind}: pm-114v,related,"2026-07-07T15:25:27.319Z",null,null +comments[2]{created_at,author,text}: + "2026-07-10T17:51:52.538Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:31.060Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[2]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/commands/next-command.spec.ts,project,240 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "## Evidence (2026-07-07)\nWith four items legitimately in_progress by codex-agent (pm-w89s/pm-x6jf/pm-cbwg under epic pm-usfg, all updated today), pm next invoked by a different caller recommends pm-w89s with reasons including 'in progress — resume to finish' and assignee: codex-agent in the same payload. The envelope tells one agent to resume another agent's active work.\n\n## Why it matters\nClaim-level protection exists (pm-8t5x conflict-on-held, PR#430 ownership semantics) but the recommendation layer routes agents INTO those conflicts: N agents all get told to resume the same foreign WIP, then N-1 bounce at claim time (the exact thundering-herd pm-114v describes) or, worse, mutate without claiming since claims are advisory. At the pm-9rxu scale story (thousands of concurrent agents) recommendation-side ownership awareness is load-bearing, not polish.\n\n## Fix sketch\nResolve caller identity from the same author-resolution chain mutations use (PM_AUTHOR/config author_default; pm-cpja is making this ergonomic). If the recommended candidate has an assignee different from the caller: skip to the next candidate by default, and annotate the skipped one under a new held_by_others section (id + assignee only) so visibility is preserved without a resume instruction. Unassigned or self-assigned WIP keeps today's resume behavior. When caller identity is unresolvable, keep current behavior but replace the reason with 'in progress — assigned to ; coordinate before resuming'. SDK-first: implement in the shared next-ranking primitive so CLI/MCP/SDK agree; relates to pm-114v (claim --next should reuse the same caller-aware ranking)." diff --git a/.agents/pm/issues/pm-z32q.toon b/.agents/pm/issues/pm-z32q.toon index ecb2eb97d..465deed48 100644 --- a/.agents/pm/issues/pm-z32q.toon +++ b/.agents/pm/issues/pm-z32q.toon @@ -2,17 +2,34 @@ id: pm-z32q title: "GH-498: pm comments rejects --body — accept it as an alias for --add and hint on unknown options" description: "Upstream GH-498 (filed 2026-07-10). Agents trained on gh/glab reach for 'pm comments --body ...' and hit 'Unknown option --body', costing a failed call plus a --help round-trip per fresh session on one of the highest-frequency write paths." type: Issue -status: open +status: closed priority: 3 tags[4]: agent-ux,cli,gh-issue,token-efficiency created_at: "2026-07-10T16:20:02.384Z" -updated_at: "2026-07-10T16:22:00.814Z" +updated_at: "2026-07-10T20:32:53.344Z" +closed_at: "2026-07-10T20:32:51.911Z" author: maintainer-agent estimated_minutes: 45 acceptance_criteria: "pm comments --body behaves exactly like --add (hidden alias, help stays lean); unknown-option errors on comments suggest the nearest valid flag (e.g. --body -> --add); contract/completion/docs sites updated per the new-flag wiring checklist; regression test covers alias and suggestion; GH-498 closed with a comment linking the fix" risk: low confidence: high +resolution: Added hidden --body and --comment aliases for comments --add across CLI and runtime contracts. +expected_result: Agent callers can append comments with common body aliases without breaking canonical help. +actual_result: "Both aliases normalize to add, remain hidden from concise help, and work from an installed tarball." dependencies[2]{id,kind,created_at,author,source_kind}: pm-g543,related,"2026-07-10T16:20:02.384Z",null,null pm-gdi7,related,"2026-07-10T16:22:00.795Z",null,null +comments[2]{created_at,author,text}: + "2026-07-10T17:52:02.121Z",codex-root,"Implementation start: active in the broad atomic work-selection and agent recovery PR. Live all-status duplicate check, full item metadata, closed prerequisite lineage, GitHub issues, security alerts, dependency audit, release state, and Sentry issue surface were reviewed before claiming. This item shares CLI/SDK contracts, recovery envelopes, and end-to-end verification with the seven sibling items in the bundle." + "2026-07-10T20:32:35.423Z",codex-wide-pm-sdk,"Verification: pnpm release:gates passed build/typecheck/static/docstring ratchets/docs/context/security, 100/100/100/100 coverage with 5,524 tests, packed npx smoke, and 88-command package dogfood; isolated installed-tarball acceptance created two items, atomically claimed one, accepted comments --body, detected one dangling dependency, and imported PmClient. Live Sentry/telemetry: 0 blocking critical/high, 0.34% finish error rate, zero missing error-code rows. pm health: all 7 checks ok; history drift: 0 across 1,709 items." +files[3]{path,scope,note}: + src/cli/commands/next.ts,project,shared agent work-selection envelope + src/cli/register-mutation.ts,project,hidden comments body alias + src/core/item/actionability.ts,project,shared dependency-aware readiness primitive +tests[1]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs test -- tests/unit/commands/comments-command.spec.ts,project,240 +docs[2]{path,scope,note}: + docs/COMMANDS.md,project,agent-facing command contract + docs/SDK.md,project,SDK context primitive contract +close_reason: "Implemented and verified in the atomic work-selection, dependency-integrity, recovery, alias, and telemetry bundle." body: "## Problem\n`pm comments --body \"...\"` fails with `Unknown option --body`. gh/glab muscle memory makes --body the reflexive flag for comment text; every fresh agent session pays ~2 wasted tool calls + a few hundred tokens rediscovering --add. Observed in a real session 2026-07-10 (vanilla @unbrained/pm-cli 2026.7.10).\n\n## Proposed fix\n1. Accept `--body ` as a hidden alias for `--add ` on `pm comments` (and mirror for `--edit` replacement text if cheap).\n2. Extend the unknown-option error path to suggest the nearest valid flag — the unknown-COMMAND suggester exists; unknown-OPTION suggestions are the gap (ranking defect on the command side is pm-g543 / GH-490, related edge).\n\n## Scope guard\nAlias only — no behavior change to --add; help output must not grow (hidden alias) per token-efficiency goals.\n\n## Source\nhttps://github.com/unbraind/pm-cli/issues/498" diff --git a/CHANGELOG.md b/CHANGELOG.md index 872a45a16..b77231d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,20 @@ ## Unreleased +### Added + +- pm claim --next: atomically claim the next actionable item so parallel agents each get distinct work ([pm-114v](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/features/pm-114v.toon)) + ### Fixed +- Dangling dependency references: accepted at create/update, skipped by validate, and treated as satisfied by pm next (silent unblock) ([pm-ol5v](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ol5v.toon)) +- Classify tracker-not-initialized Sentry CommandErrors as expected handled CLI errors ([pm-w7jq](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-w7jq.toon)) +- GH-498: pm comments rejects --body — accept it as an alias for --add and hint on unknown options ([pm-z32q](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-z32q.toon)) +- GH-500: suggested_retry renders boolean flags with a "" placeholder — literal suggestion fails ([pm-6y58](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-6y58.toon)) +- pm next repeats the recommended item verbatim as ready\[0\] — emit an id reference instead ([pm-hfg5](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-hfg5.toon)) +- pm next recommends another agent's assigned in_progress item as 'resume to finish' — recommendation must be caller-aware ([pm-yl6c](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-yl6c.toon)) +- GH-489: pm next summary reports blocked: 0 while blocked items exist — blocked companion list missing ([pm-l0bu](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-l0bu.toon)) +- GH-501: pm init roots a tracker that workspace discovery cannot find — tracker_not_initialized loop right after init ([pm-69nl](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-69nl.toon)) - GH-496: extension flags declared list:true don't accumulate repeated occurrences — host maps to scalar, Commander last-wins drops values ([pm-kfq5](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-kfq5.toon)) - Reserved item-field name collisions are invisible to SDK lint/preflight/harness ([pm-ghf1](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-ghf1.toon)) - Repeated --ac flags on create/update silently keep only the last acceptance criterion ([pm-b84u](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-b84u.toon)) @@ -666,7 +678,6 @@ ### Fixed - GH-189: create --parent should fail fast on unresolved references ([pm-p9hw](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-p9hw.toon)) -- Classify tracker-not-initialized Sentry CommandErrors as expected handled CLI errors ([pm-w7jq](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-w7jq.toon)) - GH-190: SDK locateItem should default idPrefix or throw explicit argument errors ([pm-rjh9](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-rjh9.toon)) - Telemetry queue max_attempts backlog health warning: surface items near retry exhaustion in pm health ([pm-irc7](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-irc7.toon)) - Harden history replay and compact diff against malformed patch entries ([pm-kf5q](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-kf5q.toon)) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 4d6c82a48..5116dbd84 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -101,7 +101,7 @@ pm dedupe-audit --mode parent_scope --limit 20 pm dedupe-merge --keep pm-canonical --close pm-duplicate --dry-run ``` -Use `pm next` when the only question is "what should I work on now?" — it is the distilled, dependency-aware version of `context`. It computes READINESS rather than just listing active work: an item is **ready** when its status is active, it has no open blockers (neither the scalar `blocked_by` nor any `blocked_by` dependency points at a non-terminal item), and it has no open children (a concrete leaf, never an Epic with open work beneath it). Open containers whose descendants are already terminal are governance closeout rows: they stay out of the normal ready queue while concrete leaf work exists, then surface when closeout is the next action. `pm next` returns one `recommended` item with a deterministic rationale (status, priority, deadline, blocker clearance, parent advancement, downstream unblocks, and completed-container closeout when applicable), a ranked `ready` queue, and a `blocked` companion queue annotated with each item's open blockers so you know exactly what to clear next. In-progress work is recommended before unstarted work. Scope to one epic with `--parent `, cap rows with `--limit`/`--blocked-limit`, drop the blocked list with `--ready-only`, and render markdown with `--format markdown`. Available over MCP as the narrow `pm_next` tool (and the `next` action of `pm_run`). +Use `pm next` when the only question is "what should I work on now?" It computes dependency-aware readiness, keeps dangling dependency ids blocked until repaired, and combines lifecycle-blocked plus graph-blocked work in the companion queue. The `recommended` projection is excluded from the `ready` tail, while foreign-owned in-progress work is summarized under `held_by_others`. Use `pm claim --next` to atomically select and claim caller-available work with bounded conflict retry. Scope with `--parent`, cap rows with `--limit`/`--blocked-limit`, or use `--ready-only` for the tightest projection. ```bash pm next # recommend the next action + ready/blocked queues @@ -511,6 +511,7 @@ pm remind "Review PR" --at +2d # Reminder (--at de ```bash pm claim +pm claim --next pm release pm release --allow-audit-release --author ``` @@ -531,7 +532,7 @@ pm notes --add "Keep renderer changes isolated to TOON output." pm learnings --add "Use runtime contracts instead of duplicating flag lists." ``` -Use comments for progress and evidence, notes for implementation context, and learnings for durable future guidance. For comments, choose exactly one input source (`[text]`, `--add`, `--stdin`, or `--file`) per invocation. To clean up obsolete orchestration notes, `--edit ` rewrites the comment at a 1-based index (replacement text from `[text]`/`--add`/`--stdin`/`--file`) and `--delete ` removes it; both record history and honor ownership rules (`--allow-audit-comment` for non-owner audits). +Use comments for progress and evidence, notes for implementation context, and learnings for durable future guidance. `--body` is a hidden compatibility alias for comments `--add`; choose exactly one input source (`[text]`, `--add`/`--body`, `--stdin`, or `--file`) per invocation. To clean up obsolete orchestration notes, `--edit ` rewrites the comment at a 1-based index and `--delete ` removes it; both record history and honor ownership rules. ## Linked Artifacts diff --git a/docs/SDK.md b/docs/SDK.md index d74f130f9..b3f8f4a0a 100644 --- a/docs/SDK.md +++ b/docs/SDK.md @@ -493,6 +493,12 @@ integration, or agent runtime. Presentation stays outside the SDK primitive: callers choose their own rendering while the data shape remains shared with the CLI. +The `next` result excludes the recommended item from the `ready` tail, reports +foreign-owned work in `held_by_others`, and treats dangling dependency ids as +unresolved blockers. The CLI `claim --next` composition uses the same public +ranking result and atomic claim mutation, so custom SDK tools can apply their own +selection policy without losing concurrency safety. + Lifecycle convenience methods and the matching top-level functions (`create`, `update`, `close`, `claim`, `release`, `copy`, `deleteItem`, `restore`, `focus`, `startTask`, `pauseTask`, and `closeTask`) use the same mutation paths diff --git a/scripts/release/sentry-telemetry-gate.mjs b/scripts/release/sentry-telemetry-gate.mjs index b33b4eaa2..9099ac8c4 100644 --- a/scripts/release/sentry-telemetry-gate.mjs +++ b/scripts/release/sentry-telemetry-gate.mjs @@ -135,6 +135,11 @@ const KNOWN_EXPECTED_HANDLED_CLI_ISSUE_PATTERNS = [ // Structured problem+json payload for commands run before a tracker exists. // This is a handled CLI usage error, not an unhandled runtime failure. "tracker_not_initialized", + // Starter SDK wrappers preserve handled command failures as CommandError + // titles, but Sentry truncates the embedded problem+json value before the + // tracker_not_initialized code. Keep the exact wrapper prefixes narrow. + "pm starter search: `pm search` failed:", + "pm starter context: `pm context` failed:", // Expected handled failures from package dogfood/release-smoke commands. // These stay behind the CommandError/PmCliError + isUnhandled=false guard. "pm-web exited with code", diff --git a/src/cli/commands/claim.ts b/src/cli/commands/claim.ts index 710a07d59..2f39b09cc 100644 --- a/src/cli/commands/claim.ts +++ b/src/cli/commands/claim.ts @@ -14,6 +14,7 @@ import { getSettingsPath, resolvePmRoot } from "../../core/store/paths.js"; import { readSettings } from "../../core/store/settings.js"; import { resolveAuthor } from "../../core/shared/author.js"; import { wrapOwnershipConflict } from "./annotation-command.js"; +import { runNext, type NextRecommendation, type NextOptions } from "./next.js"; /** * Documents the claim result payload exchanged by command, SDK, and package integrations. @@ -27,6 +28,24 @@ export interface ClaimResult { warnings?: string[]; } +/** Result of atomically selecting and claiming the next caller-available item. */ +export interface ClaimNextResult extends ClaimResult { + recommendation: NextRecommendation; + attempts: number; +} + +/** Returns whether a failed claim lost the atomic test-and-set race. */ +export function isAlreadyClaimedError(error: unknown): boolean { + return error instanceof PmCliError && error.context?.code === "already_claimed_by"; +} + +type ClaimRunner = ( + id: string, + force: boolean, + global: GlobalOptions, + options: ClaimMutationOptions, +) => Promise; + /** * Documents the release result payload exchanged by command, SDK, and package integrations. */ @@ -129,6 +148,53 @@ export async function runClaim( }; } +/** + * Selects ranked actionable work and claims the first candidate still available + * under the item lock. Conflicts caused by parallel claimers advance to the next + * candidate instead of returning a thundering-herd failure. + */ +export async function runClaimNext( + force: boolean, + global: GlobalOptions, + options: ClaimMutationOptions = {}, + nextOptions: NextOptions = {}, +): Promise { + const next = await runNext({ ...nextOptions, callerAuthor: options.author, limit: "10" }, global); + const recommendations = [next.recommended, ...next.ready] + .filter((entry): entry is NextRecommendation => entry !== null) + .map((entry) => ({ ...entry, reasons: "reasons" in entry ? entry.reasons : ["ranked ready candidate"] })); + return claimNextFromRecommendations(recommendations, force, global, options); +} + +/** Claims the first still-available row from a pre-ranked recommendation set. */ +export async function claimNextFromRecommendations( + recommendations: NextRecommendation[], + force: boolean, + global: GlobalOptions, + options: ClaimMutationOptions = {}, + claimRunner: ClaimRunner = runClaim, +): Promise { + let attempts = 0; + let lastSkipped: ClaimNextResult | undefined; + for (const recommendation of recommendations) { + attempts += 1; + try { + const claimed = await claimRunner(recommendation.id, force, global, options); + const result = { ...claimed, recommendation, attempts }; + if (claimed.skipped) lastSkipped = result; + else return result; + } catch (error: unknown) { + if (!isAlreadyClaimedError(error)) throw error; + } + } + if (lastSkipped) return lastSkipped; + throw new PmCliError("No actionable item remained available to claim", EXIT_CODE.CONFLICT, { + code: "no_available_next_item", + why: "Every ranked candidate was claimed by another agent before this atomic selection completed.", + nextSteps: ["Run pm claim --next again to refresh the ranked candidate set."], + }); +} + /** * Implements run release for the public runtime surface of this module. */ diff --git a/src/cli/commands/completion.ts b/src/cli/commands/completion.ts index 6590967ae..7517ee136 100644 --- a/src/cli/commands/completion.ts +++ b/src/cli/commands/completion.ts @@ -616,7 +616,7 @@ export function generateBashScript( ` COMPREPLY=(${compgen(`${LIFECYCLE_ACTIONS} ${PACKAGE_LIFECYCLE_FLAGS}`)})`, " ;;", " comments)", - ` COMPREPLY=(${compgen("--add --stdin --file --edit --delete --limit --author --message --allow-audit-comment --force --json --quiet --no-changed-fields --pm-path --path --no-extensions --no-pager --profile --help")})`, + ` COMPREPLY=(${compgen("--add --body --stdin --file --edit --delete --limit --author --message --allow-audit-comment --force --json --quiet --no-changed-fields --pm-path --path --no-extensions --no-pager --profile --help")})`, " ;;", " comments-audit)", ` COMPREPLY=(${compgen("--status --type --tag --priority --parent --sprint --release --assignee --assignee-filter --limit-items --limit --full-history --latest --json --quiet --no-changed-fields --pm-path --path --no-extensions --no-pager --profile --help")})`, @@ -1508,6 +1508,8 @@ ${zshSearchRuntimeFieldFlags} '--json[Output JSON]' \\ '--author[Mutation author]:author' \\ '--message[History message]:message' \\ '--force[Force override]' \\ + '--if-available[Skip work held by another author]' \\ + '--next[Atomically claim the next actionable item]' \\ '--json[Output JSON]' \\ '--quiet[Suppress stdout]' ;; @@ -2408,6 +2410,8 @@ complete -c pm -n '__fish_seen_subcommand_from append' -l force -d 'Force overri complete -c pm -n '__fish_seen_subcommand_from claim release start-task pause-task close close-task delete' -l author -d 'Mutation author' -r complete -c pm -n '__fish_seen_subcommand_from claim release start-task pause-task close close-task delete' -l message -d 'History message' -r complete -c pm -n '__fish_seen_subcommand_from claim release start-task pause-task close close-task delete' -l force -d 'Force override' +complete -c pm -n '__fish_seen_subcommand_from claim' -l if-available -d 'Skip work held by another author' +complete -c pm -n '__fish_seen_subcommand_from claim' -l next -d 'Atomically claim the next actionable item' complete -c pm -n '__fish_seen_subcommand_from close close-task' -l validate-close -d 'Validate closure metadata mode' -r -a 'off warn strict' complete -c pm -n '__fish_seen_subcommand_from close' -l reason -d 'Closure reason' -r complete -c pm -n '__fish_seen_subcommand_from close' -l close-reason -d 'Alias for --reason' -r diff --git a/src/cli/commands/next.ts b/src/cli/commands/next.ts index fb20a813b..d56703369 100644 --- a/src/cli/commands/next.ts +++ b/src/cli/commands/next.ts @@ -62,6 +62,8 @@ export interface NextOptions { readyOnly?: boolean; format?: string; explainRanking?: boolean; + /** Internal caller override used to align claim-next ranking with --author. */ + callerAuthor?: string; [key: string]: unknown; } @@ -106,6 +108,7 @@ export interface NextResult { recommended: NextRecommendation | null; ready: NextActionableItem[]; blocked: NextActionableItem[]; + held_by_others: Array<{ id: string; assignee: string }>; summary: NextSummary; filters: { type: string | null; @@ -207,6 +210,26 @@ function buildRecommendationReasons( return reasons; } +function partitionCallerOwnedReady( + ready: ActionableEntry[], + fallbackAuthor: string, + explicitAuthor?: string, +): { available: ActionableEntry[]; held: Array<{ id: string; assignee: string }> } { + const caller = (explicitAuthor ?? process.env.PM_AUTHOR ?? fallbackAuthor).trim() || "unknown"; + const available: ActionableEntry[] = []; + const held: Array<{ id: string; assignee: string }> = []; + for (const entry of ready) { + const assignee = typeof entry.item.assignee === "string" ? entry.item.assignee.trim() : ""; + const isForeignWork = assignee.length > 0 && assignee !== caller; + if (isForeignWork) held.push({ id: entry.item.id, assignee }); + else available.push(entry); + } + return { available, held }; +} + +/** Test-only access to deterministic next-work partitioning edge cases. */ +export const _testOnlyNextCommand = { partitionCallerOwnedReady }; + // Renders a deadline as a date token plus a relative tag (overdue/today/in Nd). // The relative delta is computed on UTC calendar dates (both sides normalized to // midnight) so a deadline due today never reads as "overdue 1d" just because the @@ -296,10 +319,11 @@ function buildNextSuggestions(recommended: NextRecommendation | null, blockedRow return undefined; } if (blockedRows.length > 0) { + const blockedWithReferences = blockedRows.find((item) => item.blockers.length > 0); return [ - `${blockedRows.length} item(s) are blocked; unblock the top one by closing ${blockedRows[0].blockers - .map((blocker) => blocker.id) - .join(", ")}`, + blockedWithReferences + ? `${blockedRows.length} item(s) are blocked; unblock the top one by closing ${blockedWithReferences.blockers.map((blocker) => blocker.id).join(", ")}` + : `${blockedRows.length} item(s) are blocked; add blocker context or move the top item back to an active status`, "pm next --ready-only after a blocker closes to re-check ready work", 'pm create --type Task --title "..." to add new ready work', ]; @@ -380,7 +404,8 @@ export async function runNext(options: NextOptions, global: GlobalOptions): Prom const report = computeActionabilityReport(candidates, corpus, statusRegistry); const childrenByParent = buildChildrenByParent(corpus); - const rankedReady = rankNextReadyEntries(report.ready, childrenByParent, statusRegistry); + const callerPartition = partitionCallerOwnedReady(report.ready, settings.author_default, options.callerAuthor); + const rankedReady = rankNextReadyEntries(callerPartition.available, childrenByParent, statusRegistry); const rankedBlocked = [...report.blocked].sort((left, right) => compareCriticalItems(left.item, right.item, statusRegistry), ); @@ -406,8 +431,9 @@ export async function runNext(options: NextOptions, global: GlobalOptions): Prom output_default: "toon", now, recommended, - ready: readyRows.slice(0, limit), + ready: readyRows.slice(1, limit + 1), blocked: readyOnly ? [] : blockedRows.slice(0, blockedLimit), + held_by_others: callerPartition.held, summary: { recommended: recommended !== null, ready: readyRows.length, diff --git a/src/cli/commands/validate.ts b/src/cli/commands/validate.ts index e36072362..185418739 100644 --- a/src/cli/commands/validate.ts +++ b/src/cli/commands/validate.ts @@ -70,6 +70,7 @@ type ValidateCheckName = | "metadata" | "resolution" | "lifecycle" + | "dependency_references" | "files" | "command_references" | "history_drift" @@ -1205,6 +1206,55 @@ function buildLifecycleDependencyGraph(activeItems: ItemWithBody[], idPrefix = " } /* c8 ignore stop */ +function buildDependencyReferencesCheck( + items: ItemWithBody[], + verboseDiagnostics: boolean, +): { check: ValidateCheck; warnings: string[] } { + const knownIds = new Set(items.map((item) => item.id.trim().toLowerCase())); + const rows: string[] = []; + for (const item of items) { + const parent = toMeaningfulString(item.parent); + if (parent && !knownIds.has(parent.toLowerCase())) rows.push(`${item.id}:${parent}:parent`); + const scalarBlocker = toMeaningfulString(item.blocked_by); + if (scalarBlocker && !knownIds.has(scalarBlocker.toLowerCase())) { + rows.push(`${item.id}:${scalarBlocker}:blocked_by`); + } + for (const dependency of item.dependencies ?? []) { + const dependencyId = toMeaningfulString(dependency.id); + if (dependencyId && !knownIds.has(dependencyId.toLowerCase())) { + rows.push(`${item.id}:${dependencyId}:${dependency.kind}`); + } + } + } + const uniqueRows = [...new Set(rows)].sort((left, right) => left.localeCompare(right)); + const diagnosticLimit = verboseDiagnostics ? Number.POSITIVE_INFINITY : DIAGNOSTIC_LIST_SUMMARY_LIMIT; + const summarizedRows = summarizeList(uniqueRows, diagnosticLimit); + const hints = summarizeList( + uniqueRows.map((row) => { + const [itemId, , kind] = row.split(":"); + return kind === "parent" + ? `pm update ${itemId} --unset parent` + : `pm update ${itemId} --replace-deps ''`; + }), + diagnosticLimit, + ); + return { + check: { + name: "dependency_references", + status: uniqueRows.length === 0 ? "ok" : "warn", + details: { + checked_items: items.length, + dangling_reference_count: uniqueRows.length, + dangling_reference_rows: summarizedRows.values, + dangling_reference_rows_truncated: summarizedRows.truncated, + remediation_hints: hints.values, + remediation_hints_truncated: hints.truncated, + }, + }, + warnings: uniqueRows.length > 0 ? [`validate_dangling_dependency_references:${uniqueRows.length}`] : [], + }; +} + function extractItemIds(value: string, idPrefix = "pm"): string[] { const normalizedPrefix = (idPrefix.trim().toLowerCase() || "pm").replace(/-+$/g, ""); const pattern = new RegExp(`(?:^|[^a-z0-9-])(${escapeRegExp(normalizedPrefix)}-[a-z0-9][a-z0-9-]*)`, "gi"); @@ -2332,6 +2382,7 @@ export const _testOnlyValidateCommand = { applyValidateFixes, attachValidateFixHints, buildCommandReferencesCheck, + buildDependencyReferencesCheck, buildCloseReasonBackfillRows, buildFilesCheck, buildLifecycleCheck, @@ -2441,6 +2492,7 @@ async function executeRequestedValidateChecks(params: { ); state.terminalParentFixRows = built.terminalParentFixRows; recordValidateCheck(state, built, fixHintsEnabled); + recordValidateCheck(state, buildDependencyReferencesCheck(params.items, fullDiagnostics), fixHintsEnabled); } if (params.requestedChecks.has("files")) { const built = await buildFilesCheck( diff --git a/src/cli/main.ts b/src/cli/main.ts index 2fedc3df8..152709ea8 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -357,8 +357,16 @@ function resolveRecoverySuggestedRetry( } const missingFlag = inferredMissing?.[0]; const normalizedMissing = missingFlag ? normalizeLongOptionFlag(missingFlag) : undefined; + const commandName = invocationArgv.find((token) => program.commands.some((candidate) => candidate.name() === token)); + const command = commandName ? program.commands.find((candidate) => candidate.name() === commandName) : undefined; + const missingOption = normalizedMissing + ? command?.options.find((option) => option.flags.split(/[ ,|]+/).includes(normalizedMissing)) + : undefined; + const missingTokens = normalizedMissing + ? missingOption?.isBoolean() === true ? [normalizedMissing] : [normalizedMissing, ""] + : []; const suggestedRetry = normalizedMissing - ? renderAttemptedCommand([...invocationArgv, normalizedMissing, ""]) + ? renderAttemptedCommand([...invocationArgv, ...missingTokens]) : attemptedCommand; return suggestedRetry === attemptedCommand ? undefined : suggestedRetry; } diff --git a/src/cli/register-mutation.ts b/src/cli/register-mutation.ts index a046c626e..ce484dfbf 100644 --- a/src/cli/register-mutation.ts +++ b/src/cli/register-mutation.ts @@ -1026,7 +1026,8 @@ function resolveCommentSources(text: string | undefined, options: Record", "Item id") .argument("[text]", "Optional comment text shorthand (equivalent to --add)") @@ -1956,6 +1957,8 @@ export function registerMutationCommands(program: Command): void { .option("--force", "Force ownership override") .description("List, add, edit, or delete comments for an item.") .action(runCommentsAction); + addHiddenOption(commentsCommand, "--body ", "Alias for --add", false); + addHiddenOption(commentsCommand, "--comment ", "Alias for --add", false); program .command("notes") diff --git a/src/cli/register-operations.ts b/src/cli/register-operations.ts index c49c9c249..c9115af71 100644 --- a/src/cli/register-operations.ts +++ b/src/cli/register-operations.ts @@ -11,7 +11,7 @@ import { PmCliError } from "../core/shared/errors.js"; import { resolvePmRoot } from "../core/store/paths.js"; import { readSettings } from "../core/store/settings.js"; import { resolveStartTaskInProgressStatus } from "../sdk/start-task-status.js"; -import { runClaim, runRelease } from "./commands/claim.js"; +import { runClaim, runClaimNext, runRelease } from "./commands/claim.js"; import { runClose } from "./commands/close.js"; import { runContracts } from "./commands/contracts.js"; import { runGc } from "./commands/gc.js"; @@ -395,13 +395,17 @@ async function runContractsAction(options: Record, command: Com } } -async function runClaimAction(id: string, options: Record, command: Command): Promise { +async function runClaimAction(id: string | undefined, options: Record, command: Command): Promise { const globalOptions = getGlobalOptions(command); const startedAt = Date.now(); - const result = await runClaim(id, Boolean(options.force), globalOptions, { + const lifecycleOptions = { ...buildLifecycleMutationOptions(options), ifAvailable: options.ifAvailable === true, - }); + }; + requireClaimTarget(id, options.next === true); + const result = options.next === true + ? await runClaimNext(Boolean(options.force), globalOptions, lifecycleOptions) + : await runClaim(id as string, Boolean(options.force), globalOptions, lifecycleOptions); await invalidateSearchCachesForMutation(globalOptions, result); printResult(result, globalOptions); if (globalOptions.profile) { @@ -409,6 +413,12 @@ async function runClaimAction(id: string, options: Record, comm } } +/** Enforces the claim target XOR selection contract before dispatch. */ +export function requireClaimTarget(id: string | undefined, next: boolean): void { + if (!next && !id) throw new PmCliError("Specify an item id or pass --next", EXIT_CODE.USAGE); + if (next && id) throw new PmCliError("Specify either an item id or --next, not both", EXIT_CODE.USAGE); +} + async function runReleaseAction(id: string, options: Record, command: Command): Promise { const globalOptions = getGlobalOptions(command); const startedAt = Date.now(); @@ -656,11 +666,12 @@ export function registerOperationCommands(program: Command): void { const claimCommand = program .command("claim") - .argument("", "Item id") + .argument("[id]", "Item id (omit with --next)") .option("--author ", "Mutation author") .option("--message ", "History message") .option("--force", "Force claim override") .option("--if-available", "Skip silently when the item is already claimed by another author (returns skipped=true)") + .option("--next", "Atomically claim the next caller-available actionable item") .description("Claim an item for active work.") .action(runClaimAction); addHiddenOption(claimCommand, "--assignee ", "Alias for --author on lifecycle ownership commands"); diff --git a/src/core/item/actionability.ts b/src/core/item/actionability.ts index 0cf36c31d..997cfb240 100644 --- a/src/core/item/actionability.ts +++ b/src/core/item/actionability.ts @@ -56,16 +56,16 @@ export interface ResolvedBlocker { id: string; title: string | null; status: ItemStatus | null; - /** True when the blocker no longer gates work: its id is unknown or terminal. */ + /** True when the blocker no longer gates work because the referenced item is terminal. */ resolved: boolean; } /** * Resolves an item's declared blockers against a corpus index, annotating each - * with the blocker's title/status and whether it still gates work. A blocker is - * treated as resolved when its id is unknown (a dangling reference cannot be - * waited on) or the referenced item is terminal — mirroring the close-time - * auto-unblock rule, which only clears a dependent once its blocker is terminal. + * with the blocker's title/status and whether it still gates work. Unknown ids + * remain unresolved: silently treating a typo as satisfied would dispatch work + * whose prerequisite was never completed. Terminal referenced items alone are + * resolved. */ export function resolveItemBlockers( item: Pick, @@ -75,7 +75,7 @@ export function resolveItemBlockers( return collectBlockedByIds(item).map((id) => { const blocker = itemsById.get(normalizeItemId(id)); if (!blocker) { - return { id, title: null, status: null, resolved: true }; + return { id, title: null, status: null, resolved: false }; } return { id, @@ -199,7 +199,7 @@ export function computeActionabilityReport( statusRegistry: RuntimeStatusRegistry, ): ActionabilityReport { const { itemsById, childrenByParent, blockedByReverse } = indexCorpus(corpus); - const activeStatuses = resolveActiveStatusSet(statusRegistry); + const activeStatuses = new Set([...resolveActiveStatusSet(statusRegistry), ...statusRegistry.blocked_statuses]); // Ids of corpus items still in flight, used to keep only the non-terminal // dependents in each item's downstream "unblocks" list. const nonTerminalIds = new Set( @@ -221,7 +221,8 @@ export function computeActionabilityReport( .filter((dependentId) => nonTerminalIds.has(normalizeItemId(dependentId))) .sort((left, right) => left.localeCompare(right)); const entry: ActionableEntry = { item, open_blockers: openBlockers, unblocks }; - if (openBlockers.length === 0) { + const lifecycleBlocked = statusRegistry.blocked_statuses.has(normalizeStatusForRegistry(item.status, statusRegistry)); + if (openBlockers.length === 0 && !lifecycleBlocked) { ready.push(entry); } else { blocked.push(entry); diff --git a/src/sdk/cli-contracts/flag-contracts.ts b/src/sdk/cli-contracts/flag-contracts.ts index c054dded6..227111f11 100644 --- a/src/sdk/cli-contracts/flag-contracts.ts +++ b/src/sdk/cli-contracts/flag-contracts.ts @@ -303,7 +303,7 @@ export const COMMENTS_AUDIT_FLAG_CONTRACTS: CliFlagContract[] = [ ]; export const COMMENTS_FLAG_CONTRACTS: CliFlagContract[] = [ - { flag: "--add", aliases: ["--comment"] }, + { flag: "--add", aliases: ["--comment", "--body"] }, { flag: "--stdin" }, { flag: "--file" }, { flag: "--edit" }, @@ -728,6 +728,8 @@ export const CLAIM_FLAG_CONTRACTS: CliFlagContract[] = [ { flag: "--author", aliases: ["--assignee"] }, { flag: "--message" }, { flag: "--force" }, + { flag: "--if-available" }, + { flag: "--next" }, ]; export const RESTORE_FLAG_CONTRACTS: CliFlagContract[] = [ diff --git a/src/sdk/context-relevance.ts b/src/sdk/context-relevance.ts index 775c2268b..1e60ca572 100644 --- a/src/sdk/context-relevance.ts +++ b/src/sdk/context-relevance.ts @@ -301,6 +301,7 @@ export interface ContextEvaluationContextResult { /** Structural next result consumed by the evaluation runner. */ export interface ContextEvaluationNextResult { + recommended?: ContextEvaluationResultItem | null; ready?: ContextEvaluationResultItem[]; ranking?: ContextEvaluationResultRanking; [key: string]: unknown; @@ -418,7 +419,10 @@ function rankingPayloadForScenario( ...((result as ContextEvaluationContextResult).blocked_fallback ?? []), ] .map((item) => item.id) - : ((result as ContextEvaluationNextResult).ready ?? []).map((item) => item.id); + : [ + ...((result as ContextEvaluationNextResult).recommended ? [(result as ContextEvaluationNextResult).recommended as ContextEvaluationResultItem] : []), + ...((result as ContextEvaluationNextResult).ready ?? []), + ].map((item) => item.id); const servedIds = new Set(rankedIds); const attribution = (result.ranking?.items ?? []) .filter((item) => servedIds.has(item.id)) diff --git a/tests/fixtures/contracts/full.json b/tests/fixtures/contracts/full.json index 4d9ff5cb7..cf9d22208 100644 --- a/tests/fixtures/contracts/full.json +++ b/tests/fixtures/contracts/full.json @@ -1063,6 +1063,12 @@ }, { "flag": "--force" + }, + { + "flag": "--if-available" + }, + { + "flag": "--next" } ], "provider": "core" @@ -1312,7 +1318,8 @@ "flags": [ { "aliases": [ - "--comment" + "--comment", + "--body" ], "flag": "--add" }, diff --git a/tests/integration/help-runtime.spec.ts b/tests/integration/help-runtime.spec.ts index e759a1283..e5b7bc753 100644 --- a/tests/integration/help-runtime.spec.ts +++ b/tests/integration/help-runtime.spec.ts @@ -211,7 +211,7 @@ describe("CLI help runtime coverage (sandboxed)", () => { }); }); - it("suggests semantic option replacements for append-style commands", async () => { + it("accepts the hidden --body compatibility alias for comments", async () => { await withTempPmPath(async (context) => { const created = context.runCli(["create", "--title", "Comment target", "--type", "Task", "--json"], { expectJson: true, @@ -220,12 +220,9 @@ describe("CLI help runtime coverage (sandboxed)", () => { const itemId = (created.json as { item?: { id?: string } }).item?.id; expect(itemId).toBeDefined(); - const result = context.runCli(["comments", itemId ?? "", "--body", "comment body", "--json"]); - expect(result.code).toBe(2); - const envelope = parseJsonErrorEnvelope(result.stderr); - expect(envelope.code).toBe("unknown_option"); - expect(envelope.next_steps).toEqual(expect.arrayContaining(["Nearest supported options: --add"])); - expect(envelope.recovery?.suggested_retry).toContain("--add"); + const result = context.runCli(["comments", itemId ?? "", "--body", "comment body", "--json"], { expectJson: true }); + expect(result.code).toBe(0); + expect((result.json as { comments: Array<{ text: string }> }).comments[0]?.text).toBe("comment body"); }); }); diff --git a/tests/unit/cli/cli-main-errors.spec.ts b/tests/unit/cli/cli-main-errors.spec.ts index b0fd60645..7b40d0728 100644 --- a/tests/unit/cli/cli-main-errors.spec.ts +++ b/tests/unit/cli/cli-main-errors.spec.ts @@ -4042,15 +4042,15 @@ describe("CLI Commander usage recovery helpers", () => { program.command("update").description("Update"); const previousArgv = process.argv; try { - process.argv = ["node", "pm", "comments", "pm-123", "--body=done"]; + process.argv = ["node", "pm", "comments", "pm-123", "--boddy=done"]; const unknown = await resolveCommanderUsageContext( - { message: "error: unknown option '--body'" }, + { message: "error: unknown option '--boddy'" }, program, new Map(), ); expect(unknown.commandName).toBe("comments"); - expect(unknown.unknownOptionSuggestions).toContain("--add"); - expect(unknown.suggestedRetryCommand).toContain("--add=done"); + expect(unknown.unknownOptionSuggestions).toContain("--body"); + expect(unknown.suggestedRetryCommand).toBe("pm comments pm-123 --body=done"); process.argv = ["node", "pm", "test-all", "--type", "Task"]; const otherCommand = await resolveCommanderUsageContext( @@ -4132,12 +4132,12 @@ describe("CLI Commander usage recovery helpers", () => { ); expect(compactMissing.suggestedRetryCommand).toBe('pm comments pm-123 --add ""'); - process.argv = ["node", "pm", "comments", "pm-123", "--body=done"]; - const json = JSON.parse(await formatCommanderUsageJson({ message: "error: unknown option '--body'" }, program, new Map())); - expect(json.recovery.suggested_retry).toContain("--add=done"); + process.argv = ["node", "pm", "comments", "pm-123", "--boddy=done"]; + const json = JSON.parse(await formatCommanderUsageJson({ message: "error: unknown option '--boddy'" }, program, new Map())); + expect(json.recovery.suggested_retry).toContain("--body=done"); - const display = await formatCommanderUsageMessage({ message: "error: unknown option '--body'" }, program, new Map()); - expect(display).toContain("Unknown option --body"); + const display = await formatCommanderUsageMessage({ message: "error: unknown option '--boddy'" }, program, new Map()); + expect(display).toContain("Unknown option --boddy"); setActiveExtensionServices({ overrides: [ diff --git a/tests/unit/commands/claim-command.spec.ts b/tests/unit/commands/claim-command.spec.ts index a7e2e2fc4..21e34fc4a 100644 --- a/tests/unit/commands/claim-command.spec.ts +++ b/tests/unit/commands/claim-command.spec.ts @@ -1,8 +1,9 @@ import { mkdtemp, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; -import { runClaim, runRelease } from "../../../src/cli/commands/claim.js"; +import { describe, expect, it, vi } from "vitest"; +import { claimNextFromRecommendations, isAlreadyClaimedError, runClaim, runClaimNext, runRelease, type ClaimMutationOptions } from "../../../src/cli/commands/claim.js"; +import type { GlobalOptions } from "../../../src/core/shared/command-types.js"; import { EXIT_CODE } from "../../../src/core/shared/constants.js"; import { PmCliError } from "../../../src/core/shared/errors.js"; import { withTempPmPath, type TempPmContext } from "../../helpers/withTempPmPath.js"; @@ -11,7 +12,7 @@ function createTask( context: TempPmContext, params: { title: string; - status: "open" | "closed"; + status: "open" | "in_progress" | "closed"; assignee?: string; }, ): string { @@ -76,6 +77,51 @@ function setGovernancePreset(context: TempPmContext, preset: "minimal" | "defaul } describe("runClaim/runRelease", () => { + it("classifies only structured already-claimed conflicts as retryable", () => { + expect(isAlreadyClaimedError(new Error("ordinary"))).toBe(false); + expect(isAlreadyClaimedError(new PmCliError("other", EXIT_CODE.CONFLICT, { code: "other" }))).toBe(false); + expect(isAlreadyClaimedError(new PmCliError("held", EXIT_CODE.CONFLICT, { code: "already_claimed_by" }))).toBe(true); + }); + + it("propagates non-conflict failures from ranked claim composition", async () => { + const recommendation = { id: "pm-x", reasons: [] } as never; + await expect( + claimNextFromRecommendations([recommendation], false, {}, {}, async () => { + throw new Error("storage unavailable"); + }), + ).rejects.toThrow("storage unavailable"); + }); + + it("honors if-available while advancing skipped ranked candidates", async () => { + const recommendations = [ + { id: "pm-held", reasons: [] }, + { id: "pm-free", reasons: [] }, + ] as never; + const runner = vi.fn(async (id: string, _force: boolean, _global: GlobalOptions, options: ClaimMutationOptions) => ({ + item: { id }, + claimed_by: id === "pm-held" ? "other" : "agent", + previous_assignee: id === "pm-held" ? "other" : null, + forced: false, + ...(id === "pm-held" ? { skipped: true } : {}), + options, + })); + const claimed = await claimNextFromRecommendations(recommendations, false, {}, { ifAvailable: true }, runner); + expect(claimed.recommendation.id).toBe("pm-free"); + expect(claimed.attempts).toBe(2); + expect(runner).toHaveBeenCalledWith("pm-held", false, {}, { ifAvailable: true }); + }); + + it("returns a non-failing skip when every if-available candidate is held", async () => { + const recommendation = { id: "pm-held", reasons: [] } as never; + const result = await claimNextFromRecommendations([recommendation], false, {}, { ifAvailable: true }, async () => ({ + item: { id: "pm-held" }, + claimed_by: "other", + previous_assignee: "other", + forced: false, + skipped: true, + })); + expect(result).toMatchObject({ skipped: true, attempts: 1, recommendation: { id: "pm-held" } }); + }); it("fails when tracker is not initialized", async () => { const tempDir = await mkdtemp(path.join(os.tmpdir(), "pm-claim-not-init-")); try { @@ -101,6 +147,47 @@ describe("runClaim/runRelease", () => { }); }); + it("atomically claims the next caller-available recommendation", async () => { + await withTempPmPath(async (context) => { + const first = createTask(context, { title: "claim-next-first", status: "open" }); + const second = createTask(context, { title: "claim-next-second", status: "open" }); + const result = await runClaimNext(false, { path: context.pmPath }, { author: "next-agent" }); + expect([first, second]).toContain(result.recommendation.id); + expect(result.claimed_by).toBe("next-agent"); + expect(result.attempts).toBe(1); + }); + }); + + it("uses the explicit claim author for next-work ownership ranking", async () => { + await withTempPmPath(async (context) => { + const mine = createTask(context, { title: "explicit-author-work", status: "in_progress" }); + context.runCli(["update", mine, "--assignee", "next-agent", "--json"], { expectJson: true }); + createTask(context, { title: "other-work", status: "open" }); + const result = await runClaimNext(false, { path: context.pmPath }, { author: "next-agent" }); + expect(result.recommendation.id).toBe(mine); + expect(result.claimed_by).toBe("next-agent"); + }); + }); + + it("distributes parallel claim-next calls and reports exhaustion", async () => { + await withTempPmPath(async (context) => { + createTask(context, { title: "parallel-next-a", status: "open" }); + createTask(context, { title: "parallel-next-b", status: "open" }); + const claimed = await Promise.all([ + runClaimNext(false, { path: context.pmPath }, { author: "parallel-a" }), + runClaimNext(false, { path: context.pmPath }, { author: "parallel-b" }), + ]); + expect(new Set(claimed.map((result) => result.recommendation.id)).size).toBe(2); + }); + await withTempPmPath(async (context) => { + await expect(runClaimNext(false, { path: context.pmPath }, { author: "nobody" })).rejects.toMatchObject({ + context: expect.objectContaining({ code: "no_available_next_item" }), + }); + const missingId = context.runCli(["claim", "--json"]); + expect(missingId.code).toBe(EXIT_CODE.USAGE); + }); + }); + it("rejects claiming terminal items unless forced", async () => { await withTempPmPath(async (context) => { const id = createTask(context, { title: "claim-closed", status: "closed" }); diff --git a/tests/unit/commands/comments-command.spec.ts b/tests/unit/commands/comments-command.spec.ts index cd2022eae..757623cbc 100644 --- a/tests/unit/commands/comments-command.spec.ts +++ b/tests/unit/commands/comments-command.spec.ts @@ -1060,4 +1060,15 @@ describe("runComments edit/delete (GH-243)", () => { expect(badIndex.code).toBe(EXIT_CODE.USAGE); }); }); + + it("accepts the hidden --body alias without expanding help", async () => { + await withTempPmPath(async (context) => { + const id = createTask(context, "comments-body-alias"); + const added = context.runCli(["comments", id, "--body", "agent evidence", "--json"], { expectJson: true }); + expect(added.code).toBe(0); + expect((added.json as { comments: Array<{ text: string }> }).comments[0].text).toBe("agent evidence"); + const help = context.runCli(["comments", "--help"]); + expect(help.stdout).not.toContain("--body"); + }); + }); }); diff --git a/tests/unit/commands/completion-command.spec.ts b/tests/unit/commands/completion-command.spec.ts index 17b1c35ef..3c457a1f6 100644 --- a/tests/unit/commands/completion-command.spec.ts +++ b/tests/unit/commands/completion-command.spec.ts @@ -234,7 +234,7 @@ describe("generateBashScript", () => { it("includes comments mutation metadata flags in bash completion", () => { const script = generateBashScript(); - expect(script).toContain("--add --stdin --file --edit --delete --limit --author --message --allow-audit-comment --force"); + expect(script).toContain("--add --body --stdin --file --edit --delete --limit --author --message --allow-audit-comment --force"); expect(script).toContain("--stdin"); expect(script).toContain("--file"); expect(script).toContain("--edit"); @@ -642,6 +642,18 @@ describe("generateZshScript", () => { expect(script).toContain("packages:Alias for package"); }); + it("places atomic claim flags only in the zsh claim block", () => { + const script = generateZshScript(); + const updateStart = script.indexOf(" update)"); + const updateBlock = script.slice(updateStart, script.indexOf(" update-many)", updateStart)); + const claimStart = script.indexOf(" claim)"); + const claimBlock = script.slice(claimStart, script.indexOf(" release)", claimStart)); + expect(updateBlock).not.toContain("--if-available"); + expect(updateBlock).not.toContain("--next["); + expect(claimBlock).toContain("--if-available"); + expect(claimBlock).toContain("--next["); + }); + it("includes zsh completion for list filters", () => { const script = generateZshScript(); expect(script).toContain("--assignee"); diff --git a/tests/unit/commands/context-relevance-command.spec.ts b/tests/unit/commands/context-relevance-command.spec.ts index 896222edb..415a5dfbb 100644 --- a/tests/unit/commands/context-relevance-command.spec.ts +++ b/tests/unit/commands/context-relevance-command.spec.ts @@ -124,7 +124,7 @@ describe("context relevance command integration", () => { expect(explainedContext.ranking?.model).toBe("default-weighted-v1"); expect(explainedContext.ranking?.available_signals).toContain("priority_pressure"); expect(explainedNext.ranking?.items.map((entry) => entry.id)).toEqual( - explainedNext.ready.map((entry) => entry.id), + [explainedNext.recommended?.id, ...explainedNext.ready.map((entry) => entry.id)].filter(Boolean), ); expect(explainedNext.ranking?.items[0]?.contributions.priority_pressure).toBeGreaterThan(0); expect(contextAlias.code).toBe(0); diff --git a/tests/unit/commands/next-command.spec.ts b/tests/unit/commands/next-command.spec.ts index ed298cfbc..be523a221 100644 --- a/tests/unit/commands/next-command.spec.ts +++ b/tests/unit/commands/next-command.spec.ts @@ -2,6 +2,7 @@ import { writeFile } from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { + _testOnlyNextCommand as nextInternals, NEXT_OUTPUT_VALUES, renderNextMarkdown, resolveNextOutputFormat, @@ -79,6 +80,19 @@ describe("resolveNextOutputFormat", () => { }); describe("runNext", () => { + it("resolves caller identity through settings and unknown fallbacks", () => { + const previousAuthor = process.env.PM_AUTHOR; + delete process.env.PM_AUTHOR; + try { + expect(nextInternals.partitionCallerOwnedReady([], "settings-author")).toEqual({ available: [], held: [] }); + expect(nextInternals.partitionCallerOwnedReady([], " ")).toEqual({ available: [], held: [] }); + expect(nextInternals.partitionCallerOwnedReady([], "settings-author", "explicit-author")).toEqual({ available: [], held: [] }); + } finally { + if (previousAuthor === undefined) delete process.env.PM_AUTHOR; + else process.env.PM_AUTHOR = previousAuthor; + } + }); + it("recommends in-progress work first, ranks ready leaves, and lists blocked leaves with their blockers", async () => { await withTempPmPath(async (context) => { const epic = createItem(context, { title: "Build auth", type: "Epic" }); @@ -173,12 +187,12 @@ describe("runNext", () => { const result = await runNext({}, { path: context.pmPath }); expect(result.recommended?.id).toBe(leaf); - expect(result.ready.map((entry) => entry.id)).toEqual([leaf]); + expect(result.ready).toEqual([]); context.runCli(["close", leaf, "done", "--json"], { expectJson: true }); const closeoutOnly = await runNext({}, { path: context.pmPath }); expect(closeoutOnly.recommended?.id).toBe(completedEpic); - expect(closeoutOnly.ready.map((entry) => entry.id)).toEqual([completedEpic]); + expect(closeoutOnly.ready).toEqual([]); expect(closeoutOnly.recommended?.reasons).toContain("completed container — governance closeout"); }); }); @@ -189,7 +203,8 @@ describe("runNext", () => { const inSubtree = createItem(context, { title: "Subtree leaf", parent: epic }); createItem(context, { title: "Outside leaf" }); const scoped = await runNext({ parent: epic }, { path: context.pmPath }); - expect(scoped.ready.map((entry) => entry.id)).toEqual([inSubtree]); + expect(scoped.recommended?.id).toBe(inSubtree); + expect(scoped.ready).toEqual([]); expect(scoped.filters.parent).toBe(epic); const limited = await runNext({ limit: "1" }, { path: context.pmPath }); @@ -201,7 +216,7 @@ describe("runNext", () => { // A non-positive --limit falls back to the default cap (surfaces both ready leaves). const zeroLimit = await runNext({ limit: "0" }, { path: context.pmPath }); expect(zeroLimit.filters.limit).toBe(5); - expect(zeroLimit.ready).toHaveLength(2); + expect(zeroLimit.ready).toHaveLength(1); const readyOnly = await runNext({ readyOnly: true }, { path: context.pmPath }); expect(readyOnly.filters.ready_only).toBe(true); @@ -209,6 +224,30 @@ describe("runNext", () => { }); }); + it("keeps lifecycle and dangling-dependency blockers visible and skips foreign work", async () => { + await withTempPmPath(async (context) => { + const mine = createItem(context, { title: "My work", priority: "1" }); + const foreign = createItem(context, { title: "Foreign work", priority: "0" }); + context.runCli(["update", foreign, "--status", "in_progress", "--assignee", "other-agent", "--json"], { expectJson: true }); + const dangling = createItem(context, { title: "Dangling blocker", dep: "id=pm-ghost,kind=blocked_by" }); + const lifecycleBlocked = createItem(context, { title: "Lifecycle blocked", status: "blocked", blockedBy: "pm-ghost" }); + + const previousAuthor = process.env.PM_AUTHOR; + process.env.PM_AUTHOR = "test-author"; + try { + const result = await runNext({}, { path: context.pmPath }); + expect(result.recommended?.id).toBe(mine); + expect(result.ready).toEqual([]); + expect(result.held_by_others).toEqual([{ id: foreign, assignee: "other-agent" }]); + expect(result.blocked.map((entry) => entry.id).sort()).toEqual([dangling, lifecycleBlocked].sort()); + expect(result.summary).toMatchObject({ ready: 1, blocked: 2, candidates: 4 }); + } finally { + if (previousAuthor === undefined) delete process.env.PM_AUTHOR; + else process.env.PM_AUTHOR = previousAuthor; + } + }); + }); + it("rejects an unknown --parent and an invalid --limit", async () => { await withTempPmPath(async (context) => { await expect(runNext({ parent: "pm-missing" }, { path: context.pmPath })).rejects.toMatchObject>({ @@ -233,6 +272,12 @@ describe("runNext", () => { expect(blockedOnly.recommended).toBeNull(); expect(blockedOnly.suggestions?.[0]).toContain(`closing ${blocker}`); }); + await withTempPmPath(async (context) => { + createItem(context, { title: "Blocked without references", status: "blocked" }); + const blockedOnly = await runNext({}, { path: context.pmPath }); + expect(blockedOnly.recommended).toBeNull(); + expect(blockedOnly.suggestions?.[0]).toContain("add blocker context"); + }); }); it("propagates de-duplicated, sorted parse warnings from the corpus reads", async () => { @@ -254,6 +299,7 @@ function nextResult(overrides: Partial): NextResult { recommended: null, ready: [], blocked: [], + held_by_others: [], summary: { recommended: false, ready: 0, blocked: 0, in_progress: 0, candidates: 0, containers: 0 }, filters: { type: null, diff --git a/tests/unit/commands/register-commands.spec.ts b/tests/unit/commands/register-commands.spec.ts index cb460b946..ed098c808 100644 --- a/tests/unit/commands/register-commands.spec.ts +++ b/tests/unit/commands/register-commands.spec.ts @@ -39,7 +39,11 @@ vi.mock("../../../src/cli/commands/health.js", () => ({ runHealth: vi.fn() })); vi.mock("../../../src/cli/commands/validate.js", () => ({ runValidate: vi.fn() })); vi.mock("../../../src/cli/commands/gc.js", () => ({ runGc: vi.fn() })); vi.mock("../../../src/cli/commands/contracts.js", () => ({ runContracts: vi.fn() })); -vi.mock("../../../src/cli/commands/claim.js", () => ({ runClaim: vi.fn(), runRelease: vi.fn() })); +vi.mock("../../../src/cli/commands/claim.js", () => ({ + runClaim: vi.fn(), + runClaimNext: vi.fn(), + runRelease: vi.fn(), +})); vi.mock("../../../src/cli/commands/create.js", () => ({ runCreate: vi.fn() })); vi.mock("../../../src/cli/commands/copy.js", () => ({ runCopy: vi.fn() })); vi.mock("../../../src/cli/commands/focus.js", () => ({ runFocus: vi.fn() })); @@ -135,7 +139,7 @@ vi.mock("../../../src/cli/commands/extension.js", () => ({ runExtension: vi.fn() vi.mock("../../../src/cli/commands/upgrade.js", () => ({ runUpgrade: vi.fn() })); import { registerListQueryCommands } from "../../../src/cli/register-list-query.js"; -import { registerOperationCommands } from "../../../src/cli/register-operations.js"; +import { registerOperationCommands, requireClaimTarget } from "../../../src/cli/register-operations.js"; import { resolveStartTaskInProgressStatus } from "../../../src/sdk/start-task-status.js"; import { resolveRuntimeStatusRegistry } from "../../../src/core/schema/runtime-schema.js"; import { @@ -166,7 +170,7 @@ import { runHealth } from "../../../src/cli/commands/health.js"; import { runValidate } from "../../../src/cli/commands/validate.js"; import { runGc } from "../../../src/cli/commands/gc.js"; import { runContracts } from "../../../src/cli/commands/contracts.js"; -import { runClaim, runRelease } from "../../../src/cli/commands/claim.js"; +import { runClaim, runClaimNext, runRelease } from "../../../src/cli/commands/claim.js"; import { runCreate } from "../../../src/cli/commands/create.js"; import { runCopy } from "../../../src/cli/commands/copy.js"; import { runFocus } from "../../../src/cli/commands/focus.js"; @@ -514,6 +518,19 @@ describe("list-query command actions", () => { }); describe("operation command actions", () => { + it("requires either an explicit claim id or --next", () => { + expect(() => requireClaimTarget(undefined, false)).toThrow("Specify an item id or pass --next"); + expect(() => requireClaimTarget("pm-a", true)).toThrow("either an item id or --next"); + expect(requireClaimTarget(undefined, true)).toBeUndefined(); + expect(requireClaimTarget("pm-a", false)).toBeUndefined(); + }); + + it("routes claim --next through ranked atomic selection", async () => { + vi.mocked(runClaimNext).mockResolvedValueOnce({ id: "pm-next" } as never); + await runCli("claim", "--next", "--if-available"); + expect(runClaimNext).toHaveBeenCalledWith(false, expect.any(Object), expect.objectContaining({ ifAvailable: true })); + }); + it("maps pm test flags and flags dependency failures via exit code", async () => { await runCli("test", "pm-1", "--list", "--match", "unit", "--env-set", "A=1"); let options = lastCallArg>(vi.mocked(runTest) as never, 1); diff --git a/tests/unit/commands/validate-command.spec.ts b/tests/unit/commands/validate-command.spec.ts index e06fc7a2d..3c4c77f3f 100644 --- a/tests/unit/commands/validate-command.spec.ts +++ b/tests/unit/commands/validate-command.spec.ts @@ -117,6 +117,7 @@ describe("runValidate", () => { "metadata", "resolution", "lifecycle", + "dependency_references", "files", "command_references", "history_drift", @@ -253,9 +254,10 @@ describe("runValidate", () => { await withTempPmPath(async (context) => { createTask(context, "validate-lifecycle-only"); const result = await runValidate({ checkLifecycle: true }, { path: context.pmPath }); - expect(result.checks).toHaveLength(1); + expect(result.checks).toHaveLength(2); expect(result.checks[0]?.name).toBe("lifecycle"); expect(result.checks[0]?.status).toBe("ok"); + expect(result.checks[1]?.name).toBe("dependency_references"); const lifecycleCheck = checkByName(result, "lifecycle"); const details = lifecycleCheck.details as { stale_blocker_checks_enabled: boolean; @@ -272,6 +274,32 @@ describe("runValidate", () => { }); }); + it("reports dangling structured dependency references with remediation", async () => { + await withTempPmPath(async (context) => { + const id = createTask(context, "dangling-dependency"); + context.runCli(["update", id, "--dep", "id=pm-ghost,kind=blocked_by", "--json"], { expectJson: true }); + const secondId = createTask(context, "second-dangling-dependency"); + context.runCli(["update", secondId, "--dep", "id=pm-phantom,kind=blocked_by", "--json"], { expectJson: true }); + const result = await runValidate({ checkLifecycle: true }, { path: context.pmPath }); + const check = checkByName(result, "dependency_references"); + expect(check.status).toBe("warn"); + expect(check.details).toMatchObject({ dangling_reference_count: 2 }); + expect((check.details.remediation_hints as string[])[0]).toContain("--replace-deps"); + const multiRowCheck = validateInternals.buildDependencyReferencesCheck([ + { id: "pm-b", parent: "pm-missing-b", dependencies: [] }, + { id: "pm-a", parent: "pm-missing-a", dependencies: [] }, + ] as never, true).check; + expect(multiRowCheck.details.dangling_reference_rows).toEqual([ + "pm-a:pm-missing-a:parent", + "pm-b:pm-missing-b:parent", + ]); + expect(multiRowCheck.details.remediation_hints).toEqual([ + "pm update pm-a --unset parent", + "pm update pm-b --unset parent", + ]); + }); + }); + it("covers validate helper edge branches for lifecycle, files, and fix application", async () => { const statusRegistry = resolveRuntimeStatusRegistry(SETTINGS_DEFAULTS.schema); expect(validateInternals.toMeaningfulString(" none ")).toBeUndefined(); @@ -2259,7 +2287,7 @@ describe("runValidate", () => { await runClose(id, "verified in review", {}, { path: context.pmPath }); const preview = await runValidate({ autoFix: true, dryRun: true }, { path: context.pmPath }); - expect(preview.checks.map((entry) => entry.name)).toEqual(["metadata", "resolution", "lifecycle"]); + expect(preview.checks.map((entry) => entry.name)).toEqual(["metadata", "resolution", "lifecycle", "dependency_references"]); expect(preview.fixes).toBeDefined(); expect(preview.fixes?.mode).toBe("dry_run"); expect(preview.fixes?.granted_fix_scopes).toEqual(["metadata", "resolution"]); diff --git a/tests/unit/core/item/actionability.spec.ts b/tests/unit/core/item/actionability.spec.ts index 5b02c5701..d8898d0f2 100644 --- a/tests/unit/core/item/actionability.spec.ts +++ b/tests/unit/core/item/actionability.spec.ts @@ -60,7 +60,7 @@ describe("collectBlockedByIds", () => { }); describe("resolveItemBlockers", () => { - it("annotates each blocker as resolved (missing/terminal) or open (non-terminal)", () => { + it("keeps missing and non-terminal blockers unresolved while resolving terminal blockers", () => { const corpus = [ item({ id: "pm-open", status: "open" }), item({ id: "pm-closed", status: "closed" }), @@ -73,7 +73,7 @@ describe("resolveItemBlockers", () => { ); expect(blockers).toEqual([ { id: "pm-closed", title: "Item pm-closed", status: "closed", resolved: true }, - { id: "pm-missing", title: null, status: null, resolved: true }, + { id: "pm-missing", title: null, status: null, resolved: false }, { id: "pm-open", title: "Item pm-open", status: "open", resolved: false }, ]); }); @@ -105,15 +105,15 @@ describe("computeActionabilityReport", () => { expect(report.blocked[0].open_blockers.map((blocked) => blocked.id)).toEqual(["pm-blocker"]); }); - it("treats a parent whose descendants are all terminal as a ready leaf and skips a missing/terminal blocker", () => { + it("treats a parent whose descendants are terminal as ready but keeps a missing blocker blocked", () => { const parent = item({ id: "pm-parent", type: "Epic", status: "open" }); const closedChild = item({ id: "pm-closed-child", parent: "pm-parent", status: "closed" }); const resolvedBlockee = item({ id: "pm-resolved", status: "open", blocked_by: "pm-gone" }); const corpus = [parent, closedChild, resolvedBlockee]; const report = computeActionabilityReport(corpus, corpus, registry); - expect(report.ready.map((entry) => entry.item.id).sort()).toEqual(["pm-parent", "pm-resolved"]); - expect(report.blocked).toHaveLength(0); + expect(report.ready.map((entry) => entry.item.id)).toEqual(["pm-parent"]); + expect(report.blocked.map((entry) => entry.item.id)).toEqual(["pm-resolved"]); expect(report.container_count).toBe(0); }); diff --git a/tests/unit/scripts/release/sentry-telemetry-gate.spec.ts b/tests/unit/scripts/release/sentry-telemetry-gate.spec.ts index c4e38a5bc..007cafb67 100644 --- a/tests/unit/scripts/release/sentry-telemetry-gate.spec.ts +++ b/tests/unit/scripts/release/sentry-telemetry-gate.spec.ts @@ -277,13 +277,29 @@ describe("scripts/release/sentry-telemetry-gate: telemetry modes", () => { isUnhandled: false, metadata: { value: "tracker_not_initialized", type: "CommandError" }, }, + { + shortId: "PM-5", + level: "error", + logger: "node", + isUnhandled: false, + title: "CommandError: pm starter search: `pm search` failed: {.", + metadata: { value: "pm starter search: `pm search` failed: {.", type: "CommandError" }, + }, + { + shortId: "PM-6", + level: "error", + logger: "node", + isUnhandled: false, + title: "CommandError: pm starter context: `pm context` failed: {.", + metadata: { value: "pm starter context: `pm context` failed: {.", type: "CommandError" }, + }, ]), }); expect(json.ok).toBe(false); expect(json.sentry.critical).toBe(1); expect(json.sentry.high).toBe(1); expect(json.sentry.ignored_noise_total).toBe(1); - expect(json.sentry.ignored_expected_handled_total).toBe(1); + expect(json.sentry.ignored_expected_handled_total).toBe(3); expect(json.telemetry.checked).toBe(true); expect(json.telemetry.ok).toBe(true); expect(process.exitCode).toBe(1); diff --git a/tests/unit/sdk/context-relevance.spec.ts b/tests/unit/sdk/context-relevance.spec.ts index c9e639f22..8d368b8e3 100644 --- a/tests/unit/sdk/context-relevance.spec.ts +++ b/tests/unit/sdk/context-relevance.spec.ts @@ -274,7 +274,7 @@ describe("context relevance SDK primitives", () => { const focus = (id: string) => ({ id }) as never; const reader = { context: async () => ({ high_level: [], low_level: [focus("pm-a"), focus("pm-b")], blocked_fallback: [], ranking }) as never, - next: async () => ({ ready: [focus("pm-b"), focus("pm-a")], ranking }) as never, + next: async () => ({ recommended: focus("pm-b"), ready: [focus("pm-a")], ranking }) as never, }; const scenarios = [ {