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,530 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for devlog-md.sh's segment-counting and insertion helpers. Run:
|
|
3
|
+
# bash hooks/scripts/test-devlog-md.sh
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
# shellcheck source=devlog-md.sh
|
|
8
|
+
. "$SCRIPT_DIR/devlog-md.sh"
|
|
9
|
+
|
|
10
|
+
TMP_ROOT="$(mktemp -d)"
|
|
11
|
+
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
12
|
+
|
|
13
|
+
FAIL=0
|
|
14
|
+
assert_eq() {
|
|
15
|
+
local desc="$1" expected="$2" actual="$3"
|
|
16
|
+
if [ "$actual" = "$expected" ]; then
|
|
17
|
+
echo "PASS: $desc"
|
|
18
|
+
else
|
|
19
|
+
echo "FAIL: $desc (expected [$expected], got [$actual])"
|
|
20
|
+
FAIL=1
|
|
21
|
+
fi
|
|
22
|
+
}
|
|
23
|
+
assert_contains() {
|
|
24
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
25
|
+
case "$haystack" in
|
|
26
|
+
*"$needle"*) echo "PASS: $desc" ;;
|
|
27
|
+
*) echo "FAIL: $desc (missing: $needle)"; FAIL=1 ;;
|
|
28
|
+
esac
|
|
29
|
+
}
|
|
30
|
+
assert_not_contains() {
|
|
31
|
+
local desc="$1" needle="$2" haystack="$3"
|
|
32
|
+
case "$haystack" in
|
|
33
|
+
*"$needle"*) echo "FAIL: $desc (should not contain: $needle)"; FAIL=1 ;;
|
|
34
|
+
*) echo "PASS: $desc" ;;
|
|
35
|
+
esac
|
|
36
|
+
}
|
|
37
|
+
assert_file_absent() {
|
|
38
|
+
local desc="$1" file="$2"
|
|
39
|
+
if [ ! -f "$file" ]; then
|
|
40
|
+
echo "PASS: $desc"
|
|
41
|
+
else
|
|
42
|
+
echo "FAIL: $desc (file exists: $file)"
|
|
43
|
+
FAIL=1
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
assert_exit() {
|
|
47
|
+
local desc="$1" expected="$2" actual="$3"
|
|
48
|
+
if [ "$actual" -eq "$expected" ]; then
|
|
49
|
+
echo "PASS: $desc"
|
|
50
|
+
else
|
|
51
|
+
echo "FAIL: $desc (expected exit code $expected, got $actual)"
|
|
52
|
+
FAIL=1
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# --- devlog_count_segments: zero segments ----------------------------------
|
|
57
|
+
cat > "$TMP_ROOT/devlog.md" <<'EOF'
|
|
58
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
59
|
+
|
|
60
|
+
### User Input
|
|
61
|
+
```text
|
|
62
|
+
hello
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Summary
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
### Reply
|
|
69
|
+
fixture reply.
|
|
70
|
+
|
|
71
|
+
### Handoff
|
|
72
|
+
#### 現況
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
### Status
|
|
76
|
+
DONE
|
|
77
|
+
EOF
|
|
78
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/devlog.md" | awk '$2==1{print $1}')"
|
|
79
|
+
END="$(devlog_block_end "$TMP_ROOT/devlog.md" "$START")"
|
|
80
|
+
COUNT="$(devlog_count_segments "$TMP_ROOT/devlog.md" "$START" "$END")"
|
|
81
|
+
assert_eq "zero segments" "0" "$COUNT"
|
|
82
|
+
|
|
83
|
+
# --- devlog_count_segments: two existing segments ---------------------------
|
|
84
|
+
cat > "$TMP_ROOT/devlog.md" <<'EOF'
|
|
85
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
86
|
+
|
|
87
|
+
### User Input
|
|
88
|
+
```text
|
|
89
|
+
hello
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 段落 1 - 09:00
|
|
93
|
+
first
|
|
94
|
+
|
|
95
|
+
### 段落 2 - 09:10
|
|
96
|
+
second
|
|
97
|
+
|
|
98
|
+
### Summary
|
|
99
|
+
done
|
|
100
|
+
|
|
101
|
+
### Reply
|
|
102
|
+
fixture reply.
|
|
103
|
+
|
|
104
|
+
### Handoff
|
|
105
|
+
#### 現況
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
### Status
|
|
109
|
+
DONE
|
|
110
|
+
EOF
|
|
111
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/devlog.md" | awk '$2==1{print $1}')"
|
|
112
|
+
END="$(devlog_block_end "$TMP_ROOT/devlog.md" "$START")"
|
|
113
|
+
COUNT="$(devlog_count_segments "$TMP_ROOT/devlog.md" "$START" "$END")"
|
|
114
|
+
assert_eq "two existing segments" "2" "$COUNT"
|
|
115
|
+
|
|
116
|
+
# --- devlog_count_segments: a fenced line that itself looks like a heading
|
|
117
|
+
# must not count (fence-awareness, not just the regex anchor) ---------------
|
|
118
|
+
cat > "$TMP_ROOT/devlog.md" <<'EOF'
|
|
119
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
120
|
+
|
|
121
|
+
### User Input
|
|
122
|
+
```text
|
|
123
|
+
### 段落 1 - 09:00
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Summary
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
### Reply
|
|
130
|
+
fixture reply.
|
|
131
|
+
|
|
132
|
+
### Handoff
|
|
133
|
+
#### 現況
|
|
134
|
+
done
|
|
135
|
+
|
|
136
|
+
### Status
|
|
137
|
+
DONE
|
|
138
|
+
EOF
|
|
139
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/devlog.md" | awk '$2==1{print $1}')"
|
|
140
|
+
END="$(devlog_block_end "$TMP_ROOT/devlog.md" "$START")"
|
|
141
|
+
COUNT="$(devlog_count_segments "$TMP_ROOT/devlog.md" "$START" "$END")"
|
|
142
|
+
assert_eq "fenced mention of 段落 not counted" "0" "$COUNT"
|
|
143
|
+
|
|
144
|
+
# --- devlog_insert_before_summary: inserts right before ### Summary --------
|
|
145
|
+
cat > "$TMP_ROOT/devlog.md" <<'EOF'
|
|
146
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
147
|
+
|
|
148
|
+
### User Input
|
|
149
|
+
```text
|
|
150
|
+
hello
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Summary
|
|
154
|
+
done
|
|
155
|
+
|
|
156
|
+
### Reply
|
|
157
|
+
fixture reply.
|
|
158
|
+
|
|
159
|
+
### Handoff
|
|
160
|
+
#### 現況
|
|
161
|
+
done
|
|
162
|
+
|
|
163
|
+
### Status
|
|
164
|
+
DONE
|
|
165
|
+
|
|
166
|
+
## Round 2 — 2026-09-09T13:00:00+08:00
|
|
167
|
+
|
|
168
|
+
### Summary
|
|
169
|
+
other round summary marker should not be touched
|
|
170
|
+
EOF
|
|
171
|
+
printf '### 段落 1 - 09:30(回覆上一輪的問題)\n```text\nmy answer\n```\n\n' > "$TMP_ROOT/segment.txt"
|
|
172
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/devlog.md" | awk '$2==1{print $1}')"
|
|
173
|
+
END="$(devlog_block_end "$TMP_ROOT/devlog.md" "$START")"
|
|
174
|
+
RESULT="$(devlog_insert_before_summary "$TMP_ROOT/devlog.md" "$START" "$END" "$TMP_ROOT/segment.txt")"
|
|
175
|
+
assert_contains "inserted segment text present" "my answer" "$RESULT"
|
|
176
|
+
# The inserted block must appear before Round 1's Summary, not Round 2's.
|
|
177
|
+
BEFORE_R1_SUMMARY="$(printf '%s\n' "$RESULT" | awk '/my answer/{print NR} /^### Summary$/{print NR; exit}')"
|
|
178
|
+
FIRST_LINE="$(printf '%s\n' "$BEFORE_R1_SUMMARY" | head -1)"
|
|
179
|
+
SECOND_LINE="$(printf '%s\n' "$BEFORE_R1_SUMMARY" | tail -1)"
|
|
180
|
+
if [ "$FIRST_LINE" -lt "$SECOND_LINE" ]; then
|
|
181
|
+
echo "PASS: segment text appears before Round 1's ### Summary"
|
|
182
|
+
else
|
|
183
|
+
echo "FAIL: segment text did not land before Round 1's ### Summary"
|
|
184
|
+
FAIL=1
|
|
185
|
+
fi
|
|
186
|
+
ROUND2_COUNT="$(printf '%s\n' "$RESULT" | grep -c 'other round summary marker')"
|
|
187
|
+
assert_eq "round 2 summary untouched (still exactly once)" "1" "$ROUND2_COUNT"
|
|
188
|
+
|
|
189
|
+
# --- devlog_insert_before_summary: still matches "### Summary" with a
|
|
190
|
+
# trailing space/tab (built via printf, not a literal in the heredoc, since
|
|
191
|
+
# editors tend to strip trailing whitespace on save) -----------------------
|
|
192
|
+
{
|
|
193
|
+
printf '## Round 1 — 2026-09-09T12:00:00+08:00\n\n'
|
|
194
|
+
printf '### User Input\n```text\nhello\n```\n\n'
|
|
195
|
+
printf '### Summary \n'
|
|
196
|
+
printf 'done\n\n'
|
|
197
|
+
printf '### Handoff\n#### 現況\ndone\n\n'
|
|
198
|
+
printf '### Status\nDONE\n'
|
|
199
|
+
} > "$TMP_ROOT/devlog_trailing_ws.md"
|
|
200
|
+
printf '### 段落 1 - 09:30(回覆上一輪的問題)\n```text\nmy trailing-ws answer\n```\n\n' > "$TMP_ROOT/segment_ws.txt"
|
|
201
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/devlog_trailing_ws.md" | awk '$2==1{print $1}')"
|
|
202
|
+
END="$(devlog_block_end "$TMP_ROOT/devlog_trailing_ws.md" "$START")"
|
|
203
|
+
RESULT="$(devlog_insert_before_summary "$TMP_ROOT/devlog_trailing_ws.md" "$START" "$END" "$TMP_ROOT/segment_ws.txt")"
|
|
204
|
+
assert_contains "inserted segment text present (Summary has trailing space)" "my trailing-ws answer" "$RESULT"
|
|
205
|
+
BEFORE_SUMMARY_WS="$(printf '%s\n' "$RESULT" | awk '/my trailing-ws answer/{print NR} /^### Summary[ \t]*$/{print NR; exit}')"
|
|
206
|
+
FIRST_LINE_WS="$(printf '%s\n' "$BEFORE_SUMMARY_WS" | head -1)"
|
|
207
|
+
SECOND_LINE_WS="$(printf '%s\n' "$BEFORE_SUMMARY_WS" | tail -1)"
|
|
208
|
+
if [ "$FIRST_LINE_WS" -lt "$SECOND_LINE_WS" ]; then
|
|
209
|
+
echo "PASS: segment text appears before the trailing-space ### Summary line"
|
|
210
|
+
else
|
|
211
|
+
echo "FAIL: segment text did not land before the trailing-space ### Summary line"
|
|
212
|
+
FAIL=1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# --- indented fence must hide ## Round N from list_round_starts ----------
|
|
217
|
+
cat > "$TMP_ROOT/indent.md" <<'EOF'
|
|
218
|
+
## Round 1 — real
|
|
219
|
+
|
|
220
|
+
### Status
|
|
221
|
+
DONE
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
## Round 99 — fake
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Round 2 — real
|
|
228
|
+
|
|
229
|
+
### Status
|
|
230
|
+
DONE
|
|
231
|
+
EOF
|
|
232
|
+
OUT="$(devlog_list_round_starts "$TMP_ROOT/indent.md")"
|
|
233
|
+
echo "$OUT" | grep -q ' 1$' || { echo "FAIL: missing round 1"; FAIL=1; }
|
|
234
|
+
echo "$OUT" | grep -q ' 2$' || { echo "FAIL: missing round 2"; FAIL=1; }
|
|
235
|
+
if echo "$OUT" | grep -q '99'; then
|
|
236
|
+
echo "FAIL: fenced round 99 counted"
|
|
237
|
+
FAIL=1
|
|
238
|
+
else
|
|
239
|
+
echo "PASS: indented fence hides Round 99"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# --- devlog_strip_kept_index: rebuilding ## Kept 索引 repeatedly must not
|
|
243
|
+
# grow the blank-line separator in front of it (regression: the strip used
|
|
244
|
+
# to preserve the pre-existing separator blank line while the rebuild step
|
|
245
|
+
# in keep-move.sh unconditionally added another one on top of it) ----------
|
|
246
|
+
cat > "$TMP_ROOT/kept.md" <<'EOF'
|
|
247
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
248
|
+
|
|
249
|
+
### Status
|
|
250
|
+
DONE
|
|
251
|
+
EOF
|
|
252
|
+
for i in 1 2 3; do
|
|
253
|
+
STRIPPED="$TMP_ROOT/kept-stripped-$i"
|
|
254
|
+
devlog_strip_kept_index "$TMP_ROOT/kept.md" "$STRIPPED"
|
|
255
|
+
{
|
|
256
|
+
cat "$STRIPPED"
|
|
257
|
+
printf '\n## Kept 索引\n'
|
|
258
|
+
printf -- '- devlog.topic-%s.md\n' "$i"
|
|
259
|
+
} > "$TMP_ROOT/kept.md"
|
|
260
|
+
done
|
|
261
|
+
HEADING_LINE="$(grep -n '^## Kept 索引' "$TMP_ROOT/kept.md" | head -1 | cut -d: -f1)"
|
|
262
|
+
PREV_LINE="$(sed -n "$((HEADING_LINE - 1))p" "$TMP_ROOT/kept.md")"
|
|
263
|
+
PREV2_LINE="$(sed -n "$((HEADING_LINE - 2))p" "$TMP_ROOT/kept.md")"
|
|
264
|
+
if [ -z "$PREV_LINE" ] && [ -n "$PREV2_LINE" ]; then
|
|
265
|
+
echo "PASS: exactly one blank line separates content from ## Kept 索引 after 3 rebuilds"
|
|
266
|
+
else
|
|
267
|
+
echo "FAIL: expected exactly one blank line before ## Kept 索引 after 3 rebuilds, got line $((HEADING_LINE - 1))=[$PREV_LINE] line $((HEADING_LINE - 2))=[$PREV2_LINE]"
|
|
268
|
+
FAIL=1
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
# --- devlog_strip_lessons_index: same blank-line regression check, for
|
|
272
|
+
# "## Lessons 索引" (docs/design/lessons-mode.md) -----------------------
|
|
273
|
+
cat > "$TMP_ROOT/lessons.md" <<'EOF'
|
|
274
|
+
## Round 1 — 2026-09-09T12:00:00+08:00
|
|
275
|
+
|
|
276
|
+
### Status
|
|
277
|
+
DONE
|
|
278
|
+
EOF
|
|
279
|
+
for i in 1 2 3; do
|
|
280
|
+
STRIPPED="$TMP_ROOT/lessons-stripped-$i"
|
|
281
|
+
devlog_strip_lessons_index "$TMP_ROOT/lessons.md" "$STRIPPED"
|
|
282
|
+
{
|
|
283
|
+
cat "$STRIPPED"
|
|
284
|
+
printf '\n## Lessons 索引\n'
|
|
285
|
+
printf -- '- devlog.lessons.topic-%s.md\n' "$i"
|
|
286
|
+
} > "$TMP_ROOT/lessons.md"
|
|
287
|
+
done
|
|
288
|
+
HEADING_LINE="$(grep -n '^## Lessons 索引' "$TMP_ROOT/lessons.md" | head -1 | cut -d: -f1)"
|
|
289
|
+
PREV_LINE="$(sed -n "$((HEADING_LINE - 1))p" "$TMP_ROOT/lessons.md")"
|
|
290
|
+
PREV2_LINE="$(sed -n "$((HEADING_LINE - 2))p" "$TMP_ROOT/lessons.md")"
|
|
291
|
+
if [ -z "$PREV_LINE" ] && [ -n "$PREV2_LINE" ]; then
|
|
292
|
+
echo "PASS: exactly one blank line separates content from ## Lessons 索引 after 3 rebuilds"
|
|
293
|
+
else
|
|
294
|
+
echo "FAIL: expected exactly one blank line before ## Lessons 索引 after 3 rebuilds, got line $((HEADING_LINE - 1))=[$PREV_LINE] line $((HEADING_LINE - 2))=[$PREV2_LINE]"
|
|
295
|
+
FAIL=1
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# --- devlog_lessons_index_lines: reads back only the block body ------------
|
|
299
|
+
LINES="$(devlog_lessons_index_lines "$TMP_ROOT/lessons.md")"
|
|
300
|
+
case "$LINES" in
|
|
301
|
+
*"devlog.lessons.topic-3.md"*) echo "PASS: devlog_lessons_index_lines reads the rebuilt line" ;;
|
|
302
|
+
*) echo "FAIL: expected devlog.lessons.topic-3.md in lines, got: $LINES"; FAIL=1 ;;
|
|
303
|
+
esac
|
|
304
|
+
case "$LINES" in
|
|
305
|
+
*"## Lessons 索引"*) echo "FAIL: devlog_lessons_index_lines must not include the heading itself"; FAIL=1 ;;
|
|
306
|
+
*) echo "PASS: devlog_lessons_index_lines excludes the heading" ;;
|
|
307
|
+
esac
|
|
308
|
+
|
|
309
|
+
# --- last-round 工作區 body + claim state (no git needed for NO_CLAIM) ----
|
|
310
|
+
cat > "$TMP_ROOT/claim.md" <<'EOF'
|
|
311
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
312
|
+
|
|
313
|
+
### Summary
|
|
314
|
+
old
|
|
315
|
+
|
|
316
|
+
### Reply
|
|
317
|
+
fixture reply.
|
|
318
|
+
|
|
319
|
+
### Handoff
|
|
320
|
+
#### 工作區
|
|
321
|
+
main @ deadbeef,工作樹乾淨
|
|
322
|
+
#### 現況
|
|
323
|
+
still going
|
|
324
|
+
|
|
325
|
+
### Status
|
|
326
|
+
IN_PROGRESS
|
|
327
|
+
|
|
328
|
+
## Round 2 — 2026-09-11T00:01:00+08:00
|
|
329
|
+
|
|
330
|
+
### Summary
|
|
331
|
+
newer
|
|
332
|
+
|
|
333
|
+
### Reply
|
|
334
|
+
fixture reply.
|
|
335
|
+
|
|
336
|
+
### Handoff
|
|
337
|
+
#### 工作區
|
|
338
|
+
feat/foo @ abc1234
|
|
339
|
+
未提交:a.txt
|
|
340
|
+
#### 現況
|
|
341
|
+
x
|
|
342
|
+
|
|
343
|
+
### Status
|
|
344
|
+
BLOCKED
|
|
345
|
+
EOF
|
|
346
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/claim.md" | awk 'END { print $1 }')"
|
|
347
|
+
END="$(devlog_block_end "$TMP_ROOT/claim.md" "$START")"
|
|
348
|
+
assert_eq "last round is 2" "2" "$(devlog_list_round_starts "$TMP_ROOT/claim.md" | awk 'END { print $2 }')"
|
|
349
|
+
assert_eq "last-round status BLOCKED" "BLOCKED" "$(devlog_round_status "$TMP_ROOT/claim.md" "$START" "$END")"
|
|
350
|
+
WS_BODY="$(devlog_round_workspace_body "$TMP_ROOT/claim.md" "$START" "$END")"
|
|
351
|
+
EXPECTED_BODY='feat/foo @ abc1234
|
|
352
|
+
未提交:a.txt'
|
|
353
|
+
assert_eq "workspace body two lines" "$EXPECTED_BODY" "$WS_BODY"
|
|
354
|
+
|
|
355
|
+
cat > "$TMP_ROOT/segments.md" <<'EOF'
|
|
356
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
357
|
+
|
|
358
|
+
### User Input
|
|
359
|
+
main @ user-input-only,工作樹乾淨
|
|
360
|
+
|
|
361
|
+
### 段落 1
|
|
362
|
+
first
|
|
363
|
+
```text
|
|
364
|
+
main @ live123,工作樹乾淨
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Summary
|
|
368
|
+
main @ summary-only,工作樹乾淨
|
|
369
|
+
|
|
370
|
+
### 段落 2
|
|
371
|
+
second
|
|
372
|
+
EOF
|
|
373
|
+
SEG_START="$(devlog_list_round_starts "$TMP_ROOT/segments.md" | awk 'END { print $1 }')"
|
|
374
|
+
SEG_END="$(devlog_block_end "$TMP_ROOT/segments.md" "$SEG_START")"
|
|
375
|
+
SEG_BODY="$(devlog_round_segments_body "$TMP_ROOT/segments.md" "$SEG_START" "$SEG_END")"
|
|
376
|
+
assert_contains "segment bodies include first segment" "first" "$SEG_BODY"
|
|
377
|
+
assert_contains "segment bodies include fenced snapshot" "main @ live123,工作樹乾淨" "$SEG_BODY"
|
|
378
|
+
assert_contains "segment bodies include second segment" "second" "$SEG_BODY"
|
|
379
|
+
case "$SEG_BODY" in
|
|
380
|
+
*user-input-only*|*summary-only*) echo "FAIL: non-segment text leaked"; FAIL=1 ;;
|
|
381
|
+
*) echo "PASS: non-segment text excluded" ;;
|
|
382
|
+
esac
|
|
383
|
+
|
|
384
|
+
cat > "$TMP_ROOT/done.md" <<'EOF'
|
|
385
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
386
|
+
|
|
387
|
+
### Handoff
|
|
388
|
+
#### 工作區
|
|
389
|
+
main @ deadbeef,工作樹乾淨
|
|
390
|
+
|
|
391
|
+
### Status
|
|
392
|
+
DONE
|
|
393
|
+
EOF
|
|
394
|
+
assert_eq "DONE is NO_CLAIM" "NO_CLAIM" "$(workspace_claim_state "$TMP_ROOT" "$TMP_ROOT/done.md")"
|
|
395
|
+
|
|
396
|
+
cat > "$TMP_ROOT/empty-ws.md" <<'EOF'
|
|
397
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
398
|
+
|
|
399
|
+
### Handoff
|
|
400
|
+
#### 現況
|
|
401
|
+
x
|
|
402
|
+
|
|
403
|
+
### Status
|
|
404
|
+
IN_PROGRESS
|
|
405
|
+
EOF
|
|
406
|
+
assert_eq "missing 工作區 is NO_CLAIM" "NO_CLAIM" "$(workspace_claim_state "$TMP_ROOT" "$TMP_ROOT/empty-ws.md")"
|
|
407
|
+
|
|
408
|
+
cat > "$TMP_ROOT/interrupted.md" <<'EOF'
|
|
409
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
410
|
+
|
|
411
|
+
### Handoff
|
|
412
|
+
#### 工作區
|
|
413
|
+
main @ deadbeef,工作樹乾淨
|
|
414
|
+
|
|
415
|
+
### Status
|
|
416
|
+
INTERRUPTED
|
|
417
|
+
[reason: dangling:next_prompt]
|
|
418
|
+
EOF
|
|
419
|
+
assert_eq "INTERRUPTED+reason with 工作區 is not DONE" "MISMATCH" "$(workspace_claim_state "$TMP_ROOT" "$TMP_ROOT/interrupted.md")"
|
|
420
|
+
|
|
421
|
+
# --- fence parity bug: an unclosed ``` earlier in the round (e.g. pasted
|
|
422
|
+
# code in User Input that forgot to close its fence) must not make the
|
|
423
|
+
# real #### 工作區 further down invisible. enforce-devlog.sh guards this
|
|
424
|
+
# exact class of bug with NOFENCE fail-open; devlog-md.sh's range-scoped
|
|
425
|
+
# functions (used by workspace_claim_state / round-start.sh /
|
|
426
|
+
# segment-watch.sh) must degrade the same way, not silently return empty. --
|
|
427
|
+
cat > "$TMP_ROOT/unclosed-fence.md" <<'EOF'
|
|
428
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
429
|
+
|
|
430
|
+
### User Input
|
|
431
|
+
```text
|
|
432
|
+
使用者貼了一段忘記收尾的程式碼
|
|
433
|
+
def foo():
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
### Handoff
|
|
437
|
+
#### 工作區
|
|
438
|
+
main @ deadbeef,工作樹乾淨
|
|
439
|
+
#### 現況
|
|
440
|
+
測試 fence parity bug
|
|
441
|
+
|
|
442
|
+
### Status
|
|
443
|
+
IN_PROGRESS
|
|
444
|
+
EOF
|
|
445
|
+
START="$(devlog_list_round_starts "$TMP_ROOT/unclosed-fence.md" | awk 'END { print $1 }')"
|
|
446
|
+
END="$(devlog_block_end "$TMP_ROOT/unclosed-fence.md" "$START")"
|
|
447
|
+
assert_eq "status survives unclosed fence" "IN_PROGRESS" "$(devlog_round_status "$TMP_ROOT/unclosed-fence.md" "$START" "$END")"
|
|
448
|
+
assert_eq "workspace body survives unclosed fence" "main @ deadbeef,工作樹乾淨" "$(devlog_round_workspace_body "$TMP_ROOT/unclosed-fence.md" "$START" "$END")"
|
|
449
|
+
# workspace_claim_state on a non-git dir: workspace_snapshot returns "非 git
|
|
450
|
+
# 工作區", which differs from the claimed body above -> must be MISMATCH.
|
|
451
|
+
# Before the fix this came back NO_CLAIM because the claimed body parsed
|
|
452
|
+
# empty, silently defeating the whole drift-detection feature.
|
|
453
|
+
NONGIT_DIR="$TMP_ROOT/nongit"
|
|
454
|
+
mkdir -p "$NONGIT_DIR"
|
|
455
|
+
assert_eq "unclosed fence still yields a claim (MISMATCH, not NO_CLAIM)" "MISMATCH" "$(workspace_claim_state "$NONGIT_DIR" "$TMP_ROOT/unclosed-fence.md")"
|
|
456
|
+
|
|
457
|
+
cat > "$TMP_ROOT/unclosed-fence-segments.md" <<'EOF'
|
|
458
|
+
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
459
|
+
|
|
460
|
+
### User Input
|
|
461
|
+
```text
|
|
462
|
+
使用者貼了一段忘記收尾的程式碼
|
|
463
|
+
def foo():
|
|
464
|
+
pass
|
|
465
|
+
|
|
466
|
+
### 段落 1
|
|
467
|
+
real segment content
|
|
468
|
+
|
|
469
|
+
### Status
|
|
470
|
+
IN_PROGRESS
|
|
471
|
+
EOF
|
|
472
|
+
SEG_START="$(devlog_list_round_starts "$TMP_ROOT/unclosed-fence-segments.md" | awk 'END { print $1 }')"
|
|
473
|
+
SEG_END="$(devlog_block_end "$TMP_ROOT/unclosed-fence-segments.md" "$SEG_START")"
|
|
474
|
+
assert_contains "segment body survives unclosed fence" "real segment content" "$(devlog_round_segments_body "$TMP_ROOT/unclosed-fence-segments.md" "$SEG_START" "$SEG_END")"
|
|
475
|
+
|
|
476
|
+
# --- devlog_merge_round_current ---------------------------------------
|
|
477
|
+
mkdir -p "$TMP_ROOT/.devlog"
|
|
478
|
+
MERGE_MAIN="$TMP_ROOT/.devlog/merge-main.md"
|
|
479
|
+
MERGE_CUR="$TMP_ROOT/.devlog/merge-current.md"
|
|
480
|
+
|
|
481
|
+
printf '## Round 1 — 2026-09-17T09:00:00+0800\n\n### Summary\ndone\n\n### Handoff\n#### 現況\nok\n\n### Status\nDONE\n' > "$MERGE_MAIN"
|
|
482
|
+
printf '## Round 2 — 2026-09-17T09:05:00+0800\n\n### User Input\n```text\nhi\n```\n\n### Summary\ns2\n\n### Handoff\n#### 現況\nc2\n\n### Status\nDONE\n' > "$MERGE_CUR"
|
|
483
|
+
|
|
484
|
+
devlog_merge_round_current "$MERGE_MAIN" "$MERGE_CUR"
|
|
485
|
+
|
|
486
|
+
MERGED="$(cat "$MERGE_MAIN")"
|
|
487
|
+
assert_contains "merge: round 1 still present" "## Round 1" "$MERGED"
|
|
488
|
+
assert_contains "merge: round 2 appended" "## Round 2" "$MERGED"
|
|
489
|
+
assert_file_absent "merge: round-current removed after merge" "$MERGE_CUR"
|
|
490
|
+
|
|
491
|
+
# merging an absent/empty round-current is a silent no-op
|
|
492
|
+
BEFORE="$(cat "$MERGE_MAIN")"
|
|
493
|
+
devlog_merge_round_current "$MERGE_MAIN" "$MERGE_CUR"
|
|
494
|
+
AFTER="$(cat "$MERGE_MAIN")"
|
|
495
|
+
if [ "$BEFORE" = "$AFTER" ]; then echo "PASS: merge no-op when round-current absent"; else echo "FAIL: merge no-op when round-current absent"; FAIL=1; fi
|
|
496
|
+
|
|
497
|
+
# --- devlog_reopen_last_round ------------------------------------------
|
|
498
|
+
REOPEN_MAIN="$TMP_ROOT/.devlog/reopen-main.md"
|
|
499
|
+
REOPEN_CUR="$TMP_ROOT/.devlog/reopen-current.md"
|
|
500
|
+
rm -f "$REOPEN_CUR"
|
|
501
|
+
|
|
502
|
+
printf '## Round 1 — 2026-09-17T09:00:00+0800\n\n### Summary\ns1\n\n### Handoff\n#### 現況\nc1\n\n### Status\nDONE\n\n## Round 2 — 2026-09-17T09:10:00+0800\n\n### User Input\n```text\n問題?\n```\n\n### Summary\ns2\n\n### Handoff\n#### 現況\nBLOCKED 等答案\n\n### Status\nBLOCKED\n' > "$REOPEN_MAIN"
|
|
503
|
+
|
|
504
|
+
devlog_reopen_last_round "$REOPEN_MAIN" "$REOPEN_CUR"
|
|
505
|
+
RC=$?
|
|
506
|
+
assert_exit "reopen: returns 0 when a round exists" 0 "$RC"
|
|
507
|
+
|
|
508
|
+
REOPENED="$(cat "$REOPEN_CUR" 2>/dev/null || echo '')"
|
|
509
|
+
assert_contains "reopen: round 2 moved into round-current" "## Round 2" "$REOPENED"
|
|
510
|
+
assert_not_contains "reopen: round 1 not pulled along" "## Round 1" "$REOPENED"
|
|
511
|
+
|
|
512
|
+
REMAINING="$(cat "$REOPEN_MAIN")"
|
|
513
|
+
assert_contains "reopen: round 1 stays in main file" "## Round 1" "$REMAINING"
|
|
514
|
+
assert_not_contains "reopen: round 2 removed from main file" "## Round 2" "$REMAINING"
|
|
515
|
+
|
|
516
|
+
# reopen on a file with no round at all fails without touching either file
|
|
517
|
+
EMPTY_MAIN="$TMP_ROOT/.devlog/empty-main.md"
|
|
518
|
+
: > "$EMPTY_MAIN"
|
|
519
|
+
rm -f "$TMP_ROOT/.devlog/should-not-exist.md"
|
|
520
|
+
devlog_reopen_last_round "$EMPTY_MAIN" "$TMP_ROOT/.devlog/should-not-exist.md"
|
|
521
|
+
assert_exit "reopen: returns 1 when no round exists" 1 $?
|
|
522
|
+
assert_file_absent "reopen: no round-current created on failure" "$TMP_ROOT/.devlog/should-not-exist.md"
|
|
523
|
+
|
|
524
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
525
|
+
echo "All checks passed."
|
|
526
|
+
exit 0
|
|
527
|
+
else
|
|
528
|
+
echo "Some checks FAILED."
|
|
529
|
+
exit 1
|
|
530
|
+
fi
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
4
|
+
. "$SCRIPT_DIR/devlog-path.sh"
|
|
5
|
+
TMP="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
7
|
+
FAIL=0
|
|
8
|
+
|
|
9
|
+
git_setup() {
|
|
10
|
+
git -C "$1" init -q
|
|
11
|
+
git -C "$1" -c user.email=t@t.t -c user.name=t commit -q --allow-empty -m init
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# --- non-git directory: falls back to devlog.md -------------------------
|
|
15
|
+
NONGIT="$TMP/nongit"
|
|
16
|
+
mkdir -p "$NONGIT"
|
|
17
|
+
devlog_resolve_paths "$NONGIT"
|
|
18
|
+
[ "$DEVLOG_FILE" = "$NONGIT/.devlog/devlog.md" ] \
|
|
19
|
+
&& echo "PASS: non-git falls back to devlog.md" \
|
|
20
|
+
|| { echo "FAIL: non-git got $DEVLOG_FILE"; FAIL=1; }
|
|
21
|
+
|
|
22
|
+
# --- main branch: devlog.md ----------------------------------------------
|
|
23
|
+
MAINREPO="$TMP/mainrepo"
|
|
24
|
+
mkdir -p "$MAINREPO"
|
|
25
|
+
git_setup "$MAINREPO"
|
|
26
|
+
git -C "$MAINREPO" branch -M main
|
|
27
|
+
devlog_resolve_paths "$MAINREPO"
|
|
28
|
+
[ "$DEVLOG_FILE" = "$MAINREPO/.devlog/devlog.md" ] \
|
|
29
|
+
&& echo "PASS: main branch keeps devlog.md" \
|
|
30
|
+
|| { echo "FAIL: main got $DEVLOG_FILE"; FAIL=1; }
|
|
31
|
+
|
|
32
|
+
# --- master branch: devlog.md --------------------------------------------
|
|
33
|
+
MASTERREPO="$TMP/masterrepo"
|
|
34
|
+
mkdir -p "$MASTERREPO"
|
|
35
|
+
git_setup "$MASTERREPO"
|
|
36
|
+
git -C "$MASTERREPO" branch -M master
|
|
37
|
+
devlog_resolve_paths "$MASTERREPO"
|
|
38
|
+
[ "$DEVLOG_FILE" = "$MASTERREPO/.devlog/devlog.md" ] \
|
|
39
|
+
&& echo "PASS: master branch keeps devlog.md" \
|
|
40
|
+
|| { echo "FAIL: master got $DEVLOG_FILE"; FAIL=1; }
|
|
41
|
+
|
|
42
|
+
# --- feature branch: devlog.<branch>.md ----------------------------------
|
|
43
|
+
FEATREPO="$TMP/featrepo"
|
|
44
|
+
mkdir -p "$FEATREPO"
|
|
45
|
+
git_setup "$FEATREPO"
|
|
46
|
+
git -C "$FEATREPO" checkout -q -b feature-x
|
|
47
|
+
devlog_resolve_paths "$FEATREPO"
|
|
48
|
+
[ "$DEVLOG_FILE" = "$FEATREPO/.devlog/devlog.feature-x.md" ] \
|
|
49
|
+
&& echo "PASS: feature branch maps to devlog.feature-x.md" \
|
|
50
|
+
|| { echo "FAIL: feature-x got $DEVLOG_FILE"; FAIL=1; }
|
|
51
|
+
|
|
52
|
+
# --- branch with slash: sanitized ----------------------------------------
|
|
53
|
+
SLASHREPO="$TMP/slashrepo"
|
|
54
|
+
mkdir -p "$SLASHREPO"
|
|
55
|
+
git_setup "$SLASHREPO"
|
|
56
|
+
git -C "$SLASHREPO" checkout -q -b feature/foo
|
|
57
|
+
devlog_resolve_paths "$SLASHREPO"
|
|
58
|
+
[ "$DEVLOG_FILE" = "$SLASHREPO/.devlog/devlog.feature-foo.md" ] \
|
|
59
|
+
&& echo "PASS: slash in branch name sanitized to -" \
|
|
60
|
+
|| { echo "FAIL: feature/foo got $DEVLOG_FILE"; FAIL=1; }
|
|
61
|
+
|
|
62
|
+
# --- detached HEAD: falls back to worktree dirname -----------------------
|
|
63
|
+
DETACHEDREPO="$TMP/detached-repo"
|
|
64
|
+
mkdir -p "$DETACHEDREPO"
|
|
65
|
+
git_setup "$DETACHEDREPO"
|
|
66
|
+
git -C "$DETACHEDREPO" checkout -q --detach
|
|
67
|
+
devlog_resolve_paths "$DETACHEDREPO"
|
|
68
|
+
[ "$DEVLOG_FILE" = "$DETACHEDREPO/.devlog/devlog.detached-repo.md" ] \
|
|
69
|
+
&& echo "PASS: detached HEAD falls back to worktree dirname" \
|
|
70
|
+
|| { echo "FAIL: detached got $DEVLOG_FILE"; FAIL=1; }
|
|
71
|
+
|
|
72
|
+
# --- migration: existing devlog.md content moves to branch file ----------
|
|
73
|
+
MIGREPO="$TMP/migrepo"
|
|
74
|
+
mkdir -p "$MIGREPO/.devlog"
|
|
75
|
+
: > "$MIGREPO/.devlog/.enabled"
|
|
76
|
+
git_setup "$MIGREPO"
|
|
77
|
+
git -C "$MIGREPO" checkout -q -b feature-y
|
|
78
|
+
echo "## Round 1 existing content" > "$MIGREPO/.devlog/devlog.md"
|
|
79
|
+
devlog_resolve_paths "$MIGREPO"
|
|
80
|
+
if [ "$DEVLOG_FILE" = "$MIGREPO/.devlog/devlog.feature-y.md" ] \
|
|
81
|
+
&& [ -f "$DEVLOG_FILE" ] \
|
|
82
|
+
&& [ ! -f "$MIGREPO/.devlog/devlog.md" ] \
|
|
83
|
+
&& grep -q "existing content" "$DEVLOG_FILE"; then
|
|
84
|
+
echo "PASS: migration renames devlog.md into branch file"
|
|
85
|
+
else
|
|
86
|
+
echo "FAIL: migration file=$DEVLOG_FILE"
|
|
87
|
+
FAIL=1
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- migration is idempotent: second call doesn't re-move or error -------
|
|
91
|
+
echo "## Round 2 written after migration" >> "$MIGREPO/.devlog/devlog.feature-y.md"
|
|
92
|
+
devlog_resolve_paths "$MIGREPO"
|
|
93
|
+
if [ "$DEVLOG_FILE" = "$MIGREPO/.devlog/devlog.feature-y.md" ] \
|
|
94
|
+
&& grep -q "Round 2 written after migration" "$DEVLOG_FILE"; then
|
|
95
|
+
echo "PASS: second resolve is a no-op, content preserved"
|
|
96
|
+
else
|
|
97
|
+
echo "FAIL: second resolve altered state, got $DEVLOG_FILE"
|
|
98
|
+
FAIL=1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# --- migration does NOT fire when .devlog/.enabled is absent (Fix 6) -----
|
|
102
|
+
NOENABLEDREPO="$TMP/noenabledrepo"
|
|
103
|
+
mkdir -p "$NOENABLEDREPO/.devlog"
|
|
104
|
+
git_setup "$NOENABLEDREPO"
|
|
105
|
+
git -C "$NOENABLEDREPO" checkout -q -b feature-z
|
|
106
|
+
echo "## Round 1 existing content" > "$NOENABLEDREPO/.devlog/devlog.md"
|
|
107
|
+
devlog_resolve_paths "$NOENABLEDREPO"
|
|
108
|
+
if [ "$DEVLOG_FILE" = "$NOENABLEDREPO/.devlog/devlog.feature-z.md" ] \
|
|
109
|
+
&& [ ! -f "$DEVLOG_FILE" ] \
|
|
110
|
+
&& [ -f "$NOENABLEDREPO/.devlog/devlog.md" ]; then
|
|
111
|
+
echo "PASS: migration does not fire when .enabled is absent, old devlog.md left untouched"
|
|
112
|
+
else
|
|
113
|
+
echo "FAIL: no-.enabled migration file=$DEVLOG_FILE resolved_exists=$([ -f "$DEVLOG_FILE" ] && echo y || echo n) old_exists=$([ -f "$NOENABLEDREPO/.devlog/devlog.md" ] && echo y || echo n)"
|
|
114
|
+
FAIL=1
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
118
|
+
else echo "Some checks FAILED."; exit 1; fi
|