cclaw-cli 4.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.
- package/dist/artifact-linter/brainstorm.js +40 -2
- package/dist/artifact-linter/design.js +2 -2
- package/dist/artifact-linter/review-army.d.ts +25 -0
- package/dist/artifact-linter/review-army.js +155 -0
- package/dist/artifact-linter/review.js +13 -0
- package/dist/artifact-linter/scope.js +9 -17
- package/dist/artifact-linter/shared.d.ts +93 -19
- package/dist/artifact-linter/shared.js +214 -96
- package/dist/artifact-linter.d.ts +1 -1
- package/dist/artifact-linter.js +1 -1
- package/dist/content/core-agents.js +6 -1
- package/dist/content/idea.js +14 -2
- package/dist/content/skills-elicitation.js +35 -18
- package/dist/content/skills.js +10 -4
- package/dist/content/stage-schema.d.ts +29 -0
- package/dist/content/stage-schema.js +17 -0
- package/dist/content/stages/_lint-metadata/index.js +1 -2
- package/dist/content/stages/brainstorm.js +5 -2
- package/dist/content/stages/design.js +13 -12
- package/dist/content/stages/review.js +21 -21
- package/dist/content/stages/scope.js +20 -18
- package/dist/content/stages/spec.js +3 -3
- package/dist/content/stages/tdd.js +1 -0
- package/dist/content/subagents.js +3 -1
- package/dist/content/templates.d.ts +2 -2
- package/dist/content/templates.js +52 -36
- package/dist/delegation.d.ts +16 -0
- package/dist/delegation.js +64 -3
- package/dist/flow-state.d.ts +12 -0
- package/dist/gate-evidence.d.ts +12 -0
- package/dist/gate-evidence.js +4 -1
- package/dist/harness-adapters.js +1 -1
- package/dist/internal/advance-stage/advance.d.ts +2 -0
- package/dist/internal/advance-stage/advance.js +2 -1
- package/dist/internal/advance-stage/parsers.d.ts +8 -0
- package/dist/internal/advance-stage/parsers.js +27 -1
- package/dist/internal/advance-stage/start-flow.js +13 -0
- package/dist/run-persistence.js +14 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SHIP_FINALIZATION_MODES } from "../constants.js";
|
|
2
2
|
import { questionBudgetHint } from "../track-heuristics.js";
|
|
3
3
|
import { FLOW_STAGES } from "../types.js";
|
|
4
|
+
import { stageSchema } from "../content/stage-schema.js";
|
|
4
5
|
/**
|
|
5
6
|
* Recognized stop-signal phrases that satisfy the Q&A floor escape hatch
|
|
6
7
|
* when recorded as a Q&A Log row. Mirrors `Stop Signals (Natural Language)`
|
|
@@ -29,9 +30,9 @@ const QA_LOG_STOP_SIGNAL_PATTERNS = [
|
|
|
29
30
|
/(?<![\p{L}\p{N}_])рухаємось\s+далі(?![\p{L}\p{N}_])/iu
|
|
30
31
|
];
|
|
31
32
|
/**
|
|
32
|
-
* Stages that run adaptive elicitation. The `
|
|
33
|
-
* fires for these. Other stages may still record a Q&A Log but no
|
|
34
|
-
* enforced.
|
|
33
|
+
* Stages that run adaptive elicitation. The `qa_log_unconverged` rule
|
|
34
|
+
* only fires for these. Other stages may still record a Q&A Log but no
|
|
35
|
+
* convergence floor is enforced.
|
|
35
36
|
*/
|
|
36
37
|
export const ELICITATION_STAGES = new Set([
|
|
37
38
|
"brainstorm",
|
|
@@ -39,9 +40,27 @@ export const ELICITATION_STAGES = new Set([
|
|
|
39
40
|
"design"
|
|
40
41
|
]);
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* Phrases that mark a Q&A Log row as "no new decision" — used by the
|
|
44
|
+
* Ralph-Loop convergence detector. When the last 2 substantive rows have
|
|
45
|
+
* a Decision impact tagged with one of these phrases, convergence has
|
|
46
|
+
* been reached even if not every forcing question was explicitly
|
|
47
|
+
* addressed.
|
|
48
|
+
*/
|
|
49
|
+
const QA_LOG_NO_DECISION_TOKENS = [
|
|
50
|
+
/\bskip(?:ped)?\b/iu,
|
|
51
|
+
/\bcontinue\b/iu,
|
|
52
|
+
/\bno[-\s]?change\b/iu,
|
|
53
|
+
/\bno[-\s]?decision\b/iu,
|
|
54
|
+
/\bno[-\s]?op\b/iu,
|
|
55
|
+
/\bnoop\b/iu,
|
|
56
|
+
/\bdone\b/iu,
|
|
57
|
+
/\bsame\b/iu,
|
|
58
|
+
/\bok\b/iu
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Decide whether a Q&A Log row counts as a "substantive" entry. Rows
|
|
62
|
+
* whose decision_impact column reads `skipped` / `waived` only do not
|
|
63
|
+
* count.
|
|
45
64
|
*/
|
|
46
65
|
function isSubstantiveQaRow(cells) {
|
|
47
66
|
if (cells.length === 0)
|
|
@@ -53,8 +72,8 @@ function isSubstantiveQaRow(cells) {
|
|
|
53
72
|
return true;
|
|
54
73
|
}
|
|
55
74
|
/**
|
|
56
|
-
* Detect a stop-signal row in the Q&A Log. Pattern is matched across
|
|
57
|
-
* cells of any row so the user's quote can live in any column.
|
|
75
|
+
* Detect a stop-signal row in the Q&A Log. Pattern is matched across
|
|
76
|
+
* all cells of any row so the user's quote can live in any column.
|
|
58
77
|
*/
|
|
59
78
|
function detectStopSignal(rows) {
|
|
60
79
|
for (const row of rows) {
|
|
@@ -67,60 +86,211 @@ function detectStopSignal(rows) {
|
|
|
67
86
|
return false;
|
|
68
87
|
}
|
|
69
88
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
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.
|
|
72
148
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
149
|
+
* Returns empty array when no forcing-questions row is present (caller
|
|
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.
|
|
153
|
+
*/
|
|
154
|
+
export function extractForcingQuestions(stage) {
|
|
155
|
+
let checklist;
|
|
156
|
+
try {
|
|
157
|
+
checklist = stageSchema(stage).executionModel.checklist;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
for (const row of checklist) {
|
|
163
|
+
const parsed = parseForcingQuestionsRow(row, `stage=${stage}`);
|
|
164
|
+
if (parsed === null)
|
|
165
|
+
continue;
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
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.
|
|
175
|
+
*/
|
|
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;
|
|
179
|
+
for (const row of rows) {
|
|
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)
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
function lastTwoRowsAllNoDecision(substantiveRows) {
|
|
192
|
+
if (substantiveRows.length < 2)
|
|
193
|
+
return false;
|
|
194
|
+
const tail = substantiveRows.slice(-2);
|
|
195
|
+
for (const row of tail) {
|
|
196
|
+
const decisionImpact = (row[row.length - 1] ?? "").trim();
|
|
197
|
+
if (decisionImpact.length === 0)
|
|
198
|
+
return false;
|
|
199
|
+
const matched = QA_LOG_NO_DECISION_TOKENS.some((pattern) => pattern.test(decisionImpact));
|
|
200
|
+
if (!matched)
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Evaluate the Q&A Log convergence floor for a brainstorm / scope /
|
|
207
|
+
* design artifact. Returns ok=true when convergence is reached or any
|
|
208
|
+
* escape hatch fires.
|
|
209
|
+
*
|
|
210
|
+
* Convergence sources (any one is sufficient):
|
|
211
|
+
* - All forcing-question topics from the stage checklist appear addressed
|
|
212
|
+
* in `## Q&A Log` (substring keyword match in question/answer columns).
|
|
213
|
+
* - The Ralph-Loop convergence detector reports the last 2 substantive
|
|
214
|
+
* rows have decision_impact marking `skip`/`continue`/`no-change`/`done`
|
|
215
|
+
* (i.e. the dialogue is no longer producing decision-changing rows).
|
|
216
|
+
* - Q&A Log contains a stop-signal row (existing
|
|
217
|
+
* `QA_LOG_STOP_SIGNAL_PATTERNS` keep working).
|
|
75
218
|
* - `--skip-questions` flag was persisted to the active stage flags
|
|
76
|
-
* (
|
|
77
|
-
* -
|
|
78
|
-
*
|
|
219
|
+
* (`options.skipQuestions=true`); finding downgrades to advisory.
|
|
220
|
+
* - The stage checklist exposes no forcing-questions row (e.g. simple
|
|
221
|
+
* refactor) AND the artifact has at least one substantive row — treat
|
|
222
|
+
* as converged because there is nothing left to force.
|
|
223
|
+
*
|
|
224
|
+
* Wave 23 (v5.0.0) replaces the count-based `qa_log_below_min` rule with
|
|
225
|
+
* `qa_log_unconverged`. The fixed count constant (10 for standard) and
|
|
226
|
+
* the `CCLAW_ELICITATION_FLOOR=advisory` env override were removed. The
|
|
227
|
+
* `min` and `liteShortCircuit` fields on the result are retained for
|
|
228
|
+
* harness UI compatibility but are always 0/false.
|
|
79
229
|
*/
|
|
80
230
|
export function evaluateQaLogFloor(qaLogBody, track, stage, options = {}) {
|
|
81
|
-
const hint = questionBudgetHint(track, stage);
|
|
82
|
-
const min = hint.min;
|
|
83
231
|
const rows = qaLogBody !== null ? getMarkdownTableRows(qaLogBody) : [];
|
|
84
232
|
const substantiveRows = rows.filter(isSubstantiveQaRow);
|
|
85
233
|
const count = substantiveRows.length;
|
|
86
234
|
const hasStopSignal = detectStopSignal(rows);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
235
|
+
const skipQuestionsAdvisory = options.skipQuestions === true;
|
|
236
|
+
const forcingTopics = (options.forcingQuestions ?? extractForcingQuestions(stage)).map((entry) => (typeof entry === "string" ? { id: entry, topic: entry } : entry));
|
|
237
|
+
const forcingCovered = [];
|
|
238
|
+
const forcingPending = [];
|
|
239
|
+
for (const topic of forcingTopics) {
|
|
240
|
+
if (isTopicAddressed(topic.id, rows))
|
|
241
|
+
forcingCovered.push(topic.id);
|
|
242
|
+
else
|
|
243
|
+
forcingPending.push(topic.id);
|
|
244
|
+
}
|
|
245
|
+
const noNewDecisions = lastTwoRowsAllNoDecision(substantiveRows);
|
|
246
|
+
const allForcingCovered = forcingTopics.length > 0 ? forcingPending.length === 0 : count >= 1;
|
|
247
|
+
const ok = allForcingCovered || noNewDecisions || hasStopSignal;
|
|
248
|
+
const pendingIdsBracket = forcingPending.length > 0
|
|
249
|
+
? `[${forcingPending.join(", ")}]`
|
|
250
|
+
: "[none]";
|
|
95
251
|
let details;
|
|
96
252
|
if (ok) {
|
|
97
|
-
if (
|
|
98
|
-
details = `Q&A Log
|
|
253
|
+
if (allForcingCovered && forcingTopics.length > 0) {
|
|
254
|
+
details = `Q&A Log converged: all ${forcingTopics.length} forcing-question topic(s) addressed across ${count} substantive row(s).`;
|
|
255
|
+
}
|
|
256
|
+
else if (allForcingCovered) {
|
|
257
|
+
details = `Q&A Log converged: stage exposes no forcing-questions row and ${count} substantive entry recorded.`;
|
|
99
258
|
}
|
|
100
|
-
else if (
|
|
101
|
-
|
|
259
|
+
else if (noNewDecisions) {
|
|
260
|
+
const remaining = forcingPending.length > 0
|
|
261
|
+
? ` ${forcingPending.length} forcing topic IDs still pending: ${pendingIdsBracket} (Ralph-Loop convergence overrode coverage).`
|
|
262
|
+
: " Ralph-Loop convergence detector says no new decision-changing rows in the last 2 turns.";
|
|
263
|
+
details = `Q&A Log converged via no-new-decisions detector at ${count} row(s).${remaining}`;
|
|
102
264
|
}
|
|
103
265
|
else {
|
|
104
|
-
details = `Q&A Log
|
|
266
|
+
details = `Q&A Log converged: explicit user stop-signal row recorded at ${count} row(s).`;
|
|
105
267
|
}
|
|
106
268
|
}
|
|
107
269
|
else if (skipQuestionsAdvisory) {
|
|
108
|
-
|
|
109
|
-
? "--skip-questions flag was set"
|
|
110
|
-
: "CCLAW_ELICITATION_FLOOR=advisory env override is active";
|
|
111
|
-
details = `Q&A Log has ${count} substantive entries, minimum for ${track}/${stage} is ${min}; ${reason}, finding downgraded to advisory.`;
|
|
270
|
+
details = `Q&A Log unconverged at ${count} row(s); --skip-questions flag downgraded the finding to advisory. Forcing topic IDs pending: ${pendingIdsBracket}.`;
|
|
112
271
|
}
|
|
113
272
|
else {
|
|
114
|
-
details = `Q&A Log
|
|
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.`;
|
|
115
274
|
}
|
|
275
|
+
// Surface advisory budget hint for harness UI without re-introducing a
|
|
276
|
+
// blocking count. `recommended` is the soft budget per track/stage.
|
|
277
|
+
const advisoryBudget = questionBudgetHint(track, stage).recommended;
|
|
116
278
|
return {
|
|
117
279
|
ok,
|
|
118
280
|
count,
|
|
119
|
-
|
|
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
|
+
min: 0,
|
|
120
285
|
hasStopSignal,
|
|
121
|
-
liteShortCircuit,
|
|
286
|
+
liteShortCircuit: false,
|
|
122
287
|
skipQuestionsAdvisory,
|
|
123
|
-
|
|
288
|
+
forcingCovered,
|
|
289
|
+
forcingPending,
|
|
290
|
+
noNewDecisions,
|
|
291
|
+
details: advisoryBudget > 0
|
|
292
|
+
? `${details} (advisory budget for ${track}/${stage}: ~${advisoryBudget} Q&A turns)`
|
|
293
|
+
: details
|
|
124
294
|
};
|
|
125
295
|
}
|
|
126
296
|
export function normalizeHeadingTitle(title) {
|
|
@@ -678,61 +848,12 @@ export function extractCanonicalScopeMode(body) {
|
|
|
678
848
|
}
|
|
679
849
|
return null;
|
|
680
850
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
// the linter only enforces that the section actually compares premise
|
|
688
|
-
// questions to answers.
|
|
689
|
-
const tableRows = getMarkdownTableRows(sectionBody);
|
|
690
|
-
const bulletRows = sectionBody
|
|
691
|
-
.split(/\r?\n/u)
|
|
692
|
-
.map((line) => line.trim())
|
|
693
|
-
.filter((line) => /^(?:[-*]|\d+\.)\s+\S/u.test(line));
|
|
694
|
-
const rowCount = Math.max(tableRows.length, bulletRows.length);
|
|
695
|
-
if (rowCount < 3) {
|
|
696
|
-
return {
|
|
697
|
-
ok: false,
|
|
698
|
-
details: `Premise Challenge needs at least 3 substantive rows in a table or bullet list. Found ${rowCount}.`
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
// For tables, each data row must have at least 2 non-empty cells so the
|
|
702
|
-
// section is genuinely a premise/answer comparison, not a list of headlines.
|
|
703
|
-
// For bullet lists, each line must be substantive so we don't accept
|
|
704
|
-
// placeholders like `- a`; punctuation style and natural language do not
|
|
705
|
-
// matter.
|
|
706
|
-
if (tableRows.length >= 3) {
|
|
707
|
-
const sparseRows = tableRows.filter((row) => {
|
|
708
|
-
const filledCells = row.filter((cell) => cell.replace(/[\s|]/gu, "").length >= 2);
|
|
709
|
-
return filledCells.length < 2;
|
|
710
|
-
});
|
|
711
|
-
if (sparseRows.length > 0) {
|
|
712
|
-
return {
|
|
713
|
-
ok: false,
|
|
714
|
-
details: "Premise Challenge table rows must populate at least the question and answer columns (no empty answers)."
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
else if (bulletRows.length >= 3) {
|
|
719
|
-
const sparseBullets = bulletRows.filter((line) => {
|
|
720
|
-
const cleaned = line.replace(/^[-*\d.\s]+/u, "").replace(/[`*_]/gu, "").trim();
|
|
721
|
-
const meaningful = cleaned.match(/[\p{L}\p{N}]/gu)?.length ?? 0;
|
|
722
|
-
return meaningful < 12;
|
|
723
|
-
});
|
|
724
|
-
if (sparseBullets.length > 0) {
|
|
725
|
-
return {
|
|
726
|
-
ok: false,
|
|
727
|
-
details: "Premise Challenge bullet list must include at least 3 substantive rows, not placeholders."
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return {
|
|
732
|
-
ok: true,
|
|
733
|
-
details: `Premise Challenge structures ${rowCount} Q/A rows.`
|
|
734
|
-
};
|
|
735
|
-
}
|
|
851
|
+
// `validatePremiseChallenge` was removed in Wave 23 (v5.0.0). Premise
|
|
852
|
+
// challenge is now owned solely by brainstorm (`## Premise Check`); scope
|
|
853
|
+
// only records `## Premise Drift` when scope-stage Q&A surfaces new
|
|
854
|
+
// evidence that materially changes the brainstorm answer. The drift
|
|
855
|
+
// section is optional and structural-only via the default `validateSectionBody`
|
|
856
|
+
// path (no specialized validator required).
|
|
736
857
|
export function validateScopeSummary(sectionBody) {
|
|
737
858
|
const meaningfulLines = sectionBody
|
|
738
859
|
.split(/\r?\n/)
|
|
@@ -1551,9 +1672,6 @@ export function validateSectionBody(sectionBody, rule, sectionName) {
|
|
|
1551
1672
|
if (sectionNameNormalized === "scope summary") {
|
|
1552
1673
|
return validateScopeSummary(sectionBody);
|
|
1553
1674
|
}
|
|
1554
|
-
if (sectionNameNormalized === "premise challenge") {
|
|
1555
|
-
return validatePremiseChallenge(sectionBody);
|
|
1556
|
-
}
|
|
1557
1675
|
if (sectionNameNormalized.startsWith("requirements")) {
|
|
1558
1676
|
return validateRequirementsTaxonomy(sectionBody);
|
|
1559
1677
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FlowStage, FlowTrack } from "./types.js";
|
|
2
2
|
import { type LintResult } from "./artifact-linter/shared.js";
|
|
3
|
-
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, type ReviewVerdictConsistencyResult, type ReviewSecurityNoChangeAttestationResult } from "./artifact-linter/review-army.js";
|
|
3
|
+
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, checkReviewTddNoCrossArtifactDuplication, type ReviewVerdictConsistencyResult, type ReviewSecurityNoChangeAttestationResult, type ReviewTddDuplicationConflict, type ReviewTddDuplicationResult } from "./artifact-linter/review-army.js";
|
|
4
4
|
export { type LintFinding, type LintResult, type LearningEntryType, type LearningConfidence, type LearningSeverity, type LearningSource, type LearningSeedEntry, type LearningsParseResult, extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
5
5
|
export interface LintArtifactOptions {
|
|
6
6
|
/**
|
package/dist/artifact-linter.js
CHANGED
|
@@ -12,7 +12,7 @@ import { lintSpecStage } from "./artifact-linter/spec.js";
|
|
|
12
12
|
import { lintTddStage } from "./artifact-linter/tdd.js";
|
|
13
13
|
import { lintReviewStage } from "./artifact-linter/review.js";
|
|
14
14
|
import { lintShipStage } from "./artifact-linter/ship.js";
|
|
15
|
-
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation } from "./artifact-linter/review-army.js";
|
|
15
|
+
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, checkReviewTddNoCrossArtifactDuplication } from "./artifact-linter/review-army.js";
|
|
16
16
|
export { extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
17
17
|
const FRONTMATTER_REQUIRED_KEYS = [
|
|
18
18
|
"stage",
|
|
@@ -392,7 +392,12 @@ export const CCLAW_AGENTS = [
|
|
|
392
392
|
"Compatibility: NO_IMPACT / FOUND_<n>",
|
|
393
393
|
"Observability: NO_IMPACT / FOUND_<n>",
|
|
394
394
|
"Security: routed to security-reviewer (always separate)",
|
|
395
|
-
"
|
|
395
|
+
"",
|
|
396
|
+
"### Companion lens skills (load on-demand, never all-at-once)",
|
|
397
|
+
"- **review-perf-lens** — load when reviewing code touching hot paths, loops over large data, network/disk I/O, render hot paths, or sub-100ms latency budgets.",
|
|
398
|
+
"- **review-compat-lens** — load when reviewing code that runs on multiple OS/runtime/browser targets, modifies shared library APIs, or changes serialized payload shapes.",
|
|
399
|
+
"- **review-observability-lens** — load when reviewing code that adds/removes logging, metrics, traces, error reporting, or audit/compliance signals.",
|
|
400
|
+
"If none of those triggers apply, do NOT load the lens skills — they are deep-dive context, not default reading.",
|
|
396
401
|
"",
|
|
397
402
|
"For each finding include:",
|
|
398
403
|
"- Severity: `Critical` | `Important` | `Suggestion`",
|
package/dist/content/idea.js
CHANGED
|
@@ -247,7 +247,12 @@ ${frameBullets}
|
|
|
247
247
|
8. **Write the artifact** at
|
|
248
248
|
\`${IDEA_ARTIFACT_PATTERN}\` using the schema in the skill.
|
|
249
249
|
9. **Present the handoff prompt** with four concrete options - not A/B/C
|
|
250
|
-
letters. Default = "Start /cc on the top recommendation".
|
|
250
|
+
letters. Default = "Start /cc on the top recommendation". When the user
|
|
251
|
+
picks the start option, plumb the chosen candidate forward via
|
|
252
|
+
\`start-flow --from-idea-artifact=<path> --from-idea-candidate=I-<n>\`
|
|
253
|
+
(Wave 23 / v5.0.0) so brainstorm reuses the idea's divergent + critique +
|
|
254
|
+
rank work via \`interactionHints.brainstorm.fromIdeaArtifact\`; do NOT
|
|
255
|
+
ask brainstorm to regenerate it.
|
|
251
256
|
|
|
252
257
|
## Headless mode (CI/automation only)
|
|
253
258
|
|
|
@@ -390,7 +395,14 @@ Required options, in this order:
|
|
|
390
395
|
### Phase 6 - Execute the choice
|
|
391
396
|
|
|
392
397
|
- Start /cc: load \`${RUNTIME_ROOT}/skills/using-cclaw/SKILL.md\` and run
|
|
393
|
-
\`/cc <phrase>\`.
|
|
398
|
+
\`/cc <phrase>\`. **Wave 23 (v5.0.0) handoff carry-forward (mandatory when starting from /cc-idea):**
|
|
399
|
+
the harness shim that turns \`/cc <phrase>\` into a \`start-flow\` invocation
|
|
400
|
+
MUST forward the originating idea artifact and chosen candidate so brainstorm
|
|
401
|
+
reuses divergent + critique + rank work instead of redoing it. Equivalent CLI
|
|
402
|
+
call (used by automation; harness handles this transparently in interactive mode):
|
|
403
|
+
\`npx cclaw-cli internal start-flow --track=<track> --prompt='<phrase>' --from-idea-artifact=${IDEA_ARTIFACT_PATTERN} --from-idea-candidate=I-<n>\`.
|
|
404
|
+
The hint lands in \`flow-state.interactionHints.brainstorm\` and brainstorm's
|
|
405
|
+
\`Idea-evidence carry-forward\` checklist row picks it up.
|
|
394
406
|
- Save and close: reply with artifact path and stop.
|
|
395
407
|
- Discard: delete the artifact and stop.
|
|
396
408
|
|
|
@@ -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
|
-
- **
|
|
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
|
|
|
@@ -103,16 +103,19 @@ Each grill question follows the same Core Protocol: ask one, wait, log, self-eva
|
|
|
103
103
|
|
|
104
104
|
Do not ask extra questions "for theater" on simple low-risk work.
|
|
105
105
|
|
|
106
|
-
## Question Budget Hint (
|
|
106
|
+
## Question Budget Hint (advisory only — Wave 23 dropped the count floor)
|
|
107
107
|
|
|
108
|
-
Source of truth: \`questionBudgetHint(track, stage)\`. The
|
|
108
|
+
Source of truth: \`questionBudgetHint(track, stage)\`. The numbers below are
|
|
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.
|
|
109
112
|
|
|
110
113
|
${budgetTable}
|
|
111
114
|
|
|
112
115
|
Track mapping note: \`quick\` ~= lightweight, \`medium\` ~= standard, \`standard\` ~= deep.
|
|
113
116
|
|
|
114
117
|
How to use the columns:
|
|
115
|
-
- \`Min\` —
|
|
118
|
+
- \`Min\` — soft minimum to surface forcing questions; not a blocking gate.
|
|
116
119
|
- \`Recommended\` — target for normal flows.
|
|
117
120
|
- \`Hard cap warning\` — point at which to stop or compress remaining forcing questions into one final batched ask. Not skip.
|
|
118
121
|
|
|
@@ -124,24 +127,38 @@ How to use the columns:
|
|
|
124
127
|
- \`skipped (already covered: turn N)\` — answered implicitly by an earlier reply; cite the turn.
|
|
125
128
|
- \`waived (user override)\` — user explicitly waived this question.
|
|
126
129
|
|
|
127
|
-
|
|
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):
|
|
128
145
|
|
|
129
146
|
- **Brainstorm**:
|
|
130
|
-
- What pain are we solving?
|
|
131
|
-
- What is the most direct path?
|
|
132
|
-
- What happens if we do nothing?
|
|
133
|
-
- Who is the operator/user impacted first?
|
|
134
|
-
- 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?
|
|
135
152
|
- **Scope**:
|
|
136
|
-
- What is definitely in and definitely out?
|
|
137
|
-
- Which decisions are already locked upstream?
|
|
138
|
-
- What is the rollback path if this fails?
|
|
139
|
-
- 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?
|
|
140
157
|
- **Design**:
|
|
141
|
-
- What is the data flow end-to-end?
|
|
142
|
-
- Where are the seams/interfaces and ownership boundaries?
|
|
143
|
-
- Which invariants must always hold?
|
|
144
|
-
- 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?
|
|
145
162
|
|
|
146
163
|
## One-Way Override (Irreversible Decisions)
|
|
147
164
|
|
package/dist/content/skills.js
CHANGED
|
@@ -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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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[];
|
|
@@ -439,6 +439,16 @@ const STAGE_SCHEMA_MAP = {
|
|
|
439
439
|
review: REVIEW,
|
|
440
440
|
ship: SHIP
|
|
441
441
|
};
|
|
442
|
+
/**
|
|
443
|
+
* Stage-level subagent dispatch matrix.
|
|
444
|
+
*
|
|
445
|
+
* NOTE on `fixer`: the `fixer` agent is intentionally NOT listed in any stage
|
|
446
|
+
* row. It is dispatched on-demand by the SDD `subagent-dev` skill (and by
|
|
447
|
+
* reviewer flows) when a review surfaces a concrete failing criterion that
|
|
448
|
+
* needs a fresh worker. Adding `fixer` to the static matrix would create
|
|
449
|
+
* proactive-waiver theatre because it can only run after a specific review
|
|
450
|
+
* finding exists. See `core-agents.ts` `fixer` definition for the contract.
|
|
451
|
+
*/
|
|
442
452
|
const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
443
453
|
brainstorm: [
|
|
444
454
|
{
|
|
@@ -801,6 +811,13 @@ export function mandatoryDelegationsForStage(stage, complexityTier = "standard")
|
|
|
801
811
|
.find((row) => row.stage === stage);
|
|
802
812
|
return summary ? summary.mandatoryAgents : [];
|
|
803
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
|
+
}
|
|
804
821
|
export function stageSchema(stage, track = "standard") {
|
|
805
822
|
const rawInput = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
|
|
806
823
|
const base = normalizeStageSchemaInput(rawInput);
|