cclaw-cli 2.0.0 → 3.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.js +2 -4
- package/dist/cli.js +2 -9
- package/dist/config.d.ts +11 -67
- package/dist/config.js +59 -649
- package/dist/content/hook-events.js +0 -3
- package/dist/content/hook-manifest.d.ts +5 -2
- package/dist/content/hook-manifest.js +18 -64
- package/dist/content/node-hooks.d.ts +0 -26
- package/dist/content/node-hooks.js +237 -105
- package/dist/content/observe.js +2 -1
- package/dist/content/opencode-plugin.js +1 -72
- package/dist/content/stages/design.js +2 -2
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/scope.js +3 -3
- package/dist/content/stages/tdd.js +11 -11
- package/dist/gate-evidence.js +1 -5
- package/dist/hook-schema.js +3 -0
- package/dist/hook-schemas/claude-hooks.v1.json +0 -2
- package/dist/hook-schemas/codex-hooks.v1.json +0 -3
- package/dist/hook-schemas/cursor-hooks.v1.json +0 -2
- package/dist/install.d.ts +2 -7
- package/dist/install.js +20 -120
- package/dist/internal/compound-readiness.js +1 -16
- package/dist/internal/early-loop-status.js +1 -3
- package/dist/internal/runtime-integrity.js +0 -20
- package/dist/policy.js +6 -9
- package/dist/runtime/run-hook.mjs +237 -213
- package/dist/tdd-verification-evidence.js +6 -18
- package/dist/types.d.ts +0 -56
- package/package.json +1 -1
|
@@ -609,10 +609,7 @@ export default function cclawPlugin(ctx) {
|
|
|
609
609
|
eventType === "session.updated";
|
|
610
610
|
if (isSessionLifecycle) {
|
|
611
611
|
// Keep OpenCode aligned with Claude/Cursor/Codex: session-start is
|
|
612
|
-
// the canonical rehydrate path
|
|
613
|
-
// Ralph Loop, compound readiness, and hook-error breadcrumbs. The
|
|
614
|
-
// plugin refreshes its local bootstrap cache afterwards so the system
|
|
615
|
-
// transform sees the side effects from the hook runtime.
|
|
612
|
+
// the canonical rehydrate path.
|
|
616
613
|
await runHookScript("session-start", eventData ?? {});
|
|
617
614
|
await refreshBootstrapCache(true);
|
|
618
615
|
}
|
|
@@ -620,74 +617,6 @@ export default function cclawPlugin(ctx) {
|
|
|
620
617
|
await runHookScript("stop-handoff", { loop_count: 0 });
|
|
621
618
|
}
|
|
622
619
|
},
|
|
623
|
-
"tool.execute.before": async (input, output) => {
|
|
624
|
-
const disabled = isCclawDisabled();
|
|
625
|
-
if (disabled.disabled) {
|
|
626
|
-
// Explicit user override (CCLAW_DISABLE=1 et al): stay fully out
|
|
627
|
-
// of the way. Any real problem with the guard chain should not
|
|
628
|
-
// prevent the user from unblocking themselves.
|
|
629
|
-
noteDisabled(disabled);
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
const payload = normalizeToolPayload(input, output);
|
|
633
|
-
if (isSafeReadOnlyTool(payload)) {
|
|
634
|
-
// Read-only tools bypass guards — they cannot mutate state and
|
|
635
|
-
// blocking them gives users an unusable session when guards are
|
|
636
|
-
// misconfigured or cclaw isn't fully initialized.
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
if (!isCclawInitialized()) {
|
|
640
|
-
// Project has no flow-state or hook runtime: cclaw isn't in use
|
|
641
|
-
// here. Never block the user's tools because of setup they didn't
|
|
642
|
-
// ask for. Surface a single advisory so they can notice.
|
|
643
|
-
noteNotInitialized();
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
const [promptOk, workflowOk] = await Promise.all([
|
|
647
|
-
runHookScript("prompt-guard", payload),
|
|
648
|
-
runHookScript("workflow-guard", payload)
|
|
649
|
-
]);
|
|
650
|
-
if (!promptOk || !workflowOk) {
|
|
651
|
-
const failed = !promptOk ? "prompt-guard" : "workflow-guard";
|
|
652
|
-
const rawDetail = lastHookStderr.get(failed) || "";
|
|
653
|
-
const detail = rawDetail.length > 0 ? rawDetail.slice(-400) : "(no stderr captured)";
|
|
654
|
-
if (looksLikeInfrastructureFailure(rawDetail)) {
|
|
655
|
-
// Never let a broken hook runtime or misrouted child-process
|
|
656
|
-
// stderr (yargs help, Node crash, ENOENT, timeout) masquerade
|
|
657
|
-
// as a policy block. Log the infra hit and let the user keep
|
|
658
|
-
// working regardless of strictness.
|
|
659
|
-
logToFile(
|
|
660
|
-
"infra: " + failed + " non-zero exit with non-guard stderr — treated as infrastructure failure, tool allowed. " +
|
|
661
|
-
"stderr=" + detail.replace(/\\s+/g, " ").slice(0, 300)
|
|
662
|
-
);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const strictness = await resolveStrictness();
|
|
666
|
-
if (strictness !== "strict") {
|
|
667
|
-
// Advisory mode (the default) — every guard refusal is a hint,
|
|
668
|
-
// not a hard stop. Users report the "failure" as a log line
|
|
669
|
-
// and keep working. Only \`strictness: strict\` in config.yaml
|
|
670
|
-
// or CCLAW_STRICTNESS=strict upgrades this to a thrown block.
|
|
671
|
-
logToFile(
|
|
672
|
-
"advisory: " + failed + " flagged tool.execute.before (strictness=" +
|
|
673
|
-
strictness + "). detail=" + detail.replace(/\\s+/g, " ").slice(0, 300)
|
|
674
|
-
);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
throw new Error(
|
|
678
|
-
"cclaw " + failed + " blocked tool.execute.before.\\n" +
|
|
679
|
-
"Reason: " + detail + "\\n" +
|
|
680
|
-
"Diagnose: run \`npx cclaw-cli sync\` in project root.\\n" +
|
|
681
|
-
"Bypass (temporary): export CCLAW_DISABLE=1 before starting OpenCode,\\n" +
|
|
682
|
-
"or set \`strictness: advisory\` in .cclaw/config.yaml."
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
"tool.execute.after": async (input, output) => {
|
|
687
|
-
const payload = normalizeToolPayload(input, output);
|
|
688
|
-
await runHookScript("context-monitor", payload);
|
|
689
|
-
void refreshBootstrapCache(false);
|
|
690
|
-
},
|
|
691
620
|
"experimental.chat.system.transform": (payload) => {
|
|
692
621
|
const bootstrap = getBootstrap();
|
|
693
622
|
if (!bootstrap) return payload;
|
|
@@ -94,7 +94,7 @@ export const DESIGN = {
|
|
|
94
94
|
"Artifact written to `.cclaw/artifacts/03-design-<slug>.md`.",
|
|
95
95
|
"Failure-mode table exists in Method/Exception/Rescue/UserSees format.",
|
|
96
96
|
"Tier-required diagram markers are present: architecture (all tiers). Standard/Deep add-ons (shadow/error) and Deep add-ons (state-machine/rollback/deployment-sequence) are included only when risk warrants them.",
|
|
97
|
-
"Stale diagram audit finding is clear
|
|
97
|
+
"Stale diagram audit finding is clear: no blast-radius file newer than diagram markers without explicit update.",
|
|
98
98
|
"Security & threat model findings are documented with mitigations.",
|
|
99
99
|
"Observability and deployment plans are explicit for critical flows.",
|
|
100
100
|
"Outside-voice findings and dispositions are recorded (accept/reject/defer).",
|
|
@@ -165,7 +165,7 @@ export const DESIGN = {
|
|
|
165
165
|
{ section: "Data-Flow Shadow Paths", required: false, validationRule: "Standard/Deep add-on: include `<!-- diagram: data-flow-shadow-paths -->` marker plus a table for high-risk choices: chosen path, shadow alternative, switch trigger, failure/rescue/degraded behavior, and verification evidence." },
|
|
166
166
|
{ section: "Error Flow Diagram", required: false, validationRule: "Standard/Deep add-on: include `<!-- diagram: error-flow -->` marker and failure-detection -> rescue -> user-visible outcome flow." },
|
|
167
167
|
{ section: "Data Flow", required: false, validationRule: "Must include data/state flow, happy path, nil input, empty input, upstream error paths, plus Interaction Edge Case matrix rows for double-click, nav-away-mid-request, 10K-result dataset, background-job abandonment, zombie connection. Each row declares handled yes/no and deferred item when not handled." },
|
|
168
|
-
{ section: "Stale Diagram Audit", required: false, validationRule: "
|
|
168
|
+
{ section: "Stale Diagram Audit", required: false, validationRule: "Blast-radius files from Codebase Investigation must not be newer than the current design diagram-marker baseline unless explicitly refreshed." },
|
|
169
169
|
{ section: "Failure Mode Table", required: true, validationRule: "Use Method/Exception/Rescue/UserSees columns and treat silent user impact without rescue as critical." },
|
|
170
170
|
{ section: "Pre-mortem", required: false, validationRule: "Recommended: list top failure scenarios, early warning signal, mitigation owner, and containment action before implementation." },
|
|
171
171
|
{ section: "Security & Threat Model", required: true, validationRule: "Must list trust boundaries, abuse/failure scenarios, mitigations, and residual risks." },
|
|
@@ -45,7 +45,7 @@ export const PLAN = {
|
|
|
45
45
|
"Group tasks into dependency batches — batch N+1 cannot start until batch N has verification evidence.",
|
|
46
46
|
"Slice into vertical tasks — each task targets 2-5 minutes, produces one testable outcome, and touches one coherent area.",
|
|
47
47
|
"Task Contract — every task has one coherent outcome, AC mapping, exact verification command/manual step, and expected evidence snippet or pass condition. Avoid vague `run tests` wording.",
|
|
48
|
-
"Annotate slice-review metadata —
|
|
48
|
+
"Annotate slice-review metadata — task rows may carry `touchCount` (rough number of files expected to change), `touchPaths` (glob hints, e.g. `migrations/**`, `src/auth/**`), and optional `highRisk: true` to force a review pass. These fields feed the TDD stage's Per-Slice Review point.",
|
|
49
49
|
"Map scope Locked Decisions — every LD#hash anchor from scope is referenced by at least one plan task (or explicitly marked deferred with reason).",
|
|
50
50
|
"Run anti-placeholder + anti-scope-reduction scans — block `TODO/TBD/...` and phrasing like `v1`, `for now`, `later` for locked boundaries.",
|
|
51
51
|
"Define validation points — mark where progress must be checked before continuing, with concrete command and expected evidence.",
|
|
@@ -127,7 +127,7 @@ export const PLAN = {
|
|
|
127
127
|
{ section: "Upstream Handoff", required: false, validationRule: "Summarizes spec/design/scope decisions, constraints, open questions, and explicit drift before task breakdown." },
|
|
128
128
|
{ section: "Dependency Graph", required: false, validationRule: "Ordering and parallel opportunities explicit. No circular dependencies." },
|
|
129
129
|
{ section: "Dependency Batches", required: true, validationRule: "Every task belongs to a batch. Each batch has an exit gate and dependency statement." },
|
|
130
|
-
{ section: "Task List", required: true, validationRule: "Each task row includes ID, description, acceptance criterion, exact verification command/manual step, expected evidence/pass condition, and effort estimate (S/M/L). Every task must also carry a minutes estimate within the 2-5 minute budget. When
|
|
130
|
+
{ section: "Task List", required: true, validationRule: "Each task row includes ID, description, acceptance criterion, exact verification command/manual step, expected evidence/pass condition, and effort estimate (S/M/L). Every task must also carry a minutes estimate within the 2-5 minute budget. When present, touchCount/touchPaths/highRisk metadata drives Per-Slice Review escalation in TDD." },
|
|
131
131
|
{ section: "Acceptance Mapping", required: true, validationRule: "Every spec criterion is covered by at least one task." },
|
|
132
132
|
{ section: "Execution Posture", required: true, validationRule: "States sequential/batch/parallel posture, stop conditions, risk triggers, and RED/GREEN/REFACTOR checkpoint or commit expectations for TDD when consistent with the repo workflow." },
|
|
133
133
|
{ section: "Locked Decision Coverage", required: false, validationRule: "Every locked decision ID (D-XX) from scope is listed with linked task IDs or explicit defer rationale." },
|
|
@@ -74,7 +74,7 @@ export const SCOPE = {
|
|
|
74
74
|
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be completed or explicitly waived for a real blocker. If the active harness cannot isolate a planner, run a role-switch planner pass instead: announce `## cclaw role-switch: scope/planner (mandatory)`, write the planner output/evidence into the scope artifact, and append a completed delegation row with `fulfillmentMode: \"role-switch\"` plus non-empty `evidenceRefs`. Then close with `node .cclaw/hooks/stage-complete.mjs scope --passed=scope_mode_selected,scope_contract_written,scope_user_approved --evidence-json '{\"scope_mode_selected\":\"<user-approved mode + rationale>\",\"scope_contract_written\":\"<artifact path + sections>\",\"scope_user_approved\":\"<explicit user approval quote or summary>\"}'`. `scope_user_approved` must cite the user's approval; review-loop evidence alone is not approval."
|
|
75
75
|
],
|
|
76
76
|
process: [
|
|
77
|
-
"Run
|
|
77
|
+
"Run pre-scope audit before premise challenge.",
|
|
78
78
|
"Run the scope pass scaled to risk: default to job-to-be-done plus explicit scope contract; add premise challenge, 10-star upside, smallest useful wedge, and change conditions only for deep/high-risk scope.",
|
|
79
79
|
"Compare minimum viable, product-grade, and ideal architecture scope alternatives with explicit reuse/effort/risk.",
|
|
80
80
|
"Recommend a scope mode with explicit rationale, then ask for user opt-in before treating it as selected.",
|
|
@@ -89,7 +89,7 @@ export const SCOPE = {
|
|
|
89
89
|
],
|
|
90
90
|
requiredEvidence: [
|
|
91
91
|
"Artifact written to `.cclaw/artifacts/02-scope-<slug>.md`.",
|
|
92
|
-
"
|
|
92
|
+
"Pre-Scope System Audit findings are captured (git log/diff/stash/debt markers).",
|
|
93
93
|
"In-scope and out-of-scope lists are explicit.",
|
|
94
94
|
"Discretion areas are explicit (or marked as `None`).",
|
|
95
95
|
"Selected mode and rationale are documented using HOLD SCOPE, SELECTIVE EXPANSION, SCOPE EXPANSION, or SCOPE REDUCTION.",
|
|
@@ -148,7 +148,7 @@ export const SCOPE = {
|
|
|
148
148
|
},
|
|
149
149
|
artifactValidation: [
|
|
150
150
|
{ section: "Upstream Handoff", required: false, validationRule: "Summarizes brainstorm/idea decisions, constraints, open questions, and explicit drift before scope decisions." },
|
|
151
|
-
{ section: "Pre-Scope System Audit", required:
|
|
151
|
+
{ section: "Pre-Scope System Audit", required: true, validationRule: "Must capture git log -30, git diff --stat, git stash list, and debt-marker scan (TODO/FIXME/XXX/HACK) before premise challenge." },
|
|
152
152
|
{ section: "Prime Directives", required: false, validationRule: "For each scoped capability: named failure modes, explicit error surface, four data-flow paths, interaction edge cases, observability expectations, and deferred-item handling." },
|
|
153
153
|
{ section: "Premise Challenge", required: false, validationRule: "Must list at least 3 question/answer rows in a markdown table or bullet list (gstack default trio: right problem? direct path? what if we do nothing? — extend with leverage and reversibility for richer scope). The linter enforces structure, not English wording — answers may be in any language." },
|
|
154
154
|
{ section: "Scope Contract", required: true, validationRule: "Canonical contract: selected mode, in scope, out of scope, requirements, locked decisions, discretion areas, deferred ideas, accepted/rejected reference ideas, success definition, and design handoff." },
|
|
@@ -41,7 +41,7 @@ export const TDD = {
|
|
|
41
41
|
"Map to acceptance criterion — identify the specific spec criterion this test proves.",
|
|
42
42
|
"Discover the test surface — inspect existing tests, fixtures, helpers, test commands, and nearby assertions before authoring RED. Reuse the local test style unless the slice genuinely needs a new pattern.",
|
|
43
43
|
"Run a system-wide impact check — name callbacks, state transitions, interfaces, schemas, CLI/config/API contracts, persistence, or event boundaries that this slice can affect. Add RED coverage for each affected public contract or record why it is out of scope.",
|
|
44
|
-
"Source/test preflight — before production edits, classify planned paths using
|
|
44
|
+
"Source/test preflight — before production edits, classify planned paths using test-path patterns; verify the RED touches a test path and the GREEN touches only source paths needed for the failing behavior.",
|
|
45
45
|
"Set execution posture — record whether this slice is sequential, batch-safe, or blocked; when the existing git workflow permits small commits, checkpoint after RED, GREEN, and REFACTOR (or record why commits are deferred).",
|
|
46
46
|
"Use the mandatory `test-author` delegation for RED — after discovery and impact check, produce failing behavior tests and RED evidence only (no production edits). Set `CCLAW_ACTIVE_AGENT=tdd-red` when the harness supports phase labels.",
|
|
47
47
|
"RED: Capture failure output — copy the exact failure output as RED evidence. Record in artifact.",
|
|
@@ -52,7 +52,7 @@ export const TDD = {
|
|
|
52
52
|
"REFACTOR: continue the `test-author` evidence cycle (or a dedicated refactor mode when available) to improve code quality without behavior changes. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when the harness supports phase labels.",
|
|
53
53
|
"Record evidence — capture test discovery, system-wide impact check, RED failure, GREEN output, and REFACTOR notes in the TDD artifact. When logging a `green` row, attach the closed acceptance-criterion IDs in `acIds` so Ralph Loop status counts them.",
|
|
54
54
|
"Annotate traceability — link to the active track's source: plan task ID + spec criterion on standard/medium, or spec acceptance item / bug reproduction slice on quick.",
|
|
55
|
-
"Per-Slice Review (conditional) — if
|
|
55
|
+
"Per-Slice Review (conditional) — if the slice meets any trigger (touchCount >= filesChangedThreshold, touchPaths match touchTriggers, or highRisk=true), append a `## Per-Slice Review` entry for this slice before moving on (see the dedicated section below).",
|
|
56
56
|
"Repeat for each slice — return to step 1 for the next plan slice."
|
|
57
57
|
],
|
|
58
58
|
interactionProtocol: [
|
|
@@ -70,7 +70,7 @@ export const TDD = {
|
|
|
70
70
|
"Use incremental RED/GREEN/REFACTOR commits when the repository workflow and working tree make that appropriate; otherwise record the checkpoint boundaries in the artifact.",
|
|
71
71
|
"Stop if regressions appear and fix before proceeding.",
|
|
72
72
|
"If a test passes unexpectedly, investigate: does the behavior already exist, or is the test wrong?",
|
|
73
|
-
"**Per-Slice Review point (conditional
|
|
73
|
+
"**Per-Slice Review point (conditional).** Check every slice against the triggers before declaring it DONE. Triggers: `touchCount >= filesChangedThreshold`, any `touchPaths` match a `touchTriggers` glob, or the plan row declares `highRisk: true`. On a trigger, run two passes on the slice alone — (1) Spec-Compliance: trace RED/GREEN/REFACTOR evidence back to its plan task + spec criterion, noting edge cases the tests skip; (2) Quality: diff-scan for naming, error handling, dead code, simpler alternatives. Record both under `## Per-Slice Review` in `06-tdd.md`, naming the trigger that fired. Dispatch the `reviewer` subagent natively when available (log `fulfillmentMode: \"isolated\"`); otherwise fulfil via in-session role switch (`fulfillmentMode: \"role-switch\"`). Never fabricate an isolated pass from memory."
|
|
74
74
|
],
|
|
75
75
|
process: [
|
|
76
76
|
"Select one vertical slice and map it to acceptance criterion(s).",
|
|
@@ -81,10 +81,10 @@ export const TDD = {
|
|
|
81
81
|
"Run tests and capture failure output.",
|
|
82
82
|
"Use `test-author` in GREEN intent and implement the smallest change needed for GREEN.",
|
|
83
83
|
"Run full tests and build checks.",
|
|
84
|
-
"Run a fresh verification-before-completion check and capture command + PASS/FAIL plus a commit SHA when
|
|
84
|
+
"Run a fresh verification-before-completion check and capture command + PASS/FAIL plus a commit SHA when `.git` is present; otherwise record explicit no-vcs reason plus content/artifact hash.",
|
|
85
85
|
"Run the REFACTOR intent preserving behavior.",
|
|
86
86
|
"Record RED, GREEN, and REFACTOR evidence in artifact.",
|
|
87
|
-
"Annotate traceability to plan task and spec criterion; on
|
|
87
|
+
"Annotate traceability to plan task and spec criterion; on per-slice triggers, append a Per-Slice Review entry before closing the slice."
|
|
88
88
|
],
|
|
89
89
|
requiredGates: [
|
|
90
90
|
{ id: "tdd_test_discovery_complete", description: "Relevant existing tests, fixtures, helpers, and runnable commands were discovered before RED tests were written." },
|
|
@@ -92,7 +92,7 @@ export const TDD = {
|
|
|
92
92
|
{ id: "tdd_red_test_written", description: "Failing tests exist before implementation changes." },
|
|
93
93
|
{ id: "tdd_green_full_suite", description: "Full relevant suite passes in GREEN state." },
|
|
94
94
|
{ id: "tdd_refactor_completed", description: "Refactor pass completed with behavior preservation verified." },
|
|
95
|
-
{ id: "tdd_verified_before_complete", description: "Fresh verification evidence includes test command, explicit pass/fail status, and a
|
|
95
|
+
{ id: "tdd_verified_before_complete", description: "Fresh verification evidence includes test command, explicit pass/fail status, and a durable ref: commit SHA when `.git` is present or explicit no-VCS attestation + hash when not." },
|
|
96
96
|
{ id: "tdd_iron_law_acknowledged", description: "Iron Law acknowledgement is explicit (`Acknowledged: yes`) before implementation proceeds." },
|
|
97
97
|
{ id: "tdd_watched_red_observed", description: "Watched-RED Proof records at least one observed failing test with ISO timestamp evidence." },
|
|
98
98
|
{ id: "tdd_slice_cycle_complete", description: "Vertical Slice Cycle records RED, GREEN, and REFACTOR phases per active slice." },
|
|
@@ -106,7 +106,7 @@ export const TDD = {
|
|
|
106
106
|
"Execution posture and vertical-slice RED/GREEN/REFACTOR checkpoint plan recorded, including commit boundaries when the repo workflow supports them.",
|
|
107
107
|
"Failing command output captured (RED).",
|
|
108
108
|
"Full test/build output recorded (GREEN).",
|
|
109
|
-
"Fresh verification evidence recorded with command, PASS/FAIL status, and
|
|
109
|
+
"Fresh verification evidence recorded with command, PASS/FAIL status, and commit SHA or no-VCS reason plus content/artifact hash before completion.",
|
|
110
110
|
"Iron Law Acknowledgement section explicitly states `Acknowledged: yes`.",
|
|
111
111
|
"Watched-RED Proof includes at least one populated row with an ISO timestamp.",
|
|
112
112
|
"Vertical Slice Cycle records RED, GREEN, and REFACTOR per active slice.",
|
|
@@ -125,7 +125,7 @@ export const TDD = {
|
|
|
125
125
|
"behavior changed during refactor",
|
|
126
126
|
"no evidence recorded",
|
|
127
127
|
"RED/GREEN blocked — classify with the managed taxonomy `NO_SOURCE_CONTEXT`, `NO_TEST_SURFACE`, `NO_IMPLEMENTABLE_SLICE`, `RED_NOT_EXPRESSIBLE`, or `NO_VCS_MODE` and capture blockedBecause, missingInputs, recommendedRoute, nextCommand, resumeCriteria, and the repair path: RED needs a failing test surface, GREEN needs full-suite pass evidence, REFACTOR needs behavior-preservation evidence.",
|
|
128
|
-
"no-VCS workspace without explicit
|
|
128
|
+
"no-VCS workspace without explicit no-vcs reason and content/artifact hash"
|
|
129
129
|
],
|
|
130
130
|
exitCriteria: [
|
|
131
131
|
"test discovery and system-wide impact check are recorded",
|
|
@@ -170,7 +170,7 @@ export const TDD = {
|
|
|
170
170
|
{ section: "Test Pyramid Shape", required: false, validationRule: "If present: per-slice count of Small/Medium/Large tests added, to let reviewers verify the suite is not drifting top-heavy." },
|
|
171
171
|
{ section: "Mock Preference Order", required: false, validationRule: "When mocks/spies appear in Test Discovery or RED Evidence, prefer Real > Fake > Stub > Mock. Mock-heavy slices should include explicit boundary justification (for example network/fs/time/external trust boundaries)." },
|
|
172
172
|
{ section: "Prove-It Reproduction", required: false, validationRule: "Required for bug-fix slices: original failing reproduction test (RED without fix), passing output with fix (GREEN), and a note confirming the test fails again if the fix is reverted." },
|
|
173
|
-
{ section: "Per-Slice Review", required: false, validationRule: "
|
|
173
|
+
{ section: "Per-Slice Review", required: false, validationRule: "Per triggered slice, a two-part record — Spec-Compliance (slice <-> plan task <-> spec criterion trace plus edge-case notes) and Quality (diff-focused review of naming, error handling, dead code, simpler alternatives). Each entry names the trigger (touchCount, touchPaths glob, or highRisk) and the delegation fulfillmentMode (`isolated` when a reviewer subagent was dispatched natively; `role-switch` when fulfilled in-session). Slices that did not meet any trigger may list `not triggered` instead of a full pass." }
|
|
174
174
|
]
|
|
175
175
|
},
|
|
176
176
|
reviewLens: {
|
|
@@ -224,9 +224,9 @@ export const TDD = {
|
|
|
224
224
|
{
|
|
225
225
|
title: "Per-Slice Review Audit (conditional)",
|
|
226
226
|
evaluationPoints: [
|
|
227
|
-
"
|
|
227
|
+
"Does every triggered slice (touchCount >= threshold, touchPaths match, or highRisk=true) carry a Per-Slice Review entry with BOTH a Spec-Compliance pass (plan task <-> spec criterion + edge-case notes) AND a Quality pass (diff-level naming/errors/dead code/simpler alternatives)?",
|
|
228
228
|
"Is the delegation `fulfillmentMode` recorded (`isolated` for a dispatched reviewer subagent, `role-switch` for an in-session pass) and does it match an entry in `.cclaw/state/delegation-log.json`?",
|
|
229
|
-
"
|
|
229
|
+
"Are there zero missed triggered slices when triggers fired?"
|
|
230
230
|
],
|
|
231
231
|
stopGate: false
|
|
232
232
|
},
|
package/dist/gate-evidence.js
CHANGED
|
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { checkReviewSecurityNoChangeAttestation, checkReviewVerdictConsistency, extractMarkdownSectionBody, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
|
|
4
4
|
import { resolveArtifactPath } from "./artifact-paths.js";
|
|
5
|
-
import { readConfig } from "./config.js";
|
|
6
5
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
7
6
|
import { stageSchema } from "./content/stage-schema.js";
|
|
8
7
|
import { readDelegationLedger } from "./delegation.js";
|
|
@@ -179,10 +178,7 @@ async function readEarlyLoopGateSnapshot(projectRoot, flowState) {
|
|
|
179
178
|
return { snapshot: onDisk };
|
|
180
179
|
}
|
|
181
180
|
try {
|
|
182
|
-
const
|
|
183
|
-
const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"), {
|
|
184
|
-
maxIterations: config.earlyLoop?.maxIterations
|
|
185
|
-
});
|
|
181
|
+
const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"));
|
|
186
182
|
return {
|
|
187
183
|
snapshot: {
|
|
188
184
|
stage: computed.stage,
|
package/dist/hook-schema.js
CHANGED
|
@@ -68,6 +68,9 @@ function validateClaudeLikeEvent(eventName, eventEntries, errors) {
|
|
|
68
68
|
if (hook.timeout !== undefined && !isPositiveNumber(hook.timeout)) {
|
|
69
69
|
errors.push(`hooks.${eventName}[${index}].hooks[${hookIndex}].timeout must be a positive number when present`);
|
|
70
70
|
}
|
|
71
|
+
if (hook.statusMessage !== undefined && !isNonEmptyString(hook.statusMessage)) {
|
|
72
|
+
errors.push(`hooks.${eventName}[${index}].hooks[${hookIndex}].statusMessage must be a non-empty string when present`);
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
}
|
package/dist/install.d.ts
CHANGED
|
@@ -11,13 +11,8 @@ export declare function initCclaw(options: InitOptions): Promise<void>;
|
|
|
11
11
|
export declare function syncCclaw(projectRoot: string, options?: SyncOptions): Promise<void>;
|
|
12
12
|
/**
|
|
13
13
|
* Refresh generated files in `.cclaw/` without touching user-authored
|
|
14
|
-
* artifacts
|
|
15
|
-
* stamps
|
|
16
|
-
*
|
|
17
|
-
* Shape preservation: if the user previously hand-authored advanced keys
|
|
18
|
-
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
19
|
-
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
20
|
-
* minimal — advanced knobs are never silently added.
|
|
14
|
+
* artifacts or state. Config remains harness-only with managed version
|
|
15
|
+
* stamps.
|
|
21
16
|
*/
|
|
22
17
|
export declare function upgradeCclaw(projectRoot: string): Promise<void>;
|
|
23
18
|
export declare function uninstallCclaw(projectRoot: string): Promise<void>;
|
package/dist/install.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
|
|
6
|
-
import { writeConfig, createDefaultConfig, readConfig, configPath,
|
|
6
|
+
import { writeConfig, createDefaultConfig, readConfig, configPath, detectAdvancedKeys } from "./config.js";
|
|
7
7
|
import { learnSkillMarkdown } from "./content/learnings.js";
|
|
8
8
|
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
9
9
|
import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea.js";
|
|
@@ -12,7 +12,7 @@ import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-co
|
|
|
12
12
|
import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
|
|
13
13
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
14
14
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
15
|
-
import {
|
|
15
|
+
import { ironLawsSkillMarkdown } from "./content/iron-laws.js";
|
|
16
16
|
import { stageCompleteScript, startFlowScript, cancelRunScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
17
17
|
import { nodeHookRuntimeScript } from "./content/node-hooks.js";
|
|
18
18
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
@@ -21,7 +21,7 @@ import { STATE_CONTRACTS } from "./content/state-contracts.js";
|
|
|
21
21
|
import { REVIEW_PROMPTS } from "./content/review-prompts.js";
|
|
22
22
|
import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
|
|
23
23
|
import { adaptiveElicitationSkillMarkdown } from "./content/skills-elicitation.js";
|
|
24
|
-
import { LANGUAGE_RULE_PACK_DIR,
|
|
24
|
+
import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
|
|
25
25
|
import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
|
|
26
26
|
import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
|
|
27
27
|
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
@@ -407,55 +407,13 @@ async function removeManagedGitHookRelays(projectRoot) {
|
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
409
|
async function syncManagedGitHooks(projectRoot, config) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (config.gitHookGuards !== true) {
|
|
415
|
-
await removeManagedGitHookRelays(projectRoot);
|
|
416
|
-
try {
|
|
417
|
-
await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
// best-effort cleanup
|
|
421
|
-
}
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const runtimeGitHooksDir = path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR);
|
|
425
|
-
await ensureDir(runtimeGitHooksDir);
|
|
426
|
-
for (const hookName of ["pre-commit", "pre-push"]) {
|
|
427
|
-
const runtimePathForHook = path.join(runtimeGitHooksDir, `${hookName}.mjs`);
|
|
428
|
-
await writeFileSafe(runtimePathForHook, managedGitRuntimeScript(hookName));
|
|
429
|
-
try {
|
|
430
|
-
await fs.chmod(runtimePathForHook, 0o755);
|
|
431
|
-
}
|
|
432
|
-
catch {
|
|
433
|
-
// best effort on constrained filesystems
|
|
434
|
-
}
|
|
410
|
+
void config;
|
|
411
|
+
await removeManagedGitHookRelays(projectRoot);
|
|
412
|
+
try {
|
|
413
|
+
await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
|
|
435
414
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const hookPath = path.join(hooksDir, hookName);
|
|
439
|
-
let canWriteRelay = true;
|
|
440
|
-
if (await exists(hookPath)) {
|
|
441
|
-
try {
|
|
442
|
-
const existing = await fs.readFile(hookPath, "utf8");
|
|
443
|
-
canWriteRelay = existing.includes(GIT_HOOK_MANAGED_MARKER);
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
canWriteRelay = false;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (!canWriteRelay) {
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
await writeFileSafe(hookPath, managedGitRelayHook(hookName));
|
|
453
|
-
try {
|
|
454
|
-
await fs.chmod(hookPath, 0o755);
|
|
455
|
-
}
|
|
456
|
-
catch {
|
|
457
|
-
// best effort on constrained filesystems
|
|
458
|
-
}
|
|
415
|
+
catch {
|
|
416
|
+
// best-effort cleanup
|
|
459
417
|
}
|
|
460
418
|
}
|
|
461
419
|
async function ensureStructure(projectRoot) {
|
|
@@ -475,7 +433,8 @@ async function writeWavePlansScaffold(projectRoot) {
|
|
|
475
433
|
await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
|
|
476
434
|
}
|
|
477
435
|
async function writeSkills(projectRoot, config) {
|
|
478
|
-
|
|
436
|
+
void config;
|
|
437
|
+
const skillTrack = "standard";
|
|
479
438
|
for (const stage of FLOW_STAGES) {
|
|
480
439
|
const folder = stageSkillFolder(stage);
|
|
481
440
|
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
|
|
@@ -503,42 +462,8 @@ async function writeSkills(projectRoot, config) {
|
|
|
503
462
|
for (const [folderName, markdown] of Object.entries(SUBAGENT_CONTEXT_SKILLS)) {
|
|
504
463
|
await writeFileSafe(runtimePath(projectRoot, "skills", folderName, "SKILL.md"), markdown);
|
|
505
464
|
}
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
// legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
|
|
509
|
-
// are cleaned up below so the new rules/lang layout is the only truth.
|
|
510
|
-
const enabledPacks = config?.languageRulePacks ?? [];
|
|
511
|
-
const enabledPackFileNames = new Set();
|
|
512
|
-
for (const pack of enabledPacks) {
|
|
513
|
-
const fileName = LANGUAGE_RULE_PACK_FILES[pack];
|
|
514
|
-
const generator = LANGUAGE_RULE_PACK_GENERATORS[pack];
|
|
515
|
-
if (!fileName || !generator)
|
|
516
|
-
continue;
|
|
517
|
-
enabledPackFileNames.add(fileName);
|
|
518
|
-
await writeFileSafe(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR, fileName), generator());
|
|
519
|
-
}
|
|
520
|
-
// Strict idempotence: once a pack is removed from config, its generated
|
|
521
|
-
// file under .cclaw/rules/lang/ must disappear on the next sync. Without
|
|
522
|
-
// this loop the directory accumulates a superset of every pack ever
|
|
523
|
-
// enabled, which silently keeps stale guidance alive.
|
|
524
|
-
const langDir = runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR);
|
|
525
|
-
if (await exists(langDir)) {
|
|
526
|
-
const knownPackFileNames = new Set(Object.values(LANGUAGE_RULE_PACK_FILES));
|
|
527
|
-
let entries = [];
|
|
528
|
-
try {
|
|
529
|
-
entries = await fs.readdir(langDir);
|
|
530
|
-
}
|
|
531
|
-
catch {
|
|
532
|
-
entries = [];
|
|
533
|
-
}
|
|
534
|
-
for (const entry of entries) {
|
|
535
|
-
if (!knownPackFileNames.has(entry))
|
|
536
|
-
continue;
|
|
537
|
-
if (enabledPackFileNames.has(entry))
|
|
538
|
-
continue;
|
|
539
|
-
await fs.rm(path.join(langDir, entry), { force: true });
|
|
540
|
-
}
|
|
541
|
-
}
|
|
465
|
+
// Wave 21: language packs are no longer materialized from config.
|
|
466
|
+
await fs.rm(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR), { recursive: true, force: true });
|
|
542
467
|
for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
|
|
543
468
|
const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
|
|
544
469
|
if (await exists(legacyPath)) {
|
|
@@ -936,22 +861,10 @@ async function writeHooks(projectRoot, config) {
|
|
|
936
861
|
const stateDir = runtimePath(projectRoot, "state");
|
|
937
862
|
await ensureDir(hooksDir);
|
|
938
863
|
await ensureDir(stateDir);
|
|
939
|
-
const effectiveStrictness = config.strictness ?? "advisory";
|
|
940
|
-
await writeFileSafe(runtimePath(projectRoot, "state", "iron-laws.json"), `${JSON.stringify(ironLawRuntimeDocument({
|
|
941
|
-
mode: effectiveStrictness,
|
|
942
|
-
strictLaws: config.ironLaws?.strictLaws
|
|
943
|
-
}), null, 2)}\n`);
|
|
944
864
|
await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
|
|
945
865
|
await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
|
|
946
866
|
await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
|
|
947
|
-
const hookRuntimeOptions = {
|
|
948
|
-
strictness: effectiveStrictness,
|
|
949
|
-
tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
|
|
950
|
-
tddProductionPathPatterns: config.tdd?.productionPathPatterns,
|
|
951
|
-
compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
|
|
952
|
-
earlyLoopEnabled: config.earlyLoop?.enabled,
|
|
953
|
-
earlyLoopMaxIterations: config.earlyLoop?.maxIterations
|
|
954
|
-
};
|
|
867
|
+
const hookRuntimeOptions = {};
|
|
955
868
|
const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
|
|
956
869
|
await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
|
|
957
870
|
await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
|
|
@@ -1082,6 +995,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
|
1082
995
|
}
|
|
1083
996
|
}
|
|
1084
997
|
async function writeState(projectRoot, config, forceReset = false) {
|
|
998
|
+
void config;
|
|
1085
999
|
// Fresh init no longer materializes flow-state.json. The first managed
|
|
1086
1000
|
// `/cc <idea>` start-flow call creates the state file.
|
|
1087
1001
|
if (!forceReset) {
|
|
@@ -1091,7 +1005,7 @@ async function writeState(projectRoot, config, forceReset = false) {
|
|
|
1091
1005
|
if (await exists(statePath)) {
|
|
1092
1006
|
return;
|
|
1093
1007
|
}
|
|
1094
|
-
const state = createInitialFlowState({ track:
|
|
1008
|
+
const state = createInitialFlowState({ track: "standard" });
|
|
1095
1009
|
await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
1096
1010
|
}
|
|
1097
1011
|
async function cleanLegacyArtifacts(projectRoot) {
|
|
@@ -1289,17 +1203,8 @@ export async function initCclaw(options) {
|
|
|
1289
1203
|
if (options.harnesses !== undefined && options.harnesses.length === 0) {
|
|
1290
1204
|
throw new Error("Select at least one harness.");
|
|
1291
1205
|
}
|
|
1292
|
-
const
|
|
1293
|
-
//
|
|
1294
|
-
// gets `go`, etc. Skipped entirely when the project root has no manifests.
|
|
1295
|
-
const detectedPacks = await detectLanguageRulePacks(options.projectRoot);
|
|
1296
|
-
const config = {
|
|
1297
|
-
...baseConfig,
|
|
1298
|
-
languageRulePacks: detectedPacks
|
|
1299
|
-
};
|
|
1300
|
-
// Write a minimal `config.yaml` — advanced knobs live in docs/config.md
|
|
1301
|
-
// and only appear in the on-disk file when the user sets them explicitly
|
|
1302
|
-
// or a non-default value was detected (e.g. languageRulePacks).
|
|
1206
|
+
const config = createDefaultConfig(options.harnesses, options.track);
|
|
1207
|
+
// Wave 21: config is always minimal and harness-only.
|
|
1303
1208
|
await writeConfig(options.projectRoot, config, { mode: "minimal" });
|
|
1304
1209
|
// Init should scaffold runtime surfaces but leave flow-state creation to the
|
|
1305
1210
|
// first managed start-flow invocation.
|
|
@@ -1339,13 +1244,8 @@ export async function syncCclaw(projectRoot, options = {}) {
|
|
|
1339
1244
|
}
|
|
1340
1245
|
/**
|
|
1341
1246
|
* Refresh generated files in `.cclaw/` without touching user-authored
|
|
1342
|
-
* artifacts
|
|
1343
|
-
* stamps
|
|
1344
|
-
*
|
|
1345
|
-
* Shape preservation: if the user previously hand-authored advanced keys
|
|
1346
|
-
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
1347
|
-
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
1348
|
-
* minimal — advanced knobs are never silently added.
|
|
1247
|
+
* artifacts or state. Config remains harness-only with managed version
|
|
1248
|
+
* stamps.
|
|
1349
1249
|
*/
|
|
1350
1250
|
export async function upgradeCclaw(projectRoot) {
|
|
1351
1251
|
const configExists = await exists(configPath(projectRoot));
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
|
-
import { readConfig } from "../config.js";
|
|
5
4
|
import { writeFileSafe } from "../fs-utils.js";
|
|
6
5
|
import { computeCompoundReadiness, readKnowledgeSafely } from "../knowledge-store.js";
|
|
7
6
|
function parseArgs(tokens) {
|
|
@@ -80,21 +79,7 @@ export function formatCompoundReadinessLine(status) {
|
|
|
80
79
|
}
|
|
81
80
|
export async function runCompoundReadinessCommand(projectRoot, argv, io) {
|
|
82
81
|
const args = parseArgs(argv);
|
|
83
|
-
|
|
84
|
-
// mis-wired / malformed config shows up in hook-errors / CI logs
|
|
85
|
-
// instead of silently degrading to default threshold.
|
|
86
|
-
let config = null;
|
|
87
|
-
try {
|
|
88
|
-
config = await readConfig(projectRoot);
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
92
|
-
io.stderr.write(`[cclaw] compound-readiness: failed to read config (${detail}); falling back to default threshold\n`);
|
|
93
|
-
}
|
|
94
|
-
const threshold = args.threshold ??
|
|
95
|
-
(typeof config?.compound?.recurrenceThreshold === "number"
|
|
96
|
-
? config.compound.recurrenceThreshold
|
|
97
|
-
: undefined);
|
|
82
|
+
const threshold = args.threshold;
|
|
98
83
|
const archivedRunsCount = await countArchivedRunsSafely(projectRoot);
|
|
99
84
|
const { entries } = await readKnowledgeSafely(projectRoot, { lockAware: true });
|
|
100
85
|
const status = computeCompoundReadiness(entries, {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readConfig } from "../config.js";
|
|
3
2
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
3
|
import { computeEarlyLoopStatus, formatEarlyLoopStatusLine, isEarlyLoopStage } from "../early-loop.js";
|
|
5
4
|
import { writeFileSafe } from "../fs-utils.js";
|
|
@@ -66,14 +65,13 @@ function stateDir(projectRoot) {
|
|
|
66
65
|
export async function runEarlyLoopStatusCommand(projectRoot, argv, io) {
|
|
67
66
|
const args = parseArgs(argv);
|
|
68
67
|
const flow = await readFlowState(projectRoot).catch(() => null);
|
|
69
|
-
const config = await readConfig(projectRoot).catch(() => null);
|
|
70
68
|
const stage = args.stage ?? flow?.currentStage;
|
|
71
69
|
if (!isEarlyLoopStage(stage)) {
|
|
72
70
|
io.stderr.write("cclaw internal early-loop-status: current stage is not an early-loop stage. Pass --stage=brainstorm|scope|design.\n");
|
|
73
71
|
return 1;
|
|
74
72
|
}
|
|
75
73
|
const runId = (args.runId ?? flow?.activeRunId ?? "active").trim() || "active";
|
|
76
|
-
const status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl")
|
|
74
|
+
const status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl"));
|
|
77
75
|
if (args.write) {
|
|
78
76
|
const target = path.join(stateDir(projectRoot), "early-loop.json");
|
|
79
77
|
await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
|