cclaw-cli 0.5.3 → 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 +41 -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 +42 -26
- package/dist/content/hooks.d.ts +4 -6
- package/dist/content/hooks.js +105 -435
- 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 +81 -55
- package/dist/content/session-hooks.js +8 -8
- package/dist/content/skills.js +9 -16
- package/dist/content/stage-schema.js +80 -97
- 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 +17 -34
- package/dist/flow-state.js +1 -1
- package/dist/harness-adapters.js +1 -1
- package/dist/install.js +23 -49
- package/dist/policy.js +1 -4
- package/dist/runs.d.ts +13 -9
- package/dist/runs.js +108 -317
- 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,9 +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
|
|
|
592
|
+
RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until cclaw archive."
|
|
593
|
+
|
|
681
594
|
# --- Escape for JSON ---
|
|
682
595
|
${ESCAPE_FN}
|
|
683
|
-
MSG=$(escape_json "Cclaw: session ending (stage=$STAGE, run=$ACTIVE_RUN). $CHECKPOINT_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.")
|
|
684
597
|
|
|
685
598
|
# --- Output harness-specific JSON ---
|
|
686
599
|
case "$HARNESS" in
|
|
@@ -704,8 +617,7 @@ esac
|
|
|
704
617
|
`;
|
|
705
618
|
}
|
|
706
619
|
// ---------------------------------------------------------------------------
|
|
707
|
-
// hooks.json generators
|
|
708
|
-
// These are kept as fallbacks for when observation is disabled.
|
|
620
|
+
// hooks.json generators are defined in observe.ts (shared across harnesses).
|
|
709
621
|
// ---------------------------------------------------------------------------
|
|
710
622
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
711
623
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
@@ -713,151 +625,70 @@ export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
|
|
713
625
|
// ---------------------------------------------------------------------------
|
|
714
626
|
// OpenCode plugin — JS module
|
|
715
627
|
// ---------------------------------------------------------------------------
|
|
716
|
-
export function opencodePluginJs(
|
|
717
|
-
const globalLearningsEnabledLiteral = options.globalLearningsEnabled === true ? "true" : "false";
|
|
718
|
-
const globalLearningsPathLiteral = JSON.stringify(options.globalLearningsPath?.trim() ?? "");
|
|
628
|
+
export function opencodePluginJs(_options = {}) {
|
|
719
629
|
return `// cclaw OpenCode plugin — generated by cclaw sync
|
|
720
|
-
import {
|
|
721
|
-
|
|
722
|
-
closeSync,
|
|
723
|
-
existsSync,
|
|
724
|
-
fstatSync,
|
|
725
|
-
mkdirSync,
|
|
726
|
-
openSync,
|
|
727
|
-
readFileSync,
|
|
728
|
-
readSync
|
|
729
|
-
} from "node:fs";
|
|
730
|
-
import { homedir } from "node:os";
|
|
731
|
-
import { isAbsolute, join } from "node:path";
|
|
630
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
631
|
+
import { join } from "node:path";
|
|
732
632
|
|
|
733
633
|
export default function cclawPlugin(ctx) {
|
|
734
634
|
const root = ctx.directory || process.cwd();
|
|
735
635
|
const runtimeDir = join(root, "${RUNTIME_ROOT}");
|
|
736
|
-
const stateDir = join(
|
|
737
|
-
const
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
const
|
|
742
|
-
const
|
|
743
|
-
const
|
|
744
|
-
const
|
|
745
|
-
const
|
|
746
|
-
const observeDisabledPath = join(root, "${RUNTIME_ROOT}/.observe-disabled");
|
|
747
|
-
const globalLearningsEnabled = ${globalLearningsEnabledLiteral};
|
|
748
|
-
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");
|
|
749
646
|
|
|
750
|
-
function
|
|
647
|
+
function ensureRuntimeDirs() {
|
|
751
648
|
try {
|
|
752
|
-
|
|
753
|
-
const state = JSON.parse(raw);
|
|
754
|
-
return {
|
|
755
|
-
stage: state.currentStage || "none",
|
|
756
|
-
completed: (state.completedStages || []).length,
|
|
757
|
-
activeRunId: state.activeRunId || "none",
|
|
758
|
-
};
|
|
649
|
+
mkdirSync(runtimeDir, { recursive: true });
|
|
759
650
|
} catch {
|
|
760
|
-
|
|
651
|
+
// ignore
|
|
761
652
|
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function readMetaSkill() {
|
|
765
653
|
try {
|
|
766
|
-
|
|
654
|
+
mkdirSync(stateDir, { recursive: true });
|
|
767
655
|
} catch {
|
|
768
|
-
|
|
656
|
+
// ignore
|
|
769
657
|
}
|
|
770
658
|
}
|
|
771
659
|
|
|
772
|
-
function
|
|
773
|
-
let fd;
|
|
660
|
+
function readFlowState() {
|
|
774
661
|
try {
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
+
};
|
|
782
669
|
} catch {
|
|
783
|
-
return "";
|
|
784
|
-
} finally {
|
|
785
|
-
if (fd !== undefined) {
|
|
786
|
-
try {
|
|
787
|
-
closeSync(fd);
|
|
788
|
-
} catch {
|
|
789
|
-
// ignore
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function readTailLines(filePath, maxLines, maxBytes = 65536) {
|
|
796
|
-
const raw = readTailText(filePath, maxBytes).trim();
|
|
797
|
-
if (!raw) return [];
|
|
798
|
-
return raw.split(/\\r?\\n/).slice(-maxLines);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function resolveGlobalLearningsPath(rawPath) {
|
|
802
|
-
if (!rawPath || typeof rawPath !== "string" || rawPath.trim().length === 0) {
|
|
803
|
-
return join(homedir(), ".cclaw-global-learnings.jsonl");
|
|
804
|
-
}
|
|
805
|
-
const trimmed = rawPath.trim();
|
|
806
|
-
if (trimmed.startsWith("~/")) {
|
|
807
|
-
return join(homedir(), trimmed.slice(2));
|
|
808
|
-
}
|
|
809
|
-
if (isAbsolute(trimmed)) {
|
|
810
|
-
return trimmed;
|
|
811
|
-
}
|
|
812
|
-
return join(root, trimmed);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
function learningFiles() {
|
|
816
|
-
const files = [learningsPath];
|
|
817
|
-
if (globalLearningsEnabled) {
|
|
818
|
-
const globalPath = resolveGlobalLearningsPath(globalLearningsPathRaw);
|
|
819
|
-
if (globalPath !== learningsPath) {
|
|
820
|
-
files.push(globalPath);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return files;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
function readLearningLines(maxLines, maxBytes) {
|
|
827
|
-
const combined = [];
|
|
828
|
-
for (const filePath of learningFiles()) {
|
|
829
|
-
combined.push(...readTailLines(filePath, maxLines, maxBytes));
|
|
670
|
+
return { stage: "none", completed: 0, activeRunId: "none" };
|
|
830
671
|
}
|
|
831
|
-
return combined;
|
|
832
672
|
}
|
|
833
673
|
|
|
834
|
-
function
|
|
674
|
+
function readFileText(filePath) {
|
|
835
675
|
try {
|
|
836
|
-
|
|
837
|
-
if (lines.length === 0) return [];
|
|
838
|
-
const entries = lines.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
839
|
-
const deduped = new Map();
|
|
840
|
-
for (const e of entries) {
|
|
841
|
-
const key = e.key + ":" + e.type;
|
|
842
|
-
if (!deduped.has(key) || e.ts > deduped.get(key).ts) deduped.set(key, e);
|
|
843
|
-
}
|
|
844
|
-
const now = Date.now();
|
|
845
|
-
return [...deduped.values()]
|
|
846
|
-
.map(e => {
|
|
847
|
-
const months = e.source === "user-stated" ? 0 : Math.floor((now - new Date(e.ts).getTime()) / (30 * 86400000));
|
|
848
|
-
const eff = Math.max(0, e.confidence - months);
|
|
849
|
-
return { ...e, effective_confidence: eff };
|
|
850
|
-
})
|
|
851
|
-
.sort((a, b) => b.effective_confidence - a.effective_confidence)
|
|
852
|
-
.slice(0, 3);
|
|
676
|
+
return readFileSync(filePath, "utf8");
|
|
853
677
|
} catch {
|
|
854
|
-
return
|
|
678
|
+
return "";
|
|
855
679
|
}
|
|
856
680
|
}
|
|
857
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
|
+
|
|
858
688
|
function readCheckpointSummary() {
|
|
859
689
|
try {
|
|
860
|
-
const raw =
|
|
690
|
+
const raw = readFileText(checkpointPath);
|
|
691
|
+
if (!raw) return "";
|
|
861
692
|
const cp = JSON.parse(raw);
|
|
862
693
|
return \`Checkpoint: stage=\${cp.stage || "none"}, status=\${cp.status || "unknown"}, run=\${cp.runId || "none"}, at=\${cp.timestamp || "unknown"}\`;
|
|
863
694
|
} catch {
|
|
@@ -868,13 +699,12 @@ export default function cclawPlugin(ctx) {
|
|
|
868
699
|
function readContextMode() {
|
|
869
700
|
let mode = "default";
|
|
870
701
|
try {
|
|
871
|
-
const
|
|
872
|
-
const parsed = JSON.parse(raw);
|
|
702
|
+
const parsed = JSON.parse(readFileText(contextModePath));
|
|
873
703
|
if (parsed && typeof parsed.activeMode === "string" && parsed.activeMode.trim().length > 0) {
|
|
874
704
|
mode = parsed.activeMode.trim();
|
|
875
705
|
}
|
|
876
706
|
} catch {
|
|
877
|
-
//
|
|
707
|
+
// keep default
|
|
878
708
|
}
|
|
879
709
|
const guidePath = join(contextsDir, mode + ".md");
|
|
880
710
|
const guide = existsSync(guidePath) ? "${RUNTIME_ROOT}/contexts/" + mode + ".md" : "";
|
|
@@ -883,40 +713,32 @@ export default function cclawPlugin(ctx) {
|
|
|
883
713
|
|
|
884
714
|
function readRecentActivity() {
|
|
885
715
|
try {
|
|
886
|
-
const lines = readTailLines(activityPath, 5
|
|
716
|
+
const lines = readTailLines(activityPath, 5);
|
|
887
717
|
if (lines.length === 0) return [];
|
|
888
|
-
return lines
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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"})\`);
|
|
895
728
|
} catch {
|
|
896
729
|
return [];
|
|
897
730
|
}
|
|
898
731
|
}
|
|
899
732
|
|
|
900
|
-
function readSessionDigest() {
|
|
901
|
-
try {
|
|
902
|
-
const digest = readFileSync(sessionDigestPath, "utf8").trim();
|
|
903
|
-
return digest;
|
|
904
|
-
} catch {
|
|
905
|
-
return "";
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
733
|
function readLatestContextWarning() {
|
|
910
734
|
try {
|
|
911
|
-
const line = readTailLines(contextWarningsPath, 1
|
|
735
|
+
const line = readTailLines(contextWarningsPath, 1)[0];
|
|
912
736
|
if (!line) return "";
|
|
913
737
|
try {
|
|
914
738
|
const parsed = JSON.parse(line);
|
|
915
|
-
if (parsed && typeof parsed.note === "string")
|
|
916
|
-
return parsed.note;
|
|
917
|
-
}
|
|
739
|
+
if (parsed && typeof parsed.note === "string") return parsed.note;
|
|
918
740
|
} catch {
|
|
919
|
-
// non-json
|
|
741
|
+
// non-json fallback
|
|
920
742
|
}
|
|
921
743
|
return line;
|
|
922
744
|
} catch {
|
|
@@ -924,95 +746,42 @@ export default function cclawPlugin(ctx) {
|
|
|
924
746
|
}
|
|
925
747
|
}
|
|
926
748
|
|
|
927
|
-
function
|
|
928
|
-
|
|
929
|
-
brainstorm: "Suggestion: list 2-3 alternatives and ask a single focused clarifying question before direction lock.",
|
|
930
|
-
scope: "Suggestion: lock explicit in-scope/out-of-scope boundaries and choose one scope mode.",
|
|
931
|
-
design: "Suggestion: map failure modes per new codepath and confirm architecture boundaries before moving forward.",
|
|
932
|
-
spec: "Suggestion: ensure every acceptance criterion is measurable and mapped to a concrete test.",
|
|
933
|
-
plan: "Suggestion: group tasks into dependency waves and keep WAIT_FOR_CONFIRM pending until approval.",
|
|
934
|
-
tdd: "Suggestion: execute RED → GREEN → REFACTOR for each selected slice and capture evidence per cycle.",
|
|
935
|
-
review: "Suggestion: run Layer 1 before Layer 2 and reconcile findings into 07-review-army.json.",
|
|
936
|
-
ship: "Suggestion: verify preflight + rollback plan before selecting exactly one finalization mode."
|
|
937
|
-
};
|
|
938
|
-
return map[stage] || "";
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function readStageSuggestion(stage) {
|
|
942
|
-
let memory = { enabled: true, mutedStages: [] };
|
|
943
|
-
try {
|
|
944
|
-
const parsed = JSON.parse(readFileSync(suggestionMemoryPath, "utf8"));
|
|
945
|
-
if (parsed && typeof parsed === "object") {
|
|
946
|
-
memory = {
|
|
947
|
-
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : true,
|
|
948
|
-
mutedStages: Array.isArray(parsed.mutedStages)
|
|
949
|
-
? parsed.mutedStages.filter((item) => typeof item === "string")
|
|
950
|
-
: []
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
} catch {
|
|
954
|
-
// use defaults
|
|
955
|
-
}
|
|
956
|
-
if (!memory.enabled || memory.mutedStages.includes(stage)) {
|
|
957
|
-
return "";
|
|
958
|
-
}
|
|
959
|
-
return stageSuggestionFor(stage);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
function readStageLearnings(stage) {
|
|
963
|
-
try {
|
|
964
|
-
const lines = readLearningLines(60, 131072);
|
|
965
|
-
const entries = lines
|
|
966
|
-
.map((line) => {
|
|
967
|
-
try {
|
|
968
|
-
return JSON.parse(line);
|
|
969
|
-
} catch {
|
|
970
|
-
return null;
|
|
971
|
-
}
|
|
972
|
-
})
|
|
973
|
-
.filter(Boolean);
|
|
974
|
-
if (entries.length === 0) return [];
|
|
975
|
-
return entries
|
|
976
|
-
.filter((entry) => typeof entry.key === "string" && entry.key.toLowerCase().includes(stage.toLowerCase()))
|
|
977
|
-
.sort((a, b) => (Number(b.confidence) || 0) - (Number(a.confidence) || 0))
|
|
978
|
-
.slice(0, 2)
|
|
979
|
-
.map((entry) => "- [stage] " + entry.key + ": " + entry.insight);
|
|
980
|
-
} catch {
|
|
981
|
-
return [];
|
|
982
|
-
}
|
|
749
|
+
function readKnowledgeSnapshot() {
|
|
750
|
+
return readTailLines(knowledgePath, 30);
|
|
983
751
|
}
|
|
984
752
|
|
|
985
753
|
function buildBootstrap() {
|
|
986
|
-
const
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
const activity = readRecentActivity();
|
|
991
|
-
const digest = readSessionDigest();
|
|
992
|
-
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
|
+
];
|
|
993
758
|
const contextMode = readContextMode();
|
|
994
|
-
const stageSuggestion = readStageSuggestion(stage);
|
|
995
|
-
const stageLearnings = readStageLearnings(stage);
|
|
996
|
-
const parts = [\`cclaw loaded. Flow: stage=\${stage} (\${completed}/8 completed, run=\${activeRunId}). Active run artifacts: ${RUNTIME_ROOT}/runs/\${activeRunId}/artifacts/\`];
|
|
997
759
|
parts.push(
|
|
998
760
|
contextMode.guide
|
|
999
761
|
? \`Context mode: \${contextMode.mode} (guide: \${contextMode.guide})\`
|
|
1000
762
|
: \`Context mode: \${contextMode.mode}\`
|
|
1001
763
|
);
|
|
764
|
+
|
|
765
|
+
const checkpoint = readCheckpointSummary();
|
|
1002
766
|
if (checkpoint) parts.push(checkpoint);
|
|
767
|
+
|
|
768
|
+
const digest = readFileText(sessionDigestPath).trim();
|
|
1003
769
|
if (digest) parts.push("Last session:", digest);
|
|
770
|
+
|
|
771
|
+
const activity = readRecentActivity();
|
|
1004
772
|
if (activity.length > 0) parts.push("Recent stage activity:", ...activity);
|
|
773
|
+
|
|
774
|
+
const warning = readLatestContextWarning();
|
|
1005
775
|
if (warning) parts.push("Latest context warning:", warning);
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
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();
|
|
1016
785
|
if (meta) parts.push("", meta);
|
|
1017
786
|
return parts.join("\\n");
|
|
1018
787
|
}
|
|
@@ -1021,23 +790,6 @@ export default function cclawPlugin(ctx) {
|
|
|
1021
790
|
console.log(buildBootstrap());
|
|
1022
791
|
}
|
|
1023
792
|
|
|
1024
|
-
function observationEnabled() {
|
|
1025
|
-
return !existsSync(observeDisabledPath);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
function ensureRuntimeDirs() {
|
|
1029
|
-
try {
|
|
1030
|
-
mkdirSync(runtimeDir, { recursive: true });
|
|
1031
|
-
} catch {
|
|
1032
|
-
// ignore
|
|
1033
|
-
}
|
|
1034
|
-
try {
|
|
1035
|
-
mkdirSync(stateDir, { recursive: true });
|
|
1036
|
-
} catch {
|
|
1037
|
-
// ignore
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
793
|
async function runHookScript(scriptFileName, payload = {}) {
|
|
1042
794
|
const { spawnSync } = await import("node:child_process");
|
|
1043
795
|
const scriptPath = join(root, "${RUNTIME_ROOT}/hooks/" + scriptFileName);
|
|
@@ -1054,53 +806,9 @@ export default function cclawPlugin(ctx) {
|
|
|
1054
806
|
}
|
|
1055
807
|
}
|
|
1056
808
|
|
|
1057
|
-
function toText(value) {
|
|
1058
|
-
if (typeof value === "string") return value;
|
|
1059
|
-
try {
|
|
1060
|
-
return JSON.stringify(value);
|
|
1061
|
-
} catch {
|
|
1062
|
-
return value == null ? "" : String(value);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
function truncateText(value, maxLen = 2000) {
|
|
1067
|
-
const text = toText(value);
|
|
1068
|
-
return text.length > maxLen ? text.slice(0, maxLen) : text;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
function extractToolName(payload) {
|
|
1072
|
-
const candidates = [
|
|
1073
|
-
payload?.tool,
|
|
1074
|
-
payload?.toolName,
|
|
1075
|
-
payload?.tool_name,
|
|
1076
|
-
payload?.name,
|
|
1077
|
-
payload?.id,
|
|
1078
|
-
payload?.command,
|
|
1079
|
-
payload?.tool?.name,
|
|
1080
|
-
payload?.tool?.id,
|
|
1081
|
-
payload?.input?.tool,
|
|
1082
|
-
payload?.input?.toolName,
|
|
1083
|
-
payload?.input?.tool_name,
|
|
1084
|
-
payload?.input?.name,
|
|
1085
|
-
payload?.input?.id,
|
|
1086
|
-
payload?.input?.command,
|
|
1087
|
-
payload?.input?.tool?.name,
|
|
1088
|
-
payload?.input?.tool?.id
|
|
1089
|
-
];
|
|
1090
|
-
for (const value of candidates) {
|
|
1091
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1092
|
-
}
|
|
1093
|
-
return "unknown";
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
809
|
function normalizeToolPayload(input, output) {
|
|
1097
|
-
if (typeof output === "undefined") {
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
return {
|
|
1101
|
-
input: input ?? {},
|
|
1102
|
-
output: output ?? {}
|
|
1103
|
-
};
|
|
810
|
+
if (typeof output === "undefined") return input ?? {};
|
|
811
|
+
return { input: input ?? {}, output: output ?? {} };
|
|
1104
812
|
}
|
|
1105
813
|
|
|
1106
814
|
function resolveEventType(payload) {
|
|
@@ -1123,77 +831,41 @@ export default function cclawPlugin(ctx) {
|
|
|
1123
831
|
return payload;
|
|
1124
832
|
}
|
|
1125
833
|
|
|
1126
|
-
|
|
1127
|
-
try {
|
|
1128
|
-
appendFileSync(filePath, JSON.stringify(value) + "\\n", "utf8");
|
|
1129
|
-
} catch {
|
|
1130
|
-
// ignore
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
function recordToolEvent(phase, payload) {
|
|
1135
|
-
if (!observationEnabled()) return;
|
|
1136
|
-
const flow = readFlowState();
|
|
1137
|
-
const ts = new Date().toISOString();
|
|
1138
|
-
const tool = extractToolName(payload);
|
|
1139
|
-
const event = phase === "pre" ? "tool_start" : "tool_complete";
|
|
1140
|
-
ensureRuntimeDirs();
|
|
1141
|
-
appendJsonLine(observationsPath, {
|
|
1142
|
-
ts,
|
|
1143
|
-
event,
|
|
1144
|
-
tool,
|
|
1145
|
-
phase,
|
|
1146
|
-
stage: flow.stage,
|
|
1147
|
-
runId: flow.activeRunId,
|
|
1148
|
-
data: truncateText(payload)
|
|
1149
|
-
});
|
|
1150
|
-
appendJsonLine(activityPath, {
|
|
1151
|
-
ts,
|
|
1152
|
-
event,
|
|
1153
|
-
tool,
|
|
1154
|
-
phase,
|
|
1155
|
-
stage: flow.stage,
|
|
1156
|
-
runId: flow.activeRunId
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
834
|
+
ensureRuntimeDirs();
|
|
1159
835
|
|
|
1160
836
|
return {
|
|
1161
837
|
event: async (payload) => {
|
|
1162
838
|
const eventType = resolveEventType(payload);
|
|
1163
839
|
const eventData = resolveEventData(payload);
|
|
1164
|
-
if (
|
|
840
|
+
if (
|
|
841
|
+
eventType === "session.created" ||
|
|
842
|
+
eventType === "session.resumed" ||
|
|
843
|
+
eventType === "session.compacted" ||
|
|
844
|
+
eventType === "session.cleared"
|
|
845
|
+
) {
|
|
1165
846
|
emitBootstrap();
|
|
1166
847
|
}
|
|
1167
|
-
if (eventType === "session.updated") {
|
|
1168
|
-
// no-op: tracked via activity log
|
|
1169
|
-
}
|
|
1170
848
|
if (eventType === "session.idle") {
|
|
1171
|
-
if (!observationEnabled()) return;
|
|
1172
|
-
await runHookScript("summarize-observations.sh");
|
|
1173
849
|
await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
|
|
1174
850
|
}
|
|
1175
851
|
if (eventType === "tool.execute.before") {
|
|
1176
852
|
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
1177
853
|
await runHookScript("prompt-guard.sh", toolPayload);
|
|
1178
854
|
await runHookScript("workflow-guard.sh", toolPayload);
|
|
1179
|
-
recordToolEvent("pre", toolPayload);
|
|
1180
855
|
}
|
|
1181
856
|
if (eventType === "tool.execute.after") {
|
|
1182
857
|
const toolPayload = normalizeToolPayload(eventData, undefined);
|
|
1183
858
|
await runHookScript("context-monitor.sh", toolPayload);
|
|
1184
|
-
recordToolEvent("post", toolPayload);
|
|
1185
859
|
}
|
|
1186
860
|
},
|
|
1187
861
|
"tool.execute.before": async (input, output) => {
|
|
1188
862
|
const payload = normalizeToolPayload(input, output);
|
|
1189
863
|
await runHookScript("prompt-guard.sh", payload);
|
|
1190
864
|
await runHookScript("workflow-guard.sh", payload);
|
|
1191
|
-
recordToolEvent("pre", payload);
|
|
1192
865
|
},
|
|
1193
866
|
"tool.execute.after": async (input, output) => {
|
|
1194
867
|
const payload = normalizeToolPayload(input, output);
|
|
1195
868
|
await runHookScript("context-monitor.sh", payload);
|
|
1196
|
-
recordToolEvent("post", payload);
|
|
1197
869
|
},
|
|
1198
870
|
"experimental.chat.system.transform": (payload) => {
|
|
1199
871
|
const bootstrap = buildBootstrap();
|
|
@@ -1205,7 +877,7 @@ export default function cclawPlugin(ctx) {
|
|
|
1205
877
|
return { ...payload, system: \`\${payload.system}\\n\\n\${bootstrap}\` };
|
|
1206
878
|
}
|
|
1207
879
|
return payload;
|
|
1208
|
-
}
|
|
880
|
+
}
|
|
1209
881
|
};
|
|
1210
882
|
}
|
|
1211
883
|
`;
|
|
@@ -1218,7 +890,7 @@ export function hooksAgentsMdBlock() {
|
|
|
1218
890
|
|
|
1219
891
|
Cclaw generates real hook integrations across harnesses:
|
|
1220
892
|
- **Claude/Cursor/Codex:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
|
|
1221
|
-
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/
|
|
893
|
+
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/knowledge snapshot)
|
|
1222
894
|
|
|
1223
895
|
| Harness | Hook file | Events |
|
|
1224
896
|
|---------|-----------|--------|
|
|
@@ -1230,7 +902,5 @@ Cclaw generates real hook integrations across harnesses:
|
|
|
1230
902
|
Hook state files:
|
|
1231
903
|
- \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
|
|
1232
904
|
- \`${RUNTIME_ROOT}/state/checkpoint.json\`
|
|
1233
|
-
|
|
1234
|
-
Disable observation: \`touch ${RUNTIME_ROOT}/.observe-disabled\`
|
|
1235
905
|
`;
|
|
1236
906
|
}
|