cclaw-cli 0.1.1 → 0.2.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 (48) hide show
  1. package/dist/artifact-linter.d.ts +20 -0
  2. package/dist/artifact-linter.js +368 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +8 -2
  5. package/dist/config.d.ts +4 -4
  6. package/dist/config.js +56 -5
  7. package/dist/constants.d.ts +4 -4
  8. package/dist/constants.js +6 -3
  9. package/dist/content/autoplan.js +51 -4
  10. package/dist/content/contexts.d.ts +9 -0
  11. package/dist/content/contexts.js +65 -0
  12. package/dist/content/hooks.d.ts +6 -2
  13. package/dist/content/hooks.js +448 -16
  14. package/dist/content/meta-skill.js +26 -0
  15. package/dist/content/next-command.d.ts +9 -0
  16. package/dist/content/next-command.js +138 -0
  17. package/dist/content/observe.d.ts +5 -1
  18. package/dist/content/observe.js +506 -24
  19. package/dist/content/skills.js +126 -0
  20. package/dist/content/stage-schema.d.ts +7 -0
  21. package/dist/content/stage-schema.js +70 -12
  22. package/dist/content/subagents.js +33 -0
  23. package/dist/content/templates.d.ts +1 -0
  24. package/dist/content/templates.js +182 -77
  25. package/dist/content/utility-skills.d.ts +5 -1
  26. package/dist/content/utility-skills.js +208 -2
  27. package/dist/delegation.d.ts +21 -0
  28. package/dist/delegation.js +94 -0
  29. package/dist/doctor.d.ts +5 -1
  30. package/dist/doctor.js +274 -23
  31. package/dist/fs-utils.d.ts +10 -0
  32. package/dist/fs-utils.js +47 -0
  33. package/dist/gate-evidence.d.ts +26 -0
  34. package/dist/gate-evidence.js +157 -0
  35. package/dist/harness-adapters.js +2 -0
  36. package/dist/hook-schema.d.ts +6 -0
  37. package/dist/hook-schema.js +45 -0
  38. package/dist/hook-schemas/claude-hooks.v1.json +12 -0
  39. package/dist/hook-schemas/codex-hooks.v1.json +12 -0
  40. package/dist/hook-schemas/cursor-hooks.v1.json +15 -0
  41. package/dist/install.js +431 -16
  42. package/dist/policy.d.ts +5 -1
  43. package/dist/policy.js +52 -1
  44. package/dist/runs.js +8 -3
  45. package/dist/trace-matrix.d.ts +13 -0
  46. package/dist/trace-matrix.js +182 -0
  47. package/dist/types.d.ts +11 -1
  48. package/package.json +1 -1
@@ -7,6 +7,13 @@
7
7
  */
8
8
  import { RUNTIME_ROOT } from "../constants.js";
9
9
  import { META_SKILL_NAME } from "./meta-skill.js";
10
+ function shellLiteral(value) {
11
+ return value
12
+ .replace(/\\/gu, "\\\\")
13
+ .replace(/"/gu, '\\"')
14
+ .replace(/\$/gu, "\\$")
15
+ .replace(/`/gu, "\\`");
16
+ }
10
17
  const ESCAPE_FN = `escape_json() {
11
18
  local str="$1"
12
19
  str=\${str//\\\\/\\\\\\\\}
@@ -36,7 +43,11 @@ if [ -z "$ROOT" ]; then
36
43
  fi`;
37
44
  /** Shared bash preamble for generated hook scripts. */
38
45
  export const RUNTIME_SHELL_DETECT_ROOT = DETECT_ROOT;
39
- export function sessionStartScript() {
46
+ export function sessionStartScript(options = {}) {
47
+ const globalLearningsEnabled = options.globalLearningsEnabled === true;
48
+ const globalLearningsPath = options.globalLearningsPath?.trim() ?? "";
49
+ const globalLearningsPathLiteral = shellLiteral(globalLearningsPath);
50
+ const globalLearningsEnabledLiteral = globalLearningsEnabled ? "true" : "false";
40
51
  return `#!/usr/bin/env bash
41
52
  # cclaw session-start hook — generated by cclaw sync
42
53
  # Injects using-cclaw + flow status + run pointer + top learnings + checkpoint/activity summary.
@@ -49,13 +60,22 @@ CHECKPOINT_FILE="$ROOT/${RUNTIME_ROOT}/state/checkpoint.json"
49
60
  ACTIVITY_FILE="$ROOT/${RUNTIME_ROOT}/state/stage-activity.jsonl"
50
61
  SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
51
62
  CONTEXT_WARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/state/context-warnings.jsonl"
63
+ CONTEXT_MODE_FILE="$ROOT/${RUNTIME_ROOT}/state/context-mode.json"
64
+ CONTEXTS_DIR="$ROOT/${RUNTIME_ROOT}/contexts"
52
65
  LEARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/learnings.jsonl"
66
+ GLOBAL_LEARNINGS_ENABLED="${globalLearningsEnabledLiteral}"
67
+ GLOBAL_LEARNINGS_FILE="${globalLearningsPathLiteral}"
68
+ if [ "$GLOBAL_LEARNINGS_ENABLED" = "true" ] && [ -z "$GLOBAL_LEARNINGS_FILE" ]; then
69
+ GLOBAL_LEARNINGS_FILE="$HOME/.cclaw-global-learnings.jsonl"
70
+ fi
53
71
  META_SKILL="$ROOT/${RUNTIME_ROOT}/skills/${META_SKILL_NAME}/SKILL.md"
54
72
 
55
73
  # --- Read flow state ---
56
74
  STAGE="none"
57
75
  COMPLETED="0"
58
76
  ACTIVE_RUN="none"
77
+ ACTIVE_CONTEXT_MODE="default"
78
+ CONTEXT_MODE_NOTE=""
59
79
  if [ -f "$STATE_FILE" ]; then
60
80
  if command -v jq >/dev/null 2>&1; then
61
81
  STAGE=$(jq -r '.currentStage // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
@@ -124,6 +144,34 @@ PY
124
144
  fi
125
145
  fi
126
146
 
147
+ if [ -f "$CONTEXT_MODE_FILE" ]; then
148
+ if command -v jq >/dev/null 2>&1; then
149
+ ACTIVE_CONTEXT_MODE=$(jq -r '.activeMode // "default"' "$CONTEXT_MODE_FILE" 2>/dev/null || echo "default")
150
+ elif command -v python3 >/dev/null 2>&1; then
151
+ ACTIVE_CONTEXT_MODE=$(python3 - "$CONTEXT_MODE_FILE" <<'PY'
152
+ import json
153
+ import sys
154
+ mode = "default"
155
+ try:
156
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
157
+ data = json.load(fh)
158
+ value = data.get("activeMode")
159
+ if isinstance(value, str) and value:
160
+ mode = value
161
+ except Exception:
162
+ pass
163
+ print(mode)
164
+ PY
165
+ )
166
+ fi
167
+ fi
168
+
169
+ if [ -f "$CONTEXTS_DIR/$ACTIVE_CONTEXT_MODE.md" ]; then
170
+ CONTEXT_MODE_NOTE="Context mode: $ACTIVE_CONTEXT_MODE (guide: ${RUNTIME_ROOT}/contexts/$ACTIVE_CONTEXT_MODE.md)"
171
+ else
172
+ CONTEXT_MODE_NOTE="Context mode: $ACTIVE_CONTEXT_MODE"
173
+ fi
174
+
127
175
  # --- Checkpoint summary ---
128
176
  CHECKPOINT_SUMMARY=""
129
177
  if [ -f "$CHECKPOINT_FILE" ]; then
@@ -149,6 +197,12 @@ PY
149
197
  fi
150
198
  fi
151
199
 
200
+ SESSION_DIGEST=""
201
+ DIGEST_PATH="$ROOT/${RUNTIME_ROOT}/state/session-digest.md"
202
+ if [ -f "$DIGEST_PATH" ]; then
203
+ SESSION_DIGEST=$(cat "$DIGEST_PATH" 2>/dev/null || echo "")
204
+ fi
205
+
152
206
  # --- Recent stage activity summary ---
153
207
  ACTIVITY_SUMMARY=""
154
208
  if [ -f "$ACTIVITY_FILE" ] && [ -s "$ACTIVITY_FILE" ]; then
@@ -274,10 +328,28 @@ fi
274
328
 
275
329
  # --- Read top learnings with decay ---
276
330
  LEARNINGS_SUMMARY=""
277
- if [ -f "$LEARNINGS_FILE" ] && [ -s "$LEARNINGS_FILE" ]; then
331
+ LEARNING_LINES=""
332
+ for source_file in "$LEARNINGS_FILE" "$GLOBAL_LEARNINGS_FILE"; do
333
+ [ -n "$source_file" ] || continue
334
+ if [ "$source_file" = "$GLOBAL_LEARNINGS_FILE" ] && [ "$GLOBAL_LEARNINGS_ENABLED" != "true" ]; then
335
+ continue
336
+ fi
337
+ [ -f "$source_file" ] || continue
338
+ [ -s "$source_file" ] || continue
339
+ CHUNK=$(tail -n 30 "$source_file" 2>/dev/null || echo "")
340
+ [ -n "$CHUNK" ] || continue
341
+ if [ -n "$LEARNING_LINES" ]; then
342
+ LEARNING_LINES="$LEARNING_LINES
343
+ $CHUNK"
344
+ else
345
+ LEARNING_LINES="$CHUNK"
346
+ fi
347
+ done
348
+
349
+ if [ -n "$LEARNING_LINES" ]; then
278
350
  if command -v jq >/dev/null 2>&1; then
279
351
  NOW_EPOCH=$(date +%s 2>/dev/null || echo "0")
280
- LEARNINGS_SUMMARY=$(tail -n 30 "$LEARNINGS_FILE" 2>/dev/null | jq -r -s --arg now "$NOW_EPOCH" '
352
+ LEARNINGS_SUMMARY=$(printf '%s\n' "$LEARNING_LINES" | jq -r -s --arg now "$NOW_EPOCH" '
281
353
  [.[] | select(.key != null)]
282
354
  | group_by([.key, .type])
283
355
  | map(sort_by(.ts) | last)
@@ -295,16 +367,54 @@ if [ -f "$LEARNINGS_FILE" ] && [ -s "$LEARNINGS_FILE" ]; then
295
367
  | join("\\n")
296
368
  ' 2>/dev/null || echo "")
297
369
  else
298
- LEARNINGS_SUMMARY=$(tail -n 3 "$LEARNINGS_FILE" 2>/dev/null || echo "")
370
+ LEARNINGS_SUMMARY=$(printf '%s\n' "$LEARNING_LINES" | tail -n 3 2>/dev/null || echo "")
299
371
  fi
300
372
  fi
301
373
 
374
+ STAGE_LEARNINGS=""
375
+ STAGE_LEARNING_LINES=""
376
+ for source_file in "$LEARNINGS_FILE" "$GLOBAL_LEARNINGS_FILE"; do
377
+ [ -n "$source_file" ] || continue
378
+ if [ "$source_file" = "$GLOBAL_LEARNINGS_FILE" ] && [ "$GLOBAL_LEARNINGS_ENABLED" != "true" ]; then
379
+ continue
380
+ fi
381
+ [ -f "$source_file" ] || continue
382
+ [ -s "$source_file" ] || continue
383
+ CHUNK=$(tail -n 50 "$source_file" 2>/dev/null || echo "")
384
+ [ -n "$CHUNK" ] || continue
385
+ if [ -n "$STAGE_LEARNING_LINES" ]; then
386
+ STAGE_LEARNING_LINES="$STAGE_LEARNING_LINES
387
+ $CHUNK"
388
+ else
389
+ STAGE_LEARNING_LINES="$CHUNK"
390
+ fi
391
+ done
392
+
393
+ if [ "$STAGE" != "none" ] && [ -n "$STAGE_LEARNING_LINES" ] && command -v jq >/dev/null 2>&1; then
394
+ STAGE_LEARNINGS=$(printf '%s\n' "$STAGE_LEARNING_LINES" | jq -r -s --arg stage "$STAGE" '
395
+ [.[] | select(.key != null and (.key | contains($stage)))]
396
+ | sort_by(-.confidence)
397
+ | .[0:2]
398
+ | map("- [stage] " + .key + ": " + .insight)
399
+ | join("\\n")
400
+ ' 2>/dev/null || echo "")
401
+ fi
402
+
302
403
  # --- Build context message ---
303
404
  CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/9 completed, run=$ACTIVE_RUN). Active run artifacts: ${RUNTIME_ROOT}/runs/$ACTIVE_RUN/artifacts/"
405
+ if [ -n "$CONTEXT_MODE_NOTE" ]; then
406
+ CTX="$CTX
407
+ $CONTEXT_MODE_NOTE"
408
+ fi
304
409
  if [ -n "$CHECKPOINT_SUMMARY" ]; then
305
410
  CTX="$CTX
306
411
  $CHECKPOINT_SUMMARY"
307
412
  fi
413
+ if [ -n "$SESSION_DIGEST" ]; then
414
+ CTX="$CTX
415
+ Last session:
416
+ $SESSION_DIGEST"
417
+ fi
308
418
  if [ -n "$ACTIVITY_SUMMARY" ]; then
309
419
  CTX="$CTX
310
420
  Recent stage activity:
@@ -325,6 +435,11 @@ if [ -n "$LEARNINGS_SUMMARY" ]; then
325
435
  Top learnings:
326
436
  $LEARNINGS_SUMMARY"
327
437
  fi
438
+ if [ -n "$STAGE_LEARNINGS" ]; then
439
+ CTX="$CTX
440
+ Stage learnings ($STAGE):
441
+ $STAGE_LEARNINGS"
442
+ fi
328
443
  if [ -n "$META_CONTENT" ]; then
329
444
  CTX="$CTX
330
445
 
@@ -366,6 +481,7 @@ STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
366
481
  STATE_FILE="$STATE_DIR/flow-state.json"
367
482
  CHECKPOINT_FILE="$STATE_DIR/checkpoint.json"
368
483
  CHECKPOINT_TMP="$STATE_DIR/checkpoint.json.tmp.$$"
484
+ CHECKPOINT_LOCK_DIR="$STATE_DIR/.checkpoint.lock"
369
485
  STAGE="none"
370
486
  ACTIVE_RUN="none"
371
487
  LOOP_COUNT=""
@@ -445,7 +561,30 @@ CHECKPOINT_WRITTEN=0
445
561
  cleanup_checkpoint_tmp() {
446
562
  rm -f "$CHECKPOINT_TMP" 2>/dev/null || true
447
563
  }
448
- trap cleanup_checkpoint_tmp EXIT INT TERM
564
+
565
+ acquire_checkpoint_lock() {
566
+ local attempt=0
567
+ while ! mkdir "$CHECKPOINT_LOCK_DIR" 2>/dev/null; do
568
+ attempt=$((attempt + 1))
569
+ if [ "$attempt" -ge 200 ]; then
570
+ return 1
571
+ fi
572
+ sleep 0.02
573
+ done
574
+ return 0
575
+ }
576
+
577
+ release_checkpoint_lock() {
578
+ rmdir "$CHECKPOINT_LOCK_DIR" 2>/dev/null || true
579
+ }
580
+
581
+ cleanup_checkpoint_state() {
582
+ cleanup_checkpoint_tmp
583
+ release_checkpoint_lock
584
+ }
585
+ trap cleanup_checkpoint_state EXIT INT TERM
586
+
587
+ acquire_checkpoint_lock || exit 0
449
588
 
450
589
  if command -v jq >/dev/null 2>&1; then
451
590
  EXISTING_JSON="{}"
@@ -532,7 +671,7 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
532
671
  fi
533
672
  fi
534
673
 
535
- cleanup_checkpoint_tmp
674
+ cleanup_checkpoint_state
536
675
  trap - EXIT INT TERM
537
676
 
538
677
  CHECKPOINT_NOTE="Checkpoint updated at ${RUNTIME_ROOT}/state/checkpoint.json."
@@ -575,15 +714,39 @@ export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
575
714
  // ---------------------------------------------------------------------------
576
715
  // OpenCode plugin — JS module
577
716
  // ---------------------------------------------------------------------------
578
- export function opencodePluginJs() {
717
+ export function opencodePluginJs(options = {}) {
718
+ const globalLearningsEnabledLiteral = options.globalLearningsEnabled === true ? "true" : "false";
719
+ const globalLearningsPathLiteral = JSON.stringify(options.globalLearningsPath?.trim() ?? "");
579
720
  return `// cclaw OpenCode plugin — generated by cclaw sync
580
- import { closeSync, fstatSync, openSync, readFileSync, readSync } from "node:fs";
581
- import { join } from "node:path";
721
+ import {
722
+ appendFileSync,
723
+ closeSync,
724
+ existsSync,
725
+ fstatSync,
726
+ mkdirSync,
727
+ openSync,
728
+ readFileSync,
729
+ readSync
730
+ } from "node:fs";
731
+ import { homedir } from "node:os";
732
+ import { isAbsolute, join } from "node:path";
582
733
 
583
734
  export default function cclawPlugin(ctx) {
584
735
  const root = ctx.directory || process.cwd();
736
+ const runtimeDir = join(root, "${RUNTIME_ROOT}");
737
+ const stateDir = join(root, "${RUNTIME_ROOT}/state");
738
+ const observationsPath = join(root, "${RUNTIME_ROOT}/observations.jsonl");
739
+ const learningsPath = join(root, "${RUNTIME_ROOT}/learnings.jsonl");
585
740
  const checkpointPath = join(root, "${RUNTIME_ROOT}/state/checkpoint.json");
586
741
  const activityPath = join(root, "${RUNTIME_ROOT}/state/stage-activity.jsonl");
742
+ const suggestionMemoryPath = join(root, "${RUNTIME_ROOT}/state/suggestion-memory.json");
743
+ const contextWarningsPath = join(root, "${RUNTIME_ROOT}/state/context-warnings.jsonl");
744
+ const contextModePath = join(root, "${RUNTIME_ROOT}/state/context-mode.json");
745
+ const contextsDir = join(root, "${RUNTIME_ROOT}/contexts");
746
+ const sessionDigestPath = join(root, "${RUNTIME_ROOT}/state/session-digest.md");
747
+ const observeDisabledPath = join(root, "${RUNTIME_ROOT}/.observe-disabled");
748
+ const globalLearningsEnabled = ${globalLearningsEnabledLiteral};
749
+ const globalLearningsPathRaw = ${globalLearningsPathLiteral};
587
750
 
588
751
  function readFlowState() {
589
752
  try {
@@ -636,9 +799,42 @@ export default function cclawPlugin(ctx) {
636
799
  return raw.split(/\\r?\\n/).slice(-maxLines);
637
800
  }
638
801
 
802
+ function resolveGlobalLearningsPath(rawPath) {
803
+ if (!rawPath || typeof rawPath !== "string" || rawPath.trim().length === 0) {
804
+ return join(homedir(), ".cclaw-global-learnings.jsonl");
805
+ }
806
+ const trimmed = rawPath.trim();
807
+ if (trimmed.startsWith("~/")) {
808
+ return join(homedir(), trimmed.slice(2));
809
+ }
810
+ if (isAbsolute(trimmed)) {
811
+ return trimmed;
812
+ }
813
+ return join(root, trimmed);
814
+ }
815
+
816
+ function learningFiles() {
817
+ const files = [learningsPath];
818
+ if (globalLearningsEnabled) {
819
+ const globalPath = resolveGlobalLearningsPath(globalLearningsPathRaw);
820
+ if (globalPath !== learningsPath) {
821
+ files.push(globalPath);
822
+ }
823
+ }
824
+ return files;
825
+ }
826
+
827
+ function readLearningLines(maxLines, maxBytes) {
828
+ const combined = [];
829
+ for (const filePath of learningFiles()) {
830
+ combined.push(...readTailLines(filePath, maxLines, maxBytes));
831
+ }
832
+ return combined;
833
+ }
834
+
639
835
  function readTopLearnings() {
640
836
  try {
641
- const lines = readTailLines(join(root, "${RUNTIME_ROOT}/learnings.jsonl"), 30, 65536);
837
+ const lines = readLearningLines(30, 65536);
642
838
  if (lines.length === 0) return [];
643
839
  const entries = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
644
840
  const deduped = new Map();
@@ -670,6 +866,22 @@ export default function cclawPlugin(ctx) {
670
866
  }
671
867
  }
672
868
 
869
+ function readContextMode() {
870
+ let mode = "default";
871
+ try {
872
+ const raw = readFileSync(contextModePath, "utf8");
873
+ const parsed = JSON.parse(raw);
874
+ if (parsed && typeof parsed.activeMode === "string" && parsed.activeMode.trim().length > 0) {
875
+ mode = parsed.activeMode.trim();
876
+ }
877
+ } catch {
878
+ // use default mode
879
+ }
880
+ const guidePath = join(contextsDir, mode + ".md");
881
+ const guide = existsSync(guidePath) ? "${RUNTIME_ROOT}/contexts/" + mode + ".md" : "";
882
+ return { mode, guide };
883
+ }
884
+
673
885
  function readRecentActivity() {
674
886
  try {
675
887
  const lines = readTailLines(activityPath, 5, 32768);
@@ -686,19 +898,123 @@ export default function cclawPlugin(ctx) {
686
898
  }
687
899
  }
688
900
 
901
+ function readSessionDigest() {
902
+ try {
903
+ const digest = readFileSync(sessionDigestPath, "utf8").trim();
904
+ return digest;
905
+ } catch {
906
+ return "";
907
+ }
908
+ }
909
+
910
+ function readLatestContextWarning() {
911
+ try {
912
+ const line = readTailLines(contextWarningsPath, 1, 8192)[0];
913
+ if (!line) return "";
914
+ try {
915
+ const parsed = JSON.parse(line);
916
+ if (parsed && typeof parsed.note === "string") {
917
+ return parsed.note;
918
+ }
919
+ } catch {
920
+ // non-json line fallback
921
+ }
922
+ return line;
923
+ } catch {
924
+ return "";
925
+ }
926
+ }
927
+
928
+ function stageSuggestionFor(stage) {
929
+ const map = {
930
+ brainstorm: "Suggestion: list 2-3 alternatives and ask a single focused clarifying question before direction lock.",
931
+ scope: "Suggestion: lock explicit in-scope/out-of-scope boundaries and choose one scope mode.",
932
+ design: "Suggestion: map failure modes per new codepath and confirm architecture boundaries before moving forward.",
933
+ spec: "Suggestion: ensure every acceptance criterion is measurable and mapped to a concrete test.",
934
+ plan: "Suggestion: group tasks into dependency waves and keep WAIT_FOR_CONFIRM pending until approval.",
935
+ test: "Suggestion: RED only in this stage — capture failing output for each selected slice.",
936
+ build: "Suggestion: apply minimal GREEN change, run full suite, then document REFACTOR notes.",
937
+ review: "Suggestion: run Layer 1 before Layer 2 and reconcile findings into 07-review-army.json.",
938
+ ship: "Suggestion: verify preflight + rollback plan before selecting exactly one finalization mode."
939
+ };
940
+ return map[stage] || "";
941
+ }
942
+
943
+ function readStageSuggestion(stage) {
944
+ let memory = { enabled: true, mutedStages: [] };
945
+ try {
946
+ const parsed = JSON.parse(readFileSync(suggestionMemoryPath, "utf8"));
947
+ if (parsed && typeof parsed === "object") {
948
+ memory = {
949
+ enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : true,
950
+ mutedStages: Array.isArray(parsed.mutedStages)
951
+ ? parsed.mutedStages.filter((item) => typeof item === "string")
952
+ : []
953
+ };
954
+ }
955
+ } catch {
956
+ // use defaults
957
+ }
958
+ if (!memory.enabled || memory.mutedStages.includes(stage)) {
959
+ return "";
960
+ }
961
+ return stageSuggestionFor(stage);
962
+ }
963
+
964
+ function readStageLearnings(stage) {
965
+ try {
966
+ const lines = readLearningLines(60, 131072);
967
+ const entries = lines
968
+ .map((line) => {
969
+ try {
970
+ return JSON.parse(line);
971
+ } catch {
972
+ return null;
973
+ }
974
+ })
975
+ .filter(Boolean);
976
+ if (entries.length === 0) return [];
977
+ return entries
978
+ .filter((entry) => typeof entry.key === "string" && entry.key.toLowerCase().includes(stage.toLowerCase()))
979
+ .sort((a, b) => (Number(b.confidence) || 0) - (Number(a.confidence) || 0))
980
+ .slice(0, 2)
981
+ .map((entry) => "- [stage] " + entry.key + ": " + entry.insight);
982
+ } catch {
983
+ return [];
984
+ }
985
+ }
986
+
689
987
  function buildBootstrap() {
690
988
  const { stage, completed, activeRunId } = readFlowState();
691
989
  const meta = readMetaSkill();
692
990
  const learnings = readTopLearnings();
693
991
  const checkpoint = readCheckpointSummary();
694
992
  const activity = readRecentActivity();
993
+ const digest = readSessionDigest();
994
+ const warning = readLatestContextWarning();
995
+ const contextMode = readContextMode();
996
+ const stageSuggestion = readStageSuggestion(stage);
997
+ const stageLearnings = readStageLearnings(stage);
695
998
  const parts = [\`cclaw loaded. Flow: stage=\${stage} (\${completed}/9 completed, run=\${activeRunId}). Active run artifacts: ${RUNTIME_ROOT}/runs/\${activeRunId}/artifacts/\`];
999
+ parts.push(
1000
+ contextMode.guide
1001
+ ? \`Context mode: \${contextMode.mode} (guide: \${contextMode.guide})\`
1002
+ : \`Context mode: \${contextMode.mode}\`
1003
+ );
696
1004
  if (checkpoint) parts.push(checkpoint);
1005
+ if (digest) parts.push("Last session:", digest);
697
1006
  if (activity.length > 0) parts.push("Recent stage activity:", ...activity);
1007
+ if (warning) parts.push("Latest context warning:", warning);
1008
+ if (stageSuggestion) {
1009
+ parts.push(stageSuggestion, "To disable suggestions persistently set .cclaw/state/suggestion-memory.json -> enabled=false.");
1010
+ }
698
1011
  if (learnings.length > 0) {
699
1012
  parts.push("Top learnings:");
700
1013
  for (const l of learnings) parts.push(\`- \${l.key} (conf \${l.effective_confidence}): \${l.insight}\`);
701
1014
  }
1015
+ if (stageLearnings.length > 0) {
1016
+ parts.push(\`Stage learnings (\${stage}):\`, ...stageLearnings);
1017
+ }
702
1018
  if (meta) parts.push("", meta);
703
1019
  return parts.join("\\n");
704
1020
  }
@@ -707,11 +1023,127 @@ export default function cclawPlugin(ctx) {
707
1023
  console.log(buildBootstrap());
708
1024
  }
709
1025
 
1026
+ function observationEnabled() {
1027
+ return !existsSync(observeDisabledPath);
1028
+ }
1029
+
1030
+ function ensureRuntimeDirs() {
1031
+ try {
1032
+ mkdirSync(runtimeDir, { recursive: true });
1033
+ } catch {
1034
+ // ignore
1035
+ }
1036
+ try {
1037
+ mkdirSync(stateDir, { recursive: true });
1038
+ } catch {
1039
+ // ignore
1040
+ }
1041
+ }
1042
+
1043
+ async function runHookScript(scriptFileName, payload = {}) {
1044
+ const { spawnSync } = await import("node:child_process");
1045
+ const scriptPath = join(root, "${RUNTIME_ROOT}/hooks/" + scriptFileName);
1046
+ const input = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
1047
+ try {
1048
+ spawnSync("bash", [scriptPath], {
1049
+ cwd: root,
1050
+ timeout: 20000,
1051
+ stdio: ["pipe", "ignore", "ignore"],
1052
+ input
1053
+ });
1054
+ } catch {
1055
+ // advisory-only runtime path
1056
+ }
1057
+ }
1058
+
1059
+ function toText(value) {
1060
+ if (typeof value === "string") return value;
1061
+ try {
1062
+ return JSON.stringify(value);
1063
+ } catch {
1064
+ return value == null ? "" : String(value);
1065
+ }
1066
+ }
1067
+
1068
+ function truncateText(value, maxLen = 2000) {
1069
+ const text = toText(value);
1070
+ return text.length > maxLen ? text.slice(0, maxLen) : text;
1071
+ }
1072
+
1073
+ function extractToolName(payload) {
1074
+ const candidates = [
1075
+ payload?.tool,
1076
+ payload?.toolName,
1077
+ payload?.tool_name,
1078
+ payload?.name,
1079
+ payload?.id,
1080
+ payload?.command,
1081
+ payload?.tool?.name,
1082
+ payload?.tool?.id,
1083
+ ];
1084
+ for (const value of candidates) {
1085
+ if (typeof value === "string" && value.trim()) return value.trim();
1086
+ }
1087
+ return "unknown";
1088
+ }
1089
+
1090
+ function appendJsonLine(filePath, value) {
1091
+ try {
1092
+ appendFileSync(filePath, JSON.stringify(value) + "\\n", "utf8");
1093
+ } catch {
1094
+ // ignore
1095
+ }
1096
+ }
1097
+
1098
+ function recordToolEvent(phase, payload) {
1099
+ if (!observationEnabled()) return;
1100
+ const flow = readFlowState();
1101
+ const ts = new Date().toISOString();
1102
+ const tool = extractToolName(payload);
1103
+ const event = phase === "pre" ? "tool_start" : "tool_complete";
1104
+ ensureRuntimeDirs();
1105
+ appendJsonLine(observationsPath, {
1106
+ ts,
1107
+ event,
1108
+ tool,
1109
+ phase,
1110
+ stage: flow.stage,
1111
+ runId: flow.activeRunId,
1112
+ data: truncateText(payload)
1113
+ });
1114
+ appendJsonLine(activityPath, {
1115
+ ts,
1116
+ event,
1117
+ tool,
1118
+ phase,
1119
+ stage: flow.stage,
1120
+ runId: flow.activeRunId
1121
+ });
1122
+ }
1123
+
710
1124
  return {
711
- "session.created": emitBootstrap,
712
- "session.resumed": emitBootstrap,
713
- "session.compacted": emitBootstrap,
714
- "session.cleared": emitBootstrap,
1125
+ event: async (name, data) => {
1126
+ if (name === "session.created" || name === "session.resumed" || name === "session.compacted" || name === "session.cleared") {
1127
+ emitBootstrap();
1128
+ }
1129
+ if (name === "session.updated") {
1130
+ // no-op: tracked via activity log
1131
+ }
1132
+ if (name === "session.idle") {
1133
+ if (!observationEnabled()) return;
1134
+ await runHookScript("summarize-observations.sh");
1135
+ await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
1136
+ }
1137
+ if (name === "tool.execute.before") {
1138
+ await runHookScript("prompt-guard.sh", data ?? {});
1139
+ await runHookScript("workflow-guard.sh", data ?? {});
1140
+ recordToolEvent("pre", data);
1141
+ }
1142
+ if (name === "tool.execute.after") {
1143
+ await runHookScript("context-monitor.sh", data ?? {});
1144
+ recordToolEvent("post", data);
1145
+ }
1146
+ },
715
1147
  "experimental.chat.system.transform": (payload) => {
716
1148
  const bootstrap = buildBootstrap();
717
1149
  if (typeof payload === "string") {
@@ -735,14 +1167,14 @@ export function hooksAgentsMdBlock() {
735
1167
 
736
1168
  Cclaw generates real hook integrations across harnesses:
737
1169
  - **Claude/Cursor/Codex:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
738
- - **OpenCode:** session lifecycle + system transform rehydration in plugin
1170
+ - **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/suggestions/stage learnings)
739
1171
 
740
1172
  | Harness | Hook file | Events |
741
1173
  |---------|-----------|--------|
742
1174
  | Claude Code | \`.claude/hooks/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
743
1175
  | Cursor | \`.cursor/hooks.json\` | sessionStart/sessionResume/sessionClear/sessionCompact, preToolUse, postToolUse, stop |
744
1176
  | Codex | \`.codex/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
745
- | OpenCode | \`${RUNTIME_ROOT}/hooks/opencode-plugin.mjs\` | session.created/resumed/compacted/cleared + system transform |
1177
+ | OpenCode | \`${RUNTIME_ROOT}/hooks/opencode-plugin.mjs\` | session.created/updated/resumed/cleared/compacted/idle, tool.execute.before/after, system transform |
746
1178
 
747
1179
  Hook state files:
748
1180
  - \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
@@ -91,9 +91,35 @@ These skills live in \`.cclaw/skills/\` but have no slash commands. They activat
91
91
  | Performance | \`performance/\` | During review; when code is perf-sensitive (DB queries, rendering, bundle size) |
92
92
  | CI/CD | \`ci-cd/\` | During ship; when pipeline config or deployment is involved |
93
93
  | Documentation | \`docs/\` | During ship; when adding public APIs, architecture changes, or breaking changes |
94
+ | Executing Plans | \`executing-plans/\` | After plan approval during sustained task execution waves |
95
+ | Context Engineering | \`context-engineering/\` | When work mode changes (execution, review, incident) or context pressure rises |
96
+ | Source-Driven Development | \`source-driven-development/\` | Before introducing new patterns/helpers; when deciding reuse vs net-new structure |
97
+ | Frontend Accessibility | \`frontend-accessibility/\` | For user-facing UI changes and accessibility quality gates |
94
98
 
95
99
  **Activation rule:** When a contextual skill applies, read its SKILL.md and follow it as a supplementary lens alongside the current stage. Do not skip the stage workflow — the contextual skill adds depth, not a detour.
96
100
 
101
+ ## Progressive Disclosure (Depth / See Also)
102
+
103
+ Use this loading order to keep context lean while preserving depth:
104
+
105
+ 1. Start with the active stage skill in \`.cclaw/skills/<stage>/SKILL.md\`.
106
+ 2. Load exactly one contextual utility skill only if its trigger appears.
107
+ 3. Open command contract (\`.cclaw/commands/<stage>.md\`) only for gate/handoff wording.
108
+ 4. Expand to adjacent stage skills only when transition ambiguity exists.
109
+
110
+ ### Depth triggers
111
+ - **Flaky/failing tests:** \`.cclaw/skills/debugging/SKILL.md\`
112
+ - **Security-sensitive change:** \`.cclaw/skills/security/SKILL.md\`
113
+ - **Performance risk:** \`.cclaw/skills/performance/SKILL.md\`
114
+ - **Release/deploy concerns:** \`.cclaw/skills/ci-cd/SKILL.md\`
115
+ - **Public API/docs impact:** \`.cclaw/skills/docs/SKILL.md\`
116
+ - **Specialist delegation needed:** \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`
117
+
118
+ ### See also
119
+ - \`.cclaw/skills/session/SKILL.md\` for session start/stop/resume behavior
120
+ - \`.cclaw/skills/learnings/SKILL.md\` for durable memory capture and reuse
121
+ - \`.cclaw/skills/autoplan/SKILL.md\` when user requests multi-stage orchestration
122
+
97
123
  ## Decision Protocol
98
124
 
99
125
  When a stage requires user input (approval, choice, direction), use this structured pattern:
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Command contract for /cc-next - agent reads flow state, evaluates gates, advances or reports blockers.
3
+ * Wire into install (write to `.cclaw/commands/next.md` + harness shim `cc-next.md`) in a follow-up; not invoked by CLI.
4
+ */
5
+ export declare function nextCommandContract(): string;
6
+ /**
7
+ * Skill body for /cc-next - flow logic and continue semantics.
8
+ */
9
+ export declare function nextCommandSkillMarkdown(): string;