cclaw-cli 0.55.2 → 1.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/README.md +3 -3
- package/dist/artifact-linter/brainstorm.js +45 -1
- package/dist/artifact-linter/design.js +32 -1
- package/dist/artifact-linter/plan.js +22 -1
- package/dist/artifact-linter/review.js +35 -1
- package/dist/artifact-linter/scope.js +19 -9
- package/dist/artifact-linter/shared.d.ts +11 -10
- package/dist/artifact-linter/shared.js +70 -41
- package/dist/artifact-linter/ship.js +36 -0
- package/dist/artifact-linter/spec.js +23 -1
- package/dist/artifact-linter/tdd.js +74 -0
- package/dist/artifact-linter.d.ts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -0
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +10 -11
- package/dist/content/core-agents.d.ts +35 -36
- package/dist/content/core-agents.js +189 -99
- package/dist/content/diff-command.js +1 -1
- package/dist/content/examples.d.ts +0 -3
- package/dist/content/examples.js +197 -752
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +10 -26
- package/dist/content/node-hooks.js +131 -97
- package/dist/content/opencode-plugin.js +12 -26
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/runtime-shared-snippets.d.ts +8 -0
- package/dist/content/runtime-shared-snippets.js +80 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.d.ts +1 -0
- package/dist/content/skills.js +50 -0
- package/dist/content/stage-schema.js +107 -63
- package/dist/content/stages/review.js +8 -8
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/ship.js +1 -1
- package/dist/content/status-command.js +3 -3
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +12 -82
- package/dist/content/templates.js +87 -6
- package/dist/content/utility-skills.js +26 -97
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -31
- package/dist/gate-evidence.js +3 -181
- package/dist/harness-adapters.js +1 -1
- package/dist/install.js +38 -4
- package/dist/internal/advance-stage/advance.js +0 -1
- package/dist/internal/advance-stage/review-loop.js +1 -10
- package/dist/knowledge-store.d.ts +2 -20
- package/dist/knowledge-store.js +43 -57
- package/dist/policy.js +3 -3
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.js +1 -4
- package/dist/run-persistence.js +14 -109
- package/dist/runtime/run-hook.entry.d.ts +3 -0
- package/dist/runtime/run-hook.entry.js +5 -0
- package/dist/runtime/run-hook.mjs +9477 -0
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -96
- package/dist/content/hook-inline-snippets.js +0 -515
- package/dist/content/idea-command.d.ts +0 -8
- package/dist/content/idea-command.js +0 -322
- package/dist/content/idea-frames.d.ts +0 -31
- package/dist/content/idea-frames.js +0 -140
- package/dist/content/idea-ranking.d.ts +0 -25
- package/dist/content/idea-ranking.js +0 -65
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import { DEFAULT_COMPOUND_RECURRENCE_THRESHOLD, DEFAULT_EARLY_LOOP_MAX_ITERATIONS } from "../config.js";
|
|
5
5
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
6
6
|
import { SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD, SMALL_PROJECT_RECURRENCE_THRESHOLD } from "../knowledge-store.js";
|
|
7
|
-
import {
|
|
7
|
+
import { SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS, SHARED_STAGE_SUPPORT_SNIPPETS } from "./runtime-shared-snippets.js";
|
|
8
8
|
function normalizePatterns(patterns, fallback) {
|
|
9
9
|
if (!patterns || patterns.length === 0)
|
|
10
10
|
return [...fallback];
|
|
@@ -84,6 +84,9 @@ const EARLY_LOOP_MAX_ITERATIONS = ${JSON.stringify(earlyLoopMaxIterations)};
|
|
|
84
84
|
const CCLAW_CLI_ENTRYPOINT = ${JSON.stringify(cliRuntime.entrypoint)};
|
|
85
85
|
const CCLAW_CLI_ARGS_PREFIX = ${JSON.stringify(cliRuntime.argsPrefix)};
|
|
86
86
|
|
|
87
|
+
${SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS}
|
|
88
|
+
${SHARED_STAGE_SUPPORT_SNIPPETS}
|
|
89
|
+
|
|
87
90
|
function resolveStrictness() {
|
|
88
91
|
return process.env.CCLAW_STRICTNESS === "strict" ? "strict" : DEFAULT_STRICTNESS;
|
|
89
92
|
}
|
|
@@ -427,6 +430,73 @@ async function runCclawInternal(root, args, options = {}) {
|
|
|
427
430
|
});
|
|
428
431
|
}
|
|
429
432
|
|
|
433
|
+
function compactStderr(value) {
|
|
434
|
+
const raw = typeof value === "string" ? value : "";
|
|
435
|
+
return raw.replace(/\\s+/gu, " ").trim();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function summarizeInternalFailure(operation, result) {
|
|
439
|
+
const detail = compactStderr(result && typeof result === "object" ? result.stderr : "");
|
|
440
|
+
return detail.length > 0 ? operation + ": " + detail : operation + " failed";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function parseJsonStdoutObject(result) {
|
|
444
|
+
const raw = typeof (result && result.stdout) === "string" ? result.stdout.trim() : "";
|
|
445
|
+
if (raw.length === 0) return null;
|
|
446
|
+
try {
|
|
447
|
+
return toObject(JSON.parse(raw));
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function firstStdoutLine(value) {
|
|
454
|
+
const raw = typeof value === "string" ? value : "";
|
|
455
|
+
const lines = raw
|
|
456
|
+
.split(/\\r?\\n/gu)
|
|
457
|
+
.map((line) => line.trim())
|
|
458
|
+
.filter((line) => line.length > 0);
|
|
459
|
+
return lines[0] || "";
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function formatRalphLoopStatusLineFromJson(status) {
|
|
463
|
+
const redOpenSlices = Array.isArray(status.redOpenSlices)
|
|
464
|
+
? status.redOpenSlices.filter((value) => typeof value === "string")
|
|
465
|
+
: [];
|
|
466
|
+
const redOpen = redOpenSlices.length > 0 ? redOpenSlices.join(",") : "none";
|
|
467
|
+
const loopIteration =
|
|
468
|
+
typeof status.loopIteration === "number" && Number.isFinite(status.loopIteration)
|
|
469
|
+
? Math.trunc(status.loopIteration)
|
|
470
|
+
: 0;
|
|
471
|
+
const sliceCount =
|
|
472
|
+
typeof status.sliceCount === "number" && Number.isFinite(status.sliceCount)
|
|
473
|
+
? Math.trunc(status.sliceCount)
|
|
474
|
+
: 0;
|
|
475
|
+
const acClosed = Array.isArray(status.acClosed) ? status.acClosed.length : 0;
|
|
476
|
+
return "Ralph Loop: iter=" + String(loopIteration) +
|
|
477
|
+
", slices=" + String(sliceCount) +
|
|
478
|
+
", acClosed=" + String(acClosed) +
|
|
479
|
+
", redOpen=" + redOpen;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function formatEarlyLoopStatusLineFromJson(status) {
|
|
483
|
+
const stage = typeof status.stage === "string" ? status.stage : "unknown";
|
|
484
|
+
const iteration =
|
|
485
|
+
typeof status.iteration === "number" && Number.isFinite(status.iteration)
|
|
486
|
+
? Math.trunc(status.iteration)
|
|
487
|
+
: 0;
|
|
488
|
+
const maxIterations =
|
|
489
|
+
typeof status.maxIterations === "number" && Number.isFinite(status.maxIterations)
|
|
490
|
+
? Math.trunc(status.maxIterations)
|
|
491
|
+
: EARLY_LOOP_MAX_ITERATIONS;
|
|
492
|
+
const openConcerns = Array.isArray(status.openConcerns) ? status.openConcerns.length : 0;
|
|
493
|
+
const convergence = status.convergenceTripped === true ? "tripped" : "clear";
|
|
494
|
+
return "Early Loop: stage=" + stage +
|
|
495
|
+
", iter=" + String(iteration) + "/" + String(maxIterations) +
|
|
496
|
+
", open=" + String(openConcerns) +
|
|
497
|
+
", convergence=" + convergence;
|
|
498
|
+
}
|
|
499
|
+
|
|
430
500
|
function detectHarness(env) {
|
|
431
501
|
if (env.CLAUDE_PROJECT_DIR) return "claude";
|
|
432
502
|
if (env.CURSOR_PROJECT_DIR || env.CURSOR_PROJECT_ROOT) return "cursor";
|
|
@@ -839,12 +909,12 @@ async function readFlowState(root) {
|
|
|
839
909
|
// empty object. Silent fallbacks used to mask stale CLI+hook drift.
|
|
840
910
|
const parsed = await readJsonFile(statePath, {}, { root, stage: "read-flow-state" });
|
|
841
911
|
const obj = toObject(parsed) || {};
|
|
842
|
-
const
|
|
912
|
+
const summary = summarizeFlowState(obj);
|
|
843
913
|
return {
|
|
844
914
|
filePath: statePath,
|
|
845
|
-
currentStage:
|
|
846
|
-
activeRunId:
|
|
847
|
-
completedCount: completed
|
|
915
|
+
currentStage: summary.stage,
|
|
916
|
+
activeRunId: summary.activeRunId === "none" ? "active" : summary.activeRunId,
|
|
917
|
+
completedCount: summary.completed,
|
|
848
918
|
raw: obj
|
|
849
919
|
};
|
|
850
920
|
}
|
|
@@ -857,44 +927,16 @@ async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
|
|
|
857
927
|
const raw = typeof prereadRaw === "string"
|
|
858
928
|
? prereadRaw
|
|
859
929
|
: await readTextFile(knowledgeFile, "");
|
|
860
|
-
const
|
|
861
|
-
let learningsCount = 0;
|
|
862
|
-
const parsedRows = [];
|
|
863
|
-
for (const line of lines) {
|
|
864
|
-
if (line.startsWith("{")) learningsCount += 1;
|
|
865
|
-
try {
|
|
866
|
-
const parsed = JSON.parse(line);
|
|
867
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
868
|
-
parsedRows.push(parsed);
|
|
869
|
-
} catch {
|
|
870
|
-
// ignore malformed knowledge line in digest
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
const relevant = parsedRows
|
|
874
|
-
.filter((row) => {
|
|
875
|
-
const stage = typeof row.stage === "string" ? row.stage : null;
|
|
876
|
-
return stage === null || stage === currentStage;
|
|
877
|
-
})
|
|
878
|
-
.slice(-6)
|
|
879
|
-
.reverse()
|
|
880
|
-
.map((row) => {
|
|
881
|
-
const confidence = typeof row.confidence === "string" ? row.confidence : "unknown";
|
|
882
|
-
const stage = typeof row.stage === "string" ? row.stage : "global";
|
|
883
|
-
const domain = typeof row.domain === "string" ? row.domain : "general";
|
|
884
|
-
const trigger = typeof row.trigger === "string" ? row.trigger : "trigger";
|
|
885
|
-
const action = typeof row.action === "string" ? row.action : "action";
|
|
886
|
-
return "- [" + confidence + " • " + stage + " • " + domain + "] " + trigger + " -> " + action;
|
|
887
|
-
});
|
|
930
|
+
const digest = parseKnowledgeDigest(raw, currentStage, 6);
|
|
888
931
|
return {
|
|
889
|
-
digestLines:
|
|
890
|
-
learningsCount
|
|
932
|
+
digestLines: digest.lines,
|
|
933
|
+
learningsCount: digest.learningsCount
|
|
891
934
|
};
|
|
892
935
|
}
|
|
893
936
|
|
|
894
937
|
async function readStageSupportContext(root, currentStage) {
|
|
895
|
-
|
|
896
|
-
const
|
|
897
|
-
if (!validStages.has(stage)) return [];
|
|
938
|
+
if (!isKnownStageId(currentStage)) return [];
|
|
939
|
+
const stage = currentStage;
|
|
898
940
|
|
|
899
941
|
const parts = [];
|
|
900
942
|
const contractPath = path.join(root, RUNTIME_ROOT, "templates", "state-contracts", stage + ".json");
|
|
@@ -906,12 +948,7 @@ async function readStageSupportContext(root, currentStage) {
|
|
|
906
948
|
);
|
|
907
949
|
}
|
|
908
950
|
|
|
909
|
-
const
|
|
910
|
-
brainstorm: "brainstorm-self-review.md",
|
|
911
|
-
scope: "scope-ceo-review.md",
|
|
912
|
-
design: "design-eng-review.md"
|
|
913
|
-
};
|
|
914
|
-
const promptName = reviewPromptByStage[stage];
|
|
951
|
+
const promptName = reviewPromptFileName(stage);
|
|
915
952
|
if (typeof promptName === "string") {
|
|
916
953
|
const promptPath = path.join(root, RUNTIME_ROOT, "skills", "review-prompts", promptName);
|
|
917
954
|
const prompt = (await readTextFile(promptPath, "")).trim();
|
|
@@ -952,15 +989,19 @@ async function handleSessionStart(runtime) {
|
|
|
952
989
|
let earlyLoopLine = "";
|
|
953
990
|
if (state.currentStage === "tdd") {
|
|
954
991
|
try {
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
",
|
|
962
|
-
|
|
963
|
-
|
|
992
|
+
const internalRalph = await runCclawInternal(
|
|
993
|
+
runtime.root,
|
|
994
|
+
["tdd-loop-status", "--json", "--write"],
|
|
995
|
+
{ captureStdout: true }
|
|
996
|
+
);
|
|
997
|
+
if (internalRalph.code !== 0) {
|
|
998
|
+
throw new Error(summarizeInternalFailure("tdd-loop-status", internalRalph));
|
|
999
|
+
}
|
|
1000
|
+
const ralphStatus = parseJsonStdoutObject(internalRalph);
|
|
1001
|
+
if (!ralphStatus) {
|
|
1002
|
+
throw new Error("tdd-loop-status returned empty or malformed JSON");
|
|
1003
|
+
}
|
|
1004
|
+
ralphLoopLine = formatRalphLoopStatusLineFromJson(ralphStatus);
|
|
964
1005
|
} catch (err) {
|
|
965
1006
|
// Best-effort — a malformed cycle log should never break
|
|
966
1007
|
// session-start. But we DO leave a breadcrumb in
|
|
@@ -978,14 +1019,27 @@ async function handleSessionStart(runtime) {
|
|
|
978
1019
|
(state.currentStage === "brainstorm" || state.currentStage === "scope" || state.currentStage === "design")
|
|
979
1020
|
) {
|
|
980
1021
|
try {
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1022
|
+
const internalEarly = await runCclawInternal(
|
|
1023
|
+
runtime.root,
|
|
1024
|
+
[
|
|
1025
|
+
"early-loop-status",
|
|
1026
|
+
"--json",
|
|
1027
|
+
"--write",
|
|
1028
|
+
"--stage",
|
|
1029
|
+
state.currentStage,
|
|
1030
|
+
"--run-id",
|
|
1031
|
+
state.activeRunId
|
|
1032
|
+
],
|
|
1033
|
+
{ captureStdout: true }
|
|
986
1034
|
);
|
|
987
|
-
|
|
988
|
-
|
|
1035
|
+
if (internalEarly.code !== 0) {
|
|
1036
|
+
throw new Error(summarizeInternalFailure("early-loop-status", internalEarly));
|
|
1037
|
+
}
|
|
1038
|
+
const earlyLoopStatus = parseJsonStdoutObject(internalEarly);
|
|
1039
|
+
if (!earlyLoopStatus) {
|
|
1040
|
+
throw new Error("early-loop-status returned empty or malformed JSON");
|
|
1041
|
+
}
|
|
1042
|
+
earlyLoopLine = formatEarlyLoopStatusLineFromJson(earlyLoopStatus);
|
|
989
1043
|
} catch (err) {
|
|
990
1044
|
await recordHookError(
|
|
991
1045
|
runtime.root,
|
|
@@ -1000,32 +1054,17 @@ async function handleSessionStart(runtime) {
|
|
|
1000
1054
|
// where lifting becomes relevant; earlier stages update the file silently.
|
|
1001
1055
|
let compoundReadinessLine = "";
|
|
1002
1056
|
try {
|
|
1003
|
-
|
|
1057
|
+
const shouldShowReadiness = state.currentStage === "review" || state.currentStage === "ship";
|
|
1004
1058
|
const internalReadiness = await runCclawInternal(
|
|
1005
1059
|
runtime.root,
|
|
1006
|
-
["compound-readiness", "--
|
|
1060
|
+
shouldShowReadiness ? ["compound-readiness"] : ["compound-readiness", "--quiet"],
|
|
1007
1061
|
{ captureStdout: true }
|
|
1008
1062
|
);
|
|
1009
|
-
if (internalReadiness.code
|
|
1010
|
-
|
|
1011
|
-
const parsed = JSON.parse(internalReadiness.stdout);
|
|
1012
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1013
|
-
readiness = parsed;
|
|
1014
|
-
}
|
|
1015
|
-
} catch {
|
|
1016
|
-
readiness = null;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
if (!readiness) {
|
|
1020
|
-
const archivedRunsCount = await countArchivedRunsInline(runtime.root);
|
|
1021
|
-
readiness = await computeCompoundReadinessInline(runtime.root, {
|
|
1022
|
-
prereadRaw: knowledgeRaw,
|
|
1023
|
-
...(typeof archivedRunsCount === "number" ? { archivedRunsCount } : {})
|
|
1024
|
-
});
|
|
1025
|
-
await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
|
|
1063
|
+
if (internalReadiness.code !== 0) {
|
|
1064
|
+
throw new Error(summarizeInternalFailure("compound-readiness", internalReadiness));
|
|
1026
1065
|
}
|
|
1027
|
-
if (
|
|
1028
|
-
compoundReadinessLine =
|
|
1066
|
+
if (shouldShowReadiness) {
|
|
1067
|
+
compoundReadinessLine = firstStdoutLine(internalReadiness.stdout);
|
|
1029
1068
|
}
|
|
1030
1069
|
} catch (err) {
|
|
1031
1070
|
// Best-effort — a malformed knowledge.jsonl must never break
|
|
@@ -1063,8 +1102,8 @@ async function handleSessionStart(runtime) {
|
|
|
1063
1102
|
"/8 completed, run=" +
|
|
1064
1103
|
state.activeRunId +
|
|
1065
1104
|
"). Active artifacts: " +
|
|
1066
|
-
RUNTIME_ROOT +
|
|
1067
|
-
"
|
|
1105
|
+
activeArtifactsPathLabel(RUNTIME_ROOT) +
|
|
1106
|
+
" Learnings: " +
|
|
1068
1107
|
String(knowledge.learningsCount) +
|
|
1069
1108
|
" entries."
|
|
1070
1109
|
];
|
|
@@ -1169,7 +1208,7 @@ async function handleStopHandoff(runtime) {
|
|
|
1169
1208
|
const shipSubstate = typeof closeoutObj.shipSubstate === "string" ? closeoutObj.shipSubstate : "idle";
|
|
1170
1209
|
const closeoutContext =
|
|
1171
1210
|
state.currentStage === "ship" || shipSubstate !== "idle"
|
|
1172
|
-
? " closeout.shipSubstate=" + shipSubstate + "; closeout chain=
|
|
1211
|
+
? " closeout.shipSubstate=" + shipSubstate + "; closeout chain=post_ship_review -> archive; continue closeout with /cc."
|
|
1173
1212
|
: "";
|
|
1174
1213
|
|
|
1175
1214
|
const message =
|
|
@@ -1246,17 +1285,6 @@ async function handlePromptGuard(runtime) {
|
|
|
1246
1285
|
return 0;
|
|
1247
1286
|
}
|
|
1248
1287
|
|
|
1249
|
-
// Inline mirrors of canonical CLI computations (compound-readiness,
|
|
1250
|
-
// ralph-loop, early-loop) are factored into
|
|
1251
|
-
// src/content/hook-inline-snippets.ts so
|
|
1252
|
-
// this 2000+-line file no longer owns their bodies. Each snippet carries
|
|
1253
|
-
// an explicit "mirrors X, parity enforced by Y" comment in the snippets
|
|
1254
|
-
// module. Parity is enforced by tests/unit/ralph-loop-parity.test.ts.
|
|
1255
|
-
${HOOK_INLINE_SHARED_HELPERS}
|
|
1256
|
-
${COMPOUND_READINESS_INLINE_SOURCE}
|
|
1257
|
-
${RALPH_LOOP_INLINE_SOURCE}
|
|
1258
|
-
${EARLY_LOOP_INLINE_SOURCE}
|
|
1259
|
-
|
|
1260
1288
|
async function hasFailingRedEvidenceForPath(stateDir, runId, rawPath) {
|
|
1261
1289
|
const cycleRaw = await readTextFile(path.join(stateDir, "tdd-cycle-log.jsonl"), "");
|
|
1262
1290
|
for (const line of cycleRaw.split(/\\r?\\n/gu)) {
|
|
@@ -1468,8 +1496,14 @@ async function handleWorkflowGuard(runtime) {
|
|
|
1468
1496
|
// writes that actually belonged to a new, not-yet-red S-2. Now
|
|
1469
1497
|
// we reuse the canonical Ralph Loop status: if NO slice has an
|
|
1470
1498
|
// open RED, we block.
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1499
|
+
const internalRalph = await runCclawInternal(
|
|
1500
|
+
runtime.root,
|
|
1501
|
+
["tdd-loop-status", "--json", "--no-write"],
|
|
1502
|
+
{ captureStdout: true }
|
|
1503
|
+
);
|
|
1504
|
+
const ralphStatus = parseJsonStdoutObject(internalRalph);
|
|
1505
|
+
const redOpen = internalRalph.code === 0 && ralphStatus?.redOpen === true;
|
|
1506
|
+
if (!redOpen) {
|
|
1473
1507
|
reasons.push("tdd_write_without_open_red");
|
|
1474
1508
|
}
|
|
1475
1509
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
2
|
import { META_SKILL_NAME } from "./meta-skill.js";
|
|
3
|
+
import { SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS, SHARED_STAGE_SUPPORT_SNIPPETS } from "./runtime-shared-snippets.js";
|
|
3
4
|
export function opencodePluginJs(_options = {}) {
|
|
4
5
|
return `// cclaw OpenCode plugin — generated by npx cclaw-cli sync
|
|
5
6
|
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
@@ -16,13 +17,8 @@ export default function cclawPlugin(ctx) {
|
|
|
16
17
|
const flowStatePath = join(stateDir, "flow-state.json");
|
|
17
18
|
const knowledgePath = join(runtimeDir, "knowledge.jsonl");
|
|
18
19
|
const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
brainstorm: "brainstorm-self-review.md",
|
|
22
|
-
scope: "scope-ceo-review.md",
|
|
23
|
-
design: "design-eng-review.md"
|
|
24
|
-
};
|
|
25
|
-
const REVIEW_PROMPT_FILES = Object.values(REVIEW_PROMPT_BY_STAGE);
|
|
20
|
+
${SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS}
|
|
21
|
+
${SHARED_STAGE_SUPPORT_SNIPPETS}
|
|
26
22
|
|
|
27
23
|
function ensureRuntimeDirs() {
|
|
28
24
|
try {
|
|
@@ -61,14 +57,9 @@ export default function cclawPlugin(ctx) {
|
|
|
61
57
|
async function readFlowState() {
|
|
62
58
|
try {
|
|
63
59
|
const raw = await readFile(flowStatePath, "utf8");
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
stage: typeof state.currentStage === "string" ? state.currentStage : "none",
|
|
67
|
-
completed: Array.isArray(state.completedStages) ? state.completedStages.length : 0,
|
|
68
|
-
activeRunId: typeof state.activeRunId === "string" ? state.activeRunId : "none"
|
|
69
|
-
};
|
|
60
|
+
return summarizeFlowState(JSON.parse(raw));
|
|
70
61
|
} catch {
|
|
71
|
-
return {
|
|
62
|
+
return summarizeFlowState({});
|
|
72
63
|
}
|
|
73
64
|
}
|
|
74
65
|
|
|
@@ -80,18 +71,13 @@ export default function cclawPlugin(ctx) {
|
|
|
80
71
|
}
|
|
81
72
|
}
|
|
82
73
|
|
|
83
|
-
async function
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
return text.split(/\\r?\\n/).slice(-maxLines);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function readKnowledgeDigest() {
|
|
90
|
-
return readTailLines(knowledgePath, 12);
|
|
74
|
+
async function readKnowledgeDigest(stage) {
|
|
75
|
+
const raw = await readFileText(knowledgePath);
|
|
76
|
+
return parseKnowledgeDigest(raw, stage, 6).lines;
|
|
91
77
|
}
|
|
92
78
|
|
|
93
79
|
async function readStageSupportContext(stage) {
|
|
94
|
-
if (
|
|
80
|
+
if (!isKnownStageId(stage)) return [];
|
|
95
81
|
const parts = [];
|
|
96
82
|
const contract = (await readFileText(join(runtimeDir, "templates/state-contracts", stage + ".json"))).trim();
|
|
97
83
|
if (contract.length > 0) {
|
|
@@ -100,7 +86,7 @@ export default function cclawPlugin(ctx) {
|
|
|
100
86
|
contract
|
|
101
87
|
);
|
|
102
88
|
}
|
|
103
|
-
const reviewPromptName =
|
|
89
|
+
const reviewPromptName = reviewPromptFileName(stage);
|
|
104
90
|
if (reviewPromptName) {
|
|
105
91
|
const prompt = (await readFileText(join(runtimeDir, "skills/review-prompts", reviewPromptName))).trim();
|
|
106
92
|
if (prompt.length > 0) {
|
|
@@ -119,12 +105,12 @@ export default function cclawPlugin(ctx) {
|
|
|
119
105
|
const flow = await readFlowState();
|
|
120
106
|
const parts = [
|
|
121
107
|
BOOTSTRAP_MARKER,
|
|
122
|
-
\`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: ${RUNTIME_ROOT}
|
|
108
|
+
\`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: \${activeArtifactsPathLabel("${RUNTIME_ROOT}")}\`
|
|
123
109
|
];
|
|
124
110
|
|
|
125
111
|
|
|
126
112
|
|
|
127
|
-
const knowledge = await readKnowledgeDigest();
|
|
113
|
+
const knowledge = await readKnowledgeDigest(flow.stage);
|
|
128
114
|
if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
|
|
129
115
|
|
|
130
116
|
const stageSupport = await readStageSupportContext(flow.stage);
|
|
@@ -201,7 +201,7 @@ export const REFERENCE_PATTERNS = [
|
|
|
201
201
|
"Review source-item coverage by vertical slice, not by file count alone.",
|
|
202
202
|
"A slice is review-ready only when RED, GREEN, REFACTOR, and verification evidence all line up."
|
|
203
203
|
],
|
|
204
|
-
artifactSections: ["Completeness Snapshot", "
|
|
204
|
+
artifactSections: ["Completeness Snapshot", "Coverage Check"]
|
|
205
205
|
}
|
|
206
206
|
]
|
|
207
207
|
},
|
|
@@ -348,7 +348,7 @@ export const REFERENCE_PATTERNS = [
|
|
|
348
348
|
{
|
|
349
349
|
stage: "review",
|
|
350
350
|
guidance: [
|
|
351
|
-
"Victory Detector: Layer 1, Layer 2, security sweep, structured findings, and
|
|
351
|
+
"Victory Detector: Layer 1, Layer 2, security sweep, structured findings, and acceptance/reproduction coverage evidence are complete with no unresolved criticals unless verdict is BLOCKED.",
|
|
352
352
|
"If the detector fails, iterate findings or route back to TDD; do not say LGTM."
|
|
353
353
|
],
|
|
354
354
|
artifactSections: ["Review Readiness Snapshot", "Final Verdict"]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared runtime snippets interpolated into generated hook/plugin scripts.
|
|
3
|
+
*
|
|
4
|
+
* Keep these string helpers minimal and dependency-free so both runtimes
|
|
5
|
+
* (node hooks and OpenCode plugin) stay in sync without duplicating constants.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS = "\nfunction summarizeFlowState(rawState) {\n const state =\n rawState && typeof rawState === \"object\" && !Array.isArray(rawState)\n ? rawState\n : {};\n return {\n stage: typeof state.currentStage === \"string\" ? state.currentStage : \"none\",\n completed: Array.isArray(state.completedStages) ? state.completedStages.length : 0,\n activeRunId: typeof state.activeRunId === \"string\" ? state.activeRunId : \"none\"\n };\n}\n\nfunction parseKnowledgeDigest(rawKnowledge, currentStage, maxRows = 6) {\n const text = typeof rawKnowledge === \"string\" ? rawKnowledge : \"\";\n if (text.trim().length === 0) {\n return { learningsCount: 0, lines: [] };\n }\n const rows = text\n .split(/\\r?\\n/gu)\n .map((line) => line.trim())\n .filter((line) => line.length > 0);\n let learningsCount = 0;\n const parsedRows = [];\n for (const line of rows) {\n if (line.startsWith(\"{\")) learningsCount += 1;\n try {\n const parsed = JSON.parse(line);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) continue;\n parsedRows.push(parsed);\n } catch {\n // ignore malformed knowledge line in digest\n }\n }\n const lines = parsedRows\n .filter((row) => {\n const stage = typeof row.stage === \"string\" ? row.stage : null;\n return stage === null || stage === currentStage;\n })\n .slice(-maxRows)\n .reverse()\n .map((row) => {\n const confidence = typeof row.confidence === \"string\" ? row.confidence : \"unknown\";\n const stage = typeof row.stage === \"string\" ? row.stage : \"global\";\n const trigger = typeof row.trigger === \"string\" ? row.trigger : \"trigger\";\n const action = typeof row.action === \"string\" ? row.action : \"action\";\n return \"- [\" + confidence + \" \u2022 \" + stage + \"] \" + trigger + \" -> \" + action;\n });\n return { learningsCount, lines };\n}\n\nfunction activeArtifactsPathLabel(runtimeRoot) {\n return String(runtimeRoot || \".cclaw\") + \"/artifacts/\";\n}\n";
|
|
8
|
+
export declare const SHARED_STAGE_SUPPORT_SNIPPETS = "\nconst STAGE_IDS = [\"brainstorm\", \"scope\", \"design\", \"spec\", \"plan\", \"tdd\", \"review\", \"ship\"];\nconst REVIEW_PROMPT_BY_STAGE = {\n brainstorm: \"brainstorm-self-review.md\",\n scope: \"scope-ceo-review.md\",\n design: \"design-eng-review.md\"\n};\nconst REVIEW_PROMPT_FILES = Object.values(REVIEW_PROMPT_BY_STAGE);\n\nfunction isKnownStageId(stage) {\n return typeof stage === \"string\" && STAGE_IDS.includes(stage);\n}\n\nfunction reviewPromptFileName(stage) {\n if (!isKnownStageId(stage)) return null;\n const name = REVIEW_PROMPT_BY_STAGE[stage];\n return typeof name === \"string\" ? name : null;\n}\n";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared runtime snippets interpolated into generated hook/plugin scripts.
|
|
3
|
+
*
|
|
4
|
+
* Keep these string helpers minimal and dependency-free so both runtimes
|
|
5
|
+
* (node hooks and OpenCode plugin) stay in sync without duplicating constants.
|
|
6
|
+
*/
|
|
7
|
+
export const SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS = `
|
|
8
|
+
function summarizeFlowState(rawState) {
|
|
9
|
+
const state =
|
|
10
|
+
rawState && typeof rawState === "object" && !Array.isArray(rawState)
|
|
11
|
+
? rawState
|
|
12
|
+
: {};
|
|
13
|
+
return {
|
|
14
|
+
stage: typeof state.currentStage === "string" ? state.currentStage : "none",
|
|
15
|
+
completed: Array.isArray(state.completedStages) ? state.completedStages.length : 0,
|
|
16
|
+
activeRunId: typeof state.activeRunId === "string" ? state.activeRunId : "none"
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseKnowledgeDigest(rawKnowledge, currentStage, maxRows = 6) {
|
|
21
|
+
const text = typeof rawKnowledge === "string" ? rawKnowledge : "";
|
|
22
|
+
if (text.trim().length === 0) {
|
|
23
|
+
return { learningsCount: 0, lines: [] };
|
|
24
|
+
}
|
|
25
|
+
const rows = text
|
|
26
|
+
.split(/\\r?\\n/gu)
|
|
27
|
+
.map((line) => line.trim())
|
|
28
|
+
.filter((line) => line.length > 0);
|
|
29
|
+
let learningsCount = 0;
|
|
30
|
+
const parsedRows = [];
|
|
31
|
+
for (const line of rows) {
|
|
32
|
+
if (line.startsWith("{")) learningsCount += 1;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(line);
|
|
35
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
36
|
+
parsedRows.push(parsed);
|
|
37
|
+
} catch {
|
|
38
|
+
// ignore malformed knowledge line in digest
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const lines = parsedRows
|
|
42
|
+
.filter((row) => {
|
|
43
|
+
const stage = typeof row.stage === "string" ? row.stage : null;
|
|
44
|
+
return stage === null || stage === currentStage;
|
|
45
|
+
})
|
|
46
|
+
.slice(-maxRows)
|
|
47
|
+
.reverse()
|
|
48
|
+
.map((row) => {
|
|
49
|
+
const confidence = typeof row.confidence === "string" ? row.confidence : "unknown";
|
|
50
|
+
const stage = typeof row.stage === "string" ? row.stage : "global";
|
|
51
|
+
const trigger = typeof row.trigger === "string" ? row.trigger : "trigger";
|
|
52
|
+
const action = typeof row.action === "string" ? row.action : "action";
|
|
53
|
+
return "- [" + confidence + " • " + stage + "] " + trigger + " -> " + action;
|
|
54
|
+
});
|
|
55
|
+
return { learningsCount, lines };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function activeArtifactsPathLabel(runtimeRoot) {
|
|
59
|
+
return String(runtimeRoot || ".cclaw") + "/artifacts/";
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
export const SHARED_STAGE_SUPPORT_SNIPPETS = `
|
|
63
|
+
const STAGE_IDS = ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"];
|
|
64
|
+
const REVIEW_PROMPT_BY_STAGE = {
|
|
65
|
+
brainstorm: "brainstorm-self-review.md",
|
|
66
|
+
scope: "scope-ceo-review.md",
|
|
67
|
+
design: "design-eng-review.md"
|
|
68
|
+
};
|
|
69
|
+
const REVIEW_PROMPT_FILES = Object.values(REVIEW_PROMPT_BY_STAGE);
|
|
70
|
+
|
|
71
|
+
function isKnownStageId(stage) {
|
|
72
|
+
return typeof stage === "string" && STAGE_IDS.includes(stage);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function reviewPromptFileName(stage) {
|
|
76
|
+
if (!isKnownStageId(stage)) return null;
|
|
77
|
+
const name = REVIEW_PROMPT_BY_STAGE[stage];
|
|
78
|
+
return typeof name === "string" ? name : null;
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
@@ -66,7 +66,7 @@ When resuming work after a break:
|
|
|
66
66
|
|
|
67
67
|
### Optional session-history scan for compound
|
|
68
68
|
|
|
69
|
-
During post-ship \`
|
|
69
|
+
During post-ship \`post_ship_review\`, ask before scanning external session history. If the user opts in, inspect only relevant Cursor/Claude/Codex transcripts for repeated failures or process lessons, summarize matches, and then apply the same overlap/refresh/supersede rules before touching \`.cclaw/knowledge.jsonl\`.
|
|
70
70
|
|
|
71
71
|
## Context Management
|
|
72
72
|
|
package/dist/content/skills.d.ts
CHANGED
|
@@ -10,3 +10,4 @@ export declare function noPlaceholdersBlock(): string;
|
|
|
10
10
|
export declare function watchedFailProofBlock(): string;
|
|
11
11
|
export declare function stageSkillFolder(stage: FlowStage): string;
|
|
12
12
|
export declare function stageSkillMarkdown(stage: FlowStage, track?: FlowTrack): string;
|
|
13
|
+
export declare function executingWavesSkillMarkdown(): string;
|
package/dist/content/skills.js
CHANGED
|
@@ -607,3 +607,53 @@ ${reviewSectionsBlock(reviewLens.reviewSections)}
|
|
|
607
607
|
- Keep decisions explicit: context, options, chosen option, rationale, risk, and rollback.
|
|
608
608
|
`;
|
|
609
609
|
}
|
|
610
|
+
export function executingWavesSkillMarkdown() {
|
|
611
|
+
return `---
|
|
612
|
+
name: executing-waves
|
|
613
|
+
description: "Execute multi-wave work using existing cclaw run resume + verify-current-state — no new CLI needed."
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
# Executing Waves (Persistent Multi-Wave Work)
|
|
617
|
+
|
|
618
|
+
## Overview
|
|
619
|
+
|
|
620
|
+
Long-form work (large refactors, multi-stage uplifts) often spans many waves.
|
|
621
|
+
This skill documents how the controller persists work across waves WITHOUT new
|
|
622
|
+
CLI commands, using existing \`cclaw run resume\` and \`internal verify-current-state\`.
|
|
623
|
+
|
|
624
|
+
## When to Use
|
|
625
|
+
|
|
626
|
+
- Work spans 2+ commits / waves with cohesion concerns between waves.
|
|
627
|
+
- Each wave has its own stage cycle (brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship).
|
|
628
|
+
- User wants explicit per-wave verification before the next wave starts.
|
|
629
|
+
- Risk of cross-wave drift exists.
|
|
630
|
+
|
|
631
|
+
## Anti-Pattern
|
|
632
|
+
|
|
633
|
+
- Running many waves linearly without verification between them, accumulating drift.
|
|
634
|
+
- Treating a wave as only a commit boundary without re-verifying upstream decisions.
|
|
635
|
+
|
|
636
|
+
## Process
|
|
637
|
+
|
|
638
|
+
1. **Wave Start**: author wave plan as \`.cclaw/wave-plans/<wave-n>.md\` referencing previous wave's ship artifact.
|
|
639
|
+
2. **Carry-forward Audit**: at brainstorm of the next wave, re-read previous wave ship artifact and explicitly record:
|
|
640
|
+
- Carrying forward: <locked decisions still valid>
|
|
641
|
+
- Drift detected: <decisions no longer valid + reason>
|
|
642
|
+
- Re-scope needed: <yes/no>
|
|
643
|
+
3. **Resume Path**: if a wave was interrupted mid-stage, \`cclaw run resume\` restores state. Run \`internal verify-current-state\` before continuing.
|
|
644
|
+
4. **Wave End**: at ship, architect cross-stage verification runs from dispatch matrix. If \`DRIFT_DETECTED\`, fix before ship.
|
|
645
|
+
5. **Next Wave Trigger**: launch new \`/cc <topic>\` for next wave and reference previous wave ship artifact in upstream handoff.
|
|
646
|
+
|
|
647
|
+
## Status Markers
|
|
648
|
+
|
|
649
|
+
- \`wave-status: in-progress\` — current stage incomplete.
|
|
650
|
+
- \`wave-status: blocked-by-prev\` — waiting on previous wave verification.
|
|
651
|
+
- \`wave-status: shipped\` — wave shipped, next wave can start.
|
|
652
|
+
- \`wave-status: rolled-back\` — previous wave invalidated, current wave needs rebase.
|
|
653
|
+
|
|
654
|
+
## Linter Hooks
|
|
655
|
+
|
|
656
|
+
- If multi-wave work is detected (>1 wave-plan files in \`.cclaw/wave-plans/\`), current brainstorm artifact MUST contain \`## Wave Carry-forward\` section with drift audit.
|
|
657
|
+
- If carry-forward drift is missing in multi-wave context, emit \`[P1] wave.drift_unaddressed\`.
|
|
658
|
+
`;
|
|
659
|
+
}
|