cclaw-cli 0.51.30 → 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 +24 -18
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +289 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +354 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +183 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +99 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +125 -0
- package/dist/artifact-linter/shared.d.ts +247 -0
- package/dist/artifact-linter/shared.js +1517 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +82 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +130 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +198 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +1 -6
- package/dist/cli.js +4 -159
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +10 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +15 -13
- package/dist/content/core-agents.d.ts +46 -29
- package/dist/content/core-agents.js +216 -82
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- 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/harness-doc.js +20 -2
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +11 -27
- package/dist/content/meta-skill.js +7 -7
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +163 -95
- package/dist/content/opencode-plugin.js +15 -29
- 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 +69 -7
- package/dist/content/stage-schema.js +147 -61
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +7 -4
- package/dist/content/stages/review.js +12 -12
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +3 -3
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.js +11 -10
- package/dist/content/status-command.js +5 -5
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +65 -81
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +187 -154
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +28 -99
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +5 -6
- package/dist/flow-state.js +4 -6
- package/dist/gate-evidence.d.ts +0 -23
- package/dist/gate-evidence.js +111 -153
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +48 -19
- package/dist/install.js +190 -32
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +479 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +161 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +5 -28
- package/dist/knowledge-store.js +57 -84
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +7 -9
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +13 -16
- package/dist/run-persistence.js +20 -15
- 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/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -83
- package/dist/content/hook-inline-snippets.js +0 -302
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-command.js +0 -315
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-frames.js +0 -140
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/ideate-ranking.js +0 -65
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2201
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { DEFAULT_COMPOUND_RECURRENCE_THRESHOLD } from "../config.js";
|
|
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];
|
|
@@ -51,6 +51,12 @@ export function nodeHookRuntimeScript(options = {}) {
|
|
|
51
51
|
options.compoundRecurrenceThreshold >= 1
|
|
52
52
|
? options.compoundRecurrenceThreshold
|
|
53
53
|
: DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
|
|
54
|
+
const earlyLoopEnabled = options.earlyLoopEnabled !== false;
|
|
55
|
+
const earlyLoopMaxIterations = typeof options.earlyLoopMaxIterations === "number" &&
|
|
56
|
+
Number.isInteger(options.earlyLoopMaxIterations) &&
|
|
57
|
+
options.earlyLoopMaxIterations >= 1
|
|
58
|
+
? options.earlyLoopMaxIterations
|
|
59
|
+
: DEFAULT_EARLY_LOOP_MAX_ITERATIONS;
|
|
54
60
|
const cliRuntime = resolveCliRuntimeForGeneratedHook();
|
|
55
61
|
return `#!/usr/bin/env node
|
|
56
62
|
import fs from "node:fs/promises";
|
|
@@ -73,9 +79,14 @@ const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS = ${JSON.stringify(tddProductionPathP
|
|
|
73
79
|
const COMPOUND_RECURRENCE_THRESHOLD = ${JSON.stringify(compoundRecurrenceThreshold)};
|
|
74
80
|
const SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD = ${JSON.stringify(SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD)};
|
|
75
81
|
const SMALL_PROJECT_RECURRENCE_THRESHOLD = ${JSON.stringify(SMALL_PROJECT_RECURRENCE_THRESHOLD)};
|
|
82
|
+
const EARLY_LOOP_ENABLED = ${JSON.stringify(earlyLoopEnabled)};
|
|
83
|
+
const EARLY_LOOP_MAX_ITERATIONS = ${JSON.stringify(earlyLoopMaxIterations)};
|
|
76
84
|
const CCLAW_CLI_ENTRYPOINT = ${JSON.stringify(cliRuntime.entrypoint)};
|
|
77
85
|
const CCLAW_CLI_ARGS_PREFIX = ${JSON.stringify(cliRuntime.argsPrefix)};
|
|
78
86
|
|
|
87
|
+
${SHARED_FLOW_AND_KNOWLEDGE_SNIPPETS}
|
|
88
|
+
${SHARED_STAGE_SUPPORT_SNIPPETS}
|
|
89
|
+
|
|
79
90
|
function resolveStrictness() {
|
|
80
91
|
return process.env.CCLAW_STRICTNESS === "strict" ? "strict" : DEFAULT_STRICTNESS;
|
|
81
92
|
}
|
|
@@ -253,7 +264,7 @@ async function readJsonFile(filePath, fallback = {}, options = {}) {
|
|
|
253
264
|
} catch (parseErr) {
|
|
254
265
|
// Emit a diagnostic breadcrumb instead of silently returning fallback.
|
|
255
266
|
// The hook must still continue (soft-fail), but the corruption is
|
|
256
|
-
// now visible in \`state/hook-errors.jsonl\` and to \`cclaw
|
|
267
|
+
// now visible in \`state/hook-errors.jsonl\` and to \`npx cclaw-cli sync\`.
|
|
257
268
|
if (options.root) {
|
|
258
269
|
await recordHookError(
|
|
259
270
|
options.root,
|
|
@@ -419,6 +430,73 @@ async function runCclawInternal(root, args, options = {}) {
|
|
|
419
430
|
});
|
|
420
431
|
}
|
|
421
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
|
+
|
|
422
500
|
function detectHarness(env) {
|
|
423
501
|
if (env.CLAUDE_PROJECT_DIR) return "claude";
|
|
424
502
|
if (env.CURSOR_PROJECT_DIR || env.CURSOR_PROJECT_ROOT) return "cursor";
|
|
@@ -684,7 +762,6 @@ function detectTargetStage(payloadLower) {
|
|
|
684
762
|
}
|
|
685
763
|
|
|
686
764
|
function isFlowProgressionCommand(payloadLower) {
|
|
687
|
-
if (/(\\/cc-next|cc-next)([^a-z0-9_-]|$)/u.test(payloadLower)) return true;
|
|
688
765
|
return /\\/cc([^a-z0-9_-]|$)/u.test(payloadLower);
|
|
689
766
|
}
|
|
690
767
|
|
|
@@ -832,12 +909,12 @@ async function readFlowState(root) {
|
|
|
832
909
|
// empty object. Silent fallbacks used to mask stale CLI+hook drift.
|
|
833
910
|
const parsed = await readJsonFile(statePath, {}, { root, stage: "read-flow-state" });
|
|
834
911
|
const obj = toObject(parsed) || {};
|
|
835
|
-
const
|
|
912
|
+
const summary = summarizeFlowState(obj);
|
|
836
913
|
return {
|
|
837
914
|
filePath: statePath,
|
|
838
|
-
currentStage:
|
|
839
|
-
activeRunId:
|
|
840
|
-
completedCount: completed
|
|
915
|
+
currentStage: summary.stage,
|
|
916
|
+
activeRunId: summary.activeRunId === "none" ? "active" : summary.activeRunId,
|
|
917
|
+
completedCount: summary.completed,
|
|
841
918
|
raw: obj
|
|
842
919
|
};
|
|
843
920
|
}
|
|
@@ -850,44 +927,16 @@ async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
|
|
|
850
927
|
const raw = typeof prereadRaw === "string"
|
|
851
928
|
? prereadRaw
|
|
852
929
|
: await readTextFile(knowledgeFile, "");
|
|
853
|
-
const
|
|
854
|
-
let learningsCount = 0;
|
|
855
|
-
const parsedRows = [];
|
|
856
|
-
for (const line of lines) {
|
|
857
|
-
if (line.startsWith("{")) learningsCount += 1;
|
|
858
|
-
try {
|
|
859
|
-
const parsed = JSON.parse(line);
|
|
860
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
861
|
-
parsedRows.push(parsed);
|
|
862
|
-
} catch {
|
|
863
|
-
// ignore malformed knowledge line in digest
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
const relevant = parsedRows
|
|
867
|
-
.filter((row) => {
|
|
868
|
-
const stage = typeof row.stage === "string" ? row.stage : null;
|
|
869
|
-
return stage === null || stage === currentStage;
|
|
870
|
-
})
|
|
871
|
-
.slice(-6)
|
|
872
|
-
.reverse()
|
|
873
|
-
.map((row) => {
|
|
874
|
-
const confidence = typeof row.confidence === "string" ? row.confidence : "unknown";
|
|
875
|
-
const stage = typeof row.stage === "string" ? row.stage : "global";
|
|
876
|
-
const domain = typeof row.domain === "string" ? row.domain : "general";
|
|
877
|
-
const trigger = typeof row.trigger === "string" ? row.trigger : "trigger";
|
|
878
|
-
const action = typeof row.action === "string" ? row.action : "action";
|
|
879
|
-
return "- [" + confidence + " • " + stage + " • " + domain + "] " + trigger + " -> " + action;
|
|
880
|
-
});
|
|
930
|
+
const digest = parseKnowledgeDigest(raw, currentStage, 6);
|
|
881
931
|
return {
|
|
882
|
-
digestLines:
|
|
883
|
-
learningsCount
|
|
932
|
+
digestLines: digest.lines,
|
|
933
|
+
learningsCount: digest.learningsCount
|
|
884
934
|
};
|
|
885
935
|
}
|
|
886
936
|
|
|
887
937
|
async function readStageSupportContext(root, currentStage) {
|
|
888
|
-
|
|
889
|
-
const
|
|
890
|
-
if (!validStages.has(stage)) return [];
|
|
938
|
+
if (!isKnownStageId(currentStage)) return [];
|
|
939
|
+
const stage = currentStage;
|
|
891
940
|
|
|
892
941
|
const parts = [];
|
|
893
942
|
const contractPath = path.join(root, RUNTIME_ROOT, "templates", "state-contracts", stage + ".json");
|
|
@@ -899,12 +948,7 @@ async function readStageSupportContext(root, currentStage) {
|
|
|
899
948
|
);
|
|
900
949
|
}
|
|
901
950
|
|
|
902
|
-
const
|
|
903
|
-
brainstorm: "brainstorm-self-review.md",
|
|
904
|
-
scope: "scope-ceo-review.md",
|
|
905
|
-
design: "design-eng-review.md"
|
|
906
|
-
};
|
|
907
|
-
const promptName = reviewPromptByStage[stage];
|
|
951
|
+
const promptName = reviewPromptFileName(stage);
|
|
908
952
|
if (typeof promptName === "string") {
|
|
909
953
|
const promptPath = path.join(root, RUNTIME_ROOT, "skills", "review-prompts", promptName);
|
|
910
954
|
const prompt = (await readTextFile(promptPath, "")).trim();
|
|
@@ -942,21 +986,26 @@ async function handleSessionStart(runtime) {
|
|
|
942
986
|
// both read a consistent "iter=N, acClosed=[...]" snapshot. Runs only when
|
|
943
987
|
// we are in tdd — other stages skip the write to keep the file stable.
|
|
944
988
|
let ralphLoopLine = "";
|
|
989
|
+
let earlyLoopLine = "";
|
|
945
990
|
if (state.currentStage === "tdd") {
|
|
946
991
|
try {
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
",
|
|
954
|
-
|
|
955
|
-
|
|
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);
|
|
956
1005
|
} catch (err) {
|
|
957
1006
|
// Best-effort — a malformed cycle log should never break
|
|
958
1007
|
// session-start. But we DO leave a breadcrumb in
|
|
959
|
-
// hook-errors.jsonl so \`cclaw
|
|
1008
|
+
// hook-errors.jsonl so \`npx cclaw-cli sync\` can surface chronic
|
|
960
1009
|
// failures (previously this was a silent swallow).
|
|
961
1010
|
await recordHookError(
|
|
962
1011
|
runtime.root,
|
|
@@ -965,44 +1014,63 @@ async function handleSessionStart(runtime) {
|
|
|
965
1014
|
);
|
|
966
1015
|
}
|
|
967
1016
|
}
|
|
1017
|
+
if (
|
|
1018
|
+
EARLY_LOOP_ENABLED &&
|
|
1019
|
+
(state.currentStage === "brainstorm" || state.currentStage === "scope" || state.currentStage === "design")
|
|
1020
|
+
) {
|
|
1021
|
+
try {
|
|
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 }
|
|
1034
|
+
);
|
|
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);
|
|
1043
|
+
} catch (err) {
|
|
1044
|
+
await recordHookError(
|
|
1045
|
+
runtime.root,
|
|
1046
|
+
"session-start:early-loop",
|
|
1047
|
+
err instanceof Error ? err.message : String(err)
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
968
1051
|
|
|
969
1052
|
// Keep compound-readiness.json fresh on every session-start (cheap derived
|
|
970
1053
|
// summary). Surface a one-line nudge only from review and ship stages
|
|
971
1054
|
// where lifting becomes relevant; earlier stages update the file silently.
|
|
972
1055
|
let compoundReadinessLine = "";
|
|
973
1056
|
try {
|
|
974
|
-
|
|
1057
|
+
const shouldShowReadiness = state.currentStage === "review" || state.currentStage === "ship";
|
|
975
1058
|
const internalReadiness = await runCclawInternal(
|
|
976
1059
|
runtime.root,
|
|
977
|
-
["compound-readiness", "--
|
|
1060
|
+
shouldShowReadiness ? ["compound-readiness"] : ["compound-readiness", "--quiet"],
|
|
978
1061
|
{ captureStdout: true }
|
|
979
1062
|
);
|
|
980
|
-
if (internalReadiness.code
|
|
981
|
-
|
|
982
|
-
const parsed = JSON.parse(internalReadiness.stdout);
|
|
983
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
984
|
-
readiness = parsed;
|
|
985
|
-
}
|
|
986
|
-
} catch {
|
|
987
|
-
readiness = null;
|
|
988
|
-
}
|
|
1063
|
+
if (internalReadiness.code !== 0) {
|
|
1064
|
+
throw new Error(summarizeInternalFailure("compound-readiness", internalReadiness));
|
|
989
1065
|
}
|
|
990
|
-
if (
|
|
991
|
-
|
|
992
|
-
readiness = await computeCompoundReadinessInline(runtime.root, {
|
|
993
|
-
prereadRaw: knowledgeRaw,
|
|
994
|
-
...(typeof archivedRunsCount === "number" ? { archivedRunsCount } : {})
|
|
995
|
-
});
|
|
996
|
-
await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
|
|
997
|
-
}
|
|
998
|
-
if (state.currentStage === "review" || state.currentStage === "ship") {
|
|
999
|
-
compoundReadinessLine = formatCompoundReadinessLineInline(toObject(readiness) || {});
|
|
1066
|
+
if (shouldShowReadiness) {
|
|
1067
|
+
compoundReadinessLine = firstStdoutLine(internalReadiness.stdout);
|
|
1000
1068
|
}
|
|
1001
1069
|
} catch (err) {
|
|
1002
1070
|
// Best-effort — a malformed knowledge.jsonl must never break
|
|
1003
1071
|
// session-start. But we DO leave a breadcrumb in
|
|
1004
1072
|
// hook-errors.jsonl so config/IO problems become visible in
|
|
1005
|
-
// \`cclaw
|
|
1073
|
+
// \`npx cclaw-cli sync\` instead of silently degrading readiness output.
|
|
1006
1074
|
await recordHookError(
|
|
1007
1075
|
runtime.root,
|
|
1008
1076
|
"session-start:compound-readiness",
|
|
@@ -1034,14 +1102,17 @@ async function handleSessionStart(runtime) {
|
|
|
1034
1102
|
"/8 completed, run=" +
|
|
1035
1103
|
state.activeRunId +
|
|
1036
1104
|
"). Active artifacts: " +
|
|
1037
|
-
RUNTIME_ROOT +
|
|
1038
|
-
"
|
|
1105
|
+
activeArtifactsPathLabel(RUNTIME_ROOT) +
|
|
1106
|
+
" Learnings: " +
|
|
1039
1107
|
String(knowledge.learningsCount) +
|
|
1040
1108
|
" entries."
|
|
1041
1109
|
];
|
|
1042
1110
|
if (ralphLoopLine.length > 0) {
|
|
1043
1111
|
parts.push(ralphLoopLine);
|
|
1044
1112
|
}
|
|
1113
|
+
if (earlyLoopLine.length > 0) {
|
|
1114
|
+
parts.push(earlyLoopLine);
|
|
1115
|
+
}
|
|
1045
1116
|
if (compoundReadinessLine.length > 0) {
|
|
1046
1117
|
parts.push(compoundReadinessLine);
|
|
1047
1118
|
}
|
|
@@ -1049,7 +1120,7 @@ async function handleSessionStart(runtime) {
|
|
|
1049
1120
|
parts.push(
|
|
1050
1121
|
"Stale stages pending acknowledgement: " +
|
|
1051
1122
|
staleStageNames.join(", ") +
|
|
1052
|
-
" (use cclaw internal rewind --ack <stage> after redo)."
|
|
1123
|
+
" (use npx cclaw-cli internal rewind --ack <stage> after redo)."
|
|
1053
1124
|
);
|
|
1054
1125
|
}
|
|
1055
1126
|
if (knowledge.digestLines.length > 0) {
|
|
@@ -1137,7 +1208,7 @@ async function handleStopHandoff(runtime) {
|
|
|
1137
1208
|
const shipSubstate = typeof closeoutObj.shipSubstate === "string" ? closeoutObj.shipSubstate : "idle";
|
|
1138
1209
|
const closeoutContext =
|
|
1139
1210
|
state.currentStage === "ship" || shipSubstate !== "idle"
|
|
1140
|
-
? " closeout.shipSubstate=" + shipSubstate + "; closeout chain=
|
|
1211
|
+
? " closeout.shipSubstate=" + shipSubstate + "; closeout chain=post_ship_review -> archive; continue closeout with /cc."
|
|
1141
1212
|
: "";
|
|
1142
1213
|
|
|
1143
1214
|
const message =
|
|
@@ -1214,15 +1285,6 @@ async function handlePromptGuard(runtime) {
|
|
|
1214
1285
|
return 0;
|
|
1215
1286
|
}
|
|
1216
1287
|
|
|
1217
|
-
// Inline mirrors of canonical CLI computations (compound-readiness,
|
|
1218
|
-
// ralph-loop) are factored into src/content/hook-inline-snippets.ts so
|
|
1219
|
-
// this 2000+-line file no longer owns their bodies. Each snippet carries
|
|
1220
|
-
// an explicit "mirrors X, parity enforced by Y" comment in the snippets
|
|
1221
|
-
// module. Parity is enforced by tests/unit/ralph-loop-parity.test.ts.
|
|
1222
|
-
${HOOK_INLINE_SHARED_HELPERS}
|
|
1223
|
-
${COMPOUND_READINESS_INLINE_SOURCE}
|
|
1224
|
-
${RALPH_LOOP_INLINE_SOURCE}
|
|
1225
|
-
|
|
1226
1288
|
async function hasFailingRedEvidenceForPath(stateDir, runId, rawPath) {
|
|
1227
1289
|
const cycleRaw = await readTextFile(path.join(stateDir, "tdd-cycle-log.jsonl"), "");
|
|
1228
1290
|
for (const line of cycleRaw.split(/\\r?\\n/gu)) {
|
|
@@ -1401,7 +1463,7 @@ async function handleWorkflowGuard(runtime) {
|
|
|
1401
1463
|
/^(read|readfile|open|view|cat|shell|runcommand|run_command|execcommand|exec_command|terminal)$/u.test(
|
|
1402
1464
|
toolLower
|
|
1403
1465
|
) &&
|
|
1404
|
-
/(\\.cclaw\\/state\\/flow-state\\.json|cclaw
|
|
1466
|
+
/(\\.cclaw\\/state\\/flow-state\\.json|npx cclaw-cli sync|npx cclaw-cli sync|npx cclaw-cli sync|cclaw sync)/u.test(payloadLower);
|
|
1405
1467
|
if (shouldRecordFlowRead) {
|
|
1406
1468
|
await writeJsonFile(guardStateFile, {
|
|
1407
1469
|
...guardState,
|
|
@@ -1434,8 +1496,14 @@ async function handleWorkflowGuard(runtime) {
|
|
|
1434
1496
|
// writes that actually belonged to a new, not-yet-red S-2. Now
|
|
1435
1497
|
// we reuse the canonical Ralph Loop status: if NO slice has an
|
|
1436
1498
|
// open RED, we block.
|
|
1437
|
-
const
|
|
1438
|
-
|
|
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) {
|
|
1439
1507
|
reasons.push("tdd_write_without_open_red");
|
|
1440
1508
|
}
|
|
1441
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,19 +105,19 @@ 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);
|
|
131
117
|
if (stageSupport.length > 0) parts.push(...stageSupport);
|
|
132
118
|
|
|
133
119
|
parts.push(
|
|
134
|
-
"If you discover a non-obvious rule or pattern during stage work, add it to the current artifact ## Learnings section; stage-complete harvests it into .cclaw/knowledge.jsonl. If this plugin does not load, run \`cclaw sync\`, verify opencode.json(.c) includes the cclaw plugin registration, then run \`cclaw
|
|
120
|
+
"If you discover a non-obvious rule or pattern during stage work, add it to the current artifact ## Learnings section; stage-complete harvests it into .cclaw/knowledge.jsonl. If this plugin does not load, run \`npx cclaw-cli sync\`, verify opencode.json(.c) includes the cclaw plugin registration, then run \`npx cclaw-cli sync\`. Direct JSONL append is only for explicit manual learnings operations."
|
|
135
121
|
);
|
|
136
122
|
|
|
137
123
|
const meta = (await readFileText(metaSkillPath)).trim();
|
|
@@ -510,7 +496,7 @@ export default function cclawPlugin(ctx) {
|
|
|
510
496
|
notInitializedAdvised = true;
|
|
511
497
|
logToFile(
|
|
512
498
|
"guards skipped: cclaw is not initialized in this project. " +
|
|
513
|
-
"Run \`cclaw init\` in " + root + " to activate flow enforcement."
|
|
499
|
+
"Run \`npx cclaw-cli init\` in " + root + " to activate flow enforcement."
|
|
514
500
|
);
|
|
515
501
|
}
|
|
516
502
|
|
|
@@ -697,7 +683,7 @@ export default function cclawPlugin(ctx) {
|
|
|
697
683
|
throw new Error(
|
|
698
684
|
"cclaw " + failed + " blocked tool.execute.before.\\n" +
|
|
699
685
|
"Reason: " + detail + "\\n" +
|
|
700
|
-
"Diagnose: run \`npx cclaw-cli
|
|
686
|
+
"Diagnose: run \`npx cclaw-cli sync\` in project root.\\n" +
|
|
701
687
|
"Bypass (temporary): export CCLAW_DISABLE=1 before starting OpenCode,\\n" +
|
|
702
688
|
"or set \`strictness: advisory\` in .cclaw/config.yaml."
|
|
703
689
|
);
|
|
@@ -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;
|