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.
- package/dist/artifact-linter.d.ts +20 -0
- package/dist/artifact-linter.js +368 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -2
- package/dist/config.d.ts +4 -4
- package/dist/config.js +56 -5
- package/dist/constants.d.ts +4 -4
- package/dist/constants.js +6 -3
- package/dist/content/autoplan.js +51 -4
- package/dist/content/contexts.d.ts +9 -0
- package/dist/content/contexts.js +65 -0
- package/dist/content/hooks.d.ts +6 -2
- package/dist/content/hooks.js +448 -16
- package/dist/content/meta-skill.js +26 -0
- package/dist/content/next-command.d.ts +9 -0
- package/dist/content/next-command.js +138 -0
- package/dist/content/observe.d.ts +5 -1
- package/dist/content/observe.js +506 -24
- package/dist/content/skills.js +126 -0
- package/dist/content/stage-schema.d.ts +7 -0
- package/dist/content/stage-schema.js +70 -12
- package/dist/content/subagents.js +33 -0
- package/dist/content/templates.d.ts +1 -0
- package/dist/content/templates.js +182 -77
- package/dist/content/utility-skills.d.ts +5 -1
- package/dist/content/utility-skills.js +208 -2
- package/dist/delegation.d.ts +21 -0
- package/dist/delegation.js +94 -0
- package/dist/doctor.d.ts +5 -1
- package/dist/doctor.js +274 -23
- package/dist/fs-utils.d.ts +10 -0
- package/dist/fs-utils.js +47 -0
- package/dist/gate-evidence.d.ts +26 -0
- package/dist/gate-evidence.js +157 -0
- package/dist/harness-adapters.js +2 -0
- package/dist/hook-schema.d.ts +6 -0
- package/dist/hook-schema.js +45 -0
- package/dist/hook-schemas/claude-hooks.v1.json +12 -0
- package/dist/hook-schemas/codex-hooks.v1.json +12 -0
- package/dist/hook-schemas/cursor-hooks.v1.json +15 -0
- package/dist/install.js +431 -16
- package/dist/policy.d.ts +5 -1
- package/dist/policy.js +52 -1
- package/dist/runs.js +8 -3
- package/dist/trace-matrix.d.ts +13 -0
- package/dist/trace-matrix.js +182 -0
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
package/dist/content/hooks.js
CHANGED
|
@@ -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
|
-
|
|
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=$(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
581
|
-
|
|
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 =
|
|
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
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
|
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/
|
|
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;
|