cclaw-cli 6.0.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact-linter/design.d.ts +16 -0
- package/dist/artifact-linter/design.js +82 -18
- package/dist/artifact-linter/scope.js +59 -20
- package/dist/artifact-linter/shared.d.ts +118 -2
- package/dist/artifact-linter/shared.js +231 -47
- package/dist/artifact-linter.js +83 -17
- package/dist/content/stage-schema.d.ts +23 -0
- package/dist/content/stage-schema.js +29 -0
- package/dist/delegation.d.ts +40 -1
- package/dist/delegation.js +75 -3
- package/dist/flow-state.d.ts +14 -0
- package/dist/internal/advance-stage/advance.d.ts +36 -0
- package/dist/internal/advance-stage/advance.js +100 -5
- package/dist/internal/advance-stage/review-loop.d.ts +9 -0
- package/dist/internal/advance-stage/review-loop.js +42 -4
- package/dist/run-persistence.js +25 -0
- package/package.json +1 -1
|
@@ -1045,16 +1045,46 @@ export const INTERACTION_EDGE_CASE_REQUIREMENTS = [
|
|
|
1045
1045
|
},
|
|
1046
1046
|
{ label: "zombie connection", pattern: /\bzombie[\s-]?connection\b/iu }
|
|
1047
1047
|
];
|
|
1048
|
-
|
|
1048
|
+
const INTERACTION_EDGE_CASE_NA_PATTERN = /^\s*n\s*\/\s*a\b/iu;
|
|
1049
|
+
const INTERACTION_EDGE_CASE_NA_WITH_REASON_PATTERN = /^\s*n\s*\/\s*a\s*[—–\-:]\s*\S/iu;
|
|
1050
|
+
const INTERACTION_EDGE_CASE_NETWORK_DEPENDENT_LABELS = new Set([
|
|
1051
|
+
"nav-away-mid-request",
|
|
1052
|
+
"10K-result dataset",
|
|
1053
|
+
"background-job abandonment",
|
|
1054
|
+
"zombie connection"
|
|
1055
|
+
]);
|
|
1056
|
+
function shouldRelaxNetworkDependentEdgeCases(context) {
|
|
1057
|
+
if (!context.liteTier)
|
|
1058
|
+
return false;
|
|
1059
|
+
const sections = context.sections ?? null;
|
|
1060
|
+
if (!sections)
|
|
1061
|
+
return true;
|
|
1062
|
+
const diagramBody = sectionBodyByName(sections, "Architecture Diagram");
|
|
1063
|
+
const failureModeBody = sectionBodyByName(sections, "Failure Mode Table");
|
|
1064
|
+
const failureModeRowCount = failureModeBody !== null ? getMarkdownTableRows(failureModeBody).length : 0;
|
|
1065
|
+
if (failureModeRowCount > 0)
|
|
1066
|
+
return false;
|
|
1067
|
+
if (diagramBody && DIAGRAM_EXTERNAL_DEPENDENCY_PATTERN.test(diagramBody))
|
|
1068
|
+
return false;
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
export function validateInteractionEdgeCaseMatrix(sectionBody, context = {}) {
|
|
1049
1072
|
const rows = getMarkdownTableRows(sectionBody);
|
|
1073
|
+
const relaxNetworkRows = shouldRelaxNetworkDependentEdgeCases(context);
|
|
1050
1074
|
if (rows.length === 0) {
|
|
1075
|
+
if (relaxNetworkRows) {
|
|
1076
|
+
return {
|
|
1077
|
+
ok: true,
|
|
1078
|
+
details: "Data Flow Interaction Edge Case matrix is advisory for lite-tier no-network designs (no Failure Mode Table rows and no external-dependency nodes detected)."
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1051
1081
|
return {
|
|
1052
1082
|
ok: false,
|
|
1053
1083
|
details: "Data Flow must include an Interaction Edge Case matrix table with required rows."
|
|
1054
1084
|
};
|
|
1055
1085
|
}
|
|
1056
1086
|
const seen = new Map();
|
|
1057
|
-
for (const [
|
|
1087
|
+
for (const [, row] of rows.entries()) {
|
|
1058
1088
|
const labelCell = (row[0] ?? "").trim();
|
|
1059
1089
|
if (!labelCell)
|
|
1060
1090
|
continue;
|
|
@@ -1067,15 +1097,31 @@ export function validateInteractionEdgeCaseMatrix(sectionBody) {
|
|
|
1067
1097
|
details: `Interaction Edge Case row "${requirement.label}" must include 4 columns: Edge case | Handled? | Design response | Deferred item.`
|
|
1068
1098
|
};
|
|
1069
1099
|
}
|
|
1070
|
-
const
|
|
1100
|
+
const handledRaw = (row[1] ?? "").trim();
|
|
1101
|
+
const handled = parseBinaryFlag(handledRaw);
|
|
1071
1102
|
const response = (row[2] ?? "").trim();
|
|
1072
1103
|
const deferred = (row[3] ?? "").trim();
|
|
1073
|
-
|
|
1104
|
+
const isNA = INTERACTION_EDGE_CASE_NA_PATTERN.test(handledRaw);
|
|
1105
|
+
if (handled === "unknown" && !isNA) {
|
|
1074
1106
|
return {
|
|
1075
1107
|
ok: false,
|
|
1076
|
-
details: `Interaction Edge Case row "${requirement.label}" must mark Handled? as yes/no.`
|
|
1108
|
+
details: `Interaction Edge Case row "${requirement.label}" must mark Handled? as yes/no, or write \`N/A — <reason>\` (em-dash + free-text reason) when the case does not apply.`
|
|
1077
1109
|
};
|
|
1078
1110
|
}
|
|
1111
|
+
if (isNA) {
|
|
1112
|
+
// Wave 25: `N/A — <reason>` short-circuits both the "must mark
|
|
1113
|
+
// yes/no" rule and the "must reference a deferred item id"
|
|
1114
|
+
// rule. The reason satisfies justification.
|
|
1115
|
+
const hasReason = INTERACTION_EDGE_CASE_NA_WITH_REASON_PATTERN.test(handledRaw) || response.length > 0;
|
|
1116
|
+
if (!hasReason) {
|
|
1117
|
+
return {
|
|
1118
|
+
ok: false,
|
|
1119
|
+
details: `Interaction Edge Case row "${requirement.label}" marked N/A but missing reason. Use \`N/A — <reason>\` (em-dash + free-text reason) in the Handled? cell or fill the Design response cell.`
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
seen.set(requirement.label, true);
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1079
1125
|
if (!response) {
|
|
1080
1126
|
return {
|
|
1081
1127
|
ok: false,
|
|
@@ -1085,7 +1131,7 @@ export function validateInteractionEdgeCaseMatrix(sectionBody) {
|
|
|
1085
1131
|
if (handled === "no" && (!deferred || /\bnone\b/iu.test(deferred))) {
|
|
1086
1132
|
return {
|
|
1087
1133
|
ok: false,
|
|
1088
|
-
details: `Interaction Edge Case row "${requirement.label}" is unhandled and must reference a deferred item id (for example D-12)
|
|
1134
|
+
details: `Interaction Edge Case row "${requirement.label}" is unhandled and must reference a deferred item id (for example D-12) or mark Handled? as \`N/A — <reason>\`.`
|
|
1089
1135
|
};
|
|
1090
1136
|
}
|
|
1091
1137
|
seen.set(requirement.label, true);
|
|
@@ -1093,15 +1139,27 @@ export function validateInteractionEdgeCaseMatrix(sectionBody) {
|
|
|
1093
1139
|
const missing = INTERACTION_EDGE_CASE_REQUIREMENTS
|
|
1094
1140
|
.map((requirement) => requirement.label)
|
|
1095
1141
|
.filter((label) => !seen.has(label));
|
|
1096
|
-
|
|
1142
|
+
const stillMissing = relaxNetworkRows
|
|
1143
|
+
? missing.filter((label) => !INTERACTION_EDGE_CASE_NETWORK_DEPENDENT_LABELS.has(label))
|
|
1144
|
+
: missing;
|
|
1145
|
+
const advisoryMissing = relaxNetworkRows
|
|
1146
|
+
? missing.filter((label) => INTERACTION_EDGE_CASE_NETWORK_DEPENDENT_LABELS.has(label))
|
|
1147
|
+
: [];
|
|
1148
|
+
if (stillMissing.length > 0) {
|
|
1149
|
+
const advisoryNote = advisoryMissing.length > 0
|
|
1150
|
+
? ` (${advisoryMissing.length} network-dependent row(s) demoted to advisory by lite-tier no-network detection: ${advisoryMissing.join(", ")})`
|
|
1151
|
+
: "";
|
|
1097
1152
|
return {
|
|
1098
1153
|
ok: false,
|
|
1099
|
-
details: `Interaction Edge Case matrix is missing required row(s): ${
|
|
1154
|
+
details: `Interaction Edge Case matrix is missing required row(s): ${stillMissing.join(", ")}${advisoryNote}.`
|
|
1100
1155
|
};
|
|
1101
1156
|
}
|
|
1157
|
+
const advisoryNote = advisoryMissing.length > 0
|
|
1158
|
+
? ` (${advisoryMissing.length} network-dependent row(s) advisory under lite-tier no-network: ${advisoryMissing.join(", ")})`
|
|
1159
|
+
: "";
|
|
1102
1160
|
return {
|
|
1103
1161
|
ok: true,
|
|
1104
|
-
details:
|
|
1162
|
+
details: `Interaction Edge Case matrix contains all required rows with handled/deferred status${advisoryNote}.`
|
|
1105
1163
|
};
|
|
1106
1164
|
}
|
|
1107
1165
|
export const PRE_SCOPE_AUDIT_SIGNALS = [
|
|
@@ -1128,9 +1186,23 @@ export function validatePreScopeSystemAudit(sectionBody) {
|
|
|
1128
1186
|
details: "Pre-Scope System Audit captures git log/diff/stash/debt-marker checks."
|
|
1129
1187
|
};
|
|
1130
1188
|
}
|
|
1131
|
-
export const DIAGRAM_ARROW_PATTERN = /(
|
|
1189
|
+
export const DIAGRAM_ARROW_PATTERN = /(?:<--?>|<?==?>|--?>|->>|=>|-\.->|→|⟶|↦|={2,}>|-{3,}>|\.{3,}>|-(?:\s-){1,}\s?->)/u;
|
|
1132
1190
|
export const DIAGRAM_FAILURE_EDGE_PATTERN = /\b(fail(?:ed|ure)?|error|timeout|fallback|degrad(?:e|ed|ation)|retry|backoff|circuit|unavailable|recover(?:y)?|rescue|mitigat(?:e|ion)|rollback|exception|abort|dead[\s-]?letter|dlq)\b/iu;
|
|
1133
1191
|
export const DIAGRAM_GENERIC_NODE_PATTERN = /\b(service|component|module|system)\s*(?:[A-Z0-9])?\b/iu;
|
|
1192
|
+
/**
|
|
1193
|
+
* Wave 25 (v6.1.0) — external-dependency keywords that trigger the
|
|
1194
|
+
* failure-edge requirement. The architecture diagram is allowed to
|
|
1195
|
+
* omit failure edges only when ALL of:
|
|
1196
|
+
* - Failure Mode Table has zero rows.
|
|
1197
|
+
* - The diagram body mentions no external-dependency keyword.
|
|
1198
|
+
*
|
|
1199
|
+
* Static landing pages (3 HTML/CSS/JS files, no network) match this:
|
|
1200
|
+
* no failure modes to map, no external systems to fail. The previous
|
|
1201
|
+
* blanket "must include at least one failure-edge" rule produced
|
|
1202
|
+
* ceremony-only failures that the agent worked around with fake
|
|
1203
|
+
* `(timeout)` annotations, defeating the spirit of the rule.
|
|
1204
|
+
*/
|
|
1205
|
+
export const DIAGRAM_EXTERNAL_DEPENDENCY_PATTERN = /\b(http|https|api|rest|grpc|graphql|websocket|socket|tcp|udp|rpc|fetch|request|database|db|sql|postgres|mysql|sqlite|mongo|redis|cache|queue|kafka|rabbitmq|sqs|sns|s3|cdn|external|upstream|downstream|third[\s-]?party|webhook|cloud|service[\s-]?bus|event[\s-]?bus|broker|stream|topic)\b/iu;
|
|
1134
1206
|
export const TEST_COMMAND_MARKER_PATTERN = /\b(?:npm|pnpm|yarn|bun|vitest|jest|pytest|go test|cargo test|mvn test|gradle test|dotnet test)\b/iu;
|
|
1135
1207
|
export const RED_FAILURE_MARKER_PATTERN = /\b(?:fail|failed|failing|assertionerror|cannot find|exception|error|exit code\s*[:=]?\s*[1-9])\b/iu;
|
|
1136
1208
|
export const GREEN_SUCCESS_MARKER_PATTERN = /\b(?:pass|passed|green|ok|0 failed|exit code\s*[:=]?\s*0)\b/iu;
|
|
@@ -1155,18 +1227,158 @@ export function hasFailureEdgeInDiagram(sectionBody) {
|
|
|
1155
1227
|
export function hasLabeledDiagramArrow(lines) {
|
|
1156
1228
|
return lines.some((line) => /\|[^|]+\|/u.test(line) || /:\s*[A-Za-z]/u.test(line));
|
|
1157
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Wave 25 (v6.1.0) — accepted async edge patterns. Returns true when
|
|
1232
|
+
* a line carries any of:
|
|
1233
|
+
*
|
|
1234
|
+
* - `-.->`, `-->>`, `~~>` (mermaid dotted/messaging arrows)
|
|
1235
|
+
* - `- - ->` (loose dotted ASCII arrow with optional spaces)
|
|
1236
|
+
* - `.....>` (3-or-more dots followed by `>`)
|
|
1237
|
+
* - `\basync\b` text token (label-based)
|
|
1238
|
+
* - `[async]` bracketed label, `async:` prefix, `async:` cell content
|
|
1239
|
+
*
|
|
1240
|
+
* The error message printed when this fails (see
|
|
1241
|
+
* `validateArchitectureDiagram`) lists every accepted pattern
|
|
1242
|
+
* verbatim so the agent does not have to guess.
|
|
1243
|
+
*/
|
|
1158
1244
|
export function hasAsyncDiagramEdge(lines) {
|
|
1159
|
-
return lines.some((line) =>
|
|
1245
|
+
return lines.some((line) => {
|
|
1246
|
+
if (/-\.->|-->>|~~>/u.test(line))
|
|
1247
|
+
return true;
|
|
1248
|
+
if (/-(?:\s-){1,}\s?->/u.test(line))
|
|
1249
|
+
return true;
|
|
1250
|
+
if (/\.{3,}\s*>/u.test(line))
|
|
1251
|
+
return true;
|
|
1252
|
+
if (/\basync\b/iu.test(line))
|
|
1253
|
+
return true;
|
|
1254
|
+
if (/\[\s*async\s*\]/iu.test(line))
|
|
1255
|
+
return true;
|
|
1256
|
+
if (/(?:^|[\s|:])async\s*:/iu.test(line))
|
|
1257
|
+
return true;
|
|
1258
|
+
return false;
|
|
1259
|
+
});
|
|
1160
1260
|
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Wave 25 (v6.1.0) — accepted sync edge patterns. Returns true when a
|
|
1263
|
+
* line carries any of:
|
|
1264
|
+
*
|
|
1265
|
+
* - `\bsync\b` text token (label-based)
|
|
1266
|
+
* - `[sync]` bracketed label, `sync:` prefix, `sync:` cell content
|
|
1267
|
+
* - Solid `-->`, `->`, `=>`, `→`, `⟶`, `↦` arrow that is NOT a known
|
|
1268
|
+
* dotted/async variant (`-.->`, `-->>`, `~~>`)
|
|
1269
|
+
* - `===>` (3+ `=` then `>`) and `--->` (3+ `-` then `>`) heavy solid
|
|
1270
|
+
* arrows
|
|
1271
|
+
*/
|
|
1161
1272
|
export function hasSyncDiagramEdge(lines) {
|
|
1162
1273
|
return lines.some((line) => {
|
|
1163
|
-
if (/\bsync\b/iu.test(line))
|
|
1274
|
+
if (/\bsync\b/iu.test(line) && !/\basync\b/iu.test(line))
|
|
1275
|
+
return true;
|
|
1276
|
+
if (/\[\s*sync\s*\]/iu.test(line))
|
|
1277
|
+
return true;
|
|
1278
|
+
if (/(?:^|[\s|:])sync\s*:/iu.test(line))
|
|
1279
|
+
return true;
|
|
1280
|
+
if (/={2,}>/u.test(line))
|
|
1281
|
+
return true;
|
|
1282
|
+
if (/-{3,}>/u.test(line))
|
|
1164
1283
|
return true;
|
|
1165
1284
|
if (!/(-->|->|=>|→|⟶|↦)/u.test(line))
|
|
1166
1285
|
return false;
|
|
1167
|
-
|
|
1286
|
+
if (/-\.->|-->>|~~>/u.test(line))
|
|
1287
|
+
return false;
|
|
1288
|
+
if (/-(?:\s-){1,}\s?->/u.test(line))
|
|
1289
|
+
return false;
|
|
1290
|
+
return true;
|
|
1168
1291
|
});
|
|
1169
1292
|
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Wave 25 (v6.1.0) — exact accepted-pattern list shown in the error
|
|
1295
|
+
* message when sync/async distinction fails. Keep in sync with
|
|
1296
|
+
* `hasAsyncDiagramEdge` / `hasSyncDiagramEdge` above.
|
|
1297
|
+
*/
|
|
1298
|
+
export const DIAGRAM_SYNC_ASYNC_ACCEPTED_PATTERNS = [
|
|
1299
|
+
"Solid arrows: `-->`, `->`, `===>`, `--->`, `=>`, `→`, `⟶`, `↦`",
|
|
1300
|
+
"Dotted/async arrows: `-.->`, `-->>`, `~~>`, `- - ->`, `.....>`",
|
|
1301
|
+
"Text labels on the same line: `sync` / `async`",
|
|
1302
|
+
"Bracket labels: `[sync]` / `[async]`",
|
|
1303
|
+
"Cell-prefix labels: `sync:` / `async:` (e.g. `A -->|sync: persist| B`)"
|
|
1304
|
+
];
|
|
1305
|
+
/**
|
|
1306
|
+
* Wave 25 (v6.1.0) — Architecture Diagram structural check.
|
|
1307
|
+
*
|
|
1308
|
+
* Promoted out of `validateSectionBody` so it can take a `sections`
|
|
1309
|
+
* map and conditionally enforce the failure-edge rule based on
|
|
1310
|
+
* cross-section context (Failure Mode Table presence + diagram body
|
|
1311
|
+
* mentioning external-dependency keywords).
|
|
1312
|
+
*/
|
|
1313
|
+
export function validateArchitectureDiagram(sectionBody, context = {}) {
|
|
1314
|
+
const edgeLines = diagramEdgeLines(sectionBody);
|
|
1315
|
+
if (edgeLines.length === 0) {
|
|
1316
|
+
return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
details: "Architecture Diagram must include at least one directional edge line (for example `A -->|action| B`)."
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
if (!hasLabeledDiagramArrow(edgeLines)) {
|
|
1322
|
+
return {
|
|
1323
|
+
ok: false,
|
|
1324
|
+
details: "Architecture Diagram must label each edge with an action/message (for example `A -->|sync: persist| B`)."
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
const genericLine = edgeLines.find((line) => DIAGRAM_GENERIC_NODE_PATTERN.test(line));
|
|
1328
|
+
if (genericLine) {
|
|
1329
|
+
return {
|
|
1330
|
+
ok: false,
|
|
1331
|
+
details: `Architecture Diagram uses a generic node label in edge "${genericLine}". Use concrete component names instead of placeholders like Service/Component.`
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
if (!hasAsyncDiagramEdge(edgeLines) || !hasSyncDiagramEdge(edgeLines)) {
|
|
1335
|
+
const acceptedList = DIAGRAM_SYNC_ASYNC_ACCEPTED_PATTERNS.map((line) => ` - ${line}`).join("\n");
|
|
1336
|
+
return {
|
|
1337
|
+
ok: false,
|
|
1338
|
+
details: `Architecture Diagram must distinguish sync vs async edges. Accepted patterns:\n${acceptedList}\nExample line that satisfies both: \`Browser -->|sync: render| App\` plus \`App -.->|async: log| Telemetry\`.`
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
if (!shouldEnforceFailureEdge(sectionBody, context)) {
|
|
1342
|
+
return {
|
|
1343
|
+
ok: true,
|
|
1344
|
+
details: "Architecture Diagram includes labeled directional edges with sync/async distinction; failure-edge enforcement skipped (no failure-mode rows and no external-dependency nodes detected)."
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
if (!hasFailureEdgeInDiagram(sectionBody)) {
|
|
1348
|
+
return {
|
|
1349
|
+
ok: false,
|
|
1350
|
+
details: "Architecture Diagram must include at least one failure-edge arrow with a failure keyword (for example: timeout, error, fallback, degraded, retry). Mark a failure path in the diagram (e.g. `App -->|timeout| FallbackCache`)."
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
return {
|
|
1354
|
+
ok: true,
|
|
1355
|
+
details: "Architecture Diagram contains labeled edges, sync/async distinction, and a failure-edge."
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Wave 25 (v6.1.0) — decide whether the failure-edge enforcement
|
|
1360
|
+
* should fire for the given Architecture Diagram body. Returns
|
|
1361
|
+
* `false` (skip the rule) when BOTH:
|
|
1362
|
+
* - The artifact's `## Failure Mode Table` (if present) has zero
|
|
1363
|
+
* data rows OR is absent entirely.
|
|
1364
|
+
* - The architecture diagram body mentions NO known external-
|
|
1365
|
+
* dependency keyword (network, db, queue, …).
|
|
1366
|
+
*
|
|
1367
|
+
* Static landing pages (no network, no failure modes) hit this
|
|
1368
|
+
* path. Designs with even one Failure Mode row OR one external
|
|
1369
|
+
* dependency keyword in the diagram fall through to the legacy
|
|
1370
|
+
* blanket failure-edge requirement.
|
|
1371
|
+
*/
|
|
1372
|
+
function shouldEnforceFailureEdge(diagramBody, context) {
|
|
1373
|
+
const sections = context.sections ?? null;
|
|
1374
|
+
const failureModeBody = sections ? sectionBodyByName(sections, "Failure Mode Table") : null;
|
|
1375
|
+
const failureModeRowCount = failureModeBody !== null ? getMarkdownTableRows(failureModeBody).length : 0;
|
|
1376
|
+
if (failureModeRowCount > 0)
|
|
1377
|
+
return true;
|
|
1378
|
+
if (DIAGRAM_EXTERNAL_DEPENDENCY_PATTERN.test(diagramBody))
|
|
1379
|
+
return true;
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1170
1382
|
export function validateTddRedEvidence(sectionBody) {
|
|
1171
1383
|
const meaningful = meaningfulLineCount(sectionBody);
|
|
1172
1384
|
if (meaningful < 2) {
|
|
@@ -1569,7 +1781,7 @@ export function collectPatternHits(text, patterns) {
|
|
|
1569
1781
|
}
|
|
1570
1782
|
return hits;
|
|
1571
1783
|
}
|
|
1572
|
-
export function validateSectionBody(sectionBody, rule, sectionName) {
|
|
1784
|
+
export function validateSectionBody(sectionBody, rule, sectionName, context = {}) {
|
|
1573
1785
|
const bodyLines = sectionBody.split(/\r?\n/).map((line) => line.trim());
|
|
1574
1786
|
const meaningful = meaningfulLineCount(sectionBody);
|
|
1575
1787
|
if (meaningful === 0) {
|
|
@@ -1676,41 +1888,13 @@ export function validateSectionBody(sectionBody, rule, sectionName) {
|
|
|
1676
1888
|
return validateRequirementsTaxonomy(sectionBody);
|
|
1677
1889
|
}
|
|
1678
1890
|
if (sectionNameNormalized === "data flow") {
|
|
1679
|
-
return validateInteractionEdgeCaseMatrix(sectionBody
|
|
1891
|
+
return validateInteractionEdgeCaseMatrix(sectionBody, {
|
|
1892
|
+
sections: context.sections ?? null,
|
|
1893
|
+
liteTier: context.liteTier ?? false
|
|
1894
|
+
});
|
|
1680
1895
|
}
|
|
1681
1896
|
if (sectionNameNormalized === "architecture diagram") {
|
|
1682
|
-
|
|
1683
|
-
if (edgeLines.length === 0) {
|
|
1684
|
-
return {
|
|
1685
|
-
ok: false,
|
|
1686
|
-
details: "Architecture Diagram must include at least one directional edge line (for example `A -->|action| B`)."
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
if (!hasLabeledDiagramArrow(edgeLines)) {
|
|
1690
|
-
return {
|
|
1691
|
-
ok: false,
|
|
1692
|
-
details: "Architecture Diagram must label each edge with an action/message (for example `A -->|sync: persist| B`)."
|
|
1693
|
-
};
|
|
1694
|
-
}
|
|
1695
|
-
const genericLine = edgeLines.find((line) => DIAGRAM_GENERIC_NODE_PATTERN.test(line));
|
|
1696
|
-
if (genericLine) {
|
|
1697
|
-
return {
|
|
1698
|
-
ok: false,
|
|
1699
|
-
details: `Architecture Diagram uses a generic node label in edge "${genericLine}". Use concrete component names instead of placeholders like Service/Component.`
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
if (!hasAsyncDiagramEdge(edgeLines) || !hasSyncDiagramEdge(edgeLines)) {
|
|
1703
|
-
return {
|
|
1704
|
-
ok: false,
|
|
1705
|
-
details: "Architecture Diagram must distinguish sync vs async edges (for example solid + dotted arrows, or `sync:` and `async:` labels)."
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
if (!hasFailureEdgeInDiagram(sectionBody)) {
|
|
1709
|
-
return {
|
|
1710
|
-
ok: false,
|
|
1711
|
-
details: "Architecture Diagram must include at least one failure-edge arrow with a failure keyword (for example: timeout, error, fallback, degraded, retry)."
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1897
|
+
return validateArchitectureDiagram(sectionBody, { sections: context.sections ?? null });
|
|
1714
1898
|
}
|
|
1715
1899
|
if (sectionNameNormalized === "acceptance criteria" &&
|
|
1716
1900
|
/observable[\s,]*measurable[\s,]+(and )?falsifiable/iu.test(rule)) {
|
package/dist/artifact-linter.js
CHANGED
|
@@ -4,6 +4,8 @@ import { exists } from "./fs-utils.js";
|
|
|
4
4
|
import { stageSchema } from "./content/stage-schema.js";
|
|
5
5
|
import { readFlowState } from "./run-persistence.js";
|
|
6
6
|
import { duplicateH2Headings, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
|
|
7
|
+
import { shouldDemoteArtifactValidationByTrack } from "./content/stage-schema.js";
|
|
8
|
+
import { recordArtifactValidationDemotedByTrack } from "./delegation.js";
|
|
7
9
|
import { lintBrainstormStage } from "./artifact-linter/brainstorm.js";
|
|
8
10
|
import { lintDesignStage } from "./artifact-linter/design.js";
|
|
9
11
|
import { lintPlanStage } from "./artifact-linter/plan.js";
|
|
@@ -105,6 +107,34 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
105
107
|
const overrideSet = isTrivialOverride
|
|
106
108
|
? new Set(schema.trivialOverrideSections.map((s) => normalizeHeadingTitle(s).toLowerCase()))
|
|
107
109
|
: null;
|
|
110
|
+
// Wave 25: precompute the lite-tier signal so the per-section
|
|
111
|
+
// validators (Interaction Edge Case matrix today, others tomorrow)
|
|
112
|
+
// can relax network-dependent mandatory rows for lite/quick/bugfix
|
|
113
|
+
// runs without each validator having to re-derive the predicate.
|
|
114
|
+
// Same flow-state read powers the post-loop demotion + audit log
|
|
115
|
+
// below; we cache the result here to avoid two disk reads.
|
|
116
|
+
let activeStageFlags = [];
|
|
117
|
+
let taskClass = null;
|
|
118
|
+
let activeRunId = null;
|
|
119
|
+
try {
|
|
120
|
+
const flowState = await readFlowState(projectRoot);
|
|
121
|
+
const hint = flowState.interactionHints?.[stage];
|
|
122
|
+
if (hint?.skipQuestions === true)
|
|
123
|
+
activeStageFlags.push("--skip-questions");
|
|
124
|
+
taskClass = flowState.taskClass ?? null;
|
|
125
|
+
activeRunId = flowState.activeRunId ?? null;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
activeStageFlags = [];
|
|
129
|
+
taskClass = null;
|
|
130
|
+
activeRunId = null;
|
|
131
|
+
}
|
|
132
|
+
for (const extra of options.extraStageFlags ?? []) {
|
|
133
|
+
if (typeof extra === "string" && extra.length > 0 && !activeStageFlags.includes(extra)) {
|
|
134
|
+
activeStageFlags.push(extra);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const liteTierForValidators = shouldDemoteArtifactValidationByTrack(track, taskClass);
|
|
108
138
|
for (const v of schema.artifactValidation) {
|
|
109
139
|
const sectionKey = normalizeHeadingTitle(v.section).toLowerCase();
|
|
110
140
|
const scopeBoundaryAlias = stage === "scope" && sectionKey === "in scope / out of scope";
|
|
@@ -122,7 +152,10 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
122
152
|
: effectiveRequiredFromOverride;
|
|
123
153
|
const validation = body === null
|
|
124
154
|
? { ok: false, details: `No ## heading matching required section "${v.section}".` }
|
|
125
|
-
: validateSectionBody(body, v.validationRule, v.section
|
|
155
|
+
: validateSectionBody(body, v.validationRule, v.section, {
|
|
156
|
+
sections,
|
|
157
|
+
liteTier: liteTierForValidators
|
|
158
|
+
});
|
|
126
159
|
const found = hasHeading && validation.ok;
|
|
127
160
|
findings.push({
|
|
128
161
|
section: v.section,
|
|
@@ -158,21 +191,6 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
158
191
|
details: `${learnings.details}${meaningfulStageNoneWarning}`
|
|
159
192
|
});
|
|
160
193
|
}
|
|
161
|
-
let activeStageFlags = [];
|
|
162
|
-
try {
|
|
163
|
-
const flowState = await readFlowState(projectRoot);
|
|
164
|
-
const hint = flowState.interactionHints?.[stage];
|
|
165
|
-
if (hint?.skipQuestions === true)
|
|
166
|
-
activeStageFlags.push("--skip-questions");
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
activeStageFlags = [];
|
|
170
|
-
}
|
|
171
|
-
for (const extra of options.extraStageFlags ?? []) {
|
|
172
|
-
if (typeof extra === "string" && extra.length > 0 && !activeStageFlags.includes(extra)) {
|
|
173
|
-
activeStageFlags.push(extra);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
194
|
const stageContext = {
|
|
177
195
|
projectRoot,
|
|
178
196
|
stage,
|
|
@@ -188,7 +206,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
188
206
|
staleDiagramAuditEnabled,
|
|
189
207
|
isTrivialOverride,
|
|
190
208
|
overrideSet,
|
|
191
|
-
activeStageFlags
|
|
209
|
+
activeStageFlags,
|
|
210
|
+
taskClass
|
|
192
211
|
};
|
|
193
212
|
switch (stage) {
|
|
194
213
|
case "brainstorm":
|
|
@@ -257,6 +276,53 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
257
276
|
});
|
|
258
277
|
}
|
|
259
278
|
}
|
|
279
|
+
const demote = shouldDemoteArtifactValidationByTrack(track, taskClass);
|
|
280
|
+
const demotedSections = [];
|
|
281
|
+
if (demote) {
|
|
282
|
+
for (const finding of findings) {
|
|
283
|
+
if (!ARTIFACT_VALIDATION_LITE_DEMOTE_SECTIONS.has(finding.section))
|
|
284
|
+
continue;
|
|
285
|
+
if (finding.found)
|
|
286
|
+
continue;
|
|
287
|
+
if (!finding.required)
|
|
288
|
+
continue;
|
|
289
|
+
finding.required = false;
|
|
290
|
+
finding.details =
|
|
291
|
+
`${finding.details} (Wave 25: demoted to advisory by track="${track}"` +
|
|
292
|
+
(taskClass ? `, taskClass="${taskClass}"` : "") +
|
|
293
|
+
").";
|
|
294
|
+
demotedSections.push(finding.section);
|
|
295
|
+
}
|
|
296
|
+
if (demotedSections.length > 0 && activeRunId) {
|
|
297
|
+
await recordArtifactValidationDemotedByTrack(projectRoot, {
|
|
298
|
+
stage,
|
|
299
|
+
track,
|
|
300
|
+
taskClass: taskClass ?? null,
|
|
301
|
+
runId: activeRunId,
|
|
302
|
+
sections: demotedSections
|
|
303
|
+
}).catch(() => { });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
260
306
|
const passed = findings.every((f) => !f.required || f.found);
|
|
261
307
|
return { stage, file: relFile, passed, findings };
|
|
262
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Wave 25 (v6.1.0) — section names whose required-finding outcome is
|
|
311
|
+
* demoted from blocking → advisory when
|
|
312
|
+
* `shouldDemoteArtifactValidationByTrack(track, taskClass)` returns
|
|
313
|
+
* `true`. Mirrors the user-reported quick-tier failure modes:
|
|
314
|
+
*
|
|
315
|
+
* - `Architecture Diagram` — sync/async + failure-edge enforcement
|
|
316
|
+
* - `Data Flow` — Interaction Edge Case mandatory rows
|
|
317
|
+
* - `Stale Diagram Drift Check` — blast-radius file mtime audit
|
|
318
|
+
* - `Expansion Strategist Delegation` — product-discovery delegation
|
|
319
|
+
*
|
|
320
|
+
* Findings remain in the result so the caller can surface them as
|
|
321
|
+
* advisory hints; only `required` flips to `false`.
|
|
322
|
+
*/
|
|
323
|
+
const ARTIFACT_VALIDATION_LITE_DEMOTE_SECTIONS = new Set([
|
|
324
|
+
"Architecture Diagram",
|
|
325
|
+
"Data Flow",
|
|
326
|
+
"Stale Diagram Drift Check",
|
|
327
|
+
"Expansion Strategist Delegation"
|
|
328
|
+
]);
|
|
@@ -84,6 +84,29 @@ export declare function mandatoryDelegationsForStage(stage: FlowStage, complexit
|
|
|
84
84
|
*/
|
|
85
85
|
export type MandatoryDelegationTaskClass = "software-standard" | "software-trivial" | "software-bugfix";
|
|
86
86
|
export declare function mandatoryAgentsFor(stage: FlowStage, track: FlowTrack, taskClass?: MandatoryDelegationTaskClass | null, complexityTier?: StageComplexityTier): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Wave 25 (v6.1.0) — track-aware artifact validation demotion.
|
|
89
|
+
*
|
|
90
|
+
* Mirrors `mandatoryAgentsFor`'s skip logic for the small-fix lanes.
|
|
91
|
+
* Returns `true` when artifact-level "advanced" validation rules
|
|
92
|
+
* (architecture-diagram async/failure edges, interaction edge-case
|
|
93
|
+
* mandatory rows, stale-diagram drift check, expansion-strategist
|
|
94
|
+
* delegation) should be DEMOTED from required → advisory.
|
|
95
|
+
*
|
|
96
|
+
* - `track === "quick"` — quick-tier runs (single-purpose
|
|
97
|
+
* landing-page edits, doc tweaks, config nudges). The advanced
|
|
98
|
+
* checks fire on architecture surfaces a quick-track artifact
|
|
99
|
+
* usually doesn't have. Same trigger as Wave 24 Phase B.
|
|
100
|
+
* - `taskClass === "software-bugfix"` — bugfixes carry RED-first
|
|
101
|
+
* repro coverage; tdd/review own the safety surface.
|
|
102
|
+
*
|
|
103
|
+
* When this returns `true`, the linter still runs the rules and prints
|
|
104
|
+
* their findings (so authors see them as advisory info), but does NOT
|
|
105
|
+
* block stage advance. An audit event of type
|
|
106
|
+
* `artifact_validation_demoted_by_track` is appended to
|
|
107
|
+
* `delegation-events.jsonl` once per stage advance for traceability.
|
|
108
|
+
*/
|
|
109
|
+
export declare function shouldDemoteArtifactValidationByTrack(track: FlowTrack, taskClass?: MandatoryDelegationTaskClass | null): boolean;
|
|
87
110
|
export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
|
|
88
111
|
export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
|
|
89
112
|
export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
@@ -818,6 +818,35 @@ export function mandatoryAgentsFor(stage, track, taskClass, complexityTier = "st
|
|
|
818
818
|
return [];
|
|
819
819
|
return mandatoryDelegationsForStage(stage, complexityTier);
|
|
820
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Wave 25 (v6.1.0) — track-aware artifact validation demotion.
|
|
823
|
+
*
|
|
824
|
+
* Mirrors `mandatoryAgentsFor`'s skip logic for the small-fix lanes.
|
|
825
|
+
* Returns `true` when artifact-level "advanced" validation rules
|
|
826
|
+
* (architecture-diagram async/failure edges, interaction edge-case
|
|
827
|
+
* mandatory rows, stale-diagram drift check, expansion-strategist
|
|
828
|
+
* delegation) should be DEMOTED from required → advisory.
|
|
829
|
+
*
|
|
830
|
+
* - `track === "quick"` — quick-tier runs (single-purpose
|
|
831
|
+
* landing-page edits, doc tweaks, config nudges). The advanced
|
|
832
|
+
* checks fire on architecture surfaces a quick-track artifact
|
|
833
|
+
* usually doesn't have. Same trigger as Wave 24 Phase B.
|
|
834
|
+
* - `taskClass === "software-bugfix"` — bugfixes carry RED-first
|
|
835
|
+
* repro coverage; tdd/review own the safety surface.
|
|
836
|
+
*
|
|
837
|
+
* When this returns `true`, the linter still runs the rules and prints
|
|
838
|
+
* their findings (so authors see them as advisory info), but does NOT
|
|
839
|
+
* block stage advance. An audit event of type
|
|
840
|
+
* `artifact_validation_demoted_by_track` is appended to
|
|
841
|
+
* `delegation-events.jsonl` once per stage advance for traceability.
|
|
842
|
+
*/
|
|
843
|
+
export function shouldDemoteArtifactValidationByTrack(track, taskClass) {
|
|
844
|
+
if (track === "quick")
|
|
845
|
+
return true;
|
|
846
|
+
if (taskClass === "software-bugfix")
|
|
847
|
+
return true;
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
821
850
|
export function stageSchema(stage, track = "standard") {
|
|
822
851
|
const rawInput = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
|
|
823
852
|
const base = normalizeStageSchemaInput(rawInput);
|
package/dist/delegation.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SubagentFallback } from "./harness-adapters.js";
|
|
2
2
|
import { type MandatoryDelegationTaskClass } from "./content/stage-schema.js";
|
|
3
3
|
import type { FlowStage } from "./types.js";
|
|
4
|
+
import type { FlowState } from "./flow-state.js";
|
|
4
5
|
export type DelegationMode = "mandatory" | "proactive";
|
|
5
6
|
export type DelegationStatus = "scheduled" | "launched" | "acknowledged" | "completed" | "failed" | "waived" | "stale";
|
|
6
7
|
export declare const DELEGATION_DISPATCH_SURFACES: readonly ["claude-task", "cursor-task", "opencode-agent", "codex-agent", "generic-task", "role-switch", "manual"];
|
|
@@ -148,7 +149,10 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
|
|
|
148
149
|
* Optional task class for the active run. When set to
|
|
149
150
|
* `"software-bugfix"`, the mandatory delegation gate is skipped
|
|
150
151
|
* entirely (Wave 24). Callers that don't classify the run leave
|
|
151
|
-
* this undefined
|
|
152
|
+
* this undefined; the function then falls back to
|
|
153
|
+
* `flowState.taskClass` (persisted in `flow-state.json`) so the
|
|
154
|
+
* Wave 24 bugfix-skip remains active across the `cclaw advance-stage`
|
|
155
|
+
* code path even when no caller forwards an explicit override.
|
|
152
156
|
*/
|
|
153
157
|
taskClass?: MandatoryDelegationTaskClass | null;
|
|
154
158
|
}): Promise<{
|
|
@@ -177,3 +181,38 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
|
|
|
177
181
|
*/
|
|
178
182
|
skippedByTrack: boolean;
|
|
179
183
|
}>;
|
|
184
|
+
/**
|
|
185
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
186
|
+
* that one or more required artifact-validation findings were
|
|
187
|
+
* demoted from blocking to advisory because the active run is on a
|
|
188
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
189
|
+
*
|
|
190
|
+
* The event mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
191
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
192
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
193
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
194
|
+
*/
|
|
195
|
+
export declare function recordArtifactValidationDemotedByTrack(projectRoot: string, params: {
|
|
196
|
+
stage: FlowStage;
|
|
197
|
+
track: FlowState["track"];
|
|
198
|
+
taskClass: MandatoryDelegationTaskClass | null;
|
|
199
|
+
runId: string;
|
|
200
|
+
sections: string[];
|
|
201
|
+
}): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
204
|
+
* that the scope-stage Expansion Strategist (`product-discovery`)
|
|
205
|
+
* delegation requirement was skipped because the active run is on a
|
|
206
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
207
|
+
*
|
|
208
|
+
* Mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
209
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
210
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
211
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
212
|
+
*/
|
|
213
|
+
export declare function recordExpansionStrategistSkippedByTrack(projectRoot: string, params: {
|
|
214
|
+
track: FlowState["track"];
|
|
215
|
+
taskClass: MandatoryDelegationTaskClass | null;
|
|
216
|
+
runId: string;
|
|
217
|
+
selectedScopeMode: string;
|
|
218
|
+
}): Promise<void>;
|