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
|
@@ -1,2 +1,18 @@
|
|
|
1
1
|
import { type StageLintContext } from "./shared.js";
|
|
2
|
+
export interface CodebaseInvestigationFileRef {
|
|
3
|
+
/** Filename to stat (parenthetical suffix already stripped). */
|
|
4
|
+
filename: string;
|
|
5
|
+
/** Raw cell content, useful for diagnostic messages. */
|
|
6
|
+
raw: string;
|
|
7
|
+
/** When true, the audit treats this row as a "new file, no baseline". */
|
|
8
|
+
newFile: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* When true, the audit skips this row entirely (suffix `(skip)`,
|
|
11
|
+
* `(deleted)`, `(stub)`, leading `#`, or a `skip:` token in the
|
|
12
|
+
* Notes column).
|
|
13
|
+
*/
|
|
14
|
+
skip: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizeCodebaseInvestigationFileRef(value: string, notesCell: string): CodebaseInvestigationFileRef | null;
|
|
17
|
+
export declare function collectCodebaseInvestigationFiles(sectionBody: string): CodebaseInvestigationFileRef[];
|
|
2
18
|
export declare function lintDesignStage(ctx: StageLintContext): Promise<void>;
|
|
@@ -110,25 +110,73 @@ async function resolveDesignDiagramTier(projectRoot, track, designRaw) {
|
|
|
110
110
|
}
|
|
111
111
|
return { tier: "standard", source: "default:standard" };
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Wave 25 (v6.1.0) — parenthetical suffixes that the audit strips
|
|
115
|
+
* from a Codebase Investigation filename cell BEFORE attempting
|
|
116
|
+
* `fs.stat`. The user's quick-tier test wrote `index.html (new)` in
|
|
117
|
+
* the table, and the linter then tried to stat the literal string
|
|
118
|
+
* `index.html (new)` (with the suffix) and failed with "could not
|
|
119
|
+
* read blast-radius file(s): index.html (new)". Authors used these
|
|
120
|
+
* markers as informational labels, not as part of the filename.
|
|
121
|
+
*
|
|
122
|
+
* Stripping happens for ANY parenthetical suffix on the same line as
|
|
123
|
+
* the filename cell so we don't have to enumerate every author
|
|
124
|
+
* convention. For new files (suffix "new"), the audit records
|
|
125
|
+
* "new file, no stale diagrams to detect" instead of trying to stat.
|
|
126
|
+
*/
|
|
127
|
+
const STALE_DIAGRAM_NEW_FILE_SUFFIX_PATTERN = /\(\s*new(?:[\s-]?file)?\s*\)/iu;
|
|
128
|
+
const STALE_DIAGRAM_SKIP_FILE_SUFFIX_PATTERN = /\(\s*(?:n\/a|skip|skipped|deleted|removed|stub|placeholder|tbd)\s*\)/iu;
|
|
129
|
+
export function normalizeCodebaseInvestigationFileRef(value, notesCell) {
|
|
130
|
+
const cleanedFull = value
|
|
115
131
|
.replace(/`/gu, "")
|
|
116
132
|
.replace(/^\s*[-*]\s*/u, "")
|
|
117
133
|
.trim();
|
|
118
|
-
if (!
|
|
134
|
+
if (!cleanedFull)
|
|
119
135
|
return null;
|
|
120
|
-
if (
|
|
136
|
+
if (/^#/u.test(cleanedFull)) {
|
|
137
|
+
return { filename: cleanedFull.replace(/^#\s*/u, ""), raw: cleanedFull, newFile: false, skip: true };
|
|
138
|
+
}
|
|
139
|
+
// Strip ANY trailing parenthetical suffix(es) so the audit operates
|
|
140
|
+
// on the raw filename. We loop because authors sometimes stack
|
|
141
|
+
// multiple suffixes (`index.html (new) (stub)`).
|
|
142
|
+
let stripped = cleanedFull;
|
|
143
|
+
let newFile = false;
|
|
144
|
+
let skip = false;
|
|
145
|
+
for (let safety = 0; safety < 4; safety += 1) {
|
|
146
|
+
const trailingParen = /\s*\([^)]*\)\s*$/u.exec(stripped);
|
|
147
|
+
if (!trailingParen)
|
|
148
|
+
break;
|
|
149
|
+
const parenText = trailingParen[0];
|
|
150
|
+
if (STALE_DIAGRAM_NEW_FILE_SUFFIX_PATTERN.test(parenText))
|
|
151
|
+
newFile = true;
|
|
152
|
+
if (STALE_DIAGRAM_SKIP_FILE_SUFFIX_PATTERN.test(parenText))
|
|
153
|
+
skip = true;
|
|
154
|
+
stripped = stripped.slice(0, trailingParen.index).trim();
|
|
155
|
+
}
|
|
156
|
+
if (!stripped)
|
|
157
|
+
return null;
|
|
158
|
+
if (/^(?:file|n\/a|none|\(none\)|tbd|\?)$/iu.test(stripped))
|
|
121
159
|
return null;
|
|
122
|
-
|
|
160
|
+
// Notes column may carry an explicit `skip:` marker (Wave 25).
|
|
161
|
+
if (/(?:^|\s|\|)skip\s*:/iu.test(notesCell))
|
|
162
|
+
skip = true;
|
|
163
|
+
return { filename: stripped, raw: cleanedFull, newFile, skip };
|
|
123
164
|
}
|
|
124
|
-
function collectCodebaseInvestigationFiles(sectionBody) {
|
|
165
|
+
export function collectCodebaseInvestigationFiles(sectionBody) {
|
|
125
166
|
const refs = [];
|
|
167
|
+
const seen = new Set();
|
|
126
168
|
for (const row of getMarkdownTableRows(sectionBody)) {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
const notesCell = row[row.length - 1] ?? "";
|
|
170
|
+
const fileCell = normalizeCodebaseInvestigationFileRef(row[0] ?? "", notesCell);
|
|
171
|
+
if (!fileCell)
|
|
172
|
+
continue;
|
|
173
|
+
const key = `${fileCell.filename}|${fileCell.skip}|${fileCell.newFile}`;
|
|
174
|
+
if (seen.has(key))
|
|
175
|
+
continue;
|
|
176
|
+
seen.add(key);
|
|
177
|
+
refs.push(fileCell);
|
|
130
178
|
}
|
|
131
|
-
return
|
|
179
|
+
return refs;
|
|
132
180
|
}
|
|
133
181
|
async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, codebaseInvestigationBody) {
|
|
134
182
|
const markerCount = (artifactRaw.match(/<!--\s*diagram:\s*[a-z0-9-]+\s*-->/giu) ?? []).length;
|
|
@@ -157,11 +205,21 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
|
|
|
157
205
|
}
|
|
158
206
|
const stale = [];
|
|
159
207
|
const missing = [];
|
|
208
|
+
const newFiles = [];
|
|
209
|
+
const skipped = [];
|
|
160
210
|
let scanned = 0;
|
|
161
211
|
for (const ref of refs) {
|
|
162
|
-
|
|
212
|
+
if (ref.skip) {
|
|
213
|
+
skipped.push(ref.filename);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (ref.newFile) {
|
|
217
|
+
newFiles.push(ref.filename);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const absPath = path.isAbsolute(ref.filename) ? ref.filename : path.join(projectRoot, ref.filename);
|
|
163
221
|
if (!(await exists(absPath))) {
|
|
164
|
-
missing.push(ref);
|
|
222
|
+
missing.push(ref.filename);
|
|
165
223
|
continue;
|
|
166
224
|
}
|
|
167
225
|
let fileStat;
|
|
@@ -169,23 +227,29 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
|
|
|
169
227
|
fileStat = await fs.stat(absPath);
|
|
170
228
|
}
|
|
171
229
|
catch {
|
|
172
|
-
missing.push(ref);
|
|
230
|
+
missing.push(ref.filename);
|
|
173
231
|
continue;
|
|
174
232
|
}
|
|
175
233
|
if (!fileStat.isFile())
|
|
176
234
|
continue;
|
|
177
235
|
scanned += 1;
|
|
178
236
|
if (fileStat.mtimeMs > artifactStat.mtimeMs) {
|
|
179
|
-
stale.push(ref);
|
|
237
|
+
stale.push(ref.filename);
|
|
180
238
|
}
|
|
181
239
|
}
|
|
182
240
|
if (missing.length > 0) {
|
|
183
241
|
return {
|
|
184
242
|
ok: false,
|
|
185
|
-
details: `Stale Diagram Audit could not read blast-radius file(s): ${missing.join(", ")}.`
|
|
243
|
+
details: `Stale Diagram Audit could not read blast-radius file(s): ${missing.join(", ")}. Strip parenthetical suffixes like \` (new)\`, \` (deleted)\`, \` (stub)\` from the filename column, mark new files as \`<path> (new)\`, or add a leading \`#\` to the filename to skip the row.`
|
|
186
244
|
};
|
|
187
245
|
}
|
|
188
|
-
|
|
246
|
+
const noteParts = [];
|
|
247
|
+
if (skipped.length > 0)
|
|
248
|
+
noteParts.push(`${skipped.length} skipped (${skipped.join(", ")})`);
|
|
249
|
+
if (newFiles.length > 0)
|
|
250
|
+
noteParts.push(`${newFiles.length} new file(s) with no stale diagrams to detect (${newFiles.join(", ")})`);
|
|
251
|
+
const notes = noteParts.length > 0 ? `; ${noteParts.join("; ")}` : "";
|
|
252
|
+
if (scanned === 0 && newFiles.length === 0 && skipped.length === 0) {
|
|
189
253
|
return {
|
|
190
254
|
ok: false,
|
|
191
255
|
details: "Stale Diagram Audit found no readable blast-radius files in Codebase Investigation."
|
|
@@ -194,12 +258,12 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
|
|
|
194
258
|
if (stale.length > 0) {
|
|
195
259
|
return {
|
|
196
260
|
ok: false,
|
|
197
|
-
details: `Stale Diagram Audit flagged stale file(s) newer than diagram baseline: ${stale.join(", ")}.`
|
|
261
|
+
details: `Stale Diagram Audit flagged stale file(s) newer than diagram baseline: ${stale.join(", ")}${notes}.`
|
|
198
262
|
};
|
|
199
263
|
}
|
|
200
264
|
return {
|
|
201
265
|
ok: true,
|
|
202
|
-
details: `Stale Diagram Audit clear: ${scanned} blast-radius file(s) are not newer than diagram baseline.`
|
|
266
|
+
details: `Stale Diagram Audit clear: ${scanned} blast-radius file(s) are not newer than diagram baseline${notes}.`
|
|
203
267
|
};
|
|
204
268
|
}
|
|
205
269
|
export async function lintDesignStage(ctx) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { checkCriticPredictionsContract, evaluateQaLogFloor, sectionBodyByHeadingPrefix, sectionBodyByName, extractCanonicalScopeMode, getMarkdownTableRows } from "./shared.js";
|
|
2
|
-
import { readDelegationLedger } from "../delegation.js";
|
|
2
|
+
import { readDelegationLedger, recordExpansionStrategistSkippedByTrack } from "../delegation.js";
|
|
3
|
+
import { shouldDemoteArtifactValidationByTrack } from "../content/stage-schema.js";
|
|
4
|
+
import { readFlowState } from "../run-persistence.js";
|
|
3
5
|
export async function lintScopeStage(ctx) {
|
|
4
|
-
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride, activeStageFlags } = ctx;
|
|
6
|
+
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride, activeStageFlags, taskClass } = ctx;
|
|
5
7
|
const lockedDecisionsBody = sectionBodyByHeadingPrefix(sections, "Locked Decisions") ?? "";
|
|
6
8
|
const scopeSummaryBody = sectionBodyByName(sections, "Scope Summary") ?? "";
|
|
7
9
|
const selectedScopeMode = extractCanonicalScopeMode(scopeSummaryBody);
|
|
@@ -32,24 +34,61 @@ export async function lintScopeStage(ctx) {
|
|
|
32
34
|
}
|
|
33
35
|
const strategistRequired = selectedScopeMode === "SCOPE EXPANSION" || selectedScopeMode === "SELECTIVE EXPANSION";
|
|
34
36
|
if (strategistRequired) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
37
|
+
// Wave 25 (v6.1.0) — for `track === "quick"` (lite-tier) or
|
|
38
|
+
// `taskClass === "software-bugfix"`, the Expansion Strategist
|
|
39
|
+
// delegation requirement is dropped entirely. The user's
|
|
40
|
+
// 3-file static landing page hit this gate without any
|
|
41
|
+
// discovery scope — pure ceremony for trivial work. Standard
|
|
42
|
+
// tracks remain unchanged.
|
|
43
|
+
const skipByTrack = shouldDemoteArtifactValidationByTrack(track, taskClass);
|
|
44
|
+
if (skipByTrack) {
|
|
45
|
+
findings.push({
|
|
46
|
+
section: "Expansion Strategist Delegation",
|
|
47
|
+
required: false,
|
|
48
|
+
rule: "When Scope Summary selects SCOPE EXPANSION or SELECTIVE EXPANSION, a completed `product-discovery` delegation for the active run with non-empty evidenceRefs is required.",
|
|
49
|
+
found: true,
|
|
50
|
+
details: `Expansion Strategist delegation requirement skipped for track="${track}"` +
|
|
51
|
+
(taskClass ? `, taskClass="${taskClass}"` : "") +
|
|
52
|
+
` (Wave 25: lite-tier escape; selectedMode=${selectedScopeMode}).`
|
|
53
|
+
});
|
|
54
|
+
// Best-effort audit; we read the flow-state runId here
|
|
55
|
+
// because StageLintContext does not surface it directly.
|
|
56
|
+
try {
|
|
57
|
+
const flowState = await readFlowState(projectRoot);
|
|
58
|
+
const runId = flowState.activeRunId ?? null;
|
|
59
|
+
if (runId) {
|
|
60
|
+
await recordExpansionStrategistSkippedByTrack(projectRoot, {
|
|
61
|
+
track,
|
|
62
|
+
taskClass: taskClass ?? null,
|
|
63
|
+
runId,
|
|
64
|
+
selectedScopeMode
|
|
65
|
+
}).catch(() => { });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Audit is best-effort; never block scope linting.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const delegationLedger = await readDelegationLedger(projectRoot);
|
|
74
|
+
const discoveryRows = delegationLedger.entries.filter((entry) => entry.stage === "scope" &&
|
|
75
|
+
entry.agent === "product-discovery" &&
|
|
76
|
+
entry.runId === delegationLedger.runId &&
|
|
77
|
+
entry.status === "completed");
|
|
78
|
+
const hasCompleted = discoveryRows.length > 0;
|
|
79
|
+
const hasEvidence = discoveryRows.some((entry) => Array.isArray(entry.evidenceRefs) && entry.evidenceRefs.length > 0);
|
|
80
|
+
findings.push({
|
|
81
|
+
section: "Expansion Strategist Delegation",
|
|
82
|
+
required: true,
|
|
83
|
+
rule: "When Scope Summary selects SCOPE EXPANSION or SELECTIVE EXPANSION, a completed `product-discovery` delegation for the active run with non-empty evidenceRefs is required.",
|
|
84
|
+
found: hasCompleted && hasEvidence,
|
|
85
|
+
details: !hasCompleted
|
|
86
|
+
? `Scope mode ${selectedScopeMode} requires a completed product-discovery delegation row for active run ${delegationLedger.runId}.`
|
|
87
|
+
: hasEvidence
|
|
88
|
+
? `product-discovery delegation satisfied for mode ${selectedScopeMode}.`
|
|
89
|
+
: "product-discovery delegation exists but evidenceRefs is empty; add at least one artifact/code evidence reference."
|
|
90
|
+
});
|
|
91
|
+
}
|
|
53
92
|
}
|
|
54
93
|
const criticPredictions = checkCriticPredictionsContract(sections);
|
|
55
94
|
if (criticPredictions !== null) {
|
|
@@ -236,7 +236,34 @@ export interface InteractionEdgeCaseRequirement {
|
|
|
236
236
|
pattern: RegExp;
|
|
237
237
|
}
|
|
238
238
|
export declare const INTERACTION_EDGE_CASE_REQUIREMENTS: readonly InteractionEdgeCaseRequirement[];
|
|
239
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Wave 25 (v6.1.0) — context for `validateInteractionEdgeCaseMatrix`.
|
|
241
|
+
*
|
|
242
|
+
* The user's quick-tier test of a 3-file static landing page hit
|
|
243
|
+
* "Interaction Edge Case row \"nav-away-mid-request\" must mark
|
|
244
|
+
* Handled? as yes/no" because they wrote `N/A` (no network at all).
|
|
245
|
+
* Then `unhandled must reference a deferred item id (for example
|
|
246
|
+
* D-12)`. Wave 25 introduces:
|
|
247
|
+
*
|
|
248
|
+
* 1. `N/A — <reason>` (em-dash + free-text reason) is now an
|
|
249
|
+
* accepted Handled? value. The reason replaces the D-XX
|
|
250
|
+
* requirement.
|
|
251
|
+
* 2. When the caller signals lite-tier and the design has no
|
|
252
|
+
* network/external dependencies (detected via the Architecture
|
|
253
|
+
* Diagram body or a missing Failure Mode Table), the standard
|
|
254
|
+
* mandatory rows (`nav-away-mid-request`, `10K-result dataset`,
|
|
255
|
+
* `background-job abandonment`, `zombie connection`) are
|
|
256
|
+
* treated as advisory rather than required. The `double-click`
|
|
257
|
+
* row stays mandatory because UI duplicate-action handling is
|
|
258
|
+
* relevant even for static pages.
|
|
259
|
+
*/
|
|
260
|
+
export interface InteractionEdgeCaseValidationContext {
|
|
261
|
+
/** Optional H2 sections map for cross-section "no network" detection. */
|
|
262
|
+
sections?: H2SectionMap | null;
|
|
263
|
+
/** When true, network-dependent mandatory rows become advisory. */
|
|
264
|
+
liteTier?: boolean;
|
|
265
|
+
}
|
|
266
|
+
export declare function validateInteractionEdgeCaseMatrix(sectionBody: string, context?: InteractionEdgeCaseValidationContext): {
|
|
240
267
|
ok: boolean;
|
|
241
268
|
details: string;
|
|
242
269
|
};
|
|
@@ -251,14 +278,76 @@ export declare function validatePreScopeSystemAudit(sectionBody: string): {
|
|
|
251
278
|
export declare const DIAGRAM_ARROW_PATTERN: RegExp;
|
|
252
279
|
export declare const DIAGRAM_FAILURE_EDGE_PATTERN: RegExp;
|
|
253
280
|
export declare const DIAGRAM_GENERIC_NODE_PATTERN: RegExp;
|
|
281
|
+
/**
|
|
282
|
+
* Wave 25 (v6.1.0) — external-dependency keywords that trigger the
|
|
283
|
+
* failure-edge requirement. The architecture diagram is allowed to
|
|
284
|
+
* omit failure edges only when ALL of:
|
|
285
|
+
* - Failure Mode Table has zero rows.
|
|
286
|
+
* - The diagram body mentions no external-dependency keyword.
|
|
287
|
+
*
|
|
288
|
+
* Static landing pages (3 HTML/CSS/JS files, no network) match this:
|
|
289
|
+
* no failure modes to map, no external systems to fail. The previous
|
|
290
|
+
* blanket "must include at least one failure-edge" rule produced
|
|
291
|
+
* ceremony-only failures that the agent worked around with fake
|
|
292
|
+
* `(timeout)` annotations, defeating the spirit of the rule.
|
|
293
|
+
*/
|
|
294
|
+
export declare const DIAGRAM_EXTERNAL_DEPENDENCY_PATTERN: RegExp;
|
|
254
295
|
export declare const TEST_COMMAND_MARKER_PATTERN: RegExp;
|
|
255
296
|
export declare const RED_FAILURE_MARKER_PATTERN: RegExp;
|
|
256
297
|
export declare const GREEN_SUCCESS_MARKER_PATTERN: RegExp;
|
|
257
298
|
export declare function diagramEdgeLines(sectionBody: string): string[];
|
|
258
299
|
export declare function hasFailureEdgeInDiagram(sectionBody: string): boolean;
|
|
259
300
|
export declare function hasLabeledDiagramArrow(lines: string[]): boolean;
|
|
301
|
+
/**
|
|
302
|
+
* Wave 25 (v6.1.0) — accepted async edge patterns. Returns true when
|
|
303
|
+
* a line carries any of:
|
|
304
|
+
*
|
|
305
|
+
* - `-.->`, `-->>`, `~~>` (mermaid dotted/messaging arrows)
|
|
306
|
+
* - `- - ->` (loose dotted ASCII arrow with optional spaces)
|
|
307
|
+
* - `.....>` (3-or-more dots followed by `>`)
|
|
308
|
+
* - `\basync\b` text token (label-based)
|
|
309
|
+
* - `[async]` bracketed label, `async:` prefix, `async:` cell content
|
|
310
|
+
*
|
|
311
|
+
* The error message printed when this fails (see
|
|
312
|
+
* `validateArchitectureDiagram`) lists every accepted pattern
|
|
313
|
+
* verbatim so the agent does not have to guess.
|
|
314
|
+
*/
|
|
260
315
|
export declare function hasAsyncDiagramEdge(lines: string[]): boolean;
|
|
316
|
+
/**
|
|
317
|
+
* Wave 25 (v6.1.0) — accepted sync edge patterns. Returns true when a
|
|
318
|
+
* line carries any of:
|
|
319
|
+
*
|
|
320
|
+
* - `\bsync\b` text token (label-based)
|
|
321
|
+
* - `[sync]` bracketed label, `sync:` prefix, `sync:` cell content
|
|
322
|
+
* - Solid `-->`, `->`, `=>`, `→`, `⟶`, `↦` arrow that is NOT a known
|
|
323
|
+
* dotted/async variant (`-.->`, `-->>`, `~~>`)
|
|
324
|
+
* - `===>` (3+ `=` then `>`) and `--->` (3+ `-` then `>`) heavy solid
|
|
325
|
+
* arrows
|
|
326
|
+
*/
|
|
261
327
|
export declare function hasSyncDiagramEdge(lines: string[]): boolean;
|
|
328
|
+
/**
|
|
329
|
+
* Wave 25 (v6.1.0) — exact accepted-pattern list shown in the error
|
|
330
|
+
* message when sync/async distinction fails. Keep in sync with
|
|
331
|
+
* `hasAsyncDiagramEdge` / `hasSyncDiagramEdge` above.
|
|
332
|
+
*/
|
|
333
|
+
export declare const DIAGRAM_SYNC_ASYNC_ACCEPTED_PATTERNS: readonly ["Solid arrows: `-->`, `->`, `===>`, `--->`, `=>`, `→`, `⟶`, `↦`", "Dotted/async arrows: `-.->`, `-->>`, `~~>`, `- - ->`, `.....>`", "Text labels on the same line: `sync` / `async`", "Bracket labels: `[sync]` / `[async]`", "Cell-prefix labels: `sync:` / `async:` (e.g. `A -->|sync: persist| B`)"];
|
|
334
|
+
export interface ArchitectureDiagramValidationContext {
|
|
335
|
+
/** Optional H2 sections map for cross-section checks (e.g. Failure Mode Table presence). */
|
|
336
|
+
sections?: H2SectionMap | null;
|
|
337
|
+
}
|
|
338
|
+
export interface ArchitectureDiagramValidationResult {
|
|
339
|
+
ok: boolean;
|
|
340
|
+
details: string;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Wave 25 (v6.1.0) — Architecture Diagram structural check.
|
|
344
|
+
*
|
|
345
|
+
* Promoted out of `validateSectionBody` so it can take a `sections`
|
|
346
|
+
* map and conditionally enforce the failure-edge rule based on
|
|
347
|
+
* cross-section context (Failure Mode Table presence + diagram body
|
|
348
|
+
* mentioning external-dependency keywords).
|
|
349
|
+
*/
|
|
350
|
+
export declare function validateArchitectureDiagram(sectionBody: string, context?: ArchitectureDiagramValidationContext): ArchitectureDiagramValidationResult;
|
|
262
351
|
export declare function validateTddRedEvidence(sectionBody: string): {
|
|
263
352
|
ok: boolean;
|
|
264
353
|
details: string;
|
|
@@ -334,7 +423,24 @@ export declare function collectPatternHits(text: string, patterns: Array<{
|
|
|
334
423
|
label: string;
|
|
335
424
|
regex: RegExp;
|
|
336
425
|
}>): string[];
|
|
337
|
-
export
|
|
426
|
+
export interface ValidateSectionBodyContext {
|
|
427
|
+
/**
|
|
428
|
+
* Wave 25 (v6.1.0) — optional H2 sections map for cross-section
|
|
429
|
+
* checks (e.g. Architecture Diagram failure-edge enforcement gates
|
|
430
|
+
* on Failure Mode Table presence). When omitted, cross-section
|
|
431
|
+
* checks fall back to legacy blanket enforcement.
|
|
432
|
+
*/
|
|
433
|
+
sections?: H2SectionMap | null;
|
|
434
|
+
/**
|
|
435
|
+
* Wave 25 (v6.1.0) — when true, lite-tier-only relaxations apply.
|
|
436
|
+
* Currently used by the Interaction Edge Case matrix to demote
|
|
437
|
+
* network-dependent mandatory rows to advisory when the design has
|
|
438
|
+
* no Failure Mode Table rows and no external-dependency keywords
|
|
439
|
+
* in the Architecture Diagram body.
|
|
440
|
+
*/
|
|
441
|
+
liteTier?: boolean;
|
|
442
|
+
}
|
|
443
|
+
export declare function validateSectionBody(sectionBody: string, rule: string, sectionName: string, context?: ValidateSectionBodyContext): {
|
|
338
444
|
ok: boolean;
|
|
339
445
|
details: string;
|
|
340
446
|
};
|
|
@@ -360,4 +466,14 @@ export interface StageLintContext {
|
|
|
360
466
|
* When orchestrator cannot read flow-state, defaults to an empty array.
|
|
361
467
|
*/
|
|
362
468
|
activeStageFlags: string[];
|
|
469
|
+
/**
|
|
470
|
+
* Wave 25 (v6.1.0) — task class for the active run, mirrored from
|
|
471
|
+
* `flow-state.json::taskClass`. `null` when not classified. Stage
|
|
472
|
+
* linters read this together with `track` via
|
|
473
|
+
* `shouldDemoteArtifactValidationByTrack` to demote advanced
|
|
474
|
+
* artifact-level checks (architecture diagram async/failure edges,
|
|
475
|
+
* interaction edge-case mandatory rows, stale-diagram drift,
|
|
476
|
+
* expansion-strategist delegation) from required → advisory.
|
|
477
|
+
*/
|
|
478
|
+
taskClass: "software-standard" | "software-trivial" | "software-bugfix" | null;
|
|
363
479
|
}
|