cclaw-cli 0.49.0 → 0.51.1

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.
Files changed (183) hide show
  1. package/README.md +57 -84
  2. package/dist/artifact-linter.d.ts +4 -0
  3. package/dist/artifact-linter.js +24 -3
  4. package/dist/cli.d.ts +1 -19
  5. package/dist/cli.js +49 -491
  6. package/dist/constants.d.ts +2 -13
  7. package/dist/constants.js +1 -43
  8. package/dist/content/closeout-guidance.d.ts +14 -0
  9. package/dist/content/closeout-guidance.js +42 -0
  10. package/dist/content/core-agents.js +55 -17
  11. package/dist/content/decision-protocol.d.ts +12 -0
  12. package/dist/content/decision-protocol.js +20 -0
  13. package/dist/content/diff-command.d.ts +1 -2
  14. package/dist/content/diff-command.js +8 -94
  15. package/dist/content/examples.d.ts +4 -10
  16. package/dist/content/examples.js +10 -20
  17. package/dist/content/hook-events.js +2 -2
  18. package/dist/content/hook-inline-snippets.d.ts +5 -2
  19. package/dist/content/hook-inline-snippets.js +33 -1
  20. package/dist/content/hook-manifest.d.ts +3 -4
  21. package/dist/content/hook-manifest.js +11 -12
  22. package/dist/content/hooks.js +44 -21
  23. package/dist/content/ideate-command.d.ts +2 -0
  24. package/dist/content/ideate-command.js +34 -25
  25. package/dist/content/iron-laws.d.ts +5 -5
  26. package/dist/content/iron-laws.js +5 -5
  27. package/dist/content/language-policy.d.ts +2 -0
  28. package/dist/content/language-policy.js +13 -0
  29. package/dist/content/learnings.d.ts +3 -4
  30. package/dist/content/learnings.js +26 -50
  31. package/dist/content/meta-skill.js +33 -22
  32. package/dist/content/next-command.js +41 -38
  33. package/dist/content/node-hooks.js +17 -345
  34. package/dist/content/opencode-plugin.js +5 -103
  35. package/dist/content/research-playbooks.js +14 -14
  36. package/dist/content/review-loop.d.ts +2 -0
  37. package/dist/content/review-loop.js +8 -0
  38. package/dist/content/session-hooks.js +15 -47
  39. package/dist/content/skills.d.ts +0 -5
  40. package/dist/content/skills.js +55 -128
  41. package/dist/content/stage-common-guidance.d.ts +0 -1
  42. package/dist/content/stage-common-guidance.js +17 -14
  43. package/dist/content/stage-schema.d.ts +26 -1
  44. package/dist/content/stage-schema.js +121 -40
  45. package/dist/content/stages/_lint-metadata/index.js +9 -15
  46. package/dist/content/stages/brainstorm.js +22 -43
  47. package/dist/content/stages/design.js +37 -57
  48. package/dist/content/stages/plan.js +22 -13
  49. package/dist/content/stages/review.js +24 -27
  50. package/dist/content/stages/scope.js +34 -46
  51. package/dist/content/stages/ship.js +7 -4
  52. package/dist/content/stages/spec.js +20 -9
  53. package/dist/content/stages/tdd.js +64 -44
  54. package/dist/content/start-command.js +13 -12
  55. package/dist/content/status-command.d.ts +2 -7
  56. package/dist/content/status-command.js +19 -146
  57. package/dist/content/subagents.d.ts +0 -5
  58. package/dist/content/subagents.js +51 -28
  59. package/dist/content/templates.d.ts +1 -1
  60. package/dist/content/templates.js +126 -135
  61. package/dist/content/track-render-context.d.ts +17 -0
  62. package/dist/content/track-render-context.js +44 -0
  63. package/dist/content/tree-command.d.ts +1 -2
  64. package/dist/content/tree-command.js +4 -87
  65. package/dist/content/utility-skills.d.ts +2 -29
  66. package/dist/content/utility-skills.js +2 -1534
  67. package/dist/content/view-command.js +31 -11
  68. package/dist/delegation.d.ts +1 -1
  69. package/dist/delegation.js +5 -15
  70. package/dist/doctor-registry.js +20 -21
  71. package/dist/doctor.js +88 -344
  72. package/dist/flow-state.d.ts +3 -0
  73. package/dist/flow-state.js +2 -0
  74. package/dist/harness-adapters.d.ts +1 -1
  75. package/dist/harness-adapters.js +51 -58
  76. package/dist/install.js +128 -358
  77. package/dist/internal/advance-stage.js +3 -9
  78. package/dist/internal/compound-readiness.d.ts +1 -1
  79. package/dist/internal/compound-readiness.js +1 -1
  80. package/dist/internal/tdd-loop-status.d.ts +1 -1
  81. package/dist/internal/tdd-loop-status.js +1 -1
  82. package/dist/knowledge-store.d.ts +16 -10
  83. package/dist/knowledge-store.js +51 -15
  84. package/dist/policy.js +16 -105
  85. package/dist/run-archive.d.ts +4 -6
  86. package/dist/run-archive.js +15 -20
  87. package/dist/run-persistence.d.ts +2 -2
  88. package/dist/run-persistence.js +3 -9
  89. package/package.json +1 -2
  90. package/dist/content/archive-command.d.ts +0 -2
  91. package/dist/content/archive-command.js +0 -124
  92. package/dist/content/compound-command.d.ts +0 -5
  93. package/dist/content/compound-command.js +0 -193
  94. package/dist/content/contexts.d.ts +0 -18
  95. package/dist/content/contexts.js +0 -24
  96. package/dist/content/contracts.d.ts +0 -2
  97. package/dist/content/contracts.js +0 -51
  98. package/dist/content/doctor-references.d.ts +0 -2
  99. package/dist/content/doctor-references.js +0 -150
  100. package/dist/content/eval-scaffold.d.ts +0 -15
  101. package/dist/content/eval-scaffold.js +0 -370
  102. package/dist/content/feature-command.d.ts +0 -2
  103. package/dist/content/feature-command.js +0 -123
  104. package/dist/content/flow-map.d.ts +0 -23
  105. package/dist/content/flow-map.js +0 -134
  106. package/dist/content/harness-doc.d.ts +0 -2
  107. package/dist/content/harness-doc.js +0 -202
  108. package/dist/content/harness-playbooks.d.ts +0 -24
  109. package/dist/content/harness-playbooks.js +0 -393
  110. package/dist/content/harness-tool-refs.d.ts +0 -20
  111. package/dist/content/harness-tool-refs.js +0 -268
  112. package/dist/content/ops-command.d.ts +0 -2
  113. package/dist/content/ops-command.js +0 -71
  114. package/dist/content/protocols.d.ts +0 -7
  115. package/dist/content/protocols.js +0 -215
  116. package/dist/content/retro-command.d.ts +0 -2
  117. package/dist/content/retro-command.js +0 -165
  118. package/dist/content/rewind-command.d.ts +0 -2
  119. package/dist/content/rewind-command.js +0 -106
  120. package/dist/content/tdd-log-command.d.ts +0 -2
  121. package/dist/content/tdd-log-command.js +0 -85
  122. package/dist/eval/agents/single-shot.d.ts +0 -27
  123. package/dist/eval/agents/single-shot.js +0 -79
  124. package/dist/eval/agents/with-tools.d.ts +0 -44
  125. package/dist/eval/agents/with-tools.js +0 -261
  126. package/dist/eval/agents/workflow.d.ts +0 -31
  127. package/dist/eval/agents/workflow.js +0 -155
  128. package/dist/eval/baseline.d.ts +0 -38
  129. package/dist/eval/baseline.js +0 -282
  130. package/dist/eval/config-loader.d.ts +0 -14
  131. package/dist/eval/config-loader.js +0 -395
  132. package/dist/eval/corpus.d.ts +0 -30
  133. package/dist/eval/corpus.js +0 -330
  134. package/dist/eval/cost-guard.d.ts +0 -102
  135. package/dist/eval/cost-guard.js +0 -190
  136. package/dist/eval/diff.d.ts +0 -64
  137. package/dist/eval/diff.js +0 -323
  138. package/dist/eval/llm-client.d.ts +0 -176
  139. package/dist/eval/llm-client.js +0 -267
  140. package/dist/eval/mode.d.ts +0 -28
  141. package/dist/eval/mode.js +0 -61
  142. package/dist/eval/progress.d.ts +0 -83
  143. package/dist/eval/progress.js +0 -59
  144. package/dist/eval/report.d.ts +0 -11
  145. package/dist/eval/report.js +0 -181
  146. package/dist/eval/rubric-loader.d.ts +0 -20
  147. package/dist/eval/rubric-loader.js +0 -143
  148. package/dist/eval/runner.d.ts +0 -81
  149. package/dist/eval/runner.js +0 -746
  150. package/dist/eval/runs.d.ts +0 -41
  151. package/dist/eval/runs.js +0 -114
  152. package/dist/eval/sandbox.d.ts +0 -38
  153. package/dist/eval/sandbox.js +0 -137
  154. package/dist/eval/tools/glob.d.ts +0 -2
  155. package/dist/eval/tools/glob.js +0 -163
  156. package/dist/eval/tools/grep.d.ts +0 -2
  157. package/dist/eval/tools/grep.js +0 -152
  158. package/dist/eval/tools/index.d.ts +0 -7
  159. package/dist/eval/tools/index.js +0 -35
  160. package/dist/eval/tools/read.d.ts +0 -2
  161. package/dist/eval/tools/read.js +0 -122
  162. package/dist/eval/tools/types.d.ts +0 -49
  163. package/dist/eval/tools/types.js +0 -41
  164. package/dist/eval/tools/write.d.ts +0 -2
  165. package/dist/eval/tools/write.js +0 -92
  166. package/dist/eval/types.d.ts +0 -561
  167. package/dist/eval/types.js +0 -47
  168. package/dist/eval/verifiers/judge.d.ts +0 -40
  169. package/dist/eval/verifiers/judge.js +0 -256
  170. package/dist/eval/verifiers/rules.d.ts +0 -24
  171. package/dist/eval/verifiers/rules.js +0 -218
  172. package/dist/eval/verifiers/structural.d.ts +0 -14
  173. package/dist/eval/verifiers/structural.js +0 -171
  174. package/dist/eval/verifiers/traceability.d.ts +0 -23
  175. package/dist/eval/verifiers/traceability.js +0 -84
  176. package/dist/eval/verifiers/workflow-consistency.d.ts +0 -21
  177. package/dist/eval/verifiers/workflow-consistency.js +0 -225
  178. package/dist/eval/workflow-corpus.d.ts +0 -7
  179. package/dist/eval/workflow-corpus.js +0 -207
  180. package/dist/feature-system.d.ts +0 -42
  181. package/dist/feature-system.js +0 -432
  182. package/dist/internal/knowledge-digest.d.ts +0 -7
  183. package/dist/internal/knowledge-digest.js +0 -93
@@ -290,12 +290,6 @@ async function appendJsonLine(filePath, value) {
290
290
  });
291
291
  }
292
292
 
293
- async function writeTextFileAtomic(filePath, content) {
294
- await withDirectoryLockInline(lockPathFor(filePath), async () => {
295
- await writeFileAtomic(filePath, content);
296
- });
297
- }
298
-
299
293
  async function readStdin() {
300
294
  return await new Promise((resolve) => {
301
295
  let data = "";
@@ -400,7 +394,7 @@ function hookEventNameForOutput(hookName) {
400
394
  if (hookName === "prompt-guard") return "PreToolUse";
401
395
  if (hookName === "workflow-guard") return "PreToolUse";
402
396
  if (hookName === "context-monitor") return "PostToolUse";
403
- if (hookName === "stop-checkpoint") return "Stop";
397
+ if (hookName === "stop-handoff") return "Stop";
404
398
  if (hookName === "pre-compact") return "PreCompact";
405
399
  if (hookName === "verify-current-state") return "UserPromptSubmit";
406
400
  return "SessionStart";
@@ -811,33 +805,9 @@ async function readFlowState(root) {
811
805
  };
812
806
  }
813
807
 
814
- function formatCheckpointSummary(checkpointObj) {
815
- const stage = typeof checkpointObj.stage === "string" ? checkpointObj.stage : "none";
816
- const status = typeof checkpointObj.status === "string" ? checkpointObj.status : "unknown";
817
- const runId = typeof checkpointObj.runId === "string" ? checkpointObj.runId : "none";
818
- const timestamp = typeof checkpointObj.timestamp === "string" ? checkpointObj.timestamp : "unknown";
819
- return "Checkpoint: stage=" + stage + ", status=" + status + ", run=" + runId + ", at=" + timestamp;
820
- }
821
-
822
- function stageSuggestion(stage) {
823
- const map = {
824
- brainstorm:
825
- "Suggestion: list 2-3 alternatives and ask a single focused clarifying question before direction lock.",
826
- scope: "Suggestion: lock explicit in-scope/out-of-scope boundaries and choose one scope mode.",
827
- design:
828
- "Suggestion: map failure modes per new codepath and confirm architecture boundaries before moving forward.",
829
- spec: "Suggestion: ensure every acceptance criterion is measurable and mapped to a concrete test.",
830
- plan: "Suggestion: group tasks into dependency batches and keep WAIT_FOR_CONFIRM pending until approval.",
831
- tdd: "Suggestion: execute RED -> GREEN -> REFACTOR for each selected slice and capture evidence per cycle.",
832
- review: "Suggestion: run Layer 1 before Layer 2 and reconcile findings into 07-review-army.json.",
833
- ship: "Suggestion: verify preflight + rollback plan before selecting exactly one finalization mode."
834
- };
835
- return map[stage] || "";
836
- }
837
808
 
838
809
  async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
839
810
  const knowledgeFile = path.join(root, RUNTIME_ROOT, "knowledge.jsonl");
840
- const digestFile = path.join(root, RUNTIME_ROOT, "state", "knowledge-digest.md");
841
811
  // Caller may supply pre-read raw bytes to avoid re-reading knowledge.jsonl.
842
812
  // Falls back to a local read if nothing is passed in.
843
813
  const raw = typeof prereadRaw === "string"
@@ -873,105 +843,19 @@ async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
873
843
  });
874
844
  const body =
875
845
  relevant.length > 0 ? relevant.join("\\n") : "(no matching entries for current stage)";
876
- await writeTextFileAtomic(
877
- digestFile,
878
- "# Knowledge digest (auto-generated)\\n\\n" + body + "\\n"
879
- );
880
846
  return {
881
847
  digestLines: relevant,
882
848
  learningsCount
883
849
  };
884
850
  }
885
851
 
886
- async function readRecentActivityLines(activityFile) {
887
- const raw = await readTextFile(activityFile, "");
888
- const lines = raw.split(/\\r?\\n/gu).map((line) => line.trim()).filter((line) => line.length > 0);
889
- const tail = lines.slice(-5);
890
- const out = [];
891
- for (const line of tail) {
892
- try {
893
- const parsed = JSON.parse(line);
894
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
895
- out.push(
896
- "- " +
897
- (typeof parsed.ts === "string" ? parsed.ts : "unknown") +
898
- " [" +
899
- (typeof parsed.phase === "string" ? parsed.phase : "unknown") +
900
- "] " +
901
- (typeof parsed.tool === "string" ? parsed.tool : "unknown") +
902
- " (stage=" +
903
- (typeof parsed.stage === "string" ? parsed.stage : "unknown") +
904
- ", run=" +
905
- (typeof parsed.runId === "string" ? parsed.runId : "none") +
906
- ")"
907
- );
908
- } catch {
909
- // ignore malformed activity lines
910
- }
911
- }
912
- return out;
913
- }
914
-
915
- async function readLatestContextWarningLine(filePath) {
916
- const raw = await readTextFile(filePath, "");
917
- const lines = raw.split(/\\r?\\n/gu).map((line) => line.trim()).filter((line) => line.length > 0);
918
- const line = lines[lines.length - 1] || "";
919
- if (line.length === 0) return "";
920
- try {
921
- const parsed = JSON.parse(line);
922
- if (parsed && typeof parsed === "object" && typeof parsed.note === "string") {
923
- return parsed.note;
924
- }
925
- } catch {
926
- // fallback
927
- }
928
- return line;
929
- }
930
-
931
852
  async function handleSessionStart(runtime) {
932
853
  const state = await readFlowState(runtime.root);
933
854
  const stateDir = path.join(runtime.root, RUNTIME_ROOT, "state");
934
- const contextsDir = path.join(runtime.root, RUNTIME_ROOT, "contexts");
935
- const activeFeatureFile = path.join(stateDir, "active-feature.json");
936
- const checkpointFile = path.join(stateDir, "checkpoint.json");
937
- const activityFile = path.join(stateDir, "stage-activity.jsonl");
938
- const contextWarningsFile = path.join(stateDir, "context-warnings.jsonl");
939
- const contextModeFile = path.join(stateDir, "context-mode.json");
940
- const suggestionMemoryFile = path.join(stateDir, "suggestion-memory.json");
941
855
  const ironLawsFile = path.join(stateDir, "iron-laws.json");
942
- const sessionDigestFile = path.join(stateDir, "session-digest.md");
943
856
  const metaSkillFile = path.join(runtime.root, RUNTIME_ROOT, "skills", "using-cclaw", "SKILL.md");
944
857
 
945
- const activeFeatureObj = toObject(await readJsonFile(activeFeatureFile, {})) || {};
946
- const activeFeature =
947
- typeof activeFeatureObj.activeFeature === "string" && activeFeatureObj.activeFeature.length > 0
948
- ? activeFeatureObj.activeFeature
949
- : "default";
950
-
951
- const contextModeObj = toObject(await readJsonFile(contextModeFile, {})) || {};
952
- const activeContextMode =
953
- typeof contextModeObj.activeMode === "string" && contextModeObj.activeMode.length > 0
954
- ? contextModeObj.activeMode
955
- : "default";
956
- const contextGuidePath = path.join(contextsDir, activeContextMode + ".md");
957
- const contextModeNote = (await fileExists(contextGuidePath))
958
- ? "Context mode: " +
959
- activeContextMode +
960
- " (guide: " +
961
- RUNTIME_ROOT +
962
- "/contexts/" +
963
- activeContextMode +
964
- ".md)"
965
- : "Context mode: " + activeContextMode;
966
-
967
- const checkpointObj = toObject(await readJsonFile(checkpointFile, {})) || {};
968
- const checkpointSummary = Object.keys(checkpointObj).length > 0
969
- ? formatCheckpointSummary(checkpointObj)
970
- : "";
971
-
972
- const sessionDigest = (await readTextFile(sessionDigestFile, "")).trim();
973
- const activitySummary = await readRecentActivityLines(activityFile);
974
- const contextWarning = await readLatestContextWarningLine(contextWarningsFile);
858
+
975
859
  // Read knowledge.jsonl exactly once per session-start while holding the
976
860
  // SAME lock CLI writers acquire in \`appendKnowledge\`. Guarantees we never
977
861
  // see a partial (mid-write) snapshot. Both the digest and
@@ -1041,32 +925,8 @@ async function handleSessionStart(runtime) {
1041
925
  });
1042
926
  await writeJsonFile(path.join(stateDir, "compound-readiness.json"), readiness);
1043
927
  }
1044
- const readinessObj = toObject(readiness) || {};
1045
- const ready = Array.isArray(readinessObj.ready) ? readinessObj.ready : [];
1046
- const readyCount =
1047
- typeof readinessObj.readyCount === "number" && Number.isFinite(readinessObj.readyCount)
1048
- ? Math.trunc(readinessObj.readyCount)
1049
- : ready.length;
1050
- const clusterCount =
1051
- typeof readinessObj.clusterCount === "number" && Number.isFinite(readinessObj.clusterCount)
1052
- ? Math.trunc(readinessObj.clusterCount)
1053
- : 0;
1054
- const threshold =
1055
- typeof readinessObj.threshold === "number" && Number.isFinite(readinessObj.threshold)
1056
- ? Math.trunc(readinessObj.threshold)
1057
- : COMPOUND_RECURRENCE_THRESHOLD;
1058
928
  if (state.currentStage === "review" || state.currentStage === "ship") {
1059
- if (readyCount === 0) {
1060
- compoundReadinessLine = "Compound readiness: no candidates (clusters=" +
1061
- String(clusterCount) + ", threshold=" + String(threshold) + ")";
1062
- } else {
1063
- const critical = ready.filter(
1064
- (entry) => entry && typeof entry === "object" && entry.severity === "critical"
1065
- ).length;
1066
- const criticalSuffix = critical > 0 ? " (critical=" + String(critical) + ")" : "";
1067
- compoundReadinessLine = "Compound readiness: clusters=" + String(clusterCount) +
1068
- ", ready=" + String(readyCount) + criticalSuffix;
1069
- }
929
+ compoundReadinessLine = formatCompoundReadinessLineInline(toObject(readiness) || {});
1070
930
  }
1071
931
  } catch (err) {
1072
932
  // Best-effort — a malformed knowledge.jsonl must never break
@@ -1080,26 +940,6 @@ async function handleSessionStart(runtime) {
1080
940
  );
1081
941
  }
1082
942
 
1083
- const suggestionMemory = toObject(await readJsonFile(suggestionMemoryFile, {})) || {};
1084
- const suggestionsEnabled = suggestionMemory.enabled !== false;
1085
- const mutedStages = Array.isArray(suggestionMemory.mutedStages)
1086
- ? suggestionMemory.mutedStages.filter((value) => typeof value === "string")
1087
- : [];
1088
- const stageMuted = mutedStages.includes(state.currentStage);
1089
- let stageHint = "";
1090
- if (suggestionsEnabled && !stageMuted) {
1091
- stageHint = stageSuggestion(state.currentStage);
1092
- if (stageHint.length > 0) {
1093
- const nextSuggestionMemory = {
1094
- enabled: suggestionsEnabled,
1095
- mutedStages,
1096
- lastSuggestedStage: state.currentStage,
1097
- lastSuggestedAt: new Date().toISOString()
1098
- };
1099
- await writeJsonFile(suggestionMemoryFile, nextSuggestionMemory);
1100
- }
1101
- }
1102
-
1103
943
  const ironLawsObj = toObject(await readJsonFile(ironLawsFile, {})) || {};
1104
944
  const laws = Array.isArray(ironLawsObj.laws) ? ironLawsObj.laws : [];
1105
945
  const ironLawLines = laws
@@ -1122,50 +962,23 @@ async function handleSessionStart(runtime) {
1122
962
  String(state.completedCount) +
1123
963
  "/8 completed, run=" +
1124
964
  state.activeRunId +
1125
- ", feature=" +
1126
- activeFeature +
1127
965
  "). Active artifacts: " +
1128
966
  RUNTIME_ROOT +
1129
- "/artifacts/. Feature registry: " +
1130
- RUNTIME_ROOT +
1131
- "/state/worktrees.json (managed roots: " +
1132
- RUNTIME_ROOT +
1133
- "/worktrees/). Learnings: " +
967
+ "/artifacts/. Learnings: " +
1134
968
  String(knowledge.learningsCount) +
1135
969
  " entries."
1136
970
  ];
1137
- parts.push(contextModeNote);
1138
- if (checkpointSummary.length > 0) {
1139
- parts.push(checkpointSummary);
1140
- }
1141
- if (sessionDigest.length > 0) {
1142
- parts.push("Last session:\\n" + sessionDigest);
1143
- }
1144
- if (activitySummary.length > 0) {
1145
- parts.push("Recent stage activity:\\n" + activitySummary.join("\\n"));
1146
- }
1147
971
  if (ralphLoopLine.length > 0) {
1148
972
  parts.push(ralphLoopLine);
1149
973
  }
1150
974
  if (compoundReadinessLine.length > 0) {
1151
975
  parts.push(compoundReadinessLine);
1152
976
  }
1153
- if (contextWarning.length > 0) {
1154
- parts.push("Latest context warning:\\n" + contextWarning);
1155
- }
1156
- if (stageHint.length > 0) {
1157
- parts.push(
1158
- stageHint +
1159
- "\\nTo disable suggestions persistently set " +
1160
- RUNTIME_ROOT +
1161
- "/state/suggestion-memory.json -> enabled=false."
1162
- );
1163
- }
1164
977
  if (staleStageNames.length > 0) {
1165
978
  parts.push(
1166
979
  "Stale stages pending acknowledgement: " +
1167
980
  staleStageNames.join(", ") +
1168
- " (use /cc-ops rewind --ack <stage> after redo)."
981
+ " (use cclaw internal rewind --ack <stage> after redo)."
1169
982
  );
1170
983
  }
1171
984
  if (knowledge.digestLines.length > 0) {
@@ -1222,15 +1035,14 @@ function stopLawIsStrict(ironLawsObj) {
1222
1035
  (row) =>
1223
1036
  row &&
1224
1037
  typeof row === "object" &&
1225
- row.id === "stop-clean-or-checkpointed" &&
1038
+ (row.id === "stop-clean-or-handoff" || row.id === "stop-clean-or-checkpointed") &&
1226
1039
  row.strict === true
1227
1040
  );
1228
1041
  }
1229
1042
 
1230
- async function handleStopCheckpoint(runtime) {
1043
+ async function handleStopHandoff(runtime) {
1231
1044
  const state = await readFlowState(runtime.root);
1232
1045
  const stateDir = path.join(runtime.root, RUNTIME_ROOT, "state");
1233
- const checkpointFile = path.join(stateDir, "checkpoint.json");
1234
1046
  const ironLawsFile = path.join(stateDir, "iron-laws.json");
1235
1047
  const input = toObject(runtime.inputData) || {};
1236
1048
  const loopCount =
@@ -1238,31 +1050,11 @@ async function handleStopCheckpoint(runtime) {
1238
1050
  ? Math.trunc(input.loop_count)
1239
1051
  : 0;
1240
1052
 
1241
- const existing = toObject(await readJsonFile(checkpointFile, {})) || {};
1242
- const timestamp = new Date().toISOString();
1243
1053
  const dirtyState = await isGitDirty(runtime.root);
1244
- const nextCheckpoint = {
1245
- ...existing,
1246
- stage: state.currentStage,
1247
- runId: state.activeRunId,
1248
- status:
1249
- typeof existing.status === "string" && existing.status.trim().length > 0
1250
- ? existing.status
1251
- : "in_progress",
1252
- dirtyState,
1253
- lastCompletedStep:
1254
- typeof existing.lastCompletedStep === "string" ? existing.lastCompletedStep : "",
1255
- remainingSteps: Array.isArray(existing.remainingSteps) ? existing.remainingSteps : [],
1256
- blockers: Array.isArray(existing.blockers) ? existing.blockers : [],
1257
- harness: runtime.harness,
1258
- timestamp
1259
- };
1260
- await writeJsonFile(checkpointFile, nextCheckpoint);
1261
-
1262
1054
  const strictStop = stopLawIsStrict(toObject(await readJsonFile(ironLawsFile, {})) || {});
1263
1055
  if (dirtyState === "dirty" && strictStop) {
1264
1056
  process.stderr.write(
1265
- '[cclaw] Stop blocked by iron law "stop-clean-or-checkpointed": working tree is dirty. Commit/revert changes or update checkpoint blockers before ending the session.\\n'
1057
+ '[cclaw] Stop blocked by iron law "stop-clean-or-handoff": working tree is dirty. Commit/revert changes or record blockers in the current artifact before ending the session.\\n'
1266
1058
  );
1267
1059
  return 1;
1268
1060
  }
@@ -1272,13 +1064,9 @@ async function handleStopCheckpoint(runtime) {
1272
1064
  state.currentStage +
1273
1065
  ", run=" +
1274
1066
  state.activeRunId +
1275
- "). Checkpoint updated at " +
1276
- RUNTIME_ROOT +
1277
- "/state/checkpoint.json. Run metadata sync removed; active artifacts stay in " +
1067
+ "). Active artifacts stay in " +
1278
1068
  RUNTIME_ROOT +
1279
- "/artifacts until /cc-ops archive (or cclaw archive runtime). Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match current feature intent, (3) if you discovered a non-obvious rule/pattern, append one strict-schema JSON line to " +
1280
- RUNTIME_ROOT +
1281
- "/knowledge.jsonl, (4) commit or revert pending changes.";
1069
+ "/artifacts until archive. Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match current intent, (3) if you discovered a non-obvious rule/pattern during stage work, add it to the current artifact ## Learnings section so stage-complete can harvest it, (4) commit or revert pending changes.";
1282
1070
 
1283
1071
  if (runtime.harness === "cursor") {
1284
1072
  if (loopCount === 0) {
@@ -1293,116 +1081,7 @@ async function handleStopCheckpoint(runtime) {
1293
1081
  return 0;
1294
1082
  }
1295
1083
 
1296
- async function handlePreCompact(runtime) {
1297
- const state = await readFlowState(runtime.root);
1298
- const stateDir = path.join(runtime.root, RUNTIME_ROOT, "state");
1299
- const flow = state.raw;
1300
- const stage = state.currentStage;
1301
- const track = typeof flow.track === "string" ? flow.track : "standard";
1302
- const skipped = Array.isArray(flow.skippedStages)
1303
- ? flow.skippedStages.filter((value) => typeof value === "string").join(",")
1304
- : "";
1305
-
1306
- const stageGateCatalog = toObject(flow.stageGateCatalog) || {};
1307
- const stageGate = toObject(stageGateCatalog[stage]) || {};
1308
- const passed = Array.isArray(stageGate.passed)
1309
- ? stageGate.passed.filter((value) => typeof value === "string").join(",")
1310
- : "";
1311
- const blocked = Array.isArray(stageGate.blocked)
1312
- ? stageGate.blocked.filter((value) => typeof value === "string").join(",")
1313
- : "";
1314
-
1315
- let delegationPending = "";
1316
- const delegationLog = await readJsonFile(path.join(stateDir, "delegation-log.json"), {});
1317
- const delegationObj = toObject(delegationLog) || {};
1318
- const entries = Array.isArray(delegationObj.entries) ? delegationObj.entries : [];
1319
- const pendingAgents = entries
1320
- .filter((row) => row && typeof row === "object")
1321
- .filter(
1322
- (row) =>
1323
- row.stage === stage &&
1324
- row.status !== "completed" &&
1325
- row.status !== "waived" &&
1326
- typeof row.agent === "string"
1327
- )
1328
- .map((row) => row.agent);
1329
- if (pendingAgents.length > 0) {
1330
- delegationPending = [...new Set(pendingAgents)].join(",");
1331
- }
1332
-
1333
- const knowledgeRaw = await readTextFile(path.join(runtime.root, RUNTIME_ROOT, "knowledge.jsonl"), "");
1334
- const knowledgeTail = knowledgeRaw
1335
- .split(/\\r?\\n/gu)
1336
- .filter((line) => line.trim().length > 0)
1337
- .slice(-12)
1338
- .join("\\n");
1339
-
1340
- let gitBranch = "unknown";
1341
- let gitHead = "unknown";
1342
- let gitDirty = "unknown";
1343
- await new Promise((resolve) => {
1344
- const child = spawn("git", ["-C", runtime.root, "rev-parse", "--abbrev-ref", "HEAD"], {
1345
- stdio: ["ignore", "pipe", "ignore"]
1346
- });
1347
- let output = "";
1348
- child.stdout.on("data", (chunk) => {
1349
- output += String(chunk);
1350
- });
1351
- child.on("close", (code) => {
1352
- if (code === 0 && output.trim().length > 0) {
1353
- gitBranch = output.trim();
1354
- }
1355
- resolve(undefined);
1356
- });
1357
- child.on("error", () => resolve(undefined));
1358
- });
1359
- await new Promise((resolve) => {
1360
- const child = spawn("git", ["-C", runtime.root, "rev-parse", "--short", "HEAD"], {
1361
- stdio: ["ignore", "pipe", "ignore"]
1362
- });
1363
- let output = "";
1364
- child.stdout.on("data", (chunk) => {
1365
- output += String(chunk);
1366
- });
1367
- child.on("close", (code) => {
1368
- if (code === 0 && output.trim().length > 0) {
1369
- gitHead = output.trim();
1370
- }
1371
- resolve(undefined);
1372
- });
1373
- child.on("error", () => resolve(undefined));
1374
- });
1375
- gitDirty = await isGitDirty(runtime.root);
1376
-
1377
- const timestamp = new Date().toISOString();
1378
- const digest = [
1379
- "# Session Digest",
1380
- "_Generated by pre-compact hook at " + timestamp + "_",
1381
- "",
1382
- "## Flow snapshot",
1383
- "- track: " + track,
1384
- "- current stage: " + stage,
1385
- "- completed: " + String(state.completedCount) + " stages",
1386
- "- skipped: " + (skipped.length > 0 ? skipped : "(none)"),
1387
- "- run: " + state.activeRunId,
1388
- "",
1389
- "## Gates (current stage)",
1390
- "- passed: " + (passed.length > 0 ? passed : "(none)"),
1391
- "- blocked: " + (blocked.length > 0 ? blocked : "(none)"),
1392
- "",
1393
- "## Outstanding delegations",
1394
- "- pending: " + (delegationPending.length > 0 ? delegationPending : "(none)"),
1395
- "",
1396
- "## Git",
1397
- "- branch: " + gitBranch,
1398
- "- head: " + gitHead,
1399
- "- worktree: " + gitDirty
1400
- ];
1401
- if (knowledgeTail.length > 0) {
1402
- digest.push("", "## Knowledge tail", knowledgeTail);
1403
- }
1404
- const digestFile = path.join(stateDir, "session-digest.md");
1405
- await writeTextFileAtomic(digestFile, digest.join("\\n") + "\\n");
1084
+ async function handlePreCompact(_runtime) {
1406
1085
  return 0;
1407
1086
  }
1408
1087
 
@@ -1781,7 +1460,6 @@ async function handleWorkflowGuard(runtime) {
1781
1460
  async function handleContextMonitor(runtime) {
1782
1461
  const stateDir = path.join(runtime.root, RUNTIME_ROOT, "state");
1783
1462
  const monitorStateFile = path.join(stateDir, "context-monitor.json");
1784
- const warningsFile = path.join(stateDir, "context-warnings.jsonl");
1785
1463
  const autoEvidenceFile = path.join(stateDir, "tdd-red-evidence.jsonl");
1786
1464
  const flowState = await readFlowState(runtime.root);
1787
1465
 
@@ -1862,14 +1540,7 @@ async function handleContextMonitor(runtime) {
1862
1540
  String(remainingPercent.toFixed(2)) +
1863
1541
  "% (" +
1864
1542
  band +
1865
- "). Consider checkpointing or compacting soon.";
1866
- await appendJsonLine(warningsFile, {
1867
- ts: now.toISOString(),
1868
- harness: runtime.harness,
1869
- band,
1870
- remainingPercent,
1871
- note
1872
- });
1543
+ "). Consider leaving a handoff note or compacting soon.";
1873
1544
  emitAdvisoryContext(runtime, "context-monitor", note);
1874
1545
  process.stderr.write("[cclaw] " + note + "\\n");
1875
1546
  nextAdvisoryBand = band;
@@ -1920,7 +1591,8 @@ async function handleVerifyCurrentState(runtime) {
1920
1591
  function normalizeHookName(rawName) {
1921
1592
  const value = normalizeText(rawName).toLowerCase();
1922
1593
  if (value === "session-start") return "session-start";
1923
- if (value === "stop-checkpoint") return "stop-checkpoint";
1594
+ if (value === "stop-handoff") return "stop-handoff";
1595
+ if (value === "stop-checkpoint") return "stop-handoff";
1924
1596
  if (value === "pre-compact") return "pre-compact";
1925
1597
  if (value === "prompt-guard") return "prompt-guard";
1926
1598
  if (value === "workflow-guard") return "workflow-guard";
@@ -1935,7 +1607,7 @@ async function main() {
1935
1607
  process.stderr.write(
1936
1608
  "[cclaw] run-hook: usage: node " +
1937
1609
  RUNTIME_ROOT +
1938
- "/hooks/run-hook.mjs <session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state>\\n"
1610
+ "/hooks/run-hook.mjs <session-start|stop-handoff|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state>\\n"
1939
1611
  );
1940
1612
  process.exitCode = 1;
1941
1613
  return;
@@ -1967,8 +1639,8 @@ async function main() {
1967
1639
  process.exitCode = await handleSessionStart(runtime);
1968
1640
  return;
1969
1641
  }
1970
- if (hookName === "stop-checkpoint") {
1971
- process.exitCode = await handleStopCheckpoint(runtime);
1642
+ if (hookName === "stop-handoff") {
1643
+ process.exitCode = await handleStopHandoff(runtime);
1972
1644
  return;
1973
1645
  }
1974
1646
  if (hookName === "pre-compact") {
@@ -1,7 +1,7 @@
1
1
  import { RUNTIME_ROOT } from "../constants.js";
2
2
  import { META_SKILL_NAME } from "./meta-skill.js";
3
3
  export function opencodePluginJs(_options = {}) {
4
- return `// cclaw OpenCode plugin — generated by cclaw sync
4
+ return `// cclaw OpenCode plugin — generated by npx cclaw-cli sync
5
5
  import { appendFileSync, existsSync, mkdirSync } from "node:fs";
6
6
  import { readFile, stat } from "node:fs/promises";
7
7
  import { join } from "node:path";
@@ -14,14 +14,7 @@ export default function cclawPlugin(ctx) {
14
14
  const pluginLogPath = join(logsDir, "opencode-plugin.log");
15
15
  const configPath = join(runtimeDir, "config.yaml");
16
16
  const flowStatePath = join(stateDir, "flow-state.json");
17
- const checkpointPath = join(stateDir, "checkpoint.json");
18
- const activityPath = join(stateDir, "stage-activity.jsonl");
19
- const contextWarningsPath = join(stateDir, "context-warnings.jsonl");
20
- const contextModePath = join(stateDir, "context-mode.json");
21
- const contextsDir = join(runtimeDir, "contexts");
22
- const sessionDigestPath = join(stateDir, "session-digest.md");
23
17
  const knowledgePath = join(runtimeDir, "knowledge.jsonl");
24
- const knowledgeDigestPath = join(stateDir, "knowledge-digest.md");
25
18
  const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
26
19
 
27
20
  function ensureRuntimeDirs() {
@@ -86,77 +79,8 @@ export default function cclawPlugin(ctx) {
86
79
  return text.split(/\\r?\\n/).slice(-maxLines);
87
80
  }
88
81
 
89
- async function readCheckpointSummary() {
90
- try {
91
- const raw = await readFileText(checkpointPath);
92
- if (!raw) return "";
93
- const cp = JSON.parse(raw);
94
- return \`Checkpoint: stage=\${cp.stage || "none"}, status=\${cp.status || "unknown"}, run=\${cp.runId || "none"}, at=\${cp.timestamp || "unknown"}\`;
95
- } catch {
96
- return "";
97
- }
98
- }
99
-
100
- async function readContextMode() {
101
- let mode = "default";
102
- try {
103
- const parsed = JSON.parse(await readFileText(contextModePath));
104
- if (parsed && typeof parsed.activeMode === "string" && parsed.activeMode.trim().length > 0) {
105
- mode = parsed.activeMode.trim();
106
- }
107
- } catch {
108
- // keep default
109
- }
110
- const guidePath = join(contextsDir, mode + ".md");
111
- const guide = existsSync(guidePath) ? "${RUNTIME_ROOT}/contexts/" + mode + ".md" : "";
112
- return { mode, guide };
113
- }
114
-
115
- async function readRecentActivity() {
116
- try {
117
- const lines = await readTailLines(activityPath, 5);
118
- if (lines.length === 0) return [];
119
- return lines
120
- .map((line) => {
121
- try {
122
- return JSON.parse(line);
123
- } catch {
124
- return null;
125
- }
126
- })
127
- .filter(Boolean)
128
- .map((entry) => \`- \${entry.ts || "unknown"} [\${entry.phase || "unknown"}] \${entry.tool || "unknown"} (stage=\${entry.stage || "unknown"}, run=\${entry.runId || "none"})\`);
129
- } catch {
130
- return [];
131
- }
132
- }
133
-
134
- async function readLatestContextWarning() {
135
- try {
136
- const line = (await readTailLines(contextWarningsPath, 1))[0];
137
- if (!line) return "";
138
- try {
139
- const parsed = JSON.parse(line);
140
- if (parsed && typeof parsed.note === "string") return parsed.note;
141
- } catch {
142
- // non-json fallback
143
- }
144
- return line;
145
- } catch {
146
- return "";
147
- }
148
- }
149
-
150
82
  async function readKnowledgeDigest() {
151
- const digest = (await readFileText(knowledgeDigestPath)).trim();
152
- if (!digest) {
153
- return readTailLines(knowledgePath, 12);
154
- }
155
- return digest
156
- .split(/\\r?\\n/)
157
- .map((line) => line.trim())
158
- .filter((line) => line.length > 0)
159
- .filter((line) => !line.startsWith("#"));
83
+ return readTailLines(knowledgePath, 12);
160
84
  }
161
85
 
162
86
  const BOOTSTRAP_MARKER = "<!-- cclaw-bootstrap-v1 -->";
@@ -167,30 +91,14 @@ export default function cclawPlugin(ctx) {
167
91
  BOOTSTRAP_MARKER,
168
92
  \`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: ${RUNTIME_ROOT}/artifacts/\`
169
93
  ];
170
- const contextMode = await readContextMode();
171
- parts.push(
172
- contextMode.guide
173
- ? \`Context mode: \${contextMode.mode} (guide: \${contextMode.guide})\`
174
- : \`Context mode: \${contextMode.mode}\`
175
- );
176
-
177
- const checkpoint = await readCheckpointSummary();
178
- if (checkpoint) parts.push(checkpoint);
179
-
180
- const digest = (await readFileText(sessionDigestPath)).trim();
181
- if (digest) parts.push("Last session:", digest);
182
94
 
183
- const activity = await readRecentActivity();
184
- if (activity.length > 0) parts.push("Recent stage activity:", ...activity);
185
95
 
186
- const warning = await readLatestContextWarning();
187
- if (warning) parts.push("Latest context warning:", warning);
188
96
 
189
97
  const knowledge = await readKnowledgeDigest();
190
98
  if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
191
99
 
192
100
  parts.push(
193
- "If you discover a non-obvious rule or pattern, append one strict-schema JSON line to .cclaw/knowledge.jsonl using type: rule, pattern, lesson, or compound."
101
+ "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. Direct JSONL append is only for explicit manual learnings operations."
194
102
  );
195
103
 
196
104
  const meta = (await readFileText(metaSkillPath)).trim();
@@ -203,13 +111,7 @@ export default function cclawPlugin(ctx) {
203
111
  let bootstrapRefreshPromise = null;
204
112
  const BOOTSTRAP_SOURCE_PATHS = [
205
113
  flowStatePath,
206
- checkpointPath,
207
- activityPath,
208
- contextWarningsPath,
209
- contextModePath,
210
- sessionDigestPath,
211
114
  knowledgePath,
212
- knowledgeDigestPath,
213
115
  metaSkillPath
214
116
  ];
215
117
 
@@ -684,7 +586,7 @@ export default function cclawPlugin(ctx) {
684
586
  await refreshBootstrapCache(true);
685
587
  }
686
588
  if (eventType === "session.idle") {
687
- await runHookScript("stop-checkpoint", { loop_count: 0 });
589
+ await runHookScript("stop-handoff", { loop_count: 0 });
688
590
  }
689
591
  },
690
592
  "tool.execute.before": async (input, output) => {
@@ -744,7 +646,7 @@ export default function cclawPlugin(ctx) {
744
646
  throw new Error(
745
647
  "cclaw " + failed + " blocked tool.execute.before.\\n" +
746
648
  "Reason: " + detail + "\\n" +
747
- "Diagnose: run \`cclaw doctor\` in project root.\\n" +
649
+ "Diagnose: run \`npx cclaw-cli doctor\` in project root.\\n" +
748
650
  "Bypass (temporary): export CCLAW_DISABLE=1 before starting OpenCode,\\n" +
749
651
  "or set \`strictness: advisory\` in .cclaw/config.yaml."
750
652
  );