cclaw-cli 0.48.12 → 0.48.14
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/content/flow-map.d.ts +23 -0
- package/dist/content/flow-map.js +117 -0
- package/dist/content/meta-skill.js +3 -0
- package/dist/content/next-command.js +27 -0
- package/dist/content/node-hooks.js +83 -0
- package/dist/content/stages/tdd.js +2 -2
- package/dist/content/start-command.js +7 -0
- package/dist/content/tdd-log-command.js +12 -2
- package/dist/doctor-registry.js +1 -1
- package/dist/install.js +2 -0
- package/dist/internal/advance-stage.js +6 -2
- package/dist/internal/tdd-loop-status.d.ts +14 -0
- package/dist/internal/tdd-loop-status.js +68 -0
- package/dist/policy.js +4 -0
- package/dist/tdd-cycle.d.ts +40 -0
- package/dist/tdd-cycle.js +71 -1
- package/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relative path used by skills/commands to cite the consolidated flow map.
|
|
3
|
+
* Stable contract — changing this value is a breaking change for doctor
|
|
4
|
+
* policies and meta-skill links.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FLOW_MAP_REL_PATH = ".cclaw/references/flow-map.md";
|
|
7
|
+
/**
|
|
8
|
+
* Canonical one-page overview of cclaw's user-facing surface.
|
|
9
|
+
*
|
|
10
|
+
* Purpose: give the model (and any curious human) a single file that
|
|
11
|
+
* answers "what does cclaw expose, where does my current stage fit, and
|
|
12
|
+
* which files drive progress?" without forcing a walk of the 8 stage
|
|
13
|
+
* skills plus the 4 router skills plus the meta-skill.
|
|
14
|
+
*
|
|
15
|
+
* Design rules:
|
|
16
|
+
* - Keep it under ~150 lines; it is a map, not a manual.
|
|
17
|
+
* - Only cite files that already exist under `.cclaw/`.
|
|
18
|
+
* - Do not duplicate protocol text (decision/completion/ethos live in
|
|
19
|
+
* `.cclaw/references/protocols/`). Link, don't inline.
|
|
20
|
+
* - Do not introduce new gates or hard rules here — flow-map is
|
|
21
|
+
* descriptive, not prescriptive.
|
|
22
|
+
*/
|
|
23
|
+
export declare function flowMapMarkdown(): string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Relative path used by skills/commands to cite the consolidated flow map.
|
|
4
|
+
* Stable contract — changing this value is a breaking change for doctor
|
|
5
|
+
* policies and meta-skill links.
|
|
6
|
+
*/
|
|
7
|
+
export const FLOW_MAP_REL_PATH = `${RUNTIME_ROOT}/references/flow-map.md`;
|
|
8
|
+
/**
|
|
9
|
+
* Canonical one-page overview of cclaw's user-facing surface.
|
|
10
|
+
*
|
|
11
|
+
* Purpose: give the model (and any curious human) a single file that
|
|
12
|
+
* answers "what does cclaw expose, where does my current stage fit, and
|
|
13
|
+
* which files drive progress?" without forcing a walk of the 8 stage
|
|
14
|
+
* skills plus the 4 router skills plus the meta-skill.
|
|
15
|
+
*
|
|
16
|
+
* Design rules:
|
|
17
|
+
* - Keep it under ~150 lines; it is a map, not a manual.
|
|
18
|
+
* - Only cite files that already exist under `.cclaw/`.
|
|
19
|
+
* - Do not duplicate protocol text (decision/completion/ethos live in
|
|
20
|
+
* `.cclaw/references/protocols/`). Link, don't inline.
|
|
21
|
+
* - Do not introduce new gates or hard rules here — flow-map is
|
|
22
|
+
* descriptive, not prescriptive.
|
|
23
|
+
*/
|
|
24
|
+
export function flowMapMarkdown() {
|
|
25
|
+
return `# cclaw Flow Map
|
|
26
|
+
|
|
27
|
+
One-page surface reference. Use this when you need the shape of cclaw
|
|
28
|
+
without reading every skill — the stage quick-map, the user-facing
|
|
29
|
+
slash commands, the Ralph Loop signal, and the key state files.
|
|
30
|
+
|
|
31
|
+
For enforcement details, load the matching stage skill or command
|
|
32
|
+
contract. For protocols (decision/completion/ethos) see
|
|
33
|
+
\`${RUNTIME_ROOT}/references/protocols/\`.
|
|
34
|
+
|
|
35
|
+
## Stages (8)
|
|
36
|
+
|
|
37
|
+
| # | Stage | Goal | Primary artifact |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| 1 | brainstorm | Explore options and constraints | \`.cclaw/artifacts/00-idea.md\` (+ \`01-brainstorm.md\`) |
|
|
40
|
+
| 2 | scope | Freeze scope (in/out, assumptions) | \`.cclaw/artifacts/02-scope.md\` |
|
|
41
|
+
| 3 | design | Pick the shape of the change | \`.cclaw/artifacts/03-design.md\` |
|
|
42
|
+
| 4 | spec | Turn design into testable acceptance criteria | \`.cclaw/artifacts/04-spec.md\` |
|
|
43
|
+
| 5 | plan | Decompose spec into executable slices | \`.cclaw/artifacts/05-plan.md\` |
|
|
44
|
+
| 6 | tdd | Drive each slice through RED → GREEN → REFACTOR (Ralph Loop) | \`.cclaw/artifacts/06-tdd.md\` + \`.cclaw/state/tdd-cycle-log.jsonl\` |
|
|
45
|
+
| 7 | review | Cross-check correctness, spec coverage, and ethos | \`.cclaw/artifacts/07-review.md\` |
|
|
46
|
+
| 8 | ship | Close out: retro, compound, archive | \`.cclaw/artifacts/08-ship.md\` (+ \`09-retro.md\`) |
|
|
47
|
+
|
|
48
|
+
Track shortcuts (set in \`.cclaw/state/flow-state.json\`):
|
|
49
|
+
|
|
50
|
+
- \`quick\` — spec → tdd → review → ship
|
|
51
|
+
- \`medium\` — brainstorm → spec → plan → tdd → review → ship
|
|
52
|
+
- \`standard\` — all 8 stages (default)
|
|
53
|
+
|
|
54
|
+
## User-facing slash commands
|
|
55
|
+
|
|
56
|
+
| Command | Role | Notes |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| \`/cc\` | Entry point. No args = resume. With prompt = classify + start. | Writes \`00-idea.md\` and picks the track. |
|
|
59
|
+
| \`/cc-next\` | Advance or resume the current stage based on gates. | Soft nudge from Ralph Loop during \`tdd\`. |
|
|
60
|
+
| \`/cc-ideate\` | Repo-improvement discovery, separate from product flow. | Produces ideas, not stage artifacts. |
|
|
61
|
+
| \`/cc-view [status\\|tree\\|diff]\` | Read-only router. Never mutates flow state. | \`diff\` refreshes the snapshot baseline by design. |
|
|
62
|
+
| \`/cc-ops [feature\\|tdd-log\\|retro\\|compound\\|archive\\|rewind]\` | Operations router for post-flow and side-channel actions. | Mutations are scoped to each subcommand. |
|
|
63
|
+
|
|
64
|
+
Subcommand dispatch lives in \`${RUNTIME_ROOT}/commands/\` and the
|
|
65
|
+
matching \`${RUNTIME_ROOT}/skills/flow-*/SKILL.md\`. The meta-skill
|
|
66
|
+
(\`${RUNTIME_ROOT}/skills/using-cclaw/SKILL.md\`) decides which router to
|
|
67
|
+
use before any substantive work.
|
|
68
|
+
|
|
69
|
+
## Ralph Loop (TDD progress signal)
|
|
70
|
+
|
|
71
|
+
When \`currentStage === "tdd"\`, SessionStart writes
|
|
72
|
+
\`${RUNTIME_ROOT}/state/ralph-loop.json\` from the TDD cycle log. Fields
|
|
73
|
+
worth acting on:
|
|
74
|
+
|
|
75
|
+
- \`loopIteration\` — how many RED → GREEN cycles already landed.
|
|
76
|
+
- \`redOpenSlices\` — slices with an unsatisfied RED. Non-empty means do
|
|
77
|
+
**not** advance to review.
|
|
78
|
+
- \`acClosed\` — distinct acceptance-criterion IDs closed by a GREEN row
|
|
79
|
+
(requires \`acIds\` on the green log entry via \`/cc-ops tdd-log\`).
|
|
80
|
+
- \`sliceCount\` — total distinct plan slices ever touched.
|
|
81
|
+
|
|
82
|
+
Ralph Loop is a signal, not a gate. Stage advancement still runs
|
|
83
|
+
through the normal \`flow-state.json\` gate catalog.
|
|
84
|
+
|
|
85
|
+
## Key state files
|
|
86
|
+
|
|
87
|
+
| Path | What it holds |
|
|
88
|
+
|---|---|
|
|
89
|
+
| \`${RUNTIME_ROOT}/state/flow-state.json\` | Track, currentStage, completedStages, gate catalog, closeout substate. |
|
|
90
|
+
| \`${RUNTIME_ROOT}/state/delegation-log.json\` | Per-stage mandatory agent status + fulfillmentMode + evidenceRefs. |
|
|
91
|
+
| \`${RUNTIME_ROOT}/state/tdd-cycle-log.jsonl\` | Append-only RED/GREEN/REFACTOR entries (source of Ralph Loop). |
|
|
92
|
+
| \`${RUNTIME_ROOT}/state/ralph-loop.json\` | Derived Ralph Loop status (TDD-only). |
|
|
93
|
+
| \`${RUNTIME_ROOT}/state/stage-activity.jsonl\` | Append-only stage-enter/exit and gate-pass signals. |
|
|
94
|
+
| \`${RUNTIME_ROOT}/state/checkpoint.json\` | Latest session checkpoint (stage + timestamp). |
|
|
95
|
+
| \`${RUNTIME_ROOT}/state/context-mode.json\` | Active context mode (\`default\`, \`headless\`, ...). |
|
|
96
|
+
| \`${RUNTIME_ROOT}/state/harness-gaps.json\` | Per-harness tier, subagent fallback, playbook path (schemaVersion 2). |
|
|
97
|
+
| \`${RUNTIME_ROOT}/knowledge.jsonl\` | Append-only learnings; surfaced to sessions via digest. |
|
|
98
|
+
|
|
99
|
+
## Strictness and hooks
|
|
100
|
+
|
|
101
|
+
Hook-driven guards respect the \`strictness\` field in
|
|
102
|
+
\`${RUNTIME_ROOT}/config.yaml\`:
|
|
103
|
+
|
|
104
|
+
- \`advisory\` (default) — hooks warn but never block tool calls.
|
|
105
|
+
- \`strict\` — hooks block tool calls that violate their scope.
|
|
106
|
+
|
|
107
|
+
Override per-session with \`CCLAW_STRICTNESS=advisory|strict\`.
|
|
108
|
+
|
|
109
|
+
## When in doubt
|
|
110
|
+
|
|
111
|
+
1. Read \`${RUNTIME_ROOT}/state/flow-state.json\` to know where you are.
|
|
112
|
+
2. Load the matching stage skill only if you are about to do
|
|
113
|
+
substantive work (see \`using-cclaw\` meta-skill).
|
|
114
|
+
3. Prefer \`/cc-next\` for progression. \`/cc-view\` for visibility.
|
|
115
|
+
\`/cc-ops\` for side-channel operations.
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FLOW_MAP_REL_PATH } from "./flow-map.js";
|
|
1
2
|
import { COMPLETION_PROTOCOL_REL_PATH, DECISION_PROTOCOL_REL_PATH, ETHOS_PROTOCOL_REL_PATH } from "./protocols.js";
|
|
2
3
|
export const META_SKILL_NAME = "using-cclaw";
|
|
3
4
|
export function usingCclawSkillMarkdown() {
|
|
@@ -76,6 +77,8 @@ Before stage work:
|
|
|
76
77
|
brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship
|
|
77
78
|
|
|
78
79
|
Tracks may skip stages via \`flow-state.track\` + \`skippedStages\`.
|
|
80
|
+
For the full surface (stages, routers, Ralph Loop, state files) load
|
|
81
|
+
\`${FLOW_MAP_REL_PATH}\` — it is the single-page overview of cclaw.
|
|
79
82
|
|
|
80
83
|
## Contextual skill activation
|
|
81
84
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
import { FLOW_MAP_REL_PATH } from "./flow-map.js";
|
|
2
3
|
import { stageSchema } from "./stage-schema.js";
|
|
3
4
|
import { stageSkillFolder } from "./skills.js";
|
|
4
5
|
const NEXT_SKILL_FOLDER = "flow-next-step";
|
|
@@ -59,6 +60,17 @@ This is the only progression command the user needs to drive the entire flow. St
|
|
|
59
60
|
→ Load **\`${RUNTIME_ROOT}/skills/<skillFolder>/SKILL.md\`** and **\`${RUNTIME_ROOT}/commands/<currentStage>.md\`** for the current stage.
|
|
60
61
|
→ Execute that stage's protocol. The stage skill handles the full interaction including STOP points and gate tracking.
|
|
61
62
|
→ Stage completion must use \`node .cclaw/hooks/stage-complete.mjs <currentStage>\` (canonical), which validates delegations + gate evidence before mutating \`flow-state.json\`.
|
|
63
|
+
→ **Ralph Loop (tdd only).** When \`currentStage === "tdd"\`, also read
|
|
64
|
+
\`${RUNTIME_ROOT}/state/ralph-loop.json\` (refreshed on every session-start
|
|
65
|
+
while the flow is in tdd). Use it as a ground-truth progress indicator:
|
|
66
|
+
- \`loopIteration\` tells you how many RED → GREEN cycles already landed.
|
|
67
|
+
- \`acClosed\` lists the distinct acceptance-criterion IDs a GREEN row has
|
|
68
|
+
closed so far — if your plan tasks map to ACs, this is the "tasks
|
|
69
|
+
remaining" signal without needing a separate counter.
|
|
70
|
+
- \`redOpenSlices\` is the set of slices with an unsatisfied RED. Do not
|
|
71
|
+
advance to review while this is non-empty.
|
|
72
|
+
- Stage advancement to \`review\` still requires the normal gates in
|
|
73
|
+
\`flow-state.json\`; Ralph Loop status is a soft nudge, not a gate.
|
|
62
74
|
|
|
63
75
|
### Path B: Current stage IS complete (all gates passed, all delegations satisfied)
|
|
64
76
|
|
|
@@ -117,6 +129,12 @@ Validate envelopes with:
|
|
|
117
129
|
## Primary skill
|
|
118
130
|
|
|
119
131
|
**${skillRel}** — full protocol and stage table.
|
|
132
|
+
|
|
133
|
+
## Surface reference
|
|
134
|
+
|
|
135
|
+
When you need the full shape of cclaw (stages, routers, Ralph Loop,
|
|
136
|
+
state files) load \`${FLOW_MAP_REL_PATH}\`. It is the single-page
|
|
137
|
+
overview and is safe to read at any time.
|
|
120
138
|
`;
|
|
121
139
|
}
|
|
122
140
|
/**
|
|
@@ -190,6 +208,15 @@ Load the current stage's skill and command contract:
|
|
|
190
208
|
|
|
191
209
|
Execute the stage protocol. The stage skill handles interaction, STOP points, gate tracking, and stage completion via \`node .cclaw/hooks/stage-complete.mjs <stage>\` (canonical flow-state mutation path).
|
|
192
210
|
|
|
211
|
+
**Ralph Loop (tdd only).** When the current stage is \`tdd\`, pair the
|
|
212
|
+
normal gate-evidence view with \`${RUNTIME_ROOT}/state/ralph-loop.json\`:
|
|
213
|
+
\`loopIteration\` is the running count of RED → GREEN cycles,
|
|
214
|
+
\`acClosed\` lists distinct acceptance-criterion IDs already closed by
|
|
215
|
+
GREEN rows (populated from \`acIds\` in \`tdd-cycle-log.jsonl\`), and
|
|
216
|
+
\`redOpenSlices\` is the "tasks remaining" indicator. Advance only when
|
|
217
|
+
every planned slice is in \`acClosed\` (or explicitly deferred) and
|
|
218
|
+
\`redOpenSlices\` is empty.
|
|
219
|
+
|
|
193
220
|
Special-case for review: if \`review_criticals_resolved\` is in \`blocked\`, route to rework instead of looping review forever — recommend \`/cc-ops rewind tdd "review_blocked_by_critical"\`.
|
|
194
221
|
|
|
195
222
|
**Path B — stage IS complete (all gates met, all delegations done):**
|
|
@@ -705,6 +705,26 @@ async function handleSessionStart(runtime) {
|
|
|
705
705
|
const contextWarning = await readLatestContextWarningLine(contextWarningsFile);
|
|
706
706
|
const knowledge = await buildKnowledgeDigest(runtime.root, state.currentStage);
|
|
707
707
|
|
|
708
|
+
// Refresh Ralph Loop status each session-start so /cc-next and the model
|
|
709
|
+
// both read a consistent "iter=N, acClosed=[...]" snapshot. Runs only when
|
|
710
|
+
// we are in tdd — other stages skip the write to keep the file stable.
|
|
711
|
+
let ralphLoopLine = "";
|
|
712
|
+
if (state.currentStage === "tdd") {
|
|
713
|
+
try {
|
|
714
|
+
const ralphStatus = await computeRalphLoopStatusInline(stateDir, state.activeRunId);
|
|
715
|
+
await writeJsonFile(path.join(stateDir, "ralph-loop.json"), ralphStatus);
|
|
716
|
+
const redOpen = ralphStatus.redOpenSlices.length > 0
|
|
717
|
+
? ralphStatus.redOpenSlices.join(",")
|
|
718
|
+
: "none";
|
|
719
|
+
ralphLoopLine = "Ralph Loop: iter=" + String(ralphStatus.loopIteration) +
|
|
720
|
+
", slices=" + String(ralphStatus.sliceCount) +
|
|
721
|
+
", acClosed=" + String(ralphStatus.acClosed.length) +
|
|
722
|
+
", redOpen=" + redOpen;
|
|
723
|
+
} catch (_err) {
|
|
724
|
+
// best-effort — a malformed cycle log should never break session-start.
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
708
728
|
const suggestionMemory = toObject(await readJsonFile(suggestionMemoryFile, {})) || {};
|
|
709
729
|
const suggestionsEnabled = suggestionMemory.enabled !== false;
|
|
710
730
|
const mutedStages = Array.isArray(suggestionMemory.mutedStages)
|
|
@@ -769,6 +789,9 @@ async function handleSessionStart(runtime) {
|
|
|
769
789
|
if (activitySummary.length > 0) {
|
|
770
790
|
parts.push("Recent stage activity:\\n" + activitySummary.join("\\n"));
|
|
771
791
|
}
|
|
792
|
+
if (ralphLoopLine.length > 0) {
|
|
793
|
+
parts.push(ralphLoopLine);
|
|
794
|
+
}
|
|
772
795
|
if (contextWarning.length > 0) {
|
|
773
796
|
parts.push("Latest context warning:\\n" + contextWarning);
|
|
774
797
|
}
|
|
@@ -1089,6 +1112,66 @@ async function tddCycleCounts(stateDir, runId) {
|
|
|
1089
1112
|
return { red, green };
|
|
1090
1113
|
}
|
|
1091
1114
|
|
|
1115
|
+
// Mirrors src/tdd-cycle.ts::computeRalphLoopStatus — kept inline so the
|
|
1116
|
+
// SessionStart hook can write ralph-loop.json without depending on the CLI
|
|
1117
|
+
// binary being installed globally. Any schema change must update both copies.
|
|
1118
|
+
async function computeRalphLoopStatusInline(stateDir, runId) {
|
|
1119
|
+
const filePath = path.join(stateDir, "tdd-cycle-log.jsonl");
|
|
1120
|
+
const raw = await readTextFile(filePath, "");
|
|
1121
|
+
const sliceMap = new Map();
|
|
1122
|
+
const acClosed = new Set();
|
|
1123
|
+
const redOpenSlices = [];
|
|
1124
|
+
let loopIteration = 0;
|
|
1125
|
+
for (const rawLine of raw.split(/\\r?\\n/gu)) {
|
|
1126
|
+
const line = rawLine.trim();
|
|
1127
|
+
if (line.length === 0) continue;
|
|
1128
|
+
let row;
|
|
1129
|
+
try { row = JSON.parse(line); } catch { continue; }
|
|
1130
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) continue;
|
|
1131
|
+
const rowRun = typeof row.runId === "string" && row.runId.length > 0 ? row.runId : runId;
|
|
1132
|
+
if (rowRun !== runId) continue;
|
|
1133
|
+
const slice = typeof row.slice === "string" && row.slice.length > 0 ? row.slice : "S-unknown";
|
|
1134
|
+
let state = sliceMap.get(slice);
|
|
1135
|
+
if (!state) {
|
|
1136
|
+
state = { slice, redCount: 0, greenCount: 0, refactorCount: 0, redOpen: false, acIds: [] };
|
|
1137
|
+
sliceMap.set(slice, state);
|
|
1138
|
+
}
|
|
1139
|
+
const exitCode = typeof row.exitCode === "number" ? row.exitCode : undefined;
|
|
1140
|
+
if (row.phase === "red") {
|
|
1141
|
+
state.redCount += 1;
|
|
1142
|
+
if (exitCode !== undefined && exitCode !== 0) state.redOpen = true;
|
|
1143
|
+
} else if (row.phase === "green") {
|
|
1144
|
+
state.greenCount += 1;
|
|
1145
|
+
state.redOpen = false;
|
|
1146
|
+
loopIteration += 1;
|
|
1147
|
+
if (Array.isArray(row.acIds)) {
|
|
1148
|
+
for (const acId of row.acIds) {
|
|
1149
|
+
if (typeof acId !== "string" || acId.length === 0) continue;
|
|
1150
|
+
acClosed.add(acId);
|
|
1151
|
+
if (!state.acIds.includes(acId)) state.acIds.push(acId);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
} else if (row.phase === "refactor") {
|
|
1155
|
+
state.refactorCount += 1;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const state of sliceMap.values()) {
|
|
1159
|
+
if (state.redOpen) redOpenSlices.push(state.slice);
|
|
1160
|
+
}
|
|
1161
|
+
const slices = Array.from(sliceMap.values()).sort((a, b) => a.slice.localeCompare(b.slice, "en"));
|
|
1162
|
+
return {
|
|
1163
|
+
schemaVersion: 1,
|
|
1164
|
+
runId,
|
|
1165
|
+
loopIteration,
|
|
1166
|
+
redOpen: redOpenSlices.length > 0,
|
|
1167
|
+
redOpenSlices,
|
|
1168
|
+
acClosed: Array.from(acClosed).sort(),
|
|
1169
|
+
sliceCount: slices.length,
|
|
1170
|
+
slices,
|
|
1171
|
+
lastUpdatedAt: new Date().toISOString()
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1092
1175
|
function tddCycleStateFromCounts(counts) {
|
|
1093
1176
|
if (counts.red <= 0) return "need_red";
|
|
1094
1177
|
if (counts.red > counts.green) return "red_open";
|
|
@@ -20,7 +20,7 @@ export const TDD = {
|
|
|
20
20
|
"The stage intent is review/ship sign-off rather than implementation"
|
|
21
21
|
],
|
|
22
22
|
checklist: [
|
|
23
|
-
"Select plan slice — pick one task from the plan. Do not batch multiple tasks.",
|
|
23
|
+
"Select plan slice — pick one task from the plan. Do not batch multiple tasks. Before starting, read `.cclaw/state/ralph-loop.json` (`loopIteration`, `acClosed[]`, `redOpenSlices[]`) so you skip cycles already closed.",
|
|
24
24
|
"Map to acceptance criterion — identify the specific spec criterion this test proves.",
|
|
25
25
|
"Dispatch mandatory `tdd-red` execution (or `test-author` in TEST_RED_ONLY mode) — produce failing behavior tests and RED evidence only (no production edits). Set `CCLAW_ACTIVE_AGENT=tdd-red` when supported.",
|
|
26
26
|
"RED: Capture failure output — copy the exact failure output as RED evidence. Record in artifact.",
|
|
@@ -29,7 +29,7 @@ export const TDD = {
|
|
|
29
29
|
"GREEN: Verify no regressions — if any existing test breaks, fix the regression before proceeding.",
|
|
30
30
|
"Run verification-before-completion discipline for the slice — capture a fresh test command, commit SHA, and explicit PASS/FAIL status before completion claims.",
|
|
31
31
|
"REFACTOR: Dispatch `tdd-refactor` execution (or dedicated refactor mode) to improve code quality without behavior changes. Set `CCLAW_ACTIVE_AGENT=tdd-refactor` when supported.",
|
|
32
|
-
"Record evidence — capture RED failure, GREEN output, and REFACTOR notes in the TDD artifact.",
|
|
32
|
+
"Record evidence — capture RED failure, GREEN output, and REFACTOR notes in the TDD artifact. When logging the `green` row via `/cc-ops tdd-log green`, attach the closed acceptance-criterion IDs in `acIds` so Ralph Loop status counts them.",
|
|
33
33
|
"Annotate traceability — link to plan task ID and spec criterion.",
|
|
34
34
|
"Per-Slice Review (conditional) — if `.cclaw/config.yaml::sliceReview.enabled` is true and 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).",
|
|
35
35
|
"Repeat for each slice — return to step 1 for the next plan slice."
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
import { FLOW_MAP_REL_PATH } from "./flow-map.js";
|
|
2
3
|
const START_SKILL_FOLDER = "flow-start";
|
|
3
4
|
const START_SKILL_NAME = "flow-start";
|
|
4
5
|
function flowStatePath() {
|
|
@@ -115,6 +116,12 @@ Validate envelopes with:
|
|
|
115
116
|
## Primary skill
|
|
116
117
|
|
|
117
118
|
**${RUNTIME_ROOT}/skills/${START_SKILL_FOLDER}/SKILL.md**
|
|
119
|
+
|
|
120
|
+
## Surface reference
|
|
121
|
+
|
|
122
|
+
For the 1-page overview of cclaw (stages, routers, Ralph Loop, state
|
|
123
|
+
files) load \`${FLOW_MAP_REL_PATH}\` — useful when a fresh session
|
|
124
|
+
needs orientation before \`/cc <prompt>\` runs.
|
|
118
125
|
`;
|
|
119
126
|
}
|
|
120
127
|
/**
|
|
@@ -35,7 +35,9 @@ Each JSON line must include:
|
|
|
35
35
|
- \`slice\` (e.g. \`S-1\`)
|
|
36
36
|
- \`phase\` (\`red\` | \`green\` | \`refactor\`)
|
|
37
37
|
- \`command\`
|
|
38
|
-
- optional: \`files\`, \`exitCode\`, \`note\`
|
|
38
|
+
- optional: \`files\`, \`exitCode\`, \`note\`, \`acIds\` (array of acceptance
|
|
39
|
+
criterion IDs like \`["AC-1"]\` — GREEN rows use this to drive the Ralph
|
|
40
|
+
Loop status summary at \`.cclaw/state/ralph-loop.json\`).
|
|
39
41
|
|
|
40
42
|
## Primary skill
|
|
41
43
|
|
|
@@ -64,8 +66,16 @@ Do not fake RED evidence. A \`red\` entry must correspond to a failing test comm
|
|
|
64
66
|
- \`slice\`: user-provided slice id
|
|
65
67
|
- \`phase\`: red|green|refactor
|
|
66
68
|
- \`command\`: test command or refactor verification command
|
|
69
|
+
- \`acIds\` (optional, recommended on \`green\`): the acceptance-criterion
|
|
70
|
+
IDs this GREEN row closes (e.g. \`["AC-1","AC-3"]\`). The SessionStart
|
|
71
|
+
hook aggregates distinct \`acIds\` from green rows into \`acClosed\`
|
|
72
|
+
inside \`.cclaw/state/ralph-loop.json\` so \`/cc-next\` can answer
|
|
73
|
+
"is the Ralph Loop done?" without parsing the artifact.
|
|
67
74
|
3. Append one line to \`${logPath()}\`.
|
|
68
|
-
4.
|
|
75
|
+
4. After append, refresh Ralph Loop status with
|
|
76
|
+
\`cclaw internal tdd-loop-status --quiet\` (the SessionStart hook also
|
|
77
|
+
refreshes it, but a manual refresh is safe and idempotent).
|
|
78
|
+
5. \`show\`: print the last 20 lines grouped by slice.
|
|
69
79
|
|
|
70
80
|
## Validation
|
|
71
81
|
|
package/dist/doctor-registry.js
CHANGED
|
@@ -85,7 +85,7 @@ const RULES = [
|
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
|
-
test: /^(meta_skill:|protocol:|stage_skill:|context_mode:)/,
|
|
88
|
+
test: /^(meta_skill:|protocol:|stage_skill:|context_mode:|reference:)/,
|
|
89
89
|
metadata: {
|
|
90
90
|
severity: "error",
|
|
91
91
|
summary: "Routing skill and protocol integrity check.",
|
package/dist/install.js
CHANGED
|
@@ -28,6 +28,7 @@ import { stageCompleteScript, opencodePluginJs, claudeHooksJson, codexHooksJson,
|
|
|
28
28
|
import { nodeHookRuntimeScript } from "./content/node-hooks.js";
|
|
29
29
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
30
30
|
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
31
|
+
import { flowMapMarkdown } from "./content/flow-map.js";
|
|
31
32
|
import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
|
|
32
33
|
import { EVAL_BASELINES_README, EVAL_CONFIG_YAML, EVAL_CORPUS_README, EVAL_REPORTS_README, EVAL_RUBRIC_FILES, EVAL_RUBRICS_README } from "./content/eval-scaffold.js";
|
|
33
34
|
import { TDD_BATCH_WALKTHROUGH_MARKDOWN, stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
|
|
@@ -355,6 +356,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
355
356
|
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "decision.md"), decisionProtocolMarkdown());
|
|
356
357
|
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "completion.md"), completionProtocolMarkdown());
|
|
357
358
|
await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "ethos.md"), ethosProtocolMarkdown());
|
|
359
|
+
await writeFileSafe(runtimePath(projectRoot, "references", "flow-map.md"), flowMapMarkdown());
|
|
358
360
|
for (const folder of UTILITY_SKILL_FOLDERS) {
|
|
359
361
|
const generator = UTILITY_SKILL_MAP[folder];
|
|
360
362
|
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
|
|
@@ -14,6 +14,7 @@ import { readFlowState, writeFlowState } from "../runs.js";
|
|
|
14
14
|
import { FLOW_STAGES } from "../types.js";
|
|
15
15
|
import { runEnvelopeValidateCommand } from "./envelope-validate.js";
|
|
16
16
|
import { runKnowledgeDigestCommand } from "./knowledge-digest.js";
|
|
17
|
+
import { runTddLoopStatusCommand } from "./tdd-loop-status.js";
|
|
17
18
|
import { runTddRedEvidenceCommand } from "./tdd-red-evidence.js";
|
|
18
19
|
function unique(values) {
|
|
19
20
|
return [...new Set(values)];
|
|
@@ -672,7 +673,7 @@ async function runHookCommand(projectRoot, args, io) {
|
|
|
672
673
|
export async function runInternalCommand(projectRoot, argv, io) {
|
|
673
674
|
const [subcommand, ...tokens] = argv;
|
|
674
675
|
if (!subcommand) {
|
|
675
|
-
io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | hook\n");
|
|
676
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | tdd-loop-status | hook\n");
|
|
676
677
|
return 1;
|
|
677
678
|
}
|
|
678
679
|
try {
|
|
@@ -694,10 +695,13 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
694
695
|
if (subcommand === "tdd-red-evidence") {
|
|
695
696
|
return await runTddRedEvidenceCommand(projectRoot, tokens, io);
|
|
696
697
|
}
|
|
698
|
+
if (subcommand === "tdd-loop-status") {
|
|
699
|
+
return await runTddLoopStatusCommand(projectRoot, tokens, io);
|
|
700
|
+
}
|
|
697
701
|
if (subcommand === "hook") {
|
|
698
702
|
return await runHookCommand(projectRoot, parseHookArgs(tokens), io);
|
|
699
703
|
}
|
|
700
|
-
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | hook\n`);
|
|
704
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state | knowledge-digest | envelope-validate | tdd-red-evidence | tdd-loop-status | hook\n`);
|
|
701
705
|
return 1;
|
|
702
706
|
}
|
|
703
707
|
catch (err) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Writable } from "node:stream";
|
|
2
|
+
import { type RalphLoopStatus } from "../tdd-cycle.js";
|
|
3
|
+
interface InternalIo {
|
|
4
|
+
stdout: Writable;
|
|
5
|
+
stderr: Writable;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Produces a one-line "Ralph Loop: iter=X, slices=Y, acClosed=Z, redOpen=..."
|
|
9
|
+
* summary — suitable for session-digest / bootstrap surfaces where the user
|
|
10
|
+
* just needs a progress indicator, not the full slice breakdown.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatRalphLoopStatusLine(status: RalphLoopStatus): string;
|
|
13
|
+
export declare function runTddLoopStatusCommand(projectRoot: string, argv: string[], io: InternalIo): Promise<number>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
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 { readFlowState } from "../runs.js";
|
|
6
|
+
import { computeRalphLoopStatus, parseTddCycleLog } from "../tdd-cycle.js";
|
|
7
|
+
function parseArgs(tokens) {
|
|
8
|
+
const args = { json: false, quiet: false, write: true };
|
|
9
|
+
for (const token of tokens) {
|
|
10
|
+
if (token === "--json")
|
|
11
|
+
args.json = true;
|
|
12
|
+
else if (token === "--quiet")
|
|
13
|
+
args.quiet = true;
|
|
14
|
+
else if (token === "--no-write")
|
|
15
|
+
args.write = false;
|
|
16
|
+
else if (token === "--write")
|
|
17
|
+
args.write = true;
|
|
18
|
+
else
|
|
19
|
+
throw new Error(`Unknown tdd-loop-status flag: ${token}`);
|
|
20
|
+
}
|
|
21
|
+
return args;
|
|
22
|
+
}
|
|
23
|
+
function stateDir(projectRoot) {
|
|
24
|
+
return path.join(projectRoot, RUNTIME_ROOT, "state");
|
|
25
|
+
}
|
|
26
|
+
async function readCycleLog(projectRoot) {
|
|
27
|
+
const filePath = path.join(stateDir(projectRoot), "tdd-cycle-log.jsonl");
|
|
28
|
+
try {
|
|
29
|
+
return await fs.readFile(filePath, "utf8");
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err.code === "ENOENT")
|
|
33
|
+
return "";
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Produces a one-line "Ralph Loop: iter=X, slices=Y, acClosed=Z, redOpen=..."
|
|
39
|
+
* summary — suitable for session-digest / bootstrap surfaces where the user
|
|
40
|
+
* just needs a progress indicator, not the full slice breakdown.
|
|
41
|
+
*/
|
|
42
|
+
export function formatRalphLoopStatusLine(status) {
|
|
43
|
+
const redOpen = status.redOpenSlices.length > 0
|
|
44
|
+
? status.redOpenSlices.join(",")
|
|
45
|
+
: "none";
|
|
46
|
+
return `Ralph Loop: iter=${status.loopIteration}, slices=${status.sliceCount}, acClosed=${status.acClosed.length}, redOpen=${redOpen}`;
|
|
47
|
+
}
|
|
48
|
+
export async function runTddLoopStatusCommand(projectRoot, argv, io) {
|
|
49
|
+
const args = parseArgs(argv);
|
|
50
|
+
const flow = await readFlowState(projectRoot).catch(() => null);
|
|
51
|
+
const runId = flow?.activeRunId ?? "active";
|
|
52
|
+
const text = await readCycleLog(projectRoot);
|
|
53
|
+
const entries = parseTddCycleLog(text);
|
|
54
|
+
const status = computeRalphLoopStatus(entries, { runId });
|
|
55
|
+
if (args.write) {
|
|
56
|
+
const target = path.join(stateDir(projectRoot), "ralph-loop.json");
|
|
57
|
+
await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
|
|
58
|
+
}
|
|
59
|
+
if (!args.quiet) {
|
|
60
|
+
if (args.json) {
|
|
61
|
+
io.stdout.write(`${JSON.stringify(status, null, 2)}\n`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
io.stdout.write(`${formatRalphLoopStatusLine(status)}\n`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
package/dist/policy.js
CHANGED
|
@@ -142,6 +142,10 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
142
142
|
{ file: runtimeFile("references/protocols/decision.md"), needle: "# Decision Protocol", name: "protocol:decision" },
|
|
143
143
|
{ file: runtimeFile("references/protocols/completion.md"), needle: "# Stage Completion Protocol", name: "protocol:completion" },
|
|
144
144
|
{ file: runtimeFile("references/protocols/ethos.md"), needle: "# Engineering Ethos", name: "protocol:ethos" },
|
|
145
|
+
{ file: runtimeFile("references/flow-map.md"), needle: "# cclaw Flow Map", name: "reference:flow_map:header" },
|
|
146
|
+
{ file: runtimeFile("references/flow-map.md"), needle: "## Stages (8)", name: "reference:flow_map:stages" },
|
|
147
|
+
{ file: runtimeFile("references/flow-map.md"), needle: "## Ralph Loop", name: "reference:flow_map:ralph_loop" },
|
|
148
|
+
{ file: runtimeFile("references/flow-map.md"), needle: "## Key state files", name: "reference:flow_map:state_files" },
|
|
145
149
|
{ file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
|
|
146
150
|
{ file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "common-guidance.md", name: "stage_skill:shared_guidance_reference" },
|
|
147
151
|
{ file: runtimeFile("skills/security/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:security:hard_gate" },
|
package/dist/tdd-cycle.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export interface TddCycleEntry {
|
|
|
9
9
|
files?: string[];
|
|
10
10
|
exitCode?: number;
|
|
11
11
|
note?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional acceptance-criterion IDs this log line relates to (e.g. `["AC-1"]`).
|
|
14
|
+
* Used by the Ralph Loop status summary to surface how many ACs have been
|
|
15
|
+
* closed by a GREEN cycle without forcing the user to track them manually.
|
|
16
|
+
*/
|
|
17
|
+
acIds?: string[];
|
|
12
18
|
}
|
|
13
19
|
export interface TddCycleValidation {
|
|
14
20
|
ok: boolean;
|
|
@@ -20,6 +26,40 @@ export declare function parseTddCycleLog(text: string): TddCycleEntry[];
|
|
|
20
26
|
export declare function validateTddCycleOrder(entries: TddCycleEntry[], options?: {
|
|
21
27
|
runId?: string;
|
|
22
28
|
}): TddCycleValidation;
|
|
29
|
+
export interface RalphLoopSliceState {
|
|
30
|
+
slice: string;
|
|
31
|
+
redCount: number;
|
|
32
|
+
greenCount: number;
|
|
33
|
+
refactorCount: number;
|
|
34
|
+
redOpen: boolean;
|
|
35
|
+
acIds: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface RalphLoopStatus {
|
|
38
|
+
schemaVersion: 1;
|
|
39
|
+
runId: string;
|
|
40
|
+
/**
|
|
41
|
+
* Number of RED -> GREEN cycles observed for the run — a rough "Ralph Loop"
|
|
42
|
+
* iteration counter that mirrors how many passing tests the loop has
|
|
43
|
+
* delivered so far.
|
|
44
|
+
*/
|
|
45
|
+
loopIteration: number;
|
|
46
|
+
redOpen: boolean;
|
|
47
|
+
redOpenSlices: string[];
|
|
48
|
+
acClosed: string[];
|
|
49
|
+
sliceCount: number;
|
|
50
|
+
slices: RalphLoopSliceState[];
|
|
51
|
+
lastUpdatedAt: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
|
|
55
|
+
* The goal is to give the model a single source of truth for "am I done
|
|
56
|
+
* iterating?" — it collapses per-slice progress and distinct closed AC IDs
|
|
57
|
+
* (from GREEN rows) into a single artifact the next-command contract reads.
|
|
58
|
+
*/
|
|
59
|
+
export declare function computeRalphLoopStatus(entries: TddCycleEntry[], options?: {
|
|
60
|
+
runId?: string;
|
|
61
|
+
now?: Date;
|
|
62
|
+
}): RalphLoopStatus;
|
|
23
63
|
/**
|
|
24
64
|
* Checks whether the log contains a failing RED record associated with
|
|
25
65
|
* `productionPath` for the active run.
|
package/dist/tdd-cycle.js
CHANGED
|
@@ -25,7 +25,11 @@ export function parseTddCycleLog(text) {
|
|
|
25
25
|
? parsed.files.filter((item) => typeof item === "string")
|
|
26
26
|
: undefined,
|
|
27
27
|
exitCode: typeof parsed.exitCode === "number" ? parsed.exitCode : undefined,
|
|
28
|
-
note: typeof parsed.note === "string" ? parsed.note : undefined
|
|
28
|
+
note: typeof parsed.note === "string" ? parsed.note : undefined,
|
|
29
|
+
acIds: Array.isArray(parsed.acIds)
|
|
30
|
+
? parsed.acIds
|
|
31
|
+
.filter((item) => typeof item === "string" && item.length > 0)
|
|
32
|
+
: undefined
|
|
29
33
|
};
|
|
30
34
|
out.push(entry);
|
|
31
35
|
}
|
|
@@ -122,6 +126,72 @@ export function validateTddCycleOrder(entries, options = {}) {
|
|
|
122
126
|
function normalizePath(value) {
|
|
123
127
|
return value.replace(/\\/gu, "/").toLowerCase();
|
|
124
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
|
|
131
|
+
* The goal is to give the model a single source of truth for "am I done
|
|
132
|
+
* iterating?" — it collapses per-slice progress and distinct closed AC IDs
|
|
133
|
+
* (from GREEN rows) into a single artifact the next-command contract reads.
|
|
134
|
+
*/
|
|
135
|
+
export function computeRalphLoopStatus(entries, options = {}) {
|
|
136
|
+
const runId = options.runId ?? "active";
|
|
137
|
+
const filtered = entries.filter((entry) => options.runId ? entry.runId === options.runId : true);
|
|
138
|
+
const slicesMap = new Map();
|
|
139
|
+
const acClosedSet = new Set();
|
|
140
|
+
let loopIteration = 0;
|
|
141
|
+
const redOpenSlices = [];
|
|
142
|
+
for (const slice of Array.from(new Set(filtered.map((entry) => entry.slice)))) {
|
|
143
|
+
slicesMap.set(slice, {
|
|
144
|
+
slice,
|
|
145
|
+
redCount: 0,
|
|
146
|
+
greenCount: 0,
|
|
147
|
+
refactorCount: 0,
|
|
148
|
+
redOpen: false,
|
|
149
|
+
acIds: []
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
for (const entry of filtered) {
|
|
153
|
+
const state = slicesMap.get(entry.slice);
|
|
154
|
+
if (!state)
|
|
155
|
+
continue;
|
|
156
|
+
if (entry.phase === "red") {
|
|
157
|
+
state.redCount += 1;
|
|
158
|
+
if (entry.exitCode !== undefined && entry.exitCode !== 0) {
|
|
159
|
+
state.redOpen = true;
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (entry.phase === "green") {
|
|
164
|
+
state.greenCount += 1;
|
|
165
|
+
state.redOpen = false;
|
|
166
|
+
loopIteration += 1;
|
|
167
|
+
if (Array.isArray(entry.acIds)) {
|
|
168
|
+
for (const acId of entry.acIds) {
|
|
169
|
+
acClosedSet.add(acId);
|
|
170
|
+
if (!state.acIds.includes(acId))
|
|
171
|
+
state.acIds.push(acId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
state.refactorCount += 1;
|
|
177
|
+
}
|
|
178
|
+
for (const state of slicesMap.values()) {
|
|
179
|
+
if (state.redOpen)
|
|
180
|
+
redOpenSlices.push(state.slice);
|
|
181
|
+
}
|
|
182
|
+
const slices = Array.from(slicesMap.values()).sort((a, b) => a.slice.localeCompare(b.slice, "en"));
|
|
183
|
+
return {
|
|
184
|
+
schemaVersion: 1,
|
|
185
|
+
runId,
|
|
186
|
+
loopIteration,
|
|
187
|
+
redOpen: redOpenSlices.length > 0,
|
|
188
|
+
redOpenSlices,
|
|
189
|
+
acClosed: Array.from(acClosedSet).sort(),
|
|
190
|
+
sliceCount: slices.length,
|
|
191
|
+
slices,
|
|
192
|
+
lastUpdatedAt: (options.now ?? new Date()).toISOString()
|
|
193
|
+
};
|
|
194
|
+
}
|
|
125
195
|
/**
|
|
126
196
|
* Checks whether the log contains a failing RED record associated with
|
|
127
197
|
* `productionPath` for the active run.
|