cclaw-cli 6.14.3 → 7.0.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/README.md +0 -2
- package/dist/artifact-linter/brainstorm.js +1 -1
- package/dist/artifact-linter/design.js +2 -2
- package/dist/artifact-linter/findings-dedup.js +1 -1
- package/dist/artifact-linter/plan.js +6 -6
- package/dist/artifact-linter/review-army.d.ts +1 -1
- package/dist/artifact-linter/review-army.js +1 -1
- package/dist/artifact-linter/scope.js +6 -6
- package/dist/artifact-linter/shared.d.ts +37 -73
- package/dist/artifact-linter/shared.js +30 -37
- package/dist/artifact-linter/spec.js +1 -1
- package/dist/artifact-linter/tdd.d.ts +20 -33
- package/dist/artifact-linter/tdd.js +89 -617
- package/dist/artifact-linter.js +11 -32
- package/dist/cli.js +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/content/core-agents.d.ts +8 -26
- package/dist/content/core-agents.js +48 -94
- package/dist/content/examples.d.ts +1 -1
- package/dist/content/examples.js +4 -4
- package/dist/content/hooks.js +62 -149
- package/dist/content/idea.js +2 -2
- package/dist/content/iron-laws.js +1 -1
- package/dist/content/node-hooks.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.d.ts +4 -6
- package/dist/content/skills.js +14 -53
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +8 -46
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/review.js +1 -1
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/spec.js +2 -2
- package/dist/content/stages/tdd.js +43 -108
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +5 -3
- package/dist/content/subagents.js +13 -74
- package/dist/content/templates.d.ts +6 -6
- package/dist/content/templates.js +23 -24
- package/dist/content/utility-skills.d.ts +1 -1
- package/dist/content/utility-skills.js +1 -1
- package/dist/delegation.d.ts +79 -139
- package/dist/delegation.js +83 -215
- package/dist/early-loop.js +1 -1
- package/dist/flow-state.d.ts +24 -129
- package/dist/flow-state.js +5 -30
- package/dist/gate-evidence.d.ts +2 -7
- package/dist/gate-evidence.js +2 -59
- package/dist/harness-adapters.d.ts +1 -1
- package/dist/harness-adapters.js +11 -10
- package/dist/install.js +24 -459
- package/dist/internal/advance-stage/advance.d.ts +5 -5
- package/dist/internal/advance-stage/advance.js +9 -24
- package/dist/internal/advance-stage/parsers.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.js +3 -3
- package/dist/internal/advance-stage/start-flow.js +1 -3
- package/dist/internal/advance-stage.js +4 -23
- package/dist/internal/cohesion-contract-stub.d.ts +8 -13
- package/dist/internal/cohesion-contract-stub.js +18 -24
- package/dist/internal/flow-state-repair.d.ts +1 -1
- package/dist/internal/plan-split-waves.d.ts +44 -7
- package/dist/internal/plan-split-waves.js +113 -12
- package/dist/internal/wave-status.d.ts +3 -6
- package/dist/internal/wave-status.js +5 -27
- package/dist/policy.js +1 -1
- package/dist/run-persistence.js +10 -44
- package/dist/runtime/run-hook.mjs +3 -3
- package/dist/track-heuristics.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/integration-fanin.d.ts +0 -44
- package/dist/integration-fanin.js +0 -180
- package/dist/internal/set-checkpoint-mode.d.ts +0 -16
- package/dist/internal/set-checkpoint-mode.js +0 -72
- package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
- package/dist/internal/set-integration-overseer-mode.js +0 -69
- package/dist/internal/set-worktree-mode.d.ts +0 -10
- package/dist/internal/set-worktree-mode.js +0 -28
- package/dist/worktree-manager.d.ts +0 -50
- package/dist/worktree-manager.js +0 -136
- package/dist/worktree-types.d.ts +0 -36
- package/dist/worktree-types.js +0 -6
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveArtifactPath } from "../../artifact-paths.js";
|
|
4
4
|
import { appendDelegation, checkMandatoryDelegations, readDelegationEvents, readDelegationLedger } from "../../delegation.js";
|
|
5
|
-
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence
|
|
5
|
+
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../../gate-evidence.js";
|
|
6
6
|
import { extractMarkdownSectionBody, learningsParseFailureHumanSummary, parseLearningsSection } from "../../artifact-linter.js";
|
|
7
7
|
import { getAvailableTransitions, getTransitionGuards } from "../../flow-state.js";
|
|
8
8
|
import { appendKnowledge } from "../../knowledge-store.js";
|
|
@@ -13,7 +13,6 @@ import { unique } from "./helpers.js";
|
|
|
13
13
|
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
14
|
import { ensureProactiveDelegationTrace } from "./proactive-delegation-trace.js";
|
|
15
15
|
import { consumeWaiverToken } from "../waiver-grant.js";
|
|
16
|
-
import { runTddDeterministicFanInBeforeAdvance } from "../../integration-fanin.js";
|
|
17
16
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
18
17
|
const natural = transitionTargets[0] ?? null;
|
|
19
18
|
const specialTargets = transitionTargets.filter((target) => target !== natural);
|
|
@@ -60,16 +59,16 @@ function nextInteractionHints(flowState, args, successor) {
|
|
|
60
59
|
return hints;
|
|
61
60
|
}
|
|
62
61
|
/**
|
|
63
|
-
*
|
|
62
|
+
* entry point — auto-hydrate evidence for an auto-hydratable
|
|
64
63
|
* gate that the agent already included in --passed but for which they
|
|
65
64
|
* forgot to provide --evidence-json. Returns silently when no
|
|
66
65
|
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
67
66
|
* envelope, etc.).
|
|
68
67
|
*
|
|
69
|
-
*
|
|
68
|
+
* layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
70
69
|
* top of this so the gate is also auto-included in selectedGateIds
|
|
71
70
|
* when the artifact yields a valid envelope. Together the two helpers
|
|
72
|
-
* remove the contradiction
|
|
71
|
+
* remove the contradiction:
|
|
73
72
|
* - "omit this gate from --evidence-json so stage-complete can
|
|
74
73
|
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
75
74
|
* gates: design_diagram_freshness".
|
|
@@ -105,7 +104,7 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
|
|
|
105
104
|
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
106
105
|
}
|
|
107
106
|
/**
|
|
108
|
-
*
|
|
107
|
+
* auto-include an auto-hydratable review-loop gate
|
|
109
108
|
* in `selectedGateIds` when:
|
|
110
109
|
* - The stage has an auto-hydratable gate registered via
|
|
111
110
|
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
@@ -162,7 +161,7 @@ export async function tryAutoHydrateAndSelectReviewLoopGate(projectRoot, stage,
|
|
|
162
161
|
return [...selectedGateIds, gateId];
|
|
163
162
|
}
|
|
164
163
|
export async function buildValidationReport(projectRoot, flowState, options = {}) {
|
|
165
|
-
//
|
|
164
|
+
// forward `flowState.taskClass` so the
|
|
166
165
|
// bugfix-skip lights up via the `cclaw advance-stage` path. The
|
|
167
166
|
// delegation helper now has its own fallback (it reads `flowState`
|
|
168
167
|
// internally), but threading the value here keeps the call site
|
|
@@ -314,11 +313,11 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
314
313
|
let selectedGateIds = args.passedGateIds.length > 0
|
|
315
314
|
? args.passedGateIds.filter((gateId) => selectableGateIds.has(gateId))
|
|
316
315
|
: requiredGateIds;
|
|
317
|
-
//
|
|
316
|
+
// if the active stage has an auto-hydratable
|
|
318
317
|
// review-loop gate (currently `design.design_architecture_locked`)
|
|
319
318
|
// and the artifact already contains a valid review-loop envelope,
|
|
320
319
|
// include the gate in selectedGateIds and hydrate evidence in one
|
|
321
|
-
// step. This removes the
|
|
320
|
+
// step. This removes the contradiction between "omit from
|
|
322
321
|
// --evidence-json so we can auto-hydrate" and "missing
|
|
323
322
|
// --evidence-json entries for passed gates".
|
|
324
323
|
selectedGateIds = await tryAutoHydrateAndSelectReviewLoopGate(projectRoot, args.stage, flowState.track, requiredGateIds, selectedGateIds, args.evidenceByGate);
|
|
@@ -363,7 +362,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
363
362
|
});
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
|
-
//
|
|
365
|
+
// hydration + auto-select happens earlier via
|
|
367
366
|
// `tryAutoHydrateAndSelectReviewLoopGate`. The previous explicit
|
|
368
367
|
// call here was redundant (helper already covered both the
|
|
369
368
|
// already-selected and not-yet-selected paths).
|
|
@@ -603,20 +602,6 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
603
602
|
}
|
|
604
603
|
const satisfiedGuards = new Set([...nextPassed, ...selectedTransitionGuards]);
|
|
605
604
|
const successor = resolveSuccessorTransition(args.stage, flowState.track, transitionTargets, satisfiedGuards, new Set(selectedTransitionGuards));
|
|
606
|
-
if (args.stage === "tdd" && successor !== null && successor !== "tdd") {
|
|
607
|
-
const fanIn = await runTddDeterministicFanInBeforeAdvance(projectRoot, flowState);
|
|
608
|
-
if (!fanIn.ok) {
|
|
609
|
-
io.stderr.write(`cclaw internal advance-stage: deterministic worktree fan-in failed:\n${fanIn.issues
|
|
610
|
-
.map((line) => ` - ${line}`)
|
|
611
|
-
.join("\n")}\n`);
|
|
612
|
-
return 1;
|
|
613
|
-
}
|
|
614
|
-
const closure = await verifyTddWorktreeFanInClosure(projectRoot, flowState);
|
|
615
|
-
if (closure.length > 0) {
|
|
616
|
-
io.stderr.write(`cclaw internal advance-stage: ${closure.join(" | ")}\n`);
|
|
617
|
-
return 1;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
605
|
const completedStages = blockedReviewRoute
|
|
621
606
|
? flowState.completedStages.filter((finished) => finished !== args.stage)
|
|
622
607
|
: flowState.completedStages.includes(args.stage)
|
|
@@ -49,7 +49,7 @@ export interface StartFlowArgs {
|
|
|
49
49
|
reclassify: boolean;
|
|
50
50
|
quiet: boolean;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* `/cc-ideate` handoff carry-forward.
|
|
53
53
|
* Workspace-relative POSIX path to `.cclaw/ideas/idea-YYYY-MM-DD-<slug>.md`
|
|
54
54
|
* (or wherever `/cc-ideate` wrote its artifact).
|
|
55
55
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FlowStage } from "../../types.js";
|
|
2
2
|
export declare const AUTO_REVIEW_LOOP_GATE_BY_STAGE: Partial<Record<FlowStage, string>>;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* exact JSON shape that gate-evidence validators
|
|
5
5
|
* accept for a review-loop envelope. The error messages emitted by
|
|
6
6
|
* `validateReviewLoopGateEvidence` always include this example so the
|
|
7
7
|
* agent never has to guess where `stage` lives (top-level of the
|
|
@@ -12,7 +12,7 @@ const REVIEW_LOOP_STOP_REASONS = new Set([
|
|
|
12
12
|
"user_opt_out"
|
|
13
13
|
]);
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* exact JSON shape that gate-evidence validators
|
|
16
16
|
* accept for a review-loop envelope. The error messages emitted by
|
|
17
17
|
* `validateReviewLoopGateEvidence` always include this example so the
|
|
18
18
|
* agent never has to guess where `stage` lives (top-level of the
|
|
@@ -183,9 +183,9 @@ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evid
|
|
|
183
183
|
export function reviewLoopArtifactFixHint(stage, gateId) {
|
|
184
184
|
if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
|
|
185
185
|
return "";
|
|
186
|
-
//
|
|
186
|
+
// the consistent flow is "include the gate in
|
|
187
187
|
// --passed AND let stage-complete auto-hydrate evidence from the
|
|
188
|
-
// artifact".
|
|
188
|
+
// artifact". An older hint told agents to omit the gate from
|
|
189
189
|
// --evidence-json, but they then hit
|
|
190
190
|
// `missing --evidence-json entries for passed gates: <gateId>`
|
|
191
191
|
// because hydration only runs when --evidence-json is also present
|
|
@@ -175,8 +175,7 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
175
175
|
guardEvidence,
|
|
176
176
|
stageGateCatalog,
|
|
177
177
|
rewinds: current.rewinds,
|
|
178
|
-
staleStages: current.staleStages
|
|
179
|
-
worktreeExecutionMode: current.worktreeExecutionMode ?? "worktree-first"
|
|
178
|
+
staleStages: current.staleStages
|
|
180
179
|
};
|
|
181
180
|
const validation = await buildValidationReport(projectRoot, nextState);
|
|
182
181
|
const evidenceIssues = completedStageClosureEvidenceIssues(nextState);
|
|
@@ -194,7 +193,6 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
194
193
|
if (nextTaskClass !== undefined) {
|
|
195
194
|
nextState = { ...nextState, taskClass: nextTaskClass };
|
|
196
195
|
}
|
|
197
|
-
nextState = { ...nextState, worktreeExecutionMode: "worktree-first" };
|
|
198
196
|
}
|
|
199
197
|
if (args.fromIdeaArtifact) {
|
|
200
198
|
const existingHints = nextState.interactionHints ?? {};
|
|
@@ -14,11 +14,8 @@ import { parseAdvanceStageArgs, parseCancelRunArgs, parseHookArgs, parseRewindAr
|
|
|
14
14
|
import { parseFlowStateRepairArgs, runFlowStateRepair } from "./flow-state-repair.js";
|
|
15
15
|
import { parseWaiverGrantArgs, runWaiverGrant } from "./waiver-grant.js";
|
|
16
16
|
import { FlowStateGuardMismatchError, verifyFlowStateGuard } from "../run-persistence.js";
|
|
17
|
-
import { DelegationTimestampError, DispatchCapError,
|
|
17
|
+
import { DelegationTimestampError, DispatchCapError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
|
|
18
18
|
import { parsePlanSplitWavesArgs, runPlanSplitWaves } from "./plan-split-waves.js";
|
|
19
|
-
import { runSetWorktreeMode } from "./set-worktree-mode.js";
|
|
20
|
-
import { runSetCheckpointMode } from "./set-checkpoint-mode.js";
|
|
21
|
-
import { runSetIntegrationOverseerMode } from "./set-integration-overseer-mode.js";
|
|
22
19
|
import { runWaveStatusCommand } from "./wave-status.js";
|
|
23
20
|
import { runCohesionContractCommand } from "./cohesion-contract-stub.js";
|
|
24
21
|
/**
|
|
@@ -33,15 +30,12 @@ const GUARD_ENFORCED_SUBCOMMANDS = new Set([
|
|
|
33
30
|
"cancel-run",
|
|
34
31
|
"rewind",
|
|
35
32
|
"verify-flow-state-diff",
|
|
36
|
-
"verify-current-state"
|
|
37
|
-
"set-worktree-mode",
|
|
38
|
-
"set-checkpoint-mode",
|
|
39
|
-
"set-integration-overseer-mode"
|
|
33
|
+
"verify-current-state"
|
|
40
34
|
]);
|
|
41
35
|
export async function runInternalCommand(projectRoot, argv, io) {
|
|
42
36
|
const [subcommand, ...tokens] = argv;
|
|
43
37
|
if (!subcommand) {
|
|
44
|
-
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves |
|
|
38
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n");
|
|
45
39
|
return 1;
|
|
46
40
|
}
|
|
47
41
|
try {
|
|
@@ -96,22 +90,13 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
96
90
|
if (subcommand === "plan-split-waves") {
|
|
97
91
|
return await runPlanSplitWaves(projectRoot, parsePlanSplitWavesArgs(tokens), io);
|
|
98
92
|
}
|
|
99
|
-
if (subcommand === "set-worktree-mode") {
|
|
100
|
-
return await runSetWorktreeMode(projectRoot, tokens, io);
|
|
101
|
-
}
|
|
102
|
-
if (subcommand === "set-checkpoint-mode") {
|
|
103
|
-
return await runSetCheckpointMode(projectRoot, tokens, io);
|
|
104
|
-
}
|
|
105
|
-
if (subcommand === "set-integration-overseer-mode") {
|
|
106
|
-
return await runSetIntegrationOverseerMode(projectRoot, tokens, io);
|
|
107
|
-
}
|
|
108
93
|
if (subcommand === "wave-status") {
|
|
109
94
|
return await runWaveStatusCommand(projectRoot, tokens, io);
|
|
110
95
|
}
|
|
111
96
|
if (subcommand === "cohesion-contract") {
|
|
112
97
|
return await runCohesionContractCommand(projectRoot, tokens, io);
|
|
113
98
|
}
|
|
114
|
-
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves |
|
|
99
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n`);
|
|
115
100
|
return 1;
|
|
116
101
|
}
|
|
117
102
|
catch (err) {
|
|
@@ -135,10 +120,6 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
135
120
|
io.stderr.write(`error: dispatch_cap — ${err.message}\n`);
|
|
136
121
|
return 2;
|
|
137
122
|
}
|
|
138
|
-
if (err instanceof DispatchClaimInvalidError) {
|
|
139
|
-
io.stderr.write(`error: dispatch_claim_invalid — ${err.message}\n`);
|
|
140
|
-
return 2;
|
|
141
|
-
}
|
|
142
123
|
io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
143
124
|
return 1;
|
|
144
125
|
}
|
|
@@ -10,20 +10,15 @@ export interface CohesionContractArgs {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function parseCohesionContractArgs(tokens: string[]): CohesionContractArgs | null;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
|
|
14
|
+
* so authors have a starting point when a multi-slice wave needs cross-slice
|
|
15
|
+
* cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
|
|
16
|
+
* `slices` from the active run delegation ledger and carries
|
|
17
|
+
* `status.verdict: "scaffold"` so reviewers know to fill in the real content
|
|
18
|
+
* before treating it as authoritative.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* downstream tooling can see which slices the contract acknowledges,
|
|
22
|
-
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
23
|
-
* reviewers know not to treat it as authoritative.
|
|
24
|
-
*
|
|
25
|
-
* Refuses to overwrite an existing contract unless `--force` is
|
|
26
|
-
* passed; the existing file is treated as authored work.
|
|
20
|
+
* Refuses to overwrite an existing contract unless `--force` is passed; the
|
|
21
|
+
* existing file is treated as authored work.
|
|
27
22
|
*/
|
|
28
23
|
export declare function runCohesionContractCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
|
|
29
24
|
export {};
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
4
|
import { writeFileSafe } from "../fs-utils.js";
|
|
5
|
-
import { readDelegationLedger } from "../delegation.js";
|
|
5
|
+
import { readDelegationLedger, isParallelTddSliceWorker } from "../delegation.js";
|
|
6
6
|
export function parseCohesionContractArgs(tokens) {
|
|
7
7
|
const args = { stub: false, force: false, reason: null };
|
|
8
8
|
for (const token of tokens) {
|
|
@@ -27,20 +27,15 @@ export function parseCohesionContractArgs(tokens) {
|
|
|
27
27
|
return args;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
|
|
31
|
+
* so authors have a starting point when a multi-slice wave needs cross-slice
|
|
32
|
+
* cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
|
|
33
|
+
* `slices` from the active run delegation ledger and carries
|
|
34
|
+
* `status.verdict: "scaffold"` so reviewers know to fill in the real content
|
|
35
|
+
* before treating it as authoritative.
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* downstream tooling can see which slices the contract acknowledges,
|
|
39
|
-
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
40
|
-
* reviewers know not to treat it as authoritative.
|
|
41
|
-
*
|
|
42
|
-
* Refuses to overwrite an existing contract unless `--force` is
|
|
43
|
-
* passed; the existing file is treated as authored work.
|
|
37
|
+
* Refuses to overwrite an existing contract unless `--force` is passed; the
|
|
38
|
+
* existing file is treated as authored work.
|
|
44
39
|
*/
|
|
45
40
|
export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
46
41
|
const parsed = parseCohesionContractArgs(tokens);
|
|
@@ -63,13 +58,12 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
|
63
58
|
const sliceIds = collectSliceIds(ledger?.entries ?? []);
|
|
64
59
|
const reasonNote = parsed.reason
|
|
65
60
|
? `Reason: ${parsed.reason}`
|
|
66
|
-
: "Reason:
|
|
61
|
+
: "Reason: scaffold for a multi-slice wave that needs cohesion documentation.";
|
|
67
62
|
const md = [
|
|
68
63
|
"# Cohesion Contract",
|
|
69
64
|
"",
|
|
70
|
-
"
|
|
71
|
-
"_Status: `
|
|
72
|
-
"_TDD linter does not block stage-complete on `tdd.cohesion_contract_missing`._",
|
|
65
|
+
"_Scaffold generated by `cclaw-cli internal cohesion-contract --stub`._",
|
|
66
|
+
"_Status: `scaffold` — replace placeholders with real cross-slice data before review._",
|
|
73
67
|
"",
|
|
74
68
|
`${reasonNote}`,
|
|
75
69
|
"",
|
|
@@ -110,21 +104,21 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
|
110
104
|
touchpoints: [],
|
|
111
105
|
slices: sliceIds.map((sid) => ({
|
|
112
106
|
sliceId: sid,
|
|
113
|
-
description: `
|
|
107
|
+
description: `Scaffold entry for ${sid}; replace with real behavior summary.`,
|
|
114
108
|
implemented: true,
|
|
115
109
|
testsPass: true,
|
|
116
110
|
cohesionVerified: false
|
|
117
111
|
})),
|
|
118
112
|
status: {
|
|
119
|
-
verdict: "
|
|
113
|
+
verdict: "scaffold",
|
|
120
114
|
generatedBy: "cclaw-cli internal cohesion-contract --stub",
|
|
121
|
-
reason: parsed.reason ?? "
|
|
115
|
+
reason: parsed.reason ?? "scaffold for a multi-slice wave"
|
|
122
116
|
}
|
|
123
117
|
};
|
|
124
118
|
await writeFileSafe(mdPath, md);
|
|
125
119
|
await writeFileSafe(jsonPath, `${JSON.stringify(jsonStub, null, 2)}\n`);
|
|
126
|
-
io.stdout.write(`cclaw: cohesion-contract
|
|
127
|
-
"Status:
|
|
120
|
+
io.stdout.write(`cclaw: cohesion-contract scaffold written (${sliceIds.length} slice(s) referenced). ` +
|
|
121
|
+
"Status: scaffold — replace placeholders with real cross-slice data.\n");
|
|
128
122
|
return 0;
|
|
129
123
|
}
|
|
130
124
|
async function fileExists(filePath) {
|
|
@@ -139,7 +133,7 @@ async function fileExists(filePath) {
|
|
|
139
133
|
function collectSliceIds(entries) {
|
|
140
134
|
const set = new Set();
|
|
141
135
|
for (const entry of entries) {
|
|
142
|
-
if (entry.agent
|
|
136
|
+
if (!isParallelTddSliceWorker(entry.agent ?? ""))
|
|
143
137
|
continue;
|
|
144
138
|
if (entry.status !== "completed")
|
|
145
139
|
continue;
|
|
@@ -8,7 +8,7 @@ export interface FlowStateRepairArgs {
|
|
|
8
8
|
json: boolean;
|
|
9
9
|
quiet: boolean;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* when true, normalize `state/early-loop.json` to the canonical
|
|
12
12
|
* shape derived from `early-loop-log.jsonl`. Lets operators recover from
|
|
13
13
|
* legacy hand-written `early-loop.json` files that drifted from the
|
|
14
14
|
* source-of-truth log.
|
|
@@ -4,7 +4,7 @@ interface InternalIo {
|
|
|
4
4
|
stderr: Writable;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* split a large `05-plan.md` Implementation Units section
|
|
8
8
|
* into wave-NN.md sub-files so an executor can carry one wave at a time
|
|
9
9
|
* without re-reading the whole plan.
|
|
10
10
|
*
|
|
@@ -31,7 +31,7 @@ export interface PlanSplitWavesArgs {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const PLAN_SPLIT_DEFAULT_WAVE_SIZE = 5;
|
|
33
33
|
export declare const PLAN_SPLIT_SMALL_PLAN_THRESHOLD = 50;
|
|
34
|
-
/**
|
|
34
|
+
/** Member line in Parallel Execution Plan or wave-NN.md */
|
|
35
35
|
export interface ParsedParallelWaveMember {
|
|
36
36
|
sliceId: string;
|
|
37
37
|
unitId: string;
|
|
@@ -57,8 +57,45 @@ export declare function extractParallelExecutionManagedBody(planMarkdown: string
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function extractMembersListFromLine(trimmedLine: string): string | null;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* Extract a `(sliceId, unitId)` pair from a markdown table data row
|
|
61
|
+
* whose first column is an `S-NN` token. Used by the wave parser to
|
|
62
|
+
* recognize the table-format Parallel Execution Plan alongside (or
|
|
63
|
+
* instead of) the `**Members:**` bullet line.
|
|
64
|
+
*
|
|
65
|
+
* Rules:
|
|
66
|
+
* - The line must start with `|` (after trimming).
|
|
67
|
+
* - Column 1 (after stripping markdown noise) must match `^S-(\d+)$` —
|
|
68
|
+
* header rows (`| sliceId | …`) and separator rows (`|---|---|…`) are
|
|
69
|
+
* silently skipped.
|
|
70
|
+
* - Column 2, when present and non-empty, becomes the `unitId`
|
|
71
|
+
* verbatim (after stripping whitespace + backticks/quotes/brackets).
|
|
72
|
+
* This lets authors record task ids (`T-010`, `T-008a`, …) in the
|
|
73
|
+
* `unit` column without forcing a `U-NN` derivation.
|
|
74
|
+
* - When column 2 is absent or empty, fall back to the
|
|
75
|
+
* `S-NN → U-NN` derivation so the `**Members:**` parser path stays
|
|
76
|
+
* bit-identical for non-table plans.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseTableRowMember(trimmedLine: string): ParsedParallelWaveMember | null;
|
|
79
|
+
/**
|
|
80
|
+
* Parse `## Parallel Execution Plan` managed block for wave headings and
|
|
81
|
+
* member declarations. Recognizes BOTH the `**Members:**` / `Members:`
|
|
82
|
+
* line shape AND the markdown-table shape
|
|
83
|
+
* (`| sliceId | unit | dependsOn | …`).
|
|
84
|
+
*
|
|
85
|
+
* Wave headings accepted (case-insensitive, trailing text allowed):
|
|
86
|
+
* - `### Wave 04`
|
|
87
|
+
* - `### Wave W-04`
|
|
88
|
+
* - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
|
|
89
|
+
*
|
|
90
|
+
* Within a single wave the parser dedupes by `sliceId`: if the same
|
|
91
|
+
* slice appears in both `**Members:**` and a table row, the first
|
|
92
|
+
* occurrence wins (line-order). Cross-wave duplicates still throw
|
|
93
|
+
* `WavePlanDuplicateSliceError`.
|
|
94
|
+
*
|
|
95
|
+
* Malformed member tokens are skipped. Empty waves (heading present
|
|
96
|
+
* but neither a Members line nor any matching `| S-NN |` row found
|
|
97
|
+
* before the next heading) are RETURNED with `members: []` so callers
|
|
98
|
+
* can surface the boundary; classification is up to the caller.
|
|
62
99
|
*/
|
|
63
100
|
export declare function parseParallelExecutionPlanWaves(planMarkdown: string): ParsedParallelWave[];
|
|
64
101
|
/**
|
|
@@ -98,20 +135,20 @@ export interface ImplementationUnitParallelFields {
|
|
|
98
135
|
}
|
|
99
136
|
export interface ParseImplementationUnitParallelOptions {
|
|
100
137
|
/**
|
|
101
|
-
*
|
|
138
|
+
* Continuation: when the plan predates explicit parallel
|
|
102
139
|
* bullets, units without a `parallelizable:` line default to serial eligibility
|
|
103
140
|
* in the scheduler (`parallelizable: false`).
|
|
104
141
|
*/
|
|
105
142
|
legacyParallelDefaultSerial?: boolean;
|
|
106
143
|
}
|
|
107
144
|
/**
|
|
108
|
-
* Parse
|
|
145
|
+
* Parse parallel-metadata bullets from an implementation unit body.
|
|
109
146
|
* Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
|
|
110
147
|
* unless `legacyParallelDefaultSerial` is set).
|
|
111
148
|
*/
|
|
112
149
|
export declare function parseImplementationUnitParallelFields(unit: ParsedImplementationUnit, options?: ParseImplementationUnitParallelOptions): ImplementationUnitParallelFields;
|
|
113
150
|
/**
|
|
114
|
-
* True when the plan has implementation units but any unit is missing
|
|
151
|
+
* True when the plan has implementation units but any unit is missing
|
|
115
152
|
* `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
|
|
116
153
|
*/
|
|
117
154
|
export declare function planArtifactLacksV613ParallelMetadata(planMarkdown: string): boolean;
|
|
@@ -61,8 +61,70 @@ export function extractMembersListFromLine(trimmedLine) {
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* Extract a `(sliceId, unitId)` pair from a markdown table data row
|
|
65
|
+
* whose first column is an `S-NN` token. Used by the wave parser to
|
|
66
|
+
* recognize the table-format Parallel Execution Plan alongside (or
|
|
67
|
+
* instead of) the `**Members:**` bullet line.
|
|
68
|
+
*
|
|
69
|
+
* Rules:
|
|
70
|
+
* - The line must start with `|` (after trimming).
|
|
71
|
+
* - Column 1 (after stripping markdown noise) must match `^S-(\d+)$` —
|
|
72
|
+
* header rows (`| sliceId | …`) and separator rows (`|---|---|…`) are
|
|
73
|
+
* silently skipped.
|
|
74
|
+
* - Column 2, when present and non-empty, becomes the `unitId`
|
|
75
|
+
* verbatim (after stripping whitespace + backticks/quotes/brackets).
|
|
76
|
+
* This lets authors record task ids (`T-010`, `T-008a`, …) in the
|
|
77
|
+
* `unit` column without forcing a `U-NN` derivation.
|
|
78
|
+
* - When column 2 is absent or empty, fall back to the
|
|
79
|
+
* `S-NN → U-NN` derivation so the `**Members:**` parser path stays
|
|
80
|
+
* bit-identical for non-table plans.
|
|
81
|
+
*/
|
|
82
|
+
export function parseTableRowMember(trimmedLine) {
|
|
83
|
+
if (!trimmedLine.startsWith("|"))
|
|
84
|
+
return null;
|
|
85
|
+
const inner = trimmedLine.replace(/^\|/u, "").replace(/\|\s*$/u, "");
|
|
86
|
+
if (inner.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
const cells = inner.split("|").map((cell) => cell.trim());
|
|
89
|
+
if (cells.length === 0)
|
|
90
|
+
return null;
|
|
91
|
+
const stripDecorations = (raw) => raw.replace(/^[`"'[\]()]+|[`"'[\]()]+$/gu, "").trim();
|
|
92
|
+
const col1 = stripDecorations(cells[0]);
|
|
93
|
+
const sliceMatch = /^S-(\d+)$/u.exec(col1);
|
|
94
|
+
if (!sliceMatch)
|
|
95
|
+
return null;
|
|
96
|
+
const sliceNum = sliceMatch[1];
|
|
97
|
+
const sliceId = `S-${sliceNum}`;
|
|
98
|
+
let unitId = `U-${sliceNum}`;
|
|
99
|
+
if (cells.length >= 2) {
|
|
100
|
+
const col2 = stripDecorations(cells[1]);
|
|
101
|
+
if (col2.length > 0) {
|
|
102
|
+
const normalized = tokenToSliceAndUnit(col2);
|
|
103
|
+
unitId = normalized ? normalized.unitId : col2;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { sliceId, unitId };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parse `## Parallel Execution Plan` managed block for wave headings and
|
|
110
|
+
* member declarations. Recognizes BOTH the `**Members:**` / `Members:`
|
|
111
|
+
* line shape AND the markdown-table shape
|
|
112
|
+
* (`| sliceId | unit | dependsOn | …`).
|
|
113
|
+
*
|
|
114
|
+
* Wave headings accepted (case-insensitive, trailing text allowed):
|
|
115
|
+
* - `### Wave 04`
|
|
116
|
+
* - `### Wave W-04`
|
|
117
|
+
* - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
|
|
118
|
+
*
|
|
119
|
+
* Within a single wave the parser dedupes by `sliceId`: if the same
|
|
120
|
+
* slice appears in both `**Members:**` and a table row, the first
|
|
121
|
+
* occurrence wins (line-order). Cross-wave duplicates still throw
|
|
122
|
+
* `WavePlanDuplicateSliceError`.
|
|
123
|
+
*
|
|
124
|
+
* Malformed member tokens are skipped. Empty waves (heading present
|
|
125
|
+
* but neither a Members line nor any matching `| S-NN |` row found
|
|
126
|
+
* before the next heading) are RETURNED with `members: []` so callers
|
|
127
|
+
* can surface the boundary; classification is up to the caller.
|
|
66
128
|
*/
|
|
67
129
|
export function parseParallelExecutionPlanWaves(planMarkdown) {
|
|
68
130
|
const body = extractParallelExecutionManagedBody(planMarkdown);
|
|
@@ -72,22 +134,60 @@ export function parseParallelExecutionPlanWaves(planMarkdown) {
|
|
|
72
134
|
const waves = [];
|
|
73
135
|
let current = null;
|
|
74
136
|
const seenSlices = new Set();
|
|
137
|
+
let inWaveSlicesSeen = new Set();
|
|
75
138
|
const flushCurrent = () => {
|
|
76
|
-
if (current
|
|
139
|
+
if (current) {
|
|
77
140
|
waves.push(current);
|
|
78
141
|
}
|
|
79
142
|
};
|
|
143
|
+
/**
|
|
144
|
+
* Strict add: throw on duplicates within the same wave OR across waves.
|
|
145
|
+
* Used for the `**Members:**` path so the duplicate-detection
|
|
146
|
+
* contract is preserved bit-identically.
|
|
147
|
+
*/
|
|
148
|
+
const addMemberStrict = (member) => {
|
|
149
|
+
if (!current)
|
|
150
|
+
return;
|
|
151
|
+
if (inWaveSlicesSeen.has(member.sliceId) ||
|
|
152
|
+
seenSlices.has(member.sliceId)) {
|
|
153
|
+
throw new WavePlanDuplicateSliceError(`duplicate slice ${member.sliceId} in Parallel Execution Plan managed block`);
|
|
154
|
+
}
|
|
155
|
+
seenSlices.add(member.sliceId);
|
|
156
|
+
inWaveSlicesSeen.add(member.sliceId);
|
|
157
|
+
current.members.push(member);
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Lenient add: silently dedupe duplicates within the same wave (so the
|
|
161
|
+
* documented "Members + table both present" case keeps the Members
|
|
162
|
+
* declaration as authoritative); still throw on cross-wave duplicates
|
|
163
|
+
* to surface real plan-authoring bugs.
|
|
164
|
+
*/
|
|
165
|
+
const addMemberDedupInWave = (member) => {
|
|
166
|
+
if (!current)
|
|
167
|
+
return;
|
|
168
|
+
if (inWaveSlicesSeen.has(member.sliceId))
|
|
169
|
+
return;
|
|
170
|
+
if (seenSlices.has(member.sliceId)) {
|
|
171
|
+
throw new WavePlanDuplicateSliceError(`duplicate slice ${member.sliceId} in Parallel Execution Plan managed block`);
|
|
172
|
+
}
|
|
173
|
+
seenSlices.add(member.sliceId);
|
|
174
|
+
inWaveSlicesSeen.add(member.sliceId);
|
|
175
|
+
current.members.push(member);
|
|
176
|
+
};
|
|
80
177
|
for (const rawLine of lines) {
|
|
81
178
|
const trimmed = rawLine.trim();
|
|
82
|
-
const waveMatch = /^###\s+Wave\s+(\d+)\
|
|
179
|
+
const waveMatch = /^###\s+Wave\s+(?:W-)?(\d+)\b/iu.exec(trimmed);
|
|
83
180
|
if (waveMatch) {
|
|
84
181
|
flushCurrent();
|
|
85
182
|
const n = waveMatch[1];
|
|
86
183
|
current = { waveId: `W-${n.padStart(2, "0")}`, members: [] };
|
|
184
|
+
inWaveSlicesSeen = new Set();
|
|
87
185
|
continue;
|
|
88
186
|
}
|
|
187
|
+
if (!current)
|
|
188
|
+
continue;
|
|
89
189
|
const membersCsv = extractMembersListFromLine(trimmed);
|
|
90
|
-
if (membersCsv !== null
|
|
190
|
+
if (membersCsv !== null) {
|
|
91
191
|
const parts = membersCsv
|
|
92
192
|
.split(/,/u)
|
|
93
193
|
.map((p) => p.trim())
|
|
@@ -96,12 +196,13 @@ export function parseParallelExecutionPlanWaves(planMarkdown) {
|
|
|
96
196
|
const ids = tokenToSliceAndUnit(part);
|
|
97
197
|
if (!ids)
|
|
98
198
|
continue;
|
|
99
|
-
|
|
100
|
-
throw new WavePlanDuplicateSliceError(`duplicate slice ${ids.sliceId} in Parallel Execution Plan managed block`);
|
|
101
|
-
}
|
|
102
|
-
seenSlices.add(ids.sliceId);
|
|
103
|
-
current.members.push(ids);
|
|
199
|
+
addMemberStrict(ids);
|
|
104
200
|
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const tableMember = parseTableRowMember(trimmed);
|
|
204
|
+
if (tableMember) {
|
|
205
|
+
addMemberDedupInWave(tableMember);
|
|
105
206
|
}
|
|
106
207
|
}
|
|
107
208
|
flushCurrent();
|
|
@@ -220,7 +321,7 @@ export function formatNextParallelWaveSyncHint(merged) {
|
|
|
220
321
|
return `Parallel Execution Plan: ${candidate.waveId} has ${candidate.members.length} parallel members (${ids}).`;
|
|
221
322
|
}
|
|
222
323
|
/**
|
|
223
|
-
* Parse
|
|
324
|
+
* Parse parallel-metadata bullets from an implementation unit body.
|
|
224
325
|
* Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
|
|
225
326
|
* unless `legacyParallelDefaultSerial` is set).
|
|
226
327
|
*/
|
|
@@ -276,7 +377,7 @@ function unitBodyHasV613ParallelBullet(body, label) {
|
|
|
276
377
|
});
|
|
277
378
|
}
|
|
278
379
|
/**
|
|
279
|
-
* True when the plan has implementation units but any unit is missing
|
|
380
|
+
* True when the plan has implementation units but any unit is missing
|
|
280
381
|
* `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
|
|
281
382
|
*/
|
|
282
383
|
export function planArtifactLacksV613ParallelMetadata(planMarkdown) {
|
|
@@ -21,9 +21,6 @@ export interface WaveStatusNextDispatch {
|
|
|
21
21
|
export interface WaveStatusReport {
|
|
22
22
|
activeRunId: string;
|
|
23
23
|
currentStage: string;
|
|
24
|
-
tddCutoverSliceId: string | null;
|
|
25
|
-
tddWorktreeCutoverSliceId: string | null;
|
|
26
|
-
legacyContinuation: boolean;
|
|
27
24
|
waves: WaveStatusWaveSummary[];
|
|
28
25
|
nextDispatch: WaveStatusNextDispatch;
|
|
29
26
|
warnings: string[];
|
|
@@ -37,11 +34,11 @@ export interface RunWaveStatusOptions {
|
|
|
37
34
|
artifactsDir?: string;
|
|
38
35
|
}
|
|
39
36
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* Deterministic helper for the TDD controller. Reads the managed
|
|
38
|
+
* `<!-- parallel-exec-managed-start -->` block from
|
|
42
39
|
* `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
|
|
43
40
|
* reports waves + the next dispatchable members so the controller does
|
|
44
|
-
* NOT have to page through a
|
|
41
|
+
* NOT have to page through a long plan to find the active wave.
|
|
45
42
|
*
|
|
46
43
|
* Always exits 0 unless the plan is malformed (no managed block AND no
|
|
47
44
|
* wave-plans directory), in which case exit 2 with a structured error.
|