devlog-tracker 0.27.0 → 0.30.0
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 +109 -100
- package/README.zh-TW.md +235 -0
- package/claude/hooks.json +21 -0
- package/cli/agents-md.js +1 -0
- package/commands/keep.md +18 -2
- package/commands/lessons-drift.md +2 -2
- package/commands/search.md +22 -0
- package/commands/status.md +2 -1
- package/core/scripts/clean-devlog.sh +1 -0
- package/core/scripts/devlog-lock.sh +17 -1
- package/core/scripts/devlog-path.sh +15 -8
- package/core/scripts/enforce-devlog.sh +25 -0
- package/core/scripts/handoff-file.sh +106 -0
- package/core/scripts/lessons-advisory-state.sh +51 -0
- package/core/scripts/lessons-append.sh +16 -0
- package/core/scripts/lessons-drift-set.sh +14 -10
- package/core/scripts/lessons-on.sh +8 -3
- package/core/scripts/lessons-subagent-done.sh +36 -0
- package/core/scripts/lessons-subagent-start.sh +36 -0
- package/core/scripts/round-start.sh +65 -16
- package/core/scripts/search-devlog.sh +63 -0
- package/core/scripts/session-start-devlog.sh +7 -0
- package/core/scripts/status-devlog.sh +4 -4
- package/core/scripts/tests/test-clean-devlog.sh +12 -0
- package/core/scripts/tests/test-devlog-lock.sh +39 -0
- package/core/scripts/tests/test-devlog-path.sh +44 -0
- package/core/scripts/tests/test-enforce-devlog-files.sh +11 -0
- package/core/scripts/tests/test-enforce-devlog-session-handoff.sh +178 -0
- package/core/scripts/tests/test-enforce-devlog-workspace.sh +35 -0
- package/core/scripts/tests/test-enforce-devlog.sh +174 -0
- package/core/scripts/tests/test-handoff-file.sh +98 -0
- package/core/scripts/tests/test-lessons-advisory-state.sh +55 -0
- package/core/scripts/tests/test-lessons-append.sh +18 -0
- package/core/scripts/tests/test-lessons-drift-set.sh +14 -5
- package/core/scripts/tests/test-lessons-on-off.sh +16 -6
- package/core/scripts/tests/test-lessons-subagent-hooks.sh +95 -0
- package/core/scripts/tests/test-round-start.sh +296 -13
- package/core/scripts/tests/test-search-devlog.sh +126 -0
- package/core/scripts/tests/test-session-start-devlog.sh +59 -0
- package/core/scripts/tests/test-status-span.sh +3 -3
- package/package.json +2 -2
- package/skills/devlog-tracker/SKILL.md +83 -13
- package/skills/devlog-tracker/references/checkpoint-mode.md +21 -6
- package/skills/devlog-tracker/references/contract.md +83 -0
- package/skills/devlog-tracker/references/lessons-mode.md +38 -8
|
@@ -14,23 +14,32 @@ OUT="$(CLAUDE_PROJECT_DIR="$TMP" bash "$SCRIPT_DIR/lessons-drift-set.sh" 5)"
|
|
|
14
14
|
[ "$OUT" = "LESSONS_NOT_ENABLED" ] && echo "PASS: lessons not enabled" || { echo "FAIL: $OUT"; FAIL=1; }
|
|
15
15
|
|
|
16
16
|
touch "$TMP/.devlog/.lessons-enabled"
|
|
17
|
-
printf '%s\n' '{"
|
|
17
|
+
printf '%s\n' '{"count": 2, "threshold": 3}' > "$TMP/.devlog/.lessons-advisory-state"
|
|
18
18
|
|
|
19
19
|
OUT="$(CLAUDE_PROJECT_DIR="$TMP" bash "$SCRIPT_DIR/lessons-drift-set.sh" 5)"
|
|
20
20
|
case "$OUT" in *LESSONS_DRIFT_THRESHOLD=5*) echo "PASS: set 5" ;; *) echo "FAIL: $OUT"; FAIL=1 ;; esac
|
|
21
|
-
grep -q '"
|
|
21
|
+
grep -q '"count": 2' "$TMP/.devlog/.lessons-advisory-state" \
|
|
22
22
|
&& echo "PASS: count preserved" || { echo "FAIL: count reset"; FAIL=1; }
|
|
23
|
-
grep -q '"threshold": 5' "$TMP/.devlog/.lessons-
|
|
23
|
+
grep -q '"threshold": 5' "$TMP/.devlog/.lessons-advisory-state" \
|
|
24
24
|
&& echo "PASS: threshold written" || { echo "FAIL: threshold"; FAIL=1; }
|
|
25
25
|
|
|
26
26
|
CLAUDE_PROJECT_DIR="$TMP" bash "$SCRIPT_DIR/lessons-drift-set.sh" 0 >/dev/null 2>&1
|
|
27
27
|
[ $? -eq 1 ] && echo "PASS: reject 0" || { echo "FAIL: accepted 0"; FAIL=1; }
|
|
28
28
|
|
|
29
|
-
rm -f "$TMP/.devlog/.lessons-
|
|
29
|
+
rm -f "$TMP/.devlog/.lessons-advisory-state"
|
|
30
30
|
OUT="$(CLAUDE_PROJECT_DIR="$TMP" bash "$SCRIPT_DIR/lessons-drift-set.sh" 7)"
|
|
31
31
|
case "$OUT" in *LESSONS_DRIFT_THRESHOLD=7*) echo "PASS: create 7" ;; *) echo "FAIL: create $OUT"; FAIL=1 ;; esac
|
|
32
|
-
grep -q '"
|
|
32
|
+
grep -q '"count": 0' "$TMP/.devlog/.lessons-advisory-state" \
|
|
33
33
|
&& echo "PASS: create count 0" || { echo "FAIL: create count"; FAIL=1; }
|
|
34
34
|
|
|
35
|
+
# --- migrates an old .lessons-drift-state before applying the new threshold
|
|
36
|
+
rm -f "$TMP/.devlog/.lessons-advisory-state"
|
|
37
|
+
printf '%s\n' '{"mismatch_count": 4, "threshold": 3}' > "$TMP/.devlog/.lessons-drift-state"
|
|
38
|
+
OUT="$(CLAUDE_PROJECT_DIR="$TMP" bash "$SCRIPT_DIR/lessons-drift-set.sh" 9)"
|
|
39
|
+
case "$OUT" in *LESSONS_DRIFT_THRESHOLD=9*) echo "PASS: migrate then set 9" ;; *) echo "FAIL: $OUT"; FAIL=1 ;; esac
|
|
40
|
+
[ ! -f "$TMP/.devlog/.lessons-drift-state" ] && echo "PASS: old file removed after migrate+set" || { echo "FAIL: old file still present"; FAIL=1; }
|
|
41
|
+
grep -q '"count": 4' "$TMP/.devlog/.lessons-advisory-state" && echo "PASS: migrated count preserved through set" || { echo "FAIL: migrated count lost"; FAIL=1; }
|
|
42
|
+
grep -q '"threshold": 9' "$TMP/.devlog/.lessons-advisory-state" && echo "PASS: new threshold applied after migrate" || { echo "FAIL: threshold not applied"; FAIL=1; }
|
|
43
|
+
|
|
35
44
|
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
36
45
|
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -44,11 +44,21 @@ case "$OUT" in
|
|
|
44
44
|
*"LESSONS_ENABLED="*) echo "PASS: reports LESSONS_ENABLED" ;;
|
|
45
45
|
*) echo "FAIL: expected LESSONS_ENABLED=..., got $OUT"; FAIL=1 ;;
|
|
46
46
|
esac
|
|
47
|
-
assert_file ".lessons-
|
|
48
|
-
grep -q '"
|
|
49
|
-
&& echo "PASS:
|
|
50
|
-
grep -q '"threshold": 3' "$TMP_ROOT/.devlog/.lessons-
|
|
51
|
-
&& echo "PASS:
|
|
47
|
+
assert_file ".lessons-advisory-state created alongside .lessons-enabled" "$TMP_ROOT/.devlog/.lessons-advisory-state"
|
|
48
|
+
grep -q '"count": 0' "$TMP_ROOT/.devlog/.lessons-advisory-state" \
|
|
49
|
+
&& echo "PASS: advisory count starts at 0" || { echo "FAIL: advisory count not 0"; FAIL=1; }
|
|
50
|
+
grep -q '"threshold": 3' "$TMP_ROOT/.devlog/.lessons-advisory-state" \
|
|
51
|
+
&& echo "PASS: advisory threshold defaults to 3" || { echo "FAIL: advisory threshold not 3"; FAIL=1; }
|
|
52
|
+
|
|
53
|
+
# --- lessons-on migrates an old .lessons-drift-state on re-run -------------
|
|
54
|
+
rm -f "$TMP_ROOT/.devlog/.lessons-advisory-state"
|
|
55
|
+
printf '%s\n' '{"mismatch_count": 2, "threshold": 7}' > "$TMP_ROOT/.devlog/.lessons-drift-state"
|
|
56
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-on.sh")"
|
|
57
|
+
assert_exit "lessons-on migrates old state file -> 0" 0 $?
|
|
58
|
+
assert_file "migration produced .lessons-advisory-state" "$TMP_ROOT/.devlog/.lessons-advisory-state"
|
|
59
|
+
assert_not_file "migration removed .lessons-drift-state" "$TMP_ROOT/.devlog/.lessons-drift-state"
|
|
60
|
+
grep -q '"count": 2' "$TMP_ROOT/.devlog/.lessons-advisory-state" && echo "PASS: migrated count preserved" || { echo "FAIL: migrated count lost"; FAIL=1; }
|
|
61
|
+
grep -q '"threshold": 7' "$TMP_ROOT/.devlog/.lessons-advisory-state" && echo "PASS: migrated threshold preserved" || { echo "FAIL: migrated threshold lost"; FAIL=1; }
|
|
52
62
|
|
|
53
63
|
# --- lessons-off disables, leaves main switch and any lessons files alone --
|
|
54
64
|
echo 'keep me' > "$TMP_ROOT/.devlog/devlog.lessons.foo.md"
|
|
@@ -61,7 +71,7 @@ esac
|
|
|
61
71
|
assert_not_file ".lessons-enabled removed" "$TMP_ROOT/.devlog/.lessons-enabled"
|
|
62
72
|
assert_file "main .enabled untouched" "$TMP_ROOT/.devlog/.enabled"
|
|
63
73
|
assert_file "lessons file untouched" "$TMP_ROOT/.devlog/devlog.lessons.foo.md"
|
|
64
|
-
assert_file ".lessons-
|
|
74
|
+
assert_file ".lessons-advisory-state untouched by lessons-off" "$TMP_ROOT/.devlog/.lessons-advisory-state"
|
|
65
75
|
|
|
66
76
|
# --- lessons-off when already off -> reports NOT_ENABLED, no error --------
|
|
67
77
|
OUT="$(bash "$SCRIPT_DIR/lessons-off.sh")"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for lessons-subagent-start.sh (SubagentStart) and
|
|
3
|
+
# lessons-subagent-done.sh (PostToolUse Agent|Task). Run:
|
|
4
|
+
# bash core/scripts/tests/test-lessons-subagent-hooks.sh
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
+
TMP_ROOT="$(mktemp -d)"
|
|
9
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
10
|
+
|
|
11
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
12
|
+
DEVLOG_DIR="$TMP_ROOT/.devlog"
|
|
13
|
+
mkdir -p "$DEVLOG_DIR"
|
|
14
|
+
|
|
15
|
+
FAIL=0
|
|
16
|
+
assert_contains() {
|
|
17
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
18
|
+
case "$haystack" in
|
|
19
|
+
*"$needle"*) echo "PASS: $desc" ;;
|
|
20
|
+
*) echo "FAIL: $desc (missing: $needle)"; FAIL=1 ;;
|
|
21
|
+
esac
|
|
22
|
+
}
|
|
23
|
+
assert_empty() {
|
|
24
|
+
local desc="$1" out="$2"
|
|
25
|
+
if [ -z "$out" ]; then echo "PASS: $desc"; else echo "FAIL: $desc (got: $out)"; FAIL=1; fi
|
|
26
|
+
}
|
|
27
|
+
assert_valid_json() {
|
|
28
|
+
local desc="$1" out="$2"
|
|
29
|
+
if ! command -v jq >/dev/null 2>&1; then echo "SKIP: $desc (no jq)"; return 0; fi
|
|
30
|
+
if printf '%s' "$out" | jq -e . >/dev/null 2>&1; then echo "PASS: $desc"; else echo "FAIL: $desc (invalid JSON: $out)"; FAIL=1; fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
START_IN='{"session_id":"s1","cwd":"/elsewhere/.claude/worktrees/agent-x","agent_id":"a1","agent_type":"general-purpose","hook_event_name":"SubagentStart"}'
|
|
34
|
+
SYNC_DONE='{"session_id":"s1","hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"prompt":"p"},"tool_response":{"status":"completed","content":[{"type":"text","text":"done"}]}}'
|
|
35
|
+
ASYNC_DONE='{"session_id":"s1","hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"prompt":"p"},"tool_response":{"isAsync":true,"status":"async_launched","agentId":"a1"}}'
|
|
36
|
+
NESTED_DONE='{"session_id":"s1","agent_id":"a1","hook_event_name":"PostToolUse","tool_name":"Agent","tool_input":{"prompt":"p"},"tool_response":{"status":"completed"}}'
|
|
37
|
+
|
|
38
|
+
# --- disabled: both hooks silent -------------------------------------------
|
|
39
|
+
OUT="$(printf '%s' "$START_IN" | bash "$SCRIPT_DIR/lessons-subagent-start.sh")"
|
|
40
|
+
assert_empty "start: .enabled absent -> silent" "$OUT"
|
|
41
|
+
OUT="$(printf '%s' "$SYNC_DONE" | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
42
|
+
assert_empty "done: .enabled absent -> silent" "$OUT"
|
|
43
|
+
|
|
44
|
+
touch "$DEVLOG_DIR/.enabled"
|
|
45
|
+
OUT="$(printf '%s' "$START_IN" | bash "$SCRIPT_DIR/lessons-subagent-start.sh")"
|
|
46
|
+
assert_empty "start: lessons off -> silent" "$OUT"
|
|
47
|
+
OUT="$(printf '%s' "$SYNC_DONE" | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
48
|
+
assert_empty "done: lessons off -> silent" "$OUT"
|
|
49
|
+
|
|
50
|
+
touch "$DEVLOG_DIR/.lessons-enabled"
|
|
51
|
+
|
|
52
|
+
# --- SubagentStart: inject lessons-append.sh instructions with absolute paths
|
|
53
|
+
OUT="$(printf '%s' "$START_IN" | bash "$SCRIPT_DIR/lessons-subagent-start.sh")"
|
|
54
|
+
assert_valid_json "start: output is valid JSON" "$OUT"
|
|
55
|
+
assert_contains "start: hookEventName SubagentStart" '"hookEventName":"SubagentStart"' "$OUT"
|
|
56
|
+
assert_contains "start: additionalContext present" '"additionalContext":' "$OUT"
|
|
57
|
+
assert_contains "start: pins the main project dir, not the agent cwd" "DEVLOG_PROJECT_DIR='$TMP_ROOT'" "$OUT"
|
|
58
|
+
assert_contains "start: absolute lessons-append.sh path" "$SCRIPT_DIR/lessons-append.sh" "$OUT"
|
|
59
|
+
assert_contains "start: asks to mention it in the final report" "最終回報" "$OUT"
|
|
60
|
+
|
|
61
|
+
# relative CLAUDE_PROJECT_DIR still resolves to an absolute path
|
|
62
|
+
(cd "$TMP_ROOT" && OUT="$(printf '%s' "$START_IN" | CLAUDE_PROJECT_DIR=. bash "$SCRIPT_DIR/lessons-subagent-start.sh")"
|
|
63
|
+
case "$OUT" in *"DEVLOG_PROJECT_DIR='$TMP_ROOT'"*|*"DEVLOG_PROJECT_DIR='$(pwd -P)'"*) echo "PASS: start: relative project dir made absolute" ;;
|
|
64
|
+
*) echo "FAIL: start: relative project dir not made absolute ($OUT)"; exit 1 ;; esac) || FAIL=1
|
|
65
|
+
|
|
66
|
+
# path needing JSON escaping stays valid JSON
|
|
67
|
+
QDIR="$TMP_ROOT/we\"ird"
|
|
68
|
+
mkdir -p "$QDIR/.devlog"
|
|
69
|
+
touch "$QDIR/.devlog/.enabled" "$QDIR/.devlog/.lessons-enabled"
|
|
70
|
+
OUT="$(printf '%s' "$START_IN" | CLAUDE_PROJECT_DIR="$QDIR" bash "$SCRIPT_DIR/lessons-subagent-start.sh")"
|
|
71
|
+
assert_valid_json "start: quote in project path still valid JSON" "$OUT"
|
|
72
|
+
|
|
73
|
+
# --- PostToolUse(Agent): foreground completion only -------------------------
|
|
74
|
+
OUT="$(printf '%s' "$SYNC_DONE" | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
75
|
+
assert_valid_json "done: output is valid JSON" "$OUT"
|
|
76
|
+
assert_contains "done: hookEventName PostToolUse" '"hookEventName":"PostToolUse"' "$OUT"
|
|
77
|
+
assert_contains "done: sub agent advisory" "[Lessons Mode 提示] sub agent 完成" "$OUT"
|
|
78
|
+
|
|
79
|
+
OUT="$(printf '%s' "$ASYNC_DONE" | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
80
|
+
assert_empty "done: async launch -> silent (task-notification covers it)" "$OUT"
|
|
81
|
+
|
|
82
|
+
OUT="$(printf '%s' "$NESTED_DONE" | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
83
|
+
assert_empty "done: nested (agent_id set) -> silent" "$OUT"
|
|
84
|
+
|
|
85
|
+
OUT="$(printf '%s' 'not json' | bash "$SCRIPT_DIR/lessons-subagent-done.sh")"
|
|
86
|
+
RC=$?
|
|
87
|
+
[ "$RC" -eq 0 ] && echo "PASS: done: garbage stdin fails open" || { echo "FAIL: done: garbage stdin exit $RC"; FAIL=1; }
|
|
88
|
+
|
|
89
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
90
|
+
echo "All checks passed."
|
|
91
|
+
exit 0
|
|
92
|
+
else
|
|
93
|
+
echo "Some checks FAILED."
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
@@ -634,6 +634,49 @@ fi
|
|
|
634
634
|
assert_file_absent "task-notification during open span: no .round-open" "$DEVLOG_DIR/.round-open"
|
|
635
635
|
rm -f "$DEVLOG_DIR/.span-open"
|
|
636
636
|
|
|
637
|
+
# --- 19: devlog-tracker admin slash commands are exempt from Round logging -
|
|
638
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-open" "$DEVLOG_DIR/.turn-start" "$DEVLOG_DIR/.round-current.md" "$DEVLOG_DIR/.checkpoint-state"
|
|
639
|
+
printf '%s\n' '{"rounds_since_checkpoint": 3, "max_silent_rounds": 20, "checkpoint_marker_count": 0}' > "$DEVLOG_DIR/.checkpoint-state"
|
|
640
|
+
ADMIN_PROMPT='<command-name>/devlog-tracker:status</command-name><command-message>status</command-message><command-args></command-args>'
|
|
641
|
+
printf '{"prompt":"%s"}' "$ADMIN_PROMPT" | bash "$SCRIPT_DIR/round-start.sh"
|
|
642
|
+
assert_file_absent "admin command: no .round-current.md" "$DEVLOG_DIR/.round-current.md"
|
|
643
|
+
assert_file_absent "admin command: no .round-open" "$DEVLOG_DIR/.round-open"
|
|
644
|
+
assert_file_absent "admin command: no .turn-start" "$DEVLOG_DIR/.turn-start"
|
|
645
|
+
assert_file_absent "admin command: no devlog.md created" "$DEVLOG_DIR/devlog.md"
|
|
646
|
+
CP="$(grep -o '"rounds_since_checkpoint"[[:space:]]*:[[:space:]]*[0-9]\+' "$DEVLOG_DIR/.checkpoint-state" | grep -o '[0-9]\+$')"
|
|
647
|
+
if [ "$CP" = "3" ]; then
|
|
648
|
+
echo "PASS: admin command does not increment rounds_since_checkpoint"
|
|
649
|
+
else
|
|
650
|
+
echo "FAIL: expected rounds_since_checkpoint to stay 3, got $CP"
|
|
651
|
+
FAIL=1
|
|
652
|
+
fi
|
|
653
|
+
rm -f "$DEVLOG_DIR/.checkpoint-state"
|
|
654
|
+
|
|
655
|
+
# --- 20: a namespaced-but-different command name is not falsely exempted ---
|
|
656
|
+
# "lessons-on" must not match the "lessons" case arm (prefix collision guard).
|
|
657
|
+
LESSONS_ON_PROMPT='<command-name>/devlog-tracker:lessons-on</command-name><command-message>lessons-on</command-message><command-args></command-args>'
|
|
658
|
+
printf '{"prompt":"%s"}' "$LESSONS_ON_PROMPT" | bash "$SCRIPT_DIR/round-start.sh"
|
|
659
|
+
if [ -f "$DEVLOG_DIR/.round-current.md" ]; then
|
|
660
|
+
echo "FAIL: lessons-on command should still be exempt (matched its own arm)"
|
|
661
|
+
FAIL=1
|
|
662
|
+
else
|
|
663
|
+
echo "PASS: lessons-on command exempt via its own case arm"
|
|
664
|
+
fi
|
|
665
|
+
|
|
666
|
+
# --- 21: /devlog-tracker:continue is NOT exempt -- real work may follow ----
|
|
667
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-open" "$DEVLOG_DIR/.turn-start" "$DEVLOG_DIR/.round-current.md"
|
|
668
|
+
CONTINUE_PROMPT='<command-name>/devlog-tracker:continue</command-name><command-message>continue</command-message><command-args></command-args>'
|
|
669
|
+
printf '{"prompt":"%s"}' "$CONTINUE_PROMPT" | bash "$SCRIPT_DIR/round-start.sh"
|
|
670
|
+
CUR_BODY="$(cat "$DEVLOG_DIR/.round-current.md" 2>/dev/null || echo '')"
|
|
671
|
+
assert_contains "continue command still opens a Round" "## Round 1 —" "$CUR_BODY"
|
|
672
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
673
|
+
echo "PASS: continue command still sets .round-open"
|
|
674
|
+
else
|
|
675
|
+
echo "FAIL: continue command should still set .round-open"
|
|
676
|
+
FAIL=1
|
|
677
|
+
fi
|
|
678
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-open" "$DEVLOG_DIR/.turn-start" "$DEVLOG_DIR/.round-current.md"
|
|
679
|
+
|
|
637
680
|
# --- workspace claim on next prompt ----------------------------------------
|
|
638
681
|
WS="$TMP_ROOT/ws"
|
|
639
682
|
mkdir -p "$WS/.devlog"
|
|
@@ -674,6 +717,10 @@ OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh"
|
|
|
674
717
|
grep -q '^## Round 2' "$WS/.devlog/.round-current.md" && echo "PASS: still opened Round 2" || { echo "FAIL: no round 2"; FAIL=1; }
|
|
675
718
|
|
|
676
719
|
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md"
|
|
720
|
+
# This fixture is reused below by the drift-only counter assertions (1 -> 2 ->
|
|
721
|
+
# reset-at-3). Its ### Status must stay non-BLOCKED: if it were BLOCKED, the
|
|
722
|
+
# BLOCKED-round bump would also fire on the same invocation and double-count
|
|
723
|
+
# the shared counter, breaking those assertions with a confusing failure.
|
|
677
724
|
cat > "$WS/.devlog/devlog.md" <<EOF
|
|
678
725
|
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
679
726
|
|
|
@@ -708,35 +755,35 @@ MARKER="$(cat "$WS/.devlog/.workspace-mismatch")"
|
|
|
708
755
|
# --- lessons drift counter: mechanical nudge on repeated mismatch --------
|
|
709
756
|
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.workspace-mismatch"
|
|
710
757
|
touch "$WS/.devlog/.lessons-enabled"
|
|
711
|
-
printf '%s\n' '{"
|
|
758
|
+
printf '%s\n' '{"count": 0, "threshold": 3}' > "$WS/.devlog/.lessons-advisory-state"
|
|
712
759
|
|
|
713
760
|
OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
714
761
|
assert_not_contains "drift count 1/3: no nudge yet" "[Lessons Mode 提示]" "$OUT"
|
|
715
|
-
grep -q '"
|
|
762
|
+
grep -q '"count": 1' "$WS/.devlog/.lessons-advisory-state" && echo "PASS: drift count -> 1" || { echo "FAIL: drift count not 1"; FAIL=1; }
|
|
716
763
|
|
|
717
764
|
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md"
|
|
718
765
|
OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
719
766
|
assert_not_contains "drift count 2/3: no nudge yet" "[Lessons Mode 提示]" "$OUT"
|
|
720
|
-
grep -q '"
|
|
767
|
+
grep -q '"count": 2' "$WS/.devlog/.lessons-advisory-state" && echo "PASS: drift count -> 2" || { echo "FAIL: drift count not 2"; FAIL=1; }
|
|
721
768
|
|
|
722
769
|
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md"
|
|
723
770
|
OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
724
771
|
assert_contains "drift count hits threshold: prints nudge" "[Lessons Mode 提示]" "$OUT"
|
|
725
|
-
grep -q '"
|
|
772
|
+
grep -q '"count": 0' "$WS/.devlog/.lessons-advisory-state" && echo "PASS: drift count reset after nudge" || { echo "FAIL: drift count not reset"; FAIL=1; }
|
|
726
773
|
|
|
727
774
|
# without .lessons-enabled: no counting, no nudge, state file untouched
|
|
728
775
|
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.lessons-enabled"
|
|
729
|
-
printf '%s\n' '{"
|
|
776
|
+
printf '%s\n' '{"count": 2, "threshold": 3}' > "$WS/.devlog/.lessons-advisory-state"
|
|
730
777
|
OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
731
778
|
assert_not_contains "lessons off: no nudge even near threshold" "[Lessons Mode 提示]" "$OUT"
|
|
732
|
-
grep -q '"
|
|
733
|
-
rm -f "$WS/.devlog/.lessons-
|
|
779
|
+
grep -q '"count": 2' "$WS/.devlog/.lessons-advisory-state" && echo "PASS: drift count untouched when lessons off" || { echo "FAIL: drift count changed when lessons off"; FAIL=1; }
|
|
780
|
+
rm -f "$WS/.devlog/.lessons-advisory-state" "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md"
|
|
734
781
|
|
|
735
782
|
# --- lessons drift counter: upgrade path — .lessons-enabled exists (from
|
|
736
|
-
# before this branch) but .lessons-
|
|
783
|
+
# before this branch) but .lessons-advisory-state was never created. round-start.sh
|
|
737
784
|
# must create it with defaults and still act on this same invocation's
|
|
738
785
|
# mismatch, not require a second message ------------------------------------
|
|
739
|
-
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.workspace-mismatch" "$WS/.devlog/.lessons-
|
|
786
|
+
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.workspace-mismatch" "$WS/.devlog/.lessons-advisory-state"
|
|
740
787
|
touch "$WS/.devlog/.lessons-enabled"
|
|
741
788
|
cat > "$WS/.devlog/devlog.md" <<EOF
|
|
742
789
|
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
@@ -761,11 +808,11 @@ do x
|
|
|
761
808
|
IN_PROGRESS
|
|
762
809
|
EOF
|
|
763
810
|
OUT="$(printf '%s' '{"prompt":"keep going"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
764
|
-
[ -f "$WS/.devlog/.lessons-
|
|
765
|
-
grep -q '"
|
|
766
|
-
grep -q '"threshold": 3' "$WS/.devlog/.lessons-
|
|
811
|
+
[ -f "$WS/.devlog/.lessons-advisory-state" ] && echo "PASS: upgrade path creates .lessons-advisory-state on first drift" || { echo "FAIL: .lessons-advisory-state not created for pre-existing lessons-enabled"; FAIL=1; }
|
|
812
|
+
grep -q '"count": 1' "$WS/.devlog/.lessons-advisory-state" 2>/dev/null && echo "PASS: newly-created state already counted this invocation's mismatch (1)" || { echo "FAIL: count not 1 in newly-created state"; FAIL=1; }
|
|
813
|
+
grep -q '"threshold": 3' "$WS/.devlog/.lessons-advisory-state" 2>/dev/null && echo "PASS: newly-created state has default threshold 3" || { echo "FAIL: threshold not 3 in newly-created state"; FAIL=1; }
|
|
767
814
|
assert_not_contains "upgrade path: no nudge yet on first-ever drift (1 < 3)" "[Lessons Mode 提示]" "$OUT"
|
|
768
|
-
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.lessons-enabled" "$WS/.devlog/.lessons-
|
|
815
|
+
rm -f "$WS/.devlog/.round-open" "$WS/.devlog/.round-current.md" "$WS/.devlog/.lessons-enabled" "$WS/.devlog/.lessons-advisory-state"
|
|
769
816
|
|
|
770
817
|
cat > "$WS/.devlog/devlog.md" <<EOF
|
|
771
818
|
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
@@ -816,6 +863,242 @@ printf '%s' '{"prompt":"tick"}' | bash "$SCRIPT_DIR/round-start.sh" >/dev/null
|
|
|
816
863
|
[ ! -f "$WS/.devlog/.workspace-mismatch" ] && echo "PASS: span skip writes no marker" || { echo "FAIL: span marker"; FAIL=1; }
|
|
817
864
|
rm -f "$WS/.devlog/.span-open"
|
|
818
865
|
|
|
866
|
+
# --- BLOCKED-round accumulation shares the advisory counter -----------------
|
|
867
|
+
BLK_DIR="$(mktemp -d)"
|
|
868
|
+
export CLAUDE_PROJECT_DIR="$BLK_DIR"
|
|
869
|
+
mkdir -p "$BLK_DIR/.devlog"
|
|
870
|
+
touch "$BLK_DIR/.devlog/.enabled" "$BLK_DIR/.devlog/.lessons-enabled"
|
|
871
|
+
printf '%s\n' '{"count": 0, "threshold": 3}' > "$BLK_DIR/.devlog/.lessons-advisory-state"
|
|
872
|
+
cat > "$BLK_DIR/.devlog/devlog.md" <<'EOF'
|
|
873
|
+
## Round 1 — 2026-09-20T00:00:00+08:00
|
|
874
|
+
|
|
875
|
+
### Summary
|
|
876
|
+
s
|
|
877
|
+
|
|
878
|
+
### Reply
|
|
879
|
+
fixture reply.
|
|
880
|
+
|
|
881
|
+
### Handoff
|
|
882
|
+
#### 現況
|
|
883
|
+
waiting
|
|
884
|
+
#### 完成條件
|
|
885
|
+
缺的外部輸入已出現,且可觀察條件達成。
|
|
886
|
+
#### 下一步
|
|
887
|
+
apply the answer
|
|
888
|
+
|
|
889
|
+
### Status
|
|
890
|
+
BLOCKED
|
|
891
|
+
EOF
|
|
892
|
+
OUT="$(printf '%s' '{"prompt":"next"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
893
|
+
assert_not_contains "BLOCKED round 1/3: no advisory yet" "[Lessons Mode 提示]" "$OUT"
|
|
894
|
+
grep -q '"count": 1' "$BLK_DIR/.devlog/.lessons-advisory-state" && echo "PASS: BLOCKED round bumped counter to 1" || { echo "FAIL: counter not bumped to 1"; FAIL=1; }
|
|
895
|
+
|
|
896
|
+
rm -f "$BLK_DIR/.devlog/.round-open" "$BLK_DIR/.devlog/.round-current.md"
|
|
897
|
+
cat >> "$BLK_DIR/.devlog/devlog.md" <<'EOF'
|
|
898
|
+
|
|
899
|
+
## Round 2 — 2026-09-20T00:05:00+08:00
|
|
900
|
+
|
|
901
|
+
### Summary
|
|
902
|
+
s
|
|
903
|
+
|
|
904
|
+
### Reply
|
|
905
|
+
fixture reply.
|
|
906
|
+
|
|
907
|
+
### Handoff
|
|
908
|
+
#### 現況
|
|
909
|
+
waiting
|
|
910
|
+
#### 完成條件
|
|
911
|
+
缺的外部輸入已出現,且可觀察條件達成。
|
|
912
|
+
#### 下一步
|
|
913
|
+
apply the answer
|
|
914
|
+
|
|
915
|
+
### Status
|
|
916
|
+
BLOCKED
|
|
917
|
+
EOF
|
|
918
|
+
OUT="$(printf '%s' '{"prompt":"next again"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
919
|
+
assert_not_contains "BLOCKED round 2/3: no advisory yet" "[Lessons Mode 提示]" "$OUT"
|
|
920
|
+
grep -q '"count": 2' "$BLK_DIR/.devlog/.lessons-advisory-state" && echo "PASS: BLOCKED round bumped counter to 2" || { echo "FAIL: counter not bumped to 2"; FAIL=1; }
|
|
921
|
+
|
|
922
|
+
rm -f "$BLK_DIR/.devlog/.round-open" "$BLK_DIR/.devlog/.round-current.md"
|
|
923
|
+
cat >> "$BLK_DIR/.devlog/devlog.md" <<'EOF'
|
|
924
|
+
|
|
925
|
+
## Round 3 — 2026-09-20T00:10:00+08:00
|
|
926
|
+
|
|
927
|
+
### Summary
|
|
928
|
+
s
|
|
929
|
+
|
|
930
|
+
### Reply
|
|
931
|
+
fixture reply.
|
|
932
|
+
|
|
933
|
+
### Handoff
|
|
934
|
+
#### 現況
|
|
935
|
+
waiting
|
|
936
|
+
#### 完成條件
|
|
937
|
+
缺的外部輸入已出現,且可觀察條件達成。
|
|
938
|
+
#### 下一步
|
|
939
|
+
apply the answer
|
|
940
|
+
|
|
941
|
+
### Status
|
|
942
|
+
BLOCKED
|
|
943
|
+
EOF
|
|
944
|
+
OUT="$(printf '%s' '{"prompt":"next once more"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
945
|
+
assert_contains "BLOCKED round 3/3: hits threshold" "[Lessons Mode 提示]" "$OUT"
|
|
946
|
+
grep -q '"count": 0' "$BLK_DIR/.devlog/.lessons-advisory-state" && echo "PASS: counter reset after threshold" || { echo "FAIL: counter not reset"; FAIL=1; }
|
|
947
|
+
rm -rf "$BLK_DIR"
|
|
948
|
+
|
|
949
|
+
# --- BLOCKED->resolved: mechanical, one-shot print, not counted -------------
|
|
950
|
+
RES_DIR="$(mktemp -d)"
|
|
951
|
+
export CLAUDE_PROJECT_DIR="$RES_DIR"
|
|
952
|
+
mkdir -p "$RES_DIR/.devlog"
|
|
953
|
+
touch "$RES_DIR/.devlog/.enabled" "$RES_DIR/.devlog/.lessons-enabled"
|
|
954
|
+
cat > "$RES_DIR/.devlog/devlog.md" <<'EOF'
|
|
955
|
+
## Round 1 — 2026-09-20T00:00:00+08:00
|
|
956
|
+
|
|
957
|
+
### Summary
|
|
958
|
+
s
|
|
959
|
+
|
|
960
|
+
### Reply
|
|
961
|
+
fixture reply.
|
|
962
|
+
|
|
963
|
+
### Handoff
|
|
964
|
+
#### 現況
|
|
965
|
+
waiting
|
|
966
|
+
#### 完成條件
|
|
967
|
+
缺的外部輸入已出現,且可觀察條件達成。
|
|
968
|
+
#### 下一步
|
|
969
|
+
apply the answer
|
|
970
|
+
|
|
971
|
+
### Status
|
|
972
|
+
BLOCKED
|
|
973
|
+
|
|
974
|
+
## Round 2 — 2026-09-20T00:05:00+08:00
|
|
975
|
+
|
|
976
|
+
### Summary
|
|
977
|
+
resolved it
|
|
978
|
+
|
|
979
|
+
### Reply
|
|
980
|
+
fixture reply.
|
|
981
|
+
|
|
982
|
+
### Handoff
|
|
983
|
+
#### 現況
|
|
984
|
+
done
|
|
985
|
+
|
|
986
|
+
### Status
|
|
987
|
+
DONE
|
|
988
|
+
EOF
|
|
989
|
+
OUT="$(printf '%s' '{"prompt":"whats next"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
990
|
+
assert_contains "BLOCKED->resolved prints a one-shot advisory" "上一輪從 BLOCKED 解開了" "$OUT"
|
|
991
|
+
[ ! -f "$RES_DIR/.devlog/.lessons-advisory-state" ] && echo "PASS: resolved-transition print does not touch the shared counter" || { echo "FAIL: resolved-transition print created/touched the counter file"; FAIL=1; }
|
|
992
|
+
|
|
993
|
+
# resolved print requires two rounds of history: with only one round of
|
|
994
|
+
# history (regardless of its own status) there's no "second-to-last" round
|
|
995
|
+
# to compare against.
|
|
996
|
+
rm -rf "$RES_DIR"
|
|
997
|
+
RES2_DIR="$(mktemp -d)"
|
|
998
|
+
export CLAUDE_PROJECT_DIR="$RES2_DIR"
|
|
999
|
+
mkdir -p "$RES2_DIR/.devlog"
|
|
1000
|
+
touch "$RES2_DIR/.devlog/.enabled" "$RES2_DIR/.devlog/.lessons-enabled"
|
|
1001
|
+
cat > "$RES2_DIR/.devlog/devlog.md" <<'EOF'
|
|
1002
|
+
## Round 1 — 2026-09-20T00:00:00+08:00
|
|
1003
|
+
|
|
1004
|
+
### Summary
|
|
1005
|
+
s
|
|
1006
|
+
|
|
1007
|
+
### Reply
|
|
1008
|
+
fixture reply.
|
|
1009
|
+
|
|
1010
|
+
### Handoff
|
|
1011
|
+
#### 現況
|
|
1012
|
+
done
|
|
1013
|
+
|
|
1014
|
+
### Status
|
|
1015
|
+
DONE
|
|
1016
|
+
EOF
|
|
1017
|
+
OUT="$(printf '%s' '{"prompt":"go"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1018
|
+
assert_not_contains "cold start (one round of history): no resolved print" "上一輪從 BLOCKED 解開了" "$OUT"
|
|
1019
|
+
rm -rf "$RES2_DIR"
|
|
1020
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
1021
|
+
|
|
1022
|
+
# --- without .lessons-enabled: BLOCKED accumulation does not run at all ----
|
|
1023
|
+
NOLES_DIR="$(mktemp -d)"
|
|
1024
|
+
export CLAUDE_PROJECT_DIR="$NOLES_DIR"
|
|
1025
|
+
mkdir -p "$NOLES_DIR/.devlog"
|
|
1026
|
+
touch "$NOLES_DIR/.devlog/.enabled"
|
|
1027
|
+
cat > "$NOLES_DIR/.devlog/devlog.md" <<'EOF'
|
|
1028
|
+
## Round 1 — 2026-09-20T00:00:00+08:00
|
|
1029
|
+
|
|
1030
|
+
### Summary
|
|
1031
|
+
s
|
|
1032
|
+
|
|
1033
|
+
### Reply
|
|
1034
|
+
fixture reply.
|
|
1035
|
+
|
|
1036
|
+
### Handoff
|
|
1037
|
+
#### 現況
|
|
1038
|
+
waiting
|
|
1039
|
+
#### 完成條件
|
|
1040
|
+
缺的外部輸入已出現,且可觀察條件達成。
|
|
1041
|
+
#### 下一步
|
|
1042
|
+
apply the answer
|
|
1043
|
+
|
|
1044
|
+
### Status
|
|
1045
|
+
BLOCKED
|
|
1046
|
+
EOF
|
|
1047
|
+
OUT="$(printf '%s' '{"prompt":"next"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1048
|
+
assert_not_contains "lessons off: no BLOCKED-accumulation advisory" "[Lessons Mode 提示]" "$OUT"
|
|
1049
|
+
[ ! -f "$NOLES_DIR/.devlog/.lessons-advisory-state" ] && echo "PASS: lessons off leaves no advisory-state file" || { echo "FAIL: advisory-state file created while lessons off"; FAIL=1; }
|
|
1050
|
+
rm -rf "$NOLES_DIR"
|
|
1051
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
1052
|
+
|
|
1053
|
+
# --- lessons: background agent / workflow task-notification advisory ------
|
|
1054
|
+
TN_DIR="$(mktemp -d)"
|
|
1055
|
+
mkdir -p "$TN_DIR/.devlog"
|
|
1056
|
+
touch "$TN_DIR/.devlog/.enabled" "$TN_DIR/.devlog/.lessons-enabled"
|
|
1057
|
+
export CLAUDE_PROJECT_DIR="$TN_DIR"
|
|
1058
|
+
tn_notif() {
|
|
1059
|
+
printf '{"prompt":"<task-notification><task-id>t9</task-id><status>%s</status><summary>Agent \\"x\\" finished</summary></task-notification>"}' "$1"
|
|
1060
|
+
}
|
|
1061
|
+
tn_reset() {
|
|
1062
|
+
rm -f "$TN_DIR/.devlog/.round-open" "$TN_DIR/.devlog/.round-current.md"
|
|
1063
|
+
printf '## Round 1 — 2026-09-23T00:00:00+0800\n\n### User Input\n```text\nx\n```\n\n### Summary\ns\n\n### Status\nDONE\n' > "$TN_DIR/.devlog/devlog.md"
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
tn_reset
|
|
1067
|
+
printf '%s\n' '{"count": 0, "threshold": 3}' > "$TN_DIR/.devlog/.lessons-advisory-state"
|
|
1068
|
+
OUT="$(tn_notif completed | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1069
|
+
assert_contains "task-notif completed: sub agent advisory printed" "[Lessons Mode 提示] 背景任務完成(status=completed)" "$OUT"
|
|
1070
|
+
grep -q '"count": 0' "$TN_DIR/.devlog/.lessons-advisory-state" && echo "PASS: task-notif completed does not bump counter" || { echo "FAIL: completed bumped counter"; FAIL=1; }
|
|
1071
|
+
|
|
1072
|
+
tn_reset
|
|
1073
|
+
OUT="$(tn_notif failed | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1074
|
+
assert_contains "task-notif failed: advisory printed" "背景任務完成(status=failed)" "$OUT"
|
|
1075
|
+
grep -q '"count": 1' "$TN_DIR/.devlog/.lessons-advisory-state" && echo "PASS: task-notif failed bumps counter" || { echo "FAIL: failed did not bump counter"; FAIL=1; }
|
|
1076
|
+
|
|
1077
|
+
tn_reset
|
|
1078
|
+
printf '%s\n' '{"count": 2, "threshold": 3}' > "$TN_DIR/.devlog/.lessons-advisory-state"
|
|
1079
|
+
OUT="$(tn_notif killed | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1080
|
+
assert_contains "task-notif killed at threshold: shared nudge printed" "流程訊號已累積出現 3 次" "$OUT"
|
|
1081
|
+
grep -q '"count": 0' "$TN_DIR/.devlog/.lessons-advisory-state" && echo "PASS: task-notif killed resets counter at threshold" || { echo "FAIL: killed did not reset counter"; FAIL=1; }
|
|
1082
|
+
|
|
1083
|
+
tn_reset
|
|
1084
|
+
printf '%s\n' '{"round": 1, "ticks_since_checkin": 0, "max_silent_ticks": 5}' > "$TN_DIR/.devlog/.span-open"
|
|
1085
|
+
OUT="$(tn_notif completed | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1086
|
+
assert_contains "task-notif during span: advisory still printed" "背景任務完成(status=completed)" "$OUT"
|
|
1087
|
+
rm -f "$TN_DIR/.devlog/.span-open"
|
|
1088
|
+
|
|
1089
|
+
tn_reset
|
|
1090
|
+
rm -f "$TN_DIR/.devlog/.lessons-enabled" "$TN_DIR/.devlog/.lessons-advisory-state"
|
|
1091
|
+
OUT="$(tn_notif failed | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1092
|
+
assert_not_contains "task-notif with lessons off: no advisory" "[Lessons Mode 提示]" "$OUT"
|
|
1093
|
+
[ ! -f "$TN_DIR/.devlog/.lessons-advisory-state" ] && echo "PASS: task-notif with lessons off leaves no advisory-state" || { echo "FAIL: advisory-state created while lessons off"; FAIL=1; }
|
|
1094
|
+
|
|
1095
|
+
touch "$TN_DIR/.devlog/.lessons-enabled"
|
|
1096
|
+
tn_reset
|
|
1097
|
+
OUT="$(printf '%s' '{"prompt":"plain message"}' | bash "$SCRIPT_DIR/round-start.sh" 2>/dev/null)"
|
|
1098
|
+
assert_not_contains "plain prompt: no background-task advisory" "背景任務完成" "$OUT"
|
|
1099
|
+
rm -rf "$TN_DIR"
|
|
1100
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
1101
|
+
|
|
819
1102
|
if [ "$FAIL" -eq 0 ]; then
|
|
820
1103
|
echo "All checks passed."
|
|
821
1104
|
exit 0
|