cclaw-cli 6.14.0 → 6.14.2
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/shared.d.ts +15 -0
- package/dist/artifact-linter/tdd.js +333 -12
- package/dist/artifact-linter.js +9 -1
- package/dist/content/core-agents.js +66 -1
- package/dist/content/hooks.js +271 -0
- package/dist/content/stages/tdd.js +11 -6
- package/dist/flow-state.d.ts +46 -0
- package/dist/flow-state.js +18 -0
- package/dist/install.js +144 -10
- package/dist/internal/advance-stage.js +21 -3
- package/dist/internal/cohesion-contract-stub.d.ts +29 -0
- package/dist/internal/cohesion-contract-stub.js +166 -0
- package/dist/internal/set-checkpoint-mode.d.ts +16 -0
- package/dist/internal/set-checkpoint-mode.js +72 -0
- package/dist/internal/set-integration-overseer-mode.d.ts +14 -0
- package/dist/internal/set-integration-overseer-mode.js +69 -0
- package/dist/internal/wave-status.d.ts +51 -0
- package/dist/internal/wave-status.js +285 -0
- package/dist/run-persistence.js +20 -0
- package/package.json +1 -1
|
@@ -17,6 +17,10 @@ import { FlowStateGuardMismatchError, verifyFlowStateGuard } from "../run-persis
|
|
|
17
17
|
import { DelegationTimestampError, DispatchCapError, DispatchClaimInvalidError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
|
|
18
18
|
import { parsePlanSplitWavesArgs, runPlanSplitWaves } from "./plan-split-waves.js";
|
|
19
19
|
import { runSetWorktreeMode } from "./set-worktree-mode.js";
|
|
20
|
+
import { runSetCheckpointMode } from "./set-checkpoint-mode.js";
|
|
21
|
+
import { runSetIntegrationOverseerMode } from "./set-integration-overseer-mode.js";
|
|
22
|
+
import { runWaveStatusCommand } from "./wave-status.js";
|
|
23
|
+
import { runCohesionContractCommand } from "./cohesion-contract-stub.js";
|
|
20
24
|
/**
|
|
21
25
|
* Subcommands that mutate or consult flow-state.json via the CLI runtime.
|
|
22
26
|
* They all require the sha256 sidecar to match before continuing so a
|
|
@@ -30,12 +34,14 @@ const GUARD_ENFORCED_SUBCOMMANDS = new Set([
|
|
|
30
34
|
"rewind",
|
|
31
35
|
"verify-flow-state-diff",
|
|
32
36
|
"verify-current-state",
|
|
33
|
-
"set-worktree-mode"
|
|
37
|
+
"set-worktree-mode",
|
|
38
|
+
"set-checkpoint-mode",
|
|
39
|
+
"set-integration-overseer-mode"
|
|
34
40
|
]);
|
|
35
41
|
export async function runInternalCommand(projectRoot, argv, io) {
|
|
36
42
|
const [subcommand, ...tokens] = argv;
|
|
37
43
|
if (!subcommand) {
|
|
38
|
-
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode\n");
|
|
44
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode | set-checkpoint-mode | set-integration-overseer-mode | wave-status | cohesion-contract\n");
|
|
39
45
|
return 1;
|
|
40
46
|
}
|
|
41
47
|
try {
|
|
@@ -93,7 +99,19 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
93
99
|
if (subcommand === "set-worktree-mode") {
|
|
94
100
|
return await runSetWorktreeMode(projectRoot, tokens, io);
|
|
95
101
|
}
|
|
96
|
-
|
|
102
|
+
if (subcommand === "set-checkpoint-mode") {
|
|
103
|
+
return await runSetCheckpointMode(projectRoot, tokens, io);
|
|
104
|
+
}
|
|
105
|
+
if (subcommand === "set-integration-overseer-mode") {
|
|
106
|
+
return await runSetIntegrationOverseerMode(projectRoot, tokens, io);
|
|
107
|
+
}
|
|
108
|
+
if (subcommand === "wave-status") {
|
|
109
|
+
return await runWaveStatusCommand(projectRoot, tokens, io);
|
|
110
|
+
}
|
|
111
|
+
if (subcommand === "cohesion-contract") {
|
|
112
|
+
return await runCohesionContractCommand(projectRoot, tokens, io);
|
|
113
|
+
}
|
|
114
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | set-worktree-mode | set-checkpoint-mode | set-integration-overseer-mode | wave-status | cohesion-contract\n`);
|
|
97
115
|
return 1;
|
|
98
116
|
}
|
|
99
117
|
catch (err) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
interface InternalIo {
|
|
3
|
+
stdout: Writable;
|
|
4
|
+
stderr: Writable;
|
|
5
|
+
}
|
|
6
|
+
export interface CohesionContractArgs {
|
|
7
|
+
stub: boolean;
|
|
8
|
+
force: boolean;
|
|
9
|
+
reason: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseCohesionContractArgs(tokens: string[]): CohesionContractArgs | null;
|
|
12
|
+
/**
|
|
13
|
+
* v6.14.2 — emit a minimal advisory cohesion contract that satisfies
|
|
14
|
+
* the linter shape check (`cohesion-contract.{md,json}`) so projects
|
|
15
|
+
* with `legacyContinuation: true` and ≥ 2 completed slice-implementer
|
|
16
|
+
* rows can clear the soft `tdd.cohesion_contract_missing` finding
|
|
17
|
+
* without hand-authoring the document.
|
|
18
|
+
*
|
|
19
|
+
* The stub is intentionally bare — `sharedTypes`, `touchpoints`, and
|
|
20
|
+
* `slices` are populated from the active run delegation ledger so
|
|
21
|
+
* downstream tooling can see which slices the contract acknowledges,
|
|
22
|
+
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
23
|
+
* reviewers know not to treat it as authoritative.
|
|
24
|
+
*
|
|
25
|
+
* Refuses to overwrite an existing contract unless `--force` is
|
|
26
|
+
* passed; the existing file is treated as authored work.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runCohesionContractCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
|
+
import { writeFileSafe } from "../fs-utils.js";
|
|
5
|
+
import { readDelegationLedger } from "../delegation.js";
|
|
6
|
+
export function parseCohesionContractArgs(tokens) {
|
|
7
|
+
const args = { stub: false, force: false, reason: null };
|
|
8
|
+
for (const token of tokens) {
|
|
9
|
+
if (token === "--stub") {
|
|
10
|
+
args.stub = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (token === "--force") {
|
|
14
|
+
args.force = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (token.startsWith("--reason=")) {
|
|
18
|
+
const raw = token.slice("--reason=".length).trim();
|
|
19
|
+
if (raw.length > 0)
|
|
20
|
+
args.reason = raw;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (!args.stub)
|
|
26
|
+
return null;
|
|
27
|
+
return args;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* v6.14.2 — emit a minimal advisory cohesion contract that satisfies
|
|
31
|
+
* the linter shape check (`cohesion-contract.{md,json}`) so projects
|
|
32
|
+
* with `legacyContinuation: true` and ≥ 2 completed slice-implementer
|
|
33
|
+
* rows can clear the soft `tdd.cohesion_contract_missing` finding
|
|
34
|
+
* without hand-authoring the document.
|
|
35
|
+
*
|
|
36
|
+
* The stub is intentionally bare — `sharedTypes`, `touchpoints`, and
|
|
37
|
+
* `slices` are populated from the active run delegation ledger so
|
|
38
|
+
* downstream tooling can see which slices the contract acknowledges,
|
|
39
|
+
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
40
|
+
* reviewers know not to treat it as authoritative.
|
|
41
|
+
*
|
|
42
|
+
* Refuses to overwrite an existing contract unless `--force` is
|
|
43
|
+
* passed; the existing file is treated as authored work.
|
|
44
|
+
*/
|
|
45
|
+
export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
46
|
+
const parsed = parseCohesionContractArgs(tokens);
|
|
47
|
+
if (!parsed) {
|
|
48
|
+
io.stderr.write("cclaw internal cohesion-contract: usage: --stub [--force] [--reason=\"<short>\"]\n");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const artifactsDir = path.join(projectRoot, RUNTIME_ROOT, "artifacts");
|
|
52
|
+
const mdPath = path.join(artifactsDir, "cohesion-contract.md");
|
|
53
|
+
const jsonPath = path.join(artifactsDir, "cohesion-contract.json");
|
|
54
|
+
if (!parsed.force) {
|
|
55
|
+
const mdExists = await fileExists(mdPath);
|
|
56
|
+
const jsonExists = await fileExists(jsonPath);
|
|
57
|
+
if (mdExists || jsonExists) {
|
|
58
|
+
io.stderr.write("cclaw internal cohesion-contract: existing cohesion-contract.{md,json} present; pass --force to overwrite.\n");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const ledger = await readDelegationLedger(projectRoot).catch(() => null);
|
|
63
|
+
const sliceIds = collectSliceIds(ledger?.entries ?? []);
|
|
64
|
+
const reasonNote = parsed.reason
|
|
65
|
+
? `Reason: ${parsed.reason}`
|
|
66
|
+
: "Reason: legacyContinuation auto-stub.";
|
|
67
|
+
const md = [
|
|
68
|
+
"# Cohesion Contract",
|
|
69
|
+
"",
|
|
70
|
+
"_Advisory stub generated by `cclaw-cli internal cohesion-contract --stub`._",
|
|
71
|
+
"_Status: `advisory_legacy` — populated for legacyContinuation projects so the_",
|
|
72
|
+
"_TDD linter does not block stage-complete on `tdd.cohesion_contract_missing`._",
|
|
73
|
+
"",
|
|
74
|
+
`${reasonNote}`,
|
|
75
|
+
"",
|
|
76
|
+
"## Shared Types & Interfaces",
|
|
77
|
+
"| Symbol | Path | Signature | Owner slice |",
|
|
78
|
+
"|---|---|---|---|",
|
|
79
|
+
"| (none recorded) | (none) | (none) | (n/a) |",
|
|
80
|
+
"",
|
|
81
|
+
"## Naming Conventions",
|
|
82
|
+
"- Stub: per-slice modules continue to follow the existing repo conventions.",
|
|
83
|
+
"",
|
|
84
|
+
"## Invariants",
|
|
85
|
+
"- Stub: no cross-slice invariants asserted; treat each slice as independent until upgraded.",
|
|
86
|
+
"",
|
|
87
|
+
"## Integration Touchpoints",
|
|
88
|
+
"| From slice | To slice | Surface | Integration test name |",
|
|
89
|
+
"|---|---|---|---|",
|
|
90
|
+
"| (none recorded) | (n/a) | (n/a) | (n/a) |",
|
|
91
|
+
"",
|
|
92
|
+
"## Behavior Specifications per Slice",
|
|
93
|
+
sliceIds.length === 0
|
|
94
|
+
? "- (none recorded)"
|
|
95
|
+
: sliceIds
|
|
96
|
+
.map((sid) => `### Slice ${sid}\n- Behavior: see \`tdd-slices/${sid}.md\` (if present).`)
|
|
97
|
+
.join("\n\n"),
|
|
98
|
+
"",
|
|
99
|
+
"## Status",
|
|
100
|
+
"| Slice | Implemented | Tests pass | Cohesion verified |",
|
|
101
|
+
"|---|---|---|---|",
|
|
102
|
+
sliceIds.length === 0
|
|
103
|
+
? "| (none) | n/a | n/a | n/a |"
|
|
104
|
+
: sliceIds.map((sid) => `| ${sid} | yes | yes | advisory |`).join("\n"),
|
|
105
|
+
""
|
|
106
|
+
].join("\n");
|
|
107
|
+
const jsonStub = {
|
|
108
|
+
version: 1,
|
|
109
|
+
sharedTypes: [],
|
|
110
|
+
touchpoints: [],
|
|
111
|
+
slices: sliceIds.map((sid) => ({
|
|
112
|
+
sliceId: sid,
|
|
113
|
+
description: `Stub entry for ${sid}; advisory under legacyContinuation.`,
|
|
114
|
+
implemented: true,
|
|
115
|
+
testsPass: true,
|
|
116
|
+
cohesionVerified: false
|
|
117
|
+
})),
|
|
118
|
+
status: {
|
|
119
|
+
verdict: "advisory_legacy",
|
|
120
|
+
generatedBy: "cclaw-cli internal cohesion-contract --stub",
|
|
121
|
+
reason: parsed.reason ?? "legacyContinuation auto-stub"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
await writeFileSafe(mdPath, md);
|
|
125
|
+
await writeFileSafe(jsonPath, `${JSON.stringify(jsonStub, null, 2)}\n`);
|
|
126
|
+
io.stdout.write(`cclaw: cohesion-contract stub written (${sliceIds.length} slice(s) referenced). ` +
|
|
127
|
+
"Status: advisory_legacy — review and replace once cross-slice cohesion data is real.\n");
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
async function fileExists(filePath) {
|
|
131
|
+
try {
|
|
132
|
+
await fs.access(filePath);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function collectSliceIds(entries) {
|
|
140
|
+
const set = new Set();
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (entry.agent !== "slice-implementer")
|
|
143
|
+
continue;
|
|
144
|
+
if (entry.status !== "completed")
|
|
145
|
+
continue;
|
|
146
|
+
if (typeof entry.sliceId !== "string")
|
|
147
|
+
continue;
|
|
148
|
+
if (entry.sliceId.length === 0)
|
|
149
|
+
continue;
|
|
150
|
+
set.add(entry.sliceId);
|
|
151
|
+
}
|
|
152
|
+
return [...set].sort((a, b) => {
|
|
153
|
+
const an = parseSliceNum(a);
|
|
154
|
+
const bn = parseSliceNum(b);
|
|
155
|
+
if (an !== null && bn !== null)
|
|
156
|
+
return an - bn;
|
|
157
|
+
return a.localeCompare(b);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function parseSliceNum(sliceId) {
|
|
161
|
+
const m = /^S-(\d+)$/u.exec(sliceId);
|
|
162
|
+
if (!m)
|
|
163
|
+
return null;
|
|
164
|
+
const n = Number.parseInt(m[1], 10);
|
|
165
|
+
return Number.isFinite(n) ? n : null;
|
|
166
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
export interface SetCheckpointModeArgs {
|
|
3
|
+
mode: "per-slice" | "global-red";
|
|
4
|
+
reason: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseSetCheckpointModeArgs(tokens: string[]): SetCheckpointModeArgs | null;
|
|
7
|
+
/**
|
|
8
|
+
* v6.14.2 — set `flow-state.json::tddCheckpointMode` without advancing
|
|
9
|
+
* the stage DAG. Mirrors `set-worktree-mode`. The `--reason` flag is
|
|
10
|
+
* optional but recommended for the audit trail; it is currently passed
|
|
11
|
+
* through to the writer subsystem string so operators can grep the
|
|
12
|
+
* `.flow-state.guard.json` sidecar.
|
|
13
|
+
*/
|
|
14
|
+
export declare function runSetCheckpointMode(projectRoot: string, tokens: string[], io: {
|
|
15
|
+
stderr: Writable;
|
|
16
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFlowState, writeFlowState } from "../runs.js";
|
|
2
|
+
export function parseSetCheckpointModeArgs(tokens) {
|
|
3
|
+
let mode = null;
|
|
4
|
+
let reason = null;
|
|
5
|
+
let positional = null;
|
|
6
|
+
for (const token of tokens) {
|
|
7
|
+
if (token.startsWith("--mode=")) {
|
|
8
|
+
const raw = token.slice("--mode=".length).trim();
|
|
9
|
+
if (raw === "per-slice" || raw === "global-red") {
|
|
10
|
+
mode = raw;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (token.startsWith("--reason=")) {
|
|
18
|
+
const raw = token.slice("--reason=".length).trim();
|
|
19
|
+
if (raw.length > 0)
|
|
20
|
+
reason = raw;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (token.startsWith("--")) {
|
|
24
|
+
// unknown flag — let the caller surface usage.
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (positional === null) {
|
|
28
|
+
positional = token.trim();
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (mode === null && positional !== null) {
|
|
34
|
+
if (positional === "per-slice" || positional === "global-red") {
|
|
35
|
+
mode = positional;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (mode === null)
|
|
42
|
+
return null;
|
|
43
|
+
return { mode, reason };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* v6.14.2 — set `flow-state.json::tddCheckpointMode` without advancing
|
|
47
|
+
* the stage DAG. Mirrors `set-worktree-mode`. The `--reason` flag is
|
|
48
|
+
* optional but recommended for the audit trail; it is currently passed
|
|
49
|
+
* through to the writer subsystem string so operators can grep the
|
|
50
|
+
* `.flow-state.guard.json` sidecar.
|
|
51
|
+
*/
|
|
52
|
+
export async function runSetCheckpointMode(projectRoot, tokens, io) {
|
|
53
|
+
const parsed = parseSetCheckpointModeArgs(tokens);
|
|
54
|
+
if (!parsed) {
|
|
55
|
+
io.stderr.write("cclaw internal set-checkpoint-mode: usage: <per-slice|global-red> [--reason=\"<short>\"] " +
|
|
56
|
+
"(or --mode=<per-slice|global-red>)\n");
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const state = await readFlowState(projectRoot);
|
|
60
|
+
const writerSubsystem = parsed.reason
|
|
61
|
+
? `set-checkpoint-mode:${slugifyReason(parsed.reason)}`
|
|
62
|
+
: "set-checkpoint-mode";
|
|
63
|
+
await writeFlowState(projectRoot, { ...state, tddCheckpointMode: parsed.mode }, { writerSubsystem });
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
function slugifyReason(reason) {
|
|
67
|
+
return (reason
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.replace(/[^a-z0-9_-]+/gu, "-")
|
|
70
|
+
.replace(/^-+|-+$/gu, "")
|
|
71
|
+
.slice(0, 60) || "unspecified");
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
export interface SetIntegrationOverseerModeArgs {
|
|
3
|
+
mode: "conditional" | "always";
|
|
4
|
+
reason: string | null;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseSetIntegrationOverseerModeArgs(tokens: string[]): SetIntegrationOverseerModeArgs | null;
|
|
7
|
+
/**
|
|
8
|
+
* v6.14.2 — set `flow-state.json::integrationOverseerMode` without
|
|
9
|
+
* advancing the stage DAG. Mirrors `set-worktree-mode` and
|
|
10
|
+
* `set-checkpoint-mode`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function runSetIntegrationOverseerMode(projectRoot: string, tokens: string[], io: {
|
|
13
|
+
stderr: Writable;
|
|
14
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFlowState, writeFlowState } from "../runs.js";
|
|
2
|
+
export function parseSetIntegrationOverseerModeArgs(tokens) {
|
|
3
|
+
let mode = null;
|
|
4
|
+
let reason = null;
|
|
5
|
+
let positional = null;
|
|
6
|
+
for (const token of tokens) {
|
|
7
|
+
if (token.startsWith("--mode=")) {
|
|
8
|
+
const raw = token.slice("--mode=".length).trim();
|
|
9
|
+
if (raw === "conditional" || raw === "always") {
|
|
10
|
+
mode = raw;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (token.startsWith("--reason=")) {
|
|
18
|
+
const raw = token.slice("--reason=".length).trim();
|
|
19
|
+
if (raw.length > 0)
|
|
20
|
+
reason = raw;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (token.startsWith("--")) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (positional === null) {
|
|
27
|
+
positional = token.trim();
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (mode === null && positional !== null) {
|
|
33
|
+
if (positional === "conditional" || positional === "always") {
|
|
34
|
+
mode = positional;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (mode === null)
|
|
41
|
+
return null;
|
|
42
|
+
return { mode, reason };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* v6.14.2 — set `flow-state.json::integrationOverseerMode` without
|
|
46
|
+
* advancing the stage DAG. Mirrors `set-worktree-mode` and
|
|
47
|
+
* `set-checkpoint-mode`.
|
|
48
|
+
*/
|
|
49
|
+
export async function runSetIntegrationOverseerMode(projectRoot, tokens, io) {
|
|
50
|
+
const parsed = parseSetIntegrationOverseerModeArgs(tokens);
|
|
51
|
+
if (!parsed) {
|
|
52
|
+
io.stderr.write("cclaw internal set-integration-overseer-mode: usage: <conditional|always> [--reason=\"<short>\"] " +
|
|
53
|
+
"(or --mode=<conditional|always>)\n");
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
const state = await readFlowState(projectRoot);
|
|
57
|
+
const writerSubsystem = parsed.reason
|
|
58
|
+
? `set-integration-overseer-mode:${slugifyReason(parsed.reason)}`
|
|
59
|
+
: "set-integration-overseer-mode";
|
|
60
|
+
await writeFlowState(projectRoot, { ...state, integrationOverseerMode: parsed.mode }, { writerSubsystem });
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
function slugifyReason(reason) {
|
|
64
|
+
return (reason
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9_-]+/gu, "-")
|
|
67
|
+
.replace(/^-+|-+$/gu, "")
|
|
68
|
+
.slice(0, 60) || "unspecified");
|
|
69
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
interface InternalIo {
|
|
3
|
+
stdout: Writable;
|
|
4
|
+
stderr: Writable;
|
|
5
|
+
}
|
|
6
|
+
export interface WaveStatusWaveSummary {
|
|
7
|
+
waveId: string;
|
|
8
|
+
members: string[];
|
|
9
|
+
closedMembers: string[];
|
|
10
|
+
openMembers: string[];
|
|
11
|
+
readyMembers: string[];
|
|
12
|
+
blockedMembers: string[];
|
|
13
|
+
status: "closed" | "open" | "partial" | "empty";
|
|
14
|
+
}
|
|
15
|
+
export interface WaveStatusNextDispatch {
|
|
16
|
+
waveId: string | null;
|
|
17
|
+
readyToDispatch: string[];
|
|
18
|
+
pathConflicts: string[];
|
|
19
|
+
mode: "single-slice" | "wave-fanout" | "none";
|
|
20
|
+
}
|
|
21
|
+
export interface WaveStatusReport {
|
|
22
|
+
activeRunId: string;
|
|
23
|
+
currentStage: string;
|
|
24
|
+
tddCutoverSliceId: string | null;
|
|
25
|
+
tddWorktreeCutoverSliceId: string | null;
|
|
26
|
+
legacyContinuation: boolean;
|
|
27
|
+
waves: WaveStatusWaveSummary[];
|
|
28
|
+
nextDispatch: WaveStatusNextDispatch;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface RunWaveStatusOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Override the artifacts directory; useful for fixture tests that
|
|
34
|
+
* place an `05-plan.md` outside `.cclaw/artifacts`. Defaults to
|
|
35
|
+
* `<projectRoot>/.cclaw/artifacts`.
|
|
36
|
+
*/
|
|
37
|
+
artifactsDir?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* v6.14.2 — deterministic helper for the TDD controller. Reads the
|
|
41
|
+
* managed `<!-- parallel-exec-managed-start -->` block from
|
|
42
|
+
* `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
|
|
43
|
+
* reports waves + the next dispatchable members so the controller does
|
|
44
|
+
* NOT have to page through a 1400-line plan to find the active wave.
|
|
45
|
+
*
|
|
46
|
+
* Always exits 0 unless the plan is malformed (no managed block AND no
|
|
47
|
+
* wave-plans directory), in which case exit 2 with a structured error.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runWaveStatus(projectRoot: string, options?: RunWaveStatusOptions): Promise<WaveStatusReport>;
|
|
50
|
+
export declare function runWaveStatusCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
|
|
51
|
+
export {};
|