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,508 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for session-start-devlog.sh, including the Span Mode
|
|
3
|
+
# resume-context note. No framework — plain assert-and-exit, matching this
|
|
4
|
+
# repo's existing style. Run directly:
|
|
5
|
+
# bash hooks/scripts/test-session-start-devlog.sh
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
TMP_ROOT="$(mktemp -d)"
|
|
10
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
11
|
+
|
|
12
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
13
|
+
DEVLOG_DIR="$TMP_ROOT/.devlog"
|
|
14
|
+
mkdir -p "$DEVLOG_DIR"
|
|
15
|
+
|
|
16
|
+
FAIL=0
|
|
17
|
+
assert_contains() {
|
|
18
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
19
|
+
case "$haystack" in
|
|
20
|
+
*"$needle"*) echo "PASS: $desc" ;;
|
|
21
|
+
*) echo "FAIL: $desc (expected output to contain: $needle)"; FAIL=1 ;;
|
|
22
|
+
esac
|
|
23
|
+
}
|
|
24
|
+
assert_not_contains() {
|
|
25
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
26
|
+
case "$haystack" in
|
|
27
|
+
*"$needle"*) echo "FAIL: $desc (expected output NOT to contain: $needle)"; FAIL=1 ;;
|
|
28
|
+
*) echo "PASS: $desc" ;;
|
|
29
|
+
esac
|
|
30
|
+
}
|
|
31
|
+
assert_exit() {
|
|
32
|
+
local desc="$1" expected="$2" actual="$3"
|
|
33
|
+
if [ "$actual" -eq "$expected" ]; then
|
|
34
|
+
echo "PASS: $desc"
|
|
35
|
+
else
|
|
36
|
+
echo "FAIL: $desc (expected exit $expected, got $actual)"
|
|
37
|
+
FAIL=1
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
write_round() {
|
|
42
|
+
local n="$1" status="$2"
|
|
43
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<EOF
|
|
44
|
+
## Round ${n} — 2026-09-09T12:00:00+08:00
|
|
45
|
+
|
|
46
|
+
### User Input
|
|
47
|
+
\`\`\`text
|
|
48
|
+
prompt ${n} with lots of bulk
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
### 段落 1 - 12:01
|
|
52
|
+
secret intermediate ${n}
|
|
53
|
+
|
|
54
|
+
### Summary
|
|
55
|
+
summary ${n}
|
|
56
|
+
|
|
57
|
+
### Reply
|
|
58
|
+
fixture reply.
|
|
59
|
+
|
|
60
|
+
### Handoff
|
|
61
|
+
#### 現況
|
|
62
|
+
handoff ${n}
|
|
63
|
+
|
|
64
|
+
### Status
|
|
65
|
+
${status}
|
|
66
|
+
EOF
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# --- Scenario 1: no devlog.md, no span -> silent exit 0 -------------------
|
|
70
|
+
OUTPUT="$(bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
71
|
+
EXIT_CODE=$?
|
|
72
|
+
if [ "$EXIT_CODE" -eq 0 ] && [ -z "$OUTPUT" ]; then
|
|
73
|
+
echo "PASS: no devlog.md, no span -> silent exit 0"
|
|
74
|
+
else
|
|
75
|
+
echo "FAIL: no devlog.md, no span -> expected silent exit 0, got exit $EXIT_CODE, output: $OUTPUT"
|
|
76
|
+
FAIL=1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# --- Scenario 2: devlog.md exists, no span -> normal output, no span note -
|
|
80
|
+
echo "## Round 1 — 2026-09-08T00:00:00+08:00" >> "$DEVLOG_DIR/devlog.md"
|
|
81
|
+
OUTPUT="$(bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
82
|
+
assert_not_contains "no span -> no span warning in output" "開啟中的 span" "$OUTPUT"
|
|
83
|
+
assert_contains "no span -> still shows devlog content" "Round 1" "$OUTPUT"
|
|
84
|
+
|
|
85
|
+
# --- Scenario 3: span open with valid round/opened_at -> warning shown ----
|
|
86
|
+
cat > "$DEVLOG_DIR/.span-open" <<'SPANEOF'
|
|
87
|
+
{
|
|
88
|
+
"round": 1,
|
|
89
|
+
"opened_at": "2026-09-08T21:40:00+08:00",
|
|
90
|
+
"ticks_since_checkin": 2,
|
|
91
|
+
"max_silent_ticks": 5
|
|
92
|
+
}
|
|
93
|
+
SPANEOF
|
|
94
|
+
OUTPUT="$(bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
95
|
+
assert_contains "span open -> warning mentions Round 1" "Round 1" "$OUTPUT"
|
|
96
|
+
assert_contains "span open -> warning mentions opened_at timestamp" "2026-09-08T21:40:00+08:00" "$OUTPUT"
|
|
97
|
+
assert_contains "span open -> warning mentions .span-open" ".span-open" "$OUTPUT"
|
|
98
|
+
|
|
99
|
+
# --- Scenario 4: span file malformed -> no warning, no crash --------------
|
|
100
|
+
echo "not valid json" > "$DEVLOG_DIR/.span-open"
|
|
101
|
+
OUTPUT="$(bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
102
|
+
EXIT_CODE=$?
|
|
103
|
+
assert_not_contains "malformed span -> no warning shown" "開啟中的 span" "$OUTPUT"
|
|
104
|
+
if [ "$EXIT_CODE" -eq 0 ]; then
|
|
105
|
+
echo "PASS: malformed span -> still exits 0"
|
|
106
|
+
else
|
|
107
|
+
echo "FAIL: malformed span -> expected exit 0, got $EXIT_CODE"
|
|
108
|
+
FAIL=1
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# --- Scenario 5: startup heals open round then still injects --------------
|
|
112
|
+
rm -f "$DEVLOG_DIR/.span-open"
|
|
113
|
+
touch "$DEVLOG_DIR/.enabled"
|
|
114
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-current.md"
|
|
115
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
116
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
117
|
+
|
|
118
|
+
### User Input
|
|
119
|
+
```text
|
|
120
|
+
crash me
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Status
|
|
124
|
+
IN_PROGRESS
|
|
125
|
+
EOF
|
|
126
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
127
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
128
|
+
assert_contains "session start still shows Round 1" "Round 1" "$OUTPUT"
|
|
129
|
+
assert_contains "session start output includes INTERRUPTED" "INTERRUPTED" "$OUTPUT"
|
|
130
|
+
assert_contains "heal reason in the file/output" "dangling:session_start" "$OUTPUT"
|
|
131
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
132
|
+
echo "FAIL: SessionStart should delete .round-open after heal"
|
|
133
|
+
FAIL=1
|
|
134
|
+
else
|
|
135
|
+
echo "PASS: SessionStart deleted .round-open"
|
|
136
|
+
fi
|
|
137
|
+
FILE="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
138
|
+
assert_contains "file stamped INTERRUPTED" "INTERRUPTED" "$FILE"
|
|
139
|
+
|
|
140
|
+
# --- Scenario 6: compact must NOT heal; Stop still enforces ---------------
|
|
141
|
+
# enforce-devlog.sh (round-current split) hashes/reads .round-current.md,
|
|
142
|
+
# not devlog.md, so the dangling "mid compact" round must be seeded there
|
|
143
|
+
# too, and .turn-start hashed from that same file, or the later Stop call's
|
|
144
|
+
# hash comparison can never observe "no write since turn start".
|
|
145
|
+
cat > "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
146
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
147
|
+
|
|
148
|
+
### User Input
|
|
149
|
+
```text
|
|
150
|
+
mid compact
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Status
|
|
154
|
+
IN_PROGRESS
|
|
155
|
+
EOF
|
|
156
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
157
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
158
|
+
|
|
159
|
+
### User Input
|
|
160
|
+
```text
|
|
161
|
+
mid compact
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Status
|
|
165
|
+
IN_PROGRESS
|
|
166
|
+
EOF
|
|
167
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
168
|
+
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
169
|
+
OUTPUT="$(echo '{"source":"compact"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
170
|
+
assert_contains "compact still injects Round 1" "Round 1" "$OUTPUT"
|
|
171
|
+
FILE="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
172
|
+
assert_contains "compact leaves Status IN_PROGRESS" "IN_PROGRESS" "$FILE"
|
|
173
|
+
assert_not_contains "compact must not stamp INTERRUPTED" "INTERRUPTED" "$FILE"
|
|
174
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
175
|
+
echo "PASS: compact left .round-open in place"
|
|
176
|
+
else
|
|
177
|
+
echo "FAIL: compact SessionStart must not delete .round-open"
|
|
178
|
+
FAIL=1
|
|
179
|
+
fi
|
|
180
|
+
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
181
|
+
assert_exit "after compact skip-heal, Stop still enforces (hash equal) -> exit 2" 2 $?
|
|
182
|
+
|
|
183
|
+
# --- Scenario 7: hooks.json SessionStart matcher includes fork -------------
|
|
184
|
+
HOOKS_JSON="$SCRIPT_DIR/../hooks.json"
|
|
185
|
+
if grep -q '"matcher": "startup|resume|clear|compact|fork"' "$HOOKS_JSON"; then
|
|
186
|
+
echo "PASS: SessionStart matcher includes fork"
|
|
187
|
+
else
|
|
188
|
+
echo "FAIL: SessionStart matcher must be exactly startup|resume|clear|compact|fork"
|
|
189
|
+
FAIL=1
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# --- Scenario 8: source=fork heals an open skeleton (script already does) --
|
|
193
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-current.md"
|
|
194
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
195
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
196
|
+
|
|
197
|
+
### User Input
|
|
198
|
+
```text
|
|
199
|
+
fork me
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Status
|
|
203
|
+
IN_PROGRESS
|
|
204
|
+
EOF
|
|
205
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
206
|
+
OUTPUT="$(echo '{"source":"fork"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
207
|
+
assert_contains "fork still shows Round 1" "Round 1" "$OUTPUT"
|
|
208
|
+
assert_contains "fork heal reason" "dangling:session_start" "$OUTPUT"
|
|
209
|
+
FILE="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
210
|
+
assert_contains "fork stamped INTERRUPTED" "INTERRUPTED" "$FILE"
|
|
211
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
212
|
+
echo "FAIL: fork SessionStart should delete .round-open after heal"
|
|
213
|
+
FAIL=1
|
|
214
|
+
else
|
|
215
|
+
echo "PASS: fork SessionStart deleted .round-open"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# --- Scenario 9: source=clear does not inject; still heals open round ------
|
|
219
|
+
rm -f "$DEVLOG_DIR/.span-open"
|
|
220
|
+
touch "$DEVLOG_DIR/.enabled"
|
|
221
|
+
rm -f "$DEVLOG_DIR/devlog.md" "$DEVLOG_DIR/.round-current.md"
|
|
222
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
223
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
224
|
+
|
|
225
|
+
### User Input
|
|
226
|
+
```text
|
|
227
|
+
cleared
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Status
|
|
231
|
+
IN_PROGRESS
|
|
232
|
+
EOF
|
|
233
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
234
|
+
OUTPUT="$(echo '{"source":"clear"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
235
|
+
if [ -z "$OUTPUT" ]; then
|
|
236
|
+
echo "PASS: source=clear with open round -> silent stdout"
|
|
237
|
+
else
|
|
238
|
+
echo "FAIL: source=clear must not inject context, got: $OUTPUT"
|
|
239
|
+
FAIL=1
|
|
240
|
+
fi
|
|
241
|
+
FILE="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
242
|
+
assert_contains "clear still stamps INTERRUPTED on disk" "INTERRUPTED" "$FILE"
|
|
243
|
+
assert_contains "clear heal reason is on disk not stdout" "dangling:session_start" "$FILE"
|
|
244
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
245
|
+
echo "FAIL: clear SessionStart should delete .round-open after heal"
|
|
246
|
+
FAIL=1
|
|
247
|
+
else
|
|
248
|
+
echo "PASS: clear SessionStart deleted .round-open"
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
# --- Scenario 10: source=clear with closed log and open span -> silent -----
|
|
252
|
+
cat > "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
253
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
254
|
+
|
|
255
|
+
### User Input
|
|
256
|
+
```text
|
|
257
|
+
done
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Summary
|
|
261
|
+
finished
|
|
262
|
+
|
|
263
|
+
### Reply
|
|
264
|
+
fixture reply.
|
|
265
|
+
|
|
266
|
+
### Handoff
|
|
267
|
+
#### 現況
|
|
268
|
+
finished
|
|
269
|
+
|
|
270
|
+
### Status
|
|
271
|
+
DONE
|
|
272
|
+
EOF
|
|
273
|
+
cat > "$DEVLOG_DIR/.span-open" <<'SPANEOF'
|
|
274
|
+
{
|
|
275
|
+
"round": 1,
|
|
276
|
+
"opened_at": "2026-09-08T21:40:00+08:00",
|
|
277
|
+
"ticks_since_checkin": 2,
|
|
278
|
+
"max_silent_ticks": 5
|
|
279
|
+
}
|
|
280
|
+
SPANEOF
|
|
281
|
+
OUTPUT="$(echo '{"source":"clear"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
282
|
+
if [ -z "$OUTPUT" ]; then
|
|
283
|
+
echo "PASS: source=clear with span -> silent stdout"
|
|
284
|
+
else
|
|
285
|
+
echo "FAIL: source=clear must not inject span warning or rounds, got: $OUTPUT"
|
|
286
|
+
FAIL=1
|
|
287
|
+
fi
|
|
288
|
+
assert_not_contains "clear must not mention 開啟中的 span" "開啟中的 span" "$OUTPUT"
|
|
289
|
+
rm -f "$DEVLOG_DIR/.span-open"
|
|
290
|
+
|
|
291
|
+
# --- Scenario 11: source=resume still injects after the clear exception ----
|
|
292
|
+
OUTPUT="$(echo '{"source":"resume"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
293
|
+
assert_contains "resume still injects Round 1" "Round 1" "$OUTPUT"
|
|
294
|
+
|
|
295
|
+
# --- Scenario 12: startup recovers a genuinely-completed last Round -------
|
|
296
|
+
# (recovered-complete: the round had already finished writing Summary/Handoff
|
|
297
|
+
# before the interrupt signal arrived; close-open-round.sh's "recovered" path
|
|
298
|
+
# — AWK_RC=3 — must detect this via .round-current.md's hash differing from
|
|
299
|
+
# .turn-start, not stamp INTERRUPTED, and still merge the completed round
|
|
300
|
+
# into devlog.md.) Round-current split: this only exercises
|
|
301
|
+
# close-open-round.sh's recovered-detection logic when .round-current.md is
|
|
302
|
+
# actually seeded and hashed into .turn-start — an empty/absent
|
|
303
|
+
# .round-current.md makes close-open-round.sh early-out (drop_markers, exit
|
|
304
|
+
# 0) before that logic ever runs, which was the bug the final whole-branch
|
|
305
|
+
# review found in this scenario: it passed because nothing ran, not because
|
|
306
|
+
# the recovered-detection logic worked.
|
|
307
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
308
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
309
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
310
|
+
|
|
311
|
+
### User Input
|
|
312
|
+
```text
|
|
313
|
+
done
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Status
|
|
317
|
+
IN_PROGRESS
|
|
318
|
+
EOF
|
|
319
|
+
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
320
|
+
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
321
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
322
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
323
|
+
|
|
324
|
+
### User Input
|
|
325
|
+
```text
|
|
326
|
+
done
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Summary
|
|
330
|
+
finished
|
|
331
|
+
|
|
332
|
+
### Reply
|
|
333
|
+
fixture reply.
|
|
334
|
+
|
|
335
|
+
### Handoff
|
|
336
|
+
#### 現況
|
|
337
|
+
finished
|
|
338
|
+
|
|
339
|
+
### Status
|
|
340
|
+
DONE
|
|
341
|
+
EOF
|
|
342
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
343
|
+
assert_contains "startup still injects Round 1" "Round 1" "$OUTPUT"
|
|
344
|
+
FILE="$(cat "$DEVLOG_DIR/devlog.md")"
|
|
345
|
+
assert_contains "startup recovered keeps DONE" $'### Status\nDONE' "$FILE"
|
|
346
|
+
assert_not_contains "startup must not stamp a completed Round" "INTERRUPTED" "$FILE"
|
|
347
|
+
if [ -f "$DEVLOG_DIR/.round-current.md" ]; then
|
|
348
|
+
echo "FAIL: startup recovered-complete should merge .round-current.md away"
|
|
349
|
+
FAIL=1
|
|
350
|
+
else
|
|
351
|
+
echo "PASS: startup recovered-complete merged .round-current.md into devlog.md"
|
|
352
|
+
fi
|
|
353
|
+
if [ -f "$DEVLOG_DIR/.round-open" ]; then
|
|
354
|
+
echo "FAIL: startup recovered-complete should delete .round-open"
|
|
355
|
+
FAIL=1
|
|
356
|
+
else
|
|
357
|
+
echo "PASS: startup recovered-complete deleted .round-open"
|
|
358
|
+
fi
|
|
359
|
+
|
|
360
|
+
# --- Scenario 13: excerpt is last two rounds, not eight -------------------
|
|
361
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
362
|
+
rm -f "$DEVLOG_DIR/.span-open"
|
|
363
|
+
n=1
|
|
364
|
+
while [ "$n" -le 8 ]; do
|
|
365
|
+
write_round "$n" DONE
|
|
366
|
+
n=$((n + 1))
|
|
367
|
+
done
|
|
368
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
369
|
+
assert_contains "excerpt names Round 8" "Round 8" "$OUTPUT"
|
|
370
|
+
assert_contains "excerpt names Round 7" "Round 7" "$OUTPUT"
|
|
371
|
+
assert_not_contains "excerpt drops Round 6" "Round 6" "$OUTPUT"
|
|
372
|
+
assert_not_contains "excerpt drops 段落" "secret intermediate" "$OUTPUT"
|
|
373
|
+
assert_not_contains "excerpt drops User Input when Summary exists" "prompt 8 with lots of bulk" "$OUTPUT"
|
|
374
|
+
assert_contains "excerpt keeps Summary" "summary 8" "$OUTPUT"
|
|
375
|
+
assert_contains "excerpt keeps Handoff" "handoff 8" "$OUTPUT"
|
|
376
|
+
|
|
377
|
+
# --- Scenario 14: last Checkpoint is included even if older than last 2 ---
|
|
378
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
379
|
+
|
|
380
|
+
## Checkpoint(Round 1-6 摘要)
|
|
381
|
+
old checkpoint body
|
|
382
|
+
EOF
|
|
383
|
+
write_round 9 DONE
|
|
384
|
+
write_round 10 DONE
|
|
385
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
386
|
+
assert_contains "includes last checkpoint" "old checkpoint body" "$OUTPUT"
|
|
387
|
+
assert_contains "still has Round 10" "Round 10" "$OUTPUT"
|
|
388
|
+
assert_not_contains "still drops Round 8 body after more writes" "summary 8" "$OUTPUT"
|
|
389
|
+
|
|
390
|
+
# --- Scenario 15: fenced ## Round inside User Input is not a real Round ---
|
|
391
|
+
cat > "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
392
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
393
|
+
|
|
394
|
+
### User Input
|
|
395
|
+
```text
|
|
396
|
+
## Round 99 — fake
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Summary
|
|
400
|
+
real one
|
|
401
|
+
|
|
402
|
+
### Reply
|
|
403
|
+
fixture reply.
|
|
404
|
+
|
|
405
|
+
### Handoff
|
|
406
|
+
#### 現況
|
|
407
|
+
real handoff
|
|
408
|
+
|
|
409
|
+
### Status
|
|
410
|
+
DONE
|
|
411
|
+
EOF
|
|
412
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
413
|
+
assert_contains "fence-aware uses Round 1" "Round 1" "$OUTPUT"
|
|
414
|
+
assert_not_contains "fence-aware ignores Round 99 heading as a round" "Round 99" "$OUTPUT"
|
|
415
|
+
|
|
416
|
+
# --- Scenario 16: skeleton last Round includes User Input -----------------
|
|
417
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
418
|
+
|
|
419
|
+
## Round 2 — 2026-09-09T12:01:00+08:00
|
|
420
|
+
|
|
421
|
+
### User Input
|
|
422
|
+
```text
|
|
423
|
+
unfinished prompt
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Status
|
|
427
|
+
IN_PROGRESS
|
|
428
|
+
EOF
|
|
429
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
430
|
+
assert_contains "skeleton User Input is shown" "unfinished prompt" "$OUTPUT"
|
|
431
|
+
|
|
432
|
+
# --- Scenario 17: clear still silent with a long log ----------------------
|
|
433
|
+
OUTPUT="$(echo '{"source":"clear"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
434
|
+
if [ -z "$OUTPUT" ]; then echo "PASS: clear still silent after excerpt change"
|
|
435
|
+
else echo "FAIL: clear injected: $OUTPUT"; FAIL=1; fi
|
|
436
|
+
|
|
437
|
+
# --- Scenario: Kept 索引 block is surfaced in the injected excerpt ---------
|
|
438
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
439
|
+
write_round 1 DONE
|
|
440
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
441
|
+
|
|
442
|
+
## Kept 索引
|
|
443
|
+
- `devlog.topic-a.md`:Round 1-1,kept_at 2026-09-10T00:00:00+08:00
|
|
444
|
+
EOF
|
|
445
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
446
|
+
assert_contains "startup excerpt includes Kept 索引 heading" "## Kept 索引" "$OUTPUT"
|
|
447
|
+
assert_contains "startup excerpt includes the kept-file line" "devlog.topic-a.md" "$OUTPUT"
|
|
448
|
+
|
|
449
|
+
# --- Scenario: no Kept 索引 block -> excerpt omits the heading entirely ----
|
|
450
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
451
|
+
write_round 1 DONE
|
|
452
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
453
|
+
assert_not_contains "no Kept 索引 block -> excerpt has no such heading" "## Kept 索引" "$OUTPUT"
|
|
454
|
+
|
|
455
|
+
# --- Scenario: source=clear still prints nothing, even with a Kept 索引 ----
|
|
456
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
457
|
+
|
|
458
|
+
## Kept 索引
|
|
459
|
+
- `devlog.topic-a.md`:Round 1-1,kept_at 2026-09-10T00:00:00+08:00
|
|
460
|
+
EOF
|
|
461
|
+
OUTPUT="$(echo '{"source":"clear"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
462
|
+
if [ -z "$OUTPUT" ]; then
|
|
463
|
+
echo "PASS: source=clear stays silent even with a Kept 索引 block present"
|
|
464
|
+
else
|
|
465
|
+
echo "FAIL: source=clear must not inject anything, got: $OUTPUT"
|
|
466
|
+
FAIL=1
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
# --- Scenario: Lessons 索引 block is surfaced in the injected excerpt ------
|
|
470
|
+
# (docs/design/lessons-mode.md「## Lessons 索引」)
|
|
471
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
472
|
+
write_round 1 DONE
|
|
473
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
474
|
+
|
|
475
|
+
## Lessons 索引
|
|
476
|
+
- `devlog.lessons.span-mode-detour.md`:1 則,最新一則「先卡住了。」(updated_at 2026-09-12T00:00:00+08:00)
|
|
477
|
+
EOF
|
|
478
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
479
|
+
assert_contains "startup excerpt includes Lessons 索引 heading" "## Lessons 索引" "$OUTPUT"
|
|
480
|
+
assert_contains "startup excerpt includes the lessons-file line" "devlog.lessons.span-mode-detour.md" "$OUTPUT"
|
|
481
|
+
|
|
482
|
+
# --- Scenario: no Lessons 索引 block -> excerpt omits the heading entirely -
|
|
483
|
+
: > "$DEVLOG_DIR/devlog.md"
|
|
484
|
+
write_round 1 DONE
|
|
485
|
+
OUTPUT="$(echo '{"source":"startup"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
486
|
+
assert_not_contains "no Lessons 索引 block -> excerpt has no such heading" "## Lessons 索引" "$OUTPUT"
|
|
487
|
+
|
|
488
|
+
# --- Scenario: source=clear still prints nothing, even with a Lessons 索引 -
|
|
489
|
+
cat >> "$DEVLOG_DIR/devlog.md" <<'EOF'
|
|
490
|
+
|
|
491
|
+
## Lessons 索引
|
|
492
|
+
- `devlog.lessons.span-mode-detour.md`:1 則,最新一則「先卡住了。」(updated_at 2026-09-12T00:00:00+08:00)
|
|
493
|
+
EOF
|
|
494
|
+
OUTPUT="$(echo '{"source":"clear"}' | bash "$SCRIPT_DIR/session-start-devlog.sh" 2>&1)"
|
|
495
|
+
if [ -z "$OUTPUT" ]; then
|
|
496
|
+
echo "PASS: source=clear stays silent even with a Lessons 索引 block present"
|
|
497
|
+
else
|
|
498
|
+
echo "FAIL: source=clear must not inject anything, got: $OUTPUT"
|
|
499
|
+
FAIL=1
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
503
|
+
echo "All checks passed."
|
|
504
|
+
exit 0
|
|
505
|
+
else
|
|
506
|
+
echo "Some checks FAILED."
|
|
507
|
+
exit 1
|
|
508
|
+
fi
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
4
|
+
TMP_ROOT="$(mktemp -d)"
|
|
5
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
6
|
+
export CLAUDE_PROJECT_DIR="$TMP_ROOT"
|
|
7
|
+
FAIL=0
|
|
8
|
+
assert_exit() {
|
|
9
|
+
local desc="$1" expected="$2" actual="$3"
|
|
10
|
+
if [ "$actual" -eq "$expected" ]; then echo "PASS: $desc"
|
|
11
|
+
else echo "FAIL: $desc (expected $expected got $actual)"; FAIL=1; fi
|
|
12
|
+
}
|
|
13
|
+
assert_file() {
|
|
14
|
+
local desc="$1" path="$2"
|
|
15
|
+
if [ -f "$path" ]; then echo "PASS: $desc"
|
|
16
|
+
else echo "FAIL: $desc"; FAIL=1; fi
|
|
17
|
+
}
|
|
18
|
+
assert_not_file() {
|
|
19
|
+
local desc="$1" path="$2"
|
|
20
|
+
if [ ! -f "$path" ]; then echo "PASS: $desc"
|
|
21
|
+
else echo "FAIL: $desc"; FAIL=1; fi
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# start on empty project
|
|
25
|
+
OUT="$(bash "$SCRIPT_DIR/start-devlog.sh")"
|
|
26
|
+
assert_exit "start empty -> 0" 0 $?
|
|
27
|
+
assert_file ".enabled" "$TMP_ROOT/.devlog/.enabled"
|
|
28
|
+
assert_file "checkpoint" "$TMP_ROOT/.devlog/.checkpoint-state"
|
|
29
|
+
assert_file "segment" "$TMP_ROOT/.devlog/.segment-state"
|
|
30
|
+
assert_not_file "no devlog.md created" "$TMP_ROOT/.devlog/devlog.md"
|
|
31
|
+
case "$OUT" in
|
|
32
|
+
*"GITIGNORE_DEVLOG=no"*) echo "PASS: reports missing gitignore entry" ;;
|
|
33
|
+
*) echo "FAIL: expected GITIGNORE_DEVLOG=no, got $OUT"; FAIL=1 ;;
|
|
34
|
+
esac
|
|
35
|
+
grep -q '"max_silent_rounds": 20' "$TMP_ROOT/.devlog/.checkpoint-state" \
|
|
36
|
+
&& echo "PASS: default checkpoint" || { echo "FAIL: checkpoint defaults"; FAIL=1; }
|
|
37
|
+
grep -q '"max_silent_seconds": 600' "$TMP_ROOT/.devlog/.segment-state" \
|
|
38
|
+
&& echo "PASS: default segment" || { echo "FAIL: segment defaults"; FAIL=1; }
|
|
39
|
+
grep -q '"session_id": ""' "$TMP_ROOT/.devlog/.segment-state" \
|
|
40
|
+
&& echo "PASS: default segment session id" || { echo "FAIL: segment session id"; FAIL=1; }
|
|
41
|
+
|
|
42
|
+
# preserve thresholds on second start
|
|
43
|
+
printf '%s\n' '{"rounds_since_checkpoint": 7, "max_silent_rounds": 3, "checkpoint_marker_count": 1}' \
|
|
44
|
+
> "$TMP_ROOT/.devlog/.checkpoint-state"
|
|
45
|
+
printf '%s\n' '{"last_change_epoch": 1, "last_seen_cksum": "x", "max_silent_seconds": 60}' \
|
|
46
|
+
> "$TMP_ROOT/.devlog/.segment-state"
|
|
47
|
+
bash "$SCRIPT_DIR/start-devlog.sh" >/dev/null
|
|
48
|
+
grep -q '"max_silent_rounds": 3' "$TMP_ROOT/.devlog/.checkpoint-state" \
|
|
49
|
+
&& echo "PASS: start does not reset max_silent_rounds" || { echo "FAIL: checkpoint reset"; FAIL=1; }
|
|
50
|
+
grep -q '"max_silent_seconds": 60' "$TMP_ROOT/.devlog/.segment-state" \
|
|
51
|
+
&& echo "PASS: start does not reset max_silent_seconds" || { echo "FAIL: segment reset"; FAIL=1; }
|
|
52
|
+
|
|
53
|
+
# gitignore already has .devlog/
|
|
54
|
+
echo '.devlog/' > "$TMP_ROOT/.gitignore"
|
|
55
|
+
OUT="$(bash "$SCRIPT_DIR/start-devlog.sh")"
|
|
56
|
+
case "$OUT" in
|
|
57
|
+
*"GITIGNORE_DEVLOG=yes"*) echo "PASS: detects gitignore entry" ;;
|
|
58
|
+
*) echo "FAIL: expected GITIGNORE_DEVLOG=yes, got $OUT"; FAIL=1 ;;
|
|
59
|
+
esac
|
|
60
|
+
|
|
61
|
+
# pause
|
|
62
|
+
touch "$TMP_ROOT/.devlog/.span-open" "$TMP_ROOT/.devlog/.round-open" "$TMP_ROOT/.devlog/.interrupted" "$TMP_ROOT/.devlog/.awaiting-reply"
|
|
63
|
+
echo 'keep me' > "$TMP_ROOT/.devlog/devlog.md"
|
|
64
|
+
OUT="$(bash "$SCRIPT_DIR/pause-devlog.sh")"
|
|
65
|
+
assert_exit "pause -> 0" 0 $?
|
|
66
|
+
assert_not_file "enabled gone" "$TMP_ROOT/.devlog/.enabled"
|
|
67
|
+
assert_not_file "span gone" "$TMP_ROOT/.devlog/.span-open"
|
|
68
|
+
assert_not_file "round-open gone" "$TMP_ROOT/.devlog/.round-open"
|
|
69
|
+
assert_not_file "interrupted gone" "$TMP_ROOT/.devlog/.interrupted"
|
|
70
|
+
assert_not_file "awaiting-reply gone" "$TMP_ROOT/.devlog/.awaiting-reply"
|
|
71
|
+
assert_file "log kept" "$TMP_ROOT/.devlog/devlog.md"
|
|
72
|
+
assert_file "checkpoint kept" "$TMP_ROOT/.devlog/.checkpoint-state"
|
|
73
|
+
|
|
74
|
+
# pause when never started
|
|
75
|
+
rm -rf "$TMP_ROOT/.devlog"
|
|
76
|
+
OUT="$(bash "$SCRIPT_DIR/pause-devlog.sh")"
|
|
77
|
+
assert_exit "pause never-started -> 0" 0 $?
|
|
78
|
+
case "$OUT" in
|
|
79
|
+
*"NOT_ENABLED"*) echo "PASS: pause reports NOT_ENABLED" ;;
|
|
80
|
+
*) echo "FAIL: expected NOT_ENABLED, got $OUT"; FAIL=1 ;;
|
|
81
|
+
esac
|
|
82
|
+
|
|
83
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
84
|
+
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
FAIL=0
|
|
8
|
+
pass() { echo "PASS: $1"; }
|
|
9
|
+
fail() { echo "FAIL: $1"; FAIL=1; }
|
|
10
|
+
|
|
11
|
+
OUT="$(bash "$SCRIPT_DIR/status-devlog.sh")"
|
|
12
|
+
[ "$OUT" = "NOT_STARTED" ] && pass "not started" || fail "not started [$OUT]"
|
|
13
|
+
|
|
14
|
+
mkdir -p "$TMP/.devlog"
|
|
15
|
+
touch "$TMP/.devlog/.enabled"
|
|
16
|
+
cat > "$TMP/.devlog/devlog.md" <<'EOF'
|
|
17
|
+
## Round 1 — now
|
|
18
|
+
|
|
19
|
+
### Status
|
|
20
|
+
DONE
|
|
21
|
+
EOF
|
|
22
|
+
OUT="$(bash "$SCRIPT_DIR/status-devlog.sh")"
|
|
23
|
+
case "$OUT" in *"ENABLED=yes"*) pass "enabled" ;; *) fail "enabled [$OUT]" ;; esac
|
|
24
|
+
case "$OUT" in *"LESSONS=no"*) pass "lessons off by default" ;; *) fail "lessons off by default [$OUT]" ;; esac
|
|
25
|
+
case "$OUT" in *"SPAN=closed"*) pass "span closed" ;; *) fail "span closed [$OUT]" ;; esac
|
|
26
|
+
case "$OUT" in *"LAST_STATUS=DONE"*) pass "last status" ;; *) fail "last status [$OUT]" ;; esac
|
|
27
|
+
|
|
28
|
+
touch "$TMP/.devlog/.lessons-enabled"
|
|
29
|
+
OUT="$(bash "$SCRIPT_DIR/status-devlog.sh")"
|
|
30
|
+
case "$OUT" in *"LESSONS=yes"*) pass "lessons on when flag present" ;; *) fail "lessons on when flag present [$OUT]" ;; esac
|
|
31
|
+
rm -f "$TMP/.devlog/.lessons-enabled"
|
|
32
|
+
|
|
33
|
+
printf '%s\n' '{"mismatch_count": 1, "threshold": 3}' > "$TMP/.devlog/.lessons-drift-state"
|
|
34
|
+
OUT="$(bash "$SCRIPT_DIR/status-devlog.sh")"
|
|
35
|
+
case "$OUT" in *"LESSONS_DRIFT=1/3"*) pass "lessons drift status line" ;; *) fail "lessons drift status line [$OUT]" ;; esac
|
|
36
|
+
rm -f "$TMP/.devlog/.lessons-drift-state"
|
|
37
|
+
|
|
38
|
+
OUT="$(bash "$SCRIPT_DIR/span-open.sh")"
|
|
39
|
+
[ "$OUT" = "OPENED=1" ] && pass "span opened" || fail "span open [$OUT]"
|
|
40
|
+
grep -q '"round": 1' "$TMP/.devlog/.span-open" && pass "round stored" || fail "round stored"
|
|
41
|
+
grep -q '"ticks_since_checkin": 0' "$TMP/.devlog/.span-open" && pass "ticks default" || fail "ticks default"
|
|
42
|
+
grep -q '"max_silent_ticks": 5' "$TMP/.devlog/.span-open" && pass "max default" || fail "max default"
|
|
43
|
+
bash "$SCRIPT_DIR/span-open.sh" >/dev/null 2>&1
|
|
44
|
+
[ "$?" -eq 1 ] && pass "second open rejected" || fail "second open"
|
|
45
|
+
OUT="$(bash "$SCRIPT_DIR/span-close.sh")"
|
|
46
|
+
[ "$OUT" = "CLOSED" ] && pass "span closed output" || fail "close output [$OUT]"
|
|
47
|
+
[ ! -e "$TMP/.devlog/.span-open" ] && pass "span file removed" || fail "span remains"
|
|
48
|
+
|
|
49
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
50
|
+
else echo "Some checks FAILED."; exit 1; fi
|