devlog-tracker 0.33.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -10
- package/README.zh-TW.md +41 -10
- package/cli/agents-md.js +1 -0
- package/cli/agents-md.test.js +10 -0
- package/cli/platforms/claude.js +1 -0
- package/codex/hooks/on-interrupt.sh +13 -0
- package/codex/hooks/on-pre-tool.sh +69 -1
- package/codex/hooks/on-session-end.sh +5 -5
- package/codex/hooks/on-session-start.sh +5 -6
- package/codex/hooks/on-stop.sh +3 -2
- package/codex/hooks/on-subagent-start.sh +19 -0
- package/codex/hooks/project-dir.sh +16 -1
- package/codex/hooks/test-adapters.sh +127 -9
- package/codex/hooks.json +21 -0
- package/commands/continue.md +3 -3
- package/commands/keep-all.md +1 -1
- package/commands/keep.md +2 -2
- package/commands/lessons-on.md +1 -1
- package/commands/migrate.md +18 -0
- package/commands/pr.md +3 -3
- package/commands/resume.md +1 -1
- package/core/scripts/close-open-round.sh +8 -2
- package/core/scripts/devlog-md.sh +6 -11
- package/core/scripts/enforce-devlog.sh +79 -85
- package/core/scripts/handoff-convert.sh +187 -0
- package/core/scripts/handoff-fields.sh +219 -0
- package/core/scripts/handoff-file.sh +19 -88
- package/core/scripts/lessons-subagent-start.sh +1 -1
- package/core/scripts/migrate-handoff.sh +91 -0
- package/core/scripts/round-start.sh +13 -2
- package/core/scripts/segment-watch.sh +1 -1
- package/core/scripts/tests/lib/xml-fixture.sh +10 -0
- package/core/scripts/tests/test-close-open-round.sh +2 -1
- package/core/scripts/tests/test-devlog-md.sh +21 -0
- package/core/scripts/tests/test-enforce-devlog-files.sh +4 -1
- package/core/scripts/tests/test-enforce-devlog-handoff-order.sh +149 -81
- package/core/scripts/tests/test-enforce-devlog-session-handoff.sh +92 -11
- package/core/scripts/tests/test-enforce-devlog-workspace.sh +42 -26
- package/core/scripts/tests/test-enforce-devlog.sh +70 -0
- package/core/scripts/tests/test-handoff-fields.sh +171 -0
- package/core/scripts/tests/test-handoff-file.sh +67 -45
- package/core/scripts/tests/test-migrate-handoff.sh +278 -0
- package/core/scripts/tests/test-round-start.sh +54 -1
- package/core/scripts/tests/test-session-start-devlog.sh +45 -0
- package/core/scripts/timeline-render.js +26 -2
- package/core/scripts/timeline-render.test.js +23 -1
- package/package.json +3 -3
- package/skills/devlog-tracker/SKILL.md +80 -55
- package/skills/devlog-tracker/references/contract.md +4 -3
- package/skills/devlog-tracker/references/lessons-mode.md +7 -7
- package/skills/devlog-tracker/references/round-segments.md +11 -5
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
set -uo pipefail
|
|
7
7
|
|
|
8
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
9
|
+
# shellcheck source=lib/xml-fixture.sh
|
|
10
|
+
. "$SCRIPT_DIR/tests/lib/xml-fixture.sh"
|
|
9
11
|
TMP_ROOT="$(mktemp -d)"
|
|
10
12
|
trap 'rm -rf "$TMP_ROOT"' EXIT
|
|
11
13
|
|
|
@@ -82,6 +84,7 @@ fixture
|
|
|
82
84
|
### Status
|
|
83
85
|
DONE
|
|
84
86
|
EOF
|
|
87
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
# Seeds *prior, already-closed* history directly into devlog.md — content
|
|
@@ -361,6 +364,7 @@ DONE
|
|
|
361
364
|
|
|
362
365
|
## Checkpoint(Round 2 摘要)
|
|
363
366
|
EOF
|
|
367
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
364
368
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
365
369
|
assert_exit "genuine checkpoint write above the corrected stored value -> allowed" 0 $?
|
|
366
370
|
assert_round_merged "genuine checkpoint write above the corrected stored value" "## Checkpoint(Round 2 摘要)"
|
|
@@ -429,6 +433,7 @@ missing summary
|
|
|
429
433
|
### Status
|
|
430
434
|
DONE
|
|
431
435
|
DEVEOF
|
|
436
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
432
437
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
433
438
|
assert_exit "last Round missing ### Summary -> blocked" 2 $?
|
|
434
439
|
|
|
@@ -446,6 +451,7 @@ has reply
|
|
|
446
451
|
### Status
|
|
447
452
|
DONE
|
|
448
453
|
DEVEOF
|
|
454
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
449
455
|
HEADING_MISS_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1)"
|
|
450
456
|
assert_exit "last Round missing ### Handoff -> blocked" 2 $?
|
|
451
457
|
case "$HEADING_MISS_MSG" in
|
|
@@ -468,6 +474,7 @@ cat > "$DEVLOG_DIR/.round-current.md" <<'DEVEOF'
|
|
|
468
474
|
### Status
|
|
469
475
|
DONE
|
|
470
476
|
DEVEOF
|
|
477
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
471
478
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
472
479
|
assert_exit "both headings present with empty bodies -> blocked" 2 $?
|
|
473
480
|
EMPTY_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1 >/dev/null)"
|
|
@@ -571,6 +578,7 @@ complete
|
|
|
571
578
|
### Status
|
|
572
579
|
DONE
|
|
573
580
|
DEVEOF
|
|
581
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
574
582
|
echo "## Checkpoint(Round 6 摘要)" >> "$DEVLOG_DIR/devlog.md"
|
|
575
583
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
576
584
|
assert_exit "Checkpoint-only append after a complete last Round -> allowed" 0 $?
|
|
@@ -600,6 +608,7 @@ complete
|
|
|
600
608
|
### Status
|
|
601
609
|
DONE
|
|
602
610
|
DEVEOF
|
|
611
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
603
612
|
cat > "$DEVLOG_DIR/.span-open" <<'SPANEOF'
|
|
604
613
|
{
|
|
605
614
|
"round": 6,
|
|
@@ -635,6 +644,7 @@ cat > "$DEVLOG_DIR/.round-current.md" <<'DEVEOF'
|
|
|
635
644
|
## Round 7 — 2026-09-09T10:30:00+08:00
|
|
636
645
|
incomplete new round
|
|
637
646
|
DEVEOF
|
|
647
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
638
648
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
639
649
|
assert_exit "span at max, new Round missing headings -> blocked" 2 $?
|
|
640
650
|
SPAN_TICKS_HELD="$(grep -o '"ticks_since_checkin"[[:space:]]*:[[:space:]]*[0-9]\+' "$DEVLOG_DIR/.span-open" | grep -o '[0-9]\+$')"
|
|
@@ -675,6 +685,7 @@ fixture reply.
|
|
|
675
685
|
### Status
|
|
676
686
|
DONE
|
|
677
687
|
DEVEOF
|
|
688
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
678
689
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
679
690
|
assert_exit "last Round complete, fenced ## Round 15 and ## 安裝 in body -> allowed" 0 $?
|
|
680
691
|
assert_round_merged "last Round complete, fenced ## Round 15 and ## 安裝 in body" "已回覆格式問題"
|
|
@@ -713,6 +724,7 @@ complete last Round
|
|
|
713
724
|
### Status
|
|
714
725
|
DONE
|
|
715
726
|
DEVEOF
|
|
727
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
716
728
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
717
729
|
assert_exit "older Round still uses ### Response, complete last Round -> allowed" 0 $?
|
|
718
730
|
assert_round_merged "older Round still uses ### Response, complete last Round" "complete last Round"
|
|
@@ -759,6 +771,7 @@ done
|
|
|
759
771
|
### Status
|
|
760
772
|
DONE
|
|
761
773
|
EOF
|
|
774
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
762
775
|
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
763
776
|
printf '%s\n' 'main @ deadbeef,工作樹乾淨' > "$DEVLOG_DIR/.workspace-mismatch"
|
|
764
777
|
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
@@ -808,6 +821,7 @@ esc
|
|
|
808
821
|
### Status
|
|
809
822
|
IN_PROGRESS
|
|
810
823
|
EOF
|
|
824
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
811
825
|
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
812
826
|
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
813
827
|
touch "$DEVLOG_DIR/.interrupted"
|
|
@@ -862,6 +876,7 @@ round finished normally
|
|
|
862
876
|
### Status
|
|
863
877
|
DONE
|
|
864
878
|
EOF
|
|
879
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
865
880
|
printf '%s\n' '{"round": 1, "opened_at": "2026-09-09T12:00:00+08:00"}' > "$DEVLOG_DIR/.round-open"
|
|
866
881
|
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
867
882
|
printf '\n' >> "$DEVLOG_DIR/.round-current.md"
|
|
@@ -894,6 +909,7 @@ mkdir -p "$RECOVER_DIR/.devlog"
|
|
|
894
909
|
touch "$RECOVER_DIR/.devlog/.enabled"
|
|
895
910
|
: > "$RECOVER_DIR/.devlog/devlog.md"
|
|
896
911
|
printf '## Round 1 — 2026-09-17T09:00:00+0800\n\n### User Input\n```text\nX\n```\n\n### Summary\n完成\n\n### Handoff\n#### 現況\nok\n\n### Status\nDONE\n' > "$RECOVER_DIR/.devlog/.round-current.md"
|
|
912
|
+
xml_fixture "$RECOVER_DIR/.devlog/.round-current.md"
|
|
897
913
|
printf '{"round": 1}\n' > "$RECOVER_DIR/.devlog/.round-open"
|
|
898
914
|
echo "stale" > "$RECOVER_DIR/.devlog/.turn-start"
|
|
899
915
|
touch "$RECOVER_DIR/.devlog/.interrupted"
|
|
@@ -936,6 +952,7 @@ stale interrupt
|
|
|
936
952
|
### Status
|
|
937
953
|
IN_PROGRESS
|
|
938
954
|
EOF
|
|
955
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
939
956
|
cksum < "$DEVLOG_DIR/.round-current.md" > "$DEVLOG_DIR/.turn-start"
|
|
940
957
|
rm -f "$DEVLOG_DIR/.round-open"
|
|
941
958
|
touch "$DEVLOG_DIR/.interrupted"
|
|
@@ -1016,6 +1033,7 @@ fixture reply.
|
|
|
1016
1033
|
### Status
|
|
1017
1034
|
DONE
|
|
1018
1035
|
EOF
|
|
1036
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1019
1037
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1020
1038
|
assert_exit "DONE with bodies and no 下一步 -> allowed" 0 $?
|
|
1021
1039
|
assert_round_merged "DONE with bodies and no 下一步" "做完了"
|
|
@@ -1050,9 +1068,51 @@ fixture reply.
|
|
|
1050
1068
|
### Status
|
|
1051
1069
|
IN_PROGRESS
|
|
1052
1070
|
EOF
|
|
1071
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1053
1072
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1054
1073
|
assert_exit "IN_PROGRESS without 下一步 -> blocked" 2 $?
|
|
1055
1074
|
|
|
1075
|
+
# --- Heading Scenario 13b: IN_PROGRESS with <done-when> but no <next> ------
|
|
1076
|
+
bash "$SCRIPT_DIR/round-start.sh" < /dev/null
|
|
1077
|
+
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
1078
|
+
## Round 91 — 2026-09-09T10:41:00+08:00
|
|
1079
|
+
|
|
1080
|
+
### Summary
|
|
1081
|
+
還在做。
|
|
1082
|
+
|
|
1083
|
+
### Reply
|
|
1084
|
+
fixture reply.
|
|
1085
|
+
|
|
1086
|
+
### Handoff
|
|
1087
|
+
<handoff>
|
|
1088
|
+
<state>
|
|
1089
|
+
做到一半。
|
|
1090
|
+
</state>
|
|
1091
|
+
<done-when>
|
|
1092
|
+
observable done via test.
|
|
1093
|
+
</done-when>
|
|
1094
|
+
</handoff>
|
|
1095
|
+
|
|
1096
|
+
### Session Handoff
|
|
1097
|
+
<session-handoff>
|
|
1098
|
+
<decisions>
|
|
1099
|
+
- (無)
|
|
1100
|
+
</decisions>
|
|
1101
|
+
<open-questions>
|
|
1102
|
+
- fixture open
|
|
1103
|
+
</open-questions>
|
|
1104
|
+
<failed-attempts>
|
|
1105
|
+
- (無)
|
|
1106
|
+
</failed-attempts>
|
|
1107
|
+
</session-handoff>
|
|
1108
|
+
|
|
1109
|
+
### Status
|
|
1110
|
+
IN_PROGRESS
|
|
1111
|
+
EOF
|
|
1112
|
+
MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1)"
|
|
1113
|
+
assert_exit "IN_PROGRESS with <done-when> but no <next> -> blocked" 2 $?
|
|
1114
|
+
assert_contains "missing <next> message names <next>" "<next>" "$MSG"
|
|
1115
|
+
|
|
1056
1116
|
# --- Heading Scenario 14: IN_PROGRESS with 下一步 -> allowed --------------
|
|
1057
1117
|
bash "$SCRIPT_DIR/round-start.sh" < /dev/null
|
|
1058
1118
|
cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
@@ -1089,6 +1149,7 @@ fixture reply.
|
|
|
1089
1149
|
### Status
|
|
1090
1150
|
IN_PROGRESS
|
|
1091
1151
|
EOF
|
|
1152
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1092
1153
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1093
1154
|
assert_exit "IN_PROGRESS with 下一步 -> allowed" 0 $?
|
|
1094
1155
|
assert_round_merged "IN_PROGRESS with 下一步" "打開 foo.ts 繼續"
|
|
@@ -1111,6 +1172,7 @@ y
|
|
|
1111
1172
|
### Status
|
|
1112
1173
|
WIP
|
|
1113
1174
|
EOF
|
|
1175
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1114
1176
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1115
1177
|
assert_exit "illegal Status -> blocked" 2 $?
|
|
1116
1178
|
|
|
@@ -1149,6 +1211,7 @@ fixture reply.
|
|
|
1149
1211
|
### Status
|
|
1150
1212
|
IN_PROGRESS
|
|
1151
1213
|
EOF
|
|
1214
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1152
1215
|
BLACKLIST_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1 >/dev/null)"
|
|
1153
1216
|
assert_exit "下一步 is pure filler phrase '繼續完成' -> blocked" 2 $?
|
|
1154
1217
|
case "$BLACKLIST_MSG" in
|
|
@@ -1190,6 +1253,7 @@ fixture reply.
|
|
|
1190
1253
|
### Status
|
|
1191
1254
|
BLOCKED
|
|
1192
1255
|
EOF
|
|
1256
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1193
1257
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1194
1258
|
assert_exit "下一步 is '持續優化。' with trailing punctuation -> blocked (BLOCKED status too)" 2 $?
|
|
1195
1259
|
|
|
@@ -1230,6 +1294,7 @@ fixture reply.
|
|
|
1230
1294
|
### Status
|
|
1231
1295
|
IN_PROGRESS
|
|
1232
1296
|
EOF
|
|
1297
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1233
1298
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1234
1299
|
assert_exit "下一步 contains 繼續完成 inside a concrete sentence -> allowed" 0 $?
|
|
1235
1300
|
assert_round_merged "下一步 contains 繼續完成 inside a concrete sentence" "錯誤處理"
|
|
@@ -1273,6 +1338,7 @@ fixture reply.
|
|
|
1273
1338
|
### Status
|
|
1274
1339
|
IN_PROGRESS
|
|
1275
1340
|
EOF
|
|
1341
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1276
1342
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1277
1343
|
assert_exit "下一步 multi-line body including a filler line -> allowed" 0 $?
|
|
1278
1344
|
assert_round_merged "下一步 multi-line body including a filler line" "bar.ts"
|
|
@@ -1292,6 +1358,7 @@ y
|
|
|
1292
1358
|
### Status
|
|
1293
1359
|
DONE
|
|
1294
1360
|
EOF
|
|
1361
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1295
1362
|
REPLY_MISS_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1 >/dev/null)"
|
|
1296
1363
|
assert_exit "last Round missing ### Reply -> blocked" 2 $?
|
|
1297
1364
|
case "$REPLY_MISS_MSG" in
|
|
@@ -1335,6 +1402,7 @@ cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
|
1335
1402
|
### Status
|
|
1336
1403
|
IN_PROGRESS
|
|
1337
1404
|
EOF
|
|
1405
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1338
1406
|
ACTION_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1 >/dev/null)"
|
|
1339
1407
|
assert_exit "IN_PROGRESS 下一步 without path/cmd/skill -> blocked" 2 $?
|
|
1340
1408
|
case "$ACTION_MSG" in
|
|
@@ -1378,6 +1446,7 @@ cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
|
1378
1446
|
### Status
|
|
1379
1447
|
BLOCKED
|
|
1380
1448
|
EOF
|
|
1449
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1381
1450
|
BLOCKED_MSG="$(echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" 2>&1 >/dev/null)"
|
|
1382
1451
|
assert_exit "BLOCKED without 缺件句式 -> blocked" 2 $?
|
|
1383
1452
|
case "$BLOCKED_MSG" in
|
|
@@ -1421,6 +1490,7 @@ cat > "$DEVLOG_DIR/.round-current.md" <<'EOF'
|
|
|
1421
1490
|
### Status
|
|
1422
1491
|
BLOCKED
|
|
1423
1492
|
EOF
|
|
1493
|
+
xml_fixture "$DEVLOG_DIR/.round-current.md"
|
|
1424
1494
|
echo '{}' | bash "$SCRIPT_DIR/enforce-devlog.sh" >/dev/null 2>&1
|
|
1425
1495
|
assert_exit "BLOCKED with 缺件句式 -> allowed" 0 $?
|
|
1426
1496
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Self-check for handoff-fields.sh (docs/design/handoff-xml.md).
|
|
3
|
+
set -uo pipefail
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
# shellcheck source=../handoff-fields.sh
|
|
6
|
+
. "$SCRIPT_DIR/handoff-fields.sh"
|
|
7
|
+
FAIL=0
|
|
8
|
+
eq() {
|
|
9
|
+
if [ "$2" = "$3" ]; then echo "PASS: $1"; else printf 'FAIL: %s\n--- expected\n%s\n--- actual\n%s\n' "$1" "$2" "$3"; FAIL=1; fi
|
|
10
|
+
}
|
|
11
|
+
check_fails() {
|
|
12
|
+
local desc="$1" body="$2" kind="$3" want="$4" out
|
|
13
|
+
if out="$(handoff_xml_check "$body" "$kind")"; then
|
|
14
|
+
echo "FAIL: $desc (expected check to fail)"; FAIL=1; return
|
|
15
|
+
fi
|
|
16
|
+
case "$out" in
|
|
17
|
+
*"$want"*) echo "PASS: $desc" ;;
|
|
18
|
+
*) echo "FAIL: $desc (message lacks '$want': $out)"; FAIL=1 ;;
|
|
19
|
+
esac
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
XML='
|
|
23
|
+
<handoff>
|
|
24
|
+
<decisions>
|
|
25
|
+
pick A
|
|
26
|
+
</decisions>
|
|
27
|
+
<workspace>
|
|
28
|
+
main @ abc1234,工作樹乾淨
|
|
29
|
+
</workspace>
|
|
30
|
+
<next>
|
|
31
|
+
edit `core/scripts/x.sh`
|
|
32
|
+
```sh
|
|
33
|
+
### not a heading
|
|
34
|
+
```
|
|
35
|
+
</next>
|
|
36
|
+
</handoff>
|
|
37
|
+
'
|
|
38
|
+
MD='
|
|
39
|
+
#### 決策
|
|
40
|
+
pick A
|
|
41
|
+
#### 工作區
|
|
42
|
+
main @ abc1234,工作樹乾淨
|
|
43
|
+
#### 下一步
|
|
44
|
+
edit `core/scripts/x.sh`
|
|
45
|
+
'
|
|
46
|
+
|
|
47
|
+
eq "format xml" xml "$(handoff_format "$XML")"
|
|
48
|
+
eq "format md" md "$(handoff_format "$MD")"
|
|
49
|
+
eq "format none" none "$(handoff_format 'just prose')"
|
|
50
|
+
eq "xml field decisions" "pick A" "$(handoff_field "$XML" decisions)"
|
|
51
|
+
eq "xml field workspace" "main @ abc1234,工作樹乾淨" "$(handoff_field "$XML" workspace)"
|
|
52
|
+
eq "xml field keeps fenced content" 'edit `core/scripts/x.sh`
|
|
53
|
+
```sh
|
|
54
|
+
### not a heading
|
|
55
|
+
```' "$(handoff_field "$XML" next)"
|
|
56
|
+
eq "xml missing field empty" "" "$(handoff_field "$XML" state)"
|
|
57
|
+
eq "md field workspace" "main @ abc1234,工作樹乾淨" "$(handoff_field "$MD" workspace)"
|
|
58
|
+
eq "md field next" 'edit `core/scripts/x.sh`' "$(handoff_field "$MD" next)"
|
|
59
|
+
eq "md heading exact match only" "" "$(handoff_field '#### 工作區(舊)
|
|
60
|
+
x' workspace)"
|
|
61
|
+
eq "md fenced heading ignored" "" "$(handoff_field '```
|
|
62
|
+
#### 工作區
|
|
63
|
+
fake
|
|
64
|
+
```' workspace)"
|
|
65
|
+
|
|
66
|
+
if handoff_xml_check "$XML" handoff >/dev/null; then echo "PASS: valid xml passes"; else echo "FAIL: valid xml"; FAIL=1; fi
|
|
67
|
+
check_fails "must open with tag" 'prose
|
|
68
|
+
<handoff>
|
|
69
|
+
</handoff>' handoff "<handoff>"
|
|
70
|
+
check_fails "text after block" '<handoff>
|
|
71
|
+
<state>
|
|
72
|
+
x
|
|
73
|
+
</state>
|
|
74
|
+
</handoff>
|
|
75
|
+
trailing' handoff "外面"
|
|
76
|
+
check_fails "unknown tag" '<handoff>
|
|
77
|
+
<done_when>
|
|
78
|
+
x
|
|
79
|
+
</done_when>
|
|
80
|
+
</handoff>' handoff "done_when"
|
|
81
|
+
check_fails "inline tag" '<handoff>
|
|
82
|
+
<next>做 X</next>
|
|
83
|
+
</handoff>' handoff "自己一行"
|
|
84
|
+
check_fails "unclosed field" '<handoff>
|
|
85
|
+
<state>
|
|
86
|
+
x
|
|
87
|
+
</handoff>' handoff "</state>"
|
|
88
|
+
check_fails "missing block close" '<handoff>
|
|
89
|
+
<state>
|
|
90
|
+
x
|
|
91
|
+
</state>' handoff "</handoff>"
|
|
92
|
+
check_fails "duplicate" '<handoff>
|
|
93
|
+
<state>
|
|
94
|
+
x
|
|
95
|
+
</state>
|
|
96
|
+
<state>
|
|
97
|
+
y
|
|
98
|
+
</state>
|
|
99
|
+
</handoff>' handoff "超過一次"
|
|
100
|
+
check_fails "order" '<handoff>
|
|
101
|
+
<next>
|
|
102
|
+
x
|
|
103
|
+
</next>
|
|
104
|
+
<state>
|
|
105
|
+
y
|
|
106
|
+
</state>
|
|
107
|
+
</handoff>' handoff "順序"
|
|
108
|
+
check_fails "empty field" '<handoff>
|
|
109
|
+
<state>
|
|
110
|
+
</state>
|
|
111
|
+
</handoff>' handoff "是空的"
|
|
112
|
+
check_fails "session requires all three" '<session-handoff>
|
|
113
|
+
<decisions>
|
|
114
|
+
- (無)
|
|
115
|
+
</decisions>
|
|
116
|
+
</session-handoff>' session-handoff "open-questions"
|
|
117
|
+
check_fails "session tag not allowed in handoff" '<handoff>
|
|
118
|
+
<open-questions>
|
|
119
|
+
x
|
|
120
|
+
</open-questions>
|
|
121
|
+
</handoff>' handoff "open-questions"
|
|
122
|
+
|
|
123
|
+
ROUND='## Round 1 — t
|
|
124
|
+
|
|
125
|
+
### Handoff
|
|
126
|
+
<handoff>
|
|
127
|
+
<state>
|
|
128
|
+
s
|
|
129
|
+
</state>
|
|
130
|
+
</handoff>
|
|
131
|
+
|
|
132
|
+
### Session Handoff
|
|
133
|
+
<session-handoff>
|
|
134
|
+
<decisions>
|
|
135
|
+
- d
|
|
136
|
+
</decisions>
|
|
137
|
+
</session-handoff>
|
|
138
|
+
|
|
139
|
+
### Status
|
|
140
|
+
DONE'
|
|
141
|
+
eq "section_of Handoff" "<handoff>
|
|
142
|
+
<state>
|
|
143
|
+
s
|
|
144
|
+
</state>
|
|
145
|
+
</handoff>" "$(handoff_section_of "$ROUND" Handoff | awk 'NF')"
|
|
146
|
+
eq "section_of Session Handoff" "<session-handoff>
|
|
147
|
+
<decisions>
|
|
148
|
+
- d
|
|
149
|
+
</decisions>
|
|
150
|
+
</session-handoff>" "$(handoff_section_of "$ROUND" 'Session Handoff' | awk 'NF')"
|
|
151
|
+
|
|
152
|
+
SKILL="$(cat "$SCRIPT_DIR/../../skills/devlog-tracker/SKILL.md")"
|
|
153
|
+
for kind in handoff session-handoff; do
|
|
154
|
+
case "$SKILL" in
|
|
155
|
+
*"$(handoff_xml_template "$kind")"*) echo "PASS: SKILL.md carries $kind template" ;;
|
|
156
|
+
*) echo "FAIL: SKILL.md template for $kind differs from handoff_xml_template"; FAIL=1 ;;
|
|
157
|
+
esac
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
MSG="$(handoff_legacy_message /abs/core/scripts/migrate-handoff.sh /abs/proj)"
|
|
161
|
+
case "$MSG" in
|
|
162
|
+
*'DEVLOG_PROJECT_DIR="/abs/proj" bash "/abs/core/scripts/migrate-handoff.sh"'*) echo "PASS: legacy message has migrate command" ;;
|
|
163
|
+
*) echo "FAIL: legacy message lacks migrate command: $MSG"; FAIL=1 ;;
|
|
164
|
+
esac
|
|
165
|
+
case "$MSG" in
|
|
166
|
+
*"npx devlog-tracker init"*"/devlog-tracker:start"*) echo "PASS: legacy message has init/start hint" ;;
|
|
167
|
+
*) echo "FAIL: legacy message lacks init/start hint"; FAIL=1 ;;
|
|
168
|
+
esac
|
|
169
|
+
|
|
170
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
171
|
+
echo "All handoff-fields checks passed."
|
|
@@ -16,80 +16,102 @@ s
|
|
|
16
16
|
r
|
|
17
17
|
|
|
18
18
|
### Handoff
|
|
19
|
-
|
|
19
|
+
<handoff>
|
|
20
|
+
<state>
|
|
20
21
|
c
|
|
21
|
-
|
|
22
|
+
</state>
|
|
23
|
+
<done-when>
|
|
22
24
|
done when tests pass
|
|
23
|
-
|
|
25
|
+
</done-when>
|
|
26
|
+
<next>
|
|
24
27
|
edit core/scripts/handoff-file.sh
|
|
28
|
+
</next>
|
|
29
|
+
</handoff>
|
|
25
30
|
|
|
26
31
|
### Session Handoff
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
<session-handoff>
|
|
33
|
+
<decisions>
|
|
29
34
|
- pick route A
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
</decisions>
|
|
36
|
+
<open-questions>
|
|
32
37
|
- (無)
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
</open-questions>
|
|
39
|
+
<failed-attempts>
|
|
35
40
|
- tried parse-in-place
|
|
41
|
+
</failed-attempts>
|
|
42
|
+
</session-handoff>
|
|
36
43
|
|
|
37
44
|
### Status
|
|
38
45
|
IN_PROGRESS
|
|
39
46
|
'
|
|
40
47
|
|
|
48
|
+
EXPECTED='<session-handoff>
|
|
49
|
+
<decisions>
|
|
50
|
+
- pick route A
|
|
51
|
+
</decisions>
|
|
52
|
+
<open-questions>
|
|
53
|
+
- (無)
|
|
54
|
+
</open-questions>
|
|
55
|
+
<failed-attempts>
|
|
56
|
+
- tried parse-in-place
|
|
57
|
+
</failed-attempts>
|
|
58
|
+
</session-handoff>'
|
|
59
|
+
|
|
41
60
|
BAD_ORDER='### Session Handoff
|
|
42
|
-
|
|
61
|
+
<session-handoff>
|
|
62
|
+
<open-questions>
|
|
43
63
|
- x
|
|
44
|
-
|
|
64
|
+
</open-questions>
|
|
65
|
+
<decisions>
|
|
45
66
|
- y
|
|
46
|
-
|
|
67
|
+
</decisions>
|
|
68
|
+
<failed-attempts>
|
|
47
69
|
- z
|
|
70
|
+
</failed-attempts>
|
|
71
|
+
</session-handoff>
|
|
48
72
|
'
|
|
49
73
|
|
|
50
74
|
MISSING='### Session Handoff
|
|
75
|
+
<session-handoff>
|
|
76
|
+
<decisions>
|
|
77
|
+
- x
|
|
78
|
+
</decisions>
|
|
79
|
+
</session-handoff>
|
|
80
|
+
'
|
|
81
|
+
|
|
82
|
+
LEGACY='### Session Handoff
|
|
51
83
|
#### 決策
|
|
52
84
|
- x
|
|
85
|
+
#### 待解問題
|
|
86
|
+
- y
|
|
53
87
|
#### 失敗嘗試
|
|
54
88
|
- z
|
|
55
89
|
'
|
|
56
90
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
echo "FAIL: bad order should fail"; FAIL=1
|
|
65
|
-
else
|
|
66
|
-
echo "PASS: bad order rejected"
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
if handoff_session_section_ok "$MISSING"; then
|
|
70
|
-
echo "FAIL: missing 待解問題 should fail"; FAIL=1
|
|
71
|
-
else
|
|
72
|
-
echo "PASS: missing subsection rejected"
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
OUT="$(handoff_extract_file_body "$GOOD_ROUND")" || { echo "FAIL: extract exited $?"; FAIL=1; OUT=""; }
|
|
76
|
-
if printf '%s\n' "$OUT" | grep -q '^## Session Handoff' \
|
|
77
|
-
&& printf '%s\n' "$OUT" | grep -q '^### 決策' \
|
|
78
|
-
&& printf '%s\n' "$OUT" | grep -q 'pick route A' \
|
|
79
|
-
&& printf '%s\n' "$OUT" | grep -q '^### 待解問題' \
|
|
80
|
-
&& printf '%s\n' "$OUT" | grep -q '(無)' \
|
|
81
|
-
&& printf '%s\n' "$OUT" | grep -q '^### 失敗嘗試' \
|
|
82
|
-
&& printf '%s\n' "$OUT" | grep -q 'tried parse-in-place'; then
|
|
83
|
-
echo "PASS: extract shape"
|
|
91
|
+
TARGET="$TMP/handoff.md"
|
|
92
|
+
if handoff_write "$TARGET" "$GOOD_ROUND"; then
|
|
93
|
+
if [ "$(cat "$TARGET")" = "$EXPECTED" ]; then
|
|
94
|
+
echo "PASS: write stores <session-handoff> block verbatim"
|
|
95
|
+
else
|
|
96
|
+
echo "FAIL: handoff.md content, got:"; cat "$TARGET"; FAIL=1
|
|
97
|
+
fi
|
|
84
98
|
else
|
|
85
|
-
echo "FAIL:
|
|
99
|
+
echo "FAIL: write good round returned non-zero"; FAIL=1
|
|
86
100
|
fi
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
expect_rejected() {
|
|
103
|
+
local name="$1" blob="$2" bad_target="$TMP/bad-$1.md"
|
|
104
|
+
if handoff_write "$bad_target" "$blob"; then
|
|
105
|
+
echo "FAIL: $name should be rejected"; FAIL=1
|
|
106
|
+
elif [ -e "$bad_target" ]; then
|
|
107
|
+
echo "FAIL: $name left a file behind"; FAIL=1
|
|
108
|
+
else
|
|
109
|
+
echo "PASS: $name rejected, no file written"
|
|
110
|
+
fi
|
|
111
|
+
}
|
|
112
|
+
expect_rejected BAD_ORDER "$BAD_ORDER"
|
|
113
|
+
expect_rejected MISSING "$MISSING"
|
|
114
|
+
expect_rejected LEGACY "$LEGACY"
|
|
93
115
|
|
|
94
116
|
handoff_clear "$TARGET"
|
|
95
117
|
[ ! -f "$TARGET" ] && echo "PASS: clear removes file" \
|