cclaw-cli 7.0.3 → 7.0.5
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.
|
@@ -5,6 +5,37 @@ import { FORBIDDEN_PLACEHOLDER_TOKENS, CONFIDENCE_FINDING_REGEX_SOURCE } from ".
|
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { PLAN_SPLIT_SMALL_PLAN_THRESHOLD, parseImplementationUnits, parseImplementationUnitParallelFields } from "../internal/plan-split-waves.js";
|
|
8
|
+
const PARALLEL_EXEC_MANAGED_START = "<!-- parallel-exec-managed-start -->";
|
|
9
|
+
const PARALLEL_EXEC_MANAGED_END = "<!-- parallel-exec-managed-end -->";
|
|
10
|
+
const TASK_ID_PATTERN = /\bT-\d{3}[a-z]?(?:\.\d{1,3})?\b/giu;
|
|
11
|
+
/**
|
|
12
|
+
* Extract every distinct T-NNN[a-z]?(.NNN)? id from a markdown body.
|
|
13
|
+
*
|
|
14
|
+
* Used by the `plan_parallel_exec_full_coverage` linter to compute the
|
|
15
|
+
* authored task set (from `## Task List`) vs. the wave-claimed task set
|
|
16
|
+
* (from inside `<!-- parallel-exec-managed-start -->`).
|
|
17
|
+
*/
|
|
18
|
+
function extractTaskIds(body) {
|
|
19
|
+
const ids = new Set();
|
|
20
|
+
for (const match of body.matchAll(TASK_ID_PATTERN)) {
|
|
21
|
+
ids.add(match[0]);
|
|
22
|
+
}
|
|
23
|
+
return ids;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Return the body between the parallel-exec managed comment markers, or
|
|
27
|
+
* an empty string if the block is absent. The TDD wave parser uses the
|
|
28
|
+
* same delimiters; keeping the regex local avoids cross-package import
|
|
29
|
+
* cycles in the linter.
|
|
30
|
+
*/
|
|
31
|
+
function extractParallelExecManagedBody(planMarkdown) {
|
|
32
|
+
const startIdx = planMarkdown.indexOf(PARALLEL_EXEC_MANAGED_START);
|
|
33
|
+
const endIdx = planMarkdown.indexOf(PARALLEL_EXEC_MANAGED_END);
|
|
34
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
return planMarkdown.slice(startIdx + PARALLEL_EXEC_MANAGED_START.length, endIdx);
|
|
38
|
+
}
|
|
8
39
|
export async function lintPlanStage(ctx) {
|
|
9
40
|
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
10
41
|
evaluateInvestigationTrace(ctx, "Implementation Units");
|
|
@@ -277,4 +308,52 @@ export async function lintPlanStage(ctx) {
|
|
|
277
308
|
: "Parallel-ready units detected or plan is single-unit."
|
|
278
309
|
});
|
|
279
310
|
}
|
|
311
|
+
// plan_parallel_exec_full_coverage: every T-NNN task listed in the
|
|
312
|
+
// plan's Task List must be assigned to a slice inside the
|
|
313
|
+
// <!-- parallel-exec-managed-start --> block. Without this, TDD
|
|
314
|
+
// cannot fan out work the plan never authored as waves; the previous
|
|
315
|
+
// failure mode was `stage-complete tdd` succeeding when only the
|
|
316
|
+
// first batch of tasks had been wave-assigned.
|
|
317
|
+
//
|
|
318
|
+
// Spike rows (`S-N`) live in the same Task List but are excluded
|
|
319
|
+
// because they are wall-clock spikes that produce evidence files
|
|
320
|
+
// and are not part of the regular slice fan-out. A task is also
|
|
321
|
+
// excluded when it appears under a `## Deferred Tasks` (or
|
|
322
|
+
// `## Backlog`) heading inside the plan with an explicit reason.
|
|
323
|
+
if (strictPlanGuards) {
|
|
324
|
+
const taskListSection = sectionBodyByName(sections, "Task List") ?? "";
|
|
325
|
+
const authoredTaskIds = extractTaskIds(taskListSection);
|
|
326
|
+
// Collect deferred / backlog task ids so they don't trigger the
|
|
327
|
+
// "uncovered" finding. Both heading variants are accepted.
|
|
328
|
+
const deferredBody = (sectionBodyByName(sections, "Deferred Tasks") ?? "") +
|
|
329
|
+
"\n" +
|
|
330
|
+
(sectionBodyByName(sections, "Backlog") ?? "");
|
|
331
|
+
const deferredIds = extractTaskIds(deferredBody);
|
|
332
|
+
const parallelExecBody = extractParallelExecManagedBody(raw);
|
|
333
|
+
const claimedIds = extractTaskIds(parallelExecBody);
|
|
334
|
+
const uncovered = [];
|
|
335
|
+
for (const id of authoredTaskIds) {
|
|
336
|
+
if (claimedIds.has(id))
|
|
337
|
+
continue;
|
|
338
|
+
if (deferredIds.has(id))
|
|
339
|
+
continue;
|
|
340
|
+
uncovered.push(id);
|
|
341
|
+
}
|
|
342
|
+
uncovered.sort();
|
|
343
|
+
const blockPresent = parallelExecBody.length > 0;
|
|
344
|
+
const taskListPresent = authoredTaskIds.size > 0;
|
|
345
|
+
findings.push({
|
|
346
|
+
section: "plan_parallel_exec_full_coverage",
|
|
347
|
+
required: taskListPresent,
|
|
348
|
+
rule: "Every T-NNN task in `## Task List` must be assigned to at least one slice inside the `<!-- parallel-exec-managed-start -->` block (or moved to an explicit `## Deferred Tasks` / `## Backlog` section). TDD cannot fan out waves the plan never authored.",
|
|
349
|
+
found: taskListPresent && blockPresent && uncovered.length === 0,
|
|
350
|
+
details: !taskListPresent
|
|
351
|
+
? "Task List section is empty or missing T-NNN ids; full-coverage check skipped."
|
|
352
|
+
: !blockPresent
|
|
353
|
+
? "`<!-- parallel-exec-managed-start -->` block is missing or empty. Author the Parallel Execution Plan with W-02..W-N covering every task before plan-final-approval."
|
|
354
|
+
: uncovered.length === 0
|
|
355
|
+
? `Parallel Execution Plan covers all ${authoredTaskIds.size} authored task id(s); ${deferredIds.size} task id(s) are explicitly deferred.`
|
|
356
|
+
: `Uncovered task id(s) — author waves for: ${uncovered.slice(0, 25).join(", ")}${uncovered.length > 25 ? `, … (${uncovered.length - 25} more)` : ""}. Either add slices for them inside <!-- parallel-exec-managed-start --> or move them under \`## Deferred Tasks\` with a reason.`
|
|
357
|
+
});
|
|
358
|
+
}
|
|
280
359
|
}
|
package/dist/content/hooks.js
CHANGED
|
@@ -838,15 +838,24 @@ async function persistEntry(root, runId, clean, event, options = {}) {
|
|
|
838
838
|
// Rerecord semantics: replace any pre-existing row with the same spanId
|
|
839
839
|
// (regardless of its status) so the legacy v1/v2 row is upgraded to v3
|
|
840
840
|
// shape on disk. The append path keeps the historical dedup semantics:
|
|
841
|
-
// an exact (spanId, status)
|
|
842
|
-
// idempotent.
|
|
841
|
+
// an exact (spanId, status, phase) triple is dropped to keep retried hooks
|
|
842
|
+
// idempotent. Including \`phase\` in the dedup key is required because a
|
|
843
|
+
// single TDD slice-builder span legitimately emits FOUR rows with
|
|
844
|
+
// status=completed (one each for phase=red|green|refactor|doc); a
|
|
845
|
+
// dedup on (spanId, status) alone would silently drop GREEN/REFACTOR/DOC
|
|
846
|
+
// and leave the linter reporting tdd_slice_green_missing for slices
|
|
847
|
+
// whose work actually landed.
|
|
843
848
|
if (options.replaceBySpanId) {
|
|
844
849
|
ledger.entries = ledger.entries.filter((entry) => entry.spanId !== clean.spanId);
|
|
845
850
|
ledger.entries.push(clean);
|
|
846
851
|
ledger.runId = runId;
|
|
847
852
|
ledger.schemaVersion = LEDGER_SCHEMA_VERSION;
|
|
848
853
|
await writeDelegationLedgerAtomic(ledgerPath, ledger);
|
|
849
|
-
} else if (!ledger.entries.some((entry) =>
|
|
854
|
+
} else if (!ledger.entries.some((entry) =>
|
|
855
|
+
entry.spanId === clean.spanId &&
|
|
856
|
+
entry.status === clean.status &&
|
|
857
|
+
(entry.phase ?? null) === (clean.phase ?? null)
|
|
858
|
+
)) {
|
|
850
859
|
ledger.entries.push(clean);
|
|
851
860
|
ledger.runId = runId;
|
|
852
861
|
ledger.schemaVersion = LEDGER_SCHEMA_VERSION;
|
|
@@ -51,6 +51,7 @@ export const PLAN = {
|
|
|
51
51
|
"Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
|
|
52
52
|
"Define validation points — mark where progress must be checked before continuing, with concrete command and expected evidence.",
|
|
53
53
|
"Define execution posture — record whether execution should be sequential, dependency-batched, parallel-safe, or blocked; include risk triggers and RED/GREEN/REFACTOR checkpoint/commit expectations when the repo workflow supports them. This fulfills the `plan_execution_posture_recorded` gate.",
|
|
54
|
+
"**Author the FULL Parallel Execution Plan.** Inside the `<!-- parallel-exec-managed-start -->` block, enumerate ALL waves W-02..W-N covering EVERY T-NNN task in `## Task List` — no `we'll author waves later`, `next batch only`, or open-ended Backlog handwave is acceptable. Each task gets a slice with `sliceId | taskId | dependsOn | claimedPaths | parallelizable | riskTier | lane`. Spike rows (`S-N`) and tasks marked `deferred` in an explicit `Deferred:` column may be omitted, but every other T-NNN must be claimed. This fulfills the `plan_parallel_exec_full_coverage` gate. The TDD stage downstream is a pure consumer of these waves — if the plan does not author them, TDD cannot fan out that work.",
|
|
54
55
|
"WAIT_FOR_CONFIRM — write plan artifact and explicitly pause. **STOP.** Do NOT proceed until user confirms. Then close the stage with `node .cclaw/hooks/stage-complete.mjs plan` and tell user to run `/cc`."
|
|
55
56
|
],
|
|
56
57
|
interactionProtocol: [
|
|
@@ -80,6 +81,7 @@ export const PLAN = {
|
|
|
80
81
|
{ id: "plan_dependency_batches_defined", description: "Tasks are grouped into executable batches with gate checks and execution posture." },
|
|
81
82
|
{ id: "plan_acceptance_mapped", description: "Each task maps to a spec acceptance criterion." },
|
|
82
83
|
{ id: "plan_execution_posture_recorded", description: "Execution posture is recorded before implementation handoff." },
|
|
84
|
+
{ id: "plan_parallel_exec_full_coverage", description: "Every T-NNN task in `## Task List` (other than spikes/explicitly-deferred) is assigned to at least one slice inside the `<!-- parallel-exec-managed-start -->` block; TDD cannot fan out work that the plan never authored as waves." },
|
|
83
85
|
{ id: "plan_wait_for_confirm", description: "Execution blocked until explicit user confirmation." }
|
|
84
86
|
],
|
|
85
87
|
requiredEvidence: [
|
package/dist/delegation.js
CHANGED
|
@@ -1031,7 +1031,9 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
1031
1031
|
stamped.fulfillmentMode = expectedFulfillmentMode(fallbacks);
|
|
1032
1032
|
}
|
|
1033
1033
|
}
|
|
1034
|
-
if (prior.entries.some((existing) => existing.spanId === stamped.spanId &&
|
|
1034
|
+
if (prior.entries.some((existing) => existing.spanId === stamped.spanId &&
|
|
1035
|
+
existing.status === stamped.status &&
|
|
1036
|
+
(existing.phase ?? null) === (stamped.phase ?? null))) {
|
|
1035
1037
|
return;
|
|
1036
1038
|
}
|
|
1037
1039
|
validateMonotonicTimestamps(stamped, prior.entries);
|