@lumenflow/cli 3.16.0 → 3.17.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.
Files changed (167) hide show
  1. package/README.md +1 -3
  2. package/dist/config-get.js +3 -3
  3. package/dist/config-get.js.map +1 -1
  4. package/dist/config-set.js +3 -3
  5. package/dist/config-set.js.map +1 -1
  6. package/dist/docs-sync.js +302 -57
  7. package/dist/docs-sync.js.map +1 -1
  8. package/dist/doctor.js +3 -3
  9. package/dist/doctor.js.map +1 -1
  10. package/dist/gate-defaults.js +3 -0
  11. package/dist/gate-defaults.js.map +1 -1
  12. package/dist/gates-plan-resolvers.js +3 -4
  13. package/dist/gates-plan-resolvers.js.map +1 -1
  14. package/dist/gates-runners.js +48 -0
  15. package/dist/gates-runners.js.map +1 -1
  16. package/dist/gates-utils.js +64 -3
  17. package/dist/gates-utils.js.map +1 -1
  18. package/dist/gates.js +2 -1
  19. package/dist/gates.js.map +1 -1
  20. package/dist/init-scaffolding.js +28 -1
  21. package/dist/init-scaffolding.js.map +1 -1
  22. package/dist/init-templates.js +6 -2
  23. package/dist/init-templates.js.map +1 -1
  24. package/dist/init.js +22 -25
  25. package/dist/init.js.map +1 -1
  26. package/dist/lane-create.js +2 -2
  27. package/dist/lane-create.js.map +1 -1
  28. package/dist/lane-edit.js +2 -2
  29. package/dist/lane-edit.js.map +1 -1
  30. package/dist/lane-lock.js +8 -6
  31. package/dist/lane-lock.js.map +1 -1
  32. package/dist/lane-setup.js +7 -5
  33. package/dist/lane-setup.js.map +1 -1
  34. package/dist/lane-status.js +7 -5
  35. package/dist/lane-status.js.map +1 -1
  36. package/dist/lane-validate.js +8 -6
  37. package/dist/lane-validate.js.map +1 -1
  38. package/dist/lumenflow-upgrade.js +22 -13
  39. package/dist/lumenflow-upgrade.js.map +1 -1
  40. package/dist/onboard.js +15 -16
  41. package/dist/onboard.js.map +1 -1
  42. package/dist/orchestrate-initiative.js +29 -8
  43. package/dist/orchestrate-initiative.js.map +1 -1
  44. package/dist/state-doctor.js +3 -4
  45. package/dist/state-doctor.js.map +1 -1
  46. package/dist/wu-done-policies.js +38 -0
  47. package/dist/wu-done-policies.js.map +1 -1
  48. package/dist/wu-edit-operations.js +27 -24
  49. package/dist/wu-edit-operations.js.map +1 -1
  50. package/dist/wu-edit-validators.js +68 -0
  51. package/dist/wu-edit-validators.js.map +1 -1
  52. package/dist/wu-edit.js +11 -3
  53. package/dist/wu-edit.js.map +1 -1
  54. package/package.json +8 -8
  55. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  56. package/packs/sidekick/package.json +1 -1
  57. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  58. package/packs/software-delivery/package.json +1 -1
  59. package/templates/core/AGENTS.md.template +2 -1
  60. package/templates/core/LUMENFLOW.md.template +5 -1
  61. package/templates/core/UPGRADING.md.template +14 -10
  62. package/templates/core/ai/onboarding/initiative-orchestration.md.template +1 -1
  63. package/templates/core/ai/onboarding/quick-ref-commands.md.template +11 -10
  64. package/templates/core/ai/onboarding/starting-prompt.md.template +3 -3
  65. package/templates/vendors/claude/.claude/CLAUDE.md.template +2 -1
  66. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +2 -1
  67. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +2 -1
  68. package/dist/chunk-2D2VOCA4.js +0 -37
  69. package/dist/chunk-2D5KFYGX.js +0 -284
  70. package/dist/chunk-2GXVIN57.js +0 -14072
  71. package/dist/chunk-2MQ7HZWZ.js +0 -26
  72. package/dist/chunk-2UFQ3A3C.js +0 -643
  73. package/dist/chunk-3RG5ZIWI.js +0 -10
  74. package/dist/chunk-4N74J3UT.js +0 -15
  75. package/dist/chunk-5GTOXFYR.js +0 -392
  76. package/dist/chunk-5VY6MQMC.js +0 -240
  77. package/dist/chunk-67XVPMRY.js +0 -1297
  78. package/dist/chunk-6HO4GWJE.js +0 -164
  79. package/dist/chunk-6W5XHWYV.js +0 -1890
  80. package/dist/chunk-6X4EMYJQ.js +0 -64
  81. package/dist/chunk-6XYXI2NQ.js +0 -772
  82. package/dist/chunk-7ANSOV6Q.js +0 -285
  83. package/dist/chunk-A624LFLB.js +0 -1380
  84. package/dist/chunk-ADN5NHG4.js +0 -126
  85. package/dist/chunk-B7YJYJKG.js +0 -33
  86. package/dist/chunk-CCLHCPKG.js +0 -210
  87. package/dist/chunk-CK36VROC.js +0 -1584
  88. package/dist/chunk-D3UOFRSB.js +0 -81
  89. package/dist/chunk-DFR4DJBM.js +0 -230
  90. package/dist/chunk-DSYBDHYH.js +0 -79
  91. package/dist/chunk-DWMLTXKQ.js +0 -1176
  92. package/dist/chunk-E3REJTAJ.js +0 -28
  93. package/dist/chunk-EA3IVO64.js +0 -633
  94. package/dist/chunk-EK2AKZKD.js +0 -55
  95. package/dist/chunk-ELD7JTTT.js +0 -343
  96. package/dist/chunk-EX6TT2XI.js +0 -195
  97. package/dist/chunk-EXINSFZE.js +0 -82
  98. package/dist/chunk-EZ6ZBYBM.js +0 -510
  99. package/dist/chunk-FBKAPTJ2.js +0 -16
  100. package/dist/chunk-FVLV5RYH.js +0 -1118
  101. package/dist/chunk-GDNSBQVK.js +0 -2485
  102. package/dist/chunk-GPQHMBNN.js +0 -278
  103. package/dist/chunk-GTFJB67L.js +0 -68
  104. package/dist/chunk-HANJXVKW.js +0 -1127
  105. package/dist/chunk-HEVS5YLD.js +0 -269
  106. package/dist/chunk-HMEVZKPQ.js +0 -9
  107. package/dist/chunk-HRGSYNLM.js +0 -3511
  108. package/dist/chunk-ISZR5N4K.js +0 -60
  109. package/dist/chunk-J6SUPR2C.js +0 -226
  110. package/dist/chunk-JERYVEIZ.js +0 -244
  111. package/dist/chunk-JHHWGL2N.js +0 -87
  112. package/dist/chunk-JONWQUB5.js +0 -775
  113. package/dist/chunk-K2DIWWDM.js +0 -1766
  114. package/dist/chunk-KY4PGL5V.js +0 -969
  115. package/dist/chunk-L737LQ4C.js +0 -1285
  116. package/dist/chunk-LFTWYIB2.js +0 -497
  117. package/dist/chunk-LV47RFNJ.js +0 -41
  118. package/dist/chunk-MKSAITI7.js +0 -15
  119. package/dist/chunk-MZ7RKIX4.js +0 -212
  120. package/dist/chunk-NAP6CFSO.js +0 -84
  121. package/dist/chunk-ND6MY37M.js +0 -16
  122. package/dist/chunk-NMG736UR.js +0 -683
  123. package/dist/chunk-NRAXROED.js +0 -32
  124. package/dist/chunk-NRIZR3A7.js +0 -690
  125. package/dist/chunk-NX43BG3M.js +0 -233
  126. package/dist/chunk-O645XLSI.js +0 -297
  127. package/dist/chunk-OMJD6A3S.js +0 -235
  128. package/dist/chunk-QB6SJD4T.js +0 -430
  129. package/dist/chunk-QFSTL4J3.js +0 -276
  130. package/dist/chunk-QLGDFMFX.js +0 -212
  131. package/dist/chunk-RIAAGL2E.js +0 -13
  132. package/dist/chunk-RWO5XMZ6.js +0 -86
  133. package/dist/chunk-RXRKBBSM.js +0 -149
  134. package/dist/chunk-RZOZMML6.js +0 -363
  135. package/dist/chunk-U7I7FS7T.js +0 -113
  136. package/dist/chunk-UI42RODY.js +0 -717
  137. package/dist/chunk-UTVMVSCO.js +0 -519
  138. package/dist/chunk-V6OJGLBA.js +0 -1746
  139. package/dist/chunk-W2JHVH7D.js +0 -152
  140. package/dist/chunk-WD3Y7VQN.js +0 -280
  141. package/dist/chunk-WOCTQ5MS.js +0 -303
  142. package/dist/chunk-WZR3ZUNN.js +0 -696
  143. package/dist/chunk-XGI665H7.js +0 -150
  144. package/dist/chunk-XKY65P2T.js +0 -304
  145. package/dist/chunk-Y4CQZY65.js +0 -57
  146. package/dist/chunk-YFEXKLVE.js +0 -194
  147. package/dist/chunk-YHO3HS5X.js +0 -287
  148. package/dist/chunk-YLS7AZSX.js +0 -738
  149. package/dist/chunk-ZE473AO6.js +0 -49
  150. package/dist/chunk-ZF747T3O.js +0 -644
  151. package/dist/chunk-ZHCZHZH3.js +0 -43
  152. package/dist/chunk-ZZNZX2XY.js +0 -87
  153. package/dist/constants-7QAP3VQ4.js +0 -23
  154. package/dist/dist-IY3UUMWK.js +0 -33
  155. package/dist/invariants-runner-W5RGHCSU.js +0 -27
  156. package/dist/lane-lock-6J36HD5O.js +0 -35
  157. package/dist/mem-checkpoint-core-EANG2GVN.js +0 -14
  158. package/dist/mem-signal-core-2LZ2WYHW.js +0 -19
  159. package/dist/memory-store-OLB5FO7K.js +0 -18
  160. package/dist/service-6BYCOCO5.js +0 -13
  161. package/dist/spawn-policy-resolver-NTSZYQ6R.js +0 -17
  162. package/dist/spawn-task-builder-R4E2BHSW.js +0 -22
  163. package/dist/wu-done-pr-WLFFFEPJ.js +0 -25
  164. package/dist/wu-done-validation-3J5E36FE.js +0 -30
  165. package/dist/wu-duplicate-id-detector-5S7JHELK.js +0 -232
  166. package/packs/sidekick/.turbo/turbo-typecheck.log +0 -4
  167. package/packs/software-delivery/.turbo/turbo-typecheck.log +0 -4
@@ -1,28 +0,0 @@
1
- import {
2
- createGitForPath
3
- } from "./chunk-2UFQ3A3C.js";
4
- import {
5
- BRANCHES
6
- } from "./chunk-DWMLTXKQ.js";
7
-
8
- // ../core/dist/core/worktree-guard.js
9
- import path from "path";
10
- var WORKTREE_PATH_PATTERN = /worktrees\/([\w-]+)-wu-(\d+)/;
11
- async function isMainBranch(options = {}) {
12
- const git = options.git || createGitForPath(process.cwd());
13
- const branch = await git.getCurrentBranch();
14
- return branch === BRANCHES.MAIN || branch === BRANCHES.MASTER;
15
- }
16
- function normalizePath(p) {
17
- return p.replace(/\\/g, "/").split(path.sep).join("/");
18
- }
19
- function isInWorktree(options = {}) {
20
- const cwd = options.cwd || process.cwd();
21
- const normalizedPath = normalizePath(cwd);
22
- return WORKTREE_PATH_PATTERN.test(normalizedPath);
23
- }
24
-
25
- export {
26
- isMainBranch,
27
- isInWorktree
28
- };
@@ -1,633 +0,0 @@
1
- import {
2
- loadSignals,
3
- markSignalsAsRead
4
- } from "./chunk-OMJD6A3S.js";
5
- import {
6
- createWuPaths
7
- } from "./chunk-6HO4GWJE.js";
8
- import {
9
- CLAUDE_HOOKS,
10
- DIRECTORIES,
11
- getHookCommand
12
- } from "./chunk-DWMLTXKQ.js";
13
-
14
- // src/hooks/generators/enforce-worktree.ts
15
- var DEFAULT_WORKTREES_DIR_SEGMENT = DIRECTORIES.WORKTREES.replace(/\/+$/g, "");
16
- var DEFAULT_WU_ALLOWLIST_PREFIX = DIRECTORIES.WU_DIR;
17
- function normalizeDirectorySegment(value, fallback) {
18
- const normalized = value.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
19
- return normalized.length > 0 ? normalized : fallback;
20
- }
21
- function ensureTrailingSlash(value) {
22
- return value.endsWith("/") ? value : `${value}/`;
23
- }
24
- function generateEnforceWorktreeScript(options = {}) {
25
- const projectRoot = options.projectRoot ?? process.cwd();
26
- let worktreesDirSegment = DEFAULT_WORKTREES_DIR_SEGMENT;
27
- let wuAllowlistPrefix = `${DEFAULT_WU_ALLOWLIST_PREFIX}/`;
28
- try {
29
- const wuPaths = createWuPaths({ projectRoot });
30
- worktreesDirSegment = normalizeDirectorySegment(
31
- wuPaths.WORKTREES_DIR(),
32
- DEFAULT_WORKTREES_DIR_SEGMENT
33
- );
34
- wuAllowlistPrefix = ensureTrailingSlash(
35
- normalizeDirectorySegment(wuPaths.WU_DIR(), DEFAULT_WU_ALLOWLIST_PREFIX)
36
- );
37
- } catch {
38
- }
39
- return `#!/bin/bash
40
- #
41
- # enforce-worktree.sh (WU-1367, WU-1501)
42
- #
43
- # PreToolUse hook that blocks Write/Edit on main checkout.
44
- # WU-1501: Fail-closed - blocks even when no worktrees exist.
45
- # Graceful degradation only when LumenFlow is NOT configured.
46
- #
47
- # Allowlist: ${wuAllowlistPrefix}, .lumenflow/, .claude/, plan/
48
- # Branch-PR claimed_mode permits writes from main checkout.
49
- #
50
- # Exit codes:
51
- # 0 = Allow operation
52
- # 2 = Block operation (stderr shown to Claude as guidance)
53
- #
54
-
55
- set -euo pipefail
56
-
57
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
58
-
59
- # Graceful degradation: if we can't determine state, allow the operation
60
- graceful_allow() {
61
- local reason="$1"
62
- exit 0
63
- }
64
-
65
- # Derive repo paths from CLAUDE_PROJECT_DIR
66
- if [[ -z "\${CLAUDE_PROJECT_DIR:-}" ]]; then
67
- graceful_allow "CLAUDE_PROJECT_DIR not set"
68
- fi
69
-
70
- MAIN_REPO_PATH="$CLAUDE_PROJECT_DIR"
71
- WORKTREES_DIR="\${MAIN_REPO_PATH}/${worktreesDirSegment}"
72
- LUMENFLOW_DIR="\${MAIN_REPO_PATH}/.lumenflow"
73
-
74
- # Check if .lumenflow exists (LumenFlow is configured)
75
- if [[ ! -d "$LUMENFLOW_DIR" ]]; then
76
- graceful_allow "No .lumenflow directory (LumenFlow not configured)"
77
- fi
78
-
79
- # Read JSON input from stdin
80
- INPUT=$(cat)
81
-
82
- if [[ -z "$INPUT" ]]; then
83
- graceful_allow "No input provided"
84
- fi
85
-
86
- # Parse JSON with Python
87
- TMPFILE=$(mktemp)
88
- echo "$INPUT" > "$TMPFILE"
89
-
90
- PARSE_RESULT=$(python3 -c "
91
- import json
92
- import sys
93
- try:
94
- with open('$TMPFILE', 'r') as f:
95
- data = json.load(f)
96
- tool_name = data.get('tool_name', '')
97
- tool_input = data.get('tool_input', {})
98
- if not isinstance(tool_input, dict):
99
- tool_input = {}
100
- file_path = tool_input.get('file_path', '')
101
- print('OK')
102
- print(tool_name if tool_name else '')
103
- print(file_path if file_path else '')
104
- except Exception as e:
105
- print('ERROR')
106
- print(str(e))
107
- print('')
108
- " 2>&1)
109
-
110
- rm -f "$TMPFILE"
111
-
112
- # Parse the result
113
- PARSE_STATUS=$(echo "$PARSE_RESULT" | head -1)
114
- TOOL_NAME=$(echo "$PARSE_RESULT" | sed -n '2p')
115
- FILE_PATH=$(echo "$PARSE_RESULT" | sed -n '3p')
116
-
117
- if [[ "$PARSE_STATUS" != "OK" ]]; then
118
- graceful_allow "JSON parse failed"
119
- fi
120
-
121
- # Only process Write and Edit tools
122
- if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
123
- exit 0
124
- fi
125
-
126
- if [[ -z "$FILE_PATH" ]]; then
127
- graceful_allow "No file_path in input"
128
- fi
129
-
130
- # Canonicalize tool path before resolution (e.g., "~/" -> "$HOME/")
131
- CANONICAL_PATH="$FILE_PATH"
132
- if [[ "$CANONICAL_PATH" == "~" ]]; then
133
- if [[ -n "\${HOME:-}" ]]; then
134
- CANONICAL_PATH="$HOME"
135
- fi
136
- elif [[ "$CANONICAL_PATH" == "~/"* || "$CANONICAL_PATH" == "~\\\\"* ]]; then
137
- if [[ -n "\${HOME:-}" ]]; then
138
- CANONICAL_PATH="\${HOME}/\${CANONICAL_PATH:2}"
139
- fi
140
- fi
141
-
142
- # Resolve the canonicalized file path
143
- RESOLVED_PATH=$(realpath -m "$CANONICAL_PATH" 2>/dev/null || echo "$CANONICAL_PATH")
144
-
145
- # Allow if path is outside repo entirely
146
- if [[ "$RESOLVED_PATH" != "\${MAIN_REPO_PATH}/"* && "$RESOLVED_PATH" != "\${MAIN_REPO_PATH}" ]]; then
147
- exit 0
148
- fi
149
-
150
- # Allow if path is inside a worktree
151
- if [[ "$RESOLVED_PATH" == "\${WORKTREES_DIR}/"* ]]; then
152
- exit 0
153
- fi
154
-
155
- # Check if UnsafeAny active worktrees exist
156
- WORKTREE_COUNT=0
157
- if [[ -d "$WORKTREES_DIR" ]]; then
158
- WORKTREE_COUNT=$(find "$WORKTREES_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
159
- fi
160
-
161
- # If worktrees exist, block writes to main repo (original behavior)
162
- if [[ "$WORKTREE_COUNT" -gt 0 ]]; then
163
- ACTIVE_WORKTREES=$(find "$WORKTREES_DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\\n' 2>/dev/null | head -5 | tr '\\n' ', ' | sed 's/,$//')
164
-
165
- echo "" >&2
166
- echo "=== Worktree Enforcement ===" >&2
167
- echo "" >&2
168
- echo "BLOCKED: $TOOL_NAME to main repo" >&2
169
- echo "" >&2
170
- echo "Active worktrees: \${ACTIVE_WORKTREES:-none detected}" >&2
171
- echo "" >&2
172
- echo "USE INSTEAD:" >&2
173
- echo " 1. cd to your worktree: cd ${worktreesDirSegment}/<lane>-wu-<id>/" >&2
174
- echo " 2. Make your edits in the worktree" >&2
175
- echo "" >&2
176
- echo "See: LUMENFLOW.md for worktree discipline" >&2
177
- echo "==============================" >&2
178
- exit 2
179
- fi
180
-
181
- # WU-1501: Fail-closed on main when no active worktrees exist
182
- # Check allowlist: paths that are always safe to write on main
183
- RELATIVE_PATH="\${RESOLVED_PATH#\${MAIN_REPO_PATH}/}"
184
-
185
- case "$RELATIVE_PATH" in
186
- ${wuAllowlistPrefix}*) exit 0 ;; # WU YAML specs
187
- .lumenflow/*) exit 0 ;; # LumenFlow state/config
188
- .claude/*) exit 0 ;; # Claude Code config
189
- plan/*) exit 0 ;; # Plan/spec scaffolds
190
- esac
191
-
192
- # Check for branch-pr claimed_mode (allows main writes without worktree)
193
- STATE_FILE="\${LUMENFLOW_DIR}/state/wu-events.jsonl"
194
- if [[ -f "$STATE_FILE" ]]; then
195
- if grep -q '"claimed_mode":"branch-pr"' "$STATE_FILE" 2>/dev/null; then
196
- if grep -q '"status":"in_progress"' "$STATE_FILE" 2>/dev/null; then
197
- exit 0 # Branch-PR WU active - allow main writes
198
- fi
199
- fi
200
- fi
201
-
202
- # WU-1501: Fail-closed - no active claim context, block the write
203
- echo "" >&2
204
- echo "=== Worktree Enforcement ===" >&2
205
- echo "" >&2
206
- echo "BLOCKED: $TOOL_NAME on main (no active WU claim)" >&2
207
- echo "" >&2
208
- echo "No worktrees exist and no branch-pr WU is in progress." >&2
209
- echo "" >&2
210
- echo "WHAT TO DO:" >&2
211
- echo " 1. Claim a WU: pnpm wu:claim --id WU-XXXX --lane \\"<Lane>\\"" >&2
212
- echo " 2. cd ${worktreesDirSegment}/<lane>-wu-xxxx" >&2
213
- echo " 3. Make your edits in the worktree" >&2
214
- echo "" >&2
215
- echo "See: LUMENFLOW.md for worktree discipline" >&2
216
- echo "==============================" >&2
217
- exit 2
218
- `;
219
- }
220
-
221
- // src/hooks/generators/require-wu.ts
222
- function generateRequireWuScript() {
223
- return `#!/bin/bash
224
- #
225
- # require-wu.sh (WU-1367)
226
- #
227
- # PreToolUse hook that blocks Write/Edit when no WU is claimed.
228
- # Graceful degradation: allows operations if state cannot be determined.
229
- #
230
- # Exit codes:
231
- # 0 = Allow operation
232
- # 2 = Block operation (stderr shown to Claude as guidance)
233
- #
234
-
235
- set -euo pipefail
236
-
237
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
238
-
239
- # Graceful degradation
240
- graceful_allow() {
241
- local reason="$1"
242
- exit 0
243
- }
244
-
245
- if [[ -z "\${CLAUDE_PROJECT_DIR:-}" ]]; then
246
- graceful_allow "CLAUDE_PROJECT_DIR not set"
247
- fi
248
-
249
- MAIN_REPO_PATH="$CLAUDE_PROJECT_DIR"
250
- WORKTREES_DIR="\${MAIN_REPO_PATH}/worktrees"
251
- LUMENFLOW_DIR="\${MAIN_REPO_PATH}/.lumenflow"
252
- STATE_FILE="\${LUMENFLOW_DIR}/state/wu-events.jsonl"
253
-
254
- # Check if LumenFlow is configured
255
- if [[ ! -d "$LUMENFLOW_DIR" ]]; then
256
- graceful_allow "No .lumenflow directory"
257
- fi
258
-
259
- # Read JSON input
260
- INPUT=$(cat)
261
- if [[ -z "$INPUT" ]]; then
262
- graceful_allow "No input"
263
- fi
264
-
265
- # Parse JSON
266
- TMPFILE=$(mktemp)
267
- echo "$INPUT" > "$TMPFILE"
268
-
269
- PARSE_RESULT=$(python3 -c "
270
- import json
271
- try:
272
- with open('$TMPFILE', 'r') as f:
273
- data = json.load(f)
274
- tool_name = data.get('tool_name', '')
275
- tool_input = data.get('tool_input', {})
276
- if not isinstance(tool_input, dict):
277
- tool_input = {}
278
- file_path = tool_input.get('file_path', '')
279
- print('OK')
280
- print(tool_name if tool_name else '')
281
- print(file_path if file_path else '')
282
- except:
283
- print('ERROR')
284
- print('')
285
- print('')
286
- " 2>/dev/null || echo "ERROR")
287
-
288
- rm -f "$TMPFILE"
289
-
290
- # Parse result
291
- PARSE_STATUS=$(echo "$PARSE_RESULT" | head -1)
292
- TOOL_NAME=$(echo "$PARSE_RESULT" | sed -n '2p')
293
- FILE_PATH=$(echo "$PARSE_RESULT" | sed -n '3p')
294
-
295
- if [[ "$PARSE_STATUS" != "OK" ]]; then
296
- graceful_allow "JSON parse failed"
297
- fi
298
-
299
- # Only check Write and Edit
300
- if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
301
- exit 0
302
- fi
303
-
304
- if [[ -z "$FILE_PATH" ]]; then
305
- graceful_allow "No file_path in input"
306
- fi
307
-
308
- # Canonicalize tool path before resolution (e.g., "~/" -> "$HOME/")
309
- CANONICAL_PATH="$FILE_PATH"
310
- if [[ "$CANONICAL_PATH" == "~" ]]; then
311
- if [[ -n "\${HOME:-}" ]]; then
312
- CANONICAL_PATH="$HOME"
313
- fi
314
- elif [[ "$CANONICAL_PATH" == "~/"* || "$CANONICAL_PATH" == "~\\\\"* ]]; then
315
- if [[ -n "\${HOME:-}" ]]; then
316
- CANONICAL_PATH="\${HOME}/\${CANONICAL_PATH:2}"
317
- fi
318
- fi
319
-
320
- # Resolve the canonicalized file path
321
- RESOLVED_PATH=$(realpath -m "$CANONICAL_PATH" 2>/dev/null || echo "$CANONICAL_PATH")
322
-
323
- # Only enforce WU requirement for writes targeting this repository
324
- if [[ "$RESOLVED_PATH" != "\${MAIN_REPO_PATH}/"* && "$RESOLVED_PATH" != "\${MAIN_REPO_PATH}" ]]; then
325
- exit 0
326
- fi
327
-
328
- # Check for active worktrees (indicates claimed WU)
329
- if [[ -d "$WORKTREES_DIR" ]]; then
330
- WORKTREE_COUNT=$(find "$WORKTREES_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
331
- if [[ "$WORKTREE_COUNT" -gt 0 ]]; then
332
- exit 0 # Has worktrees = has claimed WU
333
- fi
334
- fi
335
-
336
- # Check state file for in_progress WUs
337
- if [[ -f "$STATE_FILE" ]]; then
338
- # Look for UnsafeAny WU with in_progress status
339
- if grep -q '"status":"in_progress"' "$STATE_FILE" 2>/dev/null; then
340
- exit 0 # Has in_progress WU
341
- fi
342
- fi
343
-
344
- # No claimed WU found
345
- echo "" >&2
346
- echo "=== WU Enforcement ===" >&2
347
- echo "" >&2
348
- echo "BLOCKED: $TOOL_NAME without claimed WU" >&2
349
- echo "" >&2
350
- echo "You must claim a WU before making edits:" >&2
351
- echo " pnpm wu:claim --id WU-XXXX --lane <Lane>" >&2
352
- echo " cd worktrees/<lane>-wu-xxxx" >&2
353
- echo "" >&2
354
- echo "Or create a new WU:" >&2
355
- echo " pnpm wu:create --lane <Lane> --title "Description"" >&2
356
- echo "" >&2
357
- echo "See: LUMENFLOW.md for workflow details" >&2
358
- echo "======================" >&2
359
- exit 2
360
- `;
361
- }
362
-
363
- // src/hooks/generators/warn-incomplete.ts
364
- function generateWarnIncompleteScript() {
365
- return `#!/bin/bash
366
- #
367
- # warn-incomplete.sh (WU-1367)
368
- #
369
- # Stop hook that warns when session ends without wu:done.
370
- # This is advisory only - never blocks session termination.
371
- #
372
- # Exit codes:
373
- # 0 = Always (warnings only)
374
- #
375
-
376
- SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
377
-
378
- if [[ -z "\${CLAUDE_PROJECT_DIR:-}" ]]; then
379
- exit 0
380
- fi
381
-
382
- MAIN_REPO_PATH="$CLAUDE_PROJECT_DIR"
383
- WORKTREES_DIR="\${MAIN_REPO_PATH}/worktrees"
384
-
385
- # Check for active worktrees
386
- if [[ ! -d "$WORKTREES_DIR" ]]; then
387
- exit 0
388
- fi
389
-
390
- WORKTREE_COUNT=$(find "$WORKTREES_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
391
- if [[ "$WORKTREE_COUNT" -eq 0 ]]; then
392
- exit 0
393
- fi
394
-
395
- # Get active worktree names
396
- ACTIVE_WORKTREES=$(find "$WORKTREES_DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\\n' 2>/dev/null | head -5 | tr '\\n' ', ' | sed 's/,$//')
397
-
398
- echo "" >&2
399
- echo "=== Session Completion Reminder ===" >&2
400
- echo "" >&2
401
- echo "You have active worktrees: $ACTIVE_WORKTREES" >&2
402
- echo "" >&2
403
- echo "If your work is complete, remember to run:" >&2
404
- echo " pnpm wu:prep --id WU-XXXX (from worktree)" >&2
405
- echo " pnpm wu:done --id WU-XXXX (from main)" >&2
406
- echo "" >&2
407
- echo "If work is incomplete, it will be preserved in the worktree." >&2
408
- echo "====================================" >&2
409
-
410
- exit 0
411
- `;
412
- }
413
-
414
- // src/hooks/generators/session-start-recovery.ts
415
- function generateSessionStartRecoveryScript() {
416
- return `#!/bin/bash
417
- #
418
- # session-start-recovery.sh
419
- #
420
- # SessionStart hook - check for pending recovery and inject context (WU-1390)
421
- #
422
- # Fires after session start (on compact, resume, or clear) to:
423
- # 1. Check for recovery-pending-*.md files written by pre-compact-checkpoint.sh
424
- # 2. Display the recovery context to the agent
425
- # 3. Remove the recovery file (one-time recovery)
426
- #
427
- # This completes the durable recovery pattern:
428
- # PreCompact writes file \u2192 SessionStart reads and deletes it
429
- #
430
- # Exit codes:
431
- # 0 = Always allow (informational hook)
432
- #
433
-
434
- set -euo pipefail
435
-
436
- # Derive repo paths from CLAUDE_PROJECT_DIR
437
- if [[ -n "\${CLAUDE_PROJECT_DIR:-}" ]]; then
438
- REPO_PATH="$CLAUDE_PROJECT_DIR"
439
- else
440
- REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
441
- if [[ -z "$REPO_PATH" ]]; then
442
- exit 0
443
- fi
444
- fi
445
-
446
- RECOVERY_DIR="\${REPO_PATH}/.lumenflow/state"
447
-
448
- # Check if recovery directory exists
449
- if [[ ! -d "$RECOVERY_DIR" ]]; then
450
- exit 0
451
- fi
452
-
453
- # Find UnsafeAny pending recovery files
454
- FOUND_RECOVERY=false
455
-
456
- for recovery_file in "$RECOVERY_DIR"/recovery-pending-*.md; do
457
- # Check if glob matched UnsafeAny files (bash glob returns literal pattern if no match)
458
- [[ -f "$recovery_file" ]] || continue
459
-
460
- FOUND_RECOVERY=true
461
-
462
- # Extract WU ID from filename for display
463
- WU_ID=$(basename "$recovery_file" | sed 's/recovery-pending-\\(.*\\)\\.md/\\1/')
464
-
465
- echo "" >&2
466
- echo "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550" >&2
467
- echo "\u26A0\uFE0F POST-COMPACTION RECOVERY DETECTED" >&2
468
- echo "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550" >&2
469
- echo "" >&2
470
-
471
- # Display the recovery context
472
- cat "$recovery_file" >&2
473
-
474
- echo "" >&2
475
- echo "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550" >&2
476
- echo "" >&2
477
-
478
- # Remove after displaying (one-time recovery)
479
- rm -f "$recovery_file"
480
- done
481
-
482
- # Additional context if recovery was displayed
483
- if [[ "$FOUND_RECOVERY" == "true" ]]; then
484
- echo "IMPORTANT: Your context was compacted. Review the recovery info above." >&2
485
- echo "Recommended: Run 'pnpm wu:brief --id $WU_ID --client \${LUMENFLOW_CLIENT:-claude-code}' for fresh full context." >&2
486
- echo "" >&2
487
- fi
488
-
489
- # WU-1473: Surface unread coordination signals for non-worktree orchestrators
490
- # Even without recovery files, agents benefit from seeing recent inbox activity
491
- # This supports orchestrators running from main checkout (not in a worktree)
492
- pnpm mem:inbox --since 1h --unread-only --quiet 2>/dev/null >&2 || true
493
-
494
- exit 0
495
- `;
496
- }
497
-
498
- // src/hooks/generators/signal-utils.ts
499
- async function surfaceUnreadSignals(baseDir) {
500
- try {
501
- const signals = await loadSignals(baseDir, { unreadOnly: true });
502
- return { count: signals.length, signals };
503
- } catch {
504
- return { count: 0, signals: [] };
505
- }
506
- }
507
- async function markCompletedWUSignalsAsRead(baseDir, wuId) {
508
- try {
509
- const signals = await loadSignals(baseDir, { wuId, unreadOnly: true });
510
- if (signals.length === 0) {
511
- return { markedCount: 0 };
512
- }
513
- const signalIds = signals.map((sig) => sig.id);
514
- return await markSignalsAsRead(baseDir, signalIds);
515
- } catch {
516
- return { markedCount: 0 };
517
- }
518
- }
519
-
520
- // src/hooks/enforcement-generator.ts
521
- var HOOK_SCRIPTS = CLAUDE_HOOKS.SCRIPTS;
522
- function generateEnforcementHooks(config) {
523
- const hooks = {};
524
- const preToolUseHooks = [];
525
- if (config.block_outside_worktree || config.require_wu_for_edits) {
526
- const writeEditHooks = [];
527
- if (config.block_outside_worktree) {
528
- writeEditHooks.push({
529
- type: "command",
530
- command: getHookCommand(HOOK_SCRIPTS.ENFORCE_WORKTREE)
531
- });
532
- }
533
- if (config.require_wu_for_edits) {
534
- writeEditHooks.push({
535
- type: "command",
536
- command: getHookCommand(HOOK_SCRIPTS.REQUIRE_WU)
537
- });
538
- }
539
- if (writeEditHooks.length > 0) {
540
- preToolUseHooks.push({
541
- matcher: CLAUDE_HOOKS.MATCHERS.WRITE_EDIT,
542
- hooks: writeEditHooks
543
- });
544
- }
545
- }
546
- if (preToolUseHooks.length > 0) {
547
- hooks.preToolUse = preToolUseHooks;
548
- }
549
- if (config.warn_on_stop_without_wu_done) {
550
- hooks.stop = [
551
- {
552
- matcher: CLAUDE_HOOKS.MATCHERS.ALL,
553
- hooks: [
554
- {
555
- type: "command",
556
- command: getHookCommand(HOOK_SCRIPTS.WARN_INCOMPLETE)
557
- }
558
- ]
559
- }
560
- ];
561
- }
562
- const postToolUseHooks = [];
563
- if (config.auto_checkpoint?.enabled) {
564
- const autoCheckpointHook = {
565
- type: "command",
566
- command: getHookCommand(HOOK_SCRIPTS.AUTO_CHECKPOINT)
567
- };
568
- postToolUseHooks.push({
569
- matcher: CLAUDE_HOOKS.MATCHERS.ALL,
570
- hooks: [autoCheckpointHook]
571
- });
572
- hooks.subagentStop = [
573
- {
574
- matcher: CLAUDE_HOOKS.MATCHERS.SUBAGENT_STOP,
575
- hooks: [autoCheckpointHook]
576
- }
577
- ];
578
- }
579
- if (postToolUseHooks.length > 0) {
580
- hooks.postToolUse = postToolUseHooks;
581
- }
582
- hooks.preCompact = [
583
- {
584
- matcher: CLAUDE_HOOKS.MATCHERS.ALL,
585
- hooks: [
586
- {
587
- type: "command",
588
- command: getHookCommand(HOOK_SCRIPTS.PRE_COMPACT_CHECKPOINT)
589
- }
590
- ]
591
- }
592
- ];
593
- hooks.sessionStart = [
594
- {
595
- matcher: CLAUDE_HOOKS.MATCHERS.COMPACT,
596
- hooks: [
597
- {
598
- type: "command",
599
- command: getHookCommand(HOOK_SCRIPTS.SESSION_START_RECOVERY)
600
- }
601
- ]
602
- },
603
- {
604
- matcher: CLAUDE_HOOKS.MATCHERS.RESUME,
605
- hooks: [
606
- {
607
- type: "command",
608
- command: getHookCommand(HOOK_SCRIPTS.SESSION_START_RECOVERY)
609
- }
610
- ]
611
- },
612
- {
613
- matcher: CLAUDE_HOOKS.MATCHERS.CLEAR,
614
- hooks: [
615
- {
616
- type: "command",
617
- command: getHookCommand(HOOK_SCRIPTS.SESSION_START_RECOVERY)
618
- }
619
- ]
620
- }
621
- ];
622
- return hooks;
623
- }
624
-
625
- export {
626
- generateEnforceWorktreeScript,
627
- generateRequireWuScript,
628
- generateWarnIncompleteScript,
629
- generateSessionStartRecoveryScript,
630
- surfaceUnreadSignals,
631
- markCompletedWUSignalsAsRead,
632
- generateEnforcementHooks
633
- };