cclaw-cli 6.0.0 → 6.1.1
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.d.ts +16 -0
- package/dist/artifact-linter/design.js +82 -18
- package/dist/artifact-linter/scope.js +59 -20
- package/dist/artifact-linter/shared.d.ts +118 -2
- package/dist/artifact-linter/shared.js +231 -47
- package/dist/artifact-linter.js +83 -17
- package/dist/content/stage-schema.d.ts +23 -0
- package/dist/content/stage-schema.js +29 -0
- package/dist/delegation.d.ts +40 -1
- package/dist/delegation.js +75 -3
- package/dist/flow-state.d.ts +14 -0
- package/dist/internal/advance-stage/advance.d.ts +36 -0
- package/dist/internal/advance-stage/advance.js +100 -5
- package/dist/internal/advance-stage/review-loop.d.ts +9 -0
- package/dist/internal/advance-stage/review-loop.js +42 -4
- package/dist/run-persistence.js +25 -0
- package/package.json +1 -1
package/dist/delegation.js
CHANGED
|
@@ -327,7 +327,9 @@ export async function readDelegationLedger(projectRoot) {
|
|
|
327
327
|
* don't show up as corrupt lines.
|
|
328
328
|
*/
|
|
329
329
|
const NON_DELEGATION_AUDIT_EVENTS = new Set([
|
|
330
|
-
"mandatory_delegations_skipped_by_track"
|
|
330
|
+
"mandatory_delegations_skipped_by_track",
|
|
331
|
+
"artifact_validation_demoted_by_track",
|
|
332
|
+
"expansion_strategist_skipped_by_track"
|
|
331
333
|
]);
|
|
332
334
|
function isAuditEventLine(parsed) {
|
|
333
335
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
@@ -471,14 +473,22 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
471
473
|
const flowState = await readFlowState(projectRoot, {
|
|
472
474
|
repairFeatureSystem: options.repairFeatureSystem
|
|
473
475
|
});
|
|
474
|
-
|
|
476
|
+
// Wave 24 follow-up (v6.1.1): read `flowState.taskClass` as a fallback
|
|
477
|
+
// when the caller doesn't pass an explicit override. The
|
|
478
|
+
// `cclaw advance-stage` path (`buildValidationReport` →
|
|
479
|
+
// `checkMandatoryDelegations`) never forwarded `taskClass`, which left
|
|
480
|
+
// the `software-bugfix` skip dead for users who classified their run
|
|
481
|
+
// via `flow-state.json`. Forward-typed `null` callers still suppress
|
|
482
|
+
// the lookup explicitly; only `undefined` triggers the fallback.
|
|
483
|
+
const resolvedTaskClass = options.taskClass !== undefined ? options.taskClass : flowState.taskClass ?? null;
|
|
484
|
+
const mandatory = mandatoryAgentsFor(stage, flowState.track, resolvedTaskClass);
|
|
475
485
|
const skippedByTrack = mandatory.length === 0 &&
|
|
476
486
|
stageSchema(stage, flowState.track).mandatoryDelegations.length > 0;
|
|
477
487
|
if (skippedByTrack) {
|
|
478
488
|
await recordMandatorySkippedByTrack(projectRoot, {
|
|
479
489
|
stage,
|
|
480
490
|
track: flowState.track,
|
|
481
|
-
taskClass:
|
|
491
|
+
taskClass: resolvedTaskClass,
|
|
482
492
|
runId: flowState.activeRunId
|
|
483
493
|
});
|
|
484
494
|
}
|
|
@@ -617,3 +627,65 @@ async function recordMandatorySkippedByTrack(projectRoot, params) {
|
|
|
617
627
|
// best-effort audit; never block stage advance.
|
|
618
628
|
}
|
|
619
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
632
|
+
* that one or more required artifact-validation findings were
|
|
633
|
+
* demoted from blocking to advisory because the active run is on a
|
|
634
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
635
|
+
*
|
|
636
|
+
* The event mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
637
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
638
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
639
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
640
|
+
*/
|
|
641
|
+
export async function recordArtifactValidationDemotedByTrack(projectRoot, params) {
|
|
642
|
+
if (params.sections.length === 0)
|
|
643
|
+
return;
|
|
644
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
645
|
+
const payload = {
|
|
646
|
+
event: "artifact_validation_demoted_by_track",
|
|
647
|
+
stage: params.stage,
|
|
648
|
+
track: params.track,
|
|
649
|
+
taskClass: params.taskClass,
|
|
650
|
+
runId: params.runId,
|
|
651
|
+
sections: params.sections,
|
|
652
|
+
ts: new Date().toISOString()
|
|
653
|
+
};
|
|
654
|
+
try {
|
|
655
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
656
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
// best-effort audit; never block stage advance.
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
664
|
+
* that the scope-stage Expansion Strategist (`product-discovery`)
|
|
665
|
+
* delegation requirement was skipped because the active run is on a
|
|
666
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
667
|
+
*
|
|
668
|
+
* Mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
669
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
670
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
671
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
672
|
+
*/
|
|
673
|
+
export async function recordExpansionStrategistSkippedByTrack(projectRoot, params) {
|
|
674
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
675
|
+
const payload = {
|
|
676
|
+
event: "expansion_strategist_skipped_by_track",
|
|
677
|
+
stage: "scope",
|
|
678
|
+
track: params.track,
|
|
679
|
+
taskClass: params.taskClass,
|
|
680
|
+
runId: params.runId,
|
|
681
|
+
selectedScopeMode: params.selectedScopeMode,
|
|
682
|
+
ts: new Date().toISOString()
|
|
683
|
+
};
|
|
684
|
+
try {
|
|
685
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
686
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
// best-effort audit; never block stage advance.
|
|
690
|
+
}
|
|
691
|
+
}
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -76,6 +76,20 @@ export interface FlowState {
|
|
|
76
76
|
stageGateCatalog: Record<FlowStage, StageGateState>;
|
|
77
77
|
/** Active flow track (determines which stages are in the critical path for this run). */
|
|
78
78
|
track: FlowTrack;
|
|
79
|
+
/**
|
|
80
|
+
* Wave 25 (v6.1.0) — optional task class for the active run.
|
|
81
|
+
*
|
|
82
|
+
* Mirrors the `MandatoryDelegationTaskClass` union used by Wave 24's
|
|
83
|
+
* `mandatoryAgentsFor` helper. When set to `"software-bugfix"`, the
|
|
84
|
+
* artifact-validation escape (`shouldDemoteArtifactValidationByTrack`)
|
|
85
|
+
* collapses lite-tier-only checks (Architecture Diagram async/failure
|
|
86
|
+
* edges, Interaction Edge Case mandatory rows, Stale Diagram Drift,
|
|
87
|
+
* Expansion Strategist) from required → advisory.
|
|
88
|
+
*
|
|
89
|
+
* Persistence is best-effort: existing flow-state.json files written
|
|
90
|
+
* before Wave 25 simply omit the field (treated as `null`).
|
|
91
|
+
*/
|
|
92
|
+
taskClass?: "software-standard" | "software-trivial" | "software-bugfix" | null;
|
|
79
93
|
/** Stages explicitly skipped for this track (empty for standard; populated for quick). */
|
|
80
94
|
skippedStages: FlowStage[];
|
|
81
95
|
/** Stages invalidated by rewind operations and awaiting explicit acknowledgement. */
|
|
@@ -34,7 +34,43 @@ interface InternalValidationReport {
|
|
|
34
34
|
issues: string[];
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Wave 24 entry point — auto-hydrate evidence for an auto-hydratable
|
|
39
|
+
* gate that the agent already included in --passed but for which they
|
|
40
|
+
* forgot to provide --evidence-json. Returns silently when no
|
|
41
|
+
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
42
|
+
* envelope, etc.).
|
|
43
|
+
*
|
|
44
|
+
* Wave 25 (v6.1.0) layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
45
|
+
* top of this so the gate is also auto-included in selectedGateIds
|
|
46
|
+
* when the artifact yields a valid envelope. Together the two helpers
|
|
47
|
+
* remove the contradiction the user reported in Wave 24:
|
|
48
|
+
* - "omit this gate from --evidence-json so stage-complete can
|
|
49
|
+
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
50
|
+
* gates: design_diagram_freshness".
|
|
51
|
+
*/
|
|
37
52
|
export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Wave 25 (v6.1.0) — auto-include an auto-hydratable review-loop gate
|
|
55
|
+
* in `selectedGateIds` when:
|
|
56
|
+
* - The stage has an auto-hydratable gate registered via
|
|
57
|
+
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
58
|
+
* - The artifact yields a valid review-loop envelope via
|
|
59
|
+
* `extractReviewLoopEnvelopeFromArtifact`.
|
|
60
|
+
* - The gate is required for the active track.
|
|
61
|
+
* - The agent has NOT passed the gate yet (so we don't double-add).
|
|
62
|
+
*
|
|
63
|
+
* Returns the (possibly extended) array of selected gate IDs and
|
|
64
|
+
* mutates `evidenceByGate` to include the hydrated envelope.
|
|
65
|
+
*
|
|
66
|
+
* Together with `hydrateReviewLoopEvidenceFromArtifact` this makes the
|
|
67
|
+
* flow consistent: if the artifact contains the envelope, the agent
|
|
68
|
+
* neither has to include the gate in --passed nor pass --evidence-json
|
|
69
|
+
* for it. If the artifact does NOT contain the envelope, the agent
|
|
70
|
+
* gets a clear error pointing at the artifact section to add (via
|
|
71
|
+
* `reviewLoopArtifactFixHint`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function tryAutoHydrateAndSelectReviewLoopGate(projectRoot: string, stage: FlowStage, track: FlowState["track"], requiredGateIds: string[], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<string[]>;
|
|
38
74
|
export declare function buildValidationReport(projectRoot: string, flowState: FlowState, options?: {
|
|
39
75
|
allowBlockedReviewRoute?: boolean;
|
|
40
76
|
extraStageFlags?: string[];
|
|
@@ -10,7 +10,7 @@ import { readFlowState, writeFlowState } from "../../runs.js";
|
|
|
10
10
|
import { stageSchema } from "../../content/stage-schema.js";
|
|
11
11
|
import { extractReviewLoopEnvelopeFromArtifact } from "../../content/review-loop.js";
|
|
12
12
|
import { unique } from "./helpers.js";
|
|
13
|
-
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, validateGateEvidenceShape } from "./review-loop.js";
|
|
13
|
+
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
14
|
import { ensureProactiveDelegationTrace } from "./verify.js";
|
|
15
15
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
16
16
|
const natural = transitionTargets[0] ?? null;
|
|
@@ -57,6 +57,21 @@ function nextInteractionHints(flowState, args, successor) {
|
|
|
57
57
|
}
|
|
58
58
|
return hints;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Wave 24 entry point — auto-hydrate evidence for an auto-hydratable
|
|
62
|
+
* gate that the agent already included in --passed but for which they
|
|
63
|
+
* forgot to provide --evidence-json. Returns silently when no
|
|
64
|
+
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
65
|
+
* envelope, etc.).
|
|
66
|
+
*
|
|
67
|
+
* Wave 25 (v6.1.0) layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
68
|
+
* top of this so the gate is also auto-included in selectedGateIds
|
|
69
|
+
* when the artifact yields a valid envelope. Together the two helpers
|
|
70
|
+
* remove the contradiction the user reported in Wave 24:
|
|
71
|
+
* - "omit this gate from --evidence-json so stage-complete can
|
|
72
|
+
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
73
|
+
* gates: design_diagram_freshness".
|
|
74
|
+
*/
|
|
60
75
|
export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate) {
|
|
61
76
|
const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
|
|
62
77
|
if (!gateId)
|
|
@@ -87,8 +102,73 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
|
|
|
87
102
|
return;
|
|
88
103
|
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
89
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Wave 25 (v6.1.0) — auto-include an auto-hydratable review-loop gate
|
|
107
|
+
* in `selectedGateIds` when:
|
|
108
|
+
* - The stage has an auto-hydratable gate registered via
|
|
109
|
+
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
110
|
+
* - The artifact yields a valid review-loop envelope via
|
|
111
|
+
* `extractReviewLoopEnvelopeFromArtifact`.
|
|
112
|
+
* - The gate is required for the active track.
|
|
113
|
+
* - The agent has NOT passed the gate yet (so we don't double-add).
|
|
114
|
+
*
|
|
115
|
+
* Returns the (possibly extended) array of selected gate IDs and
|
|
116
|
+
* mutates `evidenceByGate` to include the hydrated envelope.
|
|
117
|
+
*
|
|
118
|
+
* Together with `hydrateReviewLoopEvidenceFromArtifact` this makes the
|
|
119
|
+
* flow consistent: if the artifact contains the envelope, the agent
|
|
120
|
+
* neither has to include the gate in --passed nor pass --evidence-json
|
|
121
|
+
* for it. If the artifact does NOT contain the envelope, the agent
|
|
122
|
+
* gets a clear error pointing at the artifact section to add (via
|
|
123
|
+
* `reviewLoopArtifactFixHint`).
|
|
124
|
+
*/
|
|
125
|
+
export async function tryAutoHydrateAndSelectReviewLoopGate(projectRoot, stage, track, requiredGateIds, selectedGateIds, evidenceByGate) {
|
|
126
|
+
const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
|
|
127
|
+
if (!gateId)
|
|
128
|
+
return selectedGateIds;
|
|
129
|
+
const reviewStage = stage === "scope" || stage === "design" ? stage : null;
|
|
130
|
+
if (!reviewStage)
|
|
131
|
+
return selectedGateIds;
|
|
132
|
+
if (!requiredGateIds.includes(gateId))
|
|
133
|
+
return selectedGateIds;
|
|
134
|
+
if (selectedGateIds.includes(gateId)) {
|
|
135
|
+
// Already selected — fall through to the existing hydration helper
|
|
136
|
+
// for the manual --evidence-json path.
|
|
137
|
+
await hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate);
|
|
138
|
+
return selectedGateIds;
|
|
139
|
+
}
|
|
140
|
+
const existing = evidenceByGate[gateId];
|
|
141
|
+
if (typeof existing === "string" && existing.trim().length > 0) {
|
|
142
|
+
return [...selectedGateIds, gateId];
|
|
143
|
+
}
|
|
144
|
+
const resolved = await resolveArtifactPath(stage, {
|
|
145
|
+
projectRoot,
|
|
146
|
+
track,
|
|
147
|
+
intent: "read"
|
|
148
|
+
});
|
|
149
|
+
let raw = "";
|
|
150
|
+
try {
|
|
151
|
+
raw = await fs.readFile(resolved.absPath, "utf8");
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return selectedGateIds;
|
|
155
|
+
}
|
|
156
|
+
const envelope = extractReviewLoopEnvelopeFromArtifact(raw, reviewStage, resolved.relPath);
|
|
157
|
+
if (!envelope)
|
|
158
|
+
return selectedGateIds;
|
|
159
|
+
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
160
|
+
return [...selectedGateIds, gateId];
|
|
161
|
+
}
|
|
90
162
|
export async function buildValidationReport(projectRoot, flowState, options = {}) {
|
|
91
|
-
|
|
163
|
+
// Wave 24 follow-up (v6.1.1): forward `flowState.taskClass` so the
|
|
164
|
+
// bugfix-skip lights up via the `cclaw advance-stage` path. The
|
|
165
|
+
// delegation helper now has its own fallback (it reads `flowState`
|
|
166
|
+
// internally), but threading the value here keeps the call site
|
|
167
|
+
// self-documenting and survives any future refactor that drops the
|
|
168
|
+
// implicit fallback.
|
|
169
|
+
const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage, {
|
|
170
|
+
taskClass: flowState.taskClass ?? undefined
|
|
171
|
+
});
|
|
92
172
|
const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState, {
|
|
93
173
|
extraStageFlags: options.extraStageFlags
|
|
94
174
|
});
|
|
@@ -229,9 +309,17 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
229
309
|
.flatMap((target) => getTransitionGuards(args.stage, target, flowState.track))
|
|
230
310
|
.filter((guardId) => !allowedGateIds.has(guardId)));
|
|
231
311
|
const selectableGateIds = new Set([...allowedGateIds, ...transitionGuardIds]);
|
|
232
|
-
|
|
312
|
+
let selectedGateIds = args.passedGateIds.length > 0
|
|
233
313
|
? args.passedGateIds.filter((gateId) => selectableGateIds.has(gateId))
|
|
234
314
|
: requiredGateIds;
|
|
315
|
+
// Wave 25 (v6.1.0): if the active stage has an auto-hydratable
|
|
316
|
+
// review-loop gate (currently `design.design_architecture_locked`)
|
|
317
|
+
// and the artifact already contains a valid review-loop envelope,
|
|
318
|
+
// include the gate in selectedGateIds and hydrate evidence in one
|
|
319
|
+
// step. This removes the Wave 24 contradiction between "omit from
|
|
320
|
+
// --evidence-json so we can auto-hydrate" and "missing
|
|
321
|
+
// --evidence-json entries for passed gates".
|
|
322
|
+
selectedGateIds = await tryAutoHydrateAndSelectReviewLoopGate(projectRoot, args.stage, flowState.track, requiredGateIds, selectedGateIds, args.evidenceByGate);
|
|
235
323
|
const selectedGateIdSet = new Set(selectedGateIds);
|
|
236
324
|
const selectedTransitionGuards = selectedGateIds.filter((gateId) => transitionGuardIds.has(gateId));
|
|
237
325
|
const blockedReviewRoute = args.stage === "review" && selectedGateIdSet.has("review_verdict_blocked");
|
|
@@ -240,7 +328,11 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
240
328
|
: requiredGateIds;
|
|
241
329
|
const missingRequired = requiredForSelectedRoute.filter((gateId) => !selectedGateIdSet.has(gateId));
|
|
242
330
|
if (missingRequired.length > 0) {
|
|
243
|
-
|
|
331
|
+
const autoHydrateGate = AUTO_REVIEW_LOOP_GATE_BY_STAGE[args.stage];
|
|
332
|
+
const autoHydrateHint = autoHydrateGate && missingRequired.includes(autoHydrateGate) && (args.stage === "scope" || args.stage === "design")
|
|
333
|
+
? ` Auto-hydratable gate "${autoHydrateGate}" was NOT auto-included because the design artifact is missing the review-loop envelope. Add a \`## ${args.stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop"}\` table (example envelope: ${reviewLoopEnvelopeExample(args.stage)}), or pass --evidence-json='{"${autoHydrateGate}": "<envelope-json>"}' alongside --passed=...,${autoHydrateGate}.`
|
|
334
|
+
: "";
|
|
335
|
+
io.stderr.write(`cclaw internal advance-stage: required gates not selected as passed: ${missingRequired.join(", ")}.${autoHydrateHint}\n`);
|
|
244
336
|
return 1;
|
|
245
337
|
}
|
|
246
338
|
const mandatory = new Set(schema.mandatoryDelegations);
|
|
@@ -269,7 +361,10 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
269
361
|
});
|
|
270
362
|
}
|
|
271
363
|
}
|
|
272
|
-
|
|
364
|
+
// Wave 25 (v6.1.0): hydration + auto-select happens earlier via
|
|
365
|
+
// `tryAutoHydrateAndSelectReviewLoopGate`. The previous explicit
|
|
366
|
+
// call here was redundant (helper already covered both the
|
|
367
|
+
// already-selected and not-yet-selected paths).
|
|
273
368
|
const catalog = flowState.stageGateCatalog[args.stage];
|
|
274
369
|
const nextPassed = unique([...catalog.passed, ...selectedGateIds]).filter((gateId) => allowedGateIds.has(gateId));
|
|
275
370
|
const nextPassedSet = new Set(nextPassed);
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { FlowStage } from "../../types.js";
|
|
2
2
|
export declare const AUTO_REVIEW_LOOP_GATE_BY_STAGE: Partial<Record<FlowStage, string>>;
|
|
3
|
+
/**
|
|
4
|
+
* Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
|
|
5
|
+
* accept for a review-loop envelope. The error messages emitted by
|
|
6
|
+
* `validateReviewLoopGateEvidence` always include this example so the
|
|
7
|
+
* agent never has to guess where `stage` lives (top-level of the
|
|
8
|
+
* envelope, NOT inside `payload`). Keep `stage`/`targetScore`/etc. in
|
|
9
|
+
* the order shown so a copy-paste from the error survives.
|
|
10
|
+
*/
|
|
11
|
+
export declare function reviewLoopEnvelopeExample(stage: "scope" | "design"): string;
|
|
3
12
|
export declare function pickReviewLoopEnvelope(value: unknown): Record<string, unknown> | null;
|
|
4
13
|
export declare function validateReviewLoopGateEvidence(stage: "scope" | "design", evidence: string): string | null;
|
|
5
14
|
export declare function validateUserApprovalEvidence(evidence: string): string | null;
|
|
@@ -11,6 +11,29 @@ const REVIEW_LOOP_STOP_REASONS = new Set([
|
|
|
11
11
|
"max_iterations_reached",
|
|
12
12
|
"user_opt_out"
|
|
13
13
|
]);
|
|
14
|
+
/**
|
|
15
|
+
* Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
|
|
16
|
+
* accept for a review-loop envelope. The error messages emitted by
|
|
17
|
+
* `validateReviewLoopGateEvidence` always include this example so the
|
|
18
|
+
* agent never has to guess where `stage` lives (top-level of the
|
|
19
|
+
* envelope, NOT inside `payload`). Keep `stage`/`targetScore`/etc. in
|
|
20
|
+
* the order shown so a copy-paste from the error survives.
|
|
21
|
+
*/
|
|
22
|
+
export function reviewLoopEnvelopeExample(stage) {
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
type: "review-loop",
|
|
25
|
+
stage,
|
|
26
|
+
targetScore: 0.8,
|
|
27
|
+
maxIterations: 3,
|
|
28
|
+
stopReason: "quality_threshold_met",
|
|
29
|
+
iterations: [{ iteration: 1, qualityScore: 0.8, findingsCount: 0 }]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function reviewLoopEnvelopeShapeHint(stage) {
|
|
33
|
+
return (`Expected envelope: ${reviewLoopEnvelopeExample(stage)}` +
|
|
34
|
+
" (top-level keys: type, stage, targetScore, maxIterations, stopReason, iterations[]). " +
|
|
35
|
+
"Stage MUST be at the top level — not inside payload.");
|
|
36
|
+
}
|
|
14
37
|
export function pickReviewLoopEnvelope(value) {
|
|
15
38
|
const direct = asRecord(value);
|
|
16
39
|
if (!direct)
|
|
@@ -31,14 +54,17 @@ export function validateReviewLoopGateEvidence(stage, evidence) {
|
|
|
31
54
|
parsed = JSON.parse(evidence);
|
|
32
55
|
}
|
|
33
56
|
catch {
|
|
34
|
-
return "must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`."
|
|
57
|
+
return ("must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`. " +
|
|
58
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
35
59
|
}
|
|
36
60
|
const envelope = pickReviewLoopEnvelope(parsed);
|
|
37
61
|
if (!envelope) {
|
|
38
|
-
return "must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`."
|
|
62
|
+
return ("must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`. " +
|
|
63
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
39
64
|
}
|
|
40
65
|
if (envelope.stage !== stage) {
|
|
41
|
-
return `review-loop envelope stage must be "${stage}"
|
|
66
|
+
return (`review-loop envelope stage must be "${stage}" at the top level of the envelope, not inside payload. ` +
|
|
67
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
42
68
|
}
|
|
43
69
|
const targetScore = envelope.targetScore;
|
|
44
70
|
if (typeof targetScore !== "number" || Number.isNaN(targetScore) || targetScore < 0 || targetScore > 1) {
|
|
@@ -157,5 +183,17 @@ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evid
|
|
|
157
183
|
export function reviewLoopArtifactFixHint(stage, gateId) {
|
|
158
184
|
if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
|
|
159
185
|
return "";
|
|
160
|
-
|
|
186
|
+
// Wave 25 (v6.1.0): the consistent flow is "include the gate in
|
|
187
|
+
// --passed AND let stage-complete auto-hydrate evidence from the
|
|
188
|
+
// artifact". Wave 24's hint told agents to omit the gate from
|
|
189
|
+
// --evidence-json, but they then hit
|
|
190
|
+
// `missing --evidence-json entries for passed gates: <gateId>`
|
|
191
|
+
// because hydration only runs when --evidence-json is also present
|
|
192
|
+
// OR when an artifact section yields a parseable envelope. The new
|
|
193
|
+
// hint tells the agent to:
|
|
194
|
+
// 1. Add the artifact section (so hydration succeeds), AND
|
|
195
|
+
// 2. Include the gate in --passed.
|
|
196
|
+
// No --evidence-json entry is required in that case.
|
|
197
|
+
const stageReviewSection = stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop";
|
|
198
|
+
return (` Fix in two steps: (1) Add a \`## ${stageReviewSection}\` table to the artifact with rows like \`| 1 | 0.80 | 0 |\` plus \`- Stop reason: quality_threshold_met\`, \`- Target score: 0.80\`, and \`- Max iterations: 3\`. (2) Re-run \`stage-complete ${stage} --passed=...,${gateId},...\` — stage-complete will auto-hydrate the envelope from the artifact, so you do NOT need to pass --evidence-json for ${gateId}.`);
|
|
161
199
|
}
|
package/dist/run-persistence.js
CHANGED
|
@@ -159,6 +159,29 @@ function sanitizeStageGateCatalog(value, fallback) {
|
|
|
159
159
|
function coerceTrack(value) {
|
|
160
160
|
return isFlowTrack(value) ? value : "standard";
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Wave 24 follow-up (v6.1.1) — preserve `flow-state.json#taskClass`
|
|
164
|
+
* across read/write round-trips. Before this audit fix the persistence
|
|
165
|
+
* layer silently dropped the field, which made the Wave 24 bugfix-skip
|
|
166
|
+
* (`mandatoryAgentsFor` short-circuit) and the Wave 25 artifact-validation
|
|
167
|
+
* demotion both dead in practice: the only entry point that classified
|
|
168
|
+
* a run was the unit-test harness passing `options.taskClass` directly
|
|
169
|
+
* to `checkMandatoryDelegations`. The accepted union mirrors
|
|
170
|
+
* `MandatoryDelegationTaskClass` plus `null` so callers can explicitly
|
|
171
|
+
* clear the classification without dropping the property.
|
|
172
|
+
*/
|
|
173
|
+
function coerceTaskClass(value) {
|
|
174
|
+
if (value === undefined)
|
|
175
|
+
return undefined;
|
|
176
|
+
if (value === null)
|
|
177
|
+
return null;
|
|
178
|
+
if (value === "software-standard" ||
|
|
179
|
+
value === "software-trivial" ||
|
|
180
|
+
value === "software-bugfix") {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
162
185
|
function sanitizeSkippedStages(value, track) {
|
|
163
186
|
const trackDefault = skippedStagesForTrack(track);
|
|
164
187
|
if (!Array.isArray(value)) {
|
|
@@ -354,6 +377,7 @@ function coerceFlowState(parsed) {
|
|
|
354
377
|
const activeRunId = typeof activeRunIdRaw === "string" && activeRunIdRaw.trim().length > 0
|
|
355
378
|
? activeRunIdRaw.trim()
|
|
356
379
|
: next.activeRunId;
|
|
380
|
+
const taskClass = coerceTaskClass(parsed.taskClass);
|
|
357
381
|
const state = {
|
|
358
382
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
359
383
|
activeRunId,
|
|
@@ -362,6 +386,7 @@ function coerceFlowState(parsed) {
|
|
|
362
386
|
guardEvidence: sanitizeGuardEvidence(parsed.guardEvidence),
|
|
363
387
|
stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog),
|
|
364
388
|
track,
|
|
389
|
+
...(taskClass !== undefined ? { taskClass } : {}),
|
|
365
390
|
skippedStages: sanitizeSkippedStages(parsed.skippedStages, track),
|
|
366
391
|
staleStages: sanitizeStaleStages(parsed.staleStages),
|
|
367
392
|
rewinds: sanitizeRewinds(parsed.rewinds),
|