cclaw-cli 6.9.0 → 6.11.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.
@@ -3,6 +3,8 @@ import { resolveArtifactPath as resolveStageArtifactPath } from "../artifact-pat
3
3
  import { exists } from "../fs-utils.js";
4
4
  import { FORBIDDEN_PLACEHOLDER_TOKENS, CONFIDENCE_FINDING_REGEX_SOURCE } from "../content/skills.js";
5
5
  import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { PLAN_SPLIT_SMALL_PLAN_THRESHOLD, parseImplementationUnits } from "../internal/plan-split-waves.js";
6
8
  export async function lintPlanStage(ctx) {
7
9
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
8
10
  evaluateInvestigationTrace(ctx, "Implementation Units");
@@ -114,6 +116,41 @@ export async function lintPlanStage(ctx) {
114
116
  ? "No forbidden placeholder tokens detected outside the rule section."
115
117
  : `Detected forbidden token(s) elsewhere in plan: ${filteredPlanHits.join(", ")}.`
116
118
  });
119
+ // v6.10.0 (P4) — advisory `plan_too_large_no_waves`. Fires when a
120
+ // standard-track plan has more than the wave-split threshold of
121
+ // implementation units AND the wave-plans/ directory is empty.
122
+ // Linter advisories never block stage-complete (`required: false`),
123
+ // so the agent gets a nudge to run `cclaw-cli internal plan-split-waves`
124
+ // without the plan stage failing.
125
+ try {
126
+ const planUnits = parseImplementationUnits(raw);
127
+ if (planUnits.length > PLAN_SPLIT_SMALL_PLAN_THRESHOLD) {
128
+ const artifactsDir = path.dirname(absFile);
129
+ const wavePlansDir = path.join(artifactsDir, "wave-plans");
130
+ let wavePlansHasContent = false;
131
+ try {
132
+ const dirEntries = await fs.readdir(wavePlansDir);
133
+ wavePlansHasContent = dirEntries.some((name) => /^wave-\d+\.md$/u.test(name));
134
+ }
135
+ catch {
136
+ wavePlansHasContent = false;
137
+ }
138
+ if (!wavePlansHasContent) {
139
+ findings.push({
140
+ section: "plan_too_large_no_waves",
141
+ required: false,
142
+ rule: "Plans with > 50 implementation units benefit from being split into manageable waves via `cclaw-cli internal plan-split-waves`.",
143
+ found: false,
144
+ details: `Plan has ${planUnits.length} implementation unit(s) (threshold ${PLAN_SPLIT_SMALL_PLAN_THRESHOLD}) and no wave-plans/ directory yet. ` +
145
+ "Run `cclaw-cli internal plan-split-waves` to break this plan into manageable waves; the linter is advisory only and will not block stage-complete."
146
+ });
147
+ }
148
+ }
149
+ }
150
+ catch {
151
+ // Parser errors should never block the linter — the advisory is
152
+ // purely a nudge.
153
+ }
117
154
  const handoffBody = sectionBodyByName(sections, "Execution Handoff");
118
155
  if (handoffBody !== null) {
119
156
  const ok = /(subagent-driven|inline executor)/iu.test(handoffBody);
@@ -395,11 +395,49 @@ export interface ArchitectureDiagramValidationResult {
395
395
  * mentioning external-dependency keywords).
396
396
  */
397
397
  export declare function validateArchitectureDiagram(sectionBody: string, context?: ArchitectureDiagramValidationContext): ArchitectureDiagramValidationResult;
398
- export declare function validateTddRedEvidence(sectionBody: string): {
398
+ /**
399
+ * v6.10.0 (T3) — pointer-mode evidence acceptance. RED/GREEN sections may
400
+ * substitute pasted stdout with a single line of the form
401
+ * `Evidence: <relative-or-abs-path>` or `Evidence: spanId:<id>`. The
402
+ * validator alone cannot reach the filesystem or delegation ledger
403
+ * synchronously, so the lint pipeline pre-resolves pointers and then
404
+ * passes booleans through these option flags.
405
+ */
406
+ export interface TddEvidencePointerOptions {
407
+ /**
408
+ * True when the section body has at least one `Evidence:` pointer line
409
+ * AND the pointer resolved to either an existing file or a known
410
+ * delegation spanId. The validator then short-circuits without
411
+ * requiring pasted stdout markers.
412
+ */
413
+ pointerSatisfied?: boolean;
414
+ /**
415
+ * v6.11.0 (D5) — true when `delegation-events.jsonl` carries at least
416
+ * one slice-tagged event for the current run with the matching phase
417
+ * (`phase=red` for RED, `phase=green` for GREEN) and a non-empty
418
+ * `evidenceRefs` array. Phase events are the new source of truth in
419
+ * v6.11.0, so the markdown evidence block is auto-satisfied without
420
+ * requiring hand-pasted stdout content.
421
+ */
422
+ phaseEventsSatisfied?: boolean;
423
+ }
424
+ /**
425
+ * Sync helper that scans for `Evidence:` lines in a section body and
426
+ * returns the trimmed value of each. Used by the lint pipeline to
427
+ * pre-resolve pointers (filesystem path-existence or delegation ledger
428
+ * spanId match) before invoking the validators.
429
+ *
430
+ * Recognised forms:
431
+ * Evidence: <path>
432
+ * Evidence: spanId:<id>
433
+ * - Evidence: <path>
434
+ */
435
+ export declare function extractEvidencePointers(sectionBody: string): string[];
436
+ export declare function validateTddRedEvidence(sectionBody: string, opts?: TddEvidencePointerOptions): {
399
437
  ok: boolean;
400
438
  details: string;
401
439
  };
402
- export declare function validateTddGreenEvidence(sectionBody: string): {
440
+ export declare function validateTddGreenEvidence(sectionBody: string, opts?: TddEvidencePointerOptions): {
403
441
  ok: boolean;
404
442
  details: string;
405
443
  };
@@ -543,6 +581,17 @@ export interface ValidateSectionBodyContext {
543
581
  * in the Architecture Diagram body.
544
582
  */
545
583
  liteTier?: boolean;
584
+ /**
585
+ * v6.10.0 (T3) — pre-resolved RED/GREEN Evidence pointer state. The
586
+ * artifact linter resolves `Evidence: <path|spanId:...>` lines and
587
+ * inspects the TDD slice sidecar before invoking
588
+ * `validateSectionBody`; the resulting booleans here let the
589
+ * validator short-circuit without re-doing async work.
590
+ */
591
+ tddEvidence?: {
592
+ red?: TddEvidencePointerOptions;
593
+ green?: TddEvidencePointerOptions;
594
+ };
546
595
  }
547
596
  export declare function validateSectionBody(sectionBody: string, rule: string, sectionName: string, context?: ValidateSectionBodyContext): {
548
597
  ok: boolean;
@@ -1417,7 +1417,43 @@ function shouldEnforceFailureEdge(diagramBody, context) {
1417
1417
  return true;
1418
1418
  return false;
1419
1419
  }
1420
- export function validateTddRedEvidence(sectionBody) {
1420
+ /**
1421
+ * Sync helper that scans for `Evidence:` lines in a section body and
1422
+ * returns the trimmed value of each. Used by the lint pipeline to
1423
+ * pre-resolve pointers (filesystem path-existence or delegation ledger
1424
+ * spanId match) before invoking the validators.
1425
+ *
1426
+ * Recognised forms:
1427
+ * Evidence: <path>
1428
+ * Evidence: spanId:<id>
1429
+ * - Evidence: <path>
1430
+ */
1431
+ export function extractEvidencePointers(sectionBody) {
1432
+ const pointers = [];
1433
+ const pattern = /^\s*-?\s*evidence\s*:\s*(.+?)\s*$/imu;
1434
+ for (const line of sectionBody.split(/\r?\n/u)) {
1435
+ const match = pattern.exec(line);
1436
+ if (match && match[1] !== undefined) {
1437
+ const value = match[1].trim();
1438
+ if (value.length > 0)
1439
+ pointers.push(value);
1440
+ }
1441
+ }
1442
+ return pointers;
1443
+ }
1444
+ export function validateTddRedEvidence(sectionBody, opts = {}) {
1445
+ if (opts.phaseEventsSatisfied) {
1446
+ return {
1447
+ ok: true,
1448
+ details: "RED Evidence auto-satisfied: delegation-events.jsonl carries a phase=red row with non-empty evidenceRefs for the active run."
1449
+ };
1450
+ }
1451
+ if (opts.pointerSatisfied) {
1452
+ return {
1453
+ ok: true,
1454
+ details: "RED Evidence satisfied via `Evidence: <path|spanId:...>` pointer (resolved to an existing artifact or delegation span)."
1455
+ };
1456
+ }
1421
1457
  const meaningful = meaningfulLineCount(sectionBody);
1422
1458
  if (meaningful < 2) {
1423
1459
  return {
@@ -1442,7 +1478,19 @@ export function validateTddRedEvidence(sectionBody) {
1442
1478
  details: "RED Evidence includes command + failing output markers."
1443
1479
  };
1444
1480
  }
1445
- export function validateTddGreenEvidence(sectionBody) {
1481
+ export function validateTddGreenEvidence(sectionBody, opts = {}) {
1482
+ if (opts.phaseEventsSatisfied) {
1483
+ return {
1484
+ ok: true,
1485
+ details: "GREEN Evidence auto-satisfied: delegation-events.jsonl carries a phase=green row with non-empty evidenceRefs for the active run."
1486
+ };
1487
+ }
1488
+ if (opts.pointerSatisfied) {
1489
+ return {
1490
+ ok: true,
1491
+ details: "GREEN Evidence satisfied via `Evidence: <path|spanId:...>` pointer (resolved to an existing artifact or delegation span)."
1492
+ };
1493
+ }
1446
1494
  const meaningful = meaningfulLineCount(sectionBody);
1447
1495
  if (meaningful < 2) {
1448
1496
  return {
@@ -2058,10 +2106,10 @@ export function validateSectionBody(sectionBody, rule, sectionName, context = {}
2058
2106
  }
2059
2107
  const sectionNameNormalized = normalizeHeadingTitle(sectionName).toLowerCase();
2060
2108
  if (sectionNameNormalized === "red evidence") {
2061
- return validateTddRedEvidence(sectionBody);
2109
+ return validateTddRedEvidence(sectionBody, context.tddEvidence?.red ?? {});
2062
2110
  }
2063
2111
  if (sectionNameNormalized === "green evidence") {
2064
- return validateTddGreenEvidence(sectionBody);
2112
+ return validateTddGreenEvidence(sectionBody, context.tddEvidence?.green ?? {});
2065
2113
  }
2066
2114
  if (sectionNameNormalized === "verification ladder") {
2067
2115
  return validateVerificationLadder(sectionBody);
@@ -1,13 +1,54 @@
1
- import { type StageLintContext } from "./shared.js";
1
+ import type { DelegationEntry } from "../delegation.js";
2
+ import { type LintFinding, type StageLintContext } from "./shared.js";
3
+ /**
4
+ * v6.11.0 — TDD stage linter.
5
+ *
6
+ * Source-of-truth ladder, in order of precedence:
7
+ *
8
+ * 1. **Phase events** in `delegation-events.jsonl` for the active run
9
+ * (`stage=tdd`, `sliceId=S-N`, `phase=red|green|refactor|refactor-deferred|doc`).
10
+ * When at least one slice carries any phase event, the linter
11
+ * auto-derives Watched-RED / Vertical Slice Cycle from the events
12
+ * and writes a rendered summary block between auto-render markers
13
+ * in `06-tdd.md`. Markdown table content is no longer required.
14
+ * 2. **Legacy markdown tables** (Watched-RED Proof + Vertical Slice
15
+ * Cycle) — used as a fallback when the events ledger has no slice
16
+ * phase rows for the active run. Existing v6.10 and earlier
17
+ * artifacts continue to validate via this path.
18
+ * 3. **Sharded slice files** under `<artifacts-dir>/tdd-slices/S-*.md`.
19
+ * Per-slice prose lives there. The main `06-tdd.md` is auto-indexed
20
+ * via `## Slices Index`.
21
+ */
2
22
  export declare function lintTddStage(ctx: StageLintContext): Promise<void>;
23
+ interface SliceFileInfo {
24
+ sliceId: string;
25
+ absPath: string;
26
+ }
3
27
  interface ParsedSliceCycleResult {
4
28
  ok: boolean;
5
29
  details: string;
6
30
  }
31
+ interface ExpandedSliceCycleResult extends ParsedSliceCycleResult {
32
+ findings: LintFinding[];
33
+ }
34
+ export declare function evaluateEventsWatchedRed(slices: Map<string, DelegationEntry[]>): ParsedSliceCycleResult;
35
+ export declare function evaluateEventsSliceCycle(slices: Map<string, DelegationEntry[]>): ExpandedSliceCycleResult;
36
+ interface DocCoverageResult {
37
+ missing: string[];
38
+ }
39
+ export declare function evaluateSliceDocumenterCoverage(slices: Map<string, DelegationEntry[]>): DocCoverageResult;
7
40
  export declare function parseVerticalSliceCycle(body: string): ParsedSliceCycleResult;
8
41
  interface VerificationLadderResult {
9
42
  ok: boolean;
10
43
  details: string;
11
44
  }
12
45
  export declare function evaluateVerificationLadder(body: string | null): VerificationLadderResult;
46
+ interface RenderInput {
47
+ mainArtifactPath: string;
48
+ slicesByEvents: Map<string, DelegationEntry[]>;
49
+ sliceFiles: SliceFileInfo[];
50
+ renderSummary?: boolean;
51
+ renderIndex?: boolean;
52
+ }
53
+ export declare function renderTddSliceSummary(input: RenderInput): Promise<void>;
13
54
  export {};