Skip to content
Closed
13 changes: 13 additions & 0 deletions .agents/pm/history/pm-gh1610.jsonl

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions .agents/pm/history/pm-gh1627.jsonl

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions .agents/pm/history/pm-gh1628.jsonl

Large diffs are not rendered by default.

67 changes: 62 additions & 5 deletions .agents/pm/issues/pm-gh1610.toon
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ id: pm-gh1610
title: "GH-1610: compact lean refusals and expose structured claim context"
description: "GitHub report https://github.com/unbraind/pm-cli/issues/1610 measures redundant refusal headlines and generic recovery boilerplate, with lean text larger than default. Investigate current public SDK normalization and renderers, preserve actionable recovery, and expose existing claim holder metadata as typed fields. Reported2026.10.27 behavior has not been independently reproduced; earlier completed lean-error work remains completed."
type: Issue
status: open
status: closed
priority: 2
tags[2]: agent-ux,github
created_at: "2026-10-27T10:20:14.486Z"
updated_at: "2026-10-27T17:28:06.469Z"
updated_at: "2026-10-31T09:14:59.888Z"
closed_at: "2026-10-31T01:52:15.075Z"
completed_at: "2026-10-31T01:52:15.075Z"
author: "harness:codex"
estimated_minutes: 120
acceptance_criteria: Reproduce the reported claim-conflict bytes in isolated trackers; remove duplicated and constant lean boilerplate without deleting specific recovery; preserve default compatibility and sensitive-value redaction; verify CLI and public SDK structured holder metadata; add meaningful before/after controls.
Expand All @@ -15,11 +17,66 @@ impact: CLI text and structured SDK refusal consumers
parent: pm-mpbb
risk: medium
confidence: medium
resolution: "The SDK declares the constant refusal boilerplate once (PM_GENERIC_REFUSAL_GUIDANCE, PM_RECOVERY_ECHO_FIELDS, isGenericRefusal*, projectLeanRecoveryPayload) and both lean projections consume it: lean text drops the generic requirement, generic why, pm --help examples and the invocation echo while keeping next steps, specific recovery and verifier findings; lean JSON drops generic required/examples and the recovery echo on top of title/why. Text refusals no longer repeat a single-line message under What happened. already_claimed_by carries item_id, claimed_by, claimed_at and claim_source in PmCliErrorContext, the JSON envelope and the classification (resolveItemOwnershipConflict is public), rendered in text as Held by: <holder> (<source> since <time>)."
expected_result: Lean refusals preserve code-specific recovery and typed claim metadata while never exceeding equivalent default output; shared SDK normalization owns semantics.
actual_result: "Isolated-tracker claim conflict: text 798 -> 719 bytes, --lean text 813 -> 494, --json 1,172 -> 1,299 (typed holder fields added), --json --lean 968 -> 648; an invalid-value refusal 771 -> 602 under --lean. Property test over six refusal codes: lean never larger than default (text and JSON) and no non-label line shared by every lean output. Claim-conflict tests assert claimed_by/claim_source (claim vs assignment) and claimed_at (ISO, null on corrupt history). refusal.exit_code is retained because refusal is the stable identity used by the refusal-closure tooling. Agent-task-token, refusal-closure, docs-skills, static quality gates and the linked test run pass."
dependencies[2]{id,kind,created_at,author,source_kind,author_source}:
pm-g9xk,related,"2026-10-27T10:20:14.486Z","harness:codex","cli:create:dep",detected
pm-q0kr,related,"2026-10-27T10:20:14.486Z","harness:codex","cli:create:dep",detected
comments[2]{created_at,author,text}:
"2026-10-27T10:20:14.486Z","harness:codex","Duplicate check: strict complete3137-item all-status corpus with no omissions plus request-specific hybrid and keyword searches and refreshed open/in_progress lists; full live completed lean and guidance lineages and parent metadata/history inspected. Existing closed lean implementation does not own this newly reported byte-size and claim-metadata acceptance; no matching active item found. Intake is open and unclaimed; no reproduction or repair claimed."
"2026-10-27T17:28:06.468Z","harness:codex","Additional observed refusal duplication under the same existing UX owner: pm guide capabilities --depth brief with the optional guide package absent prints two separate Next steps entries naming the same package install, once as pm install guide-shell and again as pm package install guide-shell --project. No package was installed to mask the refusal. The core contracts --summary surface remains an available discovery path. This is separate from the typed validation facts and portable completion scope currently implemented in the sibling items."
comments[5]:
- created_at: "2026-10-27T10:20:14.486Z"
author: "harness:codex"
text: "Duplicate check: strict complete3137-item all-status corpus with no omissions plus request-specific hybrid and keyword searches and refreshed open/in_progress lists; full live completed lean and guidance lineages and parent metadata/history inspected. Existing closed lean implementation does not own this newly reported byte-size and claim-metadata acceptance; no matching active item found. Intake is open and unclaimed; no reproduction or repair claimed."
- created_at: "2026-10-27T17:28:06.468Z"
author: "harness:codex"
text: "Additional observed refusal duplication under the same existing UX owner: pm guide capabilities --depth brief with the optional guide package absent prints two separate Next steps entries naming the same package install, once as pm install guide-shell and again as pm package install guide-shell --project. No package was installed to mask the refusal. The core contracts --summary surface remains an available discovery path. This is separate from the typed validation facts and portable completion scope currently implemented in the sibling items."
- created_at: "2026-10-31T01:37:13.954Z"
author: "harness:claude-code"
text: "Reproduced on current source in an isolated non-git tracker (claim held by agent-a, second claim by agent-b): text 798 bytes, --lean text 813 bytes (larger: --lean landed in attempted_command and provided_fields), --json 1,172, --json --lean 968. The headline was printed twice (Error: and What happened: byte-identical; JSON title === detail), the generic required/why/examples survived --lean, and the holder existed only inside the sentence: PmCliError context carried no claimed_by/claimed_at, so applyPmCliErrorContext had nothing to copy and the JSON envelope exposed no typed holder field."
- created_at: "2026-10-31T01:37:14.516Z"
author: "harness:claude-code"
text: "Fix: (1) the SDK declares the constant refusal boilerplate once (PM_GENERIC_REFUSAL_GUIDANCE, PM_RECOVERY_ECHO_FIELDS, isGenericRefusal*, projectLeanRecoveryPayload in agent-output-contracts, exported from ./sdk, ./sdk/runtime and ./sdk/contracts) so text and JSON lean projections agree; (2) renderGuidanceMessage takes { lean }: lean text drops the generic requirement, generic why, pm --help examples and the invocation echo while keeping next steps, specific recovery and verifier findings, and the text headline is no longer repeated under What happened when it equals the single-line message (default mode too); (3) projectLeanErrorEnvelope also drops generic required/examples and the recovery echo; (4) claim.ts resolves the holder through the new resolveItemOwnershipConflict and the refusal context carries item_id, claimed_by, claimed_at (latest assignee change from history, null when unavailable) and claim_source (claim|assignment); applyPmCliErrorContext/attachItemDiagnostics project them into GuidanceMessage, the JSON envelope and the classification, and text prints Held by: <holder> (<source> since <time>). After: text 719, --lean text 494, --json 1,299 (+127 bytes of typed holder fields), --json --lean 648; an invalid-value refusal drops 771 -> 602 under --lean. refusal.exit_code stays because refusal is the stable identity consumed by the refusal-closure tooling. Property test: six refusal codes, lean never larger than default (text and JSON) and no non-label line shared by every lean output. Gates: agent-task-token, refusal-closure, docs-skills and static quality pass; the SDK surface snapshot records the context fields and new exports."
- created_at: "2026-10-31T08:39:00.736Z"
author: "harness:claude-code"
text: "Review follow-up on PR #1637 (CodeRabbit, 2026-10-31): three fixes to the lean refusal contract and the typed holder.\n\n1. **Lean JSON kept code-specific `why` out.** `projectLeanErrorEnvelope` destructured `why` away for every envelope, while lean text and the documented contract keep a code-specific rationale. It now drops `why` only when `isGenericRefusalWhy` is true, so `--lean --json` on `already_claimed_by` carries the claim rationale like lean text does.\n2. **Mixed example lists kept the generic `pm --help`.** The all-or-nothing `isGenericRefusalExamples` predicate is replaced by a new SDK helper `withoutGenericRefusalExamples` (exported from `@unbrained/pm-cli/sdk`, `/sdk/contracts`, `/sdk/runtime`) which filters each generic entry; text and JSON lean projections both use it and drop the field only when nothing specific remains. The predicate had no remaining caller and never shipped (absent from main and v2026.10.30), so it is removed from the SDK barrels rather than kept as a dead export.\n3. **Unknown holder origin reported as `assignment`.** When history is unreadable or records no assignee change, `resolveItemOwnershipConflict` now returns `source: \"unknown\"` (and `claim_source: \"unknown\"` on the refusal) instead of claiming an assignment it cannot prove. The prose keeps the `assigned to` wording. The SDK surface snapshot acknowledges the union widening; both types were introduced unreleased on this branch.\n\nCoverage: holder rendering without a recorded time or origin, generic-why removal and per-entry example filtering are asserted in `tests/unit/cli/error-guidance.spec.ts`; the corrupt-history case in `tests/unit/commands/lifecycle/claim-command.spec.ts` now expects `unknown`."
edited_at: "2026-10-31T09:14:59.888Z"
files[11]{path,scope}:
sdk/public-surface.json,project
src/cli/error-guidance.ts,project
src/cli/main.ts,project
src/core/shared/errors.ts,project
src/sdk/cli-contracts.ts,project
src/sdk/cli-contracts/agent-output-contracts.ts,project
src/sdk/index.ts,project
src/sdk/lifecycle/claim.ts,project
src/sdk/ownership-source.ts,project
tests/unit/cli/error-guidance.spec.ts,project
tests/unit/commands/lifecycle/claim-command.spec.ts,project
tests[1]{command,scope,provenance{author,created_at,source_kind,source_ref}}:
node scripts/run-tests.mjs test -- tests/unit/cli/error-guidance.spec.ts tests/unit/commands/lifecycle/claim-command.spec.ts tests/unit/cli/cli-main-errors.spec.ts,project,"harness:claude-code","2026-10-31T01:37:13.107Z",local_mutation,fix/terminal-noop-and-bounded-next
test_runs[1]:
- run_id: test-local-mvvqn6rk-5yyb21
kind: test
status: passed
started_at: "2026-10-31T01:44:08.961Z"
finished_at: "2026-10-31T01:44:25.856Z"
recorded_at: "2026-10-31T01:44:25.856Z"
passed: 1
failed: 0
skipped: 0
executions[1]{command,requested_pm_context_mode,pm_context_mode,workspace_context_mode,trust_reason}:
node scripts/run-tests.mjs test -- tests/unit/cli/error-guidance.spec.ts tests/unit/commands/lifecycle/claim-command.spec.ts tests/unit/cli/cli-main-errors.spec.ts,schema,schema,source,local_source_ref
docs[3]{path,scope}:
docs/AGENT_GUIDE.md,project
docs/AGENT_UX_CONTRACTS.md,project
docs/COMMANDS.md,project
close_reason: Lean refusals drop constant boilerplate and the claim holder is typed
escape_class: production_defect
gate_evidence:
disposition: gate_added
owner: pm-gh1610
gate_id: "tests/unit/cli/error-guidance.spec.ts lean refusals (GH-1610): lean never larger than default across six refusal codes and no constant line shared by every lean output"
negative_control: "git stash push src/cli/error-guidance.ts src/cli/main.ts src/sdk/cli-contracts/agent-output-contracts.ts src/sdk/lifecycle/claim.ts src/sdk/ownership-source.ts && node scripts/run-tests.mjs test -- tests/unit/cli/error-guidance.spec.ts -t \"GH-1610\" ; git stash pop (lean text 813 > default 798 bytes before the fix)"
local_checks[4]: node scripts/run-tests.mjs test -- tests/unit/cli/error-guidance.spec.ts tests/unit/commands/lifecycle/claim-command.spec.ts tests/unit/cli/cli-main-errors.spec.ts,node scripts/release/agent-task-token-gate.mjs,node scripts/release/refusal-closure-gate.mjs,node scripts/release/docs-skills-gate.mjs
hosted_checks[2]: CI / Gates (static),CI / Gates (coverage)
body: ""
Loading
Loading