cclaw-cli 6.1.0 → 6.2.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 +2 -2
- package/dist/artifact-linter/brainstorm.js +13 -13
- package/dist/artifact-linter/design.js +5 -5
- package/dist/artifact-linter/scope.js +3 -3
- package/dist/artifact-linter/shared.d.ts +18 -19
- package/dist/artifact-linter/shared.js +34 -31
- package/dist/artifact-linter.js +4 -0
- package/dist/content/hooks.js +2 -2
- package/dist/content/skills-elicitation.js +8 -19
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +27 -4
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/design.js +1 -1
- package/dist/content/stages/scope.js +2 -2
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +18 -17
- package/dist/content/subagents.js +1 -1
- package/dist/delegation.d.ts +4 -1
- package/dist/delegation.js +11 -3
- package/dist/flow-state.d.ts +5 -1
- package/dist/flow-state.js +6 -1
- package/dist/gate-evidence.js +4 -3
- package/dist/internal/advance-stage/advance.js +13 -4
- package/dist/internal/advance-stage/parsers.d.ts +2 -1
- package/dist/internal/advance-stage/parsers.js +11 -1
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +19 -0
- package/dist/internal/advance-stage/proactive-delegation-trace.js +44 -0
- package/dist/internal/advance-stage/start-flow.js +17 -2
- package/dist/internal/advance-stage/verify.d.ts +0 -8
- package/dist/internal/advance-stage/verify.js +2 -30
- package/dist/run-persistence.js +35 -2
- package/dist/track-heuristics.d.ts +2 -2
- package/dist/track-heuristics.js +11 -6
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Legacy `.cclaw/runs/` directories are only auto-removed when empty. If the direc
|
|
|
68
68
|
|
|
69
69
|
That gives you:
|
|
70
70
|
|
|
71
|
-
- **One path** from idea to ship, with `
|
|
71
|
+
- **One path** from idea to ship, with one user-chosen discovery mode (`lean`, `guided`, `deep`) and internal `quick` / `medium` / `standard` tracks.
|
|
72
72
|
- **Real gates** for evidence, tests, review, delegation, stale-stage recovery, and closeout.
|
|
73
73
|
- **Subagents with accountability**: controller owns state, workers do bounded tasks, overseers validate, evidence lands in `delegation-log.json`.
|
|
74
74
|
- **Recovery instead of confusion**: `npx cclaw-cli sync` tells you blockers and next fixes.
|
|
@@ -101,7 +101,7 @@ medium brainstorm -> spec -> plan -> tdd -> review -> ship
|
|
|
101
101
|
standard brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Track selection
|
|
104
|
+
At `/cc <idea>`, the user picks **one discovery mode** (`lean`, `guided`, `deep`) for upstream shaping. Track selection remains **model-guided and advisory** during start-up; runtime enforcement begins after state is written: subsequent `/cc` turns follow the selected internal track, persisted `discoveryMode`, required gates, delegation rules, stale-stage markers, and `closeout.shipSubstate`.
|
|
105
105
|
|
|
106
106
|
## When Blocked
|
|
107
107
|
|
|
@@ -20,7 +20,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
20
20
|
});
|
|
21
21
|
if (!brainstormShortCircuitActivated) {
|
|
22
22
|
const skipQuestions = ctx.activeStageFlags.includes("--skip-questions");
|
|
23
|
-
const floor = evaluateQaLogFloor(qaLogBody, track, "brainstorm", { skipQuestions });
|
|
23
|
+
const floor = evaluateQaLogFloor(qaLogBody, track, "brainstorm", { discoveryMode: ctx.discoveryMode, skipQuestions });
|
|
24
24
|
findings.push({
|
|
25
25
|
section: "qa_log_unconverged",
|
|
26
26
|
required: !floor.skipQuestionsAdvisory,
|
|
@@ -53,7 +53,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
53
53
|
const ok = hasDecisionLine;
|
|
54
54
|
findings.push({
|
|
55
55
|
section: "Approach Tier Classification",
|
|
56
|
-
required:
|
|
56
|
+
required: false,
|
|
57
57
|
rule: "Approach Tier must explicitly classify depth as one of `lite` (a.k.a. `Lightweight`), `Standard`, or `Deep`.",
|
|
58
58
|
found: ok,
|
|
59
59
|
details: ok
|
|
@@ -77,14 +77,14 @@ export async function lintBrainstormStage(ctx) {
|
|
|
77
77
|
});
|
|
78
78
|
findings.push({
|
|
79
79
|
section: "Approaches Role/Upside Taxonomy",
|
|
80
|
-
required:
|
|
80
|
+
required: false,
|
|
81
81
|
rule: "Approaches table must use canonical Role and Upside enum values.",
|
|
82
82
|
found: approachesTaxonomy.roleUpsideOk,
|
|
83
83
|
details: approachesTaxonomy.details
|
|
84
84
|
});
|
|
85
85
|
findings.push({
|
|
86
86
|
section: "Challenger Alternative Enforcement",
|
|
87
|
-
required:
|
|
87
|
+
required: false,
|
|
88
88
|
rule: "Approaches must include one challenger option with explicit high/higher upside.",
|
|
89
89
|
found: approachesTaxonomy.challengerOk,
|
|
90
90
|
details: approachesTaxonomy.details
|
|
@@ -96,7 +96,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
96
96
|
const orderOk = reactionIndex >= 0 && reactionIndex < directionIndex;
|
|
97
97
|
findings.push({
|
|
98
98
|
section: "Approach Reaction Ordering",
|
|
99
|
-
required:
|
|
99
|
+
required: false,
|
|
100
100
|
rule: "Approach Reaction must appear before Selected Direction (propose -> react -> recommend).",
|
|
101
101
|
found: orderOk,
|
|
102
102
|
details: orderOk
|
|
@@ -151,7 +151,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
151
151
|
const hasStatus = statusValue.length > 0;
|
|
152
152
|
findings.push({
|
|
153
153
|
section: "Short-Circuit Status",
|
|
154
|
-
required:
|
|
154
|
+
required: false,
|
|
155
155
|
rule: "Short-Circuit Decision must include a `Status:` line (`activated` or `bypassed`).",
|
|
156
156
|
found: hasStatus,
|
|
157
157
|
details: hasStatus
|
|
@@ -187,7 +187,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
187
187
|
const selfReview = validateCalibratedSelfReview(selfReviewBody);
|
|
188
188
|
findings.push({
|
|
189
189
|
section: "Calibrated Self-Review Format",
|
|
190
|
-
required:
|
|
190
|
+
required: false,
|
|
191
191
|
rule: "When Self-Review Notes are present, they must use the calibrated review prompt output shape.",
|
|
192
192
|
found: selfReview.ok,
|
|
193
193
|
details: selfReview.details
|
|
@@ -197,7 +197,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
197
197
|
if (criticPredictions !== null) {
|
|
198
198
|
findings.push({
|
|
199
199
|
section: "critic.predictions_missing",
|
|
200
|
-
required:
|
|
200
|
+
required: false,
|
|
201
201
|
rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
|
|
202
202
|
found: criticPredictions.found,
|
|
203
203
|
details: criticPredictions.details
|
|
@@ -227,7 +227,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
227
227
|
const ok = tokenMatches.size === 1 && !isPlaceholder;
|
|
228
228
|
findings.push({
|
|
229
229
|
section: "Mode Block Token",
|
|
230
|
-
required:
|
|
230
|
+
required: false,
|
|
231
231
|
rule: "Mode Block must declare exactly one mode token: STARTUP, BUILDER, ENGINEERING, OPS, or RESEARCH.",
|
|
232
232
|
found: ok,
|
|
233
233
|
details: ok
|
|
@@ -246,7 +246,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
246
246
|
/^RECOMMENDATION:/imu.test(raw)) {
|
|
247
247
|
findings.push({
|
|
248
248
|
section: "Approach Detail Cards",
|
|
249
|
-
required:
|
|
249
|
+
required: false,
|
|
250
250
|
rule: "Approach Detail Cards must include ≥2 `#### APPROACH <letter>` blocks each with Summary/Effort/Risk/Pros/Cons/Reuses.",
|
|
251
251
|
found: cardCount >= 2,
|
|
252
252
|
details: cardCount >= 2
|
|
@@ -257,7 +257,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
257
257
|
const hasRecommendation = recommendationLine !== null && recommendationLine[1] !== undefined && recommendationLine[1].trim().length > 0;
|
|
258
258
|
findings.push({
|
|
259
259
|
section: "Approach Recommendation Marker",
|
|
260
|
-
required:
|
|
260
|
+
required: false,
|
|
261
261
|
rule: "Approach Detail Cards must conclude with a single `RECOMMENDATION:` line citing the chosen letter and rationale.",
|
|
262
262
|
found: hasRecommendation,
|
|
263
263
|
details: hasRecommendation
|
|
@@ -272,7 +272,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
272
272
|
const optedOut = /\bnot used\b|\bn\/a\b|\bnone\b/iu.test(outsideVoiceBody);
|
|
273
273
|
findings.push({
|
|
274
274
|
section: "Outside Voice Slot Shape",
|
|
275
|
-
required:
|
|
275
|
+
required: false,
|
|
276
276
|
rule: "Outside Voice section must either declare opt-out (`not used`/`none`) or include `source:`, `prompt:`, `tension:`, `resolution:`.",
|
|
277
277
|
found: optedOut || missing.length === 0,
|
|
278
278
|
details: optedOut || missing.length === 0
|
|
@@ -337,7 +337,7 @@ export async function lintBrainstormStage(ctx) {
|
|
|
337
337
|
const waveDriftAddressed = hasCarryForwardSection && hasCarryForwardContent && hasDriftAuditMarkers;
|
|
338
338
|
findings.push({
|
|
339
339
|
section: "wave.drift_unaddressed",
|
|
340
|
-
required:
|
|
340
|
+
required: false,
|
|
341
341
|
rule: "[P1] wave.drift_unaddressed — when `.cclaw/wave-plans/` has >=2 entries, brainstorm must include `## Wave Carry-forward` with carry-forward and drift audit markers.",
|
|
342
342
|
found: waveDriftAddressed,
|
|
343
343
|
details: waveDriftAddressed
|
|
@@ -284,7 +284,7 @@ export async function lintDesignStage(ctx) {
|
|
|
284
284
|
});
|
|
285
285
|
{
|
|
286
286
|
const skipQuestions = activeStageFlags.includes("--skip-questions");
|
|
287
|
-
const floor = evaluateQaLogFloor(qaLogBody, track, "design", { skipQuestions });
|
|
287
|
+
const floor = evaluateQaLogFloor(qaLogBody, track, "design", { discoveryMode: ctx.discoveryMode, skipQuestions });
|
|
288
288
|
findings.push({
|
|
289
289
|
section: "qa_log_unconverged",
|
|
290
290
|
required: !floor.skipQuestionsAdvisory,
|
|
@@ -297,7 +297,7 @@ export async function lintDesignStage(ctx) {
|
|
|
297
297
|
if (criticPredictions !== null) {
|
|
298
298
|
findings.push({
|
|
299
299
|
section: "critic.predictions_missing",
|
|
300
|
-
required:
|
|
300
|
+
required: false,
|
|
301
301
|
rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
|
|
302
302
|
found: criticPredictions.found,
|
|
303
303
|
details: criticPredictions.details
|
|
@@ -390,7 +390,7 @@ export async function lintDesignStage(ctx) {
|
|
|
390
390
|
const ack = markdownFieldRegex("Iron rule acknowledged", "yes|true|y").test(regressionBody);
|
|
391
391
|
findings.push({
|
|
392
392
|
section: "Regression Iron Rule Acknowledgement",
|
|
393
|
-
required:
|
|
393
|
+
required: false,
|
|
394
394
|
rule: "Regression Iron Rule section must affirm `Iron rule acknowledged: yes`.",
|
|
395
395
|
found: ack,
|
|
396
396
|
details: ack
|
|
@@ -409,7 +409,7 @@ export async function lintDesignStage(ctx) {
|
|
|
409
409
|
const ok = isEmpty || validRows.length >= 1;
|
|
410
410
|
findings.push({
|
|
411
411
|
section: "Calibrated Finding Format",
|
|
412
|
-
required:
|
|
412
|
+
required: false,
|
|
413
413
|
rule: "Calibrated Findings must either declare `None this stage` or contain at least one finding in the form `[P1|P2|P3] (confidence: <n>/10) <path>[:<line>] — <description>`.",
|
|
414
414
|
found: ok,
|
|
415
415
|
details: isEmpty
|
|
@@ -423,7 +423,7 @@ export async function lintDesignStage(ctx) {
|
|
|
423
423
|
if (layeredDocumentReview !== null) {
|
|
424
424
|
findings.push({
|
|
425
425
|
section: "Document Reviewer Structured Findings",
|
|
426
|
-
required:
|
|
426
|
+
required: false,
|
|
427
427
|
rule: "When Layered review references coherence-reviewer/scope-guardian-reviewer/feasibility-reviewer, include explicit reviewer status plus calibrated finding lines.",
|
|
428
428
|
found: layeredDocumentReview.missingStructured.length === 0,
|
|
429
429
|
details: layeredDocumentReview.missingStructured.length === 0
|
|
@@ -23,7 +23,7 @@ export async function lintScopeStage(ctx) {
|
|
|
23
23
|
});
|
|
24
24
|
{
|
|
25
25
|
const skipQuestions = activeStageFlags.includes("--skip-questions");
|
|
26
|
-
const floor = evaluateQaLogFloor(qaLogBody, track, "scope", { skipQuestions });
|
|
26
|
+
const floor = evaluateQaLogFloor(qaLogBody, track, "scope", { discoveryMode: ctx.discoveryMode, skipQuestions });
|
|
27
27
|
findings.push({
|
|
28
28
|
section: "qa_log_unconverged",
|
|
29
29
|
required: !floor.skipQuestionsAdvisory,
|
|
@@ -94,7 +94,7 @@ export async function lintScopeStage(ctx) {
|
|
|
94
94
|
if (criticPredictions !== null) {
|
|
95
95
|
findings.push({
|
|
96
96
|
section: "critic.predictions_missing",
|
|
97
|
-
required:
|
|
97
|
+
required: false,
|
|
98
98
|
rule: "[P2] critic.predictions_missing — pre-commitment predictions block missing or empty",
|
|
99
99
|
found: criticPredictions.found,
|
|
100
100
|
details: criticPredictions.details
|
|
@@ -139,7 +139,7 @@ export async function lintScopeStage(ctx) {
|
|
|
139
139
|
}
|
|
140
140
|
findings.push({
|
|
141
141
|
section: "Locked Decisions ID Integrity",
|
|
142
|
-
required:
|
|
142
|
+
required: false,
|
|
143
143
|
rule: "Locked Decisions section must list each decision with a unique stable D-XX ID. (D-XX IDs replaced the legacy LD#<sha8> hash anchors in Wave 22.)",
|
|
144
144
|
found: issues.length === 0,
|
|
145
145
|
details: issues.length === 0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FlowStage, type FlowTrack } from "../types.js";
|
|
1
|
+
import { type DiscoveryMode, type FlowStage, type FlowTrack } from "../types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Stages that run adaptive elicitation. The `qa_log_unconverged` rule
|
|
4
4
|
* only fires for these. Other stages may still record a Q&A Log but no
|
|
@@ -20,6 +20,7 @@ export interface ForcingQuestionTopic {
|
|
|
20
20
|
topic: string;
|
|
21
21
|
}
|
|
22
22
|
export interface QaLogFloorOptions {
|
|
23
|
+
discoveryMode?: DiscoveryMode;
|
|
23
24
|
/**
|
|
24
25
|
* When true, downgrades the finding to advisory (`required: false`).
|
|
25
26
|
* Set when `--skip-questions` was persisted to the active stage flags.
|
|
@@ -97,25 +98,22 @@ export declare function extractForcingQuestions(stage: FlowStage): ForcingQuesti
|
|
|
97
98
|
* design artifact. Returns ok=true when convergence is reached or any
|
|
98
99
|
* escape hatch fires.
|
|
99
100
|
*
|
|
100
|
-
* Convergence sources (any one
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* - `--skip-questions`
|
|
109
|
-
*
|
|
110
|
-
* -
|
|
111
|
-
* refactor) AND the artifact has at least one substantive row — treat
|
|
112
|
-
* as converged because there is nothing left to force.
|
|
101
|
+
* Convergence sources (any one can set ok=true — see also
|
|
102
|
+
* `adaptiveElicitationSkillMarkdown`):
|
|
103
|
+
* - Every forcing-question topic id from the stage checklist is tagged
|
|
104
|
+
* `[topic:<id>]` on at least one `## Q&A Log` row.
|
|
105
|
+
* - Ralph-Loop path: last 2 substantive rows read as no-new-decisions,
|
|
106
|
+
* substantive count ≥ max(2, questionBudgetHint(discoveryMode, stage).min),
|
|
107
|
+
* and not (guided/deep discovery with pending forcing-topic ids).
|
|
108
|
+
* - Stop-signal row (`QA_LOG_STOP_SIGNAL_PATTERNS`).
|
|
109
|
+
* - `--skip-questions` (`options.skipQuestions`): ok remains false but
|
|
110
|
+
* `skipQuestionsAdvisory` is true (linter treats as non-blocking).
|
|
111
|
+
* - No forcing-questions row in the checklist and ≥1 substantive row.
|
|
113
112
|
*
|
|
114
|
-
* Wave 23
|
|
115
|
-
* `
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* harness UI compatibility but are always 0/false.
|
|
113
|
+
* Wave 23 retired the fixed English-only count floor; Wave 24 made
|
|
114
|
+
* `[topic:<id>]` mandatory for topic coverage. The `min` and
|
|
115
|
+
* `liteShortCircuit` fields stay for harness compatibility (min is always 0;
|
|
116
|
+
* liteShortCircuit false).
|
|
119
117
|
*/
|
|
120
118
|
export declare function evaluateQaLogFloor(qaLogBody: string | null, track: FlowTrack, stage: FlowStage, options?: QaLogFloorOptions): QaLogFloorResult;
|
|
121
119
|
export interface LintFinding {
|
|
@@ -448,6 +446,7 @@ export interface StageLintContext {
|
|
|
448
446
|
projectRoot: string;
|
|
449
447
|
stage: FlowStage;
|
|
450
448
|
track: FlowTrack;
|
|
449
|
+
discoveryMode: DiscoveryMode;
|
|
451
450
|
raw: string;
|
|
452
451
|
absFile: string;
|
|
453
452
|
sections: H2SectionMap;
|
|
@@ -207,25 +207,22 @@ function lastTwoRowsAllNoDecision(substantiveRows) {
|
|
|
207
207
|
* design artifact. Returns ok=true when convergence is reached or any
|
|
208
208
|
* escape hatch fires.
|
|
209
209
|
*
|
|
210
|
-
* Convergence sources (any one
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* - `--skip-questions`
|
|
219
|
-
*
|
|
220
|
-
* -
|
|
221
|
-
* refactor) AND the artifact has at least one substantive row — treat
|
|
222
|
-
* as converged because there is nothing left to force.
|
|
210
|
+
* Convergence sources (any one can set ok=true — see also
|
|
211
|
+
* `adaptiveElicitationSkillMarkdown`):
|
|
212
|
+
* - Every forcing-question topic id from the stage checklist is tagged
|
|
213
|
+
* `[topic:<id>]` on at least one `## Q&A Log` row.
|
|
214
|
+
* - Ralph-Loop path: last 2 substantive rows read as no-new-decisions,
|
|
215
|
+
* substantive count ≥ max(2, questionBudgetHint(discoveryMode, stage).min),
|
|
216
|
+
* and not (guided/deep discovery with pending forcing-topic ids).
|
|
217
|
+
* - Stop-signal row (`QA_LOG_STOP_SIGNAL_PATTERNS`).
|
|
218
|
+
* - `--skip-questions` (`options.skipQuestions`): ok remains false but
|
|
219
|
+
* `skipQuestionsAdvisory` is true (linter treats as non-blocking).
|
|
220
|
+
* - No forcing-questions row in the checklist and ≥1 substantive row.
|
|
223
221
|
*
|
|
224
|
-
* Wave 23
|
|
225
|
-
* `
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* harness UI compatibility but are always 0/false.
|
|
222
|
+
* Wave 23 retired the fixed English-only count floor; Wave 24 made
|
|
223
|
+
* `[topic:<id>]` mandatory for topic coverage. The `min` and
|
|
224
|
+
* `liteShortCircuit` fields stay for harness compatibility (min is always 0;
|
|
225
|
+
* liteShortCircuit false).
|
|
229
226
|
*/
|
|
230
227
|
export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
231
228
|
const rows = qaLogBody !== null ? getMarkdownTableRows(qaLogBody) : [];
|
|
@@ -233,6 +230,7 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
|
233
230
|
const count = substantiveRows.length;
|
|
234
231
|
const hasStopSignal = detectStopSignal(rows);
|
|
235
232
|
const skipQuestionsAdvisory = options.skipQuestions === true;
|
|
233
|
+
const discoveryMode = options.discoveryMode ?? (track === "quick" ? "lean" : "guided");
|
|
236
234
|
const forcingTopics = (options.forcingQuestions ?? extractForcingQuestions(stage)).map((entry) => (typeof entry === "string" ? { id: entry, topic: entry } : entry));
|
|
237
235
|
const forcingCovered = [];
|
|
238
236
|
const forcingPending = [];
|
|
@@ -242,9 +240,13 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
|
242
240
|
else
|
|
243
241
|
forcingPending.push(topic.id);
|
|
244
242
|
}
|
|
243
|
+
const budget = questionBudgetHint(discoveryMode, stage);
|
|
245
244
|
const noNewDecisions = lastTwoRowsAllNoDecision(substantiveRows);
|
|
246
245
|
const allForcingCovered = forcingTopics.length > 0 ? forcingPending.length === 0 : count >= 1;
|
|
247
|
-
const
|
|
246
|
+
const minimumRowsReached = count >= Math.max(2, budget.min);
|
|
247
|
+
const riskEscalationNeeded = forcingPending.length > 0 && /^(guided|deep)$/u.test(discoveryMode);
|
|
248
|
+
const noNewDecisionConverged = noNewDecisions && minimumRowsReached && !riskEscalationNeeded;
|
|
249
|
+
const ok = allForcingCovered || noNewDecisionConverged || hasStopSignal;
|
|
248
250
|
const pendingIdsBracket = forcingPending.length > 0
|
|
249
251
|
? `[${forcingPending.join(", ")}]`
|
|
250
252
|
: "[none]";
|
|
@@ -256,10 +258,10 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
|
256
258
|
else if (allForcingCovered) {
|
|
257
259
|
details = `Q&A Log converged: stage exposes no forcing-questions row and ${count} substantive entry recorded.`;
|
|
258
260
|
}
|
|
259
|
-
else if (
|
|
261
|
+
else if (noNewDecisionConverged) {
|
|
260
262
|
const remaining = forcingPending.length > 0
|
|
261
|
-
? ` ${forcingPending.length} forcing topic IDs still pending: ${pendingIdsBracket}
|
|
262
|
-
:
|
|
263
|
+
? ` ${forcingPending.length} forcing topic IDs still pending: ${pendingIdsBracket} after the minimum ${budget.min}-row discovery pass.`
|
|
264
|
+
: ` Ralph-Loop convergence detector says no new decision-changing rows in the last 2 turns after the minimum ${budget.min}-row discovery pass.`;
|
|
263
265
|
details = `Q&A Log converged via no-new-decisions detector at ${count} row(s).${remaining}`;
|
|
264
266
|
}
|
|
265
267
|
else {
|
|
@@ -269,27 +271,28 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
|
269
271
|
else if (skipQuestionsAdvisory) {
|
|
270
272
|
details = `Q&A Log unconverged at ${count} row(s); --skip-questions flag downgraded the finding to advisory. Forcing topic IDs pending: ${pendingIdsBracket}.`;
|
|
271
273
|
}
|
|
274
|
+
else if (noNewDecisions && !minimumRowsReached) {
|
|
275
|
+
details = `Q&A Log still below the minimum ${budget.min}-row ${discoveryMode} discovery pass (${count} substantive row(s)). Forcing topic IDs pending: ${pendingIdsBracket}. Continue asking decision-changing questions before drafting.`;
|
|
276
|
+
}
|
|
277
|
+
else if (riskEscalationNeeded && noNewDecisions) {
|
|
278
|
+
details = `Q&A Log cannot converge via Ralph-Loop yet because ${discoveryMode} mode keeps pending forcing topic IDs blocking: ${pendingIdsBracket}. Cover the remaining topics or record an explicit stop-signal row.`;
|
|
279
|
+
}
|
|
272
280
|
else {
|
|
273
|
-
details = `Q&A Log unconverged at ${count} row(s). Forcing topic IDs pending: ${pendingIdsBracket}. Tag each Q&A row with \`[topic:<id>]\` to mark coverage,
|
|
281
|
+
details = `Q&A Log unconverged at ${count} row(s). Forcing topic IDs pending: ${pendingIdsBracket}. Tag each Q&A row with \`[topic:<id>]\` to mark coverage, complete the minimum ${budget.min}-row ${discoveryMode} discovery pass, or record an explicit user stop-signal row.`;
|
|
274
282
|
}
|
|
275
|
-
|
|
276
|
-
// blocking count. `recommended` is the soft budget per track/stage.
|
|
277
|
-
const advisoryBudget = questionBudgetHint(track, stage).recommended;
|
|
283
|
+
const advisoryBudget = budget.recommended;
|
|
278
284
|
return {
|
|
279
285
|
ok,
|
|
280
286
|
count,
|
|
281
|
-
// Wave 23: floor no longer enforces a count. Surfacing 0 keeps the
|
|
282
|
-
// QaLogFloorSignal shape stable for harness consumers; harness UIs
|
|
283
|
-
// may show `recommended` from `questionBudgetHint` separately.
|
|
284
287
|
min: 0,
|
|
285
288
|
hasStopSignal,
|
|
286
289
|
liteShortCircuit: false,
|
|
287
290
|
skipQuestionsAdvisory,
|
|
288
291
|
forcingCovered,
|
|
289
292
|
forcingPending,
|
|
290
|
-
noNewDecisions,
|
|
293
|
+
noNewDecisions: noNewDecisionConverged,
|
|
291
294
|
details: advisoryBudget > 0
|
|
292
|
-
? `${details} (advisory budget for ${
|
|
295
|
+
? `${details} (advisory budget for ${discoveryMode}/${stage}: ~${advisoryBudget} Q&A turns)`
|
|
293
296
|
: details
|
|
294
297
|
};
|
|
295
298
|
}
|
package/dist/artifact-linter.js
CHANGED
|
@@ -114,6 +114,7 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
114
114
|
// Same flow-state read powers the post-loop demotion + audit log
|
|
115
115
|
// below; we cache the result here to avoid two disk reads.
|
|
116
116
|
let activeStageFlags = [];
|
|
117
|
+
let discoveryMode = "guided";
|
|
117
118
|
let taskClass = null;
|
|
118
119
|
let activeRunId = null;
|
|
119
120
|
try {
|
|
@@ -121,11 +122,13 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
121
122
|
const hint = flowState.interactionHints?.[stage];
|
|
122
123
|
if (hint?.skipQuestions === true)
|
|
123
124
|
activeStageFlags.push("--skip-questions");
|
|
125
|
+
discoveryMode = flowState.discoveryMode ?? "guided";
|
|
124
126
|
taskClass = flowState.taskClass ?? null;
|
|
125
127
|
activeRunId = flowState.activeRunId ?? null;
|
|
126
128
|
}
|
|
127
129
|
catch {
|
|
128
130
|
activeStageFlags = [];
|
|
131
|
+
discoveryMode = "guided";
|
|
129
132
|
taskClass = null;
|
|
130
133
|
activeRunId = null;
|
|
131
134
|
}
|
|
@@ -195,6 +198,7 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
195
198
|
projectRoot,
|
|
196
199
|
stage,
|
|
197
200
|
track,
|
|
201
|
+
discoveryMode,
|
|
198
202
|
raw,
|
|
199
203
|
absFile,
|
|
200
204
|
sections,
|
package/dist/content/hooks.js
CHANGED
|
@@ -185,13 +185,13 @@ void main();
|
|
|
185
185
|
`;
|
|
186
186
|
}
|
|
187
187
|
export function startFlowScript() {
|
|
188
|
-
return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]", { defaultQuietEnvVar: "CCLAW_START_FLOW_QUIET" });
|
|
188
|
+
return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--discovery-mode=<lean|guided|deep>] [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]", { defaultQuietEnvVar: "CCLAW_START_FLOW_QUIET" });
|
|
189
189
|
}
|
|
190
190
|
export function cancelRunScript() {
|
|
191
191
|
return internalHelperScript("cancel-run", "cancel-run", "Usage: node " + RUNTIME_ROOT + "/hooks/cancel-run.mjs --reason=<text> [--disposition=<cancelled|abandoned>] [--name=<slug>]");
|
|
192
192
|
}
|
|
193
193
|
export function stageCompleteScript() {
|
|
194
|
-
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--
|
|
194
|
+
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--skip-questions] [--json]", {
|
|
195
195
|
positionalArgName: "stage",
|
|
196
196
|
positionalArgRequired: true,
|
|
197
197
|
defaultQuietEnvVar: "CCLAW_STAGE_COMPLETE_QUIET"
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
2
|
import { questionBudgetHint } from "../track-heuristics.js";
|
|
3
|
-
import { FLOW_TRACKS } from "../types.js";
|
|
4
3
|
const ELICITATION_STAGES = ["brainstorm", "scope", "design"];
|
|
5
4
|
function renderQuestionBudgetHintTable() {
|
|
6
5
|
const rows = [];
|
|
7
|
-
for (const
|
|
6
|
+
for (const mode of ["lean", "guided", "deep"]) {
|
|
8
7
|
for (const stage of ELICITATION_STAGES) {
|
|
9
|
-
const hint = questionBudgetHint(
|
|
10
|
-
rows.push(`| \`${
|
|
8
|
+
const hint = questionBudgetHint(mode, stage);
|
|
9
|
+
rows.push(`| \`${mode}\` | \`${stage}\` | ${hint.min} | ${hint.recommended} | ${hint.hardCapWarning} |`);
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
|
-
return `|
|
|
12
|
+
return `| Discovery mode | Stage | Min | Recommended | Hard cap warning |
|
|
14
13
|
|---|---|---|---|---|
|
|
15
14
|
${rows.join("\n")}`;
|
|
16
15
|
}
|
|
@@ -47,7 +46,7 @@ These behaviors are the exact reason this skill exists. The linter will block yo
|
|
|
47
46
|
- Ask exactly one question per turn and wait for the answer before asking the next one.
|
|
48
47
|
- Use harness-native question tools first; prose fallback is allowed only when the tool is unavailable.
|
|
49
48
|
- Keep a running Q&A trace in the active artifact under \`## Q&A Log\` in \`${RUNTIME_ROOT}/artifacts/\` as append-only rows.
|
|
50
|
-
- **Convergence floor**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges.
|
|
49
|
+
- **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-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 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).
|
|
51
50
|
- **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
51
|
- **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
52
|
|
|
@@ -103,23 +102,13 @@ Each grill question follows the same Core Protocol: ask one, wait, log, self-eva
|
|
|
103
102
|
|
|
104
103
|
Do not ask extra questions "for theater" on simple low-risk work.
|
|
105
104
|
|
|
106
|
-
## Question Budget Hint (
|
|
105
|
+
## Question Budget Hint (\`questionBudgetHint\` — min rows feed the convergence floor)
|
|
107
106
|
|
|
108
|
-
Source of truth: \`questionBudgetHint(
|
|
109
|
-
**soft hints** for harness UI and elicitation pacing; gate blocking is done
|
|
110
|
-
by the \`qa_log_unconverged\` rule (Ralph-Loop convergence detector), NOT by
|
|
111
|
-
a fixed count.
|
|
107
|
+
Source of truth: \`questionBudgetHint(discoveryMode, stage)\`. The \`Min\` column is **not advisory** for the 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.
|
|
112
108
|
|
|
113
109
|
${budgetTable}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
How to use the columns:
|
|
118
|
-
- \`Min\` — soft minimum to surface forcing questions; not a blocking gate.
|
|
119
|
-
- \`Recommended\` — target for normal flows.
|
|
120
|
-
- \`Hard cap warning\` — point at which to stop or compress remaining forcing questions into one final batched ask. Not skip.
|
|
121
|
-
|
|
122
|
-
## Stage Forcing Questions (walk in order, one per turn)
|
|
111
|
+
Default mapping note: \`lean\` maps to a lightweight specialist tier on early stages, \`guided\` to standard, \`deep\` to deep; risk signals can escalate further.
|
|
123
112
|
|
|
124
113
|
**Walk the forcing questions list one-by-one in order, asking each as a separate turn.** Do NOT batch. Do NOT pick favorites — go in order. For each question record one of:
|
|
125
114
|
- \`asked\` — question was asked and answered.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FlowStage, FlowTrack, TransitionRule } from "../types.js";
|
|
1
|
+
import type { DiscoveryMode, FlowStage, FlowTrack, TransitionRule } from "../types.js";
|
|
2
2
|
import type { StageComplexityTier, StageAutoSubagentDispatch, StageSchema } from "./stages/schema-types.js";
|
|
3
3
|
export type { ArtifactValidation, CrossStageTrace, ReviewSection, StageComplexityTier, StageExecutionModel, StagePhilosophy, StageArtifactRules, StageReviewLoop, StageReviewLens, StageAutoSubagentDispatch, StageGate, StageSchemaLegacyInput, StageSchema, StageSchemaInput, StageSchemaV2Input } from "./stages/schema-types.js";
|
|
4
4
|
export declare const SKILL_ENVELOPE_KINDS: readonly ["stage-output", "gate-result", "delegation-record"];
|
|
@@ -83,7 +83,7 @@ export declare function mandatoryDelegationsForStage(stage: FlowStage, complexit
|
|
|
83
83
|
* boundary.
|
|
84
84
|
*/
|
|
85
85
|
export type MandatoryDelegationTaskClass = "software-standard" | "software-trivial" | "software-bugfix";
|
|
86
|
-
export declare function mandatoryAgentsFor(stage: FlowStage, track: FlowTrack, taskClass?: MandatoryDelegationTaskClass | null, complexityTier?: StageComplexityTier): string[];
|
|
86
|
+
export declare function mandatoryAgentsFor(stage: FlowStage, track: FlowTrack, taskClass?: MandatoryDelegationTaskClass | null, complexityTier?: StageComplexityTier, discoveryMode?: DiscoveryMode): string[];
|
|
87
87
|
/**
|
|
88
88
|
* Wave 25 (v6.1.0) — track-aware artifact validation demotion.
|
|
89
89
|
*
|
|
@@ -107,7 +107,7 @@ export declare function mandatoryAgentsFor(stage: FlowStage, track: FlowTrack, t
|
|
|
107
107
|
* `delegation-events.jsonl` once per stage advance for traceability.
|
|
108
108
|
*/
|
|
109
109
|
export declare function shouldDemoteArtifactValidationByTrack(track: FlowTrack, taskClass?: MandatoryDelegationTaskClass | null): boolean;
|
|
110
|
-
export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
|
|
110
|
+
export declare function stageSchema(stage: FlowStage, track?: FlowTrack, discoveryMode?: DiscoveryMode, taskClass?: MandatoryDelegationTaskClass | null): StageSchema;
|
|
111
111
|
export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
|
|
112
112
|
export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
113
113
|
export declare function stageRecommendedGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
@@ -81,6 +81,20 @@ function dedupeAgentsInOrder(agents) {
|
|
|
81
81
|
}
|
|
82
82
|
return out;
|
|
83
83
|
}
|
|
84
|
+
function discoveryModeTier(mode) {
|
|
85
|
+
if (mode === "lean")
|
|
86
|
+
return "lightweight";
|
|
87
|
+
if (mode === "deep")
|
|
88
|
+
return "deep";
|
|
89
|
+
return "standard";
|
|
90
|
+
}
|
|
91
|
+
function resolvedStageComplexityTier(params) {
|
|
92
|
+
const base = params.defaultTier ?? "standard";
|
|
93
|
+
const earlyStage = params.stage === "brainstorm" || params.stage === "scope" || params.stage === "design";
|
|
94
|
+
if (!earlyStage || params.discoveryMode === undefined)
|
|
95
|
+
return base;
|
|
96
|
+
return discoveryModeTier(params.discoveryMode);
|
|
97
|
+
}
|
|
84
98
|
function defaultReturnSchemaForAgent(agent) {
|
|
85
99
|
switch (agent) {
|
|
86
100
|
case "researcher":
|
|
@@ -811,12 +825,17 @@ export function mandatoryDelegationsForStage(stage, complexityTier = "standard")
|
|
|
811
825
|
.find((row) => row.stage === stage);
|
|
812
826
|
return summary ? summary.mandatoryAgents : [];
|
|
813
827
|
}
|
|
814
|
-
export function mandatoryAgentsFor(stage, track, taskClass, complexityTier = "standard") {
|
|
828
|
+
export function mandatoryAgentsFor(stage, track, taskClass, complexityTier = "standard", discoveryMode) {
|
|
815
829
|
if (track === "quick")
|
|
816
830
|
return [];
|
|
817
831
|
if (taskClass === "software-bugfix")
|
|
818
832
|
return [];
|
|
819
|
-
|
|
833
|
+
const effectiveTier = resolvedStageComplexityTier({
|
|
834
|
+
stage,
|
|
835
|
+
defaultTier: complexityTier,
|
|
836
|
+
discoveryMode
|
|
837
|
+
});
|
|
838
|
+
return mandatoryDelegationsForStage(stage, effectiveTier);
|
|
820
839
|
}
|
|
821
840
|
/**
|
|
822
841
|
* Wave 25 (v6.1.0) — track-aware artifact validation demotion.
|
|
@@ -847,7 +866,7 @@ export function shouldDemoteArtifactValidationByTrack(track, taskClass) {
|
|
|
847
866
|
return true;
|
|
848
867
|
return false;
|
|
849
868
|
}
|
|
850
|
-
export function stageSchema(stage, track = "standard") {
|
|
869
|
+
export function stageSchema(stage, track = "standard", discoveryMode, taskClass) {
|
|
851
870
|
const rawInput = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
|
|
852
871
|
const base = normalizeStageSchemaInput(rawInput);
|
|
853
872
|
const tieredGates = tieredStageGates(stage, base.requiredGates, track);
|
|
@@ -856,7 +875,11 @@ export function stageSchema(stage, track = "standard") {
|
|
|
856
875
|
...base.crossStageTrace,
|
|
857
876
|
readsFrom: readsFromForTrack(base.crossStageTrace.readsFrom, track)
|
|
858
877
|
};
|
|
859
|
-
const complexityTier =
|
|
878
|
+
const complexityTier = resolvedStageComplexityTier({
|
|
879
|
+
stage,
|
|
880
|
+
defaultTier: base.complexityTier ?? "standard",
|
|
881
|
+
discoveryMode
|
|
882
|
+
});
|
|
860
883
|
const mandatoryDelegations = mandatoryDelegationsForStage(stage, complexityTier);
|
|
861
884
|
const philosophy = {
|
|
862
885
|
hardGate: base.hardGate,
|