devlog-tracker 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/README.zh-TW.md +7 -5
- package/codex/hooks/on-interrupt.sh +1 -0
- package/codex/hooks/on-pre-tool.sh +9 -7
- package/codex/hooks/on-session-end.sh +1 -0
- package/codex/hooks/on-session-start.sh +1 -0
- package/codex/hooks/on-stop.sh +1 -0
- package/codex/hooks/on-subagent-start.sh +1 -0
- package/codex/hooks/on-user-prompt-submit.sh +1 -0
- package/codex/hooks/test-adapters.sh +29 -19
- package/commands/clean.md +2 -2
- package/commands/continue.md +1 -1
- package/commands/keep-all.md +4 -4
- package/commands/keep.md +4 -4
- package/commands/migrate.md +2 -2
- package/commands/span.md +4 -4
- package/commands/status.md +1 -1
- package/core/scripts/await-open.sh +4 -4
- package/core/scripts/clean-devlog.sh +11 -5
- package/core/scripts/close-open-round.sh +0 -4
- package/core/scripts/devlog-md.sh +89 -8
- package/core/scripts/devlog-path.sh +128 -10
- package/core/scripts/enforce-devlog.sh +35 -19
- package/core/scripts/keep-all.js +4 -3
- package/core/scripts/keep-all.sh +2 -3
- package/core/scripts/keep-all.test.js +31 -0
- package/core/scripts/keep-move.sh +13 -9
- package/core/scripts/migrate-handoff.sh +2 -2
- package/core/scripts/on-tool-failure.sh +6 -1
- package/core/scripts/pause-devlog.sh +5 -3
- package/core/scripts/round-start.sh +31 -20
- package/core/scripts/segment-watch-set.sh +12 -8
- package/core/scripts/segment-watch.sh +3 -6
- package/core/scripts/session-start-devlog.sh +11 -5
- package/core/scripts/span-close.sh +7 -3
- package/core/scripts/span-open.sh +4 -4
- package/core/scripts/status-devlog.sh +6 -6
- package/core/scripts/tests/test-await-open.sh +9 -0
- package/core/scripts/tests/test-devlog-md.sh +76 -15
- package/core/scripts/tests/test-devlog-path.sh +126 -0
- package/core/scripts/tests/test-multi-platform.sh +286 -0
- package/core/scripts/tests/test-segment-watch-set.sh +10 -0
- package/core/scripts/tests/test-session-start-devlog.sh +15 -0
- package/cursor/hooks/on-pre-tool.sh +1 -0
- package/cursor/hooks/on-session-end.sh +1 -0
- package/cursor/hooks/on-session-start.sh +1 -0
- package/cursor/hooks/on-stop.sh +1 -0
- package/cursor/hooks/on-submit-prompt.sh +1 -0
- package/cursor/hooks/on-tool-failure.sh +1 -0
- package/cursor/hooks/test-adapters.sh +19 -16
- package/package.json +1 -1
- package/skills/devlog-tracker/SKILL.md +23 -18
- package/skills/devlog-tracker/references/contract.md +2 -2
- package/skills/devlog-tracker/references/reply-fold.md +8 -8
- package/skills/devlog-tracker/references/round-segments.md +2 -2
- package/skills/devlog-tracker/references/span-mode.md +10 -3
|
@@ -29,12 +29,7 @@ HOOKS_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
29
29
|
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
30
30
|
[ -f "$PROJECT_DIR/.devlog/.enabled" ] || exit 0
|
|
31
31
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
32
|
-
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
33
|
-
SPAN_FILE="$DEVLOG_DIR/.span-open"
|
|
34
32
|
CHECKPOINT_FILE="$DEVLOG_DIR/.checkpoint-state"
|
|
35
|
-
SEGMENT_FILE="$DEVLOG_DIR/.segment-state"
|
|
36
|
-
ROUND_OPEN="$DEVLOG_DIR/.round-open"
|
|
37
|
-
AWAITING_FILE="$DEVLOG_DIR/.awaiting-reply"
|
|
38
33
|
ADVISORY_FILE="$DEVLOG_DIR/.lessons-advisory-state"
|
|
39
34
|
|
|
40
35
|
devlog_lock_acquire
|
|
@@ -59,11 +54,9 @@ if [ -f "$AWAITING_FILE" ]; then
|
|
|
59
54
|
case "$AWAIT_ROUND" in
|
|
60
55
|
''|*[!0-9]*) AWAIT_ROUND='' ;;
|
|
61
56
|
esac
|
|
62
|
-
if [ -n "$AWAIT_ROUND" ] && [ -f "$DEVLOG_FILE" ]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
FOLD_ROUND="$AWAIT_ROUND"
|
|
66
|
-
fi
|
|
57
|
+
if [ -n "$AWAIT_ROUND" ] && [ -f "$DEVLOG_FILE" ] \
|
|
58
|
+
&& [ -n "$(devlog_fold_round_start "$DEVLOG_FILE" "$AWAIT_ROUND" "$DEVLOG_PLATFORM")" ]; then
|
|
59
|
+
FOLD_ROUND="$AWAIT_ROUND"
|
|
67
60
|
fi
|
|
68
61
|
fi
|
|
69
62
|
|
|
@@ -155,7 +148,7 @@ if [ "$SPAN_SKIP" -eq 1 ]; then
|
|
|
155
148
|
fi
|
|
156
149
|
|
|
157
150
|
if [ "$TASK_NOTIF" -eq 1 ] && [ -z "$FOLD_ROUND" ] && [ "$SPAN_SKIP" -eq 0 ] && [ -f "$DEVLOG_FILE" ]; then
|
|
158
|
-
TASK_NOTIF_LAST="$(
|
|
151
|
+
TASK_NOTIF_LAST="$(devlog_list_round_starts_of "$DEVLOG_FILE" "$DEVLOG_PLATFORM" | awk 'END { print $2 }')"
|
|
159
152
|
case "$TASK_NOTIF_LAST" in
|
|
160
153
|
''|*[!0-9]*) : ;;
|
|
161
154
|
*)
|
|
@@ -165,18 +158,17 @@ if [ "$TASK_NOTIF" -eq 1 ] && [ -z "$FOLD_ROUND" ] && [ "$SPAN_SKIP" -eq 0 ] &&
|
|
|
165
158
|
esac
|
|
166
159
|
fi
|
|
167
160
|
|
|
168
|
-
MISMATCH_FILE="$DEVLOG_DIR/.workspace-mismatch"
|
|
169
161
|
rm -f "$MISMATCH_FILE" 2>/dev/null || true
|
|
170
162
|
if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ]; then
|
|
171
|
-
CLAIM_ST="$(workspace_claim_state "$PROJECT_DIR" "$DEVLOG_FILE" 2>/dev/null || echo NO_CLAIM)"
|
|
163
|
+
CLAIM_ST="$(workspace_claim_state "$PROJECT_DIR" "$DEVLOG_FILE" "$DEVLOG_PLATFORM" 2>/dev/null || echo NO_CLAIM)"
|
|
172
164
|
if [ "$CLAIM_ST" = "MISMATCH" ]; then
|
|
173
|
-
LAST_START="$(
|
|
165
|
+
LAST_START="$(devlog_list_round_starts_of "$DEVLOG_FILE" "$DEVLOG_PLATFORM" | awk 'END { print $1 }')"
|
|
174
166
|
LAST_END="$(devlog_block_end "$DEVLOG_FILE" "$LAST_START")"
|
|
175
167
|
CLAIMED_WS="$(devlog_round_workspace_body "$DEVLOG_FILE" "$LAST_START" "$LAST_END")"
|
|
176
168
|
LIVE_WS="$(workspace_snapshot "$PROJECT_DIR")"
|
|
177
169
|
if [ -n "$LIVE_WS" ]; then
|
|
178
170
|
printf '%s\n' "$LIVE_WS" > "$MISMATCH_FILE" 2>/dev/null || true
|
|
179
|
-
printf '%s\n' "上一輪 Handoff 的工作區(\`<workspace>\`;舊格式是 \`#### 工作區\`)跟目前 git 不符。先在這一輪追加 ### 段落,寫宣稱 vs
|
|
171
|
+
printf '%s\n' "上一輪 Handoff 的工作區(\`<workspace>\`;舊格式是 \`#### 工作區\`)跟目前 git 不符。先在這一輪追加 ### 段落,寫宣稱 vs 實際(實際用下面「實際」逐字內容),再依實際工作樹行動,不要照上一輪「現況/下一步」的字面。(若同一工作樹還有其他平台在跑,差異可能來自它。)"
|
|
180
172
|
printf '\n宣稱:\n%s\n\n實際:\n%s\n' "$CLAIMED_WS" "$LIVE_WS"
|
|
181
173
|
|
|
182
174
|
if [ -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
@@ -188,7 +180,7 @@ if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ]; t
|
|
|
188
180
|
fi
|
|
189
181
|
|
|
190
182
|
if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ] && [ -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
191
|
-
BLOCKED_ROUND_STARTS="$(
|
|
183
|
+
BLOCKED_ROUND_STARTS="$(devlog_list_round_starts_of "$DEVLOG_FILE" "$DEVLOG_PLATFORM")"
|
|
192
184
|
BLOCKED_ROUND_COUNT="$(printf '%s\n' "$BLOCKED_ROUND_STARTS" | grep -c '.' || true)"
|
|
193
185
|
BLOCKED_LAST_LINE="$(printf '%s\n' "$BLOCKED_ROUND_STARTS" | awk 'END { print }')"
|
|
194
186
|
BLOCKED_LAST_START="$(printf '%s\n' "$BLOCKED_LAST_LINE" | awk '{ print $1 }')"
|
|
@@ -214,7 +206,7 @@ if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ] &&
|
|
|
214
206
|
fi
|
|
215
207
|
|
|
216
208
|
if [ -n "$FOLD_ROUND" ]; then
|
|
217
|
-
|
|
209
|
+
devlog_reopen_round "$DEVLOG_FILE" "$ROUND_CURRENT" "$FOLD_ROUND" "$DEVLOG_PLATFORM" || : > "$ROUND_CURRENT"
|
|
218
210
|
if [ -z "$PROMPT" ]; then
|
|
219
211
|
PROMPT="(無 prompt)"
|
|
220
212
|
fi
|
|
@@ -251,6 +243,7 @@ if [ -n "$FOLD_ROUND" ]; then
|
|
|
251
243
|
fi
|
|
252
244
|
|
|
253
245
|
printf '{"round": %s, "opened_at": "%s", "file": "%s"}\n' "$FOLD_ROUND" "$FULL_TS" "${DEVLOG_FILE##*/}" > "$ROUND_OPEN" 2>/dev/null || true
|
|
246
|
+
[ "$DEVLOG_PLATFORM" = claude ] || printf '這一輪寫在 .devlog/%s。\n' "${ROUND_CURRENT##*/}"
|
|
254
247
|
elif [ "$SPAN_SKIP" -eq 0 ]; then
|
|
255
248
|
if [ -z "$PROMPT" ]; then
|
|
256
249
|
PROMPT="(無 prompt)"
|
|
@@ -285,11 +278,20 @@ elif [ "$SPAN_SKIP" -eq 0 ]; then
|
|
|
285
278
|
' "$DEVLOG_FILE" 2>/dev/null || echo 0)"
|
|
286
279
|
case "$LAST_N" in ''|*[!0-9]*) LAST_N=0 ;; esac
|
|
287
280
|
fi
|
|
281
|
+
# Another platform's still-open round already owns its number
|
|
282
|
+
# (docs/design/multi-platform-concurrency.md "Round numbering").
|
|
283
|
+
RESERVED_N="$(devlog_open_rounds | awk -F '\t' -v f="${DEVLOG_FILE##*/}" '
|
|
284
|
+
($3 == f || $3 == "") && $2 ~ /^[0-9]+$/ && $2 + 0 > m { m = $2 + 0 }
|
|
285
|
+
END { print m + 0 }')"
|
|
286
|
+
case "$RESERVED_N" in ''|*[!0-9]*) RESERVED_N=0 ;; esac
|
|
287
|
+
[ "$RESERVED_N" -gt "$LAST_N" ] && LAST_N="$RESERVED_N"
|
|
288
288
|
NEXT_N=$((LAST_N + 1))
|
|
289
289
|
TS="$(date +%Y-%m-%dT%H:%M:%S%z 2>/dev/null || echo unknown)"
|
|
290
290
|
|
|
291
|
+
HEADING_SOURCE=""
|
|
292
|
+
[ "$DEVLOG_PLATFORM" = claude ] || HEADING_SOURCE=" · $DEVLOG_PLATFORM"
|
|
291
293
|
{
|
|
292
|
-
printf '## Round %s — %s\n\n' "$NEXT_N" "$TS"
|
|
294
|
+
printf '## Round %s — %s%s\n\n' "$NEXT_N" "$TS" "$HEADING_SOURCE"
|
|
293
295
|
printf '### User Input\n'
|
|
294
296
|
printf '```text\n'
|
|
295
297
|
printf '%s\n' "$PROMPT"
|
|
@@ -302,13 +304,14 @@ elif [ "$SPAN_SKIP" -eq 0 ]; then
|
|
|
302
304
|
|
|
303
305
|
if [ -f "$ROUND_CURRENT" ]; then
|
|
304
306
|
printf '{"round": %s, "opened_at": "%s", "file": "%s"}\n' "$NEXT_N" "$TS" "${DEVLOG_FILE##*/}" > "$ROUND_OPEN" 2>/dev/null || true
|
|
307
|
+
[ "$DEVLOG_PLATFORM" = claude ] || printf '這一輪寫在 .devlog/%s。\n' "${ROUND_CURRENT##*/}"
|
|
305
308
|
fi
|
|
306
309
|
fi
|
|
307
310
|
|
|
308
311
|
if [ -f "$ROUND_CURRENT" ]; then
|
|
309
|
-
cksum < "$ROUND_CURRENT" > "$
|
|
312
|
+
cksum < "$ROUND_CURRENT" > "$TURN_MARKER" 2>/dev/null || true
|
|
310
313
|
else
|
|
311
|
-
echo "MISSING" > "$
|
|
314
|
+
echo "MISSING" > "$TURN_MARKER" 2>/dev/null || true
|
|
312
315
|
fi
|
|
313
316
|
|
|
314
317
|
SPAN_WILL_PASS_THROUGH=0
|
|
@@ -337,6 +340,14 @@ if [ "$SPAN_WILL_PASS_THROUGH" -eq 0 ] && [ -z "$FOLD_ROUND" ] && [ -f "$CHECKPO
|
|
|
337
340
|
esac
|
|
338
341
|
fi
|
|
339
342
|
|
|
343
|
+
CLAUDE_SEGMENT="$DEVLOG_DIR/.segment-state"
|
|
344
|
+
if [ "$SEGMENT_FILE" != "$CLAUDE_SEGMENT" ] && [ ! -f "$SEGMENT_FILE" ] && [ -f "$CLAUDE_SEGMENT" ]; then
|
|
345
|
+
SEED_MAX="$(json_int_get "$CLAUDE_SEGMENT" max_silent_seconds)"
|
|
346
|
+
case "$SEED_MAX" in ''|*[!0-9]*) SEED_MAX=600 ;; esac
|
|
347
|
+
printf '{"last_change_epoch": 0, "last_seen_cksum": "", "max_silent_seconds": %s, "session_id": ""}\n' \
|
|
348
|
+
"$SEED_MAX" > "$SEGMENT_FILE" 2>/dev/null || true
|
|
349
|
+
fi
|
|
350
|
+
|
|
340
351
|
if [ -f "$SEGMENT_FILE" ]; then
|
|
341
352
|
SEG_EPOCH="$(json_int_get "$SEGMENT_FILE" last_change_epoch)"
|
|
342
353
|
SEG_MAX="$(json_int_get "$SEGMENT_FILE" max_silent_seconds)"
|
|
@@ -24,21 +24,25 @@ case "$SECONDS_ARG" in
|
|
|
24
24
|
;;
|
|
25
25
|
esac
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
APPLIED_ANY=0
|
|
28
|
+
for SEG in "$DEVLOG_DIR"/.segment-state "$DEVLOG_DIR"/.segment-state@*; do
|
|
29
|
+
[ -f "$SEG" ] || continue
|
|
30
|
+
APPLIED_ANY=1
|
|
31
|
+
json_int_set "$SEG" max_silent_seconds "$SECONDS_ARG"
|
|
32
|
+
APPLIED="$(json_int_get "$SEG" max_silent_seconds)"
|
|
30
33
|
if [ "$APPLIED" != "$SECONDS_ARG" ]; then
|
|
31
34
|
# Existing file didn't have a matching max_silent_seconds key to patch
|
|
32
35
|
# (malformed / hand-edited) -- rebuild it fresh rather than leave the
|
|
33
36
|
# requested value silently unapplied.
|
|
34
|
-
LAST_EPOCH="$(json_int_get "$
|
|
35
|
-
LAST_SUM="$(json_str_get "$
|
|
36
|
-
SESSION="$(json_str_get "$
|
|
37
|
+
LAST_EPOCH="$(json_int_get "$SEG" last_change_epoch)"
|
|
38
|
+
LAST_SUM="$(json_str_get "$SEG" last_seen_cksum)"
|
|
39
|
+
SESSION="$(json_str_get "$SEG" session_id)"
|
|
37
40
|
case "$LAST_EPOCH" in ''|*[!0-9]*) LAST_EPOCH=0 ;; esac
|
|
38
41
|
printf '%s\n' "{\"last_change_epoch\": ${LAST_EPOCH}, \"last_seen_cksum\": \"${LAST_SUM}\", \"max_silent_seconds\": ${SECONDS_ARG}, \"session_id\": \"${SESSION}\"}" \
|
|
39
|
-
> "$
|
|
42
|
+
> "$SEG" || exit 1
|
|
40
43
|
fi
|
|
41
|
-
|
|
44
|
+
done
|
|
45
|
+
if [ "$APPLIED_ANY" -eq 0 ]; then
|
|
42
46
|
printf '%s\n' "{\"last_change_epoch\": 0, \"last_seen_cksum\": \"\", \"max_silent_seconds\": ${SECONDS_ARG}, \"session_id\": \"\"}" \
|
|
43
47
|
> "$DEVLOG_DIR/.segment-state" || exit 1
|
|
44
48
|
fi
|
|
@@ -16,8 +16,6 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
16
16
|
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
17
17
|
[ -f "$PROJECT_DIR/.devlog/.enabled" ] || exit 0
|
|
18
18
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
19
|
-
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
20
|
-
SEGMENT_FILE="$DEVLOG_DIR/.segment-state"
|
|
21
19
|
|
|
22
20
|
INPUT="$(cat 2>/dev/null || true)"
|
|
23
21
|
[ -n "$INPUT" ] || exit 0
|
|
@@ -66,7 +64,7 @@ is_devlog_tool_allowed() {
|
|
|
66
64
|
case "$1" in
|
|
67
65
|
Write|Edit|StrReplace|Read|Grep)
|
|
68
66
|
case "$2" in
|
|
69
|
-
.devlog/.
|
|
67
|
+
".devlog/${ROUND_CURRENT##*/}"|*"/.devlog/${ROUND_CURRENT##*/}") return 0 ;;
|
|
70
68
|
esac
|
|
71
69
|
;;
|
|
72
70
|
Bash)
|
|
@@ -82,7 +80,6 @@ if [ -n "$AGENT_ID" ]; then
|
|
|
82
80
|
exit 0
|
|
83
81
|
fi
|
|
84
82
|
|
|
85
|
-
MISMATCH_FILE="$DEVLOG_DIR/.workspace-mismatch"
|
|
86
83
|
if [ -f "$MISMATCH_FILE" ]; then
|
|
87
84
|
LIVE_MARK="$(cat "$MISMATCH_FILE" 2>/dev/null || true)"
|
|
88
85
|
if [ -z "$LIVE_MARK" ]; then
|
|
@@ -103,7 +100,7 @@ if [ -f "$MISMATCH_FILE" ]; then
|
|
|
103
100
|
*"$mark_flat"*) rm -f "$MISMATCH_FILE" 2>/dev/null || true ;;
|
|
104
101
|
*)
|
|
105
102
|
if ! is_devlog_tool_allowed "$TOOL_NAME" "$FILE_PATH" "$COMMAND"; then
|
|
106
|
-
echo "上一輪 Handoff 的工作區(\`<workspace>\`;舊格式是 \`#### 工作區\`)跟目前 git 不符。請先 Read .devlog
|
|
103
|
+
echo "上一輪 Handoff 的工作區(\`<workspace>\`;舊格式是 \`#### 工作區\`)跟目前 git 不符。請先 Read .devlog/${ROUND_CURRENT##*/},再用 Edit/StrReplace 在這一輪追加 ### 段落,把下面「實際」逐字貼進段落(宣稱 vs 實際)。寫完再呼叫其他工具。不要照上一輪 Handoff「現況/下一步」的字面行動。(唯讀的 git status/diff/log/show/rev-parse 仍可執行,方便自行核對。)" >&2
|
|
107
104
|
echo "" >&2
|
|
108
105
|
echo "實際:" >&2
|
|
109
106
|
printf '%s\n' "$LIVE_MARK" >&2
|
|
@@ -190,7 +187,7 @@ fi
|
|
|
190
187
|
|
|
191
188
|
ELAPSED=$((NOW - SEG_EPOCH))
|
|
192
189
|
if [ "$ELAPSED" -ge "$SEG_MAX" ]; then
|
|
193
|
-
echo "這一輪已經 ${ELAPSED} 秒沒有更新 .devlog
|
|
190
|
+
echo "這一輪已經 ${ELAPSED} 秒沒有更新 .devlog/${ROUND_CURRENT##*/}(門檻 ${SEG_MAX} 秒)。請先 Read .devlog/${ROUND_CURRENT##*/},再用 Edit 或 StrReplace **追加**一段「### 段落」(一行也可以);禁止用 Write 覆寫整份檔。寫完再繼續呼叫其他工具。(唯讀的 git status/diff/log/show/rev-parse 仍可執行。)" >&2
|
|
194
191
|
exit 2
|
|
195
192
|
fi
|
|
196
193
|
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
# 非空就整份印出來、清楚標示「尚未收尾」,跟上面的歷史摘要分開。startup /
|
|
22
22
|
# resume / fork 這幾個 source 因為上面已經先 heal 過,這裡通常是空的、印出
|
|
23
23
|
# 來是 no-op,不特別排除,寫法比較單純。讀不到就跳過,fail-open。
|
|
24
|
+
#
|
|
25
|
+
# 其他平台的 Session Handoff:只注入本平台的 handoff 檔;其他平台非空的
|
|
26
|
+
# handoff 只印一行路徑提醒,不注入內容。
|
|
24
27
|
|
|
25
28
|
set -uo pipefail
|
|
26
29
|
|
|
@@ -35,15 +38,13 @@ HOOKS_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
35
38
|
|
|
36
39
|
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
37
40
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
38
|
-
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
39
|
-
SPAN_FILE="$DEVLOG_DIR/.span-open"
|
|
40
41
|
|
|
41
42
|
INPUT="$(cat 2>/dev/null || true)"
|
|
42
43
|
SOURCE="$(json_str_field "$INPUT" source)"
|
|
43
44
|
|
|
44
45
|
case "$SOURCE" in
|
|
45
46
|
startup|resume|clear|fork)
|
|
46
|
-
if [ -f "$
|
|
47
|
+
if [ -f "$ROUND_OPEN" ]; then
|
|
47
48
|
DANGLING_DETAIL=""
|
|
48
49
|
if [ -n "$(detect_pending_question "$(json_str_field "$INPUT" transcript_path)")" ]; then
|
|
49
50
|
DANGLING_DETAIL="awaiting_question"
|
|
@@ -64,7 +65,7 @@ if [ -f "$SPAN_FILE" ]; then
|
|
|
64
65
|
if [ -n "$SPAN_ROUND" ] && [ -n "$SPAN_OPENED_AT" ]; then
|
|
65
66
|
echo "⚠️ 有一個開啟中的 span:Round ${SPAN_ROUND},從 ${SPAN_OPENED_AT} 開始,"
|
|
66
67
|
echo "還沒有正式結束。請先確認要繼續這個自動化任務,還是要明確關閉它"
|
|
67
|
-
echo "(刪除 .devlog
|
|
68
|
+
echo "(刪除 .devlog/${SPAN_FILE##*/} 並補寫收尾的 Round)。"
|
|
68
69
|
echo ""
|
|
69
70
|
fi
|
|
70
71
|
fi
|
|
@@ -76,6 +77,11 @@ if [ -s "${HANDOFF_FILE:-}" ]; then
|
|
|
76
77
|
echo ""
|
|
77
78
|
fi
|
|
78
79
|
|
|
80
|
+
devlog_other_handoffs | while IFS="$(printf '\t')" read -r OTHER_PLATFORM OTHER_FILE; do
|
|
81
|
+
echo "另一個平台(${OTHER_PLATFORM})有未完成的交接:.devlog/${OTHER_FILE##*/}"
|
|
82
|
+
echo ""
|
|
83
|
+
done
|
|
84
|
+
|
|
79
85
|
if [ -f "$DEVLOG_FILE" ]; then
|
|
80
86
|
echo "以下是本專案 .devlog/${DEVLOG_FILE##*/} 的接手摘要(不是全文;完整紀錄請自行讀取原檔):"
|
|
81
87
|
echo ""
|
|
@@ -155,7 +161,7 @@ fi
|
|
|
155
161
|
# heal) with its real content sitting only in .round-current.md. Surface it
|
|
156
162
|
# separately from the devlog.md excerpt above so it isn't silently dropped.
|
|
157
163
|
if [ -s "$ROUND_CURRENT" ]; then
|
|
158
|
-
echo "以下是目前還沒收尾、仍在 .devlog
|
|
164
|
+
echo "以下是目前還沒收尾、仍在 .devlog/${ROUND_CURRENT##*/} 裡的這一輪內容(跟上面的歷史摘要分開,尚未併入 devlog.md):"
|
|
159
165
|
echo ""
|
|
160
166
|
cat "$ROUND_CURRENT" 2>/dev/null || true
|
|
161
167
|
echo ""
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -uo pipefail
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
_src="${BASH_SOURCE[0]}"
|
|
4
|
+
SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
5
|
+
# shellcheck source=devlog-path.sh
|
|
6
|
+
. "$SCRIPT_DIR/devlog-path.sh"
|
|
7
|
+
devlog_resolve_paths "${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
8
|
+
if [ ! -e "$SPAN_FILE" ]; then
|
|
5
9
|
echo "NOT_OPEN"
|
|
6
10
|
exit 0
|
|
7
11
|
fi
|
|
8
|
-
rm -f "$
|
|
12
|
+
rm -f "$SPAN_FILE" || exit 1
|
|
9
13
|
echo "CLOSED"
|
|
@@ -8,14 +8,14 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
8
8
|
|
|
9
9
|
devlog_resolve_paths "${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
10
10
|
[ -f "$DEVLOG_DIR/.enabled" ] || { echo "NOT_ENABLED" >&2; exit 1; }
|
|
11
|
-
[ ! -e "$
|
|
11
|
+
[ ! -e "$SPAN_FILE" ] || { echo "ALREADY_OPEN" >&2; exit 1; }
|
|
12
12
|
ROUND=""
|
|
13
|
-
[ ! -f "$
|
|
13
|
+
[ ! -f "$ROUND_OPEN" ] || ROUND="$(json_int_get "$ROUND_OPEN" round)"
|
|
14
14
|
if [ -z "$ROUND" ] && [ -f "$DEVLOG_FILE" ]; then
|
|
15
|
-
ROUND="$(
|
|
15
|
+
ROUND="$(devlog_list_round_starts_of "$DEVLOG_FILE" "$DEVLOG_PLATFORM" | awk 'END { print $2 }')"
|
|
16
16
|
fi
|
|
17
17
|
[ -n "$ROUND" ] || { echo "NO_ROUND" >&2; exit 1; }
|
|
18
18
|
OPENED_AT="$(date -Iseconds 2>/dev/null || date '+%Y-%m-%dT%H:%M:%S%z')"
|
|
19
19
|
printf '{"round": %s, "opened_at": "%s", "ticks_since_checkin": 0, "max_silent_ticks": 5}\n' \
|
|
20
|
-
"$ROUND" "$OPENED_AT" > "$
|
|
20
|
+
"$ROUND" "$OPENED_AT" > "$SPAN_FILE" || exit 1
|
|
21
21
|
printf 'OPENED=%s\n' "$ROUND"
|
|
@@ -16,11 +16,11 @@ if [ -f "$DEVLOG_DIR/.lessons-advisory-state" ]; then
|
|
|
16
16
|
advisory_max="$(json_int_get "$DEVLOG_DIR/.lessons-advisory-state" threshold)"
|
|
17
17
|
printf 'LESSONS_ADVISORY=%s/%s\n' "${advisory_count:-0}" "${advisory_max:-3}"
|
|
18
18
|
fi
|
|
19
|
-
if [ -f "$
|
|
20
|
-
r="$(json_int_get "$
|
|
21
|
-
opened="$(json_str_get "$
|
|
22
|
-
ticks="$(json_int_get "$
|
|
23
|
-
max="$(json_int_get "$
|
|
19
|
+
if [ -f "$SPAN_FILE" ]; then
|
|
20
|
+
r="$(json_int_get "$SPAN_FILE" round)"
|
|
21
|
+
opened="$(json_str_get "$SPAN_FILE" opened_at)"
|
|
22
|
+
ticks="$(json_int_get "$SPAN_FILE" ticks_since_checkin)"
|
|
23
|
+
max="$(json_int_get "$SPAN_FILE" max_silent_ticks)"
|
|
24
24
|
printf 'SPAN=%s,%s,%s,%s\n' "${r:-?}" "${opened:-?}" "${ticks:-?}" "${max:-5}"
|
|
25
25
|
else
|
|
26
26
|
echo "SPAN=closed"
|
|
@@ -28,7 +28,7 @@ fi
|
|
|
28
28
|
rounds="$(json_int_get "$DEVLOG_DIR/.checkpoint-state" rounds_since_checkpoint)"
|
|
29
29
|
checkpoint_max="$(json_int_get "$DEVLOG_DIR/.checkpoint-state" max_silent_rounds)"
|
|
30
30
|
printf 'CHECKPOINT=%s/%s\n' "${rounds:-0}" "${checkpoint_max:-20}"
|
|
31
|
-
seconds="$(json_int_get "$
|
|
31
|
+
seconds="$(json_int_get "$SEGMENT_FILE" max_silent_seconds)"
|
|
32
32
|
printf 'SEGMENT=max_silent_seconds=%s\n' "${seconds:-600}"
|
|
33
33
|
|
|
34
34
|
status="none"
|
|
@@ -103,6 +103,15 @@ grep -q 'sentinel' "$DEVLOG_DIR/.awaiting-reply" \
|
|
|
103
103
|
&& echo "PASS: existing marker left untouched" || { echo "FAIL: existing marker was overwritten"; FAIL=1; }
|
|
104
104
|
rm -f "$DEVLOG_DIR/.awaiting-reply" "$DEVLOG_DIR/devlog.md"
|
|
105
105
|
|
|
106
|
+
# --- platform: codex falls back to its own last round ----------------------
|
|
107
|
+
rm -f "$DEVLOG_DIR/.round-open" "$DEVLOG_DIR/.awaiting-reply" "$DEVLOG_DIR/.awaiting-reply@codex"
|
|
108
|
+
printf '## Round 7 — t · codex\n\n### Status\nDONE\n\n## Round 8 — t\n\n### Status\nDONE\n' > "$DEVLOG_DIR/devlog.md"
|
|
109
|
+
touch "$DEVLOG_DIR/.platform-claimed"
|
|
110
|
+
OUT="$(DEVLOG_PLATFORM=codex bash "$SCRIPT_DIR/await-open.sh")"
|
|
111
|
+
assert_contains "codex await-open uses its own last round" "OPENED=7" "$OUT"
|
|
112
|
+
[ -f "$DEVLOG_DIR/.awaiting-reply@codex" ] && [ ! -f "$DEVLOG_DIR/.awaiting-reply" ] \
|
|
113
|
+
&& echo "PASS: codex awaiting file" || { echo "FAIL: codex awaiting file"; FAIL=1; }
|
|
114
|
+
|
|
106
115
|
if [ "$FAIL" -eq 0 ]; then
|
|
107
116
|
echo "All checks passed."
|
|
108
117
|
exit 0
|
|
@@ -475,6 +475,36 @@ NONGIT_DIR="$TMP_ROOT/nongit"
|
|
|
475
475
|
mkdir -p "$NONGIT_DIR"
|
|
476
476
|
assert_eq "unclosed fence still yields a claim (MISMATCH, not NO_CLAIM)" "MISMATCH" "$(workspace_claim_state "$NONGIT_DIR" "$TMP_ROOT/unclosed-fence.md")"
|
|
477
477
|
|
|
478
|
+
WS_FILE="$TMP_ROOT/per-platform-claim.md"
|
|
479
|
+
cat > "$WS_FILE" <<'EOF'
|
|
480
|
+
## Round 1 — 2026-09-27T10:00:00+0800
|
|
481
|
+
|
|
482
|
+
### Handoff
|
|
483
|
+
<handoff>
|
|
484
|
+
<workspace>
|
|
485
|
+
非 git 工作區
|
|
486
|
+
</workspace>
|
|
487
|
+
</handoff>
|
|
488
|
+
|
|
489
|
+
### Status
|
|
490
|
+
IN_PROGRESS
|
|
491
|
+
|
|
492
|
+
## Round 2 — 2026-09-27T10:05:00+0800 · codex
|
|
493
|
+
|
|
494
|
+
### Handoff
|
|
495
|
+
<handoff>
|
|
496
|
+
<workspace>
|
|
497
|
+
bogus
|
|
498
|
+
</workspace>
|
|
499
|
+
</handoff>
|
|
500
|
+
|
|
501
|
+
### Status
|
|
502
|
+
IN_PROGRESS
|
|
503
|
+
EOF
|
|
504
|
+
assert_eq "claude claim ignores codex's later round" "MATCH" "$(workspace_claim_state "$NONGIT_DIR" "$WS_FILE" claude)"
|
|
505
|
+
assert_eq "claude is the default platform" "MATCH" "$(workspace_claim_state "$NONGIT_DIR" "$WS_FILE")"
|
|
506
|
+
assert_eq "codex claim sees its own round" "MISMATCH" "$(workspace_claim_state "$NONGIT_DIR" "$WS_FILE" codex)"
|
|
507
|
+
|
|
478
508
|
cat > "$TMP_ROOT/unclosed-fence-segments.md" <<'EOF'
|
|
479
509
|
## Round 1 — 2026-09-11T00:00:00+08:00
|
|
480
510
|
|
|
@@ -515,30 +545,61 @@ devlog_merge_round_current "$MERGE_MAIN" "$MERGE_CUR"
|
|
|
515
545
|
AFTER="$(cat "$MERGE_MAIN")"
|
|
516
546
|
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
|
|
517
547
|
|
|
518
|
-
# ---
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
548
|
+
# --- per-platform round lookup --------------------------------------------
|
|
549
|
+
MP="$TMP_ROOT/mp.md"
|
|
550
|
+
cat > "$MP" <<'EOF'
|
|
551
|
+
# 專案摘要
|
|
552
|
+
|
|
553
|
+
## Round 1 — 2026-09-27T10:00:00+0800
|
|
554
|
+
|
|
555
|
+
### Status
|
|
556
|
+
DONE
|
|
522
557
|
|
|
523
|
-
|
|
558
|
+
## Round 3 — 2026-09-27T10:05:00+0800 · codex
|
|
524
559
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
assert_exit "reopen: returns 0 when a round exists" 0 "$RC"
|
|
560
|
+
### Status
|
|
561
|
+
IN_PROGRESS
|
|
528
562
|
|
|
529
|
-
|
|
530
|
-
assert_contains "reopen: round 2 moved into round-current" "## Round 2" "$REOPENED"
|
|
531
|
-
assert_not_contains "reopen: round 1 not pulled along" "## Round 1" "$REOPENED"
|
|
563
|
+
## Round 2 — 2026-09-27T10:01:00+0800
|
|
532
564
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
565
|
+
```text
|
|
566
|
+
## Round 9 — fenced · codex
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Status
|
|
570
|
+
DONE
|
|
571
|
+
|
|
572
|
+
## Round 4 — 2026-09-27T10:09:00+0800 · cursor
|
|
573
|
+
|
|
574
|
+
### Status
|
|
575
|
+
DONE
|
|
576
|
+
EOF
|
|
577
|
+
assert_eq "claude rounds" "3 1
|
|
578
|
+
13 2" "$(devlog_list_round_starts_of "$MP" claude)"
|
|
579
|
+
assert_eq "codex rounds (fenced ignored)" "8 3" "$(devlog_list_round_starts_of "$MP" codex)"
|
|
580
|
+
assert_eq "cursor rounds" "22 4" "$(devlog_list_round_starts_of "$MP" cursor)"
|
|
581
|
+
assert_eq "by number: middle round" "8" "$(devlog_round_start_by_number "$MP" 3)"
|
|
582
|
+
assert_eq "by number: missing" "" "$(devlog_round_start_by_number "$MP" 8)"
|
|
583
|
+
|
|
584
|
+
# --- devlog_reopen_round ----------------------------------------------------
|
|
585
|
+
REOPEN_MAIN="$TMP_ROOT/reopen.md"
|
|
586
|
+
REOPEN_CUR="$TMP_ROOT/reopen-cur.md"
|
|
587
|
+
cp "$MP" "$REOPEN_MAIN"
|
|
588
|
+
devlog_reopen_round "$REOPEN_MAIN" "$REOPEN_CUR" 3
|
|
589
|
+
assert_eq "reopen middle: returns 0" "0" "$?"
|
|
590
|
+
assert_contains "reopen middle: current has round 3" "## Round 3 — 2026-09-27T10:05:00+0800 · codex" "$(cat "$REOPEN_CUR")"
|
|
591
|
+
assert_not_contains "reopen middle: devlog lost round 3" "## Round 3 " "$(cat "$REOPEN_MAIN")"
|
|
592
|
+
assert_eq "reopen middle: others stay in order" "1 2 4 " "$(devlog_list_round_starts "$REOPEN_MAIN" | awk '{printf "%s ", $2}')"
|
|
593
|
+
devlog_reopen_round "$REOPEN_MAIN" "$TMP_ROOT/should-not-exist.md" 3
|
|
594
|
+
assert_eq "reopen missing: returns 1" "1" "$?"
|
|
595
|
+
assert_file_absent "reopen missing: no current file" "$TMP_ROOT/should-not-exist.md"
|
|
536
596
|
|
|
537
597
|
# reopen on a file with no round at all fails without touching either file
|
|
598
|
+
mkdir -p "$TMP_ROOT/.devlog"
|
|
538
599
|
EMPTY_MAIN="$TMP_ROOT/.devlog/empty-main.md"
|
|
539
600
|
: > "$EMPTY_MAIN"
|
|
540
601
|
rm -f "$TMP_ROOT/.devlog/should-not-exist.md"
|
|
541
|
-
|
|
602
|
+
devlog_reopen_round "$EMPTY_MAIN" "$TMP_ROOT/.devlog/should-not-exist.md" 1
|
|
542
603
|
assert_exit "reopen: returns 1 when no round exists" 1 $?
|
|
543
604
|
assert_file_absent "reopen: no round-current created on failure" "$TMP_ROOT/.devlog/should-not-exist.md"
|
|
544
605
|
|
|
@@ -288,5 +288,131 @@ devlog_resolve_paths "$DETACHEDREPO"
|
|
|
288
288
|
&& echo "PASS: detached HANDOFF_FILE uses worktree dirname" \
|
|
289
289
|
|| { echo "FAIL: detached HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
290
290
|
|
|
291
|
+
# --- platform identity ---------------------------------------------------
|
|
292
|
+
assert_path() {
|
|
293
|
+
if [ "$2" = "$3" ]; then echo "PASS: $1"; else echo "FAIL: $1 (expected [$2], got [$3])"; FAIL=1; fi
|
|
294
|
+
}
|
|
295
|
+
assert_path "unset platform is claude" claude "$(unset DEVLOG_PLATFORM; devlog_platform)"
|
|
296
|
+
assert_path "codex platform" codex "$(DEVLOG_PLATFORM=codex devlog_platform)"
|
|
297
|
+
assert_path "unknown platform falls back to claude" claude "$(DEVLOG_PLATFORM='x;rm' devlog_platform)"
|
|
298
|
+
assert_path "claude keeps plain name" .round-current.md "$(devlog_platform_file .round-current .md claude)"
|
|
299
|
+
assert_path "codex gets @ suffix" .round-current@codex.md "$(devlog_platform_file .round-current .md codex)"
|
|
300
|
+
assert_path "extensionless @ suffix" .round-open@cursor "$(devlog_platform_file .round-open '' cursor)"
|
|
301
|
+
|
|
302
|
+
# --- per-platform state paths --------------------------------------------
|
|
303
|
+
PLATREPO="$TMP/platrepo"
|
|
304
|
+
mkdir -p "$PLATREPO"
|
|
305
|
+
git_setup "$PLATREPO"
|
|
306
|
+
git -C "$PLATREPO" branch -M main
|
|
307
|
+
DEVLOG_PLATFORM=codex devlog_resolve_paths "$PLATREPO"
|
|
308
|
+
assert_path "codex ROUND_CURRENT" "$PLATREPO/.devlog/.round-current@codex.md" "$ROUND_CURRENT"
|
|
309
|
+
assert_path "codex ROUND_OPEN" "$PLATREPO/.devlog/.round-open@codex" "$ROUND_OPEN"
|
|
310
|
+
assert_path "codex TURN_MARKER" "$PLATREPO/.devlog/.turn-start@codex" "$TURN_MARKER"
|
|
311
|
+
assert_path "codex SEGMENT_FILE" "$PLATREPO/.devlog/.segment-state@codex" "$SEGMENT_FILE"
|
|
312
|
+
assert_path "codex AWAITING_FILE" "$PLATREPO/.devlog/.awaiting-reply@codex" "$AWAITING_FILE"
|
|
313
|
+
assert_path "codex INTERRUPTED_FLAG" "$PLATREPO/.devlog/.interrupted@codex" "$INTERRUPTED_FLAG"
|
|
314
|
+
assert_path "codex SPAN_FILE" "$PLATREPO/.devlog/.span-open@codex" "$SPAN_FILE"
|
|
315
|
+
assert_path "codex MISMATCH_FILE" "$PLATREPO/.devlog/.workspace-mismatch@codex" "$MISMATCH_FILE"
|
|
316
|
+
assert_path "codex main HANDOFF_FILE" "$PLATREPO/.devlog/handoff@codex.md" "$HANDOFF_FILE"
|
|
317
|
+
assert_path "codex DEVLOG_FILE shared" "$PLATREPO/.devlog/devlog.md" "$DEVLOG_FILE"
|
|
318
|
+
unset DEVLOG_PLATFORM
|
|
319
|
+
devlog_resolve_paths "$PLATREPO"
|
|
320
|
+
assert_path "claude ROUND_CURRENT unchanged" "$PLATREPO/.devlog/.round-current.md" "$ROUND_CURRENT"
|
|
321
|
+
assert_path "claude ROUND_OPEN unchanged" "$PLATREPO/.devlog/.round-open" "$ROUND_OPEN"
|
|
322
|
+
assert_path "claude HANDOFF_FILE unchanged" "$PLATREPO/.devlog/handoff.md" "$HANDOFF_FILE"
|
|
323
|
+
git -C "$PLATREPO" checkout -q -b codex
|
|
324
|
+
DEVLOG_PLATFORM=cursor devlog_resolve_paths "$PLATREPO"
|
|
325
|
+
assert_path "branch named codex + cursor platform" "$PLATREPO/.devlog/handoff.codex@cursor.md" "$HANDOFF_FILE"
|
|
326
|
+
assert_path "HANDOFF_STEM" "$PLATREPO/.devlog/handoff.codex" "$HANDOFF_STEM"
|
|
327
|
+
unset DEVLOG_PLATFORM
|
|
328
|
+
|
|
329
|
+
# --- legacy claim ----------------------------------------------------------
|
|
330
|
+
CLAIMREPO="$TMP/claimrepo"
|
|
331
|
+
mkdir -p "$CLAIMREPO/.devlog"
|
|
332
|
+
git_setup "$CLAIMREPO"
|
|
333
|
+
git -C "$CLAIMREPO" branch -M main
|
|
334
|
+
touch "$CLAIMREPO/.devlog/.enabled"
|
|
335
|
+
echo "open round" > "$CLAIMREPO/.devlog/.round-current.md"
|
|
336
|
+
printf '{"round": 3, "opened_at": "t", "file": "devlog.md"}\n' > "$CLAIMREPO/.devlog/.round-open"
|
|
337
|
+
echo "legacy handoff" > "$CLAIMREPO/.devlog/handoff.md"
|
|
338
|
+
echo "segment cfg" > "$CLAIMREPO/.devlog/.segment-state"
|
|
339
|
+
DEVLOG_PLATFORM=codex devlog_resolve_paths "$CLAIMREPO"
|
|
340
|
+
if [ -f "$CLAIMREPO/.devlog/.round-current@codex.md" ] && [ ! -f "$CLAIMREPO/.devlog/.round-current.md" ] \
|
|
341
|
+
&& [ -f "$CLAIMREPO/.devlog/.round-open@codex" ] \
|
|
342
|
+
&& grep -q "legacy handoff" "$CLAIMREPO/.devlog/handoff@codex.md" \
|
|
343
|
+
&& [ -f "$CLAIMREPO/.devlog/.segment-state" ] \
|
|
344
|
+
&& [ -f "$CLAIMREPO/.devlog/.platform-claimed" ]; then
|
|
345
|
+
echo "PASS: first non-claude platform claims legacy state (segment config stays)"
|
|
346
|
+
else
|
|
347
|
+
echo "FAIL: legacy claim"; ls -a "$CLAIMREPO/.devlog"; FAIL=1
|
|
348
|
+
fi
|
|
349
|
+
echo "later claude round" > "$CLAIMREPO/.devlog/.round-current.md"
|
|
350
|
+
DEVLOG_PLATFORM=cursor devlog_resolve_paths "$CLAIMREPO"
|
|
351
|
+
if [ -f "$CLAIMREPO/.devlog/.round-current.md" ] && [ ! -f "$CLAIMREPO/.devlog/.round-current@cursor.md" ]; then
|
|
352
|
+
echo "PASS: marker stops later claims"
|
|
353
|
+
else
|
|
354
|
+
echo "FAIL: second claim happened"; FAIL=1
|
|
355
|
+
fi
|
|
356
|
+
unset DEVLOG_PLATFORM
|
|
357
|
+
|
|
358
|
+
CLAUDEFIRST="$TMP/claudefirst"
|
|
359
|
+
mkdir -p "$CLAUDEFIRST/.devlog"
|
|
360
|
+
git_setup "$CLAUDEFIRST"
|
|
361
|
+
git -C "$CLAUDEFIRST" branch -M main
|
|
362
|
+
touch "$CLAUDEFIRST/.devlog/.enabled"
|
|
363
|
+
echo "open round" > "$CLAUDEFIRST/.devlog/.round-current.md"
|
|
364
|
+
devlog_resolve_paths "$CLAUDEFIRST"
|
|
365
|
+
if [ -f "$CLAUDEFIRST/.devlog/.round-current.md" ] && [ -f "$CLAUDEFIRST/.devlog/.platform-claimed" ]; then
|
|
366
|
+
echo "PASS: claude-first claims nothing, writes marker"
|
|
367
|
+
else
|
|
368
|
+
echo "FAIL: claude-first claim"; FAIL=1
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
NOTENABLED="$TMP/notenabled"
|
|
372
|
+
mkdir -p "$NOTENABLED/.devlog"
|
|
373
|
+
echo "x" > "$NOTENABLED/.devlog/.round-current.md"
|
|
374
|
+
DEVLOG_PLATFORM=codex devlog_resolve_paths "$NOTENABLED"
|
|
375
|
+
if [ -f "$NOTENABLED/.devlog/.round-current.md" ] && [ ! -e "$NOTENABLED/.devlog/.platform-claimed" ]; then
|
|
376
|
+
echo "PASS: no claim without .enabled"
|
|
377
|
+
else
|
|
378
|
+
echo "FAIL: claimed while disabled"; FAIL=1
|
|
379
|
+
fi
|
|
380
|
+
unset DEVLOG_PLATFORM
|
|
381
|
+
|
|
382
|
+
# --- open rounds / other handoffs -----------------------------------------
|
|
383
|
+
LISTREPO="$TMP/listrepo"
|
|
384
|
+
mkdir -p "$LISTREPO/.devlog"
|
|
385
|
+
git_setup "$LISTREPO"
|
|
386
|
+
git -C "$LISTREPO" branch -M main
|
|
387
|
+
touch "$LISTREPO/.devlog/.enabled" "$LISTREPO/.devlog/.platform-claimed"
|
|
388
|
+
printf '{"round": 5, "opened_at": "t", "file": "devlog.md"}\n' > "$LISTREPO/.devlog/.round-open"
|
|
389
|
+
printf '{"round": 6, "opened_at": "t", "file": "devlog.md"}\n' > "$LISTREPO/.devlog/.round-open@codex"
|
|
390
|
+
echo "claude h" > "$LISTREPO/.devlog/handoff.md"
|
|
391
|
+
echo "cursor h" > "$LISTREPO/.devlog/handoff@cursor.md"
|
|
392
|
+
: > "$LISTREPO/.devlog/handoff@codex.md"
|
|
393
|
+
DEVLOG_PLATFORM=codex devlog_resolve_paths "$LISTREPO"
|
|
394
|
+
assert_path "open rounds listing" "$(printf 'claude\t5\tdevlog.md\ncodex\t6\tdevlog.md')" "$(devlog_open_rounds)"
|
|
395
|
+
assert_path "other handoffs (non-empty, not own)" \
|
|
396
|
+
"$(printf 'claude\t%s\ncursor\t%s' "$LISTREPO/.devlog/handoff.md" "$LISTREPO/.devlog/handoff@cursor.md")" \
|
|
397
|
+
"$(DEVLOG_PLATFORM=codex devlog_other_handoffs)"
|
|
398
|
+
unset DEVLOG_PLATFORM
|
|
399
|
+
|
|
400
|
+
# --- branch tail migration moves every platform's handoff -----------------
|
|
401
|
+
PTAIL="$TMP/ptail"
|
|
402
|
+
mig_repo "$PTAIL"
|
|
403
|
+
write_main_devlog "$PTAIL/.devlog/devlog.md" 1:DONE 2:IN_PROGRESS
|
|
404
|
+
touch "$PTAIL/.devlog/.platform-claimed"
|
|
405
|
+
echo "claude handoff" > "$PTAIL/.devlog/handoff.md"
|
|
406
|
+
echo "codex handoff" > "$PTAIL/.devlog/handoff@codex.md"
|
|
407
|
+
git -C "$PTAIL" checkout -q -b feature-p
|
|
408
|
+
devlog_resolve_paths "$PTAIL"
|
|
409
|
+
if grep -q "claude handoff" "$PTAIL/.devlog/handoff.feature-p.md" \
|
|
410
|
+
&& grep -q "codex handoff" "$PTAIL/.devlog/handoff.feature-p@codex.md" \
|
|
411
|
+
&& [ ! -f "$PTAIL/.devlog/handoff@codex.md" ]; then
|
|
412
|
+
echo "PASS: tail migration moves every platform's handoff"
|
|
413
|
+
else
|
|
414
|
+
echo "FAIL: platform handoff tail migration"; ls -a "$PTAIL/.devlog"; FAIL=1
|
|
415
|
+
fi
|
|
416
|
+
|
|
291
417
|
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
292
418
|
else echo "Some checks FAILED."; exit 1; fi
|