cclaw-cli 0.5.4 → 0.5.5
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 +8 -11
- package/dist/artifact-linter.js +3 -13
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +12 -1
- package/dist/config.js +0 -19
- package/dist/content/contracts.js +7 -12
- package/dist/content/examples.js +34 -34
- package/dist/content/hooks.d.ts +4 -6
- package/dist/content/hooks.js +104 -523
- package/dist/content/learnings.js +55 -203
- package/dist/content/meta-skill.js +8 -11
- package/dist/content/next-command.js +3 -3
- package/dist/content/observe.d.ts +4 -7
- package/dist/content/observe.js +10 -48
- package/dist/content/session-hooks.js +8 -8
- package/dist/content/skills.js +9 -16
- package/dist/content/stage-schema.js +80 -121
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +27 -48
- package/dist/delegation.js +7 -7
- package/dist/doctor.js +16 -47
- package/dist/flow-state.js +1 -1
- package/dist/harness-adapters.js +1 -1
- package/dist/install.js +22 -48
- package/dist/policy.js +1 -4
- package/dist/runs.d.ts +9 -9
- package/dist/runs.js +107 -320
- package/dist/trace-matrix.js +8 -18
- package/dist/types.d.ts +0 -4
- package/package.json +1 -1
- package/dist/learnings-summarizer.d.ts +0 -25
- package/dist/learnings-summarizer.js +0 -201
package/dist/content/hooks.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hook generators for all supported harnesses.
|
|
3
3
|
*
|
|
4
|
-
* SessionStart: injects using-cclaw + flow state +
|
|
4
|
+
* SessionStart: injects using-cclaw + flow state + knowledge + checkpoint/activity summary.
|
|
5
5
|
* Stop: writes checkpoint.json and reminds about flow consistency.
|
|
6
|
-
*
|
|
6
|
+
* Harness hook JSON wiring is generated in observe.ts.
|
|
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
|
-
}
|
|
17
10
|
const ESCAPE_FN = `escape_json() {
|
|
18
11
|
local str="$1"
|
|
19
12
|
str=\${str//\\\\/\\\\\\\\}
|
|
@@ -43,14 +36,10 @@ if [ -z "$ROOT" ]; then
|
|
|
43
36
|
fi`;
|
|
44
37
|
/** Shared bash preamble for generated hook scripts. */
|
|
45
38
|
export const RUNTIME_SHELL_DETECT_ROOT = DETECT_ROOT;
|
|
46
|
-
export function sessionStartScript(
|
|
47
|
-
const globalLearningsEnabled = options.globalLearningsEnabled === true;
|
|
48
|
-
const globalLearningsPath = options.globalLearningsPath?.trim() ?? "";
|
|
49
|
-
const globalLearningsPathLiteral = shellLiteral(globalLearningsPath);
|
|
50
|
-
const globalLearningsEnabledLiteral = globalLearningsEnabled ? "true" : "false";
|
|
39
|
+
export function sessionStartScript(_options = {}) {
|
|
51
40
|
return `#!/usr/bin/env bash
|
|
52
41
|
# cclaw session-start hook — generated by cclaw sync
|
|
53
|
-
# Injects using-cclaw + flow status +
|
|
42
|
+
# Injects using-cclaw + flow status + active artifacts + knowledge snapshot + checkpoint/activity summary.
|
|
54
43
|
set -euo pipefail
|
|
55
44
|
|
|
56
45
|
${DETECT_ROOT}
|
|
@@ -62,12 +51,7 @@ SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
|
|
|
62
51
|
CONTEXT_WARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/state/context-warnings.jsonl"
|
|
63
52
|
CONTEXT_MODE_FILE="$ROOT/${RUNTIME_ROOT}/state/context-mode.json"
|
|
64
53
|
CONTEXTS_DIR="$ROOT/${RUNTIME_ROOT}/contexts"
|
|
65
|
-
|
|
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
|
|
54
|
+
KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.md"
|
|
71
55
|
META_SKILL="$ROOT/${RUNTIME_ROOT}/skills/${META_SKILL_NAME}/SKILL.md"
|
|
72
56
|
|
|
73
57
|
# --- Read flow state ---
|
|
@@ -325,82 +309,14 @@ if [ -f "$META_SKILL" ]; then
|
|
|
325
309
|
META_CONTENT=$(cat "$META_SKILL" 2>/dev/null || echo "")
|
|
326
310
|
fi
|
|
327
311
|
|
|
328
|
-
# ---
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
[ -n "$source_file" ] || continue
|
|
333
|
-
if [ "$source_file" = "$GLOBAL_LEARNINGS_FILE" ] && [ "$GLOBAL_LEARNINGS_ENABLED" != "true" ]; then
|
|
334
|
-
continue
|
|
335
|
-
fi
|
|
336
|
-
[ -f "$source_file" ] || continue
|
|
337
|
-
[ -s "$source_file" ] || continue
|
|
338
|
-
CHUNK=$(tail -n 30 "$source_file" 2>/dev/null || echo "")
|
|
339
|
-
[ -n "$CHUNK" ] || continue
|
|
340
|
-
if [ -n "$LEARNING_LINES" ]; then
|
|
341
|
-
LEARNING_LINES="$LEARNING_LINES
|
|
342
|
-
$CHUNK"
|
|
343
|
-
else
|
|
344
|
-
LEARNING_LINES="$CHUNK"
|
|
345
|
-
fi
|
|
346
|
-
done
|
|
347
|
-
|
|
348
|
-
if [ -n "$LEARNING_LINES" ]; then
|
|
349
|
-
if command -v jq >/dev/null 2>&1; then
|
|
350
|
-
NOW_EPOCH=$(date +%s 2>/dev/null || echo "0")
|
|
351
|
-
LEARNINGS_SUMMARY=$(printf '%s\n' "$LEARNING_LINES" | jq -r -s --arg now "$NOW_EPOCH" '
|
|
352
|
-
[.[] | select(.key != null)]
|
|
353
|
-
| group_by([.key, .type])
|
|
354
|
-
| map(sort_by(.ts) | last)
|
|
355
|
-
| map(
|
|
356
|
-
. as $e |
|
|
357
|
-
(if $e.source == "user-stated" then 0
|
|
358
|
-
else (try ((($now|tonumber) - ($e.ts // "" | fromdateiso8601)) / 2592000 | floor) catch 0)
|
|
359
|
-
end) as $months |
|
|
360
|
-
($e.confidence - $months) as $eff |
|
|
361
|
-
. + {effective_confidence: (if $eff < 0 then 0 else $eff end)}
|
|
362
|
-
)
|
|
363
|
-
| sort_by(-.effective_confidence)
|
|
364
|
-
| .[0:3]
|
|
365
|
-
| map("- " + .key + " (conf " + (.effective_confidence|tostring) + "): " + .insight)
|
|
366
|
-
| join("\\n")
|
|
367
|
-
' 2>/dev/null || echo "")
|
|
368
|
-
else
|
|
369
|
-
LEARNINGS_SUMMARY=$(printf '%s\n' "$LEARNING_LINES" | tail -n 3 2>/dev/null || echo "")
|
|
370
|
-
fi
|
|
371
|
-
fi
|
|
372
|
-
|
|
373
|
-
STAGE_LEARNINGS=""
|
|
374
|
-
STAGE_LEARNING_LINES=""
|
|
375
|
-
for source_file in "$LEARNINGS_FILE" "$GLOBAL_LEARNINGS_FILE"; do
|
|
376
|
-
[ -n "$source_file" ] || continue
|
|
377
|
-
if [ "$source_file" = "$GLOBAL_LEARNINGS_FILE" ] && [ "$GLOBAL_LEARNINGS_ENABLED" != "true" ]; then
|
|
378
|
-
continue
|
|
379
|
-
fi
|
|
380
|
-
[ -f "$source_file" ] || continue
|
|
381
|
-
[ -s "$source_file" ] || continue
|
|
382
|
-
CHUNK=$(tail -n 50 "$source_file" 2>/dev/null || echo "")
|
|
383
|
-
[ -n "$CHUNK" ] || continue
|
|
384
|
-
if [ -n "$STAGE_LEARNING_LINES" ]; then
|
|
385
|
-
STAGE_LEARNING_LINES="$STAGE_LEARNING_LINES
|
|
386
|
-
$CHUNK"
|
|
387
|
-
else
|
|
388
|
-
STAGE_LEARNING_LINES="$CHUNK"
|
|
389
|
-
fi
|
|
390
|
-
done
|
|
391
|
-
|
|
392
|
-
if [ "$STAGE" != "none" ] && [ -n "$STAGE_LEARNING_LINES" ] && command -v jq >/dev/null 2>&1; then
|
|
393
|
-
STAGE_LEARNINGS=$(printf '%s\n' "$STAGE_LEARNING_LINES" | jq -r -s --arg stage "$STAGE" '
|
|
394
|
-
[.[] | select(.key != null and (.key | contains($stage)))]
|
|
395
|
-
| sort_by(-.confidence)
|
|
396
|
-
| .[0:2]
|
|
397
|
-
| map("- [stage] " + .key + ": " + .insight)
|
|
398
|
-
| join("\\n")
|
|
399
|
-
' 2>/dev/null || echo "")
|
|
312
|
+
# --- Load knowledge snapshot (append-only markdown) ---
|
|
313
|
+
KNOWLEDGE_SUMMARY=""
|
|
314
|
+
if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
|
|
315
|
+
KNOWLEDGE_SUMMARY=$(tail -n 30 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
|
|
400
316
|
fi
|
|
401
317
|
|
|
402
318
|
# --- Build context message ---
|
|
403
|
-
CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN). Active
|
|
319
|
+
CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN). Active artifacts: ${RUNTIME_ROOT}/artifacts/"
|
|
404
320
|
if [ -n "$CONTEXT_MODE_NOTE" ]; then
|
|
405
321
|
CTX="$CTX
|
|
406
322
|
$CONTEXT_MODE_NOTE"
|
|
@@ -429,15 +345,10 @@ if [ -n "$STAGE_SUGGESTION" ]; then
|
|
|
429
345
|
$STAGE_SUGGESTION
|
|
430
346
|
To disable suggestions persistently set ${RUNTIME_ROOT}/state/suggestion-memory.json -> enabled=false."
|
|
431
347
|
fi
|
|
432
|
-
if [ -n "$
|
|
433
|
-
CTX="$CTX
|
|
434
|
-
Top learnings:
|
|
435
|
-
$LEARNINGS_SUMMARY"
|
|
436
|
-
fi
|
|
437
|
-
if [ -n "$STAGE_LEARNINGS" ]; then
|
|
348
|
+
if [ -n "$KNOWLEDGE_SUMMARY" ]; then
|
|
438
349
|
CTX="$CTX
|
|
439
|
-
|
|
440
|
-
$
|
|
350
|
+
Knowledge snapshot (latest entries):
|
|
351
|
+
$KNOWLEDGE_SUMMARY"
|
|
441
352
|
fi
|
|
442
353
|
if [ -n "$META_CONTENT" ]; then
|
|
443
354
|
CTX="$CTX
|
|
@@ -678,98 +589,11 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
|
|
|
678
589
|
CHECKPOINT_NOTE="Checkpoint update failed. Review ${RUNTIME_ROOT}/state/checkpoint.json manually."
|
|
679
590
|
fi
|
|
680
591
|
|
|
681
|
-
RUN_SYNC_NOTE="Run metadata sync
|
|
682
|
-
if [ -n "$ACTIVE_RUN" ] && [ "$ACTIVE_RUN" != "none" ] && [ "$ACTIVE_RUN" != "run-pending" ]; then
|
|
683
|
-
RUN_DIR="$ROOT/${RUNTIME_ROOT}/runs/$ACTIVE_RUN"
|
|
684
|
-
RUN_META_FILE="$RUN_DIR/run.json"
|
|
685
|
-
RUN_HANDOFF_FILE="$RUN_DIR/handoff.md"
|
|
686
|
-
if [ -f "$RUN_META_FILE" ] && [ -f "$STATE_FILE" ] && command -v python3 >/dev/null 2>&1; then
|
|
687
|
-
if python3 - "$STATE_FILE" "$RUN_META_FILE" "$RUN_HANDOFF_FILE" "$ACTIVE_RUN" "$TS" <<'PY'
|
|
688
|
-
import json
|
|
689
|
-
import sys
|
|
690
|
-
from pathlib import Path
|
|
691
|
-
|
|
692
|
-
state_path, run_meta_path, handoff_path, active_run, timestamp = sys.argv[1:6]
|
|
693
|
-
|
|
694
|
-
try:
|
|
695
|
-
state = json.loads(Path(state_path).read_text(encoding="utf-8"))
|
|
696
|
-
except Exception:
|
|
697
|
-
raise SystemExit(1)
|
|
698
|
-
|
|
699
|
-
try:
|
|
700
|
-
meta = json.loads(Path(run_meta_path).read_text(encoding="utf-8"))
|
|
701
|
-
except Exception:
|
|
702
|
-
raise SystemExit(1)
|
|
703
|
-
|
|
704
|
-
if not isinstance(state, dict) or not isinstance(meta, dict):
|
|
705
|
-
raise SystemExit(1)
|
|
706
|
-
|
|
707
|
-
completed = state.get("completedStages")
|
|
708
|
-
if not isinstance(completed, list):
|
|
709
|
-
completed = []
|
|
710
|
-
completed = [stage for stage in completed if isinstance(stage, str)]
|
|
711
|
-
|
|
712
|
-
guard_evidence = state.get("guardEvidence")
|
|
713
|
-
if not isinstance(guard_evidence, dict):
|
|
714
|
-
guard_evidence = {}
|
|
715
|
-
guard_evidence = {k: v for k, v in guard_evidence.items() if isinstance(k, str) and isinstance(v, str)}
|
|
716
|
-
|
|
717
|
-
stage_gate_catalog = state.get("stageGateCatalog")
|
|
718
|
-
if not isinstance(stage_gate_catalog, dict):
|
|
719
|
-
stage_gate_catalog = {}
|
|
720
|
-
|
|
721
|
-
snapshot = {
|
|
722
|
-
"currentStage": state.get("currentStage") if isinstance(state.get("currentStage"), str) else "brainstorm",
|
|
723
|
-
"completedStages": completed,
|
|
724
|
-
"guardEvidence": guard_evidence,
|
|
725
|
-
"stageGateCatalog": stage_gate_catalog,
|
|
726
|
-
}
|
|
727
|
-
meta["stateSnapshot"] = snapshot
|
|
728
|
-
Path(run_meta_path).write_text(json.dumps(meta, indent=2) + "\\n", encoding="utf-8")
|
|
729
|
-
|
|
730
|
-
title = meta.get("title") if isinstance(meta.get("title"), str) and meta.get("title") else active_run
|
|
731
|
-
created_at = meta.get("createdAt") if isinstance(meta.get("createdAt"), str) and meta.get("createdAt") else "unknown"
|
|
732
|
-
archived_at = meta.get("archivedAt") if isinstance(meta.get("archivedAt"), str) and meta.get("archivedAt") else None
|
|
733
|
-
completed_display = ", ".join(completed) if completed else "(none)"
|
|
734
|
-
|
|
735
|
-
handoff_lines = [
|
|
736
|
-
"# Run Handoff",
|
|
737
|
-
"",
|
|
738
|
-
f"- ID: {active_run}",
|
|
739
|
-
f"- Title: {title}",
|
|
740
|
-
f"- Created: {created_at}",
|
|
741
|
-
f"- Archived: {archived_at if archived_at else 'active'}",
|
|
742
|
-
"",
|
|
743
|
-
"## Flow Snapshot",
|
|
744
|
-
f"- Active stage: {snapshot['currentStage']}",
|
|
745
|
-
f"- Completed stages: {completed_display}",
|
|
746
|
-
f"- Active run ID in flow-state: {state.get('activeRunId') if isinstance(state.get('activeRunId'), str) else active_run}",
|
|
747
|
-
"",
|
|
748
|
-
"## Paths",
|
|
749
|
-
f"- Active artifacts: ${RUNTIME_ROOT}/artifacts/",
|
|
750
|
-
f"- Run artifacts snapshot: ${RUNTIME_ROOT}/runs/{active_run}/artifacts/",
|
|
751
|
-
f"- Flow state: ${RUNTIME_ROOT}/state/flow-state.json",
|
|
752
|
-
"",
|
|
753
|
-
"## Resume",
|
|
754
|
-
"1. Open ${RUNTIME_ROOT}/state/flow-state.json and verify current stage.",
|
|
755
|
-
"2. Review ${RUNTIME_ROOT}/artifacts/ for the latest working artifacts.",
|
|
756
|
-
"3. Continue using /cc or /cc-next from the current stage.",
|
|
757
|
-
"",
|
|
758
|
-
f"- Last updated: {timestamp}",
|
|
759
|
-
]
|
|
760
|
-
Path(handoff_path).write_text("\\n".join(handoff_lines) + "\\n", encoding="utf-8")
|
|
761
|
-
PY
|
|
762
|
-
then
|
|
763
|
-
RUN_SYNC_NOTE="Run metadata synchronized for $ACTIVE_RUN."
|
|
764
|
-
else
|
|
765
|
-
RUN_SYNC_NOTE="Run metadata sync failed for $ACTIVE_RUN."
|
|
766
|
-
fi
|
|
767
|
-
fi
|
|
768
|
-
fi
|
|
592
|
+
RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until cclaw archive."
|
|
769
593
|
|
|
770
594
|
# --- Escape for JSON ---
|
|
771
595
|
${ESCAPE_FN}
|
|
772
|
-
MSG=$(escape_json "Cclaw: session ending (stage=$STAGE, run=$ACTIVE_RUN). $CHECKPOINT_NOTE $RUN_SYNC_NOTE Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match
|
|
596
|
+
MSG=$(escape_json "Cclaw: session ending (stage=$STAGE, run=$ACTIVE_RUN). $CHECKPOINT_NOTE $RUN_SYNC_NOTE 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 it to ${RUNTIME_ROOT}/knowledge.md, (4) commit or revert pending changes.")
|
|
773
597
|
|
|
774
598
|
# --- Output harness-specific JSON ---
|
|
775
599
|
case "$HARNESS" in
|
|
@@ -793,8 +617,7 @@ esac
|
|
|
793
617
|
`;
|
|
794
618
|
}
|
|
795
619
|
// ---------------------------------------------------------------------------
|
|
796
|
-
// hooks.json generators
|
|
797
|
-
// These are kept as fallbacks for when observation is disabled.
|
|
620
|
+
// hooks.json generators are defined in observe.ts (shared across harnesses).
|
|
798
621
|
// ---------------------------------------------------------------------------
|
|
799
622
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
800
623
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
@@ -802,151 +625,70 @@ export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
|
|
802
625
|
// ---------------------------------------------------------------------------
|
|
803
626
|
// OpenCode plugin — JS module
|
|
804
627
|
// ---------------------------------------------------------------------------
|
|
805
|
-
export function opencodePluginJs(
|
|
806
|
-
const globalLearningsEnabledLiteral = options.globalLearningsEnabled === true ? "true" : "false";
|
|
807
|
-
const globalLearningsPathLiteral = JSON.stringify(options.globalLearningsPath?.trim() ?? "");
|
|
628
|
+
export function opencodePluginJs(_options = {}) {
|
|
808
629
|
return `// cclaw OpenCode plugin — generated by cclaw sync
|
|
809
|
-
import {
|
|
810
|
-
|
|
811
|
-
closeSync,
|
|
812
|
-
existsSync,
|
|
813
|
-
fstatSync,
|
|
814
|
-
mkdirSync,
|
|
815
|
-
openSync,
|
|
816
|
-
readFileSync,
|
|
817
|
-
readSync
|
|
818
|
-
} from "node:fs";
|
|
819
|
-
import { homedir } from "node:os";
|
|
820
|
-
import { isAbsolute, join } from "node:path";
|
|
630
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
631
|
+
import { join } from "node:path";
|
|
821
632
|
|
|
822
633
|
export default function cclawPlugin(ctx) {
|
|
823
634
|
const root = ctx.directory || process.cwd();
|
|
824
635
|
const runtimeDir = join(root, "${RUNTIME_ROOT}");
|
|
825
|
-
const stateDir = join(
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
const observeDisabledPath = join(root, "${RUNTIME_ROOT}/.observe-disabled");
|
|
836
|
-
const globalLearningsEnabled = ${globalLearningsEnabledLiteral};
|
|
837
|
-
const globalLearningsPathRaw = ${globalLearningsPathLiteral};
|
|
636
|
+
const stateDir = join(runtimeDir, "state");
|
|
637
|
+
const flowStatePath = join(stateDir, "flow-state.json");
|
|
638
|
+
const checkpointPath = join(stateDir, "checkpoint.json");
|
|
639
|
+
const activityPath = join(stateDir, "stage-activity.jsonl");
|
|
640
|
+
const contextWarningsPath = join(stateDir, "context-warnings.jsonl");
|
|
641
|
+
const contextModePath = join(stateDir, "context-mode.json");
|
|
642
|
+
const contextsDir = join(runtimeDir, "contexts");
|
|
643
|
+
const sessionDigestPath = join(stateDir, "session-digest.md");
|
|
644
|
+
const knowledgePath = join(runtimeDir, "knowledge.md");
|
|
645
|
+
const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
|
|
838
646
|
|
|
839
|
-
function
|
|
647
|
+
function ensureRuntimeDirs() {
|
|
840
648
|
try {
|
|
841
|
-
|
|
842
|
-
const state = JSON.parse(raw);
|
|
843
|
-
return {
|
|
844
|
-
stage: state.currentStage || "none",
|
|
845
|
-
completed: (state.completedStages || []).length,
|
|
846
|
-
activeRunId: state.activeRunId || "none",
|
|
847
|
-
};
|
|
649
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
848
650
|
} catch {
|
|
849
|
-
|
|
651
|
+
// ignore
|
|
850
652
|
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
function readMetaSkill() {
|
|
854
653
|
try {
|
|
855
|
-
|
|
654
|
+
mkdirSync(stateDir, { recursive: true });
|
|
856
655
|
} catch {
|
|
857
|
-
|
|
656
|
+
// ignore
|
|
858
657
|
}
|
|
859
658
|
}
|
|
860
659
|
|
|
861
|
-
function
|
|
862
|
-
let fd;
|
|
660
|
+
function readFlowState() {
|
|
863
661
|
try {
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
662
|
+
const raw = readFileSync(flowStatePath, "utf8");
|
|
663
|
+
const state = JSON.parse(raw);
|
|
664
|
+
return {
|
|
665
|
+
stage: typeof state.currentStage === "string" ? state.currentStage : "none",
|
|
666
|
+
completed: Array.isArray(state.completedStages) ? state.completedStages.length : 0,
|
|
667
|
+
activeRunId: typeof state.activeRunId === "string" ? state.activeRunId : "none"
|
|
668
|
+
};
|
|
871
669
|
} catch {
|
|
872
|
-
return "";
|
|
873
|
-
} finally {
|
|
874
|
-
if (fd !== undefined) {
|
|
875
|
-
try {
|
|
876
|
-
closeSync(fd);
|
|
877
|
-
} catch {
|
|
878
|
-
// ignore
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function readTailLines(filePath, maxLines, maxBytes = 65536) {
|
|
885
|
-
const raw = readTailText(filePath, maxBytes).trim();
|
|
886
|
-
if (!raw) return [];
|
|
887
|
-
return raw.split(/\\r?\\n/).slice(-maxLines);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
function resolveGlobalLearningsPath(rawPath) {
|
|
891
|
-
if (!rawPath || typeof rawPath !== "string" || rawPath.trim().length === 0) {
|
|
892
|
-
return join(homedir(), ".cclaw-global-learnings.jsonl");
|
|
893
|
-
}
|
|
894
|
-
const trimmed = rawPath.trim();
|
|
895
|
-
if (trimmed.startsWith("~/")) {
|
|
896
|
-
return join(homedir(), trimmed.slice(2));
|
|
897
|
-
}
|
|
898
|
-
if (isAbsolute(trimmed)) {
|
|
899
|
-
return trimmed;
|
|
900
|
-
}
|
|
901
|
-
return join(root, trimmed);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
function learningFiles() {
|
|
905
|
-
const files = [learningsPath];
|
|
906
|
-
if (globalLearningsEnabled) {
|
|
907
|
-
const globalPath = resolveGlobalLearningsPath(globalLearningsPathRaw);
|
|
908
|
-
if (globalPath !== learningsPath) {
|
|
909
|
-
files.push(globalPath);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
return files;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
function readLearningLines(maxLines, maxBytes) {
|
|
916
|
-
const combined = [];
|
|
917
|
-
for (const filePath of learningFiles()) {
|
|
918
|
-
combined.push(...readTailLines(filePath, maxLines, maxBytes));
|
|
670
|
+
return { stage: "none", completed: 0, activeRunId: "none" };
|
|
919
671
|
}
|
|
920
|
-
return combined;
|
|
921
672
|
}
|
|
922
673
|
|
|
923
|
-
function
|
|
674
|
+
function readFileText(filePath) {
|
|
924
675
|
try {
|
|
925
|
-
|
|
926
|
-
if (lines.length === 0) return [];
|
|
927
|
-
const entries = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
928
|
-
const deduped = new Map();
|
|
929
|
-
for (const e of entries) {
|
|
930
|
-
const key = e.key + ":" + e.type;
|
|
931
|
-
if (!deduped.has(key) || e.ts > deduped.get(key).ts) deduped.set(key, e);
|
|
932
|
-
}
|
|
933
|
-
const now = Date.now();
|
|
934
|
-
return [...deduped.values()]
|
|
935
|
-
.map(e => {
|
|
936
|
-
const months = e.source === "user-stated" ? 0 : Math.floor((now - new Date(e.ts).getTime()) / (30 * 86400000));
|
|
937
|
-
const eff = Math.max(0, e.confidence - months);
|
|
938
|
-
return { ...e, effective_confidence: eff };
|
|
939
|
-
})
|
|
940
|
-
.sort((a, b) => b.effective_confidence - a.effective_confidence)
|
|
941
|
-
.slice(0, 3);
|
|
676
|
+
return readFileSync(filePath, "utf8");
|
|
942
677
|
} catch {
|
|
943
|
-
return
|
|
678
|
+
return "";
|
|
944
679
|
}
|
|
945
680
|
}
|
|
946
681
|
|
|
682
|
+
function readTailLines(filePath, maxLines) {
|
|
683
|
+
const text = readFileText(filePath).trim();
|
|
684
|
+
if (!text) return [];
|
|
685
|
+
return text.split(/\\r?\\n/).slice(-maxLines);
|
|
686
|
+
}
|
|
687
|
+
|
|
947
688
|
function readCheckpointSummary() {
|
|
948
689
|
try {
|
|
949
|
-
const raw =
|
|
690
|
+
const raw = readFileText(checkpointPath);
|
|
691
|
+
if (!raw) return "";
|
|
950
692
|
const cp = JSON.parse(raw);
|
|
951
693
|
return \`Checkpoint: stage=\${cp.stage || "none"}, status=\${cp.status || "unknown"}, run=\${cp.runId || "none"}, at=\${cp.timestamp || "unknown"}\`;
|
|
952
694
|
} catch {
|
|
@@ -957,13 +699,12 @@ export default function cclawPlugin(ctx) {
|
|
|
957
699
|
function readContextMode() {
|
|
958
700
|
let mode = "default";
|
|
959
701
|
try {
|
|
960
|
-
const
|
|
961
|
-
const parsed = JSON.parse(raw);
|
|
702
|
+
const parsed = JSON.parse(readFileText(contextModePath));
|
|
962
703
|
if (parsed && typeof parsed.activeMode === "string" && parsed.activeMode.trim().length > 0) {
|
|
963
704
|
mode = parsed.activeMode.trim();
|
|
964
705
|
}
|
|
965
706
|
} catch {
|
|
966
|
-
//
|
|
707
|
+
// keep default
|
|
967
708
|
}
|
|
968
709
|
const guidePath = join(contextsDir, mode + ".md");
|
|
969
710
|
const guide = existsSync(guidePath) ? "${RUNTIME_ROOT}/contexts/" + mode + ".md" : "";
|
|
@@ -972,40 +713,32 @@ export default function cclawPlugin(ctx) {
|
|
|
972
713
|
|
|
973
714
|
function readRecentActivity() {
|
|
974
715
|
try {
|
|
975
|
-
const lines = readTailLines(activityPath, 5
|
|
716
|
+
const lines = readTailLines(activityPath, 5);
|
|
976
717
|
if (lines.length === 0) return [];
|
|
977
|
-
return lines
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
718
|
+
return lines
|
|
719
|
+
.map((line) => {
|
|
720
|
+
try {
|
|
721
|
+
return JSON.parse(line);
|
|
722
|
+
} catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
.filter(Boolean)
|
|
727
|
+
.map((entry) => \`- \${entry.ts || "unknown"} [\${entry.phase || "unknown"}] \${entry.tool || "unknown"} (stage=\${entry.stage || "unknown"}, run=\${entry.runId || "none"})\`);
|
|
984
728
|
} catch {
|
|
985
729
|
return [];
|
|
986
730
|
}
|
|
987
731
|
}
|
|
988
732
|
|
|
989
|
-
function readSessionDigest() {
|
|
990
|
-
try {
|
|
991
|
-
const digest = readFileSync(sessionDigestPath, "utf8").trim();
|
|
992
|
-
return digest;
|
|
993
|
-
} catch {
|
|
994
|
-
return "";
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
733
|
function readLatestContextWarning() {
|
|
999
734
|
try {
|
|
1000
|
-
const line = readTailLines(contextWarningsPath, 1
|
|
735
|
+
const line = readTailLines(contextWarningsPath, 1)[0];
|
|
1001
736
|
if (!line) return "";
|
|
1002
737
|
try {
|
|
1003
738
|
const parsed = JSON.parse(line);
|
|
1004
|
-
if (parsed && typeof parsed.note === "string")
|
|
1005
|
-
return parsed.note;
|
|
1006
|
-
}
|
|
739
|
+
if (parsed && typeof parsed.note === "string") return parsed.note;
|
|
1007
740
|
} catch {
|
|
1008
|
-
// non-json
|
|
741
|
+
// non-json fallback
|
|
1009
742
|
}
|
|
1010
743
|
return line;
|
|
1011
744
|
} catch {
|
|
@@ -1013,95 +746,42 @@ export default function cclawPlugin(ctx) {
|
|
|
1013
746
|
}
|
|
1014
747
|
}
|
|
1015
748
|
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
brainstorm: "Suggestion: list 2-3 alternatives and ask a single focused clarifying question before direction lock.",
|
|
1019
|
-
scope: "Suggestion: lock explicit in-scope/out-of-scope boundaries and choose one scope mode.",
|
|
1020
|
-
design: "Suggestion: map failure modes per new codepath and confirm architecture boundaries before moving forward.",
|
|
1021
|
-
spec: "Suggestion: ensure every acceptance criterion is measurable and mapped to a concrete test.",
|
|
1022
|
-
plan: "Suggestion: group tasks into dependency waves and keep WAIT_FOR_CONFIRM pending until approval.",
|
|
1023
|
-
tdd: "Suggestion: execute RED → GREEN → REFACTOR for each selected slice and capture evidence per cycle.",
|
|
1024
|
-
review: "Suggestion: run Layer 1 before Layer 2 and reconcile findings into 07-review-army.json.",
|
|
1025
|
-
ship: "Suggestion: verify preflight + rollback plan before selecting exactly one finalization mode."
|
|
1026
|
-
};
|
|
1027
|
-
return map[stage] || "";
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
function readStageSuggestion(stage) {
|
|
1031
|
-
let memory = { enabled: true, mutedStages: [] };
|
|
1032
|
-
try {
|
|
1033
|
-
const parsed = JSON.parse(readFileSync(suggestionMemoryPath, "utf8"));
|
|
1034
|
-
if (parsed && typeof parsed === "object") {
|
|
1035
|
-
memory = {
|
|
1036
|
-
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : true,
|
|
1037
|
-
mutedStages: Array.isArray(parsed.mutedStages)
|
|
1038
|
-
? parsed.mutedStages.filter((item) => typeof item === "string")
|
|
1039
|
-
: []
|
|
1040
|
-
};
|
|
1041
|
-
}
|
|
1042
|
-
} catch {
|
|
1043
|
-
// use defaults
|
|
1044
|
-
}
|
|
1045
|
-
if (!memory.enabled || memory.mutedStages.includes(stage)) {
|
|
1046
|
-
return "";
|
|
1047
|
-
}
|
|
1048
|
-
return stageSuggestionFor(stage);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
function readStageLearnings(stage) {
|
|
1052
|
-
try {
|
|
1053
|
-
const lines = readLearningLines(60, 131072);
|
|
1054
|
-
const entries = lines
|
|
1055
|
-
.map((line) => {
|
|
1056
|
-
try {
|
|
1057
|
-
return JSON.parse(line);
|
|
1058
|
-
} catch {
|
|
1059
|
-
return null;
|
|
1060
|
-
}
|
|
1061
|
-
})
|
|
1062
|
-
.filter(Boolean);
|
|
1063
|
-
if (entries.length === 0) return [];
|
|
1064
|
-
return entries
|
|
1065
|
-
.filter((entry) => typeof entry.key === "string" && entry.key.toLowerCase().includes(stage.toLowerCase()))
|
|
1066
|
-
.sort((a, b) => (Number(b.confidence) || 0) - (Number(a.confidence) || 0))
|
|
1067
|
-
.slice(0, 2)
|
|
1068
|
-
.map((entry) => "- [stage] " + entry.key + ": " + entry.insight);
|
|
1069
|
-
} catch {
|
|
1070
|
-
return [];
|
|
1071
|
-
}
|
|
749
|
+
function readKnowledgeSnapshot() {
|
|
750
|
+
return readTailLines(knowledgePath, 30);
|
|
1072
751
|
}
|
|
1073
752
|
|
|
1074
753
|
function buildBootstrap() {
|
|
1075
|
-
const
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const activity = readRecentActivity();
|
|
1080
|
-
const digest = readSessionDigest();
|
|
1081
|
-
const warning = readLatestContextWarning();
|
|
754
|
+
const flow = readFlowState();
|
|
755
|
+
const parts = [
|
|
756
|
+
\`cclaw loaded. Flow: stage=\${flow.stage} (\${flow.completed}/8 completed, run=\${flow.activeRunId}). Active artifacts: ${RUNTIME_ROOT}/artifacts/\`
|
|
757
|
+
];
|
|
1082
758
|
const contextMode = readContextMode();
|
|
1083
|
-
const stageSuggestion = readStageSuggestion(stage);
|
|
1084
|
-
const stageLearnings = readStageLearnings(stage);
|
|
1085
|
-
const parts = [\`cclaw loaded. Flow: stage=\${stage} (\${completed}/8 completed, run=\${activeRunId}). Active run artifacts: ${RUNTIME_ROOT}/runs/\${activeRunId}/artifacts/\`];
|
|
1086
759
|
parts.push(
|
|
1087
760
|
contextMode.guide
|
|
1088
761
|
? \`Context mode: \${contextMode.mode} (guide: \${contextMode.guide})\`
|
|
1089
762
|
: \`Context mode: \${contextMode.mode}\`
|
|
1090
763
|
);
|
|
764
|
+
|
|
765
|
+
const checkpoint = readCheckpointSummary();
|
|
1091
766
|
if (checkpoint) parts.push(checkpoint);
|
|
767
|
+
|
|
768
|
+
const digest = readFileText(sessionDigestPath).trim();
|
|
1092
769
|
if (digest) parts.push("Last session:", digest);
|
|
770
|
+
|
|
771
|
+
const activity = readRecentActivity();
|
|
1093
772
|
if (activity.length > 0) parts.push("Recent stage activity:", ...activity);
|
|
773
|
+
|
|
774
|
+
const warning = readLatestContextWarning();
|
|
1094
775
|
if (warning) parts.push("Latest context warning:", warning);
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
}
|
|
776
|
+
|
|
777
|
+
const knowledge = readKnowledgeSnapshot();
|
|
778
|
+
if (knowledge.length > 0) parts.push("Knowledge snapshot (latest entries):", ...knowledge);
|
|
779
|
+
|
|
780
|
+
parts.push(
|
|
781
|
+
"If you discover a non-obvious rule or pattern, append it to .cclaw/knowledge.md using type: rule, pattern, or lesson."
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
const meta = readFileText(metaSkillPath).trim();
|
|
1105
785
|
if (meta) parts.push("", meta);
|
|
1106
786
|
return parts.join("\\n");
|
|
1107
787
|
}
|
|
@@ -1110,23 +790,6 @@ export default function cclawPlugin(ctx) {
|
|
|
1110
790
|
console.log(buildBootstrap());
|
|
1111
791
|
}
|
|
1112
792
|
|
|
1113
|
-
function observationEnabled() {
|
|
1114
|
-
return !existsSync(observeDisabledPath);
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
function ensureRuntimeDirs() {
|
|
1118
|
-
try {
|
|
1119
|
-
mkdirSync(runtimeDir, { recursive: true });
|
|
1120
|
-
} catch {
|
|
1121
|
-
// ignore
|
|
1122
|
-
}
|
|
1123
|
-
try {
|
|
1124
|
-
mkdirSync(stateDir, { recursive: true });
|
|
1125
|
-
} catch {
|
|
1126
|
-
// ignore
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
793
|
async function runHookScript(scriptFileName, payload = {}) {
|
|
1131
794
|
const { spawnSync } = await import("node:child_process");
|
|
1132
795
|
const scriptPath = join(root, "${RUNTIME_ROOT}/hooks/" + scriptFileName);
|
|
@@ -1143,53 +806,9 @@ export default function cclawPlugin(ctx) {
|
|
|
1143
806
|
}
|
|
1144
807
|
}
|
|
1145
808
|
|
|
1146
|
-
function toText(value) {
|
|
1147
|
-
if (typeof value === "string") return value;
|
|
1148
|
-
try {
|
|
1149
|
-
return JSON.stringify(value);
|
|
1150
|
-
} catch {
|
|
1151
|
-
return value == null ? "" : String(value);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function truncateText(value, maxLen = 2000) {
|
|
1156
|
-
const text = toText(value);
|
|
1157
|
-
return text.length > maxLen ? text.slice(0, maxLen) : text;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
function extractToolName(payload) {
|
|
1161
|
-
const candidates = [
|
|
1162
|
-
payload?.tool,
|
|
1163
|
-
payload?.toolName,
|
|
1164
|
-
payload?.tool_name,
|
|
1165
|
-
payload?.name,
|
|
1166
|
-
payload?.id,
|
|
1167
|
-
payload?.command,
|
|
1168
|
-
payload?.tool?.name,
|
|
1169
|
-
payload?.tool?.id,
|
|
1170
|
-
payload?.input?.tool,
|
|
1171
|
-
payload?.input?.toolName,
|
|
1172
|
-
payload?.input?.tool_name,
|
|
1173
|
-
payload?.input?.name,
|
|
1174
|
-
payload?.input?.id,
|
|
1175
|
-
payload?.input?.command,
|
|
1176
|
-
payload?.input?.tool?.name,
|
|
1177
|
-
payload?.input?.tool?.id
|
|
1178
|
-
];
|
|
1179
|
-
for (const value of candidates) {
|
|
1180
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1181
|
-
}
|
|
1182
|
-
return "unknown";
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
809
|
function normalizeToolPayload(input, output) {
|
|
1186
|
-
if (typeof output === "undefined") {
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
return {
|
|
1190
|
-
input: input ?? {},
|
|
1191
|
-
output: output ?? {}
|
|
1192
|
-
};
|
|
810
|
+
if (typeof output === "undefined") return input ?? {};
|
|
811
|
+
return { input: input ?? {}, output: output ?? {} };
|
|
1193
812
|
}
|
|
1194
813
|
|
|
1195
814
|
function resolveEventType(payload) {
|
|
@@ -1212,77 +831,41 @@ export default function cclawPlugin(ctx) {
|
|
|
1212
831
|
return payload;
|
|
1213
832
|
}
|
|
1214
833
|
|
|
1215
|
-
|
|
1216
|
-
try {
|
|
1217
|
-
appendFileSync(filePath, JSON.stringify(value) + "\\n", "utf8");
|
|
1218
|
-
} catch {
|
|
1219
|
-
// ignore
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
function recordToolEvent(phase, payload) {
|
|
1224
|
-
if (!observationEnabled()) return;
|
|
1225
|
-
const flow = readFlowState();
|
|
1226
|
-
const ts = new Date().toISOString();
|
|
1227
|
-
const tool = extractToolName(payload);
|
|
1228
|
-
const event = phase === "pre" ? "tool_start" : "tool_complete";
|
|
1229
|
-
ensureRuntimeDirs();
|
|
1230
|
-
appendJsonLine(observationsPath, {
|
|
1231
|
-
ts,
|
|
1232
|
-
event,
|
|
1233
|
-
tool,
|
|
1234
|
-
phase,
|
|
1235
|
-
stage: flow.stage,
|
|
1236
|
-
runId: flow.activeRunId,
|
|
1237
|
-
data: truncateText(payload)
|
|
1238
|
-
});
|
|
1239
|
-
appendJsonLine(activityPath, {
|
|
1240
|
-
ts,
|
|
1241
|
-
event,
|
|
1242
|
-
tool,
|
|
1243
|
-
phase,
|
|
1244
|
-
stage: flow.stage,
|
|
1245
|
-
runId: flow.activeRunId
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
834
|
+
ensureRuntimeDirs();
|
|
1248
835
|
|
|
1249
836
|
return {
|
|
1250
837
|
event: async (payload) => {
|
|
1251
838
|
const eventType = resolveEventType(payload);
|
|
1252
839
|
const eventData = resolveEventData(payload);
|
|
1253
|
-
if (
|
|
840
|
+
if (
|
|
841
|
+
eventType === "session.created" ||
|
|
842
|
+
eventType === "session.resumed" ||
|
|
843
|
+
eventType === "session.compacted" ||
|
|
844
|
+
eventType === "session.cleared"
|
|
845
|
+
) {
|
|
1254
846
|
emitBootstrap();
|
|
1255
847
|
}
|
|
1256
|
-
if (eventType === "session.updated") {
|
|
1257
|
-
// no-op: tracked via activity log
|
|
1258
|
-
}
|
|
1259
848
|
if (eventType === "session.idle") {
|
|
1260
|
-
if (!observationEnabled()) return;
|
|
1261
|
-
await runHookScript("summarize-observations.sh");
|
|
1262
849
|
await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
|
|
1263
850
|
}
|
|
1264
851
|
if (eventType === "tool.execute.before") {
|
|
1265
852
|
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
1266
853
|
await runHookScript("prompt-guard.sh", toolPayload);
|
|
1267
854
|
await runHookScript("workflow-guard.sh", toolPayload);
|
|
1268
|
-
recordToolEvent("pre", toolPayload);
|
|
1269
855
|
}
|
|
1270
856
|
if (eventType === "tool.execute.after") {
|
|
1271
857
|
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
1272
858
|
await runHookScript("context-monitor.sh", toolPayload);
|
|
1273
|
-
recordToolEvent("post", toolPayload);
|
|
1274
859
|
}
|
|
1275
860
|
},
|
|
1276
861
|
"tool.execute.before": async (input, output) => {
|
|
1277
862
|
const payload = normalizeToolPayload(input, output);
|
|
1278
863
|
await runHookScript("prompt-guard.sh", payload);
|
|
1279
864
|
await runHookScript("workflow-guard.sh", payload);
|
|
1280
|
-
recordToolEvent("pre", payload);
|
|
1281
865
|
},
|
|
1282
866
|
"tool.execute.after": async (input, output) => {
|
|
1283
867
|
const payload = normalizeToolPayload(input, output);
|
|
1284
868
|
await runHookScript("context-monitor.sh", payload);
|
|
1285
|
-
recordToolEvent("post", payload);
|
|
1286
869
|
},
|
|
1287
870
|
"experimental.chat.system.transform": (payload) => {
|
|
1288
871
|
const bootstrap = buildBootstrap();
|
|
@@ -1294,7 +877,7 @@ export default function cclawPlugin(ctx) {
|
|
|
1294
877
|
return { ...payload, system: \`\${payload.system}\\n\\n\${bootstrap}\` };
|
|
1295
878
|
}
|
|
1296
879
|
return payload;
|
|
1297
|
-
}
|
|
880
|
+
}
|
|
1298
881
|
};
|
|
1299
882
|
}
|
|
1300
883
|
`;
|
|
@@ -1307,7 +890,7 @@ export function hooksAgentsMdBlock() {
|
|
|
1307
890
|
|
|
1308
891
|
Cclaw generates real hook integrations across harnesses:
|
|
1309
892
|
- **Claude/Cursor/Codex:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
|
|
1310
|
-
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/
|
|
893
|
+
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/knowledge snapshot)
|
|
1311
894
|
|
|
1312
895
|
| Harness | Hook file | Events |
|
|
1313
896
|
|---------|-----------|--------|
|
|
@@ -1319,7 +902,5 @@ Cclaw generates real hook integrations across harnesses:
|
|
|
1319
902
|
Hook state files:
|
|
1320
903
|
- \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
|
|
1321
904
|
- \`${RUNTIME_ROOT}/state/checkpoint.json\`
|
|
1322
|
-
|
|
1323
|
-
Disable observation: \`touch ${RUNTIME_ROOT}/.observe-disabled\`
|
|
1324
905
|
`;
|
|
1325
906
|
}
|