cclaw-cli 6.14.3 → 7.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 +0 -2
- package/dist/artifact-linter/brainstorm.js +1 -1
- package/dist/artifact-linter/design.js +2 -2
- package/dist/artifact-linter/findings-dedup.js +1 -1
- package/dist/artifact-linter/plan.js +6 -6
- package/dist/artifact-linter/review-army.d.ts +1 -1
- package/dist/artifact-linter/review-army.js +1 -1
- package/dist/artifact-linter/scope.js +6 -6
- package/dist/artifact-linter/shared.d.ts +37 -73
- package/dist/artifact-linter/shared.js +30 -37
- package/dist/artifact-linter/spec.js +1 -1
- package/dist/artifact-linter/tdd.d.ts +20 -33
- package/dist/artifact-linter/tdd.js +89 -617
- package/dist/artifact-linter.js +11 -32
- package/dist/cli.js +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/content/core-agents.d.ts +8 -26
- package/dist/content/core-agents.js +48 -94
- package/dist/content/examples.d.ts +1 -1
- package/dist/content/examples.js +4 -4
- package/dist/content/hooks.js +62 -149
- package/dist/content/idea.js +2 -2
- package/dist/content/iron-laws.js +1 -1
- package/dist/content/node-hooks.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.d.ts +4 -6
- package/dist/content/skills.js +14 -53
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +8 -46
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/review.js +1 -1
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/spec.js +2 -2
- package/dist/content/stages/tdd.js +43 -108
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +5 -3
- package/dist/content/subagents.js +13 -74
- package/dist/content/templates.d.ts +6 -6
- package/dist/content/templates.js +23 -24
- package/dist/content/utility-skills.d.ts +1 -1
- package/dist/content/utility-skills.js +1 -1
- package/dist/delegation.d.ts +79 -139
- package/dist/delegation.js +83 -215
- package/dist/early-loop.js +1 -1
- package/dist/flow-state.d.ts +24 -129
- package/dist/flow-state.js +5 -30
- package/dist/gate-evidence.d.ts +2 -7
- package/dist/gate-evidence.js +2 -59
- package/dist/harness-adapters.d.ts +1 -1
- package/dist/harness-adapters.js +11 -10
- package/dist/install.js +24 -459
- package/dist/internal/advance-stage/advance.d.ts +5 -5
- package/dist/internal/advance-stage/advance.js +9 -24
- package/dist/internal/advance-stage/parsers.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.js +3 -3
- package/dist/internal/advance-stage/start-flow.js +1 -3
- package/dist/internal/advance-stage.js +4 -23
- package/dist/internal/cohesion-contract-stub.d.ts +8 -13
- package/dist/internal/cohesion-contract-stub.js +18 -24
- package/dist/internal/flow-state-repair.d.ts +1 -1
- package/dist/internal/plan-split-waves.d.ts +44 -7
- package/dist/internal/plan-split-waves.js +113 -12
- package/dist/internal/wave-status.d.ts +3 -6
- package/dist/internal/wave-status.js +5 -27
- package/dist/policy.js +1 -1
- package/dist/run-persistence.js +10 -44
- package/dist/runtime/run-hook.mjs +3 -3
- package/dist/track-heuristics.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/integration-fanin.d.ts +0 -44
- package/dist/integration-fanin.js +0 -180
- package/dist/internal/set-checkpoint-mode.d.ts +0 -16
- package/dist/internal/set-checkpoint-mode.js +0 -72
- package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
- package/dist/internal/set-integration-overseer-mode.js +0 -69
- package/dist/internal/set-worktree-mode.d.ts +0 -10
- package/dist/internal/set-worktree-mode.js +0 -28
- package/dist/worktree-manager.d.ts +0 -50
- package/dist/worktree-manager.js +0 -136
- package/dist/worktree-types.d.ts +0 -36
- package/dist/worktree-types.js +0 -6
package/dist/install.js
CHANGED
|
@@ -25,16 +25,16 @@ import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./con
|
|
|
25
25
|
import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
|
|
26
26
|
import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
|
|
27
27
|
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
28
|
-
import { createInitialFlowState
|
|
28
|
+
import { createInitialFlowState } from "./flow-state.js";
|
|
29
29
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
30
|
-
import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
|
|
30
|
+
import { ManagedResourceSession, readManagedResourceManifest, setActiveManagedResourceSession } from "./managed-resources.js";
|
|
31
31
|
import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
|
|
32
32
|
import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
|
|
33
33
|
import { validateHookDocument } from "./hook-schema.js";
|
|
34
34
|
import { detectHarnesses } from "./init-detect.js";
|
|
35
35
|
import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
|
|
36
|
-
import { CorruptFlowStateError, ensureRunSystem
|
|
37
|
-
import {
|
|
36
|
+
import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
|
|
37
|
+
import { formatNextParallelWaveSyncHint, mergeParallelWaveDefinitions, parseParallelExecutionPlanWaves, parseWavePlanDirectory } from "./internal/plan-split-waves.js";
|
|
38
38
|
import { FLOW_STAGES } from "./types.js";
|
|
39
39
|
const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
|
|
40
40
|
const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
|
|
@@ -165,16 +165,12 @@ const DEPRECATED_STATE_FILES = [
|
|
|
165
165
|
"context-mode.json",
|
|
166
166
|
"session-digest.md",
|
|
167
167
|
"context-warnings.jsonl",
|
|
168
|
-
// Runtime Honesty 6.9.0 removed the per-run TDD cycle JSONL: gate evidence
|
|
169
|
-
// now reads cycle phase progression directly from the artifact table.
|
|
170
168
|
"tdd-cycle-log.jsonl"
|
|
171
169
|
];
|
|
172
|
-
//
|
|
173
|
-
//
|
|
170
|
+
// Files under `<runtime>/artifacts/` that older releases generated and
|
|
171
|
+
// the current release removes. `cclaw-cli sync` deletes each so existing
|
|
174
172
|
// installs lose the obsolete sidecar without requiring manual cleanup.
|
|
175
173
|
const DEPRECATED_ARTIFACT_FILES = [
|
|
176
|
-
// v6.10.0 sidecar — replaced in v6.11.0 by phase events in
|
|
177
|
-
// delegation-events.jsonl + auto-rendered tables in 06-tdd.md.
|
|
178
174
|
"06-tdd-slices.jsonl"
|
|
179
175
|
];
|
|
180
176
|
const DEPRECATED_HOOK_FILES = [
|
|
@@ -207,10 +203,10 @@ async function resolveGitHooksDir(projectRoot) {
|
|
|
207
203
|
return null;
|
|
208
204
|
}
|
|
209
205
|
}
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
206
|
+
// Older versions installed Node-based git pre-commit/pre-push relays under
|
|
207
|
+
// `.git/hooks/*` and a runtime tree at `.cclaw/hooks/git/`. cclaw no longer
|
|
208
|
+
// manages git hooks; the cleanup below stays so existing installs shed the
|
|
209
|
+
// leftover files on next sync/uninstall.
|
|
214
210
|
const LEGACY_GIT_HOOK_MANAGED_MARKER = "cclaw-managed-git-hook";
|
|
215
211
|
const LEGACY_GIT_HOOK_RUNTIME_REL_DIR = `${RUNTIME_ROOT}/hooks/git`;
|
|
216
212
|
async function cleanupLegacyManagedGitHookRelays(projectRoot) {
|
|
@@ -257,10 +253,12 @@ async function writeWavePlansScaffold(projectRoot) {
|
|
|
257
253
|
}
|
|
258
254
|
async function writeSkills(projectRoot, config) {
|
|
259
255
|
void config;
|
|
256
|
+
const manifest = await readManagedResourceManifest(projectRoot).catch(() => null);
|
|
257
|
+
const packageVersion = manifest?.packageVersion ?? null;
|
|
260
258
|
const skillTrack = "standard";
|
|
261
259
|
for (const stage of FLOW_STAGES) {
|
|
262
260
|
const folder = stageSkillFolder(stage);
|
|
263
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
|
|
261
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack, packageVersion));
|
|
264
262
|
}
|
|
265
263
|
// Utility skills (not flow stages)
|
|
266
264
|
await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
|
|
@@ -285,7 +283,6 @@ async function writeSkills(projectRoot, config) {
|
|
|
285
283
|
for (const [folderName, markdown] of Object.entries(SUBAGENT_CONTEXT_SKILLS)) {
|
|
286
284
|
await writeFileSafe(runtimePath(projectRoot, "skills", folderName, "SKILL.md"), markdown);
|
|
287
285
|
}
|
|
288
|
-
// Wave 21: language packs are no longer materialized from config.
|
|
289
286
|
await fs.rm(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR), { recursive: true, force: true });
|
|
290
287
|
for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
|
|
291
288
|
const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
|
|
@@ -735,13 +732,12 @@ async function writeHooks(projectRoot, config) {
|
|
|
735
732
|
await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
|
|
736
733
|
}
|
|
737
734
|
else if (harness === "codex") {
|
|
738
|
-
// Codex CLI
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
//
|
|
744
|
-
// inert until the user enables codex_hooks in ~/.codex/config.toml.
|
|
735
|
+
// Codex CLI lifecycle hooks live at `.codex/hooks.json`, gated by
|
|
736
|
+
// `[features] codex_hooks = true` in `~/.codex/config.toml`. cclaw
|
|
737
|
+
// always writes the file so the moment the flag flips on, cclaw
|
|
738
|
+
// hooks start firing. PreToolUse/PostToolUse remain Bash-only on
|
|
739
|
+
// Codex; if the feature flag is off, hooks stay inert until the
|
|
740
|
+
// user enables `codex_hooks` in `~/.codex/config.toml`.
|
|
745
741
|
const codexDir = path.join(projectRoot, ".codex");
|
|
746
742
|
await ensureDir(codexDir);
|
|
747
743
|
await writeMergedHookJson(projectRoot, path.join(codexDir, "hooks.json"), codexHooksJson());
|
|
@@ -784,10 +780,6 @@ async function writeCursorWorkflowRule(projectRoot, harnesses) {
|
|
|
784
780
|
}
|
|
785
781
|
async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
786
782
|
const enabled = new Set(harnesses);
|
|
787
|
-
// v0.40.0: `.codex/hooks.json` is back on the managed list now that
|
|
788
|
-
// Codex CLI actually consumes it (v0.114+, Mar 2026). Legacy
|
|
789
|
-
// `.codex/commands/` cleanup still happens unconditionally from
|
|
790
|
-
// `cleanupLegacyCodexSurfaces` inside `syncHarnessShims`.
|
|
791
783
|
const managedHookFiles = [
|
|
792
784
|
{ harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
|
|
793
785
|
{ harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") },
|
|
@@ -836,414 +828,6 @@ async function writeState(projectRoot, config, forceReset = false) {
|
|
|
836
828
|
const state = createInitialFlowState({ track: "standard" });
|
|
837
829
|
await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
838
830
|
}
|
|
839
|
-
/**
|
|
840
|
-
* v6.12.0 — TDD auto-cutover sync. When sync detects a legacy `06-tdd.md`
|
|
841
|
-
* (no auto-render markers) carrying observable slice activity (e.g. `S-N`
|
|
842
|
-
* referenced ≥3 times in slice-section bodies), insert the v6.11.0 marker
|
|
843
|
-
* skeleton + a one-line cutover banner and stamp the highest legacy slice
|
|
844
|
-
* id into `flow-state.json::tddCutoverSliceId`. Idempotent: re-running sync
|
|
845
|
-
* is byte-stable once markers are present.
|
|
846
|
-
*
|
|
847
|
-
* Design notes:
|
|
848
|
-
* - Best-effort: read failures, missing flow-state, or unparseable JSON
|
|
849
|
-
* all short-circuit silently. We never throw inside sync for migration
|
|
850
|
-
* bookkeeping.
|
|
851
|
-
* - We use `writeFlowState({ allowReset: true })` so we don't trip
|
|
852
|
-
* `validateFlowTransition` (the only field we mutate is the new
|
|
853
|
-
* additive `tddCutoverSliceId`; transition rules don't apply).
|
|
854
|
-
* - The banner mirrors the language in the `## Per-Slice Ritual`
|
|
855
|
-
* skill section so a reader of `06-tdd.md` who hasn't seen v6.12.0
|
|
856
|
-
* understands the contract change.
|
|
857
|
-
*/
|
|
858
|
-
async function applyTddCutoverIfNeeded(projectRoot) {
|
|
859
|
-
const tddArtifactPath = runtimePath(projectRoot, "artifacts", "06-tdd.md");
|
|
860
|
-
let existing;
|
|
861
|
-
try {
|
|
862
|
-
existing = await fs.readFile(tddArtifactPath, "utf8");
|
|
863
|
-
}
|
|
864
|
-
catch {
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
if (existing.includes("<!-- auto-start: tdd-slice-summary -->")) {
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
const sliceMatches = [...existing.matchAll(/\bS-(\d+)\b/gu)];
|
|
871
|
-
if (sliceMatches.length < 3) {
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
let maxSliceNum = 0;
|
|
875
|
-
for (const match of sliceMatches) {
|
|
876
|
-
const n = Number.parseInt(match[1], 10);
|
|
877
|
-
if (Number.isFinite(n) && n > maxSliceNum) {
|
|
878
|
-
maxSliceNum = n;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (maxSliceNum <= 0) {
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
const cutoverSliceId = `S-${maxSliceNum}`;
|
|
885
|
-
const banner = [
|
|
886
|
-
`<!-- v6.12.0 cutover: slices S-1..${cutoverSliceId} use legacy per-slice tables.`,
|
|
887
|
-
` New slices (S-${maxSliceNum + 1}+) use phase events + tdd-slices/<id>.md.`,
|
|
888
|
-
" Controller MUST NOT add new rows to legacy sections. -->"
|
|
889
|
-
].join("\n");
|
|
890
|
-
const slicesIndexBlock = [
|
|
891
|
-
"<!-- auto-start: slices-index -->",
|
|
892
|
-
"## Slices Index",
|
|
893
|
-
"",
|
|
894
|
-
"_Auto-rendered from `tdd-slices/S-*.md` once slice-documenter or controller writes per-slice files. Do not edit by hand._",
|
|
895
|
-
"<!-- auto-end: slices-index -->"
|
|
896
|
-
].join("\n");
|
|
897
|
-
const summaryBlock = [
|
|
898
|
-
"<!-- auto-start: tdd-slice-summary -->",
|
|
899
|
-
"<!-- auto-end: tdd-slice-summary -->"
|
|
900
|
-
].join("\n");
|
|
901
|
-
let nextRaw;
|
|
902
|
-
if (existing.startsWith("---\n")) {
|
|
903
|
-
const fmEnd = existing.indexOf("\n---", 4);
|
|
904
|
-
if (fmEnd >= 0) {
|
|
905
|
-
const fmClose = fmEnd + 4;
|
|
906
|
-
const head = existing.slice(0, fmClose);
|
|
907
|
-
const tail = existing.slice(fmClose);
|
|
908
|
-
nextRaw = `${head}\n\n${banner}\n\n${slicesIndexBlock}\n\n${summaryBlock}\n${tail}`;
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
nextRaw = `${banner}\n\n${slicesIndexBlock}\n\n${summaryBlock}\n\n${existing}`;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
nextRaw = `${banner}\n\n${slicesIndexBlock}\n\n${summaryBlock}\n\n${existing}`;
|
|
916
|
-
}
|
|
917
|
-
await writeFileSafe(tddArtifactPath, nextRaw);
|
|
918
|
-
const slicesDir = runtimePath(projectRoot, "artifacts", "tdd-slices");
|
|
919
|
-
await ensureDir(slicesDir);
|
|
920
|
-
const flowStatePath = runtimePath(projectRoot, "state", "flow-state.json");
|
|
921
|
-
let flowStateRaw;
|
|
922
|
-
try {
|
|
923
|
-
flowStateRaw = await fs.readFile(flowStatePath, "utf8");
|
|
924
|
-
}
|
|
925
|
-
catch {
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
let parsed;
|
|
929
|
-
try {
|
|
930
|
-
parsed = JSON.parse(flowStateRaw);
|
|
931
|
-
}
|
|
932
|
-
catch {
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
const obj = parsed;
|
|
939
|
-
if (typeof obj.tddCutoverSliceId === "string" && obj.tddCutoverSliceId.length > 0) {
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
// v6.14.3 — refresh the SHA256 sidecar by writing through
|
|
943
|
-
// `writeFlowState`. The previous direct `writeFileSafe` invocation
|
|
944
|
-
// left the sidecar stale, so the very next guarded hook on a synced
|
|
945
|
-
// legacy project rejected its own `tddCutoverSliceId` stamp.
|
|
946
|
-
try {
|
|
947
|
-
const state = await readFlowState(projectRoot);
|
|
948
|
-
await writeFlowState(projectRoot, { ...state, tddCutoverSliceId: cutoverSliceId }, {
|
|
949
|
-
allowReset: true,
|
|
950
|
-
writerSubsystem: "sync-v6.12-tdd-cutover-stamp"
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
catch {
|
|
954
|
-
// Best-effort: corrupt/missing state is handled elsewhere on sync.
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
const V613_LEGACY_PLAN_BANNER = "<!-- legacy-continuation: predates v6.13 parallel metadata. New units MAY add dependsOn/claimedPaths/parallelizable; existing units treated as best-effort serial. -->";
|
|
958
|
-
/**
|
|
959
|
-
* v6.13.0 — when `05-plan.md` lacks parallel-metadata bullets on any
|
|
960
|
-
* implementation unit, stamp `flow-state.json::legacyContinuation`, insert
|
|
961
|
-
* a banner + managed Parallel Execution Plan stub, and keep behavior idempotent.
|
|
962
|
-
*/
|
|
963
|
-
async function applyPlanLegacyContinuationIfNeeded(projectRoot) {
|
|
964
|
-
const planArtifactPath = runtimePath(projectRoot, "artifacts", "05-plan.md");
|
|
965
|
-
let existingPlan;
|
|
966
|
-
try {
|
|
967
|
-
existingPlan = await fs.readFile(planArtifactPath, "utf8");
|
|
968
|
-
}
|
|
969
|
-
catch {
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
if (!planArtifactLacksV613ParallelMetadata(existingPlan)) {
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
let nextPlan = existingPlan;
|
|
976
|
-
if (!nextPlan.includes("legacy-continuation: predates v6.13")) {
|
|
977
|
-
if (nextPlan.startsWith("---\n")) {
|
|
978
|
-
const fmEnd = nextPlan.indexOf("\n---", 4);
|
|
979
|
-
if (fmEnd >= 0) {
|
|
980
|
-
const fmClose = fmEnd + 4;
|
|
981
|
-
const head = nextPlan.slice(0, fmClose);
|
|
982
|
-
const tail = nextPlan.slice(fmClose);
|
|
983
|
-
nextPlan = `${head}\n\n${V613_LEGACY_PLAN_BANNER}\n${tail}`;
|
|
984
|
-
}
|
|
985
|
-
else {
|
|
986
|
-
nextPlan = `${V613_LEGACY_PLAN_BANNER}\n\n${nextPlan}`;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
nextPlan = `${V613_LEGACY_PLAN_BANNER}\n\n${nextPlan}`;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const parallelStub = buildParallelExecutionPlanSection([], PLAN_SPLIT_DEFAULT_WAVE_SIZE);
|
|
994
|
-
if (!nextPlan.includes("<!-- parallel-exec-managed-start -->")) {
|
|
995
|
-
nextPlan = upsertParallelExecutionPlanSection(nextPlan, parallelStub);
|
|
996
|
-
}
|
|
997
|
-
if (nextPlan !== existingPlan) {
|
|
998
|
-
await writeFileSafe(planArtifactPath, nextPlan);
|
|
999
|
-
}
|
|
1000
|
-
const flowStatePath = runtimePath(projectRoot, "state", "flow-state.json");
|
|
1001
|
-
if (!(await exists(flowStatePath))) {
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
try {
|
|
1005
|
-
const state = await readFlowState(projectRoot);
|
|
1006
|
-
if (state.legacyContinuation === true) {
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
await writeFlowState(projectRoot, { ...state, legacyContinuation: true }, {
|
|
1010
|
-
allowReset: true,
|
|
1011
|
-
writerSubsystem: "plan-legacy-continuation-sync"
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
catch {
|
|
1015
|
-
// Best-effort: corrupt/missing state is handled elsewhere on sync.
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* v6.14.0 — set stream-style defaults on `cclaw-cli sync` and print a
|
|
1020
|
-
* one-line hint when defaults change.
|
|
1021
|
-
*
|
|
1022
|
-
* v6.14.2 update — flip the legacyContinuation defaults from
|
|
1023
|
-
* `global-red`/`always` to `per-slice`/`conditional`. Rationale: hox-shape
|
|
1024
|
-
* projects ran into S-17 misroutes precisely because the default
|
|
1025
|
-
* preserved the v6.12 wave barrier even after the project itself had
|
|
1026
|
-
* moved to stream-mode. Existing flow-state values are NEVER overwritten
|
|
1027
|
-
* — operators who want to pin `global-red`/`always` may set them
|
|
1028
|
-
* explicitly via `cclaw-cli internal set-checkpoint-mode global-red` and
|
|
1029
|
-
* `set-integration-overseer-mode always`.
|
|
1030
|
-
*
|
|
1031
|
-
* Strategy:
|
|
1032
|
-
*
|
|
1033
|
-
* - When `legacyContinuation: true` and `tddCheckpointMode` is unset,
|
|
1034
|
-
* default to `tddCheckpointMode: "per-slice"` (v6.14.2 — was
|
|
1035
|
-
* `global-red` in v6.14.0/v6.14.1).
|
|
1036
|
-
* - When `legacyContinuation: true` and `integrationOverseerMode` is
|
|
1037
|
-
* unset, default to `integrationOverseerMode: "conditional"` (v6.14.2
|
|
1038
|
-
* — was `always` in v6.14.0/v6.14.1).
|
|
1039
|
-
* - When `legacyContinuation` is NOT true (new / standard projects) and
|
|
1040
|
-
* neither field is set, default to `tddCheckpointMode: "per-slice"`,
|
|
1041
|
-
* `integrationOverseerMode: "conditional"`. Also default
|
|
1042
|
-
* `worktreeExecutionMode: "worktree-first"` if unset.
|
|
1043
|
-
*
|
|
1044
|
-
* Returns a one-line hint string (or `null` if nothing changed) so callers
|
|
1045
|
-
* can print it through the standard sync hint surface.
|
|
1046
|
-
*/
|
|
1047
|
-
async function applyV614DefaultsIfNeeded(projectRoot) {
|
|
1048
|
-
// Defensive read — match `applyTddCutoverIfNeeded`'s pattern (raw +
|
|
1049
|
-
// JSON.parse) so corrupt state is left untouched for the downstream
|
|
1050
|
-
// fail-fast check in `materializeRuntime` (which expects to see the
|
|
1051
|
-
// CorruptFlowStateError surfaced via `ensureRunSystem`). Calling
|
|
1052
|
-
// `readFlowState` directly would quarantine the corrupt file and hide
|
|
1053
|
-
// the failure from the caller.
|
|
1054
|
-
const flowStatePath = runtimePath(projectRoot, "state", "flow-state.json");
|
|
1055
|
-
let flowStateRaw;
|
|
1056
|
-
try {
|
|
1057
|
-
flowStateRaw = await fs.readFile(flowStatePath, "utf8");
|
|
1058
|
-
}
|
|
1059
|
-
catch {
|
|
1060
|
-
return null;
|
|
1061
|
-
}
|
|
1062
|
-
let parsed;
|
|
1063
|
-
try {
|
|
1064
|
-
parsed = JSON.parse(flowStateRaw);
|
|
1065
|
-
}
|
|
1066
|
-
catch {
|
|
1067
|
-
return null;
|
|
1068
|
-
}
|
|
1069
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
const obj = parsed;
|
|
1073
|
-
const updates = {};
|
|
1074
|
-
const summary = [];
|
|
1075
|
-
const tddCheckpointModeSet = obj.tddCheckpointMode === "per-slice" || obj.tddCheckpointMode === "global-red";
|
|
1076
|
-
const integrationOverseerModeSet = obj.integrationOverseerMode === "conditional" || obj.integrationOverseerMode === "always";
|
|
1077
|
-
const worktreeExecutionModeSet = obj.worktreeExecutionMode === "worktree-first" || obj.worktreeExecutionMode === "single-tree";
|
|
1078
|
-
const legacyContinuation = obj.legacyContinuation === true;
|
|
1079
|
-
if (legacyContinuation) {
|
|
1080
|
-
if (!tddCheckpointModeSet) {
|
|
1081
|
-
updates.tddCheckpointMode = "per-slice";
|
|
1082
|
-
summary.push("tddCheckpointMode=per-slice (legacyContinuation, v6.14.2 default flip)");
|
|
1083
|
-
}
|
|
1084
|
-
if (!integrationOverseerModeSet) {
|
|
1085
|
-
updates.integrationOverseerMode = "conditional";
|
|
1086
|
-
summary.push("integrationOverseerMode=conditional (legacyContinuation, v6.14.2 default flip)");
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
else {
|
|
1090
|
-
if (!tddCheckpointModeSet) {
|
|
1091
|
-
updates.tddCheckpointMode = "per-slice";
|
|
1092
|
-
summary.push("tddCheckpointMode=per-slice");
|
|
1093
|
-
}
|
|
1094
|
-
if (!integrationOverseerModeSet) {
|
|
1095
|
-
updates.integrationOverseerMode = "conditional";
|
|
1096
|
-
summary.push("integrationOverseerMode=conditional");
|
|
1097
|
-
}
|
|
1098
|
-
if (!worktreeExecutionModeSet) {
|
|
1099
|
-
updates.worktreeExecutionMode = "worktree-first";
|
|
1100
|
-
summary.push("worktreeExecutionMode=worktree-first");
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
if (summary.length === 0) {
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
// v6.14.3 — refresh the SHA256 sidecar in lockstep so guarded reads
|
|
1107
|
-
// (verify-current-state, advance-stage, etc.) don't trip a guard
|
|
1108
|
-
// mismatch immediately after `cclaw-cli sync`/`upgrade` writes the
|
|
1109
|
-
// v6.14.2 stream-style defaults.
|
|
1110
|
-
try {
|
|
1111
|
-
const state = await readFlowState(projectRoot);
|
|
1112
|
-
await writeFlowState(projectRoot, { ...state, ...updates }, {
|
|
1113
|
-
allowReset: true,
|
|
1114
|
-
writerSubsystem: "sync-v6.14.2-stream-defaults"
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
catch {
|
|
1118
|
-
return null;
|
|
1119
|
-
}
|
|
1120
|
-
return `v6.14.2 stream-style defaults applied: ${summary.join(", ")}. To opt out, run cclaw-cli internal set-checkpoint-mode global-red --reason="..." and/or cclaw-cli internal set-integration-overseer-mode always --reason="...".`;
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* v6.14.2 — auto-stamp `tddWorktreeCutoverSliceId` for legacyContinuation
|
|
1124
|
-
* projects in worktree-first mode that haven't yet recorded a boundary.
|
|
1125
|
-
*
|
|
1126
|
-
* Detection ("any-metadata" rule): scan the active run's
|
|
1127
|
-
* `slice-implementer` rows. The boundary is the highest `S-N` whose
|
|
1128
|
-
* rows for the active run lack ALL of `claimToken`, `ownerLaneId`, and
|
|
1129
|
-
* `leasedUntil`. When no such slice exists (every slice carries at
|
|
1130
|
-
* least one worktree field), fall back to `tddCutoverSliceId` so the
|
|
1131
|
-
* v6.12 cutover marker still confers exemption.
|
|
1132
|
-
*
|
|
1133
|
-
* Idempotent: when the field is already set, returns null without
|
|
1134
|
-
* writing. Best-effort: read failures, missing ledger, or unparseable
|
|
1135
|
-
* rows all short-circuit silently — the existing flow-state.json is
|
|
1136
|
-
* never corrupted.
|
|
1137
|
-
*
|
|
1138
|
-
* Returns a one-line hint string (or `null` if nothing changed).
|
|
1139
|
-
*/
|
|
1140
|
-
async function applyV6142WorktreeCutoverIfNeeded(projectRoot) {
|
|
1141
|
-
const flowStatePath = runtimePath(projectRoot, "state", "flow-state.json");
|
|
1142
|
-
let flowStateRaw;
|
|
1143
|
-
try {
|
|
1144
|
-
flowStateRaw = await fs.readFile(flowStatePath, "utf8");
|
|
1145
|
-
}
|
|
1146
|
-
catch {
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
|
-
let parsed;
|
|
1150
|
-
try {
|
|
1151
|
-
parsed = JSON.parse(flowStateRaw);
|
|
1152
|
-
}
|
|
1153
|
-
catch {
|
|
1154
|
-
return null;
|
|
1155
|
-
}
|
|
1156
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1159
|
-
const obj = parsed;
|
|
1160
|
-
if (obj.legacyContinuation !== true)
|
|
1161
|
-
return null;
|
|
1162
|
-
if (obj.worktreeExecutionMode !== "worktree-first")
|
|
1163
|
-
return null;
|
|
1164
|
-
if (typeof obj.tddWorktreeCutoverSliceId === "string" &&
|
|
1165
|
-
obj.tddWorktreeCutoverSliceId.trim().length > 0) {
|
|
1166
|
-
return null;
|
|
1167
|
-
}
|
|
1168
|
-
const ledgerPath = runtimePath(projectRoot, "state", "delegation-log.json");
|
|
1169
|
-
const activeRunId = typeof obj.activeRunId === "string" ? obj.activeRunId : "";
|
|
1170
|
-
let ledgerRaw;
|
|
1171
|
-
try {
|
|
1172
|
-
ledgerRaw = await fs.readFile(ledgerPath, "utf8");
|
|
1173
|
-
}
|
|
1174
|
-
catch {
|
|
1175
|
-
ledgerRaw = "";
|
|
1176
|
-
}
|
|
1177
|
-
let ledgerParsed = null;
|
|
1178
|
-
if (ledgerRaw.length > 0) {
|
|
1179
|
-
try {
|
|
1180
|
-
ledgerParsed = JSON.parse(ledgerRaw);
|
|
1181
|
-
}
|
|
1182
|
-
catch {
|
|
1183
|
-
ledgerParsed = null;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
const entries = ledgerParsed &&
|
|
1187
|
-
typeof ledgerParsed === "object" &&
|
|
1188
|
-
!Array.isArray(ledgerParsed) &&
|
|
1189
|
-
Array.isArray(ledgerParsed.entries)
|
|
1190
|
-
? ledgerParsed.entries
|
|
1191
|
-
: [];
|
|
1192
|
-
let boundary = -1;
|
|
1193
|
-
for (const entry of entries) {
|
|
1194
|
-
if (entry.agent !== "slice-implementer")
|
|
1195
|
-
continue;
|
|
1196
|
-
if (entry.status !== "completed")
|
|
1197
|
-
continue;
|
|
1198
|
-
if (typeof entry.sliceId !== "string")
|
|
1199
|
-
continue;
|
|
1200
|
-
if (activeRunId && entry.runId && entry.runId !== activeRunId)
|
|
1201
|
-
continue;
|
|
1202
|
-
const tok = typeof entry.claimToken === "string" ? entry.claimToken.trim() : "";
|
|
1203
|
-
const lane = typeof entry.ownerLaneId === "string" ? entry.ownerLaneId.trim() : "";
|
|
1204
|
-
const lease = typeof entry.leasedUntil === "string" ? entry.leasedUntil.trim() : "";
|
|
1205
|
-
if (tok.length > 0 || lane.length > 0 || lease.length > 0)
|
|
1206
|
-
continue;
|
|
1207
|
-
const m = /^S-(\d+)$/u.exec(entry.sliceId);
|
|
1208
|
-
if (!m)
|
|
1209
|
-
continue;
|
|
1210
|
-
const n = Number.parseInt(m[1], 10);
|
|
1211
|
-
if (!Number.isFinite(n))
|
|
1212
|
-
continue;
|
|
1213
|
-
if (n > boundary)
|
|
1214
|
-
boundary = n;
|
|
1215
|
-
}
|
|
1216
|
-
let stamped = null;
|
|
1217
|
-
if (boundary >= 0) {
|
|
1218
|
-
stamped = `S-${boundary}`;
|
|
1219
|
-
}
|
|
1220
|
-
else if (typeof obj.tddCutoverSliceId === "string" &&
|
|
1221
|
-
/^S-\d+$/u.test(obj.tddCutoverSliceId)) {
|
|
1222
|
-
stamped = obj.tddCutoverSliceId;
|
|
1223
|
-
}
|
|
1224
|
-
if (!stamped)
|
|
1225
|
-
return null;
|
|
1226
|
-
// v6.14.3 — go through `writeFlowState` so the SHA256 sidecar
|
|
1227
|
-
// (`.cclaw/.flow-state.guard.json`) is refreshed in lockstep with
|
|
1228
|
-
// the on-disk flow-state.json. The previous v6.14.2 implementation
|
|
1229
|
-
// wrote the field via `writeFileSafe` directly, which left the
|
|
1230
|
-
// sidecar pointing at the pre-stamp digest; the next guarded hook
|
|
1231
|
-
// (e.g. `cclaw internal verify-current-state`) then failed with
|
|
1232
|
-
// `flow-state guard mismatch` and demanded a manual repair.
|
|
1233
|
-
try {
|
|
1234
|
-
const state = await readFlowState(projectRoot);
|
|
1235
|
-
await writeFlowState(projectRoot, { ...state, tddWorktreeCutoverSliceId: stamped }, {
|
|
1236
|
-
allowReset: true,
|
|
1237
|
-
writerSubsystem: "sync-v6.14.2-worktree-cutover-stamp"
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
catch {
|
|
1241
|
-
return null;
|
|
1242
|
-
}
|
|
1243
|
-
return (`v6.14.2 stamped tddWorktreeCutoverSliceId=${stamped}; closed slices ≤ ${stamped} ` +
|
|
1244
|
-
"are exempt from worktree-first findings under legacyContinuation. " +
|
|
1245
|
-
"Edit .cclaw/state/flow-state.json to override (advisory).");
|
|
1246
|
-
}
|
|
1247
831
|
async function cleanLegacyArtifacts(projectRoot) {
|
|
1248
832
|
for (const legacyFolder of DEPRECATED_UTILITY_SKILL_FOLDERS) {
|
|
1249
833
|
await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
|
|
@@ -1381,9 +965,6 @@ async function maybeLogParallelWaveDispatchHint(projectRoot) {
|
|
|
1381
965
|
if (!(await exists(flowPath)))
|
|
1382
966
|
return;
|
|
1383
967
|
try {
|
|
1384
|
-
const state = await readFlowState(projectRoot);
|
|
1385
|
-
if (effectiveWorktreeExecutionMode(state) !== "worktree-first")
|
|
1386
|
-
return;
|
|
1387
968
|
const planPath = runtimePath(projectRoot, "artifacts", "05-plan.md");
|
|
1388
969
|
if (!(await exists(planPath)))
|
|
1389
970
|
return;
|
|
@@ -1416,18 +997,6 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
|
|
|
1416
997
|
writeRulebook(projectRoot)
|
|
1417
998
|
]);
|
|
1418
999
|
await writeState(projectRoot, config, forceStateReset);
|
|
1419
|
-
if (operation === "sync" || operation === "upgrade") {
|
|
1420
|
-
await applyTddCutoverIfNeeded(projectRoot);
|
|
1421
|
-
await applyPlanLegacyContinuationIfNeeded(projectRoot);
|
|
1422
|
-
const v614Hint = await applyV614DefaultsIfNeeded(projectRoot);
|
|
1423
|
-
if (v614Hint) {
|
|
1424
|
-
process.stdout.write(`cclaw: ${v614Hint}\n`);
|
|
1425
|
-
}
|
|
1426
|
-
const v6142Hint = await applyV6142WorktreeCutoverIfNeeded(projectRoot);
|
|
1427
|
-
if (v6142Hint) {
|
|
1428
|
-
process.stdout.write(`cclaw: ${v6142Hint}\n`);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
1000
|
try {
|
|
1432
1001
|
await ensureRunSystem(projectRoot, { createIfMissing: false });
|
|
1433
1002
|
}
|
|
@@ -1481,7 +1050,6 @@ export async function initCclaw(options) {
|
|
|
1481
1050
|
throw new Error("Select at least one harness.");
|
|
1482
1051
|
}
|
|
1483
1052
|
const config = createDefaultConfig(options.harnesses, options.track);
|
|
1484
|
-
// Wave 21: config is always minimal and harness-only.
|
|
1485
1053
|
await writeConfig(options.projectRoot, config, { mode: "minimal" });
|
|
1486
1054
|
// Init should scaffold runtime surfaces but leave flow-state creation to the
|
|
1487
1055
|
// first managed start-flow invocation.
|
|
@@ -1701,14 +1269,11 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1701
1269
|
// directory not present
|
|
1702
1270
|
}
|
|
1703
1271
|
}
|
|
1704
|
-
// Codex
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
//
|
|
1708
|
-
//
|
|
1709
|
-
// Remove all three legacy layouts on uninstall so orphans can't linger.
|
|
1710
|
-
// We only touch cclaw-owned folder names — other tools share
|
|
1711
|
-
// `.agents/skills/` with us.
|
|
1272
|
+
// Codex shims live at `.agents/skills/cc*/SKILL.md`, matching Codex's
|
|
1273
|
+
// `/use cc` prompt verbatim. Older layouts (`.codex/commands/cc*.md` and
|
|
1274
|
+
// `.agents/skills/cclaw-cc*/SKILL.md`) are purged on uninstall so orphans
|
|
1275
|
+
// can't linger. We only touch cclaw-owned folder names — other tools
|
|
1276
|
+
// share `.agents/skills/` with us.
|
|
1712
1277
|
const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
|
|
1713
1278
|
try {
|
|
1714
1279
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
@@ -19,7 +19,7 @@ interface InternalValidationReport {
|
|
|
19
19
|
corruptEventLines: number[];
|
|
20
20
|
staleWorkers: string[];
|
|
21
21
|
expectedMode: string;
|
|
22
|
-
/**
|
|
22
|
+
/** True when mandatoryAgentsFor returned [] for the run's track / taskClass. */
|
|
23
23
|
skippedByTrack: boolean;
|
|
24
24
|
};
|
|
25
25
|
gates: {
|
|
@@ -35,23 +35,23 @@ interface InternalValidationReport {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* entry point — auto-hydrate evidence for an auto-hydratable
|
|
39
39
|
* gate that the agent already included in --passed but for which they
|
|
40
40
|
* forgot to provide --evidence-json. Returns silently when no
|
|
41
41
|
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
42
42
|
* envelope, etc.).
|
|
43
43
|
*
|
|
44
|
-
*
|
|
44
|
+
* layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
45
45
|
* top of this so the gate is also auto-included in selectedGateIds
|
|
46
46
|
* when the artifact yields a valid envelope. Together the two helpers
|
|
47
|
-
* remove the contradiction
|
|
47
|
+
* remove the contradiction:
|
|
48
48
|
* - "omit this gate from --evidence-json so stage-complete can
|
|
49
49
|
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
50
50
|
* gates: design_diagram_freshness".
|
|
51
51
|
*/
|
|
52
52
|
export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* auto-include an auto-hydratable review-loop gate
|
|
55
55
|
* in `selectedGateIds` when:
|
|
56
56
|
* - The stage has an auto-hydratable gate registered via
|
|
57
57
|
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|