devlog-tracker 0.25.1 → 0.29.2
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 +131 -151
- package/README.zh-TW.md +235 -0
- package/claude/hooks.json +75 -0
- package/cli/agents-md.js +15 -8
- package/cli/agents-md.test.js +21 -1
- package/cli/init.js +6 -4
- package/cli/init.test.js +5 -4
- package/cli/merge-hooks.js +5 -2
- package/cli/merge-hooks.test.js +52 -0
- package/cli/platforms/claude.js +55 -0
- package/cli/platforms/claude.test.js +178 -0
- package/cli/platforms/codex.js +6 -18
- package/cli/skills-from-commands.js +32 -0
- package/cli/skills-from-commands.test.js +51 -0
- package/cli/vendor.js +8 -1
- package/cli/vendor.test.js +24 -5
- package/codex/hooks/on-pre-tool.sh +3 -3
- package/codex/hooks/on-session-end.sh +2 -2
- package/codex/hooks/on-session-start.sh +2 -2
- package/codex/hooks/on-stop.sh +3 -3
- package/codex/hooks/on-user-prompt-submit.sh +2 -2
- package/codex/hooks/test-adapters.sh +2 -2
- package/commands/checkpoint.md +2 -2
- package/commands/clean.md +3 -3
- package/commands/compact.md +3 -3
- package/commands/continue.md +4 -4
- package/commands/keep.md +21 -5
- package/commands/lessons-drift.md +4 -4
- package/commands/lessons-off.md +3 -3
- package/commands/lessons-on.md +3 -3
- package/commands/lessons.md +3 -3
- package/commands/overview.md +3 -3
- package/commands/pause.md +3 -3
- package/commands/resume.md +3 -3
- package/commands/search.md +22 -0
- package/commands/segment-watch.md +3 -3
- package/commands/span.md +2 -2
- package/commands/start.md +3 -3
- package/commands/status.md +5 -4
- package/{hooks → core}/scripts/await-open.sh +1 -1
- package/{hooks → core}/scripts/checkpoint-set.sh +1 -1
- package/{hooks → core}/scripts/clean-devlog.sh +2 -1
- package/{hooks → core}/scripts/close-open-round.sh +1 -1
- package/{hooks → core}/scripts/compact-devlog.sh +1 -1
- package/{hooks → core}/scripts/devlog-lock.sh +17 -1
- package/{hooks → core}/scripts/devlog-path.sh +15 -8
- package/{hooks → core}/scripts/enforce-devlog.sh +26 -1
- package/core/scripts/handoff-file.sh +106 -0
- package/{hooks → core}/scripts/keep-move.sh +1 -1
- package/{hooks → core}/scripts/kept-list.sh +1 -1
- package/core/scripts/lessons-advisory-state.sh +51 -0
- package/{hooks → core}/scripts/lessons-append.sh +18 -2
- package/core/scripts/lessons-drift-set.sh +46 -0
- package/{hooks → core}/scripts/lessons-off.sh +1 -1
- package/{hooks → core}/scripts/lessons-on.sh +9 -4
- package/{hooks → core}/scripts/lessons-read.sh +1 -1
- package/{hooks → core}/scripts/on-tool-failure.sh +1 -1
- package/{hooks → core}/scripts/pause-devlog.sh +1 -1
- package/core/scripts/project-dir.sh +19 -0
- package/{hooks → core}/scripts/resume-devlog.sh +1 -1
- package/{hooks → core}/scripts/round-start.sh +52 -17
- package/{hooks → core}/scripts/run-tests.sh +1 -1
- package/core/scripts/search-devlog.sh +63 -0
- package/{hooks → core}/scripts/segment-watch-set.sh +1 -1
- package/{hooks → core}/scripts/segment-watch.sh +1 -1
- package/{hooks → core}/scripts/session-start-devlog.sh +8 -1
- package/{hooks → core}/scripts/span-close.sh +1 -1
- package/{hooks → core}/scripts/span-open.sh +1 -1
- package/{hooks → core}/scripts/start-devlog.sh +1 -1
- package/{hooks → core}/scripts/status-devlog.sh +5 -5
- package/{hooks → core}/scripts/tests/test-clean-devlog.sh +12 -0
- package/core/scripts/tests/test-cli-init-e2e.sh +68 -0
- package/core/scripts/tests/test-devlog-lock.sh +76 -0
- package/{hooks → core}/scripts/tests/test-devlog-path.sh +44 -0
- package/{hooks → core}/scripts/tests/test-enforce-devlog-files.sh +11 -0
- package/core/scripts/tests/test-enforce-devlog-session-handoff.sh +178 -0
- package/{hooks → core}/scripts/tests/test-enforce-devlog-workspace.sh +35 -0
- package/{hooks → core}/scripts/tests/test-enforce-devlog.sh +174 -0
- package/core/scripts/tests/test-handoff-file.sh +98 -0
- package/core/scripts/tests/test-lessons-advisory-state.sh +55 -0
- package/{hooks → core}/scripts/tests/test-lessons-append.sh +18 -0
- package/{hooks → core}/scripts/tests/test-lessons-drift-set.sh +14 -5
- package/{hooks → core}/scripts/tests/test-lessons-on-off.sh +16 -6
- package/core/scripts/tests/test-project-dir.sh +30 -0
- package/{hooks → core}/scripts/tests/test-round-start.sh +247 -13
- package/core/scripts/tests/test-search-devlog.sh +126 -0
- package/{hooks → core}/scripts/tests/test-session-start-devlog.sh +61 -2
- package/{hooks → core}/scripts/tests/test-status-span.sh +3 -3
- package/{hooks → core}/scripts/workspace-snapshot.sh +2 -2
- package/cursor/hooks/on-pre-tool.sh +2 -2
- package/cursor/hooks/on-session-end.sh +2 -2
- package/cursor/hooks/on-session-start.sh +2 -2
- package/cursor/hooks/on-stop.sh +2 -2
- package/cursor/hooks/on-submit-prompt.sh +2 -2
- package/cursor/hooks/on-tool-failure.sh +2 -2
- package/package.json +5 -4
- package/skills/devlog-tracker/SKILL.md +90 -22
- package/skills/devlog-tracker/references/checkpoint-mode.md +21 -6
- package/skills/devlog-tracker/references/contract.md +83 -0
- package/skills/devlog-tracker/references/lessons-mode.md +33 -9
- package/skills/devlog-tracker/references/reply-fold.md +1 -1
- package/hooks/scripts/lessons-drift-set.sh +0 -42
- package/hooks/scripts/tests/test-cli-init-e2e.sh +0 -38
- package/hooks/scripts/tests/test-devlog-lock.sh +0 -37
- /package/{hooks → core}/scripts/detect-pending-question.sh +0 -0
- /package/{hooks → core}/scripts/devlog-md.sh +0 -0
- /package/{hooks → core}/scripts/files-snapshot.sh +0 -0
- /package/{hooks → core}/scripts/json-field.sh +0 -0
- /package/{hooks → core}/scripts/on-session-end.sh +0 -0
- /package/{hooks → core}/scripts/on-stop-failure.sh +0 -0
- /package/{hooks → core}/scripts/redact-prompt.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-await-open.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-branch-scoped-integration.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-checkpoint-set.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-close-open-round.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-compact-devlog.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-devlog-md.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-enforce-devlog-handoff-order.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-files-snapshot.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-json-field.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-keep-move.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-kept-list.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-lessons-read.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-on-interrupt.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-redact-prompt.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-resume-devlog.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-segment-watch-set.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-segment-watch.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-start-pause-devlog.sh +0 -0
- /package/{hooks → core}/scripts/tests/test-workspace-snapshot.sh +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# User-invoked: /devlog-tracker:lessons-drift filesystem side.
|
|
3
|
+
# Sets the shared advisory-signal threshold (docs/design/lessons-mode.md
|
|
4
|
+
# 「機制性訊號:共用計數器」): how many cumulative occurrences — workspace-
|
|
5
|
+
# drift mismatches or accumulated BLOCKED rounds (both round-start.sh) —
|
|
6
|
+
# before printing one advisory suggestion to write a Lessons Mode entry.
|
|
7
|
+
# Nested under Lessons Mode, which is itself nested under the main switch.
|
|
8
|
+
# $1 is required: a positive integer.
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
_src="${BASH_SOURCE[0]}"
|
|
11
|
+
SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
12
|
+
# shellcheck source=json-field.sh
|
|
13
|
+
. "$SCRIPT_DIR/json-field.sh"
|
|
14
|
+
# shellcheck source=lessons-advisory-state.sh
|
|
15
|
+
. "$SCRIPT_DIR/lessons-advisory-state.sh"
|
|
16
|
+
|
|
17
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
18
|
+
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
19
|
+
|
|
20
|
+
if [ ! -f "$DEVLOG_DIR/.enabled" ]; then
|
|
21
|
+
echo "NOT_STARTED"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
if [ ! -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
25
|
+
echo "LESSONS_NOT_ENABLED"
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
COUNT_ARG="${1:-}"
|
|
30
|
+
case "$COUNT_ARG" in
|
|
31
|
+
''|*[!0-9]*|0)
|
|
32
|
+
echo "threshold must be a positive integer number of occurrences, got: $COUNT_ARG" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
|
|
37
|
+
lessons_advisory_migrate "$DEVLOG_DIR"
|
|
38
|
+
if [ -f "$DEVLOG_DIR/.lessons-advisory-state" ]; then
|
|
39
|
+
json_int_set "$DEVLOG_DIR/.lessons-advisory-state" threshold "$COUNT_ARG"
|
|
40
|
+
else
|
|
41
|
+
printf '%s\n' "{\"count\": 0, \"threshold\": ${COUNT_ARG}}" \
|
|
42
|
+
> "$DEVLOG_DIR/.lessons-advisory-state" || exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "LESSONS_DRIFT_THRESHOLD=$(json_int_get "$DEVLOG_DIR/.lessons-advisory-state" threshold)"
|
|
46
|
+
exit 0
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# or the "## Lessons 索引" block (docs/design/lessons-mode.md), same
|
|
5
5
|
# non-destructive posture as pause-devlog.sh toward devlog.md.
|
|
6
6
|
set -uo pipefail
|
|
7
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
7
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
8
8
|
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
9
9
|
if [ ! -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
10
10
|
echo "NOT_ENABLED"
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
# disable」): refuses if .enabled is absent, since there is no Round/Status
|
|
5
5
|
# history to detect a BLOCKED->resolved transition against.
|
|
6
6
|
set -uo pipefail
|
|
7
|
-
|
|
7
|
+
_src="${BASH_SOURCE[0]}"
|
|
8
|
+
SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
9
|
+
# shellcheck source=lessons-advisory-state.sh
|
|
10
|
+
. "$SCRIPT_DIR/lessons-advisory-state.sh"
|
|
11
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
8
12
|
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
9
13
|
if [ ! -f "$DEVLOG_DIR/.enabled" ]; then
|
|
10
14
|
echo "NOT_ENABLED"
|
|
@@ -13,9 +17,10 @@ fi
|
|
|
13
17
|
if [ ! -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
14
18
|
date -Iseconds > "$DEVLOG_DIR/.lessons-enabled" 2>/dev/null || echo enabled > "$DEVLOG_DIR/.lessons-enabled"
|
|
15
19
|
fi
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
lessons_advisory_migrate "$DEVLOG_DIR"
|
|
21
|
+
if [ ! -f "$DEVLOG_DIR/.lessons-advisory-state" ]; then
|
|
22
|
+
printf '%s\n' '{"count": 0, "threshold": 3}' \
|
|
23
|
+
> "$DEVLOG_DIR/.lessons-advisory-state" 2>/dev/null || true
|
|
19
24
|
fi
|
|
20
25
|
echo "LESSONS_ENABLED=$DEVLOG_DIR/.lessons-enabled"
|
|
21
26
|
exit 0
|
|
@@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
14
14
|
# shellcheck source=devlog-path.sh
|
|
15
15
|
. "$SCRIPT_DIR/devlog-path.sh"
|
|
16
16
|
|
|
17
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
17
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
18
18
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
19
19
|
MAIN="$DEVLOG_FILE"
|
|
20
20
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# PostToolUseFailure: remember user Esc so Stop can stamp without blocking.
|
|
3
3
|
set -uo pipefail
|
|
4
4
|
|
|
5
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
5
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
6
6
|
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
7
7
|
ENABLED_FLAG="$DEVLOG_DIR/.enabled"
|
|
8
8
|
[ -f "$ENABLED_FLAG" ] || exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sourced helper: the single documented definition of "which project
|
|
3
|
+
# directory is a hook/script operating in". Precedence:
|
|
4
|
+
# 1. DEVLOG_PROJECT_DIR — neutral name, harness-agnostic
|
|
5
|
+
# 2. CLAUDE_PROJECT_DIR — Claude Code's own variable, kept as a
|
|
6
|
+
# compatibility fallback so anything that already exports it
|
|
7
|
+
# (Claude Code itself, or an external script) keeps working
|
|
8
|
+
# unmodified
|
|
9
|
+
# 3. "." — current directory, when neither is set
|
|
10
|
+
#
|
|
11
|
+
# Most core/scripts/*.sh scripts inline the equivalent expression
|
|
12
|
+
# (`${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}`) directly rather than
|
|
13
|
+
# sourcing this file, to keep each entry-point script a single self-
|
|
14
|
+
# contained line. This file is the canonical statement of that precedence
|
|
15
|
+
# and is exercised by core/scripts/tests/test-project-dir.sh; source it
|
|
16
|
+
# instead of re-deriving the fallback chain in new code.
|
|
17
|
+
devlog_project_dir() {
|
|
18
|
+
printf '%s\n' "${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
19
|
+
}
|
|
@@ -20,7 +20,7 @@ NAME="$(slugify "$NAME")"
|
|
|
20
20
|
[ -n "$NAME" ] && [ "$NAME" != "archive" ] && [ "${#NAME}" -le 64 ] \
|
|
21
21
|
|| { echo "INVALID_NAME" >&2; exit 1; }
|
|
22
22
|
|
|
23
|
-
DEVLOG_DIR="${CLAUDE_PROJECT_DIR:-.}/.devlog"
|
|
23
|
+
DEVLOG_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}/.devlog"
|
|
24
24
|
FILE="$DEVLOG_DIR/devlog.$NAME.md"
|
|
25
25
|
if [ ! -f "$FILE" ]; then
|
|
26
26
|
candidates=""
|
|
@@ -24,7 +24,9 @@ HOOKS_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
24
24
|
. "$HOOKS_DIR/detect-pending-question.sh"
|
|
25
25
|
# shellcheck source=devlog-path.sh
|
|
26
26
|
. "$HOOKS_DIR/devlog-path.sh"
|
|
27
|
-
|
|
27
|
+
# shellcheck source=lessons-advisory-state.sh
|
|
28
|
+
. "$HOOKS_DIR/lessons-advisory-state.sh"
|
|
29
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
28
30
|
[ -f "$PROJECT_DIR/.devlog/.enabled" ] || exit 0
|
|
29
31
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
30
32
|
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
@@ -33,7 +35,7 @@ CHECKPOINT_FILE="$DEVLOG_DIR/.checkpoint-state"
|
|
|
33
35
|
SEGMENT_FILE="$DEVLOG_DIR/.segment-state"
|
|
34
36
|
ROUND_OPEN="$DEVLOG_DIR/.round-open"
|
|
35
37
|
AWAITING_FILE="$DEVLOG_DIR/.awaiting-reply"
|
|
36
|
-
|
|
38
|
+
ADVISORY_FILE="$DEVLOG_DIR/.lessons-advisory-state"
|
|
37
39
|
|
|
38
40
|
devlog_lock_acquire
|
|
39
41
|
trap 'devlog_lock_release' EXIT
|
|
@@ -78,6 +80,26 @@ fi
|
|
|
78
80
|
|
|
79
81
|
PROMPT="$(json_str_field "$INPUT" prompt)"
|
|
80
82
|
|
|
83
|
+
# devlog-tracker 自己的純管理指令(讀狀態或操作 devlog 系統本身,不做開發工作):
|
|
84
|
+
# 這一輪完全不開 Round、不動任何計數器,整輪直接放行,等於這個 tick 沒發生過。
|
|
85
|
+
# 不包含 continue/resume——這兩個會接著做實際開發工作,仍要照常強制記錄。
|
|
86
|
+
# 偵測依據:Claude Code 呼叫 slash command/skill 時,這一輪的 prompt 內容會帶
|
|
87
|
+
# 一個 <command-name> 標籤(本專案這些指令都是以 skill 形式提供給 Claude)。
|
|
88
|
+
# 抓不到就落回正常流程(fail-open,不影響一般訊息)。
|
|
89
|
+
CMD_NAME=""
|
|
90
|
+
case "$PROMPT" in
|
|
91
|
+
*'<command-name>'*)
|
|
92
|
+
CMD_NAME="${PROMPT#*<command-name>}"
|
|
93
|
+
CMD_NAME="${CMD_NAME%%</command-name>*}"
|
|
94
|
+
CMD_NAME="${CMD_NAME#/}"
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
case "$CMD_NAME" in
|
|
98
|
+
devlog-tracker:checkpoint|devlog-tracker:clean|devlog-tracker:compact|devlog-tracker:keep|devlog-tracker:lessons|devlog-tracker:lessons-drift|devlog-tracker:lessons-off|devlog-tracker:lessons-on|devlog-tracker:overview|devlog-tracker:pause|devlog-tracker:search|devlog-tracker:segment-watch|devlog-tracker:span|devlog-tracker:start|devlog-tracker:status)
|
|
99
|
+
exit 0
|
|
100
|
+
;;
|
|
101
|
+
esac
|
|
102
|
+
|
|
81
103
|
# 背景 task-notification(例如子 agent 完成通知)不是使用者真的打字:不留原始
|
|
82
104
|
# XML,換成精簡摘要;也不當成新話題開新 Round,改折進最後一個 Round 當段落。
|
|
83
105
|
TASK_NOTIF=0
|
|
@@ -133,21 +155,34 @@ if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ]; t
|
|
|
133
155
|
printf '\n宣稱:\n%s\n\n實際:\n%s\n' "$CLAIMED_WS" "$LIVE_WS"
|
|
134
156
|
|
|
135
157
|
if [ -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
lessons_advisory_migrate "$DEVLOG_DIR"
|
|
159
|
+
lessons_advisory_bump "$ADVISORY_FILE"
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
if [ "$SPAN_SKIP" -eq 0 ] && [ "$TASK_NOTIF" -eq 0 ] && [ -f "$DEVLOG_FILE" ] && [ -f "$DEVLOG_DIR/.lessons-enabled" ]; then
|
|
166
|
+
BLOCKED_ROUND_STARTS="$(devlog_list_round_starts "$DEVLOG_FILE")"
|
|
167
|
+
BLOCKED_ROUND_COUNT="$(printf '%s\n' "$BLOCKED_ROUND_STARTS" | grep -c '.' || true)"
|
|
168
|
+
BLOCKED_LAST_LINE="$(printf '%s\n' "$BLOCKED_ROUND_STARTS" | awk 'END { print }')"
|
|
169
|
+
BLOCKED_LAST_START="$(printf '%s\n' "$BLOCKED_LAST_LINE" | awk '{ print $1 }')"
|
|
170
|
+
if [ -n "$BLOCKED_LAST_START" ]; then
|
|
171
|
+
BLOCKED_LAST_END="$(devlog_block_end "$DEVLOG_FILE" "$BLOCKED_LAST_START")"
|
|
172
|
+
BLOCKED_LAST_STATUS="$(devlog_round_status "$DEVLOG_FILE" "$BLOCKED_LAST_START" "$BLOCKED_LAST_END")"
|
|
173
|
+
|
|
174
|
+
if [ "$BLOCKED_LAST_STATUS" = "BLOCKED" ]; then
|
|
175
|
+
lessons_advisory_migrate "$DEVLOG_DIR"
|
|
176
|
+
lessons_advisory_bump "$ADVISORY_FILE"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
if [ "$BLOCKED_ROUND_COUNT" -ge 2 ]; then
|
|
180
|
+
BLOCKED_PREV_LINE="$(printf '%s\n' "$BLOCKED_ROUND_STARTS" | tail -2 | head -1)"
|
|
181
|
+
BLOCKED_PREV_START="$(printf '%s\n' "$BLOCKED_PREV_LINE" | awk '{ print $1 }')"
|
|
182
|
+
BLOCKED_PREV_END="$(devlog_block_end "$DEVLOG_FILE" "$BLOCKED_PREV_START")"
|
|
183
|
+
BLOCKED_PREV_STATUS="$(devlog_round_status "$DEVLOG_FILE" "$BLOCKED_PREV_START" "$BLOCKED_PREV_END")"
|
|
184
|
+
if [ "$BLOCKED_PREV_STATUS" = "BLOCKED" ] && [ "$BLOCKED_LAST_STATUS" != "BLOCKED" ]; then
|
|
185
|
+
printf '\n[Lessons Mode 提示] 上一輪從 BLOCKED 解開了。可考慮用 lessons-append.sh 記一筆這次卡在哪、怎麼解開,非強制。\n'
|
|
151
186
|
fi
|
|
152
187
|
fi
|
|
153
188
|
fi
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Filesystem side of /devlog-tracker:search <關鍵字>. Plain read only -- no
|
|
3
|
+
# workspace verification, no wait-for-confirmation flow, same posture as
|
|
4
|
+
# overview.md / lessons.md. Greps every devlog*.md file under .devlog/:
|
|
5
|
+
# devlog.md, devlog.archive.md, kept devlog.<name>.md, and
|
|
6
|
+
# devlog.lessons.<topic>.md all match this one glob, so there is no
|
|
7
|
+
# per-file-type logic to keep in sync when a new kind of devlog file is
|
|
8
|
+
# added. Case-insensitive fixed-string match; not fence-aware (a match or a
|
|
9
|
+
# heading inside a ``` block is still reported) -- this is a navigation aid,
|
|
10
|
+
# not a Stop-grade verifier.
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
|
|
13
|
+
_src="${BASH_SOURCE[0]}"
|
|
14
|
+
SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
15
|
+
# shellcheck source=devlog-path.sh
|
|
16
|
+
. "$SCRIPT_DIR/devlog-path.sh"
|
|
17
|
+
|
|
18
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
19
|
+
devlog_resolve_paths "$PROJECT_DIR"
|
|
20
|
+
|
|
21
|
+
KEYWORD="${1:-}"
|
|
22
|
+
if [ -z "$KEYWORD" ]; then
|
|
23
|
+
echo "MISSING_KEYWORD" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
if [ ! -d "$DEVLOG_DIR" ]; then
|
|
28
|
+
echo "NO_INDEX"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
shopt -s nullglob
|
|
33
|
+
FILES=("$DEVLOG_DIR"/devlog*.md)
|
|
34
|
+
if [ ${#FILES[@]} -eq 0 ]; then
|
|
35
|
+
echo "NO_INDEX"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
FOUND=0
|
|
40
|
+
for f in "${FILES[@]}"; do
|
|
41
|
+
[ -f "$f" ] || continue
|
|
42
|
+
leaf="${f##*/}"
|
|
43
|
+
MATCHES="$(awk -v kw="$KEYWORD" '
|
|
44
|
+
BEGIN { heading = "" }
|
|
45
|
+
/^#{2,3} / { heading = $0 }
|
|
46
|
+
{
|
|
47
|
+
if (index(tolower($0), tolower(kw)) > 0) {
|
|
48
|
+
h = heading
|
|
49
|
+
gsub(/"/, "\\\"", h)
|
|
50
|
+
printf "HEADING=\"%s\" LINE=%d: %s\n", h, NR, $0
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
' "$f")"
|
|
54
|
+
if [ -n "$MATCHES" ]; then
|
|
55
|
+
FOUND=1
|
|
56
|
+
printf 'FILE=.devlog/%s\n' "$leaf"
|
|
57
|
+
printf '%s\n' "$MATCHES"
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
if [ "$FOUND" -eq 0 ]; then
|
|
62
|
+
echo "NO_MATCH"
|
|
63
|
+
fi
|
|
@@ -8,7 +8,7 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
8
8
|
# shellcheck source=json-field.sh
|
|
9
9
|
. "$SCRIPT_DIR/json-field.sh"
|
|
10
10
|
|
|
11
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
11
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
12
12
|
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
13
13
|
|
|
14
14
|
if [ ! -f "$DEVLOG_DIR/.enabled" ]; then
|
|
@@ -13,7 +13,7 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
13
13
|
# shellcheck source=devlog-path.sh
|
|
14
14
|
. "$SCRIPT_DIR/devlog-path.sh"
|
|
15
15
|
|
|
16
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
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
19
|
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
@@ -33,7 +33,7 @@ HOOKS_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
33
33
|
# shellcheck source=devlog-path.sh
|
|
34
34
|
. "$HOOKS_DIR/devlog-path.sh"
|
|
35
35
|
|
|
36
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
36
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
37
37
|
devlog_resolve_paths "$PROJECT_DIR"
|
|
38
38
|
ROUND_CURRENT="$DEVLOG_DIR/.round-current.md"
|
|
39
39
|
SPAN_FILE="$DEVLOG_DIR/.span-open"
|
|
@@ -69,6 +69,13 @@ if [ -f "$SPAN_FILE" ]; then
|
|
|
69
69
|
fi
|
|
70
70
|
fi
|
|
71
71
|
|
|
72
|
+
if [ -s "${HANDOFF_FILE:-}" ]; then
|
|
73
|
+
echo "以下是目前的 Session Handoff 快照(.devlog/${HANDOFF_FILE##*/};精簡狀態,不是全文):"
|
|
74
|
+
echo ""
|
|
75
|
+
cat "$HANDOFF_FILE" 2>/dev/null || true
|
|
76
|
+
echo ""
|
|
77
|
+
fi
|
|
78
|
+
|
|
72
79
|
if [ -f "$DEVLOG_FILE" ]; then
|
|
73
80
|
echo "以下是本專案 .devlog/${DEVLOG_FILE##*/} 的接手摘要(不是全文;完整紀錄請自行讀取原檔):"
|
|
74
81
|
echo ""
|
|
@@ -6,7 +6,7 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
6
6
|
. "$SCRIPT_DIR/devlog-md.sh"
|
|
7
7
|
. "$SCRIPT_DIR/devlog-path.sh"
|
|
8
8
|
|
|
9
|
-
devlog_resolve_paths "${CLAUDE_PROJECT_DIR:-.}"
|
|
9
|
+
devlog_resolve_paths "${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
10
10
|
[ -f "$DEVLOG_DIR/.enabled" ] || { echo "NOT_ENABLED" >&2; exit 1; }
|
|
11
11
|
[ ! -e "$DEVLOG_DIR/.span-open" ] || { echo "ALREADY_OPEN" >&2; exit 1; }
|
|
12
12
|
ROUND=""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# User-invoked: /devlog-tracker:start filesystem side.
|
|
3
3
|
set -uo pipefail
|
|
4
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
4
|
+
PROJECT_DIR="${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
5
5
|
DEVLOG_DIR="$PROJECT_DIR/.devlog"
|
|
6
6
|
mkdir -p "$DEVLOG_DIR" || exit 1
|
|
7
7
|
if [ ! -f "$DEVLOG_DIR/.enabled" ]; then
|
|
@@ -6,15 +6,15 @@ SCRIPT_DIR="$(cd "${_src%/*}" && pwd)"
|
|
|
6
6
|
. "$SCRIPT_DIR/devlog-md.sh"
|
|
7
7
|
. "$SCRIPT_DIR/devlog-path.sh"
|
|
8
8
|
|
|
9
|
-
devlog_resolve_paths "${CLAUDE_PROJECT_DIR:-.}"
|
|
9
|
+
devlog_resolve_paths "${DEVLOG_PROJECT_DIR:-${CLAUDE_PROJECT_DIR:-.}}"
|
|
10
10
|
[ -d "$DEVLOG_DIR" ] || { echo "NOT_STARTED"; exit 0; }
|
|
11
11
|
|
|
12
12
|
[ -f "$DEVLOG_DIR/.enabled" ] && echo "ENABLED=yes" || echo "ENABLED=no"
|
|
13
13
|
[ -f "$DEVLOG_DIR/.lessons-enabled" ] && echo "LESSONS=yes" || echo "LESSONS=no"
|
|
14
|
-
if [ -f "$DEVLOG_DIR/.lessons-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
printf '
|
|
14
|
+
if [ -f "$DEVLOG_DIR/.lessons-advisory-state" ]; then
|
|
15
|
+
advisory_count="$(json_int_get "$DEVLOG_DIR/.lessons-advisory-state" count)"
|
|
16
|
+
advisory_max="$(json_int_get "$DEVLOG_DIR/.lessons-advisory-state" threshold)"
|
|
17
|
+
printf 'LESSONS_ADVISORY=%s/%s\n' "${advisory_count:-0}" "${advisory_max:-3}"
|
|
18
18
|
fi
|
|
19
19
|
if [ -f "$DEVLOG_DIR/.span-open" ]; then
|
|
20
20
|
r="$(json_int_get "$DEVLOG_DIR/.span-open" round)"
|
|
@@ -156,5 +156,17 @@ printf '%s\n' '{"round": 1, "opened_at": "now"}' > "$ENAB/.devlog/.round-open"
|
|
|
156
156
|
bash "$SCRIPT_DIR/clean-devlog.sh" --confirmed >/dev/null
|
|
157
157
|
[ -f "$ENAB/.devlog/.enabled" ] && echo "PASS: .enabled untouched" || { echo "FAIL: .enabled removed"; FAIL=1; }
|
|
158
158
|
|
|
159
|
+
# clean removes handoff.md
|
|
160
|
+
HOFF="$TMP/handoffclean"
|
|
161
|
+
mkdir -p "$HOFF/.devlog"
|
|
162
|
+
export CLAUDE_PROJECT_DIR="$HOFF"
|
|
163
|
+
printf '# project\n\n' > "$HOFF/.devlog/devlog.md"
|
|
164
|
+
make_round "$HOFF/.devlog/devlog.md" 1 DONE
|
|
165
|
+
echo "## Session Handoff" > "$HOFF/.devlog/handoff.md"
|
|
166
|
+
bash "$SCRIPT_DIR/clean-devlog.sh" --confirmed >/dev/null
|
|
167
|
+
[ ! -f "$HOFF/.devlog/handoff.md" ] \
|
|
168
|
+
&& echo "PASS: clean removes handoff.md" \
|
|
169
|
+
|| { echo "FAIL: handoff.md survived clean"; FAIL=1; }
|
|
170
|
+
|
|
159
171
|
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
160
172
|
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
5
|
+
TMP="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
7
|
+
FAIL=0
|
|
8
|
+
|
|
9
|
+
command -v node >/dev/null 2>&1 || { echo "SKIP: node not installed"; exit 0; }
|
|
10
|
+
|
|
11
|
+
PROJECT="$TMP/project"
|
|
12
|
+
mkdir -p "$PROJECT"
|
|
13
|
+
|
|
14
|
+
cd "$PROJECT" || exit 1
|
|
15
|
+
NODE_BIN="$(command -v node)"
|
|
16
|
+
"$NODE_BIN" "$REPO_ROOT/bin/devlog-tracker.js" init --claude --codex --cursor >/dev/null 2>"$TMP/stderr" \
|
|
17
|
+
|| { echo "FAIL: init exited non-zero"; cat "$TMP/stderr"; FAIL=1; }
|
|
18
|
+
|
|
19
|
+
[ -f "$PROJECT/.devlog-tracker/VERSION" ] && echo "PASS: vendored VERSION exists" || { echo "FAIL: no VERSION"; FAIL=1; }
|
|
20
|
+
[ -f "$PROJECT/.devlog-tracker/env.sh" ] && echo "PASS: vendored env.sh exists" || { echo "FAIL: no env.sh"; FAIL=1; }
|
|
21
|
+
[ -f "$PROJECT/.claude/settings.local.json" ] && echo "PASS: .claude/settings.local.json written" || { echo "FAIL: no .claude/settings.local.json"; FAIL=1; }
|
|
22
|
+
[ -f "$PROJECT/.claude/skills/devlog-start/SKILL.md" ] && echo "PASS: Claude start skill written" || { echo "FAIL: no Claude start skill"; FAIL=1; }
|
|
23
|
+
[ -f "$PROJECT/CLAUDE.md" ] && grep -q 'devlog-tracker:begin' "$PROJECT/CLAUDE.md" \
|
|
24
|
+
&& echo "PASS: CLAUDE.md fallback block written" || { echo "FAIL: no devlog-tracker block in CLAUDE.md"; FAIL=1; }
|
|
25
|
+
[ -f "$PROJECT/.codex/hooks.json" ] && echo "PASS: .codex/hooks.json written" || { echo "FAIL: no .codex/hooks.json"; FAIL=1; }
|
|
26
|
+
[ -f "$PROJECT/.agents/skills/devlog-start/SKILL.md" ] && echo "PASS: Codex start skill written" || { echo "FAIL: no Codex start skill"; FAIL=1; }
|
|
27
|
+
[ -f "$PROJECT/.cursor/hooks.json" ] && echo "PASS: .cursor/hooks.json written" || { echo "FAIL: no .cursor/hooks.json"; FAIL=1; }
|
|
28
|
+
|
|
29
|
+
# Controller addition 1a: Check vendored core scripts exist
|
|
30
|
+
[ -f "$PROJECT/.devlog-tracker/core/scripts/enforce-devlog.sh" ] && echo "PASS: vendored core/scripts/enforce-devlog.sh exists" || { echo "FAIL: no core/scripts/enforce-devlog.sh"; FAIL=1; }
|
|
31
|
+
[ -f "$PROJECT/.devlog-tracker/claude/hooks.json" ] && echo "PASS: vendored claude/hooks.json exists" || { echo "FAIL: no claude/hooks.json"; FAIL=1; }
|
|
32
|
+
|
|
33
|
+
# Controller addition 1b: Check merged Claude hook commands are resolved (no placeholders)
|
|
34
|
+
if grep -q '\${CLAUDE_PLUGIN_ROOT\|${DEVLOG_TRACKER_ROOT' "$PROJECT/.claude/settings.local.json"; then
|
|
35
|
+
echo "FAIL: unresolved placeholders in .claude/settings.local.json"; FAIL=1
|
|
36
|
+
else
|
|
37
|
+
echo "PASS: no unresolved placeholders in .claude/settings.local.json"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Verify that Stop hook command references .devlog-tracker/core/scripts/
|
|
41
|
+
STOP_CMD=$("$NODE_BIN" -e "console.log(JSON.parse(require('fs').readFileSync('$PROJECT/.claude/settings.local.json')).hooks.Stop[0].hooks[0].command)")
|
|
42
|
+
if echo "$STOP_CMD" | grep -q '\.devlog-tracker/core/scripts/enforce-devlog\.sh'; then
|
|
43
|
+
echo "PASS: Claude Stop hook references .devlog-tracker/core/scripts/enforce-devlog.sh"
|
|
44
|
+
else
|
|
45
|
+
echo "FAIL: Claude Stop hook does not reference .devlog-tracker/core/scripts/enforce-devlog.sh"; FAIL=1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# re-running must not duplicate entries
|
|
49
|
+
COUNT_BEFORE_CODEX=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PROJECT/.codex/hooks.json')).hooks.Stop.length)")
|
|
50
|
+
COUNT_BEFORE_CLAUDE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PROJECT/.claude/settings.local.json')).hooks.Stop.length)")
|
|
51
|
+
"$NODE_BIN" "$REPO_ROOT/bin/devlog-tracker.js" init --claude --codex --cursor >/dev/null 2>"$TMP/stderr" \
|
|
52
|
+
|| { echo "FAIL: second init exited non-zero"; cat "$TMP/stderr"; FAIL=1; }
|
|
53
|
+
COUNT_AFTER_CODEX=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PROJECT/.codex/hooks.json')).hooks.Stop.length)")
|
|
54
|
+
COUNT_AFTER_CLAUDE=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$PROJECT/.claude/settings.local.json')).hooks.Stop.length)")
|
|
55
|
+
if [ "$COUNT_BEFORE_CODEX" = "$COUNT_AFTER_CODEX" ]; then
|
|
56
|
+
echo "PASS: re-running init does not duplicate Codex hook entries"
|
|
57
|
+
else
|
|
58
|
+
echo "FAIL: Codex hook entries duplicated ($COUNT_BEFORE_CODEX -> $COUNT_AFTER_CODEX)"; FAIL=1
|
|
59
|
+
fi
|
|
60
|
+
if [ "$COUNT_BEFORE_CLAUDE" = "$COUNT_AFTER_CLAUDE" ]; then
|
|
61
|
+
echo "PASS: re-running init does not duplicate Claude hook entries"
|
|
62
|
+
else
|
|
63
|
+
echo "FAIL: Claude hook entries duplicated ($COUNT_BEFORE_CLAUDE -> $COUNT_AFTER_CLAUDE)"; FAIL=1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
67
|
+
else echo "Some checks FAILED."; exit 1
|
|
68
|
+
fi
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
4
|
+
. "$SCRIPT_DIR/devlog-lock.sh"
|
|
5
|
+
TMP="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
7
|
+
DEVLOG_DIR="$TMP/.devlog"
|
|
8
|
+
mkdir -p "$DEVLOG_DIR"
|
|
9
|
+
FAIL=0
|
|
10
|
+
|
|
11
|
+
devlog_lock_acquire
|
|
12
|
+
[ "${LOCK_HELD:-0}" -eq 1 ] && [ -d "$DEVLOG_DIR/.lock" ] \
|
|
13
|
+
&& echo "PASS: acquired" || { echo "FAIL: acquire"; FAIL=1; }
|
|
14
|
+
mkdir "$DEVLOG_DIR/.lock" >/dev/null 2>&1
|
|
15
|
+
[ "$?" -ne 0 ] && echo "PASS: second mkdir excluded" || { echo "FAIL: duplicate mkdir"; FAIL=1; }
|
|
16
|
+
devlog_lock_release
|
|
17
|
+
devlog_lock_acquire
|
|
18
|
+
[ "${LOCK_HELD:-0}" -eq 1 ] && echo "PASS: reacquired after release" || { echo "FAIL: reacquire"; FAIL=1; }
|
|
19
|
+
devlog_lock_release
|
|
20
|
+
|
|
21
|
+
mkdir "$DEVLOG_DIR/.lock"
|
|
22
|
+
START="$(date +%s)"
|
|
23
|
+
devlog_lock_acquire
|
|
24
|
+
END="$(date +%s)"
|
|
25
|
+
ELAPSED=$((END - START))
|
|
26
|
+
[ "${LOCK_HELD:-0}" -eq 0 ] && [ "$ELAPSED" -ge 2 ] && [ "$ELAPSED" -le 3 ] \
|
|
27
|
+
&& echo "PASS: contention fails open" || { echo "FAIL: contention held=$LOCK_HELD elapsed=$ELAPSED"; FAIL=1; }
|
|
28
|
+
rmdir "$DEVLOG_DIR/.lock"
|
|
29
|
+
|
|
30
|
+
MIN_PATH="$TMP/min-path"
|
|
31
|
+
mkdir "$MIN_PATH"
|
|
32
|
+
ln -s "$(command -v bash)" "$MIN_PATH/bash"
|
|
33
|
+
HELD="$(PATH="$MIN_PATH" bash -c '. "$1"; DEVLOG_DIR="$2"; devlog_lock_acquire; echo "${LOCK_HELD:-0}"' _ "$SCRIPT_DIR/devlog-lock.sh" "$DEVLOG_DIR")"
|
|
34
|
+
[ "$HELD" = "0" ] && echo "PASS: missing lock tools fails open" || { echo "FAIL: restricted path held=$HELD"; FAIL=1; }
|
|
35
|
+
|
|
36
|
+
# Acquire writes its own pid into the lock dir; release removes the whole
|
|
37
|
+
# dir (pid file included), not just an empty rmdir.
|
|
38
|
+
devlog_lock_acquire
|
|
39
|
+
[ -f "$DEVLOG_DIR/.lock/pid" ] && [ "$(cat "$DEVLOG_DIR/.lock/pid")" = "$$" ] \
|
|
40
|
+
&& echo "PASS: acquire writes own pid" || { echo "FAIL: pid file missing/wrong"; FAIL=1; }
|
|
41
|
+
devlog_lock_release
|
|
42
|
+
[ ! -d "$DEVLOG_DIR/.lock" ] && echo "PASS: release removes dir incl. pid file" \
|
|
43
|
+
|| { echo "FAIL: lock dir survives release"; FAIL=1; }
|
|
44
|
+
|
|
45
|
+
# Stale lock (pid file names a process that is no longer running): reclaimed
|
|
46
|
+
# near-instantly instead of waiting out the full contention timeout.
|
|
47
|
+
mkdir "$DEVLOG_DIR/.lock"
|
|
48
|
+
echo 999999 > "$DEVLOG_DIR/.lock/pid"
|
|
49
|
+
START="$(date +%s)"
|
|
50
|
+
devlog_lock_acquire
|
|
51
|
+
END="$(date +%s)"
|
|
52
|
+
ELAPSED=$((END - START))
|
|
53
|
+
[ "${LOCK_HELD:-0}" -eq 1 ] && [ "$ELAPSED" -le 1 ] \
|
|
54
|
+
&& echo "PASS: stale lock reclaimed fast" || { echo "FAIL: stale reclaim held=$LOCK_HELD elapsed=$ELAPSED"; FAIL=1; }
|
|
55
|
+
devlog_lock_release
|
|
56
|
+
|
|
57
|
+
# Live contention (pid file names a still-running process): fails open after
|
|
58
|
+
# the timeout as before, but now reports who is holding it.
|
|
59
|
+
sleep 5 &
|
|
60
|
+
LIVE_PID=$!
|
|
61
|
+
mkdir "$DEVLOG_DIR/.lock"
|
|
62
|
+
echo "$LIVE_PID" > "$DEVLOG_DIR/.lock/pid"
|
|
63
|
+
START="$(date +%s)"
|
|
64
|
+
devlog_lock_acquire
|
|
65
|
+
END="$(date +%s)"
|
|
66
|
+
ELAPSED=$((END - START))
|
|
67
|
+
[ "${LOCK_HELD:-0}" -eq 0 ] && [ "$ELAPSED" -ge 2 ] && [ "$ELAPSED" -le 3 ] \
|
|
68
|
+
&& [ "${LOCK_CONTENDED_BY:-}" = "$LIVE_PID" ] \
|
|
69
|
+
&& echo "PASS: live contention reports holder pid" \
|
|
70
|
+
|| { echo "FAIL: live contention held=$LOCK_HELD elapsed=$ELAPSED by=${LOCK_CONTENDED_BY:-}"; FAIL=1; }
|
|
71
|
+
kill "$LIVE_PID" 2>/dev/null || true
|
|
72
|
+
wait "$LIVE_PID" 2>/dev/null || true
|
|
73
|
+
rm -rf "$DEVLOG_DIR/.lock"
|
|
74
|
+
|
|
75
|
+
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
76
|
+
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -114,5 +114,49 @@ else
|
|
|
114
114
|
FAIL=1
|
|
115
115
|
fi
|
|
116
116
|
|
|
117
|
+
# --- HANDOFF_FILE mirrors DEVLOG_FILE naming (no rename migration) --------
|
|
118
|
+
devlog_resolve_paths "$NONGIT"
|
|
119
|
+
[ "$HANDOFF_FILE" = "$NONGIT/.devlog/handoff.md" ] \
|
|
120
|
+
&& echo "PASS: non-git HANDOFF_FILE is handoff.md" \
|
|
121
|
+
|| { echo "FAIL: non-git HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
122
|
+
|
|
123
|
+
devlog_resolve_paths "$MAINREPO"
|
|
124
|
+
[ "$HANDOFF_FILE" = "$MAINREPO/.devlog/handoff.md" ] \
|
|
125
|
+
&& echo "PASS: main HANDOFF_FILE is handoff.md" \
|
|
126
|
+
|| { echo "FAIL: main HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
127
|
+
|
|
128
|
+
devlog_resolve_paths "$FEATREPO"
|
|
129
|
+
[ "$HANDOFF_FILE" = "$FEATREPO/.devlog/handoff.feature-x.md" ] \
|
|
130
|
+
&& echo "PASS: feature HANDOFF_FILE is handoff.feature-x.md" \
|
|
131
|
+
|| { echo "FAIL: feature HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
132
|
+
|
|
133
|
+
devlog_resolve_paths "$SLASHREPO"
|
|
134
|
+
[ "$HANDOFF_FILE" = "$SLASHREPO/.devlog/handoff.feature-foo.md" ] \
|
|
135
|
+
&& echo "PASS: slash branch HANDOFF_FILE sanitized" \
|
|
136
|
+
|| { echo "FAIL: slash HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
137
|
+
|
|
138
|
+
devlog_resolve_paths "$DETACHEDREPO"
|
|
139
|
+
[ "$HANDOFF_FILE" = "$DETACHEDREPO/.devlog/handoff.detached-repo.md" ] \
|
|
140
|
+
&& echo "PASS: detached HANDOFF_FILE uses worktree dirname" \
|
|
141
|
+
|| { echo "FAIL: detached HANDOFF_FILE=$HANDOFF_FILE"; FAIL=1; }
|
|
142
|
+
|
|
143
|
+
# migration must NOT move handoff.md
|
|
144
|
+
MIGHO="$TMP/mighandoff"
|
|
145
|
+
mkdir -p "$MIGHO/.devlog"
|
|
146
|
+
: > "$MIGHO/.devlog/.enabled"
|
|
147
|
+
git_setup "$MIGHO"
|
|
148
|
+
git -C "$MIGHO" checkout -q -b feature-h
|
|
149
|
+
echo "old handoff" > "$MIGHO/.devlog/handoff.md"
|
|
150
|
+
echo "## Round 1" > "$MIGHO/.devlog/devlog.md"
|
|
151
|
+
devlog_resolve_paths "$MIGHO"
|
|
152
|
+
if [ -f "$MIGHO/.devlog/handoff.md" ] \
|
|
153
|
+
&& [ ! -f "$MIGHO/.devlog/handoff.feature-h.md" ] \
|
|
154
|
+
&& [ "$HANDOFF_FILE" = "$MIGHO/.devlog/handoff.feature-h.md" ]; then
|
|
155
|
+
echo "PASS: handoff.md is not renamed on first branch resolve"
|
|
156
|
+
else
|
|
157
|
+
echo "FAIL: handoff migration diverged (HANDOFF_FILE=$HANDOFF_FILE)"
|
|
158
|
+
FAIL=1
|
|
159
|
+
fi
|
|
160
|
+
|
|
117
161
|
if [ "$FAIL" -eq 0 ]; then echo "All checks passed."; exit 0
|
|
118
162
|
else echo "Some checks FAILED."; exit 1; fi
|
|
@@ -80,6 +80,17 @@ write_round() {
|
|
|
80
80
|
echo "observable done via test."
|
|
81
81
|
echo "#### 下一步"
|
|
82
82
|
echo "edit hooks/scripts/enforce-devlog.sh"
|
|
83
|
+
echo ""
|
|
84
|
+
echo "### Session Handoff"
|
|
85
|
+
echo ""
|
|
86
|
+
echo "#### 決策"
|
|
87
|
+
echo "- (無)"
|
|
88
|
+
echo ""
|
|
89
|
+
echo "#### 待解問題"
|
|
90
|
+
echo "- fixture open"
|
|
91
|
+
echo ""
|
|
92
|
+
echo "#### 失敗嘗試"
|
|
93
|
+
echo "- (無)"
|
|
83
94
|
fi
|
|
84
95
|
echo ""
|
|
85
96
|
echo "### Status"
|