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
package/dist/install.js
CHANGED
|
@@ -5,22 +5,21 @@ import { promisify } from "node:util";
|
|
|
5
5
|
import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
|
|
6
6
|
import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
|
|
7
7
|
import { learnSkillMarkdown } from "./content/learnings.js";
|
|
8
|
-
import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
|
|
9
8
|
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
10
|
-
import {
|
|
9
|
+
import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea.js";
|
|
11
10
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
12
11
|
import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
|
|
13
12
|
import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
|
|
14
13
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
15
14
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
16
15
|
import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
|
|
17
|
-
import { stageCompleteScript, startFlowScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
16
|
+
import { stageCompleteScript, startFlowScript, cancelRunScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
18
17
|
import { nodeHookRuntimeScript } from "./content/node-hooks.js";
|
|
19
18
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
20
19
|
import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
|
|
21
20
|
import { STATE_CONTRACTS } from "./content/state-contracts.js";
|
|
22
21
|
import { REVIEW_PROMPTS } from "./content/review-prompts.js";
|
|
23
|
-
import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
|
|
22
|
+
import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
|
|
24
23
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
|
|
25
24
|
import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
|
|
26
25
|
import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
|
|
@@ -29,10 +28,11 @@ import { createInitialFlowState } from "./flow-state.js";
|
|
|
29
28
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
30
29
|
import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
|
|
31
30
|
import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
|
|
32
|
-
import { HARNESS_ADAPTERS, harnessShimFileNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
|
|
31
|
+
import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
|
|
33
32
|
import { validateHookDocument } from "./hook-schema.js";
|
|
34
33
|
import { detectHarnesses } from "./init-detect.js";
|
|
35
|
-
import {
|
|
34
|
+
import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
|
|
35
|
+
import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
|
|
36
36
|
import { FLOW_STAGES } from "./types.js";
|
|
37
37
|
const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
|
|
38
38
|
const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
|
|
@@ -49,6 +49,23 @@ async function writeInitSentinel(projectRoot, operation) {
|
|
|
49
49
|
await writeFileSafe(sentinelPath, `${JSON.stringify({ operation, startedAt: new Date().toISOString() }, null, 2)}\n`);
|
|
50
50
|
return sentinelPath;
|
|
51
51
|
}
|
|
52
|
+
async function warnStaleInitSentinel(projectRoot, operation) {
|
|
53
|
+
const sentinelPath = runtimePath(projectRoot, "state", INIT_SENTINEL_FILE);
|
|
54
|
+
if (!(await exists(sentinelPath)))
|
|
55
|
+
return;
|
|
56
|
+
let startedAt = "unknown time";
|
|
57
|
+
try {
|
|
58
|
+
const raw = await fs.readFile(sentinelPath, "utf8");
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
if (parsed && typeof parsed.startedAt === "string" && parsed.startedAt.trim().length > 0) {
|
|
61
|
+
startedAt = parsed.startedAt;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// best-effort parse of stale sentinel metadata
|
|
66
|
+
}
|
|
67
|
+
process.stderr.write(`[${operation}] Detected stale .init-in-progress sentinel from ${startedAt}; previous run may have crashed. Continuing.\n`);
|
|
68
|
+
}
|
|
52
69
|
async function removeBestEffort(targetPath, recursive = false) {
|
|
53
70
|
try {
|
|
54
71
|
await fs.rm(targetPath, { recursive, force: true });
|
|
@@ -89,6 +106,16 @@ const DEPRECATED_UTILITY_SKILL_FOLDERS = [
|
|
|
89
106
|
"flow-tree",
|
|
90
107
|
"flow-diff"
|
|
91
108
|
];
|
|
109
|
+
const DEPRECATED_STAGE_SKILL_FOLDERS = [
|
|
110
|
+
"brainstorming",
|
|
111
|
+
"scope-shaping",
|
|
112
|
+
"engineering-design-lock",
|
|
113
|
+
"specification-authoring",
|
|
114
|
+
"planning-and-task-breakdown",
|
|
115
|
+
"test-driven-development",
|
|
116
|
+
"two-layer-review",
|
|
117
|
+
"shipping-and-handoff"
|
|
118
|
+
];
|
|
92
119
|
const DEPRECATED_AGENT_FILES = [
|
|
93
120
|
"securityer.md",
|
|
94
121
|
"spec-reviewer.md",
|
|
@@ -443,6 +470,9 @@ async function writeArtifactTemplates(projectRoot) {
|
|
|
443
470
|
await writeFileSafe(runtimePath(projectRoot, "templates", "state-contracts", fileName), content);
|
|
444
471
|
}));
|
|
445
472
|
}
|
|
473
|
+
async function writeWavePlansScaffold(projectRoot) {
|
|
474
|
+
await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
|
|
475
|
+
}
|
|
446
476
|
async function writeSkills(projectRoot, config) {
|
|
447
477
|
const skillTrack = config?.defaultTrack ?? "standard";
|
|
448
478
|
for (const stage of FLOW_STAGES) {
|
|
@@ -451,8 +481,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
451
481
|
}
|
|
452
482
|
// Utility skills (not flow stages)
|
|
453
483
|
await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
|
|
454
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-
|
|
455
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
|
|
484
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-idea", "SKILL.md"), ideaCommandSkillMarkdown());
|
|
456
485
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
|
|
457
486
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
|
|
458
487
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
|
|
@@ -460,6 +489,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
460
489
|
await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
|
|
461
490
|
await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
|
|
462
491
|
await writeFileSafe(runtimePath(projectRoot, "skills", "iron-laws", "SKILL.md"), ironLawsSkillMarkdown());
|
|
492
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "executing-waves", "SKILL.md"), executingWavesSkillMarkdown());
|
|
463
493
|
await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
|
|
464
494
|
// In-thread research procedures (no YAML frontmatter, not delegated personas).
|
|
465
495
|
for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
|
|
@@ -516,8 +546,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
516
546
|
}
|
|
517
547
|
async function writeEntryCommands(projectRoot) {
|
|
518
548
|
await writeFileSafe(runtimePath(projectRoot, "commands", "start.md"), startCommandContract());
|
|
519
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "
|
|
520
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
549
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "idea.md"), ideaCommandContract());
|
|
521
550
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
522
551
|
await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
|
|
523
552
|
for (const stage of FLOW_STAGES) {
|
|
@@ -858,18 +887,47 @@ async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
|
|
|
858
887
|
if (harness) {
|
|
859
888
|
const generatedSchema = validateHookDocument(harness, generatedDoc);
|
|
860
889
|
if (!generatedSchema.ok) {
|
|
861
|
-
throw new Error(`
|
|
890
|
+
throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: generated hook document is invalid (${generatedSchema.errors.join("; ")}). ` +
|
|
891
|
+
"Run `npx cclaw-cli sync` to regenerate managed hooks or repair the generated hook shape manually.");
|
|
862
892
|
}
|
|
863
893
|
}
|
|
864
894
|
const mergedDoc = mergeHookDocuments(existingDoc, generatedDoc);
|
|
865
895
|
if (harness) {
|
|
866
896
|
const mergedSchema = validateHookDocument(harness, mergedDoc);
|
|
867
897
|
if (!mergedSchema.ok) {
|
|
868
|
-
throw new Error(`
|
|
898
|
+
throw new Error(`[sync fail-fast] Hook document drift detected for ${harness}: merged hook document is invalid (${mergedSchema.errors.join("; ")}). ` +
|
|
899
|
+
"Run `npx cclaw-cli sync` after fixing the custom hook entry or remove the malformed user-authored hook block.");
|
|
869
900
|
}
|
|
870
901
|
}
|
|
871
902
|
await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
|
|
872
903
|
}
|
|
904
|
+
async function readBundledRunHookRuntimeScript(options) {
|
|
905
|
+
const bundleUrl = new URL("./runtime/run-hook.mjs", import.meta.url);
|
|
906
|
+
try {
|
|
907
|
+
await fs.stat(bundleUrl);
|
|
908
|
+
}
|
|
909
|
+
catch {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const moduleUrl = `${bundleUrl.href}?ts=${Date.now()}`;
|
|
914
|
+
const loaded = await import(moduleUrl);
|
|
915
|
+
const factory = typeof loaded.buildRunHookRuntimeScript === "function"
|
|
916
|
+
? loaded.buildRunHookRuntimeScript
|
|
917
|
+
: typeof loaded.default === "function"
|
|
918
|
+
? loaded.default
|
|
919
|
+
: null;
|
|
920
|
+
if (!factory)
|
|
921
|
+
return null;
|
|
922
|
+
const script = factory(options);
|
|
923
|
+
if (typeof script !== "string")
|
|
924
|
+
return null;
|
|
925
|
+
return script.trim().length > 0 ? script : null;
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
873
931
|
async function writeHooks(projectRoot, config) {
|
|
874
932
|
const harnesses = config.harnesses;
|
|
875
933
|
const hooksDir = runtimePath(projectRoot, "hooks");
|
|
@@ -883,12 +941,17 @@ async function writeHooks(projectRoot, config) {
|
|
|
883
941
|
}), null, 2)}\n`);
|
|
884
942
|
await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
|
|
885
943
|
await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
|
|
886
|
-
await writeFileSafe(path.join(hooksDir, "run
|
|
944
|
+
await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
|
|
945
|
+
const hookRuntimeOptions = {
|
|
887
946
|
strictness: effectiveStrictness,
|
|
888
947
|
tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
|
|
889
948
|
tddProductionPathPatterns: config.tdd?.productionPathPatterns,
|
|
890
|
-
compoundRecurrenceThreshold: config.compound?.recurrenceThreshold
|
|
891
|
-
|
|
949
|
+
compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
|
|
950
|
+
earlyLoopEnabled: config.earlyLoop?.enabled,
|
|
951
|
+
earlyLoopMaxIterations: config.earlyLoop?.maxIterations
|
|
952
|
+
};
|
|
953
|
+
const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
|
|
954
|
+
await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
|
|
892
955
|
await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
|
|
893
956
|
await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
|
|
894
957
|
const opencodePluginSource = opencodePluginJs();
|
|
@@ -900,7 +963,8 @@ async function writeHooks(projectRoot, config) {
|
|
|
900
963
|
"run-hook.mjs",
|
|
901
964
|
"run-hook.cmd",
|
|
902
965
|
"delegation-record.mjs",
|
|
903
|
-
"opencode-plugin.mjs"
|
|
966
|
+
"opencode-plugin.mjs",
|
|
967
|
+
"cancel-run.mjs"
|
|
904
968
|
]) {
|
|
905
969
|
await fs.chmod(path.join(hooksDir, script), 0o755);
|
|
906
970
|
}
|
|
@@ -938,8 +1002,8 @@ async function writeHooks(projectRoot, config) {
|
|
|
938
1002
|
// flag in `~/.codex/config.toml`. cclaw always writes the file so
|
|
939
1003
|
// the moment the flag flips on, the cclaw hooks start firing. See
|
|
940
1004
|
// `codexHooksJsonWithObservation` for the Bash-only caveat on
|
|
941
|
-
// PreToolUse/PostToolUse.
|
|
942
|
-
//
|
|
1005
|
+
// PreToolUse/PostToolUse. If the feature flag is off, hooks remain
|
|
1006
|
+
// inert until the user enables codex_hooks in ~/.codex/config.toml.
|
|
943
1007
|
const codexDir = path.join(projectRoot, ".codex");
|
|
944
1008
|
await ensureDir(codexDir);
|
|
945
1009
|
await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
|
|
@@ -989,7 +1053,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
|
989
1053
|
for (const entry of managedHookFiles) {
|
|
990
1054
|
if (enabled.has(entry.harness))
|
|
991
1055
|
continue;
|
|
992
|
-
await removeManagedHookEntries(entry.hookPath);
|
|
1056
|
+
await removeManagedHookEntries(entry.hookPath, { failOnParseError: true });
|
|
993
1057
|
}
|
|
994
1058
|
if (!enabled.has("opencode")) {
|
|
995
1059
|
try {
|
|
@@ -998,8 +1062,22 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
|
998
1062
|
catch {
|
|
999
1063
|
// best-effort cleanup
|
|
1000
1064
|
}
|
|
1065
|
+
try {
|
|
1066
|
+
await fs.rm(path.join(projectRoot, ".opencode/agents"), { recursive: true, force: true });
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
// best-effort cleanup
|
|
1070
|
+
}
|
|
1001
1071
|
await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
|
|
1002
1072
|
}
|
|
1073
|
+
if (!enabled.has("codex")) {
|
|
1074
|
+
try {
|
|
1075
|
+
await fs.rm(path.join(projectRoot, ".codex/agents"), { recursive: true, force: true });
|
|
1076
|
+
}
|
|
1077
|
+
catch {
|
|
1078
|
+
// best-effort cleanup
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1003
1081
|
}
|
|
1004
1082
|
async function writeState(projectRoot, config, forceReset = false) {
|
|
1005
1083
|
const statePath = runtimePath(projectRoot, "state", "flow-state.json");
|
|
@@ -1013,6 +1091,9 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1013
1091
|
for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
|
|
1014
1092
|
await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
|
|
1015
1093
|
}
|
|
1094
|
+
for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
|
|
1095
|
+
await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
|
|
1096
|
+
}
|
|
1016
1097
|
for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
|
|
1017
1098
|
await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
|
|
1018
1099
|
}
|
|
@@ -1037,16 +1118,30 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1037
1118
|
for (const legacyRuntimeDir of DEPRECATED_RUNTIME_DIRS) {
|
|
1038
1119
|
await removeBestEffort(runtimePath(projectRoot, legacyRuntimeDir), true);
|
|
1039
1120
|
}
|
|
1040
|
-
//
|
|
1041
|
-
//
|
|
1121
|
+
// Archive storage migration: `.cclaw/runs` is legacy and no longer a valid
|
|
1122
|
+
// archive root. Remove only when empty; otherwise keep it so users can
|
|
1123
|
+
// manually migrate or inspect old data.
|
|
1124
|
+
const legacyRunsDir = runtimePath(projectRoot, "runs");
|
|
1125
|
+
try {
|
|
1126
|
+
const entries = await fs.readdir(legacyRunsDir);
|
|
1127
|
+
if (entries.length === 0) {
|
|
1128
|
+
await fs.rm(legacyRunsDir, { recursive: true, force: true });
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch {
|
|
1132
|
+
// missing or unreadable legacy dir; keep best-effort behavior
|
|
1133
|
+
}
|
|
1134
|
+
// D-4 terminology migration: rename historical ideation artifact prefixes to
|
|
1135
|
+
// the canonical idea-* naming without deleting user-authored content.
|
|
1136
|
+
const legacyIdeaArtifactPattern = /^ideation-(.+\.md)$/u;
|
|
1042
1137
|
const artifactsDir = runtimePath(projectRoot, "artifacts");
|
|
1043
1138
|
try {
|
|
1044
1139
|
const entries = await fs.readdir(artifactsDir);
|
|
1045
1140
|
for (const entry of entries) {
|
|
1046
|
-
const match =
|
|
1141
|
+
const match = legacyIdeaArtifactPattern.exec(entry);
|
|
1047
1142
|
if (!match)
|
|
1048
1143
|
continue;
|
|
1049
|
-
const nextName = `
|
|
1144
|
+
const nextName = `idea-${match[1]}`;
|
|
1050
1145
|
const from = path.join(artifactsDir, entry);
|
|
1051
1146
|
const to = path.join(artifactsDir, nextName);
|
|
1052
1147
|
if (await exists(to)) {
|
|
@@ -1094,7 +1189,34 @@ async function cleanStaleFiles(projectRoot) {
|
|
|
1094
1189
|
// Keep user-owned custom assets under .cclaw/agents and .cclaw/skills.
|
|
1095
1190
|
// Legacy managed removals happen in cleanLegacyArtifacts() with explicit paths.
|
|
1096
1191
|
}
|
|
1192
|
+
async function assertExpectedHarnessShims(projectRoot, harnesses) {
|
|
1193
|
+
const expectedFiles = harnessShimFileNames();
|
|
1194
|
+
const expectedSkillFolders = harnessShimSkillNames();
|
|
1195
|
+
for (const harness of harnesses) {
|
|
1196
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
1197
|
+
const base = path.join(projectRoot, adapter.commandDir);
|
|
1198
|
+
for (const fileName of expectedFiles) {
|
|
1199
|
+
const target = adapter.shimKind === "skill"
|
|
1200
|
+
? path.join(base, fileName.replace(/\.md$/u, ""), "SKILL.md")
|
|
1201
|
+
: path.join(base, fileName);
|
|
1202
|
+
if (!(await exists(target))) {
|
|
1203
|
+
throw new Error(`[sync fail-fast] Harness shim drift detected for ${harness}: missing ${target}. ` +
|
|
1204
|
+
`Run \`npx cclaw-cli sync\` again; if the file is still missing, inspect harness permissions/paths.`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (adapter.shimKind === "skill") {
|
|
1208
|
+
for (const folder of expectedSkillFolders) {
|
|
1209
|
+
const skillPath = path.join(base, folder, "SKILL.md");
|
|
1210
|
+
if (!(await exists(skillPath))) {
|
|
1211
|
+
throw new Error(`[sync fail-fast] Harness skill shim drift detected for ${harness}: missing ${skillPath}. ` +
|
|
1212
|
+
`Run \`npx cclaw-cli sync\` again; if the issue persists, inspect generated .agents/skills surfaces.`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1097
1218
|
async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
|
|
1219
|
+
await warnStaleInitSentinel(projectRoot, operation);
|
|
1098
1220
|
const sentinelPath = await writeInitSentinel(projectRoot, operation);
|
|
1099
1221
|
const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
|
|
1100
1222
|
setActiveManagedResourceSession(managedSession);
|
|
@@ -1107,28 +1229,55 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
|
|
|
1107
1229
|
writeEntryCommands(projectRoot),
|
|
1108
1230
|
writeSkills(projectRoot, config),
|
|
1109
1231
|
writeArtifactTemplates(projectRoot),
|
|
1232
|
+
writeWavePlansScaffold(projectRoot),
|
|
1110
1233
|
writeRulebook(projectRoot)
|
|
1111
1234
|
]);
|
|
1112
1235
|
await writeState(projectRoot, config, forceStateReset);
|
|
1113
|
-
|
|
1236
|
+
try {
|
|
1237
|
+
await ensureRunSystem(projectRoot, { createIfMissing: false });
|
|
1238
|
+
}
|
|
1239
|
+
catch (error) {
|
|
1240
|
+
if (error instanceof CorruptFlowStateError) {
|
|
1241
|
+
throw new Error(`[sync fail-fast] Corrupt flow state detected: ${error.message} ` +
|
|
1242
|
+
`Resolve the quarantined flow-state file and re-run \`npx cclaw-cli sync\`.`);
|
|
1243
|
+
}
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
1114
1246
|
await ensureKnowledgeStore(projectRoot);
|
|
1115
1247
|
await writeHooks(projectRoot, config);
|
|
1116
1248
|
await syncDisabledHarnessArtifacts(projectRoot, harnesses);
|
|
1117
1249
|
await syncManagedGitHooks(projectRoot, config);
|
|
1118
1250
|
await syncHarnessShims(projectRoot, harnesses);
|
|
1251
|
+
await assertExpectedHarnessShims(projectRoot, harnesses);
|
|
1119
1252
|
await writeCursorWorkflowRule(projectRoot, harnesses);
|
|
1120
1253
|
await ensureGitignore(projectRoot);
|
|
1121
1254
|
await managedSession.commit();
|
|
1122
1255
|
await fs.unlink(sentinelPath).catch(() => undefined);
|
|
1123
1256
|
}
|
|
1124
1257
|
catch (error) {
|
|
1125
|
-
// Leave the sentinel in place so
|
|
1258
|
+
// Leave the sentinel in place so the interrupted run is visible.
|
|
1126
1259
|
throw error;
|
|
1127
1260
|
}
|
|
1128
1261
|
finally {
|
|
1129
1262
|
setActiveManagedResourceSession(null);
|
|
1130
1263
|
}
|
|
1131
1264
|
}
|
|
1265
|
+
async function warnCodexHooksFeatureFlagIfDisabled(harnesses) {
|
|
1266
|
+
if (!harnesses.includes("codex"))
|
|
1267
|
+
return;
|
|
1268
|
+
const codexTomlPath = codexConfigPath();
|
|
1269
|
+
let existing;
|
|
1270
|
+
try {
|
|
1271
|
+
existing = await readCodexConfig(codexTomlPath);
|
|
1272
|
+
}
|
|
1273
|
+
catch (error) {
|
|
1274
|
+
process.stderr.write(`cclaw: could not read ${codexTomlPath} to validate codex_hooks flag: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (classifyCodexHooksFlag(existing) === "enabled")
|
|
1278
|
+
return;
|
|
1279
|
+
process.stderr.write(`cclaw: Codex hooks file written, but [features] codex_hooks is not true in ${codexTomlPath} — hooks are inert until you enable it.\n`);
|
|
1280
|
+
}
|
|
1132
1281
|
export async function initCclaw(options) {
|
|
1133
1282
|
if (options.harnesses !== undefined && options.harnesses.length === 0) {
|
|
1134
1283
|
throw new Error("Select at least one harness.");
|
|
@@ -1157,7 +1306,7 @@ export async function syncCclaw(projectRoot, options = {}) {
|
|
|
1157
1306
|
// Prefer detected harness markers over the hardcoded default list.
|
|
1158
1307
|
// Without this, a user running `cclaw sync` in a `.claude`-only
|
|
1159
1308
|
// project ends up with a config that also enables cursor/opencode/
|
|
1160
|
-
// codex, which then
|
|
1309
|
+
// codex, which then creates invalid harness expectations.
|
|
1161
1310
|
// Fall back to the previous default (config.harnesses) if no markers
|
|
1162
1311
|
// are found so brand-new projects still bootstrap cleanly.
|
|
1163
1312
|
const detected = await detectHarnesses(projectRoot);
|
|
@@ -1177,6 +1326,7 @@ export async function syncCclaw(projectRoot, options = {}) {
|
|
|
1177
1326
|
});
|
|
1178
1327
|
}
|
|
1179
1328
|
await materializeRuntime(projectRoot, config, false, "sync");
|
|
1329
|
+
await warnCodexHooksFeatureFlagIfDisabled(config.harnesses);
|
|
1180
1330
|
}
|
|
1181
1331
|
/**
|
|
1182
1332
|
* Refresh generated files in `.cclaw/` without touching user-authored
|
|
@@ -1274,20 +1424,28 @@ function isManagedRuntimeHookCommand(command) {
|
|
|
1274
1424
|
// Codex UserPromptSubmit non-blocking state nudge.
|
|
1275
1425
|
return /internal verify-current-state(?:\s|$)/u.test(normalized);
|
|
1276
1426
|
}
|
|
1277
|
-
async function removeManagedHookEntries(hookFilePath) {
|
|
1427
|
+
async function removeManagedHookEntries(hookFilePath, options = {}) {
|
|
1278
1428
|
if (!(await exists(hookFilePath)))
|
|
1279
1429
|
return;
|
|
1280
1430
|
let parsed = null;
|
|
1281
1431
|
try {
|
|
1282
1432
|
const raw = await fs.readFile(hookFilePath, "utf8");
|
|
1283
1433
|
const recovered = tryParseHookDocument(raw);
|
|
1284
|
-
|
|
1434
|
+
if (recovered === null) {
|
|
1435
|
+
if (options.failOnParseError === true) {
|
|
1436
|
+
throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — JSON is unparseable. ` +
|
|
1437
|
+
`Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
|
|
1438
|
+
}
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
parsed = recovered.parsed;
|
|
1285
1442
|
}
|
|
1286
|
-
catch {
|
|
1443
|
+
catch (error) {
|
|
1444
|
+
if (options.failOnParseError === true) {
|
|
1445
|
+
throw new Error(`[sync fail-fast] Cannot strip managed hook entries from ${hookFilePath} — ${error instanceof Error ? error.message : String(error)}. Run \`rm ${hookFilePath}\` and rerun \`npx cclaw-cli sync\`.`);
|
|
1446
|
+
}
|
|
1287
1447
|
return;
|
|
1288
1448
|
}
|
|
1289
|
-
if (parsed === null)
|
|
1290
|
-
return;
|
|
1291
1449
|
const { updated, changed } = stripManagedHookCommands(parsed);
|
|
1292
1450
|
if (!changed)
|
|
1293
1451
|
return;
|
|
@@ -1369,7 +1527,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1369
1527
|
try {
|
|
1370
1528
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
1371
1529
|
for (const entry of entries) {
|
|
1372
|
-
if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|
|
|
1530
|
+
if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|idea|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
|
|
1373
1531
|
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1374
1532
|
}
|
|
1375
1533
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type FlowState } from "../../flow-state.js";
|
|
2
|
+
import { type FlowStage } from "../../types.js";
|
|
3
|
+
import type { AdvanceStageArgs } from "./parsers.js";
|
|
4
|
+
import type { Writable } from "node:stream";
|
|
5
|
+
interface InternalIo {
|
|
6
|
+
stdout: Writable;
|
|
7
|
+
stderr: Writable;
|
|
8
|
+
}
|
|
9
|
+
interface InternalValidationReport {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
stage: FlowStage;
|
|
12
|
+
delegation: {
|
|
13
|
+
satisfied: boolean;
|
|
14
|
+
missing: string[];
|
|
15
|
+
waived: string[];
|
|
16
|
+
missingEvidence: string[];
|
|
17
|
+
missingDispatchProof: string[];
|
|
18
|
+
legacyInferredCompletions: string[];
|
|
19
|
+
corruptEventLines: number[];
|
|
20
|
+
staleWorkers: string[];
|
|
21
|
+
expectedMode: string;
|
|
22
|
+
};
|
|
23
|
+
gates: {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
complete: boolean;
|
|
26
|
+
issues: string[];
|
|
27
|
+
missingRequired: string[];
|
|
28
|
+
missingTriggeredConditional: string[];
|
|
29
|
+
};
|
|
30
|
+
completedStages: {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
issues: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
|
|
36
|
+
export declare function buildValidationReport(projectRoot: string, flowState: FlowState, options?: {
|
|
37
|
+
allowBlockedReviewRoute?: boolean;
|
|
38
|
+
}): Promise<InternalValidationReport>;
|
|
39
|
+
interface HarvestLearningsResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
markerWritten: boolean;
|
|
42
|
+
parsedEntries: number;
|
|
43
|
+
appendedEntries: number;
|
|
44
|
+
skippedDuplicates: number;
|
|
45
|
+
details: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function withLearningsHarvestMarker(artifactMarkdown: string, appendedEntries: number, skippedDuplicates: number): string;
|
|
48
|
+
export declare function harvestStageLearnings(projectRoot: string, stage: FlowStage, track: FlowState["track"]): Promise<HarvestLearningsResult>;
|
|
49
|
+
export declare function runAdvanceStage(projectRoot: string, args: AdvanceStageArgs, io: InternalIo): Promise<number>;
|
|
50
|
+
export {};
|