cclaw-cli 5.0.0 → 6.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.
@@ -24,7 +24,7 @@ export async function lintBrainstormStage(ctx) {
24
24
  findings.push({
25
25
  section: "qa_log_unconverged",
26
26
  required: !floor.skipQuestionsAdvisory,
27
- rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until forcing-question topics are addressed, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
27
+ rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until every forcing-question topic id is tagged with `[topic:<id>]` on at least one row, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
28
28
  found: floor.ok,
29
29
  details: floor.details
30
30
  });
@@ -224,7 +224,7 @@ export async function lintDesignStage(ctx) {
224
224
  findings.push({
225
225
  section: "qa_log_unconverged",
226
226
  required: !floor.skipQuestionsAdvisory,
227
- rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until forcing-question topics are addressed, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
227
+ rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until every forcing-question topic id is tagged with `[topic:<id>]` on at least one row, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
228
228
  found: floor.ok,
229
229
  details: floor.details
230
230
  });
@@ -25,7 +25,7 @@ export async function lintScopeStage(ctx) {
25
25
  findings.push({
26
26
  section: "qa_log_unconverged",
27
27
  required: !floor.skipQuestionsAdvisory,
28
- rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until forcing-question topics are addressed, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
28
+ rule: "[P1] qa_log_unconverged — Q&A Log has not converged for this stage. Continue elicitation until every forcing-question topic id is tagged with `[topic:<id>]` on at least one row, the last 2 rows produce no decision-changing impact (Ralph-Loop), or an explicit user stop-signal row is appended.",
29
29
  found: floor.ok,
30
30
  details: floor.details
31
31
  });
@@ -5,6 +5,20 @@ import { type FlowStage, type FlowTrack } from "../types.js";
5
5
  * convergence floor is enforced.
6
6
  */
7
7
  export declare const ELICITATION_STAGES: ReadonlySet<FlowStage>;
8
+ /**
9
+ * Wave 24 (v6.0.0) — language-neutral forcing-question topic descriptor.
10
+ *
11
+ * Each forcing-question row in a stage's checklist now declares topics as
12
+ * `id: human-readable label` pairs (e.g. `pain: what pain are we solving`).
13
+ * The `id` (kebab-case ASCII) is the machine-key authors stamp on Q&A Log
14
+ * rows via `[topic:<id>]` so the linter can verify coverage in ANY natural
15
+ * language (RU/EN/UA/etc.). Wave 23's English keyword fallback was removed
16
+ * because it silently mis-reported convergence on RU/UA Q&A.
17
+ */
18
+ export interface ForcingQuestionTopic {
19
+ id: string;
20
+ topic: string;
21
+ }
8
22
  export interface QaLogFloorOptions {
9
23
  /**
10
24
  * When true, downgrades the finding to advisory (`required: false`).
@@ -12,11 +26,12 @@ export interface QaLogFloorOptions {
12
26
  */
13
27
  skipQuestions?: boolean;
14
28
  /**
15
- * Optional pre-extracted forcing-question topics. When omitted, the
16
- * evaluator calls `extractForcingQuestions(stage)` which scans the
17
- * stage's checklist row.
29
+ * Optional pre-extracted forcing-question topic descriptors. When
30
+ * omitted, the evaluator calls `extractForcingQuestions(stage)` which
31
+ * scans the stage's checklist row. Strings are accepted as topic IDs
32
+ * (label = id) for callers that build their own list.
18
33
  */
19
- forcingQuestions?: string[];
34
+ forcingQuestions?: ReadonlyArray<ForcingQuestionTopic | string>;
20
35
  }
21
36
  export interface QaLogFloorResult {
22
37
  /** Whether convergence is satisfied (passes the gate). */
@@ -53,17 +68,30 @@ export interface QaLogFloorResult {
53
68
  details: string;
54
69
  }
55
70
  /**
56
- * Extract forcing-question topics from a stage's checklist. Looks for
57
- * the canonical `**<Stage> forcing questions (must be covered or
58
- * explicitly waived)** <topic1>, <topic2>, ...` row and tokenizes the
59
- * comma-separated topic list. Returns trimmed topic strings stripped of
60
- * leading question words (`what`/`who`/`where`/`which`/`how`/`is`/`do`/`does`).
71
+ * Parse a single checklist row into the list of forcing-question topic
72
+ * descriptors it declares. Returns `null` when the row is not a
73
+ * forcing-questions header. Throws when the header is found but its
74
+ * body does not match the Wave 24 `id: topic; id: topic; ...` syntax
75
+ * authors fix the stage definition rather than silently ship
76
+ * un-coverable topics.
77
+ *
78
+ * Exposed for unit tests that exercise the parser without depending on
79
+ * the live stage schema.
80
+ */
81
+ export declare function parseForcingQuestionsRow(row: string, context?: string): ForcingQuestionTopic[] | null;
82
+ /**
83
+ * Extract forcing-question topics from a stage's checklist.
84
+ *
85
+ * Wave 24 (v6.0.0): only the new `id: topic; id: topic; ...` syntax is
86
+ * accepted. Throws when the syntax is malformed so authors fix the
87
+ * stage definition rather than silently shipping un-coverable topics.
61
88
  *
62
89
  * Returns empty array when no forcing-questions row is present (caller
63
- * should treat absence as "no forcing requirement" — convergence falls
64
- * back to the no-new-decisions / stop-signal detectors).
90
+ * treats absence as "no forcing requirement" — convergence falls back
91
+ * to the no-new-decisions / stop-signal detectors). Returning [] when
92
+ * the row exists but lists no segments is also legal.
65
93
  */
66
- export declare function extractForcingQuestions(stage: FlowStage): string[];
94
+ export declare function extractForcingQuestions(stage: FlowStage): ForcingQuestionTopic[];
67
95
  /**
68
96
  * Evaluate the Q&A Log convergence floor for a brainstorm / scope /
69
97
  * design artifact. Returns ok=true when convergence is reached or any
@@ -86,15 +86,70 @@ function detectStopSignal(rows) {
86
86
  return false;
87
87
  }
88
88
  /**
89
- * Extract forcing-question topics from a stage's checklist. Looks for
90
- * the canonical `**<Stage> forcing questions (must be covered or
91
- * explicitly waived)** <topic1>, <topic2>, ...` row and tokenizes the
92
- * comma-separated topic list. Returns trimmed topic strings stripped of
93
- * leading question words (`what`/`who`/`where`/`which`/`how`/`is`/`do`/`does`).
89
+ * Validate the kebab-case ASCII shape of a forcing-question topic ID.
90
+ * Wave 24 enforces that IDs are short, language-neutral identifiers
91
+ * authors can paste into a `[topic:<id>]` tag without typos.
92
+ */
93
+ const TOPIC_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/u;
94
+ function isValidTopicId(id) {
95
+ return TOPIC_ID_PATTERN.test(id);
96
+ }
97
+ /**
98
+ * Parse a single checklist row into the list of forcing-question topic
99
+ * descriptors it declares. Returns `null` when the row is not a
100
+ * forcing-questions header. Throws when the header is found but its
101
+ * body does not match the Wave 24 `id: topic; id: topic; ...` syntax
102
+ * — authors fix the stage definition rather than silently ship
103
+ * un-coverable topics.
104
+ *
105
+ * Exposed for unit tests that exercise the parser without depending on
106
+ * the live stage schema.
107
+ */
108
+ export function parseForcingQuestionsRow(row, context = "row") {
109
+ const headerMatch = /\*\*\s*[A-Za-z]+\s+forcing\s+questions\s*\([^)]*\)\s*\*\*\s*(?:[—\-–:]+)?\s*(.+)/iu.exec(row);
110
+ if (!headerMatch)
111
+ return null;
112
+ const body = (headerMatch[1] ?? "").trim();
113
+ if (body.length === 0)
114
+ return [];
115
+ // Take everything up to the first sentence-ending `.` followed by a
116
+ // space + capital letter. We split on `;` only; commas are part of
117
+ // human labels. Authors stop the list with `.` so the trailing
118
+ // prose ("Tag the matching ...") is excluded.
119
+ const listSection = body.split(/\.\s+(?=[A-Z])/u)[0] ?? body;
120
+ const segments = listSection
121
+ .split(/;\s*/u)
122
+ .map((segment) => segment.trim())
123
+ .filter((segment) => segment.length > 0);
124
+ const topics = [];
125
+ for (const segment of segments) {
126
+ const match = /^[`*_]?\s*([A-Za-z0-9][A-Za-z0-9-]*)\s*[`*_]?\s*:\s*(.+?)\s*$/u.exec(segment);
127
+ if (!match) {
128
+ throw new Error(`parseForcingQuestionsRow(${context}): segment "${segment}" does not match required \`id: topic\` syntax. Wave 24 (v6.0.0) requires \`id: topic; id: topic; ...\` form.`);
129
+ }
130
+ const id = (match[1] ?? "").toLowerCase();
131
+ const topic = (match[2] ?? "").replace(/[`*_]+$/u, "").trim();
132
+ if (!isValidTopicId(id)) {
133
+ throw new Error(`parseForcingQuestionsRow(${context}): invalid topic id "${id}" in segment "${segment}". IDs must match ${TOPIC_ID_PATTERN.source}.`);
134
+ }
135
+ if (topic.length === 0) {
136
+ throw new Error(`parseForcingQuestionsRow(${context}): empty topic label after id "${id}" in segment "${segment}".`);
137
+ }
138
+ topics.push({ id, topic });
139
+ }
140
+ return topics;
141
+ }
142
+ /**
143
+ * Extract forcing-question topics from a stage's checklist.
144
+ *
145
+ * Wave 24 (v6.0.0): only the new `id: topic; id: topic; ...` syntax is
146
+ * accepted. Throws when the syntax is malformed so authors fix the
147
+ * stage definition rather than silently shipping un-coverable topics.
94
148
  *
95
149
  * Returns empty array when no forcing-questions row is present (caller
96
- * should treat absence as "no forcing requirement" — convergence falls
97
- * back to the no-new-decisions / stop-signal detectors).
150
+ * treats absence as "no forcing requirement" — convergence falls back
151
+ * to the no-new-decisions / stop-signal detectors). Returning [] when
152
+ * the row exists but lists no segments is also legal.
98
153
  */
99
154
  export function extractForcingQuestions(stage) {
100
155
  let checklist;
@@ -105,61 +160,29 @@ export function extractForcingQuestions(stage) {
105
160
  return [];
106
161
  }
107
162
  for (const row of checklist) {
108
- const headerMatch = /\*\*\s*[A-Za-z]+\s+forcing\s+questions\s*\([^)]*\)\s*\*\*\s*(?:[—\-–:]+)?\s*(.+)/iu.exec(row);
109
- if (!headerMatch)
163
+ const parsed = parseForcingQuestionsRow(row, `stage=${stage}`);
164
+ if (parsed === null)
110
165
  continue;
111
- const body = (headerMatch[1] ?? "")
112
- .replace(/\.$/u, "")
113
- .trim();
114
- if (body.length === 0)
115
- return [];
116
- return body
117
- .split(/,\s*(?:and\s+)?|\s+and\s+/iu)
118
- .map((topic) => topic.trim())
119
- .filter((topic) => topic.length > 0)
120
- .map((topic) => topic
121
- .replace(/^[*_`]+|[*_`]+$/gu, "")
122
- .replace(/^(?:what|who|where|which|how|is|are|do|does|did|can|will|would|could|should|may|might)\s+/iu, "")
123
- .replace(/\?+$/u, "")
124
- .trim())
125
- .filter((topic) => topic.length > 0);
166
+ return parsed;
126
167
  }
127
168
  return [];
128
169
  }
129
170
  /**
130
- * Build a salient-keyword set for a forcing-question topic. Splits on
131
- * whitespace, drops short/stop words, lowercases. Used for fuzzy
132
- * substring match against Q&A Log row content.
171
+ * Detect whether a Q&A Log row carries an explicit `[topic:<id>]` tag
172
+ * for the requested forcing-topic id. Matching is case-insensitive on
173
+ * the id, ASCII-only on the tag boundary. NO keyword fallback: the user
174
+ * must stamp the tag in any cell of the row.
133
175
  */
134
- function topicKeywords(topic) {
135
- const STOP_WORDS = new Set([
136
- "the", "a", "an", "is", "are", "was", "were", "be", "to", "of", "in", "on", "at",
137
- "for", "and", "or", "but", "if", "then", "else", "with", "without", "by", "as",
138
- "we", "us", "our", "they", "them", "their", "you", "your", "i", "me", "my",
139
- "this", "that", "these", "those", "it", "its", "do", "does", "did", "can",
140
- "will", "would", "should", "could", "may", "might", "any", "some", "no", "not",
141
- "from", "into", "onto", "upon", "than", "very", "much", "many", "more", "most",
142
- "must", "have", "has", "had", "been", "being", "where", "when", "while",
143
- "what", "which", "who", "whose", "whom", "why", "how", "non"
144
- ]);
145
- return topic
146
- .toLowerCase()
147
- .split(/[\s\-/.,;:()\[\]{}'"`*_]+/u)
148
- .map((token) => token.replace(/[^\p{L}\p{N}-]/gu, ""))
149
- .filter((token) => token.length >= 3 && !STOP_WORDS.has(token));
150
- }
151
- function isTopicAddressed(topic, rows) {
152
- const keywords = topicKeywords(topic);
153
- if (keywords.length === 0)
154
- return true;
155
- const minHits = keywords.length === 1 ? 1 : Math.min(2, keywords.length);
176
+ function isTopicAddressed(id, rows) {
177
+ const needle = id.toLowerCase();
178
+ const tagPattern = /\[topic:\s*([A-Za-z0-9][A-Za-z0-9-]*)\s*\]/giu;
156
179
  for (const row of rows) {
157
- const haystack = row.join(" | ").toLowerCase();
158
- let hits = 0;
159
- for (const keyword of keywords) {
160
- if (haystack.includes(keyword))
161
- hits += 1;
162
- if (hits >= minHits)
180
+ const haystack = row.join(" | ");
181
+ tagPattern.lastIndex = 0;
182
+ let match;
183
+ while ((match = tagPattern.exec(haystack)) !== null) {
184
+ const candidate = (match[1] ?? "").toLowerCase();
185
+ if (candidate === needle)
163
186
  return true;
164
187
  }
165
188
  }
@@ -210,18 +233,21 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
210
233
  const count = substantiveRows.length;
211
234
  const hasStopSignal = detectStopSignal(rows);
212
235
  const skipQuestionsAdvisory = options.skipQuestions === true;
213
- const forcingTopics = options.forcingQuestions ?? extractForcingQuestions(stage);
236
+ const forcingTopics = (options.forcingQuestions ?? extractForcingQuestions(stage)).map((entry) => (typeof entry === "string" ? { id: entry, topic: entry } : entry));
214
237
  const forcingCovered = [];
215
238
  const forcingPending = [];
216
239
  for (const topic of forcingTopics) {
217
- if (isTopicAddressed(topic, rows))
218
- forcingCovered.push(topic);
240
+ if (isTopicAddressed(topic.id, rows))
241
+ forcingCovered.push(topic.id);
219
242
  else
220
- forcingPending.push(topic);
243
+ forcingPending.push(topic.id);
221
244
  }
222
245
  const noNewDecisions = lastTwoRowsAllNoDecision(substantiveRows);
223
246
  const allForcingCovered = forcingTopics.length > 0 ? forcingPending.length === 0 : count >= 1;
224
247
  const ok = allForcingCovered || noNewDecisions || hasStopSignal;
248
+ const pendingIdsBracket = forcingPending.length > 0
249
+ ? `[${forcingPending.join(", ")}]`
250
+ : "[none]";
225
251
  let details;
226
252
  if (ok) {
227
253
  if (allForcingCovered && forcingTopics.length > 0) {
@@ -232,7 +258,7 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
232
258
  }
233
259
  else if (noNewDecisions) {
234
260
  const remaining = forcingPending.length > 0
235
- ? ` ${forcingPending.length} forcing topic(s) still pending but last 2 rows produced no decision changes (Ralph-Loop convergence).`
261
+ ? ` ${forcingPending.length} forcing topic IDs still pending: ${pendingIdsBracket} (Ralph-Loop convergence overrode coverage).`
236
262
  : " Ralph-Loop convergence detector says no new decision-changing rows in the last 2 turns.";
237
263
  details = `Q&A Log converged via no-new-decisions detector at ${count} row(s).${remaining}`;
238
264
  }
@@ -241,10 +267,10 @@ export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
241
267
  }
242
268
  }
243
269
  else if (skipQuestionsAdvisory) {
244
- details = `Q&A Log unconverged at ${count} row(s); --skip-questions flag downgraded the finding to advisory. Pending forcing topic(s): ${forcingPending.length > 0 ? forcingPending.join("; ") : "(none extracted)"}.`;
270
+ details = `Q&A Log unconverged at ${count} row(s); --skip-questions flag downgraded the finding to advisory. Forcing topic IDs pending: ${pendingIdsBracket}.`;
245
271
  }
246
272
  else {
247
- details = `Q&A Log unconverged at ${count} row(s). Continue the elicitation loop until forcing-question topics are addressed (${forcingPending.length > 0 ? forcingPending.join("; ") : "no forcing topics extracted"}), the last 2 rows record no-decision impact, or an explicit user stop-signal row is appended.`;
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, append a no-new-decisions pair, or record an explicit user stop-signal row.`;
248
274
  }
249
275
  // Surface advisory budget hint for harness UI without re-introducing a
250
276
  // blocking count. `recommended` is the soft budget per track/stage.
@@ -47,7 +47,7 @@ These behaviors are the exact reason this skill exists. The linter will block yo
47
47
  - Ask exactly one question per turn and wait for the answer before asking the next one.
48
48
  - Use harness-native question tools first; prose fallback is allowed only when the tool is unavailable.
49
49
  - 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. Convergence is reached when ANY of: (a) all forcing-question topics are addressed in \`## Q&A Log\`, (b) the last 2 substantive rows produce no decision-changing impact (\`skip\`/\`continue\`/\`no-change\`/\`done\`), or (c) an explicit user stop-signal row is recorded. The linter rule \`qa_log_unconverged\` enforces this; \`stage-complete\` will fail otherwise. Wave 23 (v5.0.0) replaced the fixed-count floor with this convergence detector.
50
+ - **Convergence floor**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. Convergence is reached when ANY of: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row, (b) the last 2 substantive rows produce no decision-changing impact (\`skip\`/\`continue\`/\`no-change\`/\`done\`), or (c) an explicit user stop-signal row is recorded. The linter rule \`qa_log_unconverged\` enforces this; \`stage-complete\` will fail otherwise. Wave 24 (v6.0.0) made the topic tag MANDATORY (no English keyword fallback) so the gate works in any natural language.
51
51
  - **NEVER run shell hash commands** (\`shasum\`, \`sha256sum\`, \`md5sum\`, \`Get-FileHash\`, \`certutil\`, etc.) to compute artifact hashes. If a linter ever asks you for a hash, that is a linter bug — report failure and stop, do not auto-fix in bash.
52
52
  - **NEVER paste cclaw command lines into chat** (e.g. \`node .cclaw/hooks/stage-complete.mjs ... --evidence-json '{...}'\`). Run them via the tool layer; report only the resulting summary. The user does not run cclaw manually and seeing the command line is noise.
53
53
 
@@ -127,24 +127,38 @@ How to use the columns:
127
127
  - \`skipped (already covered: turn N)\` — answered implicitly by an earlier reply; cite the turn.
128
128
  - \`waived (user override)\` — user explicitly waived this question.
129
129
 
130
- Stage forcing question lists:
130
+ ### Topic tagging (MANDATORY for forcing-question rows)
131
+
132
+ Each forcing question has a stable topic id (kebab-case ASCII, e.g. \`pain\`, \`do-nothing\`, \`data-flow\`). Tag the matching Q&A Log row's \`Decision impact\` cell with \`[topic:<id>]\` so the linter can verify coverage in any natural language. This is a **HARD requirement** in Wave 24 (v6.0.0): the linter no longer keyword-matches English question prose, so an un-tagged row does NOT count toward coverage even if the answer fully addresses the topic.
133
+
134
+ RU example (after asking \`pain\` in Russian):
135
+
136
+ \`\`\`
137
+ | Turn | Question | User answer (1-line) | Decision impact |
138
+ |---|---|---|---|
139
+ | 1 | Какую боль мы решаем? | Регистрация занимает 30 минут. | scope-shaping [topic:pain] |
140
+ \`\`\`
141
+
142
+ Multiple tags in one row are allowed when one answer covers several topics: \`[topic:pain] [topic:do-nothing]\`. Stop-signal rows do NOT need a tag.
143
+
144
+ Stage forcing question lists (id → topic):
131
145
 
132
146
  - **Brainstorm**:
133
- - What pain are we solving?
134
- - What is the most direct path?
135
- - What happens if we do nothing?
136
- - Who is the operator/user impacted first?
137
- - What are non-negotiable no-go boundaries?
147
+ - \`pain\` — What pain are we solving?
148
+ - \`direct-path\` — What is the most direct path?
149
+ - \`do-nothing\` — What happens if we do nothing?
150
+ - \`operator\` — Who is the operator/user impacted first?
151
+ - \`no-go\` — What are non-negotiable no-go boundaries?
138
152
  - **Scope**:
139
- - What is definitely in and definitely out?
140
- - Which decisions are already locked upstream?
141
- - What is the rollback path if this fails?
142
- - What are the top failure modes we must design for?
153
+ - \`in-out\` — What is definitely in and definitely out?
154
+ - \`locked-upstream\` — Which decisions are already locked upstream?
155
+ - \`rollback\` — What is the rollback path if this fails?
156
+ - \`failure-modes\` — What are the top failure modes we must design for?
143
157
  - **Design**:
144
- - What is the data flow end-to-end?
145
- - Where are the seams/interfaces and ownership boundaries?
146
- - Which invariants must always hold?
147
- - What will we explicitly NOT refactor now?
158
+ - \`data-flow\` — What is the data flow end-to-end?
159
+ - \`seams\` — Where are the seams/interfaces and ownership boundaries?
160
+ - \`invariants\` — Which invariants must always hold?
161
+ - \`not-refactor\` — What will we explicitly NOT refactor now?
148
162
 
149
163
  ## One-Way Override (Irreversible Decisions)
150
164
 
@@ -360,10 +360,16 @@ function mergedAntiPatterns(philosophy, execution) {
360
360
  }
361
361
  function completionParametersBlock(schema, track) {
362
362
  const gateList = schema.executionModel.requiredGates.map((g) => `\`${g.id}\``).join(", ");
363
- const mandatoryAgents = schema.reviewLens.mandatoryDelegations;
364
- const mandatory = schema.reviewLens.mandatoryDelegations.length > 0
365
- ? schema.reviewLens.mandatoryDelegations.map((a) => `\`${a}\``).join(", ")
366
- : "none";
363
+ // Wave 24 (v6.0.0): mandatory agents are dropped on `quick` track. Surface
364
+ // the empty list so the rendered SKILL.md doesn't tell quick-track runs to
365
+ // dispatch agents the linter is going to skip.
366
+ const trackAwareMandatoryAgents = track === "quick" ? [] : schema.reviewLens.mandatoryDelegations;
367
+ const mandatoryAgents = trackAwareMandatoryAgents;
368
+ const mandatory = trackAwareMandatoryAgents.length > 0
369
+ ? trackAwareMandatoryAgents.map((a) => `\`${a}\``).join(", ")
370
+ : track === "quick" && schema.reviewLens.mandatoryDelegations.length > 0
371
+ ? "none (skipped: quick track — Wave 24)"
372
+ : "none";
367
373
  const resolvedNextStage = nextStageForTrack(schema.stage, track);
368
374
  const nextStage = resolvedNextStage ?? "done";
369
375
  const nextDescription = nextStage === "done"
@@ -55,6 +55,35 @@ export declare function validateSkillEnvelope(value: unknown): SkillEnvelopeVali
55
55
  export declare function parseSkillEnvelope(raw: string): SkillEnvelope | null;
56
56
  /** Transition guard: agents with `mode: "mandatory"` in auto-subagent dispatch for this stage. */
57
57
  export declare function mandatoryDelegationsForStage(stage: FlowStage, complexityTier?: StageComplexityTier): string[];
58
+ /**
59
+ * Wave 24 (v6.0.0) — track-aware mandatory delegation lookup.
60
+ *
61
+ * Returns `[]` (skip the gate entirely) when the run is on a small-fix
62
+ * track or classified as a software bugfix:
63
+ *
64
+ * - `track === "quick"` — the quick track is for trivial single-purpose
65
+ * fixes (landing-page copy, doc edits, config tweaks). Mandatory
66
+ * subagent dispatch is theatre on that surface area.
67
+ * - `taskClass === "software-bugfix"` — bugfixes carry a RED-first
68
+ * repro contract; the test author + reviewer in the tdd/review
69
+ * stages already cover the safety surface, so mandatory upstream
70
+ * delegation only burns tokens.
71
+ *
72
+ * Otherwise returns the registered mandatory list for the stage at the
73
+ * given tier. Callers (gate-evidence, advance-stage validator,
74
+ * subagents.ts table generator) MUST go through this helper instead of
75
+ * `mandatoryDelegationsForStage` so the track-aware drop applies
76
+ * uniformly.
77
+ *
78
+ * NOTE: the user query also calls this `lite/quick`. There is no `lite`
79
+ * FlowTrack — the closest concept in cclaw is the `quick` track plus the
80
+ * brainstorm `lightweight` complexity tier. We key on the FlowTrack
81
+ * because the run-level decision is what matters at gate time;
82
+ * complexity tier is a per-stage knob that doesn't survive the dispatch
83
+ * boundary.
84
+ */
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[];
58
87
  export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
59
88
  export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
60
89
  export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
@@ -811,6 +811,13 @@ export function mandatoryDelegationsForStage(stage, complexityTier = "standard")
811
811
  .find((row) => row.stage === stage);
812
812
  return summary ? summary.mandatoryAgents : [];
813
813
  }
814
+ export function mandatoryAgentsFor(stage, track, taskClass, complexityTier = "standard") {
815
+ if (track === "quick")
816
+ return [];
817
+ if (taskClass === "software-bugfix")
818
+ return [];
819
+ return mandatoryDelegationsForStage(stage, complexityTier);
820
+ }
814
821
  export function stageSchema(stage, track = "standard") {
815
822
  const rawInput = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
816
823
  const base = normalizeStageSchemaInput(rawInput);
@@ -36,9 +36,9 @@ export const BRAINSTORM = {
36
36
  },
37
37
  executionModel: {
38
38
  checklist: [
39
- "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer. Continue until forcing-questions converge (all answered/skipped/waived) OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to delegations, drafts, or analysis. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
39
+ "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:pain]`). Continue until every forcing-question topic id is tagged on a row OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to delegations, drafts, or analysis. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
40
40
  "**Explore project context** — after the elicitation loop converges, inspect existing files/docs/recent activity to refine the Discovered context section; capture matching files/patterns/seeds in `Context > Discovered context` so downstream stages don't redo discovery.",
41
- "**Brainstorm forcing questions (must be covered or explicitly waived)** — what pain are we solving, what is the direct path, what happens if we do nothing, who is the first operator/user affected, and what no-go boundaries are non-negotiable.",
41
+ "**Brainstorm forcing questions (must be covered or explicitly waived)** — `pain: what pain are we solving`; `direct-path: what is the direct path`; `do-nothing: what happens if we do nothing`; `operator: who is the first operator/user affected`; `no-go: what no-go boundaries are non-negotiable`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:pain]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage.",
42
42
  "**Classify stage depth** — choose `lite` for clear low-risk tasks, `standard` for normal engineering/product changes, or `deep` for ambiguity, architecture, external dependency, security/data risk, or explicit think-bigger requests.",
43
43
  "**Write the Problem Decision Record** — pick a free-form `Frame type` label that names how this work is framed (examples: product, technical-maintenance, research-spike, ops-incident, infrastructure), then fill the universal Framing fields: affected user/role/operator, current state/failure mode/opportunity, desired observable outcome, evidence/signal, why now, do-nothing consequence, and non-goals.",
44
44
  "**Premise check (one pass)** — answer the three gstack-style questions in the artifact body: *Right problem? Direct path? What if we do nothing?* Take a position; do not hedge.",
@@ -41,8 +41,8 @@ export const DESIGN = {
41
41
  },
42
42
  executionModel: {
43
43
  checklist: [
44
- "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer. Continue until forcing-questions converge (all answered/skipped/waived) OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to research, investigator pass, architecture lock, or any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
45
- "**Design forcing questions (must be covered or explicitly waived)** — what is the end-to-end data flow, where are seams/ownership boundaries, which invariants must hold, and what will explicitly NOT be refactored now.",
44
+ "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:data-flow]`). Continue until every forcing-question topic id is tagged on a row OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to research, investigator pass, architecture lock, or any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
45
+ "**Design forcing questions (must be covered or explicitly waived)** — `data-flow: what is the end-to-end data flow`; `seams: where are seams/ownership boundaries`; `invariants: which invariants must hold`; `not-refactor: what will explicitly NOT be refactored now`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:data-flow]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage.",
46
46
  "**Out-of-scope carry-forward (do NOT re-author)** — scope OWNS the out-of-scope list. Cite scope's `## In Scope / Out of Scope > Out of Scope` via `## Upstream Handoff > Decisions carried forward`; do NOT add a separate `## NOT in scope` section in the design artifact. Add a row to `## Spec Handoff` only if a design-stage decision NEWLY excludes something not already in scope's out-of-scope.",
47
47
  "Compact design lock — design does not decide what to build; it decides how the approved scope works. For simple slices, produce a tight lock: upstream handoff, existing fit, architecture boundary, one labeled diagram, data/state flow, critical path, failure/rescue, trust boundaries, test/perf expectations, rollout/rollback, rejected alternative, and spec handoff.",
48
48
  "Trivial-Change Escape Hatch — for <=3 files, no new interfaces, and no cross-module data flow, produce a mini-design (rationale, changed files, one risk) and proceed to spec.",
@@ -46,8 +46,8 @@ export const SCOPE = {
46
46
  },
47
47
  executionModel: {
48
48
  checklist: [
49
- "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer. Continue until forcing-questions converge (all answered/skipped/waived) OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then propose the scope contract draft, recommend a mode, or dispatch any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
50
- "**Scope forcing questions (must be covered or explicitly waived)** — what is definitely in/out, which upstream decisions are locked, and what rollback path protects users if scope assumptions fail.",
49
+ "**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:in-out]`). Continue until every forcing-question topic id is tagged on a row OR Ralph-Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then propose the scope contract draft, recommend a mode, or dispatch any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
50
+ "**Scope forcing questions (must be covered or explicitly waived)** — `in-out: what is definitely in/out`; `locked-upstream: which upstream decisions are locked`; `rollback: what rollback path protects users if scope assumptions fail`; `failure-modes: what are the top failure modes we must design for`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:in-out]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage.",
51
51
  "**Scope contract first** — read brainstorm handoff, name upstream decisions used, explicit drift, confidence, unresolved questions, and next-stage risk hints; draft the in-scope/out-of-scope/deferred/discretion contract before any design choice.",
52
52
  "**Premise carry-forward (do NOT re-author)** — brainstorm OWNS the premise check (right problem / direct path / what if nothing). Cite brainstorm's `## Premise Check` section in `## Upstream Handoff > Decisions carried forward`. Add a row to `## Premise Drift` only when the scope-stage Q&A surfaced NEW evidence that materially changes the brainstorm answer (e.g. new constraint, new user signal). Otherwise mark `Premise Drift: None` — do not duplicate the brainstorm premise table.",
53
53
  "**Conditional 10-star boundary** — for deep/high-risk/product-strategy work, show what would make the product meaningfully better, then explicitly choose what ships now, what is deferred, and what is excluded without vague `later/for now` placeholders. Skip this for straightforward repair work and record `not needed: compact scope`.",
@@ -16,7 +16,9 @@ function automaticStageDelegationTable() {
16
16
  }).join("\n");
17
17
  return `| Stage | Mandatory agents | Proactive agents |
18
18
  |---|---|---|
19
- ${rows}`;
19
+ ${rows}
20
+
21
+ > **Track-aware skip (Wave 24, v6.0.0):** mandatory agents are skipped entirely when \`track === "quick"\` OR \`taskClass === "software-bugfix"\`. Use \`mandatoryAgentsFor(stage, track, taskClass)\` from \`src/content/stage-schema.ts\` for the authoritative list at runtime. Proactive agents stay enforced because they fire only on triggers (high blast radius, security-sensitive paths, etc.), not on every run.`;
20
22
  }
21
23
  function stageSummary(stage) {
22
24
  return stageDelegationSummary("standard").find((row) => row.stage === stage)
@@ -6,6 +6,6 @@ export declare const RULEBOOK_MARKDOWN = "# Cclaw Rulebook\n\n## MUST_ALWAYS\n-
6
6
  * loading skills. Three hard rules cover the most common Wave 22 regressions
7
7
  * (premature draft, premature subagent dispatch, command-line echo to chat).
8
8
  */
9
- export declare const CURSOR_GUIDELINES_RULE_MDC = "---\ndescription: cclaw zero-install behavior baseline (always-on)\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-guidelines-rule -->\n\n# Cclaw Baseline Guidelines\n\nThese three rules apply to every Cursor agent session in this project,\nregardless of whether stage skills loaded.\n\n## 1. Q&A floor before drafting (brainstorm/scope/design)\n\nBefore drafting any `.cclaw/artifacts/01-brainstorm-*.md`,\n`02-scope-*.md`, or `03-design-*.md`, verify that the artifact's\n`## Q&A Log` table demonstrates Ralph-Loop convergence: forcing-question\ntopics are addressed (see the stage's forcing-questions checklist row),\nthe last 2 turns produce no new decision-changing impact, OR an explicit\nuser stop-signal row is recorded. Walk the stage forcing questions one at\na time via the `AskQuestion` tool. If you find yourself proposing a\ndraft after 1-2 questions while forcing topics remain unaddressed, STOP\nand continue the loop.\n\nThe `qa_log_unconverged` linter rule will block `stage-complete` when\nconvergence has not been reached.\n\n## 2. Mandatory subagents run after Q&A approval\n\nFor brainstorm / scope / design, mandatory subagents (\n`product-discovery`, `critic`, `planner`, `architect`,\n`test-author`) run **only AFTER the user approves the elicitation\noutcome**, never before the Q&A loop converges. Dispatching them early\npreempts the user dialogue and violates the elicitation contract \u2014 the\nlinter will block stage-complete.\n\nSee each stage's \"Run Phase: post-elicitation\" rows in the materialized\nAutomatic Subagent Dispatch table.\n\n## 3. Never echo cclaw command lines to chat\n\nThe user does not run cclaw helpers (`node .cclaw/hooks/...`) manually.\nNEVER paste full command lines, `--evidence-json '{...}'` payloads,\n`--waive-delegation=...`, or shell hash commands (`shasum`,\n`sha256sum`, `Get-FileHash`, `certutil`, etc.) into chat. Run the\nhelper via the tool layer and report only the resulting summary. On\nfailure, report a compact human-readable summary plus the helper JSON in\na single fenced `json` block.\n";
10
- export declare const CURSOR_WORKFLOW_RULE_MDC = "---\ndescription: cclaw workflow guardrails for Cursor agent sessions\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-workflow-rule -->\n\n# Cclaw Workflow Guardrails\n\n## Activation Rule\n\nBefore responding to coding work:\n1. Read `.cclaw/state/flow-state.json`.\n2. Start with `/cc` or continue with `/cc`.\n3. If no software-stage flow applies, respond normally.\n\n## Stage Order\n\n`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship`\n\nTrack-specific skips are allowed only when `flow-state.track` + `skippedStages` explicitly say so.\n\n## Task Classification\n\n| Class | Route |\n|---|---|\n| non-trivial software work | `/cc <idea>` |\n| trivial software fix | `/cc <idea>` (quick track) |\n| bugfix with repro | `/cc <idea>` and enforce RED-first in tdd |\n| pure question / non-software | direct answer (no stage flow) |\n\n## Command Surface\n\n- `/cc` = entry and resume.\n- `/cc` = only progression path.\n- Knowledge capture and recall use the `learnings` skill when requested.\n\n## Verification Discipline\n\n- No completion claim without fresh command evidence in this turn.\n- Do not mark gates passed from memory.\n- Keep evidence in `.cclaw/artifacts/`; archive through closeout via `/cc` or cancel early via `node .cclaw/hooks/cancel-run.mjs`.\n\n## Delegation And Approvals\n\n- Machine-only checks in design/plan/tdd/review/ship should auto-dispatch when tooling supports it.\n- **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool \u2014 `AskQuestion` in Cursor). Walk the stage forcing-questions list one-by-one. Do NOT batch and do NOT defer to a single approval gate at the end. The `qa_log_unconverged` linter rule will block `stage-complete` when convergence is not reached (forcing topics unaddressed AND last 2 turns still produce decision-changing rows AND no stop-signal).\n- **For other stages** (spec/plan/tdd/build/review/ship): ask user input only at explicit approval gates (scope mode, plan approval, challenge resolution, ship finalization), not for routine progress updates.\n- If you find yourself proposing a draft after 1-2 questions in brainstorm/scope/design, STOP \u2014 go back to the forcing-questions list and continue.\n- Mandatory subagents in brainstorm/scope/design run only AFTER the user approves the elicitation outcome (see each stage's \"Run Phase: post-elicitation\" rows). Dispatching them before the Q&A loop converges violates the contract.\n- Never echo cclaw command lines (`node .cclaw/hooks/...`, `--evidence-json '{...}'`) to chat \u2014 the user does not run cclaw manually. Run helpers via the tool layer; report only the resulting summary.\n- If harness capabilities are partial, record waiver reasons in delegation logs.\n\n## Routing Source Of Truth\n\n- Primary router: `.cclaw/skills/using-cclaw/SKILL.md`.\n- Stage behavior: current stage skill plus `.cclaw/state/flow-state.json`.\n- Preamble budget: keep role/status announcements brief and avoid repeating\n them unless the stage or role changes.\n";
9
+ export declare const CURSOR_GUIDELINES_RULE_MDC = "---\ndescription: cclaw zero-install behavior baseline (always-on)\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-guidelines-rule -->\n\n# Cclaw Baseline Guidelines\n\nThese three rules apply to every Cursor agent session in this project,\nregardless of whether stage skills loaded.\n\n## 1. Q&A floor before drafting (brainstorm/scope/design)\n\nBefore drafting any `.cclaw/artifacts/01-brainstorm-*.md`,\n`02-scope-*.md`, or `03-design-*.md`, verify that the artifact's\n`## Q&A Log` table demonstrates Ralph-Loop convergence: every\nforcing-question topic id is tagged `[topic:<id>]` on at least one row\n(see the stage's forcing-questions checklist for the id list), the last\n2 turns produce no new decision-changing impact, OR an explicit user\nstop-signal row is recorded. Walk the stage forcing questions one at a\ntime via the `AskQuestion` tool. If you find yourself proposing a\ndraft after 1-2 questions while forcing topic ids remain untagged, STOP\nand continue the loop.\n\nThe `qa_log_unconverged` linter rule will block `stage-complete` when\nconvergence has not been reached. Wave 24 (v6.0.0) made `[topic:<id>]`\ntagging mandatory; the English keyword fallback was removed because it\nmis-reported convergence on RU/UA Q&A logs.\n\n## 2. Mandatory subagents run after Q&A approval\n\nFor brainstorm / scope / design, mandatory subagents (\n`product-discovery`, `critic`, `planner`, `architect`,\n`test-author`) run **only AFTER the user approves the elicitation\noutcome**, never before the Q&A loop converges. Dispatching them early\npreempts the user dialogue and violates the elicitation contract \u2014 the\nlinter will block stage-complete.\n\nSee each stage's \"Run Phase: post-elicitation\" rows in the materialized\nAutomatic Subagent Dispatch table.\n\n## 3. Never echo cclaw command lines to chat\n\nThe user does not run cclaw helpers (`node .cclaw/hooks/...`) manually.\nNEVER paste full command lines, `--evidence-json '{...}'` payloads,\n`--waive-delegation=...`, or shell hash commands (`shasum`,\n`sha256sum`, `Get-FileHash`, `certutil`, etc.) into chat. Run the\nhelper via the tool layer and report only the resulting summary. On\nfailure, report a compact human-readable summary plus the helper JSON in\na single fenced `json` block.\n";
10
+ export declare const CURSOR_WORKFLOW_RULE_MDC = "---\ndescription: cclaw workflow guardrails for Cursor agent sessions\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-workflow-rule -->\n\n# Cclaw Workflow Guardrails\n\n## Activation Rule\n\nBefore responding to coding work:\n1. Read `.cclaw/state/flow-state.json`.\n2. Start with `/cc` or continue with `/cc`.\n3. If no software-stage flow applies, respond normally.\n\n## Stage Order\n\n`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship`\n\nTrack-specific skips are allowed only when `flow-state.track` + `skippedStages` explicitly say so.\n\n## Task Classification\n\n| Class | Route |\n|---|---|\n| non-trivial software work | `/cc <idea>` |\n| trivial software fix | `/cc <idea>` (quick track) |\n| bugfix with repro | `/cc <idea>` and enforce RED-first in tdd |\n| pure question / non-software | direct answer (no stage flow) |\n\n## Command Surface\n\n- `/cc` = entry and resume.\n- `/cc` = only progression path.\n- Knowledge capture and recall use the `learnings` skill when requested.\n\n## Verification Discipline\n\n- No completion claim without fresh command evidence in this turn.\n- Do not mark gates passed from memory.\n- Keep evidence in `.cclaw/artifacts/`; archive through closeout via `/cc` or cancel early via `node .cclaw/hooks/cancel-run.mjs`.\n\n## Delegation And Approvals\n\n- Machine-only checks in design/plan/tdd/review/ship should auto-dispatch when tooling supports it.\n- **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool \u2014 `AskQuestion` in Cursor). Walk the stage forcing-questions list one-by-one. **Tag each Q&A Log row's `Decision impact` cell with `[topic:<id>]`** (the id is given in the stage's forcing-questions checklist) so the linter can verify coverage in any natural language. Do NOT batch and do NOT defer to a single approval gate at the end. The `qa_log_unconverged` linter rule will block `stage-complete` when convergence is not reached (forcing topic ids untagged AND last 2 turns still produce decision-changing rows AND no stop-signal).\n- **For other stages** (spec/plan/tdd/build/review/ship): ask user input only at explicit approval gates (scope mode, plan approval, challenge resolution, ship finalization), not for routine progress updates.\n- If you find yourself proposing a draft after 1-2 questions in brainstorm/scope/design, STOP \u2014 go back to the forcing-questions list and continue.\n- Mandatory subagents in brainstorm/scope/design run only AFTER the user approves the elicitation outcome (see each stage's \"Run Phase: post-elicitation\" rows). Dispatching them before the Q&A loop converges violates the contract.\n- Never echo cclaw command lines (`node .cclaw/hooks/...`, `--evidence-json '{...}'`) to chat \u2014 the user does not run cclaw manually. Run helpers via the tool layer; report only the resulting summary.\n- If harness capabilities are partial, record waiver reasons in delegation logs.\n\n## Routing Source Of Truth\n\n- Primary router: `.cclaw/skills/using-cclaw/SKILL.md`.\n- Stage behavior: current stage skill plus `.cclaw/state/flow-state.json`.\n- Preamble budget: keep role/status announcements brief and avoid repeating\n them unless the stage or role changes.\n";
11
11
  export declare function buildRulesJson(): Record<string, unknown>;
@@ -86,9 +86,10 @@ export const ARTIFACT_TEMPLATES = {
86
86
  ## Q&A Log
87
87
  | Turn | Question | User answer (1-line) | Decision impact |
88
88
  |---|---|---|---|
89
- | 1 | | | |
89
+ | 1 | | | scope-shaping [topic:pain] |
90
90
 
91
91
  > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
92
+ > **Topic tag is MANDATORY for forcing-question rows.** Stamp \`[topic:<id>]\` in the \`Decision impact\` cell so the linter can verify coverage in any natural language (RU/EN/UA/etc.). Brainstorm IDs: \`pain\`, \`direct-path\`, \`do-nothing\`, \`operator\`, \`no-go\`. Multiple tags allowed when one answer covers several topics. Stop-signal rows do NOT need a tag. Wave 24 (v6.0.0) removed the English keyword fallback.
92
93
 
93
94
  ## Approach Tier
94
95
  - Tier: lite | standard | deep
@@ -209,9 +210,10 @@ ${MARKDOWN_CODE_FENCE}
209
210
  ## Q&A Log
210
211
  | Turn | Question | User answer (1-line) | Decision impact |
211
212
  |---|---|---|---|
212
- | 1 | | | |
213
+ | 1 | | | scope-shaping [topic:in-out] |
213
214
 
214
215
  > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
216
+ > **Topic tag is MANDATORY for forcing-question rows.** Stamp \`[topic:<id>]\` in the \`Decision impact\` cell so the linter can verify coverage in any natural language (RU/EN/UA/etc.). Scope IDs: \`in-out\`, \`locked-upstream\`, \`rollback\`, \`failure-modes\`. Multiple tags allowed when one answer covers several topics. Stop-signal rows do NOT need a tag. Wave 24 (v6.0.0) removed the English keyword fallback.
215
217
 
216
218
  ## Pre-Scope System Audit
217
219
  | Check | Command | Findings |
@@ -447,9 +449,10 @@ ${MARKDOWN_CODE_FENCE}
447
449
  ## Q&A Log
448
450
  | Turn | Question | User answer (1-line) | Decision impact |
449
451
  |---|---|---|---|
450
- | 1 | | | |
452
+ | 1 | | | architecture-shaping [topic:data-flow] |
451
453
 
452
454
  > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
455
+ > **Topic tag is MANDATORY for forcing-question rows.** Stamp \`[topic:<id>]\` in the \`Decision impact\` cell so the linter can verify coverage in any natural language (RU/EN/UA/etc.). Design IDs: \`data-flow\`, \`seams\`, \`invariants\`, \`not-refactor\`. Multiple tags allowed when one answer covers several topics. Stop-signal rows do NOT need a tag. Wave 24 (v6.0.0) removed the English keyword fallback.
453
456
 
454
457
  ## Codebase Investigation
455
458
  | File | Current responsibility | Patterns discovered | Existing fit / reuse candidate |
@@ -1484,16 +1487,19 @@ regardless of whether stage skills loaded.
1484
1487
 
1485
1488
  Before drafting any \`.cclaw/artifacts/01-brainstorm-*.md\`,
1486
1489
  \`02-scope-*.md\`, or \`03-design-*.md\`, verify that the artifact's
1487
- \`## Q&A Log\` table demonstrates Ralph-Loop convergence: forcing-question
1488
- topics are addressed (see the stage's forcing-questions checklist row),
1489
- the last 2 turns produce no new decision-changing impact, OR an explicit
1490
- user stop-signal row is recorded. Walk the stage forcing questions one at
1491
- a time via the \`AskQuestion\` tool. If you find yourself proposing a
1492
- draft after 1-2 questions while forcing topics remain unaddressed, STOP
1490
+ \`## Q&A Log\` table demonstrates Ralph-Loop convergence: every
1491
+ forcing-question topic id is tagged \`[topic:<id>]\` on at least one row
1492
+ (see the stage's forcing-questions checklist for the id list), the last
1493
+ 2 turns produce no new decision-changing impact, OR an explicit user
1494
+ stop-signal row is recorded. Walk the stage forcing questions one at a
1495
+ time via the \`AskQuestion\` tool. If you find yourself proposing a
1496
+ draft after 1-2 questions while forcing topic ids remain untagged, STOP
1493
1497
  and continue the loop.
1494
1498
 
1495
1499
  The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when
1496
- convergence has not been reached.
1500
+ convergence has not been reached. Wave 24 (v6.0.0) made \`[topic:<id>]\`
1501
+ tagging mandatory; the English keyword fallback was removed because it
1502
+ mis-reported convergence on RU/UA Q&A logs.
1497
1503
 
1498
1504
  ## 2. Mandatory subagents run after Q&A approval
1499
1505
 
@@ -1565,7 +1571,7 @@ Track-specific skips are allowed only when \`flow-state.track\` + \`skippedStage
1565
1571
  ## Delegation And Approvals
1566
1572
 
1567
1573
  - Machine-only checks in design/plan/tdd/review/ship should auto-dispatch when tooling supports it.
1568
- - **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool — \`AskQuestion\` in Cursor). Walk the stage forcing-questions list one-by-one. Do NOT batch and do NOT defer to a single approval gate at the end. The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence is not reached (forcing topics unaddressed AND last 2 turns still produce decision-changing rows AND no stop-signal).
1574
+ - **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool — \`AskQuestion\` in Cursor). Walk the stage forcing-questions list one-by-one. **Tag each Q&A Log row's \`Decision impact\` cell with \`[topic:<id>]\`** (the id is given in the stage's forcing-questions checklist) so the linter can verify coverage in any natural language. Do NOT batch and do NOT defer to a single approval gate at the end. The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence is not reached (forcing topic ids untagged AND last 2 turns still produce decision-changing rows AND no stop-signal).
1569
1575
  - **For other stages** (spec/plan/tdd/build/review/ship): ask user input only at explicit approval gates (scope mode, plan approval, challenge resolution, ship finalization), not for routine progress updates.
1570
1576
  - If you find yourself proposing a draft after 1-2 questions in brainstorm/scope/design, STOP — go back to the forcing-questions list and continue.
1571
1577
  - Mandatory subagents in brainstorm/scope/design run only AFTER the user approves the elicitation outcome (see each stage's "Run Phase: post-elicitation" rows). Dispatching them before the Q&A loop converges violates the contract.
@@ -1,4 +1,5 @@
1
1
  import { type SubagentFallback } from "./harness-adapters.js";
2
+ import { type MandatoryDelegationTaskClass } from "./content/stage-schema.js";
2
3
  import type { FlowStage } from "./types.js";
3
4
  export type DelegationMode = "mandatory" | "proactive";
4
5
  export type DelegationStatus = "scheduled" | "launched" | "acknowledged" | "completed" | "failed" | "waived" | "stale";
@@ -143,6 +144,13 @@ export declare function appendDelegation(projectRoot: string, entry: DelegationE
143
144
  export declare function expectedFulfillmentMode(fallbacks: SubagentFallback[]): DelegationFulfillmentMode;
144
145
  export declare function checkMandatoryDelegations(projectRoot: string, stage: FlowStage, options?: {
145
146
  repairFeatureSystem?: boolean;
147
+ /**
148
+ * Optional task class for the active run. When set to
149
+ * `"software-bugfix"`, the mandatory delegation gate is skipped
150
+ * entirely (Wave 24). Callers that don't classify the run leave
151
+ * this undefined and rely on the track-based skip.
152
+ */
153
+ taskClass?: MandatoryDelegationTaskClass | null;
146
154
  }): Promise<{
147
155
  satisfied: boolean;
148
156
  missing: string[];
@@ -160,4 +168,12 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
160
168
  staleWorkers: string[];
161
169
  /** Expected fulfillment mode for the active harness set. */
162
170
  expectedMode: DelegationFulfillmentMode;
171
+ /**
172
+ * Wave 24 (v6.0.0): true when `mandatoryAgentsFor` returned [] for
173
+ * this (track, taskClass) combination — i.e. the gate was skipped
174
+ * entirely on quick track or software-bugfix runs. The skip is also
175
+ * recorded as a `mandatory_delegations_skipped_by_track` event in
176
+ * `delegation-events.jsonl` for audit traceability.
177
+ */
178
+ skippedByTrack: boolean;
163
179
  }>;
@@ -7,7 +7,7 @@ import { readConfig } from "./config.js";
7
7
  import { exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
8
8
  import { HARNESS_ADAPTERS } from "./harness-adapters.js";
9
9
  import { readFlowState } from "./runs.js";
10
- import { stageSchema } from "./content/stage-schema.js";
10
+ import { mandatoryAgentsFor, stageSchema } from "./content/stage-schema.js";
11
11
  const execFileAsync = promisify(execFile);
12
12
  const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived", "stale"]);
13
13
  export const DELEGATION_DISPATCH_SURFACES = [
@@ -320,6 +320,21 @@ export async function readDelegationLedger(projectRoot) {
320
320
  return { runId: activeRunId, entries: [] };
321
321
  }
322
322
  }
323
+ /**
324
+ * Wave 24 (v6.0.0) audit-only event types that live in
325
+ * `delegation-events.jsonl` but do NOT carry a delegation lifecycle
326
+ * payload (no agent/spanId). The parser must accept them so they
327
+ * don't show up as corrupt lines.
328
+ */
329
+ const NON_DELEGATION_AUDIT_EVENTS = new Set([
330
+ "mandatory_delegations_skipped_by_track"
331
+ ]);
332
+ function isAuditEventLine(parsed) {
333
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
334
+ return false;
335
+ const evt = parsed.event;
336
+ return typeof evt === "string" && NON_DELEGATION_AUDIT_EVENTS.has(evt);
337
+ }
323
338
  export async function readDelegationEvents(projectRoot) {
324
339
  const filePath = delegationEventsPath(projectRoot);
325
340
  if (!(await exists(filePath))) {
@@ -338,6 +353,11 @@ export async function readDelegationEvents(projectRoot) {
338
353
  if (isDelegationEvent(parsed)) {
339
354
  events.push(parsed);
340
355
  }
356
+ else if (isAuditEventLine(parsed)) {
357
+ // Wave 24 audit-only row (e.g. mandatory_delegations_skipped_by_track).
358
+ // Not a delegation lifecycle event but valid audit content.
359
+ continue;
360
+ }
341
361
  else {
342
362
  corruptLines.push(index + 1);
343
363
  }
@@ -451,7 +471,17 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
451
471
  const flowState = await readFlowState(projectRoot, {
452
472
  repairFeatureSystem: options.repairFeatureSystem
453
473
  });
454
- const mandatory = stageSchema(stage, flowState.track).mandatoryDelegations;
474
+ const mandatory = mandatoryAgentsFor(stage, flowState.track, options.taskClass ?? null);
475
+ const skippedByTrack = mandatory.length === 0 &&
476
+ stageSchema(stage, flowState.track).mandatoryDelegations.length > 0;
477
+ if (skippedByTrack) {
478
+ await recordMandatorySkippedByTrack(projectRoot, {
479
+ stage,
480
+ track: flowState.track,
481
+ taskClass: options.taskClass ?? null,
482
+ runId: flowState.activeRunId
483
+ });
484
+ }
455
485
  const { activeRunId } = flowState;
456
486
  const ledger = await readDelegationLedger(projectRoot);
457
487
  const events = await readDelegationEvents(projectRoot);
@@ -553,6 +583,37 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
553
583
  legacyInferredCompletions,
554
584
  corruptEventLines: events.corruptLines,
555
585
  staleWorkers,
556
- expectedMode
586
+ expectedMode,
587
+ skippedByTrack
557
588
  };
558
589
  }
590
+ /**
591
+ * Wave 24 (v6.0.0) — append a non-delegation audit event to
592
+ * `delegation-events.jsonl` recording that the mandatory delegation
593
+ * gate was skipped because of the active track / task class. Plays the
594
+ * same audit role as a `waived` row but does NOT carry an agent —
595
+ * downstream tooling treats `event === "mandatory_delegations_skipped_by_track"`
596
+ * lines as informational.
597
+ *
598
+ * Failures are swallowed: the audit log is best-effort. Missing the
599
+ * event must never block stage advance because the gate skip itself is
600
+ * authoritative.
601
+ */
602
+ async function recordMandatorySkippedByTrack(projectRoot, params) {
603
+ const eventsPath = delegationEventsPath(projectRoot);
604
+ const payload = {
605
+ event: "mandatory_delegations_skipped_by_track",
606
+ stage: params.stage,
607
+ track: params.track,
608
+ taskClass: params.taskClass,
609
+ runId: params.runId,
610
+ ts: new Date().toISOString()
611
+ };
612
+ try {
613
+ await fs.mkdir(path.dirname(eventsPath), { recursive: true });
614
+ await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
615
+ }
616
+ catch {
617
+ // best-effort audit; never block stage advance.
618
+ }
619
+ }
@@ -349,7 +349,7 @@ Before responding to a coding request:
349
349
 
350
350
  Three rules apply to every cclaw stage in this project, regardless of which skills loaded:
351
351
 
352
- 1. **Q&A convergence before drafting** — for brainstorm / scope / design, walk the stage forcing questions one at a time via the harness-native question tool (Claude \`AskUserQuestion\`, Cursor \`AskQuestion\`, Codex \`request_user_input\`, Gemini \`ask_user\`). The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence has not been reached. Convergence is satisfied when ANY of: (a) all forcing-question topics are addressed in \`## Q&A Log\`, (b) the last 2 substantive rows produce no decision-changing impact (Ralph-Loop), or (c) an explicit user stop-signal row is recorded. The fixed count floor (10 for standard) was removed in Wave 23.
352
+ 1. **Q&A convergence before drafting** — for brainstorm / scope / design, walk the stage forcing questions one at a time via the harness-native question tool (Claude \`AskUserQuestion\`, Cursor \`AskQuestion\`, Codex \`request_user_input\`, Gemini \`ask_user\`). The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence has not been reached. Convergence is satisfied when ANY of: (a) every forcing-question topic id is tagged \`[topic:<id>]\` in at least one \`## Q&A Log\` row, (b) the last 2 substantive rows produce no decision-changing impact (Ralph-Loop), or (c) an explicit user stop-signal row is recorded. The fixed count floor (10 for standard) was removed in Wave 23. Wave 24 (v6.0.0) made \`[topic:<id>]\` tagging mandatory (no English keyword fallback) so the gate works in any natural language.
353
353
  2. **Subagents run after Q&A approval** — mandatory subagents in brainstorm / scope / design (\`product-discovery\`, \`critic\`, \`planner\`, \`architect\`, \`test-author\`) run only AFTER the user approves the elicitation outcome. See each stage's "Run Phase: post-elicitation" rows in the materialized Automatic Subagent Dispatch table.
354
354
  3. **No command-line echo to chat** — the user does not run cclaw helpers manually. Never paste \`node .cclaw/hooks/...\` invocations, \`--evidence-json '{...}'\` payloads, or shell hash commands (\`shasum\`, \`sha256sum\`, \`Get-FileHash\`, \`certutil\`, etc.) into chat. Run helpers via the tool layer; report only the resulting summary.
355
355
 
@@ -19,6 +19,8 @@ interface InternalValidationReport {
19
19
  corruptEventLines: number[];
20
20
  staleWorkers: string[];
21
21
  expectedMode: string;
22
+ /** Wave 24: true when mandatoryAgentsFor returned [] for the run's track / taskClass. */
23
+ skippedByTrack: boolean;
22
24
  };
23
25
  gates: {
24
26
  ok: boolean;
@@ -111,7 +111,8 @@ export async function buildValidationReport(projectRoot, flowState, options = {}
111
111
  legacyInferredCompletions: delegation.legacyInferredCompletions,
112
112
  corruptEventLines: delegation.corruptEventLines,
113
113
  staleWorkers: delegation.staleWorkers,
114
- expectedMode: delegation.expectedMode
114
+ expectedMode: delegation.expectedMode,
115
+ skippedByTrack: delegation.skippedByTrack
115
116
  },
116
117
  gates: {
117
118
  ok: gates.ok,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {