@yunhozz/arc 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/MANIFEST.sha256 +75 -69
  2. package/claude/agents/arc-implementer.md +8 -23
  3. package/claude/agents/arc-planner.md +14 -23
  4. package/claude/agents/arc-qa.md +9 -19
  5. package/claude/agents/arc-reviewer.md +11 -20
  6. package/claude/agents/arc-scouter.md +10 -17
  7. package/claude/skill/SKILL.md +8 -8
  8. package/claude/skill/references/arc-visual-companion.md +3 -3
  9. package/claude/skill/references/brainstorming.md +60 -38
  10. package/claude/skill/references/commit.md +6 -6
  11. package/claude/skill/references/coordination.md +18 -6
  12. package/claude/skill/references/git-recovery.md +2 -2
  13. package/claude/skill/references/handoff-prep.md +7 -5
  14. package/claude/skill/references/handoff.md +62 -9
  15. package/claude/skill/references/limits.md +5 -5
  16. package/claude/skill/references/planning.md +18 -12
  17. package/claude/skill/references/scripts/start-server.sh +7 -0
  18. package/claude/skill/references/superpowers-provenance.json +4 -4
  19. package/claude/skill/references/verification.md +6 -4
  20. package/claude/skill/runtime/bootstrap.mjs +2 -2
  21. package/claude/skill/runtime/cooperative-schema.mjs +32 -1
  22. package/claude/skill/runtime/cooperative.mjs +68 -13
  23. package/claude/skill/runtime/evaluate.mjs +85 -6
  24. package/claude/skill/runtime/git-evidence.mjs +98 -0
  25. package/claude/skill/runtime/operations.mjs +2 -2
  26. package/claude/skill/runtime/role-policy.json +6 -6
  27. package/claude/skill/runtime/store.mjs +10 -0
  28. package/claude/skill/runtime/target-preparation.mjs +110 -0
  29. package/claude/skill/runtime/verification.mjs +6 -4
  30. package/codex/agents/arc-implementer.toml +10 -25
  31. package/codex/agents/arc-planner.toml +12 -19
  32. package/codex/agents/arc-qa.toml +7 -15
  33. package/codex/agents/arc-reviewer.toml +9 -16
  34. package/codex/agents/arc-scouter.toml +8 -13
  35. package/codex/skill/SKILL.md +8 -8
  36. package/codex/skill/references/arc-visual-companion.md +3 -3
  37. package/codex/skill/references/brainstorming.md +60 -38
  38. package/codex/skill/references/commit.md +6 -6
  39. package/codex/skill/references/coordination.md +18 -6
  40. package/codex/skill/references/git-recovery.md +2 -2
  41. package/codex/skill/references/handoff-prep.md +5 -5
  42. package/codex/skill/references/handoff-worktree.md +4 -4
  43. package/codex/skill/references/handoff.md +61 -8
  44. package/codex/skill/references/limits.md +5 -5
  45. package/codex/skill/references/planning.md +19 -13
  46. package/codex/skill/references/scripts/start-server.sh +7 -0
  47. package/codex/skill/references/superpowers-provenance.json +4 -4
  48. package/codex/skill/references/verification.md +6 -4
  49. package/codex/skill/runtime/bootstrap.mjs +2 -2
  50. package/codex/skill/runtime/cooperative-schema.mjs +32 -1
  51. package/codex/skill/runtime/cooperative.mjs +68 -13
  52. package/codex/skill/runtime/evaluate.mjs +85 -6
  53. package/codex/skill/runtime/git-evidence.mjs +98 -0
  54. package/codex/skill/runtime/operations.mjs +2 -2
  55. package/codex/skill/runtime/role-policy.json +6 -6
  56. package/codex/skill/runtime/store.mjs +10 -0
  57. package/codex/skill/runtime/target-preparation.mjs +110 -0
  58. package/codex/skill/runtime/verification.mjs +6 -4
  59. package/lib/installer/manifest.mjs +6 -2
  60. package/lib/installer/plan.mjs +3 -1
  61. package/lib/installer/runtime-assets.mjs +1 -0
  62. package/lib/policy/role-policy.json +6 -6
  63. package/lib/prompts/agent-fragments.json +254 -0
  64. package/lib/prompts/render.mjs +199 -0
  65. package/lib/runtime/bootstrap.mjs +2 -2
  66. package/lib/runtime/cooperative-schema.mjs +32 -1
  67. package/lib/runtime/cooperative.mjs +68 -13
  68. package/lib/runtime/evaluate.mjs +85 -6
  69. package/lib/runtime/git-evidence.mjs +98 -0
  70. package/lib/runtime/operations.mjs +2 -2
  71. package/lib/runtime/store.mjs +10 -0
  72. package/lib/runtime/target-preparation.mjs +110 -0
  73. package/lib/runtime/verification.mjs +6 -4
  74. package/lib/tui/setup.mjs +3 -2
  75. package/package.json +1 -1
@@ -1,14 +1,14 @@
1
1
  <a id="subagent-limits"></a>
2
2
  # Subagent limits
3
3
 
4
- Before first creation, Orchestrator reads this reference and adjacent `../limits.toml` (installed at `skills/arc/limits.toml`). Require `version = 1` and positive safe-integer `max_total_subagents`/`max_concurrent_subagents`. Installation defaults are `100`/`5`. Missing, malformed or unreadable policy blocks creation; report it without assuming defaults. Freeze settings for one ARC session/tree; reinstalling affects new sessions.
4
+ Before the first creation, Orchestrator reads this reference and the adjacent `../limits.toml` (installed at `skills/arc/limits.toml`). Require `version = 1` and positive safe-integer `max_total_subagents`/`max_concurrent_subagents`. Installation defaults are `100`/`5`. Missing, malformed or unreadable policy blocks creation. Report it without assuming defaults. Freeze settings for one ARC session/tree; reinstalling affects new sessions.
5
5
 
6
6
  <a id="counting-and-reservations"></a>
7
7
  ## Counting and reservations
8
8
 
9
9
  - Track instance IDs, parent IDs, pending reservations, creation outcomes and active/completed state in the session ledger. Exclude Orchestrator, but count every created descendant, including nested/non-ARC agents.
10
- - Completion never refunds cumulative creation. Same-instance follow-up/resume adds nothing; fresh instances, new-instance retries and replacements increment the total.
11
- - Waiting, blocked, running and resumable idle instances consume concurrency until confirmed completion/termination. Obey lower actual runtime capacity, including native retention of completed instances. ARC counts do not prove free native slots.
10
+ - Completion never refunds cumulative creation. A follow-up or resume using the same instance adds nothing; fresh instances, new-instance retries and replacements increment the total.
11
+ - Waiting, blocked, running and resumable idle instances consume concurrency until completion or termination is confirmed. Obey lower actual runtime capacity, including native retention of completed instances. ARC counts do not prove free native slots.
12
12
 
13
13
  Reserve the full proposed parallel batch before dispatch, satisfying both:
14
14
 
@@ -20,9 +20,9 @@ Reserve before each call. Confirmed creation becomes a created/active ID. Releas
20
20
  <a id="continuation-and-exhaustion"></a>
21
21
  ## Continuation and exhaustion
22
22
 
23
- Preserve the ledger across continuation/resume/compaction and reconcile actual IDs before dispatch. Never reset lost counters to zero. Incomplete reconstruction holds creation and reports unknown budget; use a conservative upper bound only when records support it.
23
+ Preserve the ledger across continuation, resume and compaction, and reconcile actual IDs before dispatch. Never reset lost counters to zero. Incomplete reconstruction holds creation and reports unknown budget; use a conservative upper bound only when records support it.
24
24
 
25
- Completion frees confirmed active capacity only. Exhaustion holds creation and reports remaining work/budget; it never waives QA/Reviewer, role freshness, approval, ownership, dependency or same-file edit gates.
25
+ Confirmed completion or termination frees active capacity. Exhaustion holds creation and reports remaining work/budget; it never waives QA/Reviewer, role freshness, approval, ownership, dependency or same-file edit gates.
26
26
 
27
27
  Cumulative limits and scheduling are prompt policy, not hard runtime controls. Text tests prove package consistency, not live compliance/native support.
28
28
  Codex `[agents].max_concurrent_threads_per_session` and Claude `settings.json.env.CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS` constrain their respective sessions.
@@ -1,16 +1,18 @@
1
1
  <a id="planning-contract"></a>
2
2
  # Planning contract
3
3
 
4
- Read before depth judgments, spec/plan drafting/approval and first saves.
4
+ Read before depth judgments, spec or plan drafting and approval, and first saves.
5
5
 
6
6
  <a id="authorization-and-scope"></a>
7
7
  ## Authorization and scope
8
8
 
9
+ Use the [fast gate and envelope](handoff.md#orchestrator-direct-path-for-small-tasks) alongside these existing scope and approval rules; reuse valid approvals and hold changed scope under these rules.
10
+
9
11
  An explicit execution request authorizes only sufficiently specified scope from that request or a description the user explicitly references. Confirm intent/outcome/constraints/document depth/verification risk and no unresolved material behavior/method/risk decision. Record approval evidence, scope, criteria, constraints and depth. Orchestrator may choose routine execution/check details, not unapproved product behavior/material scope. Restating/handoff/restart of unchanged scope needs no new approval.
10
12
 
11
13
  Silence, ambiguous assent, other-work approval and vague blanket improvements are insufficient. Planner's new material choices are not automatically approved. Request missing decisions through Orchestrator. Required specs/plans retain separate ordered approval.
12
14
 
13
- Material scope/method/risk changes hold affected work with missing decision/evidence/remaining scope reported. Target change, unclear ownership, unexpected state, unresolved failure or missing inputs also hold under [handoff](handoff.md)/[recovery](git-recovery.md). Distinguish new authorization from existing-scope execution issues; routine correction/reverification needs no duplicate approval. Revalidate after resolution.
15
+ Hold affected work for material changes to scope, method or risk, and report the missing decision, evidence and remaining scope. Target change, unclear ownership, unexpected state, unresolved failure or missing inputs also hold under [handoff](handoff.md)/[recovery](git-recovery.md). Distinguish new authorization from existing-scope execution issues; routine correction/reverification needs no duplicate approval. Revalidate after resolution.
14
16
 
15
17
  <a id="probe-scope"></a>
16
18
  ### Probe scope
@@ -22,24 +24,26 @@ If explicitly requested probe+implementation is fully specified, classify the wh
22
24
  <a id="formal-drafting-boundary"></a>
23
25
  ## Formal drafting boundary
24
26
 
25
- Initial design/brainstorming may precede target choice. Formal spec/plan includes complete pre-save drafts returned only in chat. Before that, Orchestrator records actual absolute workspace/repository, branch/detached HEAD/HEAD, changes/ownership, explicit choice/preparation and in-target absolute document paths. Planner drafts for verified target; Orchestrator saves there. Missing/mismatched evidence stops affected drafting/saving. Planned paths prove no actual preparation. Initial preparation never starts Implementer without full handoff.
27
+ Initial design/brainstorming may precede target choice. Before the first required spec/plan draft, record verified source workspace/repository, branch/HEAD, changes/ownership, explicit choice, approved scope/depth/document set and planned relative paths. Planner returns the complete first draft and self-review in chat before target preparation. Revalidate source and selected target at the focused preparation gate, prepare the chosen target, compare actual target and preservation, then review and save in the verified prepared target. Planned paths prove no preparation. Missing or mismatched evidence holds affected work; preparation alone never starts Implementer.
26
28
 
27
29
  Small-task direct edits follow only [handoff](handoff.md#orchestrator-direct-path-for-small-tasks), stopping/reclassifying changed scope/risk. Otherwise follow this reference. `brainstorming`/`writing-plans` instruct existing roles, not extra agents; ARC routing/approval overrides alternate executors. Missing required skills block their stage. Integration includes saving, reviewing and directly revising the same spec or plan after the complete Planner first draft; design/brainstorming retain their Planner drafting path.
28
30
 
29
- For Korean requests/briefs, Planner uses Korean titles/prose unless another language is requested, preserving identifiers, skill/API names, code, paths, commands and literals. Orchestrator preserves agreed language when saving.
31
+ For Korean requests and briefs, Planner uses Korean titles and prose unless another language is requested, preserving identifiers, skill/API names, code, paths, commands and literals. Orchestrator preserves agreed language when saving.
30
32
 
31
33
  <a id="planner-drafting-preparation-and-consolidated-feedback"></a>
32
34
  ## Planner drafting preparation and consolidated feedback
33
35
 
34
36
  Planner reports initial/revision progress only to Orchestrator: brief stage/feedback/blocker/next step, never body/excerpts/incremental drafts. Return complete draft/self-review. Raw responses may appear in app/tool surfaces; prompt policy cannot hide them. User updates stay brief/status-based without draft content. At approval present integrated/saved/reviewed Markdown without repeating its body in chat.
35
37
 
38
+ Before Planner dispatch, evaluate F1–F8 direct-path eligibility and Bounded plan-omission eligibility independently. Enter each path only when all of that path's existing conditions are proven; if neither path qualifies, use normal full evaluation. Orchestrator can read a few already identified files directly; route material research and broad static exploration to Scouter.
39
+
36
40
  1. Preflight relevant policy/references/profiles/tests/parity/manifest/conflicts. Apply formal-target and coordination provenance/input/local-skill gates, including required paths, approved bounded read alternatives and return conditions. Missing inputs/conflicts hold drafting. Read recovery grants no preparation/check/write/approval authority.
37
41
  2. Maintain a compact ledger of original requirements/IDs, approved/open decisions, exclusions and scope deltas. Consolidate intervening user changes into one brief delta. Separate approvals, path/line-verified facts, assumptions and open decisions.
38
- 3. Send one complete stage-specific initial brief: prepared target/HEAD/ownership, scope/exclusions/depth/dependencies/shared contracts/criteria/checks/evidence/holds; preceding approved versions, paths, current behavior and connected source/generated/runtime/installer/docs, policy conflicts/resolution direction and exact deliverable. Visual work includes approved sources/versions/criteria/open interpretations.
42
+ 3. Reuse a relevant Planner instance across related stages when role, context and source remain valid. Send one complete stage-specific initial brief. Include verified source/HEAD/ownership, selected target intent and planned paths (or prepared target for later stages), and scope/exclusions/depth/dependencies/shared contracts/criteria/checks/evidence/holds. Include preceding approved versions and paths, current behavior and connected source/generated/runtime/installer/docs, policy conflicts/resolution direction and exact deliverable. Visual work includes approved sources/versions/criteria/open interpretations. Follow-up briefs send changed fields and cite an accessible prior brief for unchanged fields; inaccessible conversations or hashes alone cannot replace required inputs. Reuse evidence only for the same source set, absolute locator, resolved path, content fingerprint, purpose, approval scope and accessible context. When source, target or scope changed, reconfirm affected inputs.
39
43
  4. Planner returns the complete proportional first draft and self-review. Orchestrator saves and reviews it against the fixed checklist: scope/evidence; targets/ownership/paths; policy/conflicts; task boundaries/dependencies/criteria; checks/evidence/holds; roles/approval/commit boundaries; completeness. For spec or plan, Orchestrator directly revises the same document, including substantive omissions, before requesting approval. New product decisions outside approved scope require user input first; Orchestrator then revises the document. Missing or failed first drafts stop the stage and do not transfer initial authorship.
40
44
  5. For design and brainstorming, finish the checklist before one consolidated correction covering every substantive omission with criterion/defect/evidence/expected fix. Use deltas/`unchanged`; never split by omission. Further calls after correction need approved material scope/evidence changes or concrete unmet checklist items plus meaningful new input/criterion progress. State changed input/target criterion/expected artifact/progress. Existing-scope omissions need no renewed scope approval. Repeated unmet criteria without new input/evidence/progress hold the stage; report criterion/observation/exact unblock decision. Do not repeat resolved questions or count rewording/agent replacement as progress.
41
45
 
42
- For design and brainstorming, allow initial draft and one consolidated correction. Reformatting/re-expression/save/patch errors belong to Orchestrator, not Planner calls. Separate substantive work uses normal gates. Design/spec/plan stages remain separately ordered; spec approval precedes dependent planning. Add no fixed call cap/profile count condition or incomplete-draft acceptance.
46
+ For design and brainstorming, allow initial draft and one consolidated correction. Orchestrator handles reformatting, re-expression, save and patch errors without Planner calls. Separate substantive work uses normal gates. Design/spec/plan stages remain separately ordered; spec approval precedes dependent planning. Add no fixed call cap/profile count condition or incomplete-draft acceptance.
43
47
 
44
48
  <a id="design-and-planning"></a>
45
49
  <a id="design-path-and-plan-depth"></a>
@@ -53,10 +57,12 @@ Record depth separately from risk. Risk controls checks/review, not design paths
53
57
  4. Record dependencies/depth reasons. Planner returns complete plan/assumptions/open decisions/checks/self-review. Orchestrator saves/reviews/obtains approval before Implementer at user path or `docs/superpowers/plans/YYYY-MM-DD-<feature-name>.md`. Default spec path is `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`.
54
58
  5. Every Implementer first start/restart needs applicable approvals/artifacts/brief/findings and full handoff. Non-behavior docs/config omit only inapplicable stages. Direct edits need approved scope record and same first-edit gates.
55
59
 
56
- If Bounded requires written spec, record necessity/scope using approved short design as basis. Planner returns proportional full spec/self-review; Orchestrator saves/reviews/obtains individual approval before dependent plan/preparation. Short design approval never substitutes. This applies recorded judgment, not new spec necessity. Required plans alone imply no written spec.
60
+ If Bounded requires written spec, record necessity/scope using approved short design as basis. Planner returns proportional full spec/self-review; After actual preparation, Orchestrator saves/reviews/obtains individual approval in the prepared target before the dependent plan. Short design approval never substitutes. This applies recorded judgment, not new spec necessity. Required plans alone imply no written spec.
57
61
 
58
62
  Only Planner authors the first complete spec or plan draft and self-review. Orchestrator reviews and directly revises the same document before approval. Unavailable/failed Planner stops first drafting rather than transferring authorship. Design approval does not transfer initial authorship.
59
63
 
64
+ For initial fast judgment apply the [fast gate](handoff.md#orchestrator-direct-path-for-small-tasks); its sufficient conditions do not change these depth decisions.
65
+
60
66
  <a id="top-level-plan-tasks"></a>
61
67
  ## Top-level plan tasks
62
68
 
@@ -85,7 +91,7 @@ Before first Architectural spec, required Bounded spec or plan save, read this a
85
91
  | Spec | Approved design scope/evidence, complete Planner draft/self-review, Orchestrator integration/save/review ownership and absolute path. Required Bounded specs also need depth judgment, short design, necessity/scope |
86
92
  | Plan | Approved design and required approved spec/version/evidence, depth judgment, plan list/dependencies, complete Planner draft/self-review, ownership and absolute path |
87
93
 
88
- Saving for review does not require prior written-spec approval, but does require valid target choice/actual preparation. Individually approved specs enter the document set before dependent planning. Never call uncommitted files committed. Missing/mismatched evidence stops the affected save.
94
+ Saving for review does not require prior written-spec approval, but does require valid target choice/actual preparation. Individually approved specs enter the document set before dependent planning. Never call uncommitted files committed. Missing or mismatched evidence stops the affected save.
89
95
 
90
96
  <a id="complete-planning-document-set"></a>
91
97
  ## Complete planning-document set
@@ -94,7 +100,7 @@ Include all current-scope required specs/plans, excluding future projects. Recor
94
100
 
95
101
  A required plan implies no spec. Bounded follows recorded judgment; spec stages cover Architectural/required Bounded only, plan stages required plans only. Add no spec duty to Spike/plan-skip/non-behavior paths.
96
102
 
97
- Order: initial design approval → explicit target → actual preparation/verification → required spec drafting/save/self-review/individual approval → required plans drafting/save/review/individual approval → full set → verify existing commits/batch remaining approvals → final Git → Implementer. Omission skips only inapplicable documents with rationale. Complete documents precede staging/commit/handoff, not initial preparation/choice request. Partial approvals authorize no early implementation.
103
+ Order: initial design approval → explicit target and source snapshot → first required draft/self-review → focused preparation gate → actual preparation/comparison → save/review/individual approval → dependent plans drafting/save/review/individual approval → full set → verify existing commits/batch remaining approvals → final Git → Implementer. Omission skips only inapplicable documents with rationale. Complete documents precede staging/commit/handoff, not initial preparation/choice request. Partial approvals authorize no early implementation.
98
104
 
99
105
  Defer local `brainstorming`'s early spec commit: save/self-review/approve uncommitted spec, approve all plans, then final batch. Modify no external skill. `writing-plans` implementation commits grant neither planning-document commits nor early Implementer.
100
106
 
@@ -102,8 +108,8 @@ Defer local `brainstorming`'s early spec commit: save/self-review/approve uncomm
102
108
  |---|---|
103
109
  | Spike | Approve question/method/scope/criteria/constraints; no forced unknown-implementation docs. Implementer probes require handoff; read-only Orchestrator/Planner probes without Implementer do not trigger it |
104
110
  | Bounded, plan skipped | Approve short design; record omission conditions. Complete approvals, target choice, actual docs or valid absence, then handoff |
105
- | Bounded, plan required | Approve design/all required plans; approve necessary specs before dependent planning. Prepare target before drafting, complete set before batch/handoff |
106
- | Architectural | Design → written spec → all `writing-plans` plans. Target before drafting; full set before batch/final checks/handoff |
111
+ | Bounded, plan required | Approve design/all required plans; approve necessary specs before dependent planning. Draft the first required document before target preparation; complete set before batch/handoff |
112
+ | Architectural | Design → written spec → all `writing-plans` plans. First spec draft before target preparation; full set before batch/final checks/handoff |
107
113
  | Non-behavior docs/config | Record scope/criteria/inapplicability. Preserve target/Git/docs-or-absence/handoff |
108
114
 
109
115
  New planning scope requires list/depth/dependency reassessment and approvals before dependent drafting/commit. Approved-body changes need revised approval except mechanical records below. New implementation scope holds execution until docs/approvals/preparation complete. Running Implementer stops before newly dependent edits; inspect actual changes/Git without retrospective preparation claims. Reflect approved versions through original-commit updates below. Unchanged-document restarts follow handoff lifetime.
@@ -113,7 +119,7 @@ New planning scope requires list/depth/dependency reassessment and approvals bef
113
119
 
114
120
  Mechanical checkboxes/results/evidence/completion preserving the approved contract need no new approval; record classification/reason/actual time. Removing checks, invented success, unsupported acceptance or mixed scope changes are not mechanical.
115
121
 
116
- Goal/task boundary/scope/criteria/method/risk/dependency/check changes require revised approval before affected work. Hold, obtain ordered design/spec/plan approvals and prepare target/ownership/checks/handoff. Other body corrections follow version approval; classify typos/paths/commands/explanations by semantic effect.
122
+ Changes to goals, task boundaries, scope, criteria, method, risk, dependencies or checks require revised approval before affected work. Hold, obtain ordered design/spec/plan approvals and prepare target/ownership/checks/handoff. Other body corrections follow version approval; classify typos/paths/commands/explanations by semantic effect.
117
123
 
118
124
  Both kinds amend the original planning commit at safe boundaries, never separate docs/fixup commits, including new scope. Preserve implementation commits. Rewriting does not backdate approval; latest content in an old commit proves no earlier authorization.
119
125
 
@@ -151,7 +157,7 @@ Generated from lib/policy/role-policy.json; do not edit this managed section man
151
157
  - [review.significant-plan] Significant/high-risk plan work follows Implementer → required fresh QA → Orchestrator acceptance and focused commit for each task. After all tasks are accepted and required QA is complete, Reviewer reviews the final full change once. Reviewer is not a per-task or per-commit gate. Preserve required checks and necessary finding rechecks.
152
158
  - [review.final-plan-recheck] Final overall Reviewer findings on plan work, including behavior findings, follow Implementer → Reviewer without QA reassignment until resolved. This exception applies only at the final plan stage. Preserve required QA and QA failure rechecks before the initial final review and outside this exception.
153
159
  - [planning.plan-amend] Mechanical plan progress records during approved execution need no new user approval. Changes to goals, scope, criteria, method, risk, dependencies or required verification need revised-version approval and preparation before affected work. In both cases Orchestrator amends the original planning-document commit at a safe boundary, with no separate document or fixup commit. Preserve actual approval chronology, recoverable old/new content, hashes, versions and SHA mappings; never authorize past work retroactively. Rewrite only unpublished, unshared local linear history with clear ownership and no merges. Unknown publication/ownership, unexpected state or replay conflicts stop rewriting; no force push or fallback document commit. Preserve implementation task boundaries and revalidate affected evidence. Follow planning, handoff-prep, commit and verification for the full contract.
154
- - [planning.planner-brief] Follow planning.md's Planner drafting preparation and review procedure, including preflight, ledger, evidence classification, target preparation, preceding approvals, provenance and reference gates. Send one complete stage-specific initial brief. Planner returns a complete proportional first draft and self-review. Orchestrator reviews and directly revises the same spec or plan when needed. A missing or failed first draft does not transfer initial authorship to Orchestrator. New product decisions outside approved scope require user input before Orchestrator revises the document. For design and brainstorming, allow the initial draft and one consolidated correction. Orchestrator reviews once with the fixed checklist and sends one consolidated correction using changed fields only. Further calls require user-approved material scope/evidence changes or concrete unmet checklist items after correction, plus meaningful new input or criterion-state progress. State changed input, target criterion, expected artifact and expected progress. Repeated unmet criteria without new input, evidence or progress hold the stage; report the criterion, observed evidence and exact unblock input/decision. Do not split omissions, repeat resolved items or count rewording/agent replacement as progress. Do not invoke Planner only for reformatting, re-expression, save errors or patch-application errors; Orchestrator handles these. Evaluate separate substantive work under the normal Planner gate. Preserve separate sequential design/spec/plan approvals. Add no fixed call cap or profile call-count conditions, and never accept an incomplete draft. Keep user progress brief and based on Planner status without relaying draft content. At approval, present the integrated, saved and reviewed Markdown without repeating its body in chat.
160
+ - [planning.planner-brief] Follow planning.md's Planner drafting preparation and review procedure, including preflight, ledger, evidence classification, target preparation, preceding approvals, provenance and reference gates. Before Planner dispatch, evaluate F1–F8 direct-path eligibility and Bounded plan-omission eligibility independently. Enter each path only when all of that path's existing conditions are proven; if neither path qualifies, use normal full evaluation. Send one complete stage-specific initial brief. Reuse the same Planner instance across related stages when its role, context and source remain valid. Follow-up briefs send changed fields and cite an accessible prior brief for unchanged fields. Reuse evidence only for the same source set, absolute locator, resolved path, content fingerprint, purpose and approval scope in an accessible matching context. When source, target or scope changed, reconfirm affected inputs. Planner returns a complete proportional first draft and self-review. Orchestrator reviews and directly revises the same spec or plan when needed. A missing or failed first draft does not transfer initial authorship to Orchestrator. New product decisions outside approved scope require user input before Orchestrator revises the document. For design and brainstorming, allow the initial draft and one consolidated correction. Orchestrator reviews once with the fixed checklist and sends one consolidated correction using changed fields only. Further calls require user-approved material scope/evidence changes or concrete unmet checklist items after correction, plus meaningful new input or criterion-state progress. State changed input, target criterion, expected artifact and expected progress. Repeated unmet criteria without new input, evidence or progress hold the stage; report the criterion, observed evidence and exact unblock input/decision. Do not split omissions, repeat resolved items or count rewording/agent replacement as progress. Do not invoke Planner only for reformatting, re-expression, save errors or patch-application errors; Orchestrator handles these. Evaluate separate substantive work under the normal Planner gate. Preserve separate sequential design/spec/plan approvals. Add no fixed call cap or profile call-count conditions, and never accept an incomplete draft. Keep user progress brief and based on Planner status without relaying draft content. At approval, present the integrated, saved and reviewed Markdown without repeating its body in chat.
155
161
  - [platform.codex] Role restrictions are prompt-level contracts, not a filesystem sandbox. ARC supplies no native observer hook or role-specific Git guard. Existing shell capabilities are retained because static read access after shell reduction has not been verified; Implementer and QA need shell for approved checks.
156
162
  [ARC role policy: contract end]
157
163
 
@@ -114,6 +114,13 @@ umask 077
114
114
  SESSION_ID="$$-$(date +%s)"
115
115
 
116
116
  if [[ -n "$PROJECT_DIR" ]]; then
117
+ # Make the project dir absolute now: the script cds into its own directory
118
+ # before starting the server, which would re-root a relative path there.
119
+ # mkdir -p keeps the existing behavior of creating a missing project dir.
120
+ if ! PROJECT_DIR="$(mkdir -p -- "$PROJECT_DIR" && CDPATH='' cd -- "$PROJECT_DIR" && pwd)"; then
121
+ echo "{\"error\": \"Cannot create or enter --project-dir\"}"
122
+ exit 1
123
+ fi
117
124
  SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
118
125
  # Persist the bound port and key per project so a restart reuses them and an
119
126
  # already-open browser tab reconnects to the same URL with a valid cookie.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "repository": "https://github.com/obra/superpowers",
4
- "revision": "2ce8aa15d05bd57213e6f746f953aafb7ded7a8b",
5
- "fetchedAt": "2026-10-29T17:10:27.098Z",
4
+ "revision": "22f0b463e7a5e579904dd6211a520321035526f7",
5
+ "fetchedAt": "2026-11-06T16:48:59.704Z",
6
6
  "skills": [
7
7
  "brainstorming",
8
8
  "writing-plans",
@@ -10,12 +10,12 @@
10
10
  ],
11
11
  "files": {
12
12
  "LICENSE": "a37e0e9697144819e1d965176ac4ae5bc3fa02d11e7812036bbcadf6dafe2400",
13
- "brainstorming/SKILL.md": "61a7ef918652108c76a3fd01e02dd92d39996c6b46d4cb2819cbd03f6c45e46a",
13
+ "brainstorming/SKILL.md": "2c9ca71f94886f9ca9f4aef055f7cafb3e1a9bdf3aeebf8f4cb547c69405d207",
14
14
  "brainstorming/builder-check-prompt.md": "36d51a4723d6f0a3ce5811a0ea3b3a8424f07533947e1355f5d8cb8cce40c55e",
15
15
  "brainstorming/scripts/frame-template.html": "6a8a4e58bd6a44b904e2e3c57de774481d909204597e1498de53f1b2fecc4c4e",
16
16
  "brainstorming/scripts/helper.js": "43c6d69954a46ec34a2a262bcc62a9a7e83e839c739f199cb72646d397c686e3",
17
17
  "brainstorming/scripts/server.cjs": "763abf96510e0b892a1141397b31aa82ef900cf87ed34dde94bc8782baf4a7ca",
18
- "brainstorming/scripts/start-server.sh": "a4e5ae84275bcaacd2f84345afeabe59cf7b00ba080e123da7cc1fb226f12847",
18
+ "brainstorming/scripts/start-server.sh": "314dd68dc00e8dd94c7491aca8a5f004c7fddffc161e2b07482f29f48d13659a",
19
19
  "brainstorming/scripts/stop-server.sh": "0b5ccbbd57f62d3ed88993f7940b5ee0e5c0fc9b21c550c623da4f6292e47daf",
20
20
  "brainstorming/visual-companion.md": "6ffcb038b53254bd31debaa15e12dcd4ff24bd63d690e0b644ac6396701cf2e2",
21
21
  "writing-plans/SKILL.md": "a6c67c1900064347c2a329990dd3c555657c51c3ec53b259a08aa01a2c26139a",
@@ -8,14 +8,16 @@ Read before verification design, QA/Reviewer assignment, correction rechecks or
8
8
  <a id="visual-evidence-and-adjudication"></a>
9
9
  ## Visual evidence and adjudication
10
10
 
11
- Each criterion needs exact approved source/version/approval/access, IDs/properties/method and output-revision linkage. Implementer reporting does not replace fresh QA. Record pass/mismatch/unverified and connect to criterion verdicts; pass needs accessible supporting evidence. Missing linkage is unverified; visible difference is mismatch, separately, without inferring current branch UI. Inaccessible materials/missing interpretation approval hold comparison.
11
+ Each criterion needs the exact approved source, version, approval and access state; IDs, properties and comparison method; and a link between output and revision. Implementer reporting does not replace fresh QA. Record pass/mismatch/unverified and connect to criterion verdicts; pass needs accessible supporting evidence. Missing linkage is unverified; visible difference is mismatch, separately, without inferring current branch UI. Hold comparison when materials are inaccessible or interpretation approval is missing.
12
12
 
13
- Policy/generated/manifest checks prove structure, not role behavior, installed builds, Claude Code CLI compliance or fidelity. Report changes/exclusions/run-omitted checks/criterion evidence-limits/platform availability. Separate baseline failures; independent progress relaxes no checks.
13
+ Policy, generated-file and manifest checks prove structure, not role behavior, installed builds, Claude Code CLI compliance or fidelity. Report changes, exclusions, checks run and omitted, criterion evidence and limits, and platform availability. Report baseline failures separately; independent progress does not relax checks.
14
14
 
15
15
  <a id="risk-scaled-execution"></a>
16
16
  ## Risk-scaled execution
17
17
 
18
- Edits belong to Implementer except the [small direct path](handoff.md#orchestrator-direct-path-for-small-tasks). Orchestrator determines behavior/risk/minimum meaningful checks.
18
+ The [fast gate](handoff.md#orchestrator-direct-path-for-small-tasks) references this risk definition; refresh affected evidence when inputs change and preserve independent verification boundaries.
19
+
20
+ Edits belong to Implementer except the [small direct path](handoff.md#orchestrator-direct-path-for-small-tasks). Orchestrator determines behavior, risk and minimum meaningful checks.
19
21
 
20
22
  | Risk | Minimum flow |
21
23
  |---|---|
@@ -34,7 +36,7 @@ Ordinary/significant tasks need fresh QA before acceptance. Low risk requires na
34
36
 
35
37
  Record which risk/instruction/approved obligations each check satisfies. Do not automatically assign QA/Reviewer for clear low-risk scope with sufficient evidence and no requirement. Metadata counts alone prove no significance; one permission/loss rule may. Vague file deletion is not low-risk evidence; reclassify installer/manifest impacts. Confirm cleanup targets unused, required materials intact, ignore patterns scoped and diff approved before focused-check-only acceptance.
36
38
 
37
- Separate environment/code failure and report exact limits/gaps. Use Planner/Scouter only for unresolved planning/discovery. Reviewer covers substantial correctness/factual/security/data/interface risks. Orchestrator assigns support as needed. Claim only verified outputs. Read cited `references/test-driven-development.md`, preserving exceptions.
39
+ Distinguish environment failures from code failures and report exact limits and gaps. Use Planner/Scouter only for unresolved planning/discovery. Reviewer covers substantial correctness/factual/security/data/interface risks. Orchestrator assigns support as needed. Claim only verified outputs. Read cited `references/test-driven-development.md`, preserving exceptions.
38
40
 
39
41
  <a id="final-plan-reviewer-correction-loop"></a>
40
42
  ### Final plan Reviewer correction loop
@@ -6,7 +6,7 @@ import {observeCooperativeTarget,validateObservedTarget,validateWorkflowId,workf
6
6
  import {saveDurableState} from './store.mjs';
7
7
  export const COOPERATIVE_RUNTIME_MODULES=Object.freeze([
8
8
  'cli.mjs','command.mjs','bootstrap.mjs','cooperative-schema.mjs','cooperative.mjs','evaluate.mjs',
9
- 'scope.mjs','store.mjs','git-evidence.mjs','assignment.mjs','verification.mjs','acceptance.mjs','external.mjs','schema.mjs','operations.mjs','gate.mjs','adapters/codex.mjs','adapters/claude.mjs','capabilities.mjs','identity.mjs','git-query.mjs'
9
+ 'scope.mjs','store.mjs','git-evidence.mjs','assignment.mjs','verification.mjs','acceptance.mjs','external.mjs','schema.mjs','operations.mjs','gate.mjs','adapters/codex.mjs','adapters/claude.mjs','capabilities.mjs','identity.mjs','git-query.mjs','target-preparation.mjs'
10
10
  ]);
11
11
  export const cooperativeCoverage=()=>({enforcement:'cooperative',provenance:'operator-attested',nativeVerified:false});
12
12
  export function commandResult({workflowId,state,input,decision='allow',ruleId='cooperative.recorded',persisted=false,...extra}){
@@ -16,7 +16,7 @@ export async function cooperativeVersions(){
16
16
  const hash=bytes=>'sha256:'+crypto.createHash('sha256').update(bytes).digest('hex');
17
17
  let policy;try{policy=await fs.readFile(new URL('../policy/role-policy.json',import.meta.url));}catch(error){if(error.code!=='ENOENT')throw error;policy=await fs.readFile(new URL('./role-policy.json',import.meta.url));}
18
18
  const runtime=await Promise.all(COOPERATIVE_RUNTIME_MODULES.map(file=>fs.readFile(new URL(file,import.meta.url))));
19
- return {policy:'1',runtime:'cooperative-4',policyHash:hash(policy),runtimeHash:hash(Buffer.concat(runtime))};
19
+ return {policy:'1',runtime:'cooperative-6',policyHash:hash(policy),runtimeHash:hash(Buffer.concat(runtime))};
20
20
  }
21
21
  export async function initializeCooperativeWorkflow({cwd,workflowId,input,observe=observeCooperativeTarget,storage={}}){
22
22
  validateWorkflowId(workflowId);validateCooperativeCommand({command:'init',input});
@@ -2,6 +2,8 @@ import {ROLES} from './operations.mjs';
2
2
  import {assertSecretFree} from './external.mjs';
3
3
  import {object,text,validatePlannerLedger} from './schema.mjs';
4
4
  import {absolute,validateWorkflowId} from './scope.mjs';
5
+ import path from 'node:path';
6
+ import {validateTargetPreparationRecord} from './target-preparation.mjs';
5
7
  const fail=message=>{throw Error('Cooperative schema: '+message);};
6
8
  const hash=value=>typeof value==='string'&&/^sha256:[a-f0-9]{64}$/.test(value);
7
9
  const revision=value=>Number.isSafeInteger(value)&&value>=0;
@@ -9,6 +11,24 @@ const actor=value=>object(value)&&text(value.id)&&Object.keys(value).every(k=>['
9
11
  const statement=value=>object(value)&&text(value.messageRef)&&object(value.data);
10
12
  function fields(value,allowed){if(!object(value)||Object.keys(value).some(k=>!allowed.includes(k)))fail('unexpected input field');}
11
13
  function json(value){try{const encoded=JSON.stringify(value);if(encoded===undefined)fail('JSON input');}catch{fail('JSON input');}}
14
+ const exact=(value,keys)=>object(value)&&Object.keys(value).length===keys.length&&keys.every(key=>Object.hasOwn(value,key));
15
+ const oid=value=>typeof value==='string'&&/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(value);
16
+ const time=value=>typeof value==='string'&&Number.isFinite(Date.parse(value))&&/(?:Z|[+-]\d\d:\d\d)$/.test(value);
17
+ const unique=value=>Array.isArray(value)&&new Set(value).size===value.length;
18
+ const gitPath=value=>text(value)&&!/[\\\x00\r\n:]/.test(value)&&!value.startsWith('/')&&!value.split('/').some(part=>!part||part==='.'||part==='..');
19
+ const ref=value=>text(value)&&/^refs\/[A-Za-z0-9][A-Za-z0-9._\/-]*$/.test(value)&&!value.includes('..')&&!value.includes('//')&&!value.endsWith('/')&&!value.endsWith('.')&&!value.endsWith('.lock');
20
+ const approval=value=>exact(value,['ref','version','hash','approvedAt'])&&text(value.ref)&&Number.isSafeInteger(value.version)&&value.version>0&&hash(value.hash)&&time(value.approvedAt);
21
+ const document=value=>exact(value,['version','hash','content'])&&Number.isSafeInteger(value.version)&&value.version>0&&hash(value.hash)&&typeof value.content==='string'&&Buffer.byteLength(value.content)<=1024*1024;
22
+ export function validatePlanAmendArguments(a){
23
+ if(!exact(a,['classification','reason','changedAt','affectedTaskIds','plan','history','approvals','writers','ownership','publication','remotes','recovery'])||!['contract','mechanical'].includes(a.classification)||!text(a.reason)||!time(a.changedAt)||!unique(a.affectedTaskIds)||!a.affectedTaskIds.length||!a.affectedTaskIds.every(text))fail('plan-amend request');
24
+ if(!exact(a.plan,['path','gitPath','old','new'])||!absolute(a.plan.path)||!gitPath(a.plan.gitPath)||!document(a.plan.old)||!document(a.plan.new)||a.plan.new.version<=a.plan.old.version)fail('plan-amend document');
25
+ if(!exact(a.history,['base','planCommit','head','branch'])||![a.history.base,a.history.planCommit,a.history.head].every(oid)||!text(a.history.branch)||/[\x00\r\n]/.test(a.history.branch))fail('plan-amend history');
26
+ if(!exact(a.approvals,['original','amendment'])||!approval(a.approvals.original)||!(a.classification==='mechanical'&&a.approvals.amendment===null||approval(a.approvals.amendment)))fail('plan-amend approvals');
27
+ if(!Array.isArray(a.writers)||!a.writers.length||a.writers.some(w=>!exact(w,['assignmentId','stoppedAt','evidenceRef'])||!text(w.assignmentId)||!time(w.stoppedAt)||!text(w.evidenceRef))||!unique(a.writers.map(w=>w.assignmentId)))fail('plan-amend writers');
28
+ if(!['exclusive','shared','unknown'].includes(a.ownership)||!['unpublished','published','unknown'].includes(a.publication)||!Array.isArray(a.remotes)||a.remotes.some(r=>!exact(r,['name','identity','complete','refs','observedAt','evidenceRef'])||!text(r.name)||!hash(r.identity)||r.complete!==true||!time(r.observedAt)||!text(r.evidenceRef)||!Array.isArray(r.refs)||r.refs.some(x=>!exact(x,['name','oid'])||!ref(x.name)||!oid(x.oid))||!unique(r.refs.map(x=>x.name)))||!unique(a.remotes.map(r=>r.name)))fail('plan-amend remote snapshot');
29
+ if(!exact(a.recovery,['ref','oid'])||!ref(a.recovery.ref)||!a.recovery.ref.startsWith('refs/arc/recovery/')||!oid(a.recovery.oid)||path.normalize(a.plan.path)!==a.plan.path)fail('plan-amend recovery');
30
+ return a;
31
+ }
12
32
  export function validateCooperativeCommand(command){
13
33
  assertSecretFree(command);fields(command,['command','input']);const {input,command:name}=command;
14
34
  if(!['init','record','status','reconcile','prepare','result'].includes(name))fail('command');
@@ -25,7 +45,7 @@ export function validateCooperativeCommand(command){
25
45
  if(name==='record'){
26
46
  fields(input.evidence,['kind','messageRef','data']);
27
47
  if(input.evidence?.data?.affectedTaskIds!==undefined&&(!Array.isArray(input.evidence.data.affectedTaskIds)||!input.evidence.data.affectedTaskIds.every(text)||new Set(input.evidence.data.affectedTaskIds).size!==input.evidence.data.affectedTaskIds.length))fail('affected task IDs');
28
- if(!statement(input.evidence)||!['note','approval','external-approval','document','scope','check','finding','review','acceptance-evidence','planner-stage','planner-evidence','planner-result','planner-review'].includes(input.evidence.kind))fail('evidence');
48
+ if(!statement(input.evidence)||!['note','approval','external-approval','document','scope','check','finding','review','acceptance-evidence','planner-stage','planner-evidence','planner-result','planner-review','target-preparation'].includes(input.evidence.kind))fail('evidence');
29
49
  }
30
50
  if(resultStatement){
31
51
  fields(input.result,['status','messageRef','data','notExecutedEvidence']);
@@ -33,6 +53,7 @@ export function validateCooperativeCommand(command){
33
53
  if(input.result.status==='cancelled'&&(!object(input.result.notExecutedEvidence)||!text(input.result.notExecutedEvidence.messageRef)))fail('cancel requires not-executed evidence');
34
54
  }
35
55
  if((name==='prepare'||name==='result'&&!resultStatement)&&!text(input.operation))fail('operation');
56
+ if(name==='prepare'&&input.operation==='plan-amend')validatePlanAmendArguments(input.arguments);
36
57
  return command;
37
58
  }
38
59
  export function validateCooperativeState(state){
@@ -44,13 +65,23 @@ export function validateCooperativeState(state){
44
65
  for(const key of ['actors','selections','assignments','identities','leases','scopes','approvals','documents','tasks','requests','intents','results','checks','findings','acceptances','commits','audit','unresolved','records','targetHistory'])if(!Array.isArray(state[key]))fail(key);
45
66
  if(state.tasks.some(t=>!object(t)||!text(t.id)||!Array.isArray(t.paths)||!Array.isArray(t.criteria)||!Array.isArray(t.requiredChecks)||!Array.isArray(t.dependencies))||new Set(state.tasks.map(t=>t.id)).size!==state.tasks.length)fail('task contracts');
46
67
  if(state.commitObservations!==undefined&&(!Array.isArray(state.commitObservations)||state.commitObservations.some(o=>!object(o)||o.source!=='local-git'||!text(o.intentId)||!revision(o.revision))))fail('commit observations');
68
+ if(state.planAmendObservations!==undefined&&(!Array.isArray(state.planAmendObservations)||state.planAmendObservations.some(o=>!object(o)||o.source!=='local-git'||!text(o.intentId)||!revision(o.revision))))fail('plan-amend observations');
69
+ if(state.planAmendments!==undefined&&(!Array.isArray(state.planAmendments)||state.planAmendments.some(a=>!object(a)||a.source!=='operator-attested'||a.observationSource!=='local-git'||!text(a.intentId)||!absolute(a.path)||!hash(a.oldHash)||!hash(a.newHash)||!oid(a.oldCommit)||!oid(a.newCommit)||!Array.isArray(a.mapping)||!a.mapping.length)))fail('plan-amend connections');
47
70
  if(!revision(state.scopeVersion)||!revision(state.targetVersion))fail('scope/target version');
48
71
  if(state.actors.some(a=>!actor(a.actor)||a.source!=='operator-attested')||state.selections.some(s=>s.source!=='operator-attested'||!text(s.messageRef)))fail('attestations');
49
72
  for(const key of ['scopes','approvals','documents','checks','findings','acceptances','commits'])if(state[key].some(entry=>!object(entry)||entry.source!=='operator-attested'))fail('attestation source');
50
73
  if(state.records.some(r=>!actor(r.actor)||r.source!=='operator-attested'||!text(r.kind)||!statement(r)))fail('records');
51
74
  if(state.requests.some(r=>!text(r.requestId)||!hash(r.requestFingerprint)||!object(r.decision))||new Set(state.requests.map(r=>r.requestId)).size!==state.requests.length)fail('request replay');
52
75
  if(state.intents.some(i=>!text(i.requestId)||!['prepared','succeeded','failed','unknown','mismatch','cancelled'].includes(i.status)||!hash(i.fingerprint))||new Set(state.intents.map(i=>i.requestId)).size!==state.intents.length)fail('intents');
76
+ for(const intent of state.intents.filter(i=>i.operation==='plan-amend')){validatePlanAmendArguments(intent.arguments);if(!object(intent.target)||!object(intent.versions)||!object(intent.observation)||intent.source!=='operator-attested'||intent.checkpoint!==undefined&&!object(intent.checkpoint))fail('plan-amend intent');}
53
77
  if(state.results.some(r=>!text(r.requestId)||!['succeeded','failed','unknown','mismatch','cancelled'].includes(r.status)||r.source!=='operator-attested'||!object(r.data)))fail('results');
54
78
  if(state.plannerLedger!==undefined){validatePlannerLedger(state.plannerLedger);if(state.plannerLedger.stages.some(stage=>stage.workflowId!==state.workflowId||!Array.isArray(stage.artifacts)))fail('Planner workflow binding or artifacts');}
79
+ for(const key of ['targetIntents','targetCheckpoints','targetTransitions','targetDocuments'])if(state[key]!==undefined&&!Array.isArray(state[key]))fail(key);
80
+ const preparationIntents=state.targetIntents??[],checkpoints=state.targetCheckpoints??[],transitions=state.targetTransitions??[],documents=state.targetDocuments??[];
81
+ if(new Set(preparationIntents.map(i=>i.id)).size!==preparationIntents.length)fail('target intent identity');
82
+ for(const intent of preparationIntents){try{validateTargetPreparationRecord('intent',intent);}catch{fail('target intent');}const approved=state.records.find(r=>r.kind==='approval'&&r.messageRef===intent.approvalRef&&r.source==='operator-attested'&&r.actor?.role==='orchestrator'&&r.scopeVersion===intent.scopeVersion&&r.targetVersion===intent.sourceTargetVersion)?.data?.targetPreparation;if(intent.source!=='operator-attested'||intent.workflowId!==state.workflowId||!revision(intent.scopeVersion)||!hash(intent.sourceFingerprint)||!object(intent.sourceSnapshot)||!approved||['depth','selectionRef','kind','candidate','base','workflowId','firstStageId','sourceTargetVersion'].some(k=>approved[k]!==intent[k])||JSON.stringify(approved.sourceTarget)!==JSON.stringify(intent.sourceTarget)||JSON.stringify(approved.documents)!==JSON.stringify(intent.documents))fail('target intent provenance');}
83
+ for(const checkpoint of checkpoints){try{validateTargetPreparationRecord('checkpoint',{type:'checkpoint',...checkpoint});}catch{fail('target checkpoint');}if(checkpoint.source!=='operator-attested'||!text(checkpoint.messageRef)||!preparationIntents.some(i=>i.id===checkpoint.intentId&&i.firstStageId===checkpoint.stageId)||!state.records.some(r=>r.kind==='target-preparation'&&r.messageRef===checkpoint.messageRef&&r.source==='operator-attested'))fail('target checkpoint provenance');}
84
+ for(const transition of transitions){const request=transition.validation?.request;try{validateTargetPreparationRecord('transition',request);}catch{fail('target transition');}if(!object(transition.target)||!text(transition.messageRef)||!Number.isSafeInteger(transition.targetVersion)||transition.targetVersion!==transition.sourceTargetVersion+1||request.intentId!==transition.intentId||request.checkpointRef!==transition.checkpointRef||!preparationIntents.some(i=>i.id===transition.intentId)||!checkpoints.some(c=>c.intentId===transition.intentId&&c.messageRef===transition.checkpointRef)||!state.records.some(r=>r.kind==='target-preparation'&&r.messageRef===transition.messageRef&&r.source==='operator-attested'))fail('target transition provenance');}
85
+ for(const document of documents){try{validateTargetPreparationRecord('destination',document);}catch{fail('target document');}if(document.sourceLink!=='operator-attested'||document.verification?.status!=='verified'||document.verification?.source!=='local-bytes'||!text(document.messageRef)||!transitions.some(t=>t.intentId===document.intentId&&t.messageRef===document.transitionRef&&t.targetVersion===document.targetVersion)||!state.records.some(r=>r.kind==='target-preparation'&&r.messageRef===document.messageRef&&r.source==='operator-attested'))fail('target document provenance');}
55
86
  assertSecretFree(state);json(state);return state;
56
87
  }
@@ -1,7 +1,7 @@
1
1
  import {EXTERNAL_ACTIONS,reconcileCooperativeExternal,cooperativeSupport} from './external.mjs';
2
2
  import {reconcileCooperativeCommit} from './acceptance.mjs';
3
3
  import {evaluateCooperativeVerification,invalidateAffectedEvidence,readCooperativeChanges} from './verification.mjs';
4
- import {readAssignmentGitEvidence,readCooperativeCommitGitEvidence} from './git-evidence.mjs';
4
+ import {readAssignmentGitEvidence,readCooperativeCommitGitEvidence,readPlanAmendGitEvidence} from './git-evidence.mjs';
5
5
  import {recordCooperativeAssignmentResult} from './assignment.mjs';
6
6
  import fs from 'node:fs/promises';
7
7
  import {constants} from 'node:fs';
@@ -9,13 +9,14 @@ import path from 'node:path';
9
9
  import {createHash} from 'node:crypto';
10
10
  import {validateCooperativeCommand,validateCooperativeState} from './cooperative-schema.mjs';
11
11
  import {observeCooperativeTarget,validateObservedTarget,validateWorkflowId,workflowStatePath,inspectPath,absolute,within} from './scope.mjs';
12
- import {fingerprintRequest,saveDurableState,applyCooperativeCommitTransition} from './store.mjs';
12
+ import {fingerprintRequest,saveDurableState,applyCooperativeCommitTransition,applyCooperativeTargetTransition} from './store.mjs';
13
13
  import {commandResult,cooperativeVersions} from './bootstrap.mjs';
14
- import {evaluateCooperative,reconcileCooperativeResult} from './evaluate.mjs';
14
+ import {evaluateCooperative,reconcileCooperativeResult,reconcileCooperativePlanAmend} from './evaluate.mjs';
15
+ import {evaluateTargetPreparation,readTargetPreparationEvidence} from './target-preparation.mjs';
15
16
  const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
16
17
  const sameCheckout=(a,b)=>['realpath','gitCommonDir','gitDir'].every(k=>a[k]===b[k]);
17
18
  /** Only trusted code may inject observation/storage. CLI JSON cannot set either. */
18
- export async function runCooperativeCommand({cwd,workflowId,command,input,observe=observeCooperativeTarget,storage={},readGit=readAssignmentGitEvidence,readChanges=readCooperativeChanges,readCommitGit=readCooperativeCommitGitEvidence}){
19
+ export async function runCooperativeCommand({cwd,workflowId,command,input,observe=observeCooperativeTarget,storage={},readGit=readAssignmentGitEvidence,readChanges=readCooperativeChanges,readCommitGit=readCooperativeCommitGitEvidence,readPlanAmendGit=readPlanAmendGitEvidence,clock=Date.now}){
19
20
  validateWorkflowId(workflowId);validateCooperativeCommand({command,input});
20
21
  if(command==='init')throw Error('Use initializeCooperativeWorkflow for init');
21
22
  const target=await validateObservedTarget(cwd,await observe(cwd)),statePath=await workflowStatePath(target,workflowId);
@@ -26,7 +27,7 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
26
27
  if(!sameCheckout(state.target,target))return result(undefined,{decision:'hold',ruleIds:['cooperative.target']});
27
28
  const changes=state.tasks.length?await readChanges({state}):undefined;
28
29
  const evaluation=evaluateCooperative({state,request:{command,input},evidence:{target,versions:await cooperativeVersions(),changes}});
29
- const unresolved=state.plannerLedger?.stages.some(s=>s.pending)||state.unresolved.some(entry=>!entry.resolvedBy)||state.intents.some(i=>['prepared','unknown','mismatch'].includes(i.status)&&!(['check','review'].includes(i.operation)&&i.stale));
30
+ const unresolved=state.plannerLedger?.stages.some(s=>s.pending)||state.unresolved.some(entry=>!entry.resolvedBy)||state.intents.some(i=>(['prepared','unknown','mismatch'].includes(i.status)||i.operation==='plan-amend'&&i.status==='failed')&&!(['check','review'].includes(i.operation)&&i.stale));
30
31
  return {...result(state,evaluation.decision==='allow'&&unresolved?{decision:'hold',ruleIds:['cooperative.unresolved']}:evaluation,{persisted:true}),state,support:cooperativeSupport(),...(changes?{changeFingerprints:changes}:{})};
31
32
  }
32
33
  let lock,plannerLock;
@@ -49,10 +50,43 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
49
50
  if(command==='prepare'&&['accept','commit'].includes(input.operation)||commitIntent){
50
51
  try{git=await readCommitGit({target:currentTarget,...(commitIntent&&input.result?.data?.commit?{commit:input.result.data.commit}:{})});}catch(error){gitError=error.message;}
51
52
  }
53
+ const planIntent=state.intents.find(i=>i.operation==='plan-amend'&&i.requestId===((command==='record'&&input.evidence.kind==='note'&&input.evidence.data?.type==='plan-amend-checkpoint')?input.evidence.data.intentId:['result','reconcile'].includes(command)?input.intentId:null));
54
+ const planArguments=command==='prepare'&&input.operation==='plan-amend'?input.arguments:planIntent?.arguments;
55
+ let now;
56
+ if(planArguments){
57
+ try{git=await readPlanAmendGit({target:currentTarget,arguments:planArguments,mapping:command==='reconcile'&&input.result?.status==='succeeded'?input.result.data?.mapping??[]:[]});}catch(error){gitError=error.message;}
58
+ }
52
59
  const changes=state.tasks.length?await readChanges({state}):undefined;
53
60
  const plannerArtifacts=plannerWork?await readPlannerArtifacts(state,command==='record'&&input.evidence.kind==='planner-evidence'?input.evidence:undefined):undefined;
54
61
  const plannerHistory=command==='record'&&input.evidence.kind==='planner-stage'?await readPlannerHistory(state,statePath,input.evidence.data.document?.path):{};
55
- let decision=evaluateCooperative({state,request:{command,input},evidence:{target:currentTarget,versions,git,changes,requestFingerprint,plannerArtifacts,...plannerHistory}});
62
+ if(planArguments&&!gitError){try{now=clock();if(!Number.isFinite(now))throw Error('Invalid observation clock');}catch(error){gitError=error.message;}}
63
+ let destinationReview=false;
64
+ let decision=evaluateCooperative({state,request:{command,input},evidence:{target:currentTarget,versions,git,changes,requestFingerprint,plannerArtifacts,now,...plannerHistory}});
65
+ if(command==='record'&&input.evidence.kind==='planner-review'){
66
+ const data=input.evidence.data,stage=state.plannerLedger?.stages.find(s=>s.id===data.stageId),intent=state.targetIntents?.find(i=>i.id===stage?.targetIntentId),transition=state.targetTransitions?.find(t=>t.intentId===intent?.id&&t.targetVersion===state.targetVersion),document=intent?.documents.find(d=>d.kind===stage?.document.kind);
67
+ if(transition&&document){
68
+ let observed;try{observed=await readTargetPreparationEvidence({state,targetIntent:intent,destination:{realpath:state.target.realpath,path:document.path},observe,readChanges});}catch{}
69
+ const valid=input.actor.role==='orchestrator'&&stage.scopeVersion===state.scopeVersion&&same(versions,state.versions)&&same(currentTarget,state.target)&&!stage.pending&&stage.result?.status==='succeeded'&&stage.result.ref===data.resultRef&&stage.result.chatDraft?.complete===true&&data.full===true&&data.complete===true&&observed?.destination?.document?.hash===data.hash&&Array.isArray(data.criteria)&&data.criteria.length===stage.criteria.length&&new Set(data.criteria.map(c=>c.id)).size===stage.criteria.length&&data.criteria.every(c=>stage.criteria.some(s=>s.id===c.id)&&c.status==='resolved'&&typeof c.judgmentRef==='string'&&c.judgmentRef);
70
+ destinationReview=!!valid;
71
+ decision=valid?{decision:'allow',ruleIds:['cooperative.target-preparation']}:{decision:'hold',ruleIds:['cooperative.target-preparation'],reason:'Current destination bytes and complete source-bound review required'};
72
+ }
73
+ }
74
+ if(command==='record'&&['document','approval'].includes(input.evidence.kind)&&state.targetIntents?.some(intent=>!state.targetTransitions?.some(t=>t.intentId===intent.id)&&intent.documents.some(doc=>doc.kind===input.evidence.data.kind&&(input.evidence.data.path===doc.path||input.evidence.data.path===path.join(intent.sourceTarget.realpath,doc.path)))))decision={decision:'hold',ruleIds:['cooperative.target-preparation'],reason:'Prepared target transition required before document records'};
75
+ if(command==='record'&&input.evidence.kind==='approval'){
76
+ const data=input.evidence.data,intent=state.targetIntents?.find(i=>state.targetTransitions?.some(t=>t.intentId===i.id&&t.targetVersion===state.targetVersion)&&i.documents.some(d=>d.kind===data.kind));
77
+ if(intent){
78
+ const document=intent.documents.find(d=>d.kind===data.kind),firstStage=state.plannerLedger?.stages.find(s=>s.id===intent.firstStageId);
79
+ const stage=firstStage?.document.kind===data.kind?firstStage:state.plannerLedger?.stages.find(s=>s.document.kind===data.kind&&s.document.path===path.join(state.target.realpath,document.path)&&s.targetVersion===state.targetVersion);
80
+ const review=state.records.findLast(r=>r.kind==='planner-review'&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion&&r.data?.stageId===stage?.id&&r.data?.resultRef===stage?.result?.ref&&r.data?.full===true&&r.data?.complete===true&&r.data?.hash===data.contentHash);
81
+ let observed;try{observed=await readTargetPreparationEvidence({state,targetIntent:intent,destination:{realpath:state.target.realpath,path:document.path},observe,readChanges});}catch{}
82
+ if(stage?.scopeVersion!==state.scopeVersion||![document.path,path.join(state.target.realpath,document.path)].includes(data.path)||!review||observed?.destination?.document?.hash!==data.contentHash||!same(currentTarget,state.target)||!same(versions,state.versions)||input.actor.role!=='orchestrator')decision={decision:'hold',ruleIds:['cooperative.target-preparation'],reason:'Current reviewed destination bytes required for approval'};
83
+ }
84
+ }
85
+ if(command==='record'&&input.evidence.kind==='target-preparation'){
86
+ let observed;
87
+ try{const intent=state.targetIntents?.find(i=>i.id===input.evidence.data.intentId);observed=await readTargetPreparationEvidence({state,targetIntent:intent,destination:input.evidence.data.destination,observe,readChanges});}catch{}
88
+ decision=input.actor.role==='orchestrator'&&same(versions,state.versions)?evaluateTargetPreparation({state,request:input.evidence.data,evidence:{...observed,versions}}):{decision:'hold',ruleIds:['cooperative.target-preparation']};
89
+ }
56
90
  if(gitError)decision={decision:'hold',ruleIds:[input.operation==='assign-agent'?'cooperative.documents':'cooperative.git-evidence'],reason:gitError};
57
91
  if(command==='prepare'&&decision.decision==='allow'){
58
92
  if(decision.proposedTransition?.plannerLedger)state.plannerLedger=decision.proposedTransition.plannerLedger;
@@ -62,10 +96,10 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
62
96
  if(['reconcile','result'].includes(command)&&input.result){
63
97
  if(input.expectedRevision!==state.revision)state.unresolved.push({kind:'stale-result',requestId:input.requestId,intentId:input.intentId,expectedRevision:input.expectedRevision,revision:state.revision+1});
64
98
  let intent=state.intents.find(i=>i.requestId===input.intentId);
65
- decision=EXTERNAL_ACTIONS.includes(intent?.operation)?reconcileCooperativeExternal({state,intent,result:input.result,observations:{target:currentTarget}}):intent?.operation==='commit'?reconcileCooperativeCommit({state,intent,result:input.result,gitEvidence:git}):['check','review'].includes(intent?.operation)?evaluateCooperativeVerification({state,request:{...input,operation:'verification-result'},evidence:{changes}}):intent?.operation==='assign-agent'?recordCooperativeAssignmentResult({state,intent,result:input.result,observations:{target:currentTarget}}):reconcileCooperativeResult({state,intent,result:input.result,observations:{target:currentTarget}});
66
- if(['assign-agent','check','review','commit',...EXTERNAL_ACTIONS].includes(intent?.operation)&&!same(versions,state.versions))decision={...decision,decision:'hold',ruleIds:['cooperative.versions']};
99
+ decision=EXTERNAL_ACTIONS.includes(intent?.operation)?reconcileCooperativeExternal({state,intent,result:input.result,observations:{target:currentTarget}}):intent?.operation==='commit'?reconcileCooperativeCommit({state,intent,result:input.result,gitEvidence:git}):intent?.operation==='plan-amend'?reconcileCooperativePlanAmend({state,intent,result:input.result,observations:{target:currentTarget,git},now}):['check','review'].includes(intent?.operation)?evaluateCooperativeVerification({state,request:{...input,operation:'verification-result'},evidence:{changes}}):intent?.operation==='assign-agent'?recordCooperativeAssignmentResult({state,intent,result:input.result,observations:{target:currentTarget}}):reconcileCooperativeResult({state,intent,result:input.result,observations:{target:currentTarget}});
100
+ if(['assign-agent','check','review','commit','plan-amend',...EXTERNAL_ACTIONS].includes(intent?.operation)&&!same(versions,state.versions))decision={...decision,decision:'hold',ruleIds:['cooperative.versions']};
67
101
  if(input.expectedRevision!==state.revision)decision={...decision,decision:'hold',ruleIds:['cooperative.revision']};
68
- if((intent?.operation==='commit'||EXTERNAL_ACTIONS.includes(intent?.operation))&&input.actor.role!=='orchestrator')decision={...decision,decision:'hold',ruleIds:['cooperative.acceptance-actor']};
102
+ if((['commit','plan-amend'].includes(intent?.operation)||EXTERNAL_ACTIONS.includes(intent?.operation))&&input.actor.role!=='orchestrator')decision={...decision,decision:'hold',ruleIds:['cooperative.acceptance-actor']};
69
103
  if(['check','review'].includes(intent?.operation)&&!same(currentTarget,state.target))decision={...decision,decision:'hold',ruleIds:['cooperative.target']};
70
104
  if(intent?.operation==='commit'){
71
105
  const verified=decision.proposedTransition;
@@ -90,6 +124,20 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
90
124
  state.commitObservations??=[];state.commitObservations.push(observation);
91
125
  if(decision.decision!=='allow'&&verified?.result?.status==='succeeded')verified.result.status='mismatch';
92
126
  }
127
+ if(intent?.operation==='plan-amend'){
128
+ if(command!=='reconcile'||gitError||state.unresolved.some(u=>!u.resolvedBy&&!(u.intentId===input.intentId&&['target-changed','stale-result'].includes(u.kind))))decision={...decision,decision:'hold',ruleIds:['cooperative.plan-amend']};
129
+ if(decision.decision!=='allow'&&decision.proposedTransition?.result?.status==='succeeded')decision.proposedTransition.result.status='mismatch';
130
+ const observed={intentId:input.intentId,revision:state.revision+1,source:'local-git',target:currentTarget,observation:git??null,...(gitError?{error:gitError}:{})};
131
+ if(decision.decision==='allow'){
132
+ const previous=structuredClone(state.target),nextVersion=state.targetVersion+1,a=intent.arguments,transition=decision.proposedTransition;
133
+ state=invalidateAffectedEvidence({state,changedVersions:{targetVersion:nextVersion,documentVersion:a.plan.new.version},affectedTaskIds:a.affectedTaskIds});
134
+ state.targetHistory.push({target:previous,revision:state.revision+1,source:'local-observation',kind:'verified-plan-amend-parent',intentId:input.intentId});state.target=structuredClone(transition.target);state.targetVersion=nextVersion;
135
+ state.planAmendments??=[];state.planAmendments.push({intentId:input.intentId,path:a.plan.path,oldVersion:a.plan.old.version,newVersion:a.plan.new.version,oldHash:a.plan.old.hash,newHash:a.plan.new.hash,oldCommit:a.history.planCommit,newCommit:transition.mapping[0].new,mapping:structuredClone(transition.mapping),approvalRefs:structuredClone(a.approvals),source:'operator-attested',observationSource:'local-git'});
136
+ for(const u of state.unresolved)if(!u.resolvedBy&&u.intentId===input.intentId&&u.kind==='target-changed')u.resolvedBy=input.requestId;
137
+ intent=state.intents.find(i=>i.requestId===input.intentId);
138
+ }
139
+ state.planAmendObservations??=[];state.planAmendObservations.push(observed);
140
+ }
93
141
  const verification=decision.proposedTransition;
94
142
  if(verification?.check){
95
143
  if(decision.decision!=='allow'&&verification.check.status==='passed'){verification.check.status='unverified';verification.check.stale=true;verification.findings=[];verification.result.status='mismatch';}
@@ -100,17 +148,17 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
100
148
  if(decision.decision==='allow'&&input.expectedRevision===state.revision&&['succeeded','failed','cancelled'].includes(decision.proposedTransition?.result?.status))for(const entry of state.unresolved)if(entry.kind==='stale-result'&&entry.intentId===input.intentId&&!entry.resolvedBy){entry.resolvedBy=input.requestId;entry.resolutionEvidence={intentId:input.intentId,resultRef:input.result.messageRef,revision:state.revision};}
101
149
  const actualResult=decision.proposedTransition?.result??input.result;
102
150
  // Preserve actual statements even when runtime/HEAD changed, without rebinding target.
103
- if(!same(currentTarget,state.target)){state.targetHistory.push({target:currentTarget,revision:state.revision+1,source:'local-observation'});state.unresolved.push({kind:'target-changed',...(intent?.operation==='commit'?{intentId:input.intentId}:{}),revision:state.revision+1});}
151
+ if(!same(currentTarget,state.target)){state.targetHistory.push({target:currentTarget,revision:state.revision+1,source:'local-observation'});state.unresolved.push({kind:'target-changed',...(['commit','plan-amend'].includes(intent?.operation)?{intentId:input.intentId}:{}),revision:state.revision+1});}
104
152
  if(!same(versions,state.versions))state.unresolved.push({kind:'versions-changed',revision:state.revision+1});
105
153
  state.results.push({...actualResult,requestId:input.intentId,recordRequestId:input.requestId,actor:input.actor,source:'operator-attested'});
106
- if(intent&&(['assign-agent','check','review','commit',...EXTERNAL_ACTIONS].includes(intent.operation)?decision.proposedTransition?.result&&['prepared','unknown','mismatch'].includes(intent.status):true)){
154
+ if(intent&&(['assign-agent','check','review','commit','plan-amend',...EXTERNAL_ACTIONS].includes(intent.operation)?decision.proposedTransition?.result&&['prepared','unknown','mismatch'].includes(intent.status):true)){
107
155
  intent.status=actualResult.status;
108
156
  if(decision.decision==='allow'&&actualResult.status==='succeeded'&&intent.operation==='assign-agent')state.assignments.push({intentId:intent.requestId,taskId:intent.taskId,agentId:actualResult.data.agentId,status:'active',source:'operator-attested',identityVerified:false});
109
157
  }
110
158
  else state.unresolved.push({kind:'unmatched-result',requestId:input.intentId,revision:state.revision+1});
111
159
  }else if(command==='record'&&input.evidence.kind.startsWith('planner-')){
112
- if(decision.proposedTransition?.plannerLedger&&(decision.decision==='allow'||decision.proposedTransition.recordUncertainty)){
113
- state.plannerLedger=decision.proposedTransition.plannerLedger;
160
+ if(destinationReview||decision.proposedTransition?.plannerLedger&&(decision.decision==='allow'||decision.proposedTransition.recordUncertainty)){
161
+ if(!destinationReview)state.plannerLedger=decision.proposedTransition.plannerLedger;
114
162
  state.records.push({...input.evidence,actor:input.actor,source:'operator-attested',requestId:input.requestId,revision:state.revision+1,scopeVersion:state.scopeVersion,targetVersion:state.targetVersion});
115
163
  if(input.evidence.kind==='planner-result'){
116
164
  const intent=state.intents.find(i=>i.operation==='planner-call'&&i.requestId===input.evidence.data.callRequestId);
@@ -118,7 +166,12 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
118
166
  }
119
167
  }
120
168
  }else if(command==='record'&&decision.decision==='allow'){
169
+ if(input.evidence.kind==='target-preparation'&&input.evidence.data.type==='transition')state=applyCooperativeTargetTransition({state,decision,messageRef:input.evidence.messageRef});
170
+ if(input.evidence.kind==='target-preparation'&&decision.proposedTransition?.intent){state.targetIntents??=[];state.targetIntents.push(decision.proposedTransition.intent);}
171
+ if(input.evidence.kind==='target-preparation'&&decision.proposedTransition?.checkpoint){state.targetCheckpoints??=[];state.targetCheckpoints.push({...decision.proposedTransition.checkpoint,messageRef:input.evidence.messageRef,source:'operator-attested'});}
172
+ if(input.evidence.kind==='target-preparation'&&decision.proposedTransition?.destination){state.targetDocuments??=[];state.targetDocuments.push({...decision.proposedTransition.destination,messageRef:input.evidence.messageRef});}
121
173
  const entry={...input.evidence,actor:input.actor,source:'operator-attested',requestId:input.requestId,revision:state.revision+1,scopeVersion:state.scopeVersion,targetVersion:state.targetVersion};
174
+ if(entry.kind==='note'&&decision.proposedTransition?.checkpoint){const intent=state.intents.find(i=>i.operation==='plan-amend'&&i.requestId===decision.proposedTransition.checkpoint.intentId);if(!intent)throw Error('Checkpoint intent unavailable');intent.checkpoint=structuredClone(decision.proposedTransition.checkpoint);}
122
175
  if(entry.kind==='scope'){
123
176
  if(!Array.isArray(entry.data.paths)||entry.data.paths.some(p=>!absolute(p)||!within(state.target.realpath,p)))throw Error('Scope paths must stay inside workspace');
124
177
  state=invalidateAffectedEvidence({state,changedVersions:{scopeVersion:state.scopeVersion+1},affectedTaskIds:entry.data.affectedTaskIds});entry.scopeVersion=state.scopeVersion;state.scopes.push(entry);
@@ -143,6 +196,8 @@ export async function runCooperativeCommand({cwd,workflowId,command,input,observ
143
196
  if(ready){marker.resolvedBy=input.requestId;marker.resolutionEvidence={taskIds:ids,contracts:proof,scopeVersion:state.scopeVersion};}
144
197
  }
145
198
  }
199
+ // The observed identifiers contain no URLs and do not verify remote state.
200
+ if(planArguments&&Array.isArray(git?.configuredRemotes)&&git.configuredRemotes.every(r=>r&&typeof r.name==='string'&&/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(r.name)&&/^sha256:[a-f0-9]{64}$/.test(r.identity??'')))decision={...decision,observedRemoteIdentities:{source:'local-git',remotes:git.configuredRemotes.map(r=>({name:r.name,identity:r.identity}))}};
146
201
  // A held request is durable audit, never an executable intent.
147
202
  state.revision++;
148
203
  const output=result(state,decision,{persisted:true});