cclaw-cli 6.8.0 → 6.10.0
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.
- package/dist/artifact-linter/design.js +1 -1
- package/dist/artifact-linter/plan.js +37 -0
- package/dist/artifact-linter/shared.d.ts +48 -2
- package/dist/artifact-linter/shared.js +54 -5
- package/dist/artifact-linter/tdd.d.ts +31 -0
- package/dist/artifact-linter/tdd.js +357 -17
- package/dist/artifact-linter.js +87 -2
- package/dist/content/examples.js +9 -9
- package/dist/content/harness-doc.js +1 -1
- package/dist/content/hooks.js +140 -3
- package/dist/content/iron-laws.js +6 -2
- package/dist/content/node-hooks.js +15 -1308
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.js +1 -1
- package/dist/content/stages/brainstorm.js +2 -2
- package/dist/content/stages/design.js +2 -2
- package/dist/content/stages/scope.js +2 -2
- package/dist/content/stages/tdd.js +7 -8
- package/dist/content/subagents.js +20 -2
- package/dist/content/templates.js +5 -15
- package/dist/delegation.d.ts +102 -3
- package/dist/delegation.js +172 -14
- package/dist/early-loop.js +15 -1
- package/dist/gate-evidence.js +15 -23
- package/dist/harness-adapters.js +4 -2
- package/dist/install.js +37 -221
- package/dist/internal/advance-stage.js +19 -3
- package/dist/internal/detect-supply-chain-changes.d.ts +6 -0
- package/dist/internal/detect-supply-chain-changes.js +138 -0
- package/dist/internal/flow-state-repair.d.ts +7 -0
- package/dist/internal/flow-state-repair.js +57 -18
- package/dist/internal/plan-split-waves.d.ts +66 -0
- package/dist/internal/plan-split-waves.js +249 -0
- package/dist/run-persistence.d.ts +2 -0
- package/dist/run-persistence.js +62 -3
- package/dist/runtime/run-hook.mjs +44 -8729
- package/dist/tdd-slices.d.ts +90 -0
- package/dist/tdd-slices.js +375 -0
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@ export const REFERENCE_PATTERNS = [
|
|
|
36
36
|
"Discover tests and affected contracts before opening a RED vertical slice.",
|
|
37
37
|
"Map the slice to the active source item before editing production code."
|
|
38
38
|
],
|
|
39
|
-
artifactSections: ["Test Discovery", "System-Wide Impact Check", "Acceptance
|
|
39
|
+
artifactSections: ["Test Discovery", "System-Wide Impact Check", "Acceptance & Failure Map"]
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
stage: "review",
|
|
@@ -143,7 +143,7 @@ export const REFERENCE_PATTERNS = [
|
|
|
143
143
|
"Open one packet as one vertical slice; do not mix unrelated packet evidence.",
|
|
144
144
|
"Close packet only when RED, GREEN, REFACTOR, and verification evidence align."
|
|
145
145
|
],
|
|
146
|
-
artifactSections: ["Acceptance
|
|
146
|
+
artifactSections: ["Acceptance & Failure Map", "RED Evidence", "GREEN Evidence", "REFACTOR Notes"]
|
|
147
147
|
}
|
|
148
148
|
]
|
|
149
149
|
},
|
|
@@ -47,7 +47,7 @@ These behaviors are the exact reason this skill exists. The linter will block yo
|
|
|
47
47
|
- Use harness-native question tools first; prose fallback is allowed only when the tool is unavailable.
|
|
48
48
|
- Keep a running Q&A trace in the active artifact under \`## Q&A Log\` in \`${RUNTIME_ROOT}/artifacts/\` as append-only rows.
|
|
49
49
|
- **Early-loop ledger discipline**: Never append \`.cclaw/state/early-loop-log.jsonl\` rows whose \`iteration\` exceeds the active \`maxIterations\`. If the cap fired, escalate or accept convergence outcomes—do not bump the iteration counter afterward. \`deriveEarlyLoopStatus\` clamps persistence, but the log source should stay honest too.
|
|
50
|
-
- **Convergence floor**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. The machine contract matches \`evaluateQaLogFloor\` in \`src/artifact-linter/shared.ts\` (rule \`qa_log_unconverged\`). Pass when ANY holds: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row; (b) the Ralph
|
|
50
|
+
- **Convergence floor (a.k.a. "Q&A Ralph Loop" / "Elicitation Convergence")**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. The machine contract matches \`evaluateQaLogFloor\` in \`src/artifact-linter/shared.ts\` (rule \`qa_log_unconverged\`). Pass when ANY holds: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row; (b) the Q&A Ralph Loop detector fires (last 2 substantive rows are non-decision-changing: \`skip\`/\`continue\`/\`no-change\`/\`done\`/etc.) **and** the log has at least \`max(2, questionBudgetHint(discoveryMode, stage).min)\` substantive rows — **unless** \`discoveryMode\` is \`guided\` or \`deep\` with pending forcing-topic ids (then the Q&A Ralph Loop alone cannot pass until topics are tagged, a stop-signal is recorded, or \`--skip-questions\` downgrades the finding to advisory); (c) an explicit user stop-signal row; or (d) \`--skip-questions\` was persisted (unconverged is advisory only). Wave 24 (v6.0.0) made \`[topic:<id>]\` mandatory (no English keyword fallback). The "Q&A Ralph Loop" is the elicitation-stage convergence mechanism; the producer/critic Concern Ledger that drives early-stage iteration is the **Early-Loop**, persisted in \`.cclaw/state/early-loop-log.jsonl\` and \`early-loop.json\` — they are different machines, do not conflate them.
|
|
51
51
|
- **NEVER run shell hash commands** (\`shasum\`, \`sha256sum\`, \`md5sum\`, \`Get-FileHash\`, \`certutil\`, etc.) to compute artifact hashes. If a linter ever asks you for a hash, that is a linter bug — report failure and stop, do not auto-fix in bash.
|
|
52
52
|
- **NEVER paste cclaw command lines into chat** (e.g. \`node .cclaw/hooks/stage-complete.mjs ... --evidence-json '{...}'\`). Run them via the tool layer; report only the resulting summary. The user does not run cclaw manually and seeing the command line is noise.
|
|
53
53
|
|
|
@@ -107,7 +107,7 @@ Do not ask extra questions "for theater" on simple low-risk work.
|
|
|
107
107
|
|
|
108
108
|
## Question Budget Hint (\`questionBudgetHint\` — min rows feed the convergence floor)
|
|
109
109
|
|
|
110
|
-
Source of truth: \`questionBudgetHint(discoveryMode, stage)\`. The \`Min\` column is **not advisory** for the Ralph
|
|
110
|
+
Source of truth: \`questionBudgetHint(discoveryMode, stage)\`. The \`Min\` column is **not advisory** for the Q&A Ralph Loop exit: \`evaluateQaLogFloor\` requires at least \`max(2, Min)\` substantive rows before the no-new-decisions path can converge (other exits — full topic coverage, stop-signal, \`--skip-questions\` advisory — ignore that minimum). \`Recommended\` and \`Hard cap warning\` remain pacing hints for the harness.
|
|
111
111
|
|
|
112
112
|
${budgetTable}
|
|
113
113
|
|
package/dist/content/skills.js
CHANGED
|
@@ -102,7 +102,7 @@ Any "the failure is real" claim (failing test, broken build, regression catch, d
|
|
|
102
102
|
|
|
103
103
|
\`proof: <iso-ts> | <observed snippet — first 200 chars> | source: <command or log path>\`
|
|
104
104
|
|
|
105
|
-
For TDD specifically, this is the watched-RED proof and is required per new test before \`stage-complete\` accepts the stage.
|
|
105
|
+
For TDD specifically, this is the watched-RED proof and is required per new test before \`stage-complete\` accepts the stage. From v6.10.0 onward, record TDD slice transitions through the sidecar CLI \`cclaw-cli internal tdd-slice-record --slice <id> --status red|green|refactor-done|refactor-deferred ...\` rather than hand-editing the \`Watched-RED Proof\` or \`Vertical Slice Cycle\` markdown tables; the linter reads \`.cclaw/artifacts/06-tdd-slices.jsonl\` when present and treats the markdown as an auto-derived view.
|
|
106
106
|
`;
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
@@ -36,7 +36,7 @@ export const BRAINSTORM = {
|
|
|
36
36
|
},
|
|
37
37
|
executionModel: {
|
|
38
38
|
checklist: [
|
|
39
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:pain]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
39
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:pain]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to delegations, drafts, or analysis. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
40
40
|
"**Explore project context** — after the elicitation loop converges, inspect existing files/docs/recent activity to refine the Discovered context section; capture matching files/patterns/seeds in `Context > Discovered context` so downstream stages don't redo discovery.",
|
|
41
41
|
"**Brainstorm forcing questions (must be covered or explicitly waived)** — `pain: what pain are we solving`; `direct-path: what is the direct path`; `operator: who is the first operator/user affected`; `no-go: what no-go boundaries are non-negotiable`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:pain]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage. Round 6 (v6.7.0) removed the counterfactual `do-nothing` topic; the Problem Decision Record already captures `Do-nothing consequence`.",
|
|
42
42
|
"**Discovery posture (flow-state `discoveryMode`)** — follow `lean` / `guided` / `deep` from the active run. Use lean for smallest safe discovery pass; guided as the default balanced pass; escalate to deep when ambiguity, architecture, external dependency, security/data risk, or explicit think-bigger requests warrant fuller option pressure and mandatory specialist coverage.",
|
|
@@ -52,7 +52,7 @@ export const BRAINSTORM = {
|
|
|
52
52
|
"**Compare 2-3 distinct approaches with stable Role/Upside columns** — Role values are `baseline` | `challenger` | `wild-card`; Upside is `low` | `modest` | `high` | `higher`; include real trade-offs, reuse notes, and reference-pattern source/disposition when a known pattern influenced the option; include exactly one challenger with explicit `high` or `higher` upside.",
|
|
53
53
|
"**Collect reaction before recommending** — ask which option feels closest and what concern remains, then recommend based on that reaction.",
|
|
54
54
|
"**Write the `Not Doing` list** — name 3-5 things this brainstorm explicitly is not committing to (vs. deferred). This protects scope from silent enlargement and the next stage from rework.",
|
|
55
|
-
"**Run
|
|
55
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
56
56
|
"**Embedded Grill (post-pick, one-at-a-time)** — after `Selected Direction` is named, if grilling triggers fire (irreversibility, security/auth boundary, domain-model ambiguity per `adaptive-elicitation:Conditional Grilling`), continue the elicitation loop with sharper questions **one at a time**, appended to `## Q&A Log` and reflected as rows in `## Embedded Grill`. Do NOT batch the 3-5 grill checks — each one follows the Core Protocol (ask, wait, log, self-eval, ask next).",
|
|
57
57
|
"**Self-review before user approval** — re-read the artifact and patch contradictions, weak trade-offs, placeholders, ambiguity, and weak handoff language. Record the result in `Self-Review Notes` using the calibrated review format: `- Status: Approved` (or `Issues Found`), `- Patches applied:` with inline note or sub-bullets, `- Remaining concerns:` with inline note or sub-bullets. Use `Patches applied: None` and `Remaining concerns: None` when there is nothing to record.",
|
|
58
58
|
"**Request explicit approval to close the stage** — state exactly what direction is being approved after the adaptive elicitation loop converges; do not advance without approval and artifact review.",
|
|
@@ -41,7 +41,7 @@ export const DESIGN = {
|
|
|
41
41
|
},
|
|
42
42
|
executionModel: {
|
|
43
43
|
checklist: [
|
|
44
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:data-flow]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
44
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:data-flow]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to research, investigator pass, architecture lock, or any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
45
45
|
"**Design forcing questions (must be covered or explicitly waived)** — `data-flow: what is the end-to-end data flow`; `seams: where are seams/ownership boundaries`; `invariants: which invariants must hold`; `not-refactor: what will explicitly NOT be refactored now`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:data-flow]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage.",
|
|
46
46
|
"**Out-of-scope carry-forward (do NOT re-author)** — scope OWNS the out-of-scope list. Cite scope's `## In Scope / Out of Scope > Out of Scope` via `## Upstream Handoff > Decisions carried forward`; do NOT add a separate `## NOT in scope` section in the design artifact. Add a row to `## Spec Handoff` only if a design-stage decision NEWLY excludes something not already in scope's out-of-scope.",
|
|
47
47
|
"Compact design lock — design does not decide what to build; it decides how the approved scope works. For simple slices, produce a tight lock: upstream handoff, existing fit, architecture boundary, one labeled diagram, data/state flow, critical path, failure/rescue, trust boundaries, test/perf expectations, rollout/rollback, rejected alternative, and spec handoff.",
|
|
@@ -55,7 +55,7 @@ export const DESIGN = {
|
|
|
55
55
|
"Review core risk areas — existing system fit, data/state flow, critical path, security/trust boundaries, tests, performance budget, observability/debuggability, rollout/rollback, rejected alternatives, and spec handoff.",
|
|
56
56
|
"**ADR + pre-mortem contract** — capture ADR-style decision rows (context, decision, alternatives, consequences), run a pre-mortem on likely failures, and map each critical flow to a validating test and diagram anchor before lock.",
|
|
57
57
|
"Critic pass — run/reconcile adversarial second opinion on architecture, coupling, failure modes, and cheaper alternatives; record outcomes per the Design Outside Voice Loop policy.",
|
|
58
|
-
"**Run
|
|
58
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
59
59
|
"Run stale-diagram audit as a design freshness gate (default-on; explicit config opt-out allowed).",
|
|
60
60
|
"Capture leftovers — seed high-upside deferred ideas, list unresolved decisions with defaults, document distribution for new artifact types, and cross-reference deferred items to scope or unresolved decisions."
|
|
61
61
|
],
|
|
@@ -46,7 +46,7 @@ export const SCOPE = {
|
|
|
46
46
|
},
|
|
47
47
|
executionModel: {
|
|
48
48
|
checklist: [
|
|
49
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:in-out]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
49
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:in-out]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then propose the scope contract draft, recommend a mode, or dispatch any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
50
50
|
"**Scope forcing questions (must be covered or explicitly waived)** — `in-out: what is definitely in/out`; `locked-upstream: which upstream decisions are locked`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:in-out]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage. Round 6 (v6.7.0) removed the counterfactual `rollback` and `failure-modes` topics from scope forcing questions; Design still owns the Failure Mode Table and rollback evidence.",
|
|
51
51
|
"**Scope contract first** — read brainstorm handoff, name upstream decisions used, explicit drift, confidence, unresolved questions, and next-stage risk hints; draft the in-scope/out-of-scope/deferred/discretion contract before any design choice.",
|
|
52
52
|
"**Premise carry-forward (do NOT re-author)** — brainstorm OWNS the premise check (right problem / direct path). Cite brainstorm's `## Premise Check` section in `## Upstream Handoff > Decisions carried forward`. Add a row to `## Premise Drift` only when the scope-stage Q&A surfaced NEW evidence that materially changes the brainstorm answer (e.g. new constraint, new user signal). Otherwise mark `Premise Drift: None` — do not duplicate the brainstorm premise table.",
|
|
@@ -58,7 +58,7 @@ export const SCOPE = {
|
|
|
58
58
|
"**Architecture handoff (do NOT pick architecture tier here)** — design OWNS architecture choice (minimum-viable / product-grade / ideal). Scope only picks the SCOPE MODE (HOLD/SELECTIVE/EXPAND/REDUCE) and boundary; record in `## Scope Contract > Design handoff` what design must decide (e.g. `architecture-tier`, `framework`, `data-model`). Do NOT enumerate Implementation Alternatives in scope.",
|
|
59
59
|
"**Constraints (carry-forward from brainstorm/external sources)** — record explicit external/regulatory/system/integration constraints in `## Scope Contract > Constraints`. Spec OWNS testable assumptions (`## Assumptions Before Finalization`); do NOT duplicate constraint material as assumption material.",
|
|
60
60
|
"**Run outside voice before final approval** — for simple/low-risk scope, record one concise adversarial self-check row; for complex/high-risk/configured scope, iterate until threshold. Record the loop summary in `## Scope Outside Voice Loop`, but do not treat it as user approval.",
|
|
61
|
-
"**Run
|
|
61
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
62
62
|
"**Ask only one decision-changing question** — if the user rejects the contract but is unsure, offer 3-4 concrete scope moves instead of open-ended interrogation.",
|
|
63
63
|
"**Write the scope contract after approval** — include selected mode, in scope, out of scope, requirements, locked decisions, discretion areas, deferred ideas, accepted/rejected reference ideas, success definition, design handoff, completion dashboard, and explicit approval evidence."
|
|
64
64
|
],
|
|
@@ -42,14 +42,14 @@ export const TDD = {
|
|
|
42
42
|
"Discover the test surface — inspect existing tests, fixtures, helpers, test commands, and nearby assertions before authoring RED. Reuse the local test style unless the slice genuinely needs a new pattern.",
|
|
43
43
|
"Run a system-wide impact check — name callbacks, state transitions, interfaces, schemas, CLI/config/API contracts, persistence, or event boundaries that this slice can affect. Add RED coverage for each affected public contract or record why it is out of scope.",
|
|
44
44
|
"Source/test preflight — before production edits, classify planned paths using test-path patterns; verify the RED touches a test path and the GREEN touches only source paths needed for the failing behavior.",
|
|
45
|
-
"Set execution posture — record whether this slice is sequential, batch-safe, or blocked; when the existing git workflow permits small commits, checkpoint after RED, GREEN, and REFACTOR (or record why commits are deferred).",
|
|
46
45
|
"Use the mandatory `test-author` delegation for RED — after discovery and impact check, produce failing behavior tests and RED evidence only (no production edits). Set `CCLAW_ACTIVE_AGENT=tdd-red` when the harness supports phase labels.",
|
|
47
|
-
"RED: Capture failure output — copy the exact failure output as RED evidence. Record in
|
|
46
|
+
"RED: Capture failure output — copy the exact failure output as RED evidence. Record the slice in `.cclaw/artifacts/06-tdd-slices.jsonl` via `cclaw-cli internal tdd-slice-record --slice <id> --status red --test-file <path> --command <cmd> --paths <comma-separated>` (the markdown `Watched-RED Proof` table is now auto-derived from this sidecar).",
|
|
48
47
|
"Continue the same `test-author` delegation intent for GREEN — minimal implementation plus full-suite GREEN evidence. Set `CCLAW_ACTIVE_AGENT=tdd-green` when the harness supports phase labels.",
|
|
49
48
|
"GREEN: Run full suite — execute ALL tests, not just the ones you wrote. The full suite must be GREEN.",
|
|
50
49
|
"GREEN: Verify no regressions — if any existing test breaks, fix the regression before proceeding.",
|
|
51
50
|
"Run verification-before-completion discipline for the slice — capture a fresh test command, explicit PASS/FAIL status, and a config-aware ref (commit SHA when VCS is present/required, or no-vcs attestation when allowed).",
|
|
52
|
-
"
|
|
51
|
+
"GREEN: append a `green` row to `.cclaw/artifacts/06-tdd-slices.jsonl` via `cclaw-cli internal tdd-slice-record --slice <id> --status green [--green-output-ref <path|spanId:...>]` so the Vertical Slice Cycle linter validates the sidecar instead of a hand-edited table.",
|
|
52
|
+
"REFACTOR: continue the `test-author` evidence cycle (or a dedicated refactor mode when available) to improve code quality without behavior changes, then record `--status refactor-done` (or `--status refactor-deferred --refactor-rationale \"<why>\"`) via the same `tdd-slice-record` CLI. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when the harness supports phase labels.",
|
|
53
53
|
"Record evidence — capture test discovery, system-wide impact check, RED failure, GREEN output, and REFACTOR notes in the TDD artifact. When logging a `green` row, attach the closed acceptance-criterion IDs in `acIds` so Ralph Loop status counts them.",
|
|
54
54
|
"Annotate traceability — link to the active track's source: plan task ID + spec criterion on standard/medium, or spec acceptance item / bug reproduction slice on quick.",
|
|
55
55
|
"**Boundary with review (do NOT escalate single-slice findings to whole-diff review).** `tdd.Per-Slice Review` OWNS severity-classified findings WITHIN one slice (correctness, edge cases, regression). `review` OWNS whole-diff Layer 1 (spec compliance) plus Layer 2 (cross-slice integration, security sweep, dependency/version audit, observability). When a single-slice finding genuinely needs whole-diff escalation, surface it in `06-tdd.md > Per-Slice Review` first; review will cite it (not re-classify) and the cross-artifact-duplication linter requires matching severity/disposition.",
|
|
@@ -58,6 +58,7 @@ export const TDD = {
|
|
|
58
58
|
],
|
|
59
59
|
interactionProtocol: [
|
|
60
60
|
"Pick one vertical slice at a time: source item, RED test, GREEN implementation, REFACTOR, and verification evidence move together.",
|
|
61
|
+
"Slice implementers are sequential by default. Parallel implementers are allowed only when (a) lanes touch non-overlapping files, (b) the controller passes `--allow-parallel` on each ledger row, and (c) an `integration-overseer` is dispatched after the parallel lanes and writes cohesion-evidence into the artifact before the gate is marked passed.",
|
|
61
62
|
"Controller owns orchestration; one mandatory `test-author` delegation carries phase-specific RED -> GREEN -> REFACTOR evidence instead of spawning separate workers by default.",
|
|
62
63
|
"Before writing RED tests, discover relevant existing tests and commands so the new test extends the suite instead of fighting it.",
|
|
63
64
|
"Before implementation, perform a system-wide impact check across callbacks, state, interfaces, schemas, and external contracts touched by the slice.",
|
|
@@ -157,10 +158,8 @@ export const TDD = {
|
|
|
157
158
|
{ section: "Upstream Handoff", required: false, validationRule: "Summarizes plan/spec/design decisions, constraints, open questions, and explicit drift before RED work." },
|
|
158
159
|
{ section: "Test Discovery", required: true, validationRule: "Before RED: lists existing tests, fixtures/helpers, exact commands, and the chosen local pattern to extend." },
|
|
159
160
|
{ section: "System-Wide Impact Check", required: true, validationRule: "Before implementation: names affected callbacks, state transitions, interfaces, schemas, public APIs/config/CLI, persistence, or event contracts, with coverage or explicit out-of-scope notes." },
|
|
160
|
-
{ section: "Execution Posture", required: false, validationRule: "Records sequential/batch/blocked posture and vertical-slice RED/GREEN/REFACTOR checkpoint plan, including incremental commit boundaries when consistent with the repository git workflow." },
|
|
161
161
|
{ section: "RED Evidence", required: true, validationRule: "Failing test output captured per slice." },
|
|
162
|
-
{ section: "Acceptance
|
|
163
|
-
{ section: "Failure Analysis", required: false, validationRule: "Failure reason matches expected missing behavior." },
|
|
162
|
+
{ section: "Acceptance & Failure Map", required: false, validationRule: "Each slice row carries Source ID, AC ID, expected behavior, and a RED-link (delegation spanId, evidence path, or sidecar redOutputRef)." },
|
|
164
163
|
{ section: "GREEN Evidence", required: true, validationRule: "Full suite pass output captured." },
|
|
165
164
|
{ section: "REFACTOR Notes", required: true, validationRule: "What changed, why, behavior preservation confirmed." },
|
|
166
165
|
{ section: "Traceability", required: true, validationRule: "Plan task ID and spec criterion linked." },
|
|
@@ -301,11 +300,11 @@ function tddStageVariantForTrack(track) {
|
|
|
301
300
|
traceabilityRule: "Every RED test traces to an acceptance criterion. Every GREEN change traces to a RED test. Evidence chain must be unbroken."
|
|
302
301
|
},
|
|
303
302
|
artifactValidation: TDD.artifactRules.artifactValidation.map((row) => {
|
|
304
|
-
if (row.section === "Acceptance
|
|
303
|
+
if (row.section === "Acceptance & Failure Map") {
|
|
305
304
|
return {
|
|
306
305
|
...row,
|
|
307
306
|
required: true,
|
|
308
|
-
validationRule: "Each
|
|
307
|
+
validationRule: "Each slice row carries Source ID, AC ID (spec acceptance criterion ID, for example AC-1), expected behavior, and a RED-link (delegation spanId, evidence path, or sidecar redOutputRef)."
|
|
309
308
|
};
|
|
310
309
|
}
|
|
311
310
|
if (row.section === "Traceability") {
|
|
@@ -176,7 +176,17 @@ Before parallel dispatch, answer yes to all gates: tasks are independent, write
|
|
|
176
176
|
- Copy each task verbatim into a working queue (checklist is fine).
|
|
177
177
|
- Normalize each task so it includes: goal, acceptance criteria, constraints, and explicit “out of scope.”
|
|
178
178
|
|
|
179
|
-
2. **For each task
|
|
179
|
+
2. **For each task — sequential by default; parallel only with cohesion controls:**
|
|
180
|
+
- Implementation subagents are sequential by default. Parallel implementers
|
|
181
|
+
are allowed only when ALL three conditions hold:
|
|
182
|
+
- (a) the lanes touch non-overlapping files (verify via the plan's task
|
|
183
|
+
file-set list before dispatch),
|
|
184
|
+
- (b) the controller passes \`--allow-parallel\` on each ledger row, and
|
|
185
|
+
- (c) an \`integration-overseer\` is dispatched after the parallel lanes
|
|
186
|
+
complete and writes cohesion-evidence (cross-file integration tests,
|
|
187
|
+
contract checks, or merge-conflict scan) into the artifact before any
|
|
188
|
+
gate is marked passed.
|
|
189
|
+
If any of the three conditions are unmet, serialize.
|
|
180
190
|
1. **Dispatch implementer subagent** with the **full task text pasted in** (not a file reference).
|
|
181
191
|
2. **Check return status:** \`DONE\` / \`DONE_WITH_CONCERNS\` / \`NEEDS_CONTEXT\` / \`BLOCKED\`
|
|
182
192
|
3. If \`DONE\`: dispatch **reviewer** subagent to verify actual code matches spec and quality expectations.
|
|
@@ -668,7 +678,7 @@ You are a slice-implementer subagent.
|
|
|
668
678
|
|
|
669
679
|
SLICE: {single vertical slice}
|
|
670
680
|
RED_EVIDENCE: {failing test and expected failure}
|
|
671
|
-
ALLOWED_FILES: {explicit file boundaries}
|
|
681
|
+
ALLOWED_FILES: {explicit file boundaries — surfaced to scheduler as Files: <paths>}
|
|
672
682
|
FORBIDDEN_CHANGES: {scope/compatibility limits}
|
|
673
683
|
VERIFICATION: {commands expected}
|
|
674
684
|
|
|
@@ -676,6 +686,12 @@ Rules:
|
|
|
676
686
|
- Implement only the minimal GREEN change for the existing RED evidence.
|
|
677
687
|
- Keep REFACTOR behavior-preserving.
|
|
678
688
|
- Return the strict worker JSON schema first.
|
|
689
|
+
|
|
690
|
+
Slice ledger contract (v6.10.0):
|
|
691
|
+
- After observing the failing test, run \`cclaw-cli internal tdd-slice-record --slice <id> --status red --test-file <path> --command <cmd> --paths <comma-separated> [--ac <id>] [--plan-unit <id>]\`. The command appends to \`.cclaw/artifacts/06-tdd-slices.jsonl\`.
|
|
692
|
+
- After the same test passes, run \`cclaw-cli internal tdd-slice-record --slice <id> --status green [--green-output-ref <path|spanId:...>]\`.
|
|
693
|
+
- After REFACTOR, run \`cclaw-cli internal tdd-slice-record --slice <id> --status refactor-done\` or \`--status refactor-deferred --refactor-rationale "<why>"\`.
|
|
694
|
+
- Do NOT hand-edit the Watched-RED Proof or Vertical Slice Cycle markdown tables; the linter reads the JSONL sidecar when present and the markdown becomes an auto-derived view.
|
|
679
695
|
${MARKDOWN_CODE_FENCE}
|
|
680
696
|
|
|
681
697
|
`;
|
|
@@ -921,10 +937,12 @@ Process (mandatory):
|
|
|
921
937
|
1) If STAGE_MODE=TEST_RED_ONLY:
|
|
922
938
|
- RED only — add failing tests proving the gap (show failing output excerpt).
|
|
923
939
|
- Do NOT edit production code.
|
|
940
|
+
- Append the slice to the sidecar via \`cclaw-cli internal tdd-slice-record --slice <id> --status red --test-file <path> --command <cmd> --paths <comma-separated>\` instead of editing the Watched-RED Proof markdown table.
|
|
924
941
|
- Report: TESTS_ADDED, RED_COMMAND_RUN, RED_EVIDENCE, STATUS: DONE|BLOCKED.
|
|
925
942
|
2) If STAGE_MODE=BUILD_GREEN_REFACTOR:
|
|
926
943
|
- GREEN — minimal production code to satisfy existing RED tests, rerun full suite.
|
|
927
944
|
- REFACTOR — only after full suite is green; preserve behavior.
|
|
945
|
+
- Append \`--status green\` (and \`--status refactor-done\` or \`--status refactor-deferred --refactor-rationale "<why>"\` after refactor) via \`cclaw-cli internal tdd-slice-record\`. The Vertical Slice Cycle markdown stays auto-derived from this sidecar.
|
|
928
946
|
- Report: FILES_EDITED, GREEN_COMMAND_RUN, REFACTOR_NOTES, STATUS: DONE|BLOCKED.
|
|
929
947
|
${MARKDOWN_CODE_FENCE}
|
|
930
948
|
|
|
@@ -986,27 +986,17 @@ ${renderBehaviorAnchorTemplateLine("tdd")}
|
|
|
986
986
|
|---|---|---|
|
|
987
987
|
| S-1 | | covered/out-of-scope because |
|
|
988
988
|
|
|
989
|
-
## Execution Posture
|
|
990
|
-
- Posture: sequential | dependency-batched | blocked
|
|
991
|
-
- Vertical-slice RED/GREEN/REFACTOR checkpoint plan:
|
|
992
|
-
- Incremental commits: yes/no/deferred because
|
|
993
|
-
|
|
994
989
|
## RED Evidence
|
|
995
990
|
| Slice | Test name | Command | Failure output summary |
|
|
996
991
|
|---|---|---|---|
|
|
997
992
|
| S-1 | | | |
|
|
998
993
|
|
|
999
|
-
## Acceptance
|
|
1000
|
-
|
|
|
1001
|
-
|
|
1002
|
-
| S-1 | SRC-1 | AC-1 |
|
|
1003
|
-
|
|
1004
|
-
> Map each slice to the active track's source item: plan slice on standard/medium, or the \`Quick Reproduction Contract\` bug slice / spec acceptance item on quick.
|
|
994
|
+
## Acceptance & Failure Map
|
|
995
|
+
| Slice | Source ID | AC ID | Expected behavior | RED-link |
|
|
996
|
+
|---|---|---|---|---|
|
|
997
|
+
| S-1 | SRC-1 | AC-1 | | |
|
|
1005
998
|
|
|
1006
|
-
|
|
1007
|
-
| Slice | Expected missing behavior | Actual failure reason |
|
|
1008
|
-
|---|---|---|
|
|
1009
|
-
| S-1 | | |
|
|
999
|
+
> Each slice maps to the active track's source item (plan slice on standard/medium, or the \`Quick Reproduction Contract\` bug slice / spec acceptance item on quick) and to a spec criterion. The RED-link column is satisfied by either a \`spanId:<id>\` from the delegation ledger, an \`<artifacts-dir>/<file>\` evidence pointer, or a \`redOutputRef\` recorded via \`cclaw-cli internal tdd-slice-record\` in the sidecar ledger.
|
|
1010
1000
|
|
|
1011
1001
|
## GREEN Evidence
|
|
1012
1002
|
- Full suite command:
|
package/dist/delegation.d.ts
CHANGED
|
@@ -129,6 +129,17 @@ export type DelegationEntry = {
|
|
|
129
129
|
* coherent successor chain.
|
|
130
130
|
*/
|
|
131
131
|
supersededBy?: string;
|
|
132
|
+
/**
|
|
133
|
+
* v6.10.0 (P1) — repo-relative paths the delegated unit will edit.
|
|
134
|
+
* Used by the slice-implementer file-overlap scheduler to either
|
|
135
|
+
* auto-allow parallel dispatch (disjoint paths) or block the row
|
|
136
|
+
* with `DispatchOverlapError` (overlapping paths). For agents
|
|
137
|
+
* other than slice-implementer the field is advisory.
|
|
138
|
+
*
|
|
139
|
+
* keep in sync with the inline copy in
|
|
140
|
+
* `src/content/hooks.ts::delegationRecordScript`.
|
|
141
|
+
*/
|
|
142
|
+
claimedPaths?: string[];
|
|
132
143
|
};
|
|
133
144
|
export declare const DELEGATION_LEDGER_SCHEMA_VERSION: 3;
|
|
134
145
|
export type DelegationLedger = {
|
|
@@ -232,11 +243,99 @@ export declare class DispatchDuplicateError extends Error {
|
|
|
232
243
|
});
|
|
233
244
|
}
|
|
234
245
|
/**
|
|
235
|
-
* v6.
|
|
246
|
+
* v6.10.0 (P1) — thrown by `validateFileOverlap` when a new
|
|
247
|
+
* `slice-implementer` is scheduled on a TDD stage with at least one
|
|
248
|
+
* `claimedPaths` entry that overlaps an active span. The cclaw scheduler
|
|
249
|
+
* auto-allows parallel dispatch when paths are disjoint, so an explicit
|
|
250
|
+
* overlap is treated as a serialization signal: the operator must wait
|
|
251
|
+
* for the existing span to terminate or pass `--allow-parallel`
|
|
252
|
+
* deliberately to acknowledge the conflict.
|
|
253
|
+
*/
|
|
254
|
+
export declare class DispatchOverlapError extends Error {
|
|
255
|
+
readonly existingSpanId: string;
|
|
256
|
+
readonly newSpanId: string;
|
|
257
|
+
readonly pair: {
|
|
258
|
+
stage: string;
|
|
259
|
+
agent: string;
|
|
260
|
+
};
|
|
261
|
+
readonly conflictingPaths: string[];
|
|
262
|
+
constructor(params: {
|
|
263
|
+
existingSpanId: string;
|
|
264
|
+
newSpanId: string;
|
|
265
|
+
pair: {
|
|
266
|
+
stage: string;
|
|
267
|
+
agent: string;
|
|
268
|
+
};
|
|
269
|
+
conflictingPaths: string[];
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* v6.10.0 (P2) — thrown when the count of active `slice-implementer`
|
|
274
|
+
* spans (after fold) reaches `MAX_PARALLEL_SLICE_IMPLEMENTERS` and a new
|
|
275
|
+
* scheduled row would push it past the cap. Cap can be overridden once
|
|
276
|
+
* via `--override-cap=N` on the hook flag or globally via
|
|
277
|
+
* `CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS=<N>` env.
|
|
278
|
+
*/
|
|
279
|
+
export declare class DispatchCapError extends Error {
|
|
280
|
+
readonly cap: number;
|
|
281
|
+
readonly active: number;
|
|
282
|
+
readonly pair: {
|
|
283
|
+
stage: string;
|
|
284
|
+
agent: string;
|
|
285
|
+
};
|
|
286
|
+
constructor(params: {
|
|
287
|
+
cap: number;
|
|
288
|
+
active: number;
|
|
289
|
+
pair: {
|
|
290
|
+
stage: string;
|
|
291
|
+
agent: string;
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* v6.10.0 (P2) — default cap on active `slice-implementer` spans in a
|
|
297
|
+
* single TDD run. Aligned with evanflow's parallel cap. Override via
|
|
298
|
+
* `CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS=<int>` (validated `>=1`).
|
|
299
|
+
*/
|
|
300
|
+
export declare const MAX_PARALLEL_SLICE_IMPLEMENTERS: 5;
|
|
301
|
+
/**
|
|
302
|
+
* v6.10.0 (P1) — when scheduling a `slice-implementer` on a TDD stage,
|
|
303
|
+
* compare `claimedPaths` against every currently active span on the
|
|
304
|
+
* same `(stage, agent)` pair. Overlap → throw `DispatchOverlapError`;
|
|
305
|
+
* disjoint paths → return `{ autoParallel: true }` so the caller can
|
|
306
|
+
* mark the new entry `allowParallel = true` without explicit operator
|
|
307
|
+
* intent. When the agent is not a slice-implementer or no
|
|
308
|
+
* `claimedPaths` are supplied, the function returns
|
|
309
|
+
* `{ autoParallel: false }` and the legacy dedup path takes over.
|
|
310
|
+
*/
|
|
311
|
+
export declare function validateFileOverlap(stamped: DelegationEntry, activeEntries: DelegationEntry[]): {
|
|
312
|
+
autoParallel: boolean;
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* v6.10.0 (P2) — enforce the slice-implementer fan-out cap. The new
|
|
316
|
+
* scheduled row pushes the active count from N to N+1; if that would
|
|
317
|
+
* exceed the cap (default 5, env-overridable), throw `DispatchCapError`.
|
|
318
|
+
*
|
|
319
|
+
* Caller passes the already-folded list of active entries (latest row
|
|
320
|
+
* per spanId, ACTIVE statuses only). The function counts entries that
|
|
321
|
+
* match the agent on the same `stage`. The new row's own spanId is
|
|
322
|
+
* excluded so re-recording a `scheduled` doesn't trip the cap on a
|
|
323
|
+
* span that's already counted.
|
|
324
|
+
*/
|
|
325
|
+
export declare function validateFanOutCap(stamped: DelegationEntry, activeEntries: DelegationEntry[], override?: number | null): void;
|
|
326
|
+
/**
|
|
327
|
+
* v6.9.0 — find the latest active span for a given `(stage, agent)`
|
|
236
328
|
* pair in the supplied ledger entries. Returns the row whose latest
|
|
237
329
|
* status (after the latest-by-spanId fold) is still in the active set
|
|
238
|
-
* (`scheduled | launched | acknowledged`).
|
|
239
|
-
*
|
|
330
|
+
* (`scheduled | launched | acknowledged`).
|
|
331
|
+
*
|
|
332
|
+
* Run-scope is **strict**: only entries whose `runId` matches the
|
|
333
|
+
* supplied `runId` are folded. Entries with empty/missing `runId`
|
|
334
|
+
* (legacy ledgers from v6.8 and earlier) are treated as NOT belonging
|
|
335
|
+
* to the current run, so they cannot keep an old span "active" across
|
|
336
|
+
* a fresh dispatch and trip a spurious `dispatch_duplicate`. This
|
|
337
|
+
* fixes R7: a slice-implementer that ran in run-1 must not block a
|
|
338
|
+
* slice-implementer scheduled in run-2.
|
|
240
339
|
*
|
|
241
340
|
* keep in sync with the inline copy in
|
|
242
341
|
* `src/content/hooks.ts::delegationRecordScript`.
|
package/dist/delegation.js
CHANGED
|
@@ -224,7 +224,9 @@ function isDelegationEntry(value) {
|
|
|
224
224
|
(o.skill === undefined || typeof o.skill === "string") &&
|
|
225
225
|
(o.schemaVersion === undefined || o.schemaVersion === 1 || o.schemaVersion === 2 || o.schemaVersion === 3) &&
|
|
226
226
|
(o.allowParallel === undefined || typeof o.allowParallel === "boolean") &&
|
|
227
|
-
(o.supersededBy === undefined || typeof o.supersededBy === "string")
|
|
227
|
+
(o.supersededBy === undefined || typeof o.supersededBy === "string") &&
|
|
228
|
+
(o.claimedPaths === undefined ||
|
|
229
|
+
(Array.isArray(o.claimedPaths) && o.claimedPaths.every((item) => typeof item === "string"))));
|
|
228
230
|
}
|
|
229
231
|
function isDelegationDispatchSurface(value) {
|
|
230
232
|
return typeof value === "string" && DELEGATION_DISPATCH_SURFACES.includes(value);
|
|
@@ -548,18 +550,159 @@ export class DispatchDuplicateError extends Error {
|
|
|
548
550
|
}
|
|
549
551
|
}
|
|
550
552
|
/**
|
|
551
|
-
* v6.
|
|
553
|
+
* v6.10.0 (P1) — thrown by `validateFileOverlap` when a new
|
|
554
|
+
* `slice-implementer` is scheduled on a TDD stage with at least one
|
|
555
|
+
* `claimedPaths` entry that overlaps an active span. The cclaw scheduler
|
|
556
|
+
* auto-allows parallel dispatch when paths are disjoint, so an explicit
|
|
557
|
+
* overlap is treated as a serialization signal: the operator must wait
|
|
558
|
+
* for the existing span to terminate or pass `--allow-parallel`
|
|
559
|
+
* deliberately to acknowledge the conflict.
|
|
560
|
+
*/
|
|
561
|
+
export class DispatchOverlapError extends Error {
|
|
562
|
+
existingSpanId;
|
|
563
|
+
newSpanId;
|
|
564
|
+
pair;
|
|
565
|
+
conflictingPaths;
|
|
566
|
+
constructor(params) {
|
|
567
|
+
super(`dispatch_overlap — slice-implementer span ${params.newSpanId} claims path(s) ${params.conflictingPaths.join(", ")} already held by active spanId=${params.existingSpanId} on stage=${params.pair.stage}. ` +
|
|
568
|
+
`Wait for ${params.existingSpanId} to finish, dispatch a non-overlapping slice, or pass --allow-parallel to acknowledge the conflict.`);
|
|
569
|
+
this.name = "DispatchOverlapError";
|
|
570
|
+
this.existingSpanId = params.existingSpanId;
|
|
571
|
+
this.newSpanId = params.newSpanId;
|
|
572
|
+
this.pair = params.pair;
|
|
573
|
+
this.conflictingPaths = params.conflictingPaths;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* v6.10.0 (P2) — thrown when the count of active `slice-implementer`
|
|
578
|
+
* spans (after fold) reaches `MAX_PARALLEL_SLICE_IMPLEMENTERS` and a new
|
|
579
|
+
* scheduled row would push it past the cap. Cap can be overridden once
|
|
580
|
+
* via `--override-cap=N` on the hook flag or globally via
|
|
581
|
+
* `CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS=<N>` env.
|
|
582
|
+
*/
|
|
583
|
+
export class DispatchCapError extends Error {
|
|
584
|
+
cap;
|
|
585
|
+
active;
|
|
586
|
+
pair;
|
|
587
|
+
constructor(params) {
|
|
588
|
+
super(`dispatch_cap — ${params.active} active ${params.pair.agent}(s) at the cap of ${params.cap}. ` +
|
|
589
|
+
`Complete one before scheduling another, or pass --override-cap=N (or CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS=N) to lift the cap for this run.`);
|
|
590
|
+
this.name = "DispatchCapError";
|
|
591
|
+
this.cap = params.cap;
|
|
592
|
+
this.active = params.active;
|
|
593
|
+
this.pair = params.pair;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* v6.10.0 (P2) — default cap on active `slice-implementer` spans in a
|
|
598
|
+
* single TDD run. Aligned with evanflow's parallel cap. Override via
|
|
599
|
+
* `CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS=<int>` (validated `>=1`).
|
|
600
|
+
*/
|
|
601
|
+
export const MAX_PARALLEL_SLICE_IMPLEMENTERS = 5;
|
|
602
|
+
function readMaxParallelOverrideFromEnv() {
|
|
603
|
+
const raw = process.env.CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS;
|
|
604
|
+
if (typeof raw !== "string" || raw.trim().length === 0)
|
|
605
|
+
return null;
|
|
606
|
+
const parsed = Number(raw);
|
|
607
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1)
|
|
608
|
+
return null;
|
|
609
|
+
return parsed;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* v6.10.0 (P1) — when scheduling a `slice-implementer` on a TDD stage,
|
|
613
|
+
* compare `claimedPaths` against every currently active span on the
|
|
614
|
+
* same `(stage, agent)` pair. Overlap → throw `DispatchOverlapError`;
|
|
615
|
+
* disjoint paths → return `{ autoParallel: true }` so the caller can
|
|
616
|
+
* mark the new entry `allowParallel = true` without explicit operator
|
|
617
|
+
* intent. When the agent is not a slice-implementer or no
|
|
618
|
+
* `claimedPaths` are supplied, the function returns
|
|
619
|
+
* `{ autoParallel: false }` and the legacy dedup path takes over.
|
|
620
|
+
*/
|
|
621
|
+
export function validateFileOverlap(stamped, activeEntries) {
|
|
622
|
+
if (stamped.agent !== "slice-implementer" || stamped.stage !== "tdd") {
|
|
623
|
+
return { autoParallel: false };
|
|
624
|
+
}
|
|
625
|
+
const newPaths = Array.isArray(stamped.claimedPaths) ? stamped.claimedPaths : [];
|
|
626
|
+
if (newPaths.length === 0) {
|
|
627
|
+
return { autoParallel: false };
|
|
628
|
+
}
|
|
629
|
+
const sameLane = activeEntries.filter((entry) => entry.stage === stamped.stage &&
|
|
630
|
+
entry.agent === stamped.agent &&
|
|
631
|
+
entry.spanId !== stamped.spanId);
|
|
632
|
+
if (sameLane.length === 0) {
|
|
633
|
+
return { autoParallel: true };
|
|
634
|
+
}
|
|
635
|
+
for (const existing of sameLane) {
|
|
636
|
+
const existingPaths = Array.isArray(existing.claimedPaths) ? existing.claimedPaths : [];
|
|
637
|
+
if (existingPaths.length === 0) {
|
|
638
|
+
// We can't prove disjoint without the other side declaring paths;
|
|
639
|
+
// be conservative and let the legacy dedup error path fire.
|
|
640
|
+
return { autoParallel: false };
|
|
641
|
+
}
|
|
642
|
+
const overlap = newPaths.filter((p) => existingPaths.includes(p));
|
|
643
|
+
if (overlap.length > 0) {
|
|
644
|
+
throw new DispatchOverlapError({
|
|
645
|
+
existingSpanId: existing.spanId ?? "unknown",
|
|
646
|
+
newSpanId: stamped.spanId ?? "unknown",
|
|
647
|
+
pair: { stage: stamped.stage, agent: stamped.agent },
|
|
648
|
+
conflictingPaths: overlap
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return { autoParallel: true };
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* v6.10.0 (P2) — enforce the slice-implementer fan-out cap. The new
|
|
656
|
+
* scheduled row pushes the active count from N to N+1; if that would
|
|
657
|
+
* exceed the cap (default 5, env-overridable), throw `DispatchCapError`.
|
|
658
|
+
*
|
|
659
|
+
* Caller passes the already-folded list of active entries (latest row
|
|
660
|
+
* per spanId, ACTIVE statuses only). The function counts entries that
|
|
661
|
+
* match the agent on the same `stage`. The new row's own spanId is
|
|
662
|
+
* excluded so re-recording a `scheduled` doesn't trip the cap on a
|
|
663
|
+
* span that's already counted.
|
|
664
|
+
*/
|
|
665
|
+
export function validateFanOutCap(stamped, activeEntries, override) {
|
|
666
|
+
if (stamped.agent !== "slice-implementer" || stamped.stage !== "tdd")
|
|
667
|
+
return;
|
|
668
|
+
if (stamped.status !== "scheduled")
|
|
669
|
+
return;
|
|
670
|
+
const cap = (override !== null && override !== undefined && Number.isInteger(override) && override >= 1)
|
|
671
|
+
? override
|
|
672
|
+
: (readMaxParallelOverrideFromEnv() ?? MAX_PARALLEL_SLICE_IMPLEMENTERS);
|
|
673
|
+
const sameLaneActive = activeEntries.filter((entry) => entry.stage === stamped.stage &&
|
|
674
|
+
entry.agent === stamped.agent &&
|
|
675
|
+
entry.spanId !== stamped.spanId);
|
|
676
|
+
if (sameLaneActive.length + 1 > cap) {
|
|
677
|
+
throw new DispatchCapError({
|
|
678
|
+
cap,
|
|
679
|
+
active: sameLaneActive.length,
|
|
680
|
+
pair: { stage: stamped.stage, agent: stamped.agent }
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* v6.9.0 — find the latest active span for a given `(stage, agent)`
|
|
552
686
|
* pair in the supplied ledger entries. Returns the row whose latest
|
|
553
687
|
* status (after the latest-by-spanId fold) is still in the active set
|
|
554
|
-
* (`scheduled | launched | acknowledged`).
|
|
555
|
-
*
|
|
688
|
+
* (`scheduled | launched | acknowledged`).
|
|
689
|
+
*
|
|
690
|
+
* Run-scope is **strict**: only entries whose `runId` matches the
|
|
691
|
+
* supplied `runId` are folded. Entries with empty/missing `runId`
|
|
692
|
+
* (legacy ledgers from v6.8 and earlier) are treated as NOT belonging
|
|
693
|
+
* to the current run, so they cannot keep an old span "active" across
|
|
694
|
+
* a fresh dispatch and trip a spurious `dispatch_duplicate`. This
|
|
695
|
+
* fixes R7: a slice-implementer that ran in run-1 must not block a
|
|
696
|
+
* slice-implementer scheduled in run-2.
|
|
556
697
|
*
|
|
557
698
|
* keep in sync with the inline copy in
|
|
558
699
|
* `src/content/hooks.ts::delegationRecordScript`.
|
|
559
700
|
*/
|
|
560
701
|
export function findActiveSpanForPair(stage, agent, runId, ledger) {
|
|
561
702
|
const sameRun = ledger.entries.filter((entry) => {
|
|
562
|
-
if (entry.runId
|
|
703
|
+
if (typeof entry.runId !== "string" || entry.runId.length === 0)
|
|
704
|
+
return false;
|
|
705
|
+
if (entry.runId !== runId)
|
|
563
706
|
return false;
|
|
564
707
|
return entry.stage === stage && entry.agent === agent;
|
|
565
708
|
});
|
|
@@ -648,15 +791,30 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
648
791
|
return;
|
|
649
792
|
}
|
|
650
793
|
validateMonotonicTimestamps(stamped, prior.entries);
|
|
651
|
-
if (stamped.status === "scheduled"
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
794
|
+
if (stamped.status === "scheduled") {
|
|
795
|
+
// v6.10.0 (P1+P2): for slice-implementer rows with declared
|
|
796
|
+
// claimedPaths, the file-overlap scheduler runs first. Disjoint
|
|
797
|
+
// paths auto-promote the row to allowParallel so the legacy
|
|
798
|
+
// dispatch_duplicate guard does not fire. Overlapping paths
|
|
799
|
+
// throw DispatchOverlapError. The fan-out cap then runs against
|
|
800
|
+
// the active set (excluding the new row's spanId).
|
|
801
|
+
const sameRunPrior = prior.entries.filter((entry) => entry.runId === activeRunId);
|
|
802
|
+
const activeForRun = computeActiveSubagents(sameRunPrior);
|
|
803
|
+
const overlap = validateFileOverlap(stamped, activeForRun);
|
|
804
|
+
if (overlap.autoParallel && stamped.allowParallel !== true) {
|
|
805
|
+
stamped.allowParallel = true;
|
|
806
|
+
}
|
|
807
|
+
validateFanOutCap(stamped, activeForRun);
|
|
808
|
+
if (stamped.allowParallel !== true) {
|
|
809
|
+
const existing = findActiveSpanForPair(stamped.stage, stamped.agent, activeRunId, prior);
|
|
810
|
+
if (existing && existing.spanId && existing.spanId !== stamped.spanId) {
|
|
811
|
+
throw new DispatchDuplicateError({
|
|
812
|
+
existingSpanId: existing.spanId,
|
|
813
|
+
existingStatus: existing.status,
|
|
814
|
+
newSpanId: stamped.spanId,
|
|
815
|
+
pair: { stage: stamped.stage, agent: stamped.agent }
|
|
816
|
+
});
|
|
817
|
+
}
|
|
660
818
|
}
|
|
661
819
|
}
|
|
662
820
|
await appendDelegationEvent(projectRoot, eventFromEntry(stamped));
|