workflow-ledger 0.3.6 → 0.3.8
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +10 -8
- package/README.zh-CN.md +10 -8
- package/bin/workflow-ledger.js +455 -19
- package/docs/cli.md +10 -10
- package/docs/design.md +1 -1
- package/docs/design.zh-CN.md +1 -1
- package/docs/usage.md +12 -9
- package/docs/usage.zh-CN.md +9 -6
- package/examples/claude-project/CLAUDE.md.snippet +3 -2
- package/examples/claude-project/CLAUDE.zh-CN.md.snippet +3 -2
- package/examples/codex-project/AGENTS.md.snippet +1 -0
- package/examples/codex-project/AGENTS.zh-CN.md.snippet +1 -0
- package/install.sh +7 -34
- package/package.json +1 -1
- package/skills/workflow-ledger/SKILL.md +11 -5
- package/bin/workflow-ledger +0 -467
package/bin/workflow-ledger
DELETED
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -u
|
|
3
|
-
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
-
if [ -n "${WORKFLOW_LEDGER_ROOT:-}" ]; then
|
|
6
|
-
ROOT="$WORKFLOW_LEDGER_ROOT"
|
|
7
|
-
elif [ "$(basename "$SCRIPT_DIR")" = "bin" ] && [ "$(basename "$(dirname "$SCRIPT_DIR")")" = ".claude" ]; then
|
|
8
|
-
ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
9
|
-
else
|
|
10
|
-
ROOT="$PWD"
|
|
11
|
-
fi
|
|
12
|
-
LEDGER="$ROOT/.claude/WORKFLOW.md"
|
|
13
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
14
|
-
|
|
15
|
-
error_count=0
|
|
16
|
-
warning_count=0
|
|
17
|
-
|
|
18
|
-
print_help() {
|
|
19
|
-
cat <<'HELP'
|
|
20
|
-
workflow-ledger — lightweight project-local workflow guardrails
|
|
21
|
-
|
|
22
|
-
Usage:
|
|
23
|
-
workflow-ledger help
|
|
24
|
-
workflow-ledger init [--lang en|zh-CN]
|
|
25
|
-
workflow-ledger doctor
|
|
26
|
-
workflow-ledger list
|
|
27
|
-
workflow-ledger hooks status
|
|
28
|
-
workflow-ledger hooks install
|
|
29
|
-
|
|
30
|
-
Exit codes:
|
|
31
|
-
help: always 0
|
|
32
|
-
init: 0 on created/already exists/guidance, 1 on filesystem errors
|
|
33
|
-
doctor: 0 when no errors, 1 when errors exist
|
|
34
|
-
list: 0 for readable output or missing ledger, 1 when ledger exists but cannot be read
|
|
35
|
-
hooks status: 0 when status is reported, 1 on filesystem errors
|
|
36
|
-
hooks install: 0 when installed or no-op, 1 on filesystem errors
|
|
37
|
-
HELP
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
say_error() {
|
|
41
|
-
error_count=$((error_count + 1))
|
|
42
|
-
printf 'ERROR: %s\n' "$1"
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
say_warning() {
|
|
46
|
-
warning_count=$((warning_count + 1))
|
|
47
|
-
printf 'WARNING: %s\n' "$1"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
say_info() {
|
|
51
|
-
printf 'INFO: %s\n' "$1"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
section_exists() {
|
|
55
|
-
local name="$1"
|
|
56
|
-
grep -Eq "^##[[:space:]]+$name[[:space:]]*$" "$LEDGER"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
hook_status_value() {
|
|
60
|
-
local hooks_json="$ROOT/.claude/hooks/hooks.json"
|
|
61
|
-
local hook_script="$ROOT/.claude/hooks/session-start"
|
|
62
|
-
if [ ! -f "$hooks_json" ] || [ ! -f "$hook_script" ]; then
|
|
63
|
-
printf 'not installed'
|
|
64
|
-
return 0
|
|
65
|
-
fi
|
|
66
|
-
if grep -Fq 'SessionStart' "$hooks_json" && grep -Fq '.claude/hooks/session-start' "$hooks_json" && [ -x "$hook_script" ]; then
|
|
67
|
-
printf 'installed'
|
|
68
|
-
else
|
|
69
|
-
printf 'incomplete'
|
|
70
|
-
fi
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
find_template() {
|
|
74
|
-
local language="$1"
|
|
75
|
-
local template_name='WORKFLOW.md'
|
|
76
|
-
if [ "$language" = 'zh-CN' ]; then
|
|
77
|
-
template_name='WORKFLOW.zh-CN.md'
|
|
78
|
-
fi
|
|
79
|
-
local candidates=(
|
|
80
|
-
"$ROOT/.claude/skills/workflow-ledger/templates/$template_name"
|
|
81
|
-
"$REPO_ROOT/skills/workflow-ledger/templates/$template_name"
|
|
82
|
-
)
|
|
83
|
-
local candidate
|
|
84
|
-
for candidate in "${candidates[@]}"; do
|
|
85
|
-
if [ -f "$candidate" ]; then
|
|
86
|
-
printf '%s' "$candidate"
|
|
87
|
-
return 0
|
|
88
|
-
fi
|
|
89
|
-
done
|
|
90
|
-
return 1
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
normalize_language() {
|
|
94
|
-
case "$1" in
|
|
95
|
-
en|english) printf 'en' ;;
|
|
96
|
-
zh|zh-CN|zh-cn|zh_CN|cn|chinese|中文) printf 'zh-CN' ;;
|
|
97
|
-
*) return 1 ;;
|
|
98
|
-
esac
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
choose_language() {
|
|
102
|
-
local answer=''
|
|
103
|
-
if [ -t 0 ] && [ -t 1 ]; then
|
|
104
|
-
printf 'Choose language / 选择语言 [1] English [2] 简体中文: '
|
|
105
|
-
IFS= read -r answer || answer=''
|
|
106
|
-
case "$answer" in
|
|
107
|
-
2|zh|zh-CN|zh-cn|cn|中文) printf 'zh-CN'; return 0 ;;
|
|
108
|
-
esac
|
|
109
|
-
fi
|
|
110
|
-
printf 'en'
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
cmd_init() {
|
|
114
|
-
local language=''
|
|
115
|
-
while [ "$#" -gt 0 ]; do
|
|
116
|
-
case "$1" in
|
|
117
|
-
--lang|--language)
|
|
118
|
-
language="${2:-}"
|
|
119
|
-
shift 2
|
|
120
|
-
;;
|
|
121
|
-
--lang=*|--language=*)
|
|
122
|
-
language="${1#*=}"
|
|
123
|
-
shift
|
|
124
|
-
;;
|
|
125
|
-
*)
|
|
126
|
-
shift
|
|
127
|
-
;;
|
|
128
|
-
esac
|
|
129
|
-
done
|
|
130
|
-
if [ -n "$language" ]; then
|
|
131
|
-
if ! language="$(normalize_language "$language")"; then
|
|
132
|
-
printf 'error: unknown language. Expected en or zh-CN.\n' >&2
|
|
133
|
-
return 1
|
|
134
|
-
fi
|
|
135
|
-
else
|
|
136
|
-
language="$(choose_language)"
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
if ! mkdir -p "$ROOT/.claude" 2>/dev/null; then
|
|
140
|
-
printf 'error: cannot create .claude directory\n' >&2
|
|
141
|
-
return 1
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
if [ -f "$LEDGER" ]; then
|
|
145
|
-
printf 'kept existing .claude/WORKFLOW.md\n'
|
|
146
|
-
else
|
|
147
|
-
local template
|
|
148
|
-
if ! template="$(find_template "$language")"; then
|
|
149
|
-
printf 'workflow-ledger skill template not found; run install.sh from the workflow-ledger checkout.\n' >&2
|
|
150
|
-
return 0
|
|
151
|
-
fi
|
|
152
|
-
if ! cp "$template" "$LEDGER" 2>/dev/null; then
|
|
153
|
-
printf 'error: cannot create .claude/WORKFLOW.md\n' >&2
|
|
154
|
-
return 1
|
|
155
|
-
fi
|
|
156
|
-
printf 'created .claude/WORKFLOW.md\n'
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
if [ ! -d "$ROOT/.claude/skills/workflow-ledger" ]; then
|
|
160
|
-
printf 'note: .claude/skills/workflow-ledger is missing; run install.sh to install the skill.\n' >&2
|
|
161
|
-
fi
|
|
162
|
-
return 0
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
cmd_doctor() {
|
|
166
|
-
error_count=0
|
|
167
|
-
warning_count=0
|
|
168
|
-
|
|
169
|
-
if [ ! -f "$LEDGER" ]; then
|
|
170
|
-
say_error '.claude/WORKFLOW.md is missing.'
|
|
171
|
-
return 1
|
|
172
|
-
fi
|
|
173
|
-
if [ ! -r "$LEDGER" ]; then
|
|
174
|
-
say_error '.claude/WORKFLOW.md exists but cannot be read.'
|
|
175
|
-
return 1
|
|
176
|
-
fi
|
|
177
|
-
|
|
178
|
-
section_exists 'Active' || say_error 'Missing ## Active section.'
|
|
179
|
-
section_exists 'Backlog / Future' || say_error 'Missing ## Backlog / Future section.'
|
|
180
|
-
section_exists 'Completed' || say_error 'Missing ## Completed section.'
|
|
181
|
-
|
|
182
|
-
local active_count=0
|
|
183
|
-
local backlog_items=0
|
|
184
|
-
local completed_items=0
|
|
185
|
-
local hook_status
|
|
186
|
-
hook_status="$(hook_status_value)"
|
|
187
|
-
|
|
188
|
-
local in_active=0 in_backlog=0 in_completed=0
|
|
189
|
-
local task_title='' task_status='' task_level='' task_current=''
|
|
190
|
-
local task_has_intent=0 task_has_todo=0 task_has_changes=0 task_has_prereq=0 task_has_resume=0 task_has_blocked_by=0 task_has_close_summary=0
|
|
191
|
-
local task_line_count=0
|
|
192
|
-
|
|
193
|
-
finish_task() {
|
|
194
|
-
if [ -z "$task_title" ]; then
|
|
195
|
-
return 0
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
if [ "$task_status" = 'In Progress' ]; then
|
|
199
|
-
[ -n "$task_current" ] || say_error "In Progress task '$task_title' lacks Current phase."
|
|
200
|
-
[ "$task_has_intent" -eq 1 ] || say_error "In Progress task '$task_title' lacks Intent."
|
|
201
|
-
[ "$task_has_todo" -eq 1 ] || say_error "In Progress task '$task_title' lacks Current todo."
|
|
202
|
-
[ "$task_has_resume" -eq 1 ] || say_error "In Progress task '$task_title' lacks Resume next."
|
|
203
|
-
if [ "$task_level" = '2' ] || [ "$task_level" = '3' ]; then
|
|
204
|
-
[ "$task_has_changes" -eq 1 ] || say_warning "Level $task_level task '$task_title' lacks Changes."
|
|
205
|
-
[ "$task_has_prereq" -eq 1 ] || say_warning "Level $task_level task '$task_title' lacks Prerequisites."
|
|
206
|
-
fi
|
|
207
|
-
fi
|
|
208
|
-
|
|
209
|
-
if [ "$task_status" = 'Blocked' ]; then
|
|
210
|
-
[ "$task_has_blocked_by" -eq 1 ] || say_error "Blocked task '$task_title' lacks Blocked by."
|
|
211
|
-
[ "$task_has_resume" -eq 1 ] || say_error "Blocked task '$task_title' lacks Resume next."
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
if [ "$task_status" = 'Done' ] || [ "$task_status" = 'Completed' ]; then
|
|
215
|
-
[ "$task_has_close_summary" -eq 1 ] || say_warning "Completed task '$task_title' is still under Active and lacks Close summary. Move it to ## Completed when closing."
|
|
216
|
-
fi
|
|
217
|
-
|
|
218
|
-
if [ "$task_line_count" -gt 80 ]; then
|
|
219
|
-
say_warning "Task '$task_title' has more than 80 lines."
|
|
220
|
-
fi
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
while IFS= read -r line || [ -n "$line" ]; do
|
|
224
|
-
case "$line" in
|
|
225
|
-
'## Active')
|
|
226
|
-
finish_task
|
|
227
|
-
in_active=1; in_backlog=0; in_completed=0
|
|
228
|
-
task_title=''
|
|
229
|
-
continue
|
|
230
|
-
;;
|
|
231
|
-
'## Backlog / Future')
|
|
232
|
-
finish_task
|
|
233
|
-
in_active=0; in_backlog=1; in_completed=0
|
|
234
|
-
task_title=''
|
|
235
|
-
continue
|
|
236
|
-
;;
|
|
237
|
-
'## Completed')
|
|
238
|
-
finish_task
|
|
239
|
-
in_active=0; in_backlog=0; in_completed=1
|
|
240
|
-
task_title=''
|
|
241
|
-
continue
|
|
242
|
-
;;
|
|
243
|
-
'## '*)
|
|
244
|
-
finish_task
|
|
245
|
-
in_active=0; in_backlog=0; in_completed=0
|
|
246
|
-
task_title=''
|
|
247
|
-
;;
|
|
248
|
-
esac
|
|
249
|
-
|
|
250
|
-
if [ "$in_backlog" -eq 1 ] && [[ "$line" =~ ^-[[:space:]]+(\[[[:space:]xX]\][[:space:]]+)? ]]; then
|
|
251
|
-
backlog_items=$((backlog_items + 1))
|
|
252
|
-
fi
|
|
253
|
-
if [ "$in_completed" -eq 1 ] && [[ "$line" =~ ^###[[:space:]]+ ]]; then
|
|
254
|
-
completed_items=$((completed_items + 1))
|
|
255
|
-
fi
|
|
256
|
-
|
|
257
|
-
if [ "$in_active" -eq 1 ]; then
|
|
258
|
-
if [ -n "$task_title" ]; then
|
|
259
|
-
task_line_count=$((task_line_count + 1))
|
|
260
|
-
fi
|
|
261
|
-
if [[ "$line" =~ ^###[[:space:]]+(.+) ]]; then
|
|
262
|
-
finish_task
|
|
263
|
-
active_count=$((active_count + 1))
|
|
264
|
-
task_title="${BASH_REMATCH[1]}"
|
|
265
|
-
task_status=''; task_level=''; task_current=''
|
|
266
|
-
task_has_intent=0; task_has_todo=0; task_has_changes=0; task_has_prereq=0; task_has_resume=0; task_has_blocked_by=0; task_has_close_summary=0
|
|
267
|
-
task_line_count=1
|
|
268
|
-
continue
|
|
269
|
-
fi
|
|
270
|
-
[[ "$line" =~ ^Status:[[:space:]]*(.+) ]] && [ -z "$task_status" ] && task_status="${BASH_REMATCH[1]}"
|
|
271
|
-
[[ "$line" =~ ^Level:[[:space:]]*([0-3]) ]] && task_level="${BASH_REMATCH[1]}"
|
|
272
|
-
[[ "$line" =~ ^Current[[:space:]]phase:[[:space:]]*(.+) ]] && task_current="${BASH_REMATCH[1]}"
|
|
273
|
-
[[ "$line" =~ ^Intent: ]] && task_has_intent=1
|
|
274
|
-
[[ "$line" =~ ^Current[[:space:]]todo: ]] && task_has_todo=1
|
|
275
|
-
[[ "$line" =~ ^Changes: ]] && task_has_changes=1
|
|
276
|
-
[[ "$line" =~ ^Prerequisites: ]] && task_has_prereq=1
|
|
277
|
-
[[ "$line" =~ ^Blocked[[:space:]]by: ]] && task_has_blocked_by=1
|
|
278
|
-
[[ "$line" =~ ^Resume[[:space:]]next: ]] && task_has_resume=1
|
|
279
|
-
[[ "$line" =~ ^Close[[:space:]]summary: ]] && task_has_close_summary=1
|
|
280
|
-
fi
|
|
281
|
-
done < "$LEDGER"
|
|
282
|
-
finish_task
|
|
283
|
-
|
|
284
|
-
if [ "$backlog_items" -gt 10 ]; then
|
|
285
|
-
say_warning 'Backlog / Future contains more than 10 items.'
|
|
286
|
-
fi
|
|
287
|
-
if [ "$active_count" -gt 1 ]; then
|
|
288
|
-
say_warning 'More than one Active task; include priority, blocker state, and Resume next if this is intentional.'
|
|
289
|
-
fi
|
|
290
|
-
|
|
291
|
-
say_info "Active tasks: $active_count"
|
|
292
|
-
say_info "Backlog items: $backlog_items"
|
|
293
|
-
say_info "Completed tasks: $completed_items"
|
|
294
|
-
if ledger_mtime="$(stat -c %Y "$LEDGER" 2>/dev/null)"; then
|
|
295
|
-
say_info "Ledger modified: $(date -d "@$ledger_mtime" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || printf '%s' "$ledger_mtime")"
|
|
296
|
-
fi
|
|
297
|
-
if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
298
|
-
local commit_time
|
|
299
|
-
if commit_time="$(git -C "$ROOT" log -1 --format=%ct 2>/dev/null)" && [ -n "$commit_time" ]; then
|
|
300
|
-
say_info "Latest git commit: $(date -d "@$commit_time" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || printf '%s' "$commit_time")"
|
|
301
|
-
if [ -n "${ledger_mtime:-}" ] && [ "$ledger_mtime" -lt "$commit_time" ]; then
|
|
302
|
-
say_warning 'Ledger modified time is older than latest git commit.'
|
|
303
|
-
fi
|
|
304
|
-
fi
|
|
305
|
-
fi
|
|
306
|
-
say_info "Hooks: $hook_status"
|
|
307
|
-
|
|
308
|
-
if [ "$error_count" -gt 0 ]; then
|
|
309
|
-
printf 'doctor finished with %d error(s), %d warning(s).\n' "$error_count" "$warning_count"
|
|
310
|
-
return 1
|
|
311
|
-
fi
|
|
312
|
-
printf 'doctor finished with 0 errors, %d warning(s).\n' "$warning_count"
|
|
313
|
-
return 0
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
cmd_list() {
|
|
317
|
-
if [ ! -f "$LEDGER" ]; then
|
|
318
|
-
printf 'No .claude/WORKFLOW.md found; no tasks to list.\n' >&2
|
|
319
|
-
return 0
|
|
320
|
-
fi
|
|
321
|
-
if [ ! -r "$LEDGER" ]; then
|
|
322
|
-
printf 'error: .claude/WORKFLOW.md exists but cannot be read.\n' >&2
|
|
323
|
-
return 1
|
|
324
|
-
fi
|
|
325
|
-
|
|
326
|
-
local in_active=0 in_backlog=0 in_completed=0 in_resume=0
|
|
327
|
-
local backlog_items=0 completed_items=0
|
|
328
|
-
local current_task='' status='' level='' current_phase='' resume_next=''
|
|
329
|
-
|
|
330
|
-
print_task() {
|
|
331
|
-
if [ -n "$current_task" ]; then
|
|
332
|
-
local meta=''
|
|
333
|
-
[ -n "$level" ] && meta="[Level $level]"
|
|
334
|
-
[ -n "$status" ] && meta="$meta $status"
|
|
335
|
-
printf -- '- %s %s\n' "$current_task" "$meta"
|
|
336
|
-
[ -n "$current_phase" ] && printf ' Current phase: %s\n' "$current_phase"
|
|
337
|
-
[ -n "$resume_next" ] && printf ' Resume next: %s\n' "$resume_next"
|
|
338
|
-
fi
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
printf 'Active:\n'
|
|
342
|
-
while IFS= read -r line || [ -n "$line" ]; do
|
|
343
|
-
case "$line" in
|
|
344
|
-
'## Active') in_active=1; in_backlog=0; in_completed=0; in_resume=0; continue ;;
|
|
345
|
-
'## Backlog / Future') print_task; current_task=''; in_active=0; in_backlog=1; in_completed=0; in_resume=0; continue ;;
|
|
346
|
-
'## Completed') print_task; current_task=''; in_active=0; in_backlog=0; in_completed=1; in_resume=0; continue ;;
|
|
347
|
-
'## '*) print_task; current_task=''; in_active=0; in_backlog=0; in_completed=0; in_resume=0 ;;
|
|
348
|
-
esac
|
|
349
|
-
if [ "$in_active" -eq 1 ]; then
|
|
350
|
-
if [[ "$line" =~ ^###[[:space:]]+(.+) ]]; then
|
|
351
|
-
print_task
|
|
352
|
-
current_task="${BASH_REMATCH[1]}"; status=''; level=''; current_phase=''; resume_next=''; in_resume=0
|
|
353
|
-
elif [[ "$line" =~ ^Status:[[:space:]]*(.+) ]] && [ -z "$status" ]; then
|
|
354
|
-
status="${BASH_REMATCH[1]}"
|
|
355
|
-
in_resume=0
|
|
356
|
-
elif [[ "$line" =~ ^Level:[[:space:]]*([0-3]) ]]; then
|
|
357
|
-
level="${BASH_REMATCH[1]}"
|
|
358
|
-
in_resume=0
|
|
359
|
-
elif [[ "$line" =~ ^Current[[:space:]]phase:[[:space:]]*(.+) ]]; then
|
|
360
|
-
current_phase="${BASH_REMATCH[1]}"
|
|
361
|
-
in_resume=0
|
|
362
|
-
elif [[ "$line" =~ ^Resume[[:space:]]next: ]]; then
|
|
363
|
-
in_resume=1
|
|
364
|
-
elif [ "$in_resume" -eq 1 ] && [[ "$line" =~ ^-[[:space:]]+(.+) ]]; then
|
|
365
|
-
[ -z "$resume_next" ] && resume_next="${BASH_REMATCH[1]}"
|
|
366
|
-
elif [[ -n "$line" && ! "$line" =~ ^[[:space:]]*$ ]]; then
|
|
367
|
-
in_resume=0
|
|
368
|
-
fi
|
|
369
|
-
elif [ "$in_backlog" -eq 1 ] && [[ "$line" =~ ^-[[:space:]]+(\[[[:space:]xX]\][[:space:]]+)? ]]; then
|
|
370
|
-
backlog_items=$((backlog_items + 1))
|
|
371
|
-
elif [ "$in_completed" -eq 1 ] && [[ "$line" =~ ^###[[:space:]]+ ]]; then
|
|
372
|
-
completed_items=$((completed_items + 1))
|
|
373
|
-
fi
|
|
374
|
-
done < "$LEDGER"
|
|
375
|
-
print_task
|
|
376
|
-
printf '\nBacklog / Future:\n- %d items\n\nCompleted:\n- %d items\n' "$backlog_items" "$completed_items"
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
cmd_hooks_status() {
|
|
380
|
-
local hooks_json="$ROOT/.claude/hooks/hooks.json"
|
|
381
|
-
local hook_script="$ROOT/.claude/hooks/session-start"
|
|
382
|
-
printf 'hooks: %s\n' "$(hook_status_value)"
|
|
383
|
-
printf 'hooks.json: %s\n' "$hooks_json"
|
|
384
|
-
printf 'session-start: %s\n' "$hook_script"
|
|
385
|
-
return 0
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
cmd_hooks_install() {
|
|
389
|
-
local target_dir="$ROOT/.claude/hooks"
|
|
390
|
-
if ! mkdir -p "$target_dir" 2>/dev/null; then
|
|
391
|
-
printf 'error: cannot create .claude/hooks directory.\n' >&2
|
|
392
|
-
return 1
|
|
393
|
-
fi
|
|
394
|
-
if [ -e "$target_dir/hooks.json" ]; then
|
|
395
|
-
printf 'kept existing .claude/hooks/hooks.json\n'
|
|
396
|
-
else
|
|
397
|
-
cat > "$target_dir/hooks.json" <<'HOOKS_JSON'
|
|
398
|
-
{
|
|
399
|
-
"hooks": {
|
|
400
|
-
"SessionStart": [
|
|
401
|
-
{
|
|
402
|
-
"matcher": "",
|
|
403
|
-
"command": ".claude/hooks/session-start"
|
|
404
|
-
}
|
|
405
|
-
]
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
HOOKS_JSON
|
|
409
|
-
printf 'installed .claude/hooks/hooks.json\n'
|
|
410
|
-
fi
|
|
411
|
-
if [ -e "$target_dir/session-start" ]; then
|
|
412
|
-
printf 'kept existing .claude/hooks/session-start\n'
|
|
413
|
-
else
|
|
414
|
-
cat > "$target_dir/session-start" <<'HOOK_SCRIPT'
|
|
415
|
-
#!/usr/bin/env bash
|
|
416
|
-
set -u
|
|
417
|
-
|
|
418
|
-
ledger=".claude/WORKFLOW.md"
|
|
419
|
-
cli=".claude/bin/workflow-ledger"
|
|
420
|
-
|
|
421
|
-
if [ ! -f "$ledger" ]; then
|
|
422
|
-
exit 0
|
|
423
|
-
fi
|
|
424
|
-
|
|
425
|
-
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
|
|
426
|
-
cat <<'PLUGIN_JSON'
|
|
427
|
-
{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"Workflow Ledger detected.\n- Read .claude/WORKFLOW.md before resuming tracked work.\n- Check Active tasks, Current phase/current focus, Current todo, and Resume next.\n- Run workflow-ledger doctor if state may be stale."}}
|
|
428
|
-
PLUGIN_JSON
|
|
429
|
-
exit 0
|
|
430
|
-
fi
|
|
431
|
-
|
|
432
|
-
printf 'Workflow Ledger detected.\n'
|
|
433
|
-
printf -- '- Read .claude/WORKFLOW.md before resuming tracked work.\n'
|
|
434
|
-
printf -- '- Check Active tasks, Current phase/current focus, Current todo, and Resume next.\n'
|
|
435
|
-
|
|
436
|
-
if [ -x "$cli" ]; then
|
|
437
|
-
printf -- '- Run .claude/bin/workflow-ledger doctor if state may be stale.\n'
|
|
438
|
-
else
|
|
439
|
-
printf -- '- Run workflow-ledger doctor if the project CLI is available.\n'
|
|
440
|
-
fi
|
|
441
|
-
|
|
442
|
-
exit 0
|
|
443
|
-
HOOK_SCRIPT
|
|
444
|
-
chmod +x "$target_dir/session-start" || return 1
|
|
445
|
-
printf 'installed .claude/hooks/session-start\n'
|
|
446
|
-
fi
|
|
447
|
-
}
|
|
448
|
-
main() {
|
|
449
|
-
local cmd="${1:-help}"
|
|
450
|
-
case "$cmd" in
|
|
451
|
-
help|-h|--help) print_help; return 0 ;;
|
|
452
|
-
init) shift; cmd_init "$@" ;;
|
|
453
|
-
doctor) shift; cmd_doctor "$@" ;;
|
|
454
|
-
list) shift; cmd_list "$@" ;;
|
|
455
|
-
hooks)
|
|
456
|
-
local sub="${2:-status}"
|
|
457
|
-
case "$sub" in
|
|
458
|
-
status) cmd_hooks_status ;;
|
|
459
|
-
install) cmd_hooks_install ;;
|
|
460
|
-
*) printf 'error: unknown hooks command: %s\n' "$sub" >&2; return 1 ;;
|
|
461
|
-
esac
|
|
462
|
-
;;
|
|
463
|
-
*) printf 'error: unknown command: %s\n\n' "$cmd" >&2; print_help; return 1 ;;
|
|
464
|
-
esac
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
main "$@"
|