devlog-tracker 0.22.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/LICENSE +201 -0
- package/README.md +234 -0
- package/bin/devlog-tracker.js +51 -0
- package/cli/init.js +55 -0
- package/cli/init.test.js +37 -0
- package/cli/merge-hooks.js +60 -0
- package/cli/merge-hooks.test.js +187 -0
- package/cli/platforms/codex.js +13 -0
- package/cli/platforms/cursor.js +13 -0
- package/cli/status.js +14 -0
- package/cli/status.test.js +33 -0
- package/cli/vendor.js +37 -0
- package/cli/vendor.test.js +84 -0
- package/codex/hooks/on-pre-tool.sh +19 -0
- package/codex/hooks/on-session-end.sh +31 -0
- package/codex/hooks/on-session-start.sh +37 -0
- package/codex/hooks/on-stop.sh +18 -0
- package/codex/hooks/on-user-prompt-submit.sh +15 -0
- package/codex/hooks/project-dir.sh +13 -0
- package/codex/hooks/test-adapters.sh +98 -0
- package/codex/hooks.json +54 -0
- package/commands/checkpoint.md +17 -0
- package/commands/clean.md +50 -0
- package/commands/compact.md +24 -0
- package/commands/continue.md +30 -0
- package/commands/keep.md +100 -0
- package/commands/lessons-drift.md +19 -0
- package/commands/lessons-off.md +16 -0
- package/commands/lessons-on.md +20 -0
- package/commands/lessons.md +20 -0
- package/commands/overview.md +32 -0
- package/commands/pause.md +20 -0
- package/commands/resume.md +18 -0
- package/commands/segment-watch.md +21 -0
- package/commands/span.md +13 -0
- package/commands/start.md +19 -0
- package/commands/status.md +14 -0
- package/cursor/hooks/on-pre-tool.sh +24 -0
- package/cursor/hooks/on-session-end.sh +11 -0
- package/cursor/hooks/on-session-start.sh +15 -0
- package/cursor/hooks/on-stop.sh +44 -0
- package/cursor/hooks/on-submit-prompt.sh +28 -0
- package/cursor/hooks/on-tool-failure.sh +16 -0
- package/cursor/hooks/project-dir.sh +10 -0
- package/cursor/hooks/test-adapters.sh +201 -0
- package/cursor/hooks.json +26 -0
- package/hooks/scripts/await-open.sh +23 -0
- package/hooks/scripts/checkpoint-set.sh +44 -0
- package/hooks/scripts/clean-devlog.sh +69 -0
- package/hooks/scripts/close-open-round.sh +186 -0
- package/hooks/scripts/compact-devlog.sh +72 -0
- package/hooks/scripts/detect-pending-question.sh +31 -0
- package/hooks/scripts/devlog-lock.sh +38 -0
- package/hooks/scripts/devlog-md.sh +222 -0
- package/hooks/scripts/devlog-path.sh +70 -0
- package/hooks/scripts/enforce-devlog.sh +629 -0
- package/hooks/scripts/files-snapshot.sh +70 -0
- package/hooks/scripts/json-field.sh +76 -0
- package/hooks/scripts/keep-move.sh +155 -0
- package/hooks/scripts/kept-list.sh +40 -0
- package/hooks/scripts/lessons-append.sh +93 -0
- package/hooks/scripts/lessons-drift-set.sh +42 -0
- package/hooks/scripts/lessons-off.sh +15 -0
- package/hooks/scripts/lessons-on.sh +21 -0
- package/hooks/scripts/lessons-read.sh +60 -0
- package/hooks/scripts/on-session-end.sh +14 -0
- package/hooks/scripts/on-stop-failure.sh +18 -0
- package/hooks/scripts/on-tool-failure.sh +25 -0
- package/hooks/scripts/pause-devlog.sh +13 -0
- package/hooks/scripts/redact-prompt.sh +19 -0
- package/hooks/scripts/resume-devlog.sh +41 -0
- package/hooks/scripts/round-start.sh +333 -0
- package/hooks/scripts/run-tests.sh +26 -0
- package/hooks/scripts/segment-watch-set.sh +47 -0
- package/hooks/scripts/segment-watch.sh +197 -0
- package/hooks/scripts/session-start-devlog.sh +157 -0
- package/hooks/scripts/span-close.sh +9 -0
- package/hooks/scripts/span-open.sh +21 -0
- package/hooks/scripts/start-devlog.sh +24 -0
- package/hooks/scripts/status-devlog.sh +43 -0
- package/hooks/scripts/tests/test-await-open.sh +112 -0
- package/hooks/scripts/tests/test-branch-scoped-integration.sh +108 -0
- package/hooks/scripts/tests/test-checkpoint-set.sh +36 -0
- package/hooks/scripts/tests/test-clean-devlog.sh +160 -0
- package/hooks/scripts/tests/test-cli-init-e2e.sh +37 -0
- package/hooks/scripts/tests/test-close-open-round.sh +325 -0
- package/hooks/scripts/tests/test-compact-devlog.sh +62 -0
- package/hooks/scripts/tests/test-devlog-lock.sh +37 -0
- package/hooks/scripts/tests/test-devlog-md.sh +530 -0
- package/hooks/scripts/tests/test-devlog-path.sh +118 -0
- package/hooks/scripts/tests/test-enforce-devlog-files.sh +233 -0
- package/hooks/scripts/tests/test-enforce-devlog-handoff-order.sh +223 -0
- package/hooks/scripts/tests/test-enforce-devlog-workspace.sh +333 -0
- package/hooks/scripts/tests/test-enforce-devlog.sh +1290 -0
- package/hooks/scripts/tests/test-files-snapshot.sh +142 -0
- package/hooks/scripts/tests/test-json-field.sh +57 -0
- package/hooks/scripts/tests/test-keep-move.sh +127 -0
- package/hooks/scripts/tests/test-kept-list.sh +80 -0
- package/hooks/scripts/tests/test-lessons-append.sh +138 -0
- package/hooks/scripts/tests/test-lessons-drift-set.sh +36 -0
- package/hooks/scripts/tests/test-lessons-on-off.sh +75 -0
- package/hooks/scripts/tests/test-lessons-read.sh +97 -0
- package/hooks/scripts/tests/test-on-interrupt.sh +186 -0
- package/hooks/scripts/tests/test-redact-prompt.sh +27 -0
- package/hooks/scripts/tests/test-resume-devlog.sh +35 -0
- package/hooks/scripts/tests/test-round-start.sh +825 -0
- package/hooks/scripts/tests/test-segment-watch-set.sh +75 -0
- package/hooks/scripts/tests/test-segment-watch.sh +468 -0
- package/hooks/scripts/tests/test-session-start-devlog.sh +508 -0
- package/hooks/scripts/tests/test-start-pause-devlog.sh +84 -0
- package/hooks/scripts/tests/test-status-span.sh +50 -0
- package/hooks/scripts/tests/test-workspace-snapshot.sh +130 -0
- package/hooks/scripts/workspace-snapshot.sh +69 -0
- package/package.json +37 -0
- package/skills/devlog-tracker/SKILL.md +411 -0
- package/skills/devlog-tracker/references/checkpoint-mode.md +42 -0
- package/skills/devlog-tracker/references/lessons-mode.md +32 -0
- package/skills/devlog-tracker/references/reply-fold.md +129 -0
- package/skills/devlog-tracker/references/round-segments.md +55 -0
- package/skills/devlog-tracker/references/span-mode.md +65 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for lessons-on.sh / lessons-off.sh (docs/design/lessons-mode.md
|
|
3
|
+
# "Enable / disable"). No framework, plain assert-and-exit, matching this
|
|
4
|
+
# repo's existing style.
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
TMP_ROOT="$(mktemp -d)"
|
|
8
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
9
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
10
|
+
FAIL=0
|
|
11
|
+
assert_exit() {
|
|
12
|
+
local desc="$1" expected="$2" actual="$3"
|
|
13
|
+
if [ "$actual" -eq "$expected" ]; then echo "PASS: $desc"
|
|
14
|
+
else echo "FAIL: $desc (expected $expected got $actual)"; FAIL=1; fi
|
|
15
|
+
}
|
|
16
|
+
assert_file() {
|
|
17
|
+
local desc="$1" path="$2"
|
|
18
|
+
if [ -f "$path" ]; then echo "PASS: $desc"
|
|
19
|
+
else echo "FAIL: $desc"; FAIL=1; fi
|
|
20
|
+
}
|
|
21
|
+
assert_not_file() {
|
|
22
|
+
local desc="$1" path="$2"
|
|
23
|
+
if [ ! -f "$path" ]; then echo "PASS: $desc"
|
|
24
|
+
else echo "FAIL: $desc"; FAIL=1; fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
mkdir -p "$TMP_ROOT/.devlog"
|
|
28
|
+
|
|
29
|
+
# --- lessons-on without the main .enabled switch -> refuses ---------------
|
|
30
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-on.sh")"
|
|
31
|
+
assert_exit "lessons-on without main switch -> exit 0 (advisory refusal, not a hard error)" 0 $?
|
|
32
|
+
case "$OUT" in
|
|
33
|
+
*"NOT_ENABLED"*) echo "PASS: reports NOT_ENABLED when main switch is off" ;;
|
|
34
|
+
*) echo "FAIL: expected NOT_ENABLED, got $OUT"; FAIL=1 ;;
|
|
35
|
+
esac
|
|
36
|
+
assert_not_file "no .lessons-enabled created without main switch" "$TMP_ROOT/.devlog/.lessons-enabled"
|
|
37
|
+
|
|
38
|
+
# --- lessons-on with the main switch present -> enables --------------------
|
|
39
|
+
touch "$TMP_ROOT/.devlog/.enabled"
|
|
40
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-on.sh")"
|
|
41
|
+
assert_exit "lessons-on with main switch -> 0" 0 $?
|
|
42
|
+
assert_file ".lessons-enabled created" "$TMP_ROOT/.devlog/.lessons-enabled"
|
|
43
|
+
case "$OUT" in
|
|
44
|
+
*"LESSONS_ENABLED="*) echo "PASS: reports LESSONS_ENABLED" ;;
|
|
45
|
+
*) echo "FAIL: expected LESSONS_ENABLED=..., got $OUT"; FAIL=1 ;;
|
|
46
|
+
esac
|
|
47
|
+
assert_file ".lessons-drift-state created alongside .lessons-enabled" "$TMP_ROOT/.devlog/.lessons-drift-state"
|
|
48
|
+
grep -q '"mismatch_count": 0' "$TMP_ROOT/.devlog/.lessons-drift-state" \
|
|
49
|
+
&& echo "PASS: drift count starts at 0" || { echo "FAIL: drift count not 0"; FAIL=1; }
|
|
50
|
+
grep -q '"threshold": 3' "$TMP_ROOT/.devlog/.lessons-drift-state" \
|
|
51
|
+
&& echo "PASS: drift threshold defaults to 3" || { echo "FAIL: drift threshold not 3"; FAIL=1; }
|
|
52
|
+
|
|
53
|
+
# --- lessons-off disables, leaves main switch and any lessons files alone --
|
|
54
|
+
echo 'keep me' > "$TMP_ROOT/.devlog/devlog.lessons.foo.md"
|
|
55
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-off.sh")"
|
|
56
|
+
assert_exit "lessons-off -> 0" 0 $?
|
|
57
|
+
case "$OUT" in
|
|
58
|
+
*"LESSONS_DISABLED"*) echo "PASS: reports LESSONS_DISABLED" ;;
|
|
59
|
+
*) echo "FAIL: expected LESSONS_DISABLED, got $OUT"; FAIL=1 ;;
|
|
60
|
+
esac
|
|
61
|
+
assert_not_file ".lessons-enabled removed" "$TMP_ROOT/.devlog/.lessons-enabled"
|
|
62
|
+
assert_file "main .enabled untouched" "$TMP_ROOT/.devlog/.enabled"
|
|
63
|
+
assert_file "lessons file untouched" "$TMP_ROOT/.devlog/devlog.lessons.foo.md"
|
|
64
|
+
assert_file ".lessons-drift-state untouched by lessons-off" "$TMP_ROOT/.devlog/.lessons-drift-state"
|
|
65
|
+
|
|
66
|
+
# --- lessons-off when already off -> reports NOT_ENABLED, no error --------
|
|
67
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-off.sh")"
|
|
68
|
+
assert_exit "lessons-off when already off -> 0" 0 $?
|
|
69
|
+
case "$OUT" in
|
|
70
|
+
*"NOT_ENABLED"*) echo "PASS: lessons-off reports NOT_ENABLED when already off" ;;
|
|
71
|
+
*) echo "FAIL: expected NOT_ENABLED, got $OUT"; FAIL=1 ;;
|
|
72
|
+
esac
|
|
73
|
+
|
|
74
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
75
|
+
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for lessons-read.sh (docs/design/lessons-mode.md "Reading
|
|
3
|
+
# lessons on demand"). No framework, plain assert-and-exit.
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
6
|
+
TMP_ROOT="$(mktemp -d)"
|
|
7
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
8
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
9
|
+
DEVLOG_DIR="$TMP_ROOT/.devlog"
|
|
10
|
+
mkdir -p "$DEVLOG_DIR"
|
|
11
|
+
FAIL=0
|
|
12
|
+
assert_exit() {
|
|
13
|
+
local desc="$1" expected="$2" actual="$3"
|
|
14
|
+
if [ "$actual" -eq "$expected" ]; then echo "PASS: $desc"
|
|
15
|
+
else echo "FAIL: $desc (expected $expected got $actual)"; FAIL=1; fi
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# --- No devlog.md at all, no topic -> NO_INDEX ------------------------------
|
|
19
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" 2>&1)"
|
|
20
|
+
assert_exit "no devlog.md, no topic -> 0" 0 $?
|
|
21
|
+
case "$OUT" in
|
|
22
|
+
*"NO_INDEX"*) echo "PASS: reports NO_INDEX when devlog.md is absent" ;;
|
|
23
|
+
*) echo "FAIL: expected NO_INDEX, got $OUT"; FAIL=1 ;;
|
|
24
|
+
esac
|
|
25
|
+
|
|
26
|
+
# --- devlog.md exists but no Lessons 索引 -> NO_INDEX -----------------------
|
|
27
|
+
echo '## Round 1 — 2026-09-12T00:00:00+08:00
|
|
28
|
+
|
|
29
|
+
### Summary
|
|
30
|
+
fixture
|
|
31
|
+
|
|
32
|
+
### Reply
|
|
33
|
+
fixture reply.
|
|
34
|
+
|
|
35
|
+
### Handoff
|
|
36
|
+
#### 現況
|
|
37
|
+
fixture
|
|
38
|
+
|
|
39
|
+
### Status
|
|
40
|
+
DONE' > "$DEVLOG_DIR/devlog.md"
|
|
41
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" 2>&1)"
|
|
42
|
+
assert_exit "devlog.md with no index, no topic -> 0" 0 $?
|
|
43
|
+
case "$OUT" in
|
|
44
|
+
*"NO_INDEX"*) echo "PASS: reports NO_INDEX when no Lessons 索引 block exists" ;;
|
|
45
|
+
*) echo "FAIL: expected NO_INDEX, got $OUT"; FAIL=1 ;;
|
|
46
|
+
esac
|
|
47
|
+
|
|
48
|
+
# --- Index present -> printed verbatim, no topic ----------------------------
|
|
49
|
+
touch "$DEVLOG_DIR/.enabled" "$DEVLOG_DIR/.lessons-enabled"
|
|
50
|
+
bash "$SCRIPT_DIR/lessons-append.sh" --topic "foo-bar" --text "教訓內容一。" >/dev/null
|
|
51
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" 2>&1)"
|
|
52
|
+
assert_exit "index present, no topic -> 0" 0 $?
|
|
53
|
+
case "$OUT" in
|
|
54
|
+
*"devlog.lessons.foo-bar.md"*) echo "PASS: index line for foo-bar printed" ;;
|
|
55
|
+
*) echo "FAIL: expected index line, got $OUT"; FAIL=1 ;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
# --- Read a specific existing topic, full content ---------------------------
|
|
59
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" "foo-bar" 2>&1)"
|
|
60
|
+
assert_exit "read existing topic -> 0" 0 $?
|
|
61
|
+
case "$OUT" in
|
|
62
|
+
*"PATH=.devlog/devlog.lessons.foo-bar.md"*) echo "PASS: reports PATH" ;;
|
|
63
|
+
*) echo "FAIL: expected PATH=..., got $OUT"; FAIL=1 ;;
|
|
64
|
+
esac
|
|
65
|
+
case "$OUT" in
|
|
66
|
+
*"教訓內容一。"*) echo "PASS: full entry text returned" ;;
|
|
67
|
+
*) echo "FAIL: expected entry text, got $OUT"; FAIL=1 ;;
|
|
68
|
+
esac
|
|
69
|
+
|
|
70
|
+
# --- devlog.lessons.<x>.md / .md input normalizes the same as --topic -------
|
|
71
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" "devlog.lessons.foo-bar.md" 2>&1)"
|
|
72
|
+
assert_exit "read with devlog.lessons.<x>.md input -> 0" 0 $?
|
|
73
|
+
case "$OUT" in
|
|
74
|
+
*"PATH=.devlog/devlog.lessons.foo-bar.md"*) echo "PASS: normalized input reads the same file" ;;
|
|
75
|
+
*) echo "FAIL: expected same PATH, got $OUT"; FAIL=1 ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# --- Missing topic -> MISSING + candidates ----------------------------------
|
|
79
|
+
bash "$SCRIPT_DIR/lessons-append.sh" --topic "another-topic" --text "教訓內容二。" >/dev/null
|
|
80
|
+
OUT="$(bash "$SCRIPT_DIR/lessons-read.sh" "does-not-exist" 2>&1)"
|
|
81
|
+
assert_exit "missing topic -> exit 1" 1 $?
|
|
82
|
+
case "$OUT" in
|
|
83
|
+
*"MISSING"*) echo "PASS: reports MISSING" ;;
|
|
84
|
+
*) echo "FAIL: expected MISSING, got $OUT"; FAIL=1 ;;
|
|
85
|
+
esac
|
|
86
|
+
case "$OUT" in
|
|
87
|
+
*"CANDIDATES="*"foo-bar"*"another-topic"*|*"CANDIDATES="*"another-topic"*"foo-bar"*)
|
|
88
|
+
echo "PASS: candidates list both existing topics" ;;
|
|
89
|
+
*) echo "FAIL: expected candidates listing both topics, got $OUT"; FAIL=1 ;;
|
|
90
|
+
esac
|
|
91
|
+
|
|
92
|
+
# --- Path traversal in topic -> rejected -------------------------------------
|
|
93
|
+
bash "$SCRIPT_DIR/lessons-read.sh" "../etc" >/dev/null 2>&1
|
|
94
|
+
assert_exit "path-traversal topic -> exit 1" 1 $?
|
|
95
|
+
|
|
96
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
97
|
+
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for on-stop-failure.sh / on-session-end.sh / on-tool-failure.sh.
|
|
3
|
+
# Run: bash hooks/scripts/test-on-interrupt.sh
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
TMP_ROOT="$(mktemp -d)"
|
|
8
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
9
|
+
|
|
10
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
11
|
+
DEVLOG_DIR="$TMP_ROOT/.devlog"
|
|
12
|
+
mkdir -p "$DEVLOG_DIR"
|
|
13
|
+
touch "$DEVLOG_DIR/.enabled"
|
|
14
|
+
|
|
15
|
+
FAIL=0
|
|
16
|
+
assert_exit() {
|
|
17
|
+
local desc="$1" expected="$2" actual="$3"
|
|
18
|
+
if [ "$actual" -eq "$expected" ]; then
|
|
19
|
+
echo "PASS: $desc"
|
|
20
|
+
else
|
|
21
|
+
echo "FAIL: $desc (expected exit $expected, got $actual)"
|
|
22
|
+
FAIL=1
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
assert_contains() {
|
|
26
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
27
|
+
case "$haystack" in
|
|
28
|
+
*"$needle"*) echo "PASS: $desc" ;;
|
|
29
|
+
*) echo "FAIL: $desc (missing: $needle)"; FAIL=1 ;;
|
|
30
|
+
esac
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
write_open() {
|
|
34
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-current.md"
|
|
35
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
36
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
37
|
+
|
|
38
|
+
### Status
|
|
39
|
+
IN_PROGRESS
|
|
40
|
+
EOF
|
|
41
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# usage skips
|
|
45
|
+
for err in rate_limit billing_error account_on_hold; do
|
|
46
|
+
write_open
|
|
47
|
+
echo "{\"error\":\"$err\"}" | bash "$SCRIPT_DIR/on-stop-failure.sh"
|
|
48
|
+
assert_exit "StopFailure $err -> exit 0" 0 $?
|
|
49
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md" 2>/dev/null || echo '')"
|
|
50
|
+
case "$BODY" in
|
|
51
|
+
*INTERRUPTED*) echo "FAIL: $err must not stamp INTERRUPTED"; FAIL=1 ;;
|
|
52
|
+
*) echo "PASS: $err left Status IN_PROGRESS" ;;
|
|
53
|
+
esac
|
|
54
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
55
|
+
echo "PASS: $err left .round-open"
|
|
56
|
+
else
|
|
57
|
+
echo "FAIL: $err should not close .round-open"
|
|
58
|
+
FAIL=1
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
# server_error stamps
|
|
63
|
+
write_open
|
|
64
|
+
echo '{"error":"server_error"}' | bash "$SCRIPT_DIR/on-stop-failure.sh"
|
|
65
|
+
assert_exit "StopFailure server_error -> exit 0" 0 $?
|
|
66
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
67
|
+
assert_contains "server_error status" "INTERRUPTED" "$BODY"
|
|
68
|
+
assert_contains "server_error reason" "StopFailure:server_error" "$BODY"
|
|
69
|
+
|
|
70
|
+
# missing error -> unknown
|
|
71
|
+
write_open
|
|
72
|
+
echo '{}' | bash "$SCRIPT_DIR/on-stop-failure.sh"
|
|
73
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
74
|
+
assert_contains "missing error -> unknown" "StopFailure:unknown" "$BODY"
|
|
75
|
+
|
|
76
|
+
# SessionEnd with marker
|
|
77
|
+
write_open
|
|
78
|
+
echo '{"reason":"clear"}' | bash "$SCRIPT_DIR/on-session-end.sh"
|
|
79
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
80
|
+
assert_contains "session end reason" "SessionEnd:clear" "$BODY"
|
|
81
|
+
|
|
82
|
+
# SessionEnd without marker
|
|
83
|
+
write_open
|
|
84
|
+
rm -f "$DEVLOG_DIR/.round-open"
|
|
85
|
+
BEFORE="$(cat "$DEVLOG_DIR/devlog.md" 2>/dev/null || echo '')"
|
|
86
|
+
echo '{"reason":"other"}' | bash "$SCRIPT_DIR/on-session-end.sh"
|
|
87
|
+
AFTER="$(cat "$DEVLOG_DIR/devlog.md" 2>/dev/null || echo '')"
|
|
88
|
+
if [ "$BEFORE" = "$AFTER" ]; then
|
|
89
|
+
echo "PASS: SessionEnd without .round-open is a no-op"
|
|
90
|
+
else
|
|
91
|
+
echo "FAIL: SessionEnd edited a closed Round"
|
|
92
|
+
FAIL=1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# PostToolUseFailure interrupt
|
|
96
|
+
rm -f "$DEVLOG_DIR/.interrupted"
|
|
97
|
+
echo '{"is_interrupt":true}' | bash "$SCRIPT_DIR/on-tool-failure.sh"
|
|
98
|
+
assert_exit "tool failure interrupt -> exit 0" 0 $?
|
|
99
|
+
if [ -f "$DEVLOG_DIR/.interrupted" ]; then
|
|
100
|
+
echo "PASS: is_interrupt created .interrupted"
|
|
101
|
+
else
|
|
102
|
+
echo "FAIL: .interrupted missing"
|
|
103
|
+
FAIL=1
|
|
104
|
+
fi
|
|
105
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md" 2>/dev/null || echo '')"
|
|
106
|
+
case "$BODY" in
|
|
107
|
+
*INTERRUPTED*) echo "FAIL: on-tool-failure must not patch devlog.md"; FAIL=1 ;;
|
|
108
|
+
*) echo "PASS: on-tool-failure did not patch devlog.md" ;;
|
|
109
|
+
esac
|
|
110
|
+
|
|
111
|
+
# is_interrupt false
|
|
112
|
+
rm -f "$DEVLOG_DIR/.interrupted"
|
|
113
|
+
echo '{"is_interrupt":false}' | bash "$SCRIPT_DIR/on-tool-failure.sh"
|
|
114
|
+
if [ -f "$DEVLOG_DIR/.interrupted" ]; then
|
|
115
|
+
echo "FAIL: is_interrupt false should not create the marker"
|
|
116
|
+
FAIL=1
|
|
117
|
+
else
|
|
118
|
+
echo "PASS: is_interrupt false created no marker"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# no .enabled -> no marker
|
|
122
|
+
rm -f "$DEVLOG_DIR/.enabled" "$DEVLOG_DIR/.interrupted"
|
|
123
|
+
echo '{"is_interrupt":true}' | bash "$SCRIPT_DIR/on-tool-failure.sh"
|
|
124
|
+
if [ -f "$DEVLOG_DIR/.interrupted" ]; then
|
|
125
|
+
echo "FAIL: disabled project must not create .interrupted"
|
|
126
|
+
FAIL=1
|
|
127
|
+
else
|
|
128
|
+
echo "PASS: disabled -> no .interrupted"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# SessionEnd on a completed open Round must not stamp, but is still merged
|
|
132
|
+
# into devlog.md (close-open-round.sh always merges; only the INTERRUPTED
|
|
133
|
+
# stamp is skipped when recovered).
|
|
134
|
+
touch "$DEVLOG_DIR/.enabled"
|
|
135
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-current.md"
|
|
136
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
137
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
138
|
+
|
|
139
|
+
### User Input
|
|
140
|
+
```text
|
|
141
|
+
done
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Summary
|
|
145
|
+
finished
|
|
146
|
+
|
|
147
|
+
### Reply
|
|
148
|
+
fixture reply.
|
|
149
|
+
|
|
150
|
+
### Handoff
|
|
151
|
+
#### 現況
|
|
152
|
+
finished
|
|
153
|
+
|
|
154
|
+
### Status
|
|
155
|
+
DONE
|
|
156
|
+
EOF
|
|
157
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
158
|
+
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
159
|
+
printf '\n' >> "$DEVLOG_DIR/.round-current.md"
|
|
160
|
+
echo '{"reason":"clear"}' | bash "$SCRIPT_DIR/on-session-end.sh"
|
|
161
|
+
BODY="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
162
|
+
case "$BODY" in
|
|
163
|
+
*INTERRUPTED*) echo "FAIL: SessionEnd must not stamp a completed Round"; FAIL=1 ;;
|
|
164
|
+
*) echo "PASS: SessionEnd left completed Status DONE" ;;
|
|
165
|
+
esac
|
|
166
|
+
assert_contains "recovered-complete merged into devlog.md" $'### Status\nDONE' "$BODY"
|
|
167
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
168
|
+
echo "FAIL: SessionEnd recovered-complete should delete .round-open"
|
|
169
|
+
FAIL=1
|
|
170
|
+
else
|
|
171
|
+
echo "PASS: SessionEnd recovered-complete deleted .round-open"
|
|
172
|
+
fi
|
|
173
|
+
if [ -f "$DEVLOG_DIR/.round-current.md" ]; then
|
|
174
|
+
echo "FAIL: SessionEnd recovered-complete should delete .round-current.md"
|
|
175
|
+
FAIL=1
|
|
176
|
+
else
|
|
177
|
+
echo "PASS: SessionEnd recovered-complete deleted .round-current.md"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
181
|
+
echo "All checks passed."
|
|
182
|
+
exit 0
|
|
183
|
+
else
|
|
184
|
+
echo "Some checks FAILED."
|
|
185
|
+
exit 1
|
|
186
|
+
fi
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
4
|
+
. "$SCRIPT_DIR/redact-prompt.sh"
|
|
5
|
+
FAIL=0
|
|
6
|
+
check() {
|
|
7
|
+
local d="$1" in="$2" needle="$3" forbidden="$4"
|
|
8
|
+
out="$(printf '%s' "$in" | redact_prompt)"
|
|
9
|
+
case "$out" in *"$needle"*) echo "PASS: $d masked" ;; *) echo "FAIL: $d no mask [$out]"; FAIL=1 ;; esac
|
|
10
|
+
if [ -n "$forbidden" ]; then
|
|
11
|
+
case "$out" in *"$forbidden"*) echo "FAIL: $d leaked"; FAIL=1 ;; *) echo "PASS: $d no leak" ;; esac
|
|
12
|
+
fi
|
|
13
|
+
}
|
|
14
|
+
check ant "token sk-ant-api03-ABCDEFG123456 end" "(已遮罩)" "sk-ant-api03-ABCDEFG123456"
|
|
15
|
+
check ghp "ghp_abcdefghijklmnopqrstuvwxyz0123456789" "(已遮罩)" "ghp_abcdefghijklmnopqrstuvwxyz0123456789"
|
|
16
|
+
check akia "AKIAIOSFODNN7EXAMPLE" "(已遮罩)" "AKIAIOSFODNN7EXAMPLE"
|
|
17
|
+
check openai "key sk-abcdefghijklmnopqrstuvwxyz123456 end" "(已遮罩)" "sk-abcdefghijklmnopqrstuvwxyz123456"
|
|
18
|
+
check bearer "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.aaaaabbbbbcccccdddddeeeee.fffffggggghhhhhiiiiijjjjj" "(已遮罩)" "Bearer eyJ"
|
|
19
|
+
check jwt "token eyJhbGciOiJIUzI1NiJ9.aaaaabbbbbcccccdddddeeeee.fffffggggghhhhhiiiiijjjjj end" "(已遮罩)" "eyJhbGciOiJIUzI1NiJ9"
|
|
20
|
+
keep="$(printf '%s' 'see https://example.com/user/alpha' | redact_prompt)"
|
|
21
|
+
[ "$keep" = "see https://example.com/user/alpha" ] && echo "PASS: url kept" || { echo "FAIL: url $keep"; FAIL=1; }
|
|
22
|
+
keep_uuid="$(printf '%s' 'id 550e8400-e29b-41d4-a716-446655440000' | redact_prompt)"
|
|
23
|
+
[ "$keep_uuid" = "id 550e8400-e29b-41d4-a716-446655440000" ] && echo "PASS: uuid kept" || { echo "FAIL: uuid $keep_uuid"; FAIL=1; }
|
|
24
|
+
pem="$(printf '%s\n' '-----BEGIN PRIVATE KEY-----' 'MIIB' '-----END PRIVATE KEY-----' | redact_prompt)"
|
|
25
|
+
case "$pem" in *PRIVATE\ KEY*) echo "FAIL: pem leaked"; FAIL=1 ;; *"(已遮罩)"*) echo "PASS: pem masked" ;; *) echo "FAIL: pem [$pem]"; FAIL=1 ;; esac
|
|
26
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
27
|
+
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
4
|
+
TMP="$(mktemp -d)"
|
|
5
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
6
|
+
export CLAUDE_PROJECT_DIR="$TMP"
|
|
7
|
+
mkdir -p "$TMP/.devlog"
|
|
8
|
+
FAIL=0
|
|
9
|
+
check_exit_1() {
|
|
10
|
+
bash "$SCRIPT_DIR/resume-devlog.sh" "$@" >/dev/null 2>&1
|
|
11
|
+
[ "$?" -eq 1 ] && echo "PASS: $1 rejected" || { echo "FAIL: $1"; FAIL=1; }
|
|
12
|
+
}
|
|
13
|
+
check_exit_1 --name
|
|
14
|
+
check_exit_1 --name archive
|
|
15
|
+
check_exit_1 --name ../x
|
|
16
|
+
INVALID="$(bash "$SCRIPT_DIR/resume-devlog.sh" --name devlog.md 2>&1)"
|
|
17
|
+
[ "$?" -eq 1 ] && [ "$INVALID" = "INVALID_NAME" ] \
|
|
18
|
+
&& echo "PASS: devlog.md rejected" || { echo "FAIL: devlog.md [$INVALID]"; FAIL=1; }
|
|
19
|
+
cat > "$TMP/.devlog/devlog.foo.md" <<'EOF'
|
|
20
|
+
# Kept log
|
|
21
|
+
|
|
22
|
+
## Round 4 — now
|
|
23
|
+
|
|
24
|
+
### Status
|
|
25
|
+
DONE
|
|
26
|
+
EOF
|
|
27
|
+
OUT="$(bash "$SCRIPT_DIR/resume-devlog.sh" --name devlog.foo.md)"
|
|
28
|
+
case "$OUT" in *"PATH=.devlog/devlog.foo.md"*) echo "PASS: path" ;; *) echo "FAIL: path [$OUT]"; FAIL=1 ;; esac
|
|
29
|
+
case "$OUT" in *"## Round 4"*) echo "PASS: content" ;; *) echo "FAIL: content"; FAIL=1 ;; esac
|
|
30
|
+
OUT="$(bash "$SCRIPT_DIR/resume-devlog.sh" --name missing 2>&1)"
|
|
31
|
+
[ "$?" -eq 1 ] || { echo "FAIL: missing exit"; FAIL=1; }
|
|
32
|
+
case "$OUT" in *"MISSING"*"CANDIDATES=foo"*) echo "PASS: candidates" ;; *) echo "FAIL: candidates [$OUT]"; FAIL=1 ;; esac
|
|
33
|
+
|
|
34
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
35
|
+
else echo "Some checks FAILED."; exit 1; fi
|