@lumenflow/core 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.
Files changed (263) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +119 -0
  3. package/dist/active-wu-detector.d.ts +33 -0
  4. package/dist/active-wu-detector.js +106 -0
  5. package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
  6. package/dist/adapters/filesystem-metrics.adapter.js +519 -0
  7. package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
  8. package/dist/adapters/terminal-renderer.adapter.js +337 -0
  9. package/dist/arg-parser.d.ts +63 -0
  10. package/dist/arg-parser.js +560 -0
  11. package/dist/backlog-editor.d.ts +98 -0
  12. package/dist/backlog-editor.js +179 -0
  13. package/dist/backlog-generator.d.ts +111 -0
  14. package/dist/backlog-generator.js +381 -0
  15. package/dist/backlog-parser.d.ts +45 -0
  16. package/dist/backlog-parser.js +102 -0
  17. package/dist/backlog-sync-validator.d.ts +78 -0
  18. package/dist/backlog-sync-validator.js +294 -0
  19. package/dist/branch-drift.d.ts +34 -0
  20. package/dist/branch-drift.js +51 -0
  21. package/dist/cleanup-install-config.d.ts +33 -0
  22. package/dist/cleanup-install-config.js +37 -0
  23. package/dist/cleanup-lock.d.ts +139 -0
  24. package/dist/cleanup-lock.js +313 -0
  25. package/dist/code-path-validator.d.ts +146 -0
  26. package/dist/code-path-validator.js +537 -0
  27. package/dist/code-paths-overlap.d.ts +55 -0
  28. package/dist/code-paths-overlap.js +245 -0
  29. package/dist/commands-logger.d.ts +77 -0
  30. package/dist/commands-logger.js +254 -0
  31. package/dist/commit-message-utils.d.ts +25 -0
  32. package/dist/commit-message-utils.js +41 -0
  33. package/dist/compliance-parser.d.ts +150 -0
  34. package/dist/compliance-parser.js +507 -0
  35. package/dist/constants/backlog-patterns.d.ts +20 -0
  36. package/dist/constants/backlog-patterns.js +23 -0
  37. package/dist/constants/dora-constants.d.ts +49 -0
  38. package/dist/constants/dora-constants.js +53 -0
  39. package/dist/constants/gate-constants.d.ts +15 -0
  40. package/dist/constants/gate-constants.js +15 -0
  41. package/dist/constants/linter-constants.d.ts +16 -0
  42. package/dist/constants/linter-constants.js +16 -0
  43. package/dist/constants/tokenizer-constants.d.ts +15 -0
  44. package/dist/constants/tokenizer-constants.js +15 -0
  45. package/dist/core/scope-checker.d.ts +97 -0
  46. package/dist/core/scope-checker.js +163 -0
  47. package/dist/core/tool-runner.d.ts +161 -0
  48. package/dist/core/tool-runner.js +393 -0
  49. package/dist/core/tool.constants.d.ts +105 -0
  50. package/dist/core/tool.constants.js +101 -0
  51. package/dist/core/tool.schemas.d.ts +226 -0
  52. package/dist/core/tool.schemas.js +226 -0
  53. package/dist/core/worktree-guard.d.ts +130 -0
  54. package/dist/core/worktree-guard.js +242 -0
  55. package/dist/coverage-gate.d.ts +108 -0
  56. package/dist/coverage-gate.js +196 -0
  57. package/dist/date-utils.d.ts +75 -0
  58. package/dist/date-utils.js +140 -0
  59. package/dist/dependency-graph.d.ts +142 -0
  60. package/dist/dependency-graph.js +550 -0
  61. package/dist/dependency-guard.d.ts +54 -0
  62. package/dist/dependency-guard.js +142 -0
  63. package/dist/dependency-validator.d.ts +105 -0
  64. package/dist/dependency-validator.js +154 -0
  65. package/dist/docs-path-validator.d.ts +36 -0
  66. package/dist/docs-path-validator.js +95 -0
  67. package/dist/domain/orchestration.constants.d.ts +99 -0
  68. package/dist/domain/orchestration.constants.js +97 -0
  69. package/dist/domain/orchestration.schemas.d.ts +280 -0
  70. package/dist/domain/orchestration.schemas.js +211 -0
  71. package/dist/domain/orchestration.types.d.ts +133 -0
  72. package/dist/domain/orchestration.types.js +12 -0
  73. package/dist/error-handler.d.ts +116 -0
  74. package/dist/error-handler.js +136 -0
  75. package/dist/file-classifiers.d.ts +62 -0
  76. package/dist/file-classifiers.js +108 -0
  77. package/dist/gates-agent-mode.d.ts +81 -0
  78. package/dist/gates-agent-mode.js +94 -0
  79. package/dist/generate-traceability.d.ts +107 -0
  80. package/dist/generate-traceability.js +411 -0
  81. package/dist/git-adapter.d.ts +395 -0
  82. package/dist/git-adapter.js +649 -0
  83. package/dist/git-staged-validator.d.ts +32 -0
  84. package/dist/git-staged-validator.js +48 -0
  85. package/dist/hardcoded-strings.d.ts +61 -0
  86. package/dist/hardcoded-strings.js +270 -0
  87. package/dist/incremental-lint.d.ts +78 -0
  88. package/dist/incremental-lint.js +129 -0
  89. package/dist/incremental-test.d.ts +39 -0
  90. package/dist/incremental-test.js +61 -0
  91. package/dist/index.d.ts +42 -0
  92. package/dist/index.js +61 -0
  93. package/dist/invariants/check-automated-tests.d.ts +50 -0
  94. package/dist/invariants/check-automated-tests.js +166 -0
  95. package/dist/invariants-runner.d.ts +103 -0
  96. package/dist/invariants-runner.js +527 -0
  97. package/dist/lane-checker.d.ts +50 -0
  98. package/dist/lane-checker.js +319 -0
  99. package/dist/lane-inference.d.ts +39 -0
  100. package/dist/lane-inference.js +195 -0
  101. package/dist/lane-lock.d.ts +211 -0
  102. package/dist/lane-lock.js +474 -0
  103. package/dist/lane-validator.d.ts +48 -0
  104. package/dist/lane-validator.js +114 -0
  105. package/dist/logs-lib.d.ts +104 -0
  106. package/dist/logs-lib.js +207 -0
  107. package/dist/lumenflow-config-schema.d.ts +272 -0
  108. package/dist/lumenflow-config-schema.js +207 -0
  109. package/dist/lumenflow-config.d.ts +95 -0
  110. package/dist/lumenflow-config.js +236 -0
  111. package/dist/manual-test-validator.d.ts +80 -0
  112. package/dist/manual-test-validator.js +200 -0
  113. package/dist/merge-lock.d.ts +115 -0
  114. package/dist/merge-lock.js +251 -0
  115. package/dist/micro-worktree.d.ts +159 -0
  116. package/dist/micro-worktree.js +427 -0
  117. package/dist/migration-deployer.d.ts +69 -0
  118. package/dist/migration-deployer.js +151 -0
  119. package/dist/orchestration-advisory-loader.d.ts +28 -0
  120. package/dist/orchestration-advisory-loader.js +87 -0
  121. package/dist/orchestration-advisory.d.ts +58 -0
  122. package/dist/orchestration-advisory.js +94 -0
  123. package/dist/orchestration-di.d.ts +48 -0
  124. package/dist/orchestration-di.js +57 -0
  125. package/dist/orchestration-rules.d.ts +57 -0
  126. package/dist/orchestration-rules.js +201 -0
  127. package/dist/orphan-detector.d.ts +131 -0
  128. package/dist/orphan-detector.js +226 -0
  129. package/dist/path-classifiers.d.ts +57 -0
  130. package/dist/path-classifiers.js +93 -0
  131. package/dist/piped-command-detector.d.ts +34 -0
  132. package/dist/piped-command-detector.js +64 -0
  133. package/dist/ports/dashboard-renderer.port.d.ts +112 -0
  134. package/dist/ports/dashboard-renderer.port.js +25 -0
  135. package/dist/ports/metrics-collector.port.d.ts +132 -0
  136. package/dist/ports/metrics-collector.port.js +26 -0
  137. package/dist/process-detector.d.ts +84 -0
  138. package/dist/process-detector.js +172 -0
  139. package/dist/prompt-linter.d.ts +72 -0
  140. package/dist/prompt-linter.js +312 -0
  141. package/dist/prompt-monitor.d.ts +15 -0
  142. package/dist/prompt-monitor.js +205 -0
  143. package/dist/rebase-artifact-cleanup.d.ts +145 -0
  144. package/dist/rebase-artifact-cleanup.js +433 -0
  145. package/dist/retry-strategy.d.ts +189 -0
  146. package/dist/retry-strategy.js +283 -0
  147. package/dist/risk-detector.d.ts +108 -0
  148. package/dist/risk-detector.js +252 -0
  149. package/dist/rollback-utils.d.ts +76 -0
  150. package/dist/rollback-utils.js +104 -0
  151. package/dist/section-headings.d.ts +43 -0
  152. package/dist/section-headings.js +49 -0
  153. package/dist/spawn-escalation.d.ts +90 -0
  154. package/dist/spawn-escalation.js +253 -0
  155. package/dist/spawn-monitor.d.ts +229 -0
  156. package/dist/spawn-monitor.js +672 -0
  157. package/dist/spawn-recovery.d.ts +82 -0
  158. package/dist/spawn-recovery.js +298 -0
  159. package/dist/spawn-registry-schema.d.ts +98 -0
  160. package/dist/spawn-registry-schema.js +108 -0
  161. package/dist/spawn-registry-store.d.ts +146 -0
  162. package/dist/spawn-registry-store.js +273 -0
  163. package/dist/spawn-tree.d.ts +121 -0
  164. package/dist/spawn-tree.js +285 -0
  165. package/dist/stamp-status-validator.d.ts +84 -0
  166. package/dist/stamp-status-validator.js +134 -0
  167. package/dist/stamp-utils.d.ts +100 -0
  168. package/dist/stamp-utils.js +229 -0
  169. package/dist/state-machine.d.ts +26 -0
  170. package/dist/state-machine.js +83 -0
  171. package/dist/system-map-validator.d.ts +80 -0
  172. package/dist/system-map-validator.js +272 -0
  173. package/dist/telemetry.d.ts +80 -0
  174. package/dist/telemetry.js +213 -0
  175. package/dist/token-counter.d.ts +51 -0
  176. package/dist/token-counter.js +145 -0
  177. package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
  178. package/dist/usecases/get-dashboard-data.usecase.js +61 -0
  179. package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
  180. package/dist/usecases/get-suggestions.usecase.js +153 -0
  181. package/dist/user-normalizer.d.ts +41 -0
  182. package/dist/user-normalizer.js +141 -0
  183. package/dist/validators/phi-constants.d.ts +97 -0
  184. package/dist/validators/phi-constants.js +152 -0
  185. package/dist/validators/phi-scanner.d.ts +58 -0
  186. package/dist/validators/phi-scanner.js +215 -0
  187. package/dist/worktree-ownership.d.ts +50 -0
  188. package/dist/worktree-ownership.js +74 -0
  189. package/dist/worktree-scanner.d.ts +103 -0
  190. package/dist/worktree-scanner.js +168 -0
  191. package/dist/worktree-symlink.d.ts +99 -0
  192. package/dist/worktree-symlink.js +359 -0
  193. package/dist/wu-backlog-updater.d.ts +17 -0
  194. package/dist/wu-backlog-updater.js +37 -0
  195. package/dist/wu-checkpoint.d.ts +124 -0
  196. package/dist/wu-checkpoint.js +233 -0
  197. package/dist/wu-claim-helpers.d.ts +26 -0
  198. package/dist/wu-claim-helpers.js +63 -0
  199. package/dist/wu-claim-resume.d.ts +106 -0
  200. package/dist/wu-claim-resume.js +276 -0
  201. package/dist/wu-consistency-checker.d.ts +95 -0
  202. package/dist/wu-consistency-checker.js +567 -0
  203. package/dist/wu-constants.d.ts +1275 -0
  204. package/dist/wu-constants.js +1382 -0
  205. package/dist/wu-create-validators.d.ts +42 -0
  206. package/dist/wu-create-validators.js +93 -0
  207. package/dist/wu-done-branch-only.d.ts +63 -0
  208. package/dist/wu-done-branch-only.js +191 -0
  209. package/dist/wu-done-messages.d.ts +119 -0
  210. package/dist/wu-done-messages.js +185 -0
  211. package/dist/wu-done-pr.d.ts +72 -0
  212. package/dist/wu-done-pr.js +174 -0
  213. package/dist/wu-done-retry-helpers.d.ts +85 -0
  214. package/dist/wu-done-retry-helpers.js +172 -0
  215. package/dist/wu-done-ui.d.ts +37 -0
  216. package/dist/wu-done-ui.js +69 -0
  217. package/dist/wu-done-validators.d.ts +411 -0
  218. package/dist/wu-done-validators.js +1229 -0
  219. package/dist/wu-done-worktree.d.ts +182 -0
  220. package/dist/wu-done-worktree.js +1097 -0
  221. package/dist/wu-helpers.d.ts +128 -0
  222. package/dist/wu-helpers.js +248 -0
  223. package/dist/wu-lint.d.ts +70 -0
  224. package/dist/wu-lint.js +234 -0
  225. package/dist/wu-paths.d.ts +171 -0
  226. package/dist/wu-paths.js +178 -0
  227. package/dist/wu-preflight-validators.d.ts +86 -0
  228. package/dist/wu-preflight-validators.js +251 -0
  229. package/dist/wu-recovery.d.ts +138 -0
  230. package/dist/wu-recovery.js +341 -0
  231. package/dist/wu-repair-core.d.ts +131 -0
  232. package/dist/wu-repair-core.js +669 -0
  233. package/dist/wu-schema-normalization.d.ts +17 -0
  234. package/dist/wu-schema-normalization.js +82 -0
  235. package/dist/wu-schema.d.ts +793 -0
  236. package/dist/wu-schema.js +881 -0
  237. package/dist/wu-spawn-helpers.d.ts +121 -0
  238. package/dist/wu-spawn-helpers.js +271 -0
  239. package/dist/wu-spawn.d.ts +158 -0
  240. package/dist/wu-spawn.js +1306 -0
  241. package/dist/wu-state-schema.d.ts +213 -0
  242. package/dist/wu-state-schema.js +156 -0
  243. package/dist/wu-state-store.d.ts +264 -0
  244. package/dist/wu-state-store.js +691 -0
  245. package/dist/wu-status-transition.d.ts +63 -0
  246. package/dist/wu-status-transition.js +382 -0
  247. package/dist/wu-status-updater.d.ts +25 -0
  248. package/dist/wu-status-updater.js +116 -0
  249. package/dist/wu-transaction-collectors.d.ts +116 -0
  250. package/dist/wu-transaction-collectors.js +272 -0
  251. package/dist/wu-transaction.d.ts +170 -0
  252. package/dist/wu-transaction.js +273 -0
  253. package/dist/wu-validation-constants.d.ts +60 -0
  254. package/dist/wu-validation-constants.js +66 -0
  255. package/dist/wu-validation.d.ts +118 -0
  256. package/dist/wu-validation.js +243 -0
  257. package/dist/wu-validator.d.ts +62 -0
  258. package/dist/wu-validator.js +325 -0
  259. package/dist/wu-yaml-fixer.d.ts +97 -0
  260. package/dist/wu-yaml-fixer.js +264 -0
  261. package/dist/wu-yaml.d.ts +86 -0
  262. package/dist/wu-yaml.js +222 -0
  263. package/package.json +114 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * WU Status Transition Module
3
+ *
4
+ * Shared logic for block/unblock status transitions.
5
+ * Eliminates 90% code duplication between wu-block.mjs and wu-unblock.mjs.
6
+ *
7
+ * Responsibilities:
8
+ * - Validate state transitions (via state-machine)
9
+ * - Update WU YAML status and notes
10
+ * - Sync backlog.md and status.md
11
+ * - Handle worktree creation/removal
12
+ *
13
+ * Created: WU-1340 (2025-11-29)
14
+ */
15
+ /**
16
+ * Direction type for status transitions
17
+ * @typedef {'block' | 'unblock'} TransitionDirection
18
+ */
19
+ /**
20
+ * Transition WU status between blocked and in_progress states
21
+ *
22
+ * @param {object} options - Transition options
23
+ * @param {string} options.id - WU ID (e.g., 'WU-100')
24
+ * @param {TransitionDirection} options.direction - Transition direction: 'block' or 'unblock'
25
+ * @param {string} [options.reason] - Reason for transition (optional)
26
+ * @param {string} [options.worktreeOverride] - Custom worktree path (optional)
27
+ * @param {boolean} [options.removeWorktree] - Remove worktree after blocking (default: false)
28
+ * @param {boolean} [options.createWorktree] - Create worktree after unblocking (default: false)
29
+ * @param {object} [options.gitAdapter] - Git adapter for testing (optional, defaults to getGitForCwd())
30
+ * @returns {{id: string, fromStatus: string, toStatus: string}} Transition result
31
+ * @throws {Error} If state transition is invalid or files not found
32
+ *
33
+ * @example
34
+ * // Block a WU
35
+ * transitionWUStatus({
36
+ * id: 'WU-100',
37
+ * direction: 'block',
38
+ * reason: 'Blocked by WU-200',
39
+ * removeWorktree: true
40
+ * });
41
+ *
42
+ * @example
43
+ * // Unblock a WU
44
+ * transitionWUStatus({
45
+ * id: 'WU-100',
46
+ * direction: 'unblock',
47
+ * reason: 'Blocker resolved',
48
+ * createWorktree: true
49
+ * });
50
+ */
51
+ export declare function transitionWUStatus({ id, direction, reason, worktreeOverride, removeWorktree, createWorktree, gitAdapter, }: {
52
+ id: any;
53
+ direction: any;
54
+ reason: any;
55
+ worktreeOverride: any;
56
+ removeWorktree?: boolean;
57
+ createWorktree?: boolean;
58
+ gitAdapter: any;
59
+ }): Promise<{
60
+ id: any;
61
+ fromStatus: any;
62
+ toStatus: string;
63
+ }>;
@@ -0,0 +1,382 @@
1
+ /**
2
+ * WU Status Transition Module
3
+ *
4
+ * Shared logic for block/unblock status transitions.
5
+ * Eliminates 90% code duplication between wu-block.mjs and wu-unblock.mjs.
6
+ *
7
+ * Responsibilities:
8
+ * - Validate state transitions (via state-machine)
9
+ * - Update WU YAML status and notes
10
+ * - Sync backlog.md and status.md
11
+ * - Handle worktree creation/removal
12
+ *
13
+ * Created: WU-1340 (2025-11-29)
14
+ */
15
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
16
+ import { assertTransition } from './state-machine.js';
17
+ // WU-1574: Removed BacklogManager - using state store + generator
18
+ import { WUStateStore } from './wu-state-store.js';
19
+ import { generateBacklog } from './backlog-generator.js';
20
+ import { writeFile } from 'node:fs/promises';
21
+ import { parseBacklogFrontmatter, getSectionHeadings } from './backlog-parser.js';
22
+ import { createError, ErrorCodes } from './error-handler.js';
23
+ import { todayISO } from './date-utils.js';
24
+ import { getStateStoreDirFromBacklog, WU_PATHS } from './wu-paths.js';
25
+ import { readWU, writeWU, appendNote } from './wu-yaml.js';
26
+ import { toKebab, REMOTES, BRANCHES, WU_STATUS, STRING_LITERALS, } from './wu-constants.js';
27
+ /**
28
+ * Direction type for status transitions
29
+ * @typedef {'block' | 'unblock'} TransitionDirection
30
+ */
31
+ /**
32
+ * Transition WU status between blocked and in_progress states
33
+ *
34
+ * @param {object} options - Transition options
35
+ * @param {string} options.id - WU ID (e.g., 'WU-100')
36
+ * @param {TransitionDirection} options.direction - Transition direction: 'block' or 'unblock'
37
+ * @param {string} [options.reason] - Reason for transition (optional)
38
+ * @param {string} [options.worktreeOverride] - Custom worktree path (optional)
39
+ * @param {boolean} [options.removeWorktree] - Remove worktree after blocking (default: false)
40
+ * @param {boolean} [options.createWorktree] - Create worktree after unblocking (default: false)
41
+ * @param {object} [options.gitAdapter] - Git adapter for testing (optional, defaults to getGitForCwd())
42
+ * @returns {{id: string, fromStatus: string, toStatus: string}} Transition result
43
+ * @throws {Error} If state transition is invalid or files not found
44
+ *
45
+ * @example
46
+ * // Block a WU
47
+ * transitionWUStatus({
48
+ * id: 'WU-100',
49
+ * direction: 'block',
50
+ * reason: 'Blocked by WU-200',
51
+ * removeWorktree: true
52
+ * });
53
+ *
54
+ * @example
55
+ * // Unblock a WU
56
+ * transitionWUStatus({
57
+ * id: 'WU-100',
58
+ * direction: 'unblock',
59
+ * reason: 'Blocker resolved',
60
+ * createWorktree: true
61
+ * });
62
+ */
63
+ // WU-1574: Made async for updateBacklogAndStatus
64
+ export async function transitionWUStatus({ id, direction, reason, worktreeOverride, removeWorktree = false, createWorktree = false, gitAdapter, }) {
65
+ // Validate inputs
66
+ if (!id) {
67
+ throw createError(ErrorCodes.VALIDATION_ERROR, 'WU ID is required');
68
+ }
69
+ if (!direction || !['block', 'unblock'].includes(direction)) {
70
+ throw createError(ErrorCodes.VALIDATION_ERROR, `Invalid direction: ${direction}. Must be 'block' or 'unblock'`);
71
+ }
72
+ // Resolve paths
73
+ const paths = {
74
+ wu: WU_PATHS.WU(id),
75
+ status: WU_PATHS.STATUS(),
76
+ backlog: WU_PATHS.BACKLOG(),
77
+ };
78
+ // Validate files exist
79
+ if (!existsSync(paths.wu)) {
80
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${paths.wu}`, {
81
+ path: paths.wu,
82
+ id,
83
+ });
84
+ }
85
+ if (!existsSync(paths.backlog)) {
86
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Missing ${paths.backlog}`, {
87
+ path: paths.backlog,
88
+ });
89
+ }
90
+ if (!existsSync(paths.status)) {
91
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Missing ${paths.status}`, {
92
+ path: paths.status,
93
+ });
94
+ }
95
+ // Read WU document
96
+ const doc = readWU(paths.wu, id);
97
+ const title = doc.title || '';
98
+ const currentStatus = doc.status || WU_STATUS.IN_PROGRESS;
99
+ // Determine target status
100
+ const toStatus = direction === 'block' ? WU_STATUS.BLOCKED : WU_STATUS.IN_PROGRESS;
101
+ // Validate state transition (may throw if invalid)
102
+ try {
103
+ assertTransition(currentStatus, toStatus, id);
104
+ }
105
+ catch (error) {
106
+ // If already in target state, make operation idempotent (don't throw)
107
+ if (currentStatus === toStatus) {
108
+ console.warn(`[wu-status-transition] WU ${id} already ${toStatus}, skipping transition (idempotent)`);
109
+ return { id, fromStatus: currentStatus, toStatus };
110
+ }
111
+ // Re-throw validation errors
112
+ throw createError(ErrorCodes.STATE_ERROR, `State transition validation failed: ${error.message}`, { id, fromStatus: currentStatus, toStatus, originalError: error.message });
113
+ }
114
+ // Update WU YAML
115
+ doc.status = toStatus;
116
+ const noteLine = createNoteEntry(direction, reason);
117
+ appendNote(doc, noteLine);
118
+ writeWU(paths.wu, doc);
119
+ // Update backlog.md and status.md (WU-1574: now async)
120
+ await updateBacklogAndStatus(paths, id, title, currentStatus, toStatus, direction, reason);
121
+ // Handle worktree operations (only if gitAdapter provided)
122
+ if (gitAdapter) {
123
+ if (direction === 'block' && removeWorktree) {
124
+ handleWorktreeRemoval(doc, worktreeOverride, gitAdapter);
125
+ }
126
+ else if (direction === 'unblock' && createWorktree) {
127
+ handleWorktreeCreation(doc, worktreeOverride, gitAdapter);
128
+ }
129
+ }
130
+ return {
131
+ id,
132
+ fromStatus: currentStatus,
133
+ toStatus,
134
+ };
135
+ }
136
+ /**
137
+ * Create note entry for transition
138
+ *
139
+ * @private
140
+ * @param {TransitionDirection} direction - Transition direction
141
+ * @param {string} [reason] - Transition reason
142
+ * @returns {string} Note text
143
+ */
144
+ function createNoteEntry(direction, reason) {
145
+ const action = direction === 'block' ? 'Blocked' : 'Unblocked';
146
+ const date = todayISO();
147
+ return reason ? `${action} (${date}): ${reason}` : `${action} (${date})`;
148
+ }
149
+ /**
150
+ * Update backlog.md and status.md files
151
+ *
152
+ * @private
153
+ * @param {object} paths - File paths
154
+ * @param {string} id - WU ID
155
+ * @param {string} title - WU title
156
+ * @param {string} fromStatus - Current status
157
+ * @param {string} toStatus - Target status
158
+ * @param {TransitionDirection} direction - Transition direction
159
+ * @param {string} [reason] - Transition reason
160
+ */
161
+ // WU-1574: Made async for generateBacklog
162
+ async function updateBacklogAndStatus(paths, id, title, fromStatus, toStatus, direction, reason) {
163
+ // Parse frontmatter to get section headings
164
+ let frontmatter;
165
+ try {
166
+ ({ frontmatter } = parseBacklogFrontmatter(paths.backlog));
167
+ }
168
+ catch (err) {
169
+ throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse backlog frontmatter: ${err.message}`, { path: paths.backlog, originalError: err.message });
170
+ }
171
+ const headings = frontmatter ? getSectionHeadings(frontmatter) : {};
172
+ const inProgressHeading = headings.in_progress || '## 🔧 In progress';
173
+ const blockedHeading = headings.blocked || '## ⛔ Blocked';
174
+ // Determine source and target sections
175
+ let fromSection, toSection, format;
176
+ if (direction === 'block') {
177
+ fromSection = inProgressHeading;
178
+ toSection = blockedHeading;
179
+ format = 'blocked';
180
+ }
181
+ else {
182
+ fromSection = blockedHeading;
183
+ toSection = inProgressHeading;
184
+ format = 'progress';
185
+ }
186
+ // WU-1574: Regenerate backlog.md from state store (replaces BacklogManager)
187
+ const stateDir = getStateStoreDirFromBacklog(paths.backlog);
188
+ const store = new WUStateStore(stateDir);
189
+ await store.load();
190
+ const content = await generateBacklog(store);
191
+ await writeFile(paths.backlog, content, 'utf-8');
192
+ // Update status.md
193
+ updateStatusFile(paths.status, id, title, direction, reason);
194
+ }
195
+ /**
196
+ * Update status.md file
197
+ *
198
+ * @private
199
+ * @param {string} statusPath - Path to status.md
200
+ * @param {string} id - WU ID
201
+ * @param {string} title - WU title
202
+ * @param {TransitionDirection} direction - Transition direction
203
+ * @param {string} [reason] - Transition reason
204
+ */
205
+ function updateStatusFile(statusPath, id, title, direction, reason) {
206
+ const rel = `wu/${id}.yaml`;
207
+ const lines = readFileSync(statusPath, { encoding: 'utf-8' }).split(/\r?\n/);
208
+ const findHeader = (h) => lines.findIndex((l) => l.trim().toLowerCase() === h.toLowerCase());
209
+ const inProgIdx = findHeader('## in progress');
210
+ const blockedIdx = findHeader('## blocked');
211
+ if (direction === 'block') {
212
+ // Remove from In Progress
213
+ removeFromSection(lines, inProgIdx, rel, id);
214
+ // Add to Blocked
215
+ if (blockedIdx !== -1) {
216
+ const reasonSuffix = reason ? ` — ${reason}` : '';
217
+ const bullet = `- [${id} — ${title}](${rel})${reasonSuffix}`;
218
+ const sectionStart = blockedIdx + 1;
219
+ // Check if already exists (idempotent)
220
+ if (!lines.slice(sectionStart).some((l) => l.includes(rel))) {
221
+ lines.splice(sectionStart, 0, '', bullet);
222
+ }
223
+ }
224
+ }
225
+ else {
226
+ // Remove from Blocked
227
+ removeFromSection(lines, blockedIdx, rel, id);
228
+ // Add to In Progress
229
+ if (inProgIdx !== -1) {
230
+ const bullet = `- [${id} — ${title}](${rel})`;
231
+ const sectionStart = inProgIdx + 1;
232
+ // Remove "No items" placeholder if present
233
+ let endIdx = lines.slice(sectionStart).findIndex((l) => l.startsWith('## '));
234
+ if (endIdx === -1)
235
+ endIdx = lines.length - sectionStart;
236
+ else
237
+ endIdx = sectionStart + endIdx;
238
+ for (let i = sectionStart; i < endIdx; i++) {
239
+ if (lines[i] && lines[i].includes('No items currently in progress')) {
240
+ lines.splice(i, 1);
241
+ endIdx--;
242
+ break;
243
+ }
244
+ }
245
+ // Check if already exists (idempotent)
246
+ if (!lines.slice(sectionStart, endIdx).some((l) => l.includes(rel))) {
247
+ lines.splice(sectionStart, 0, '', bullet);
248
+ }
249
+ }
250
+ }
251
+ writeFileSync(statusPath, lines.join(STRING_LITERALS.NEWLINE), { encoding: 'utf-8' });
252
+ }
253
+ /**
254
+ * Remove WU entry from section in status.md
255
+ *
256
+ * @private
257
+ * @param {string[]} lines - File lines
258
+ * @param {number} sectionIdx - Section header index
259
+ * @param {string} rel - Relative WU path
260
+ * @param {string} id - WU ID
261
+ */
262
+ function removeFromSection(lines, sectionIdx, rel, id) {
263
+ if (sectionIdx === -1)
264
+ return;
265
+ let i = sectionIdx + 1;
266
+ while (i < lines.length) {
267
+ if (lines[i].startsWith('## '))
268
+ break;
269
+ if (lines[i].includes(rel) || lines[i].includes(`[${id}`)) {
270
+ lines.splice(i, 1);
271
+ continue;
272
+ }
273
+ i++;
274
+ }
275
+ }
276
+ /**
277
+ * Handle worktree removal after blocking
278
+ *
279
+ * @private
280
+ * @param {object} doc - WU document
281
+ * @param {string} [worktreeOverride] - Custom worktree path
282
+ * @param {object} gitAdapter - Git adapter
283
+ */
284
+ function handleWorktreeRemoval(doc, worktreeOverride, gitAdapter) {
285
+ const wt = worktreeOverride || defaultWorktreeFrom(doc);
286
+ if (wt && existsSync(wt)) {
287
+ try {
288
+ gitAdapter.removeWorktree(wt);
289
+ }
290
+ catch (e) {
291
+ console.warn(`[wu-status-transition] Could not remove worktree ${wt}: ${e.message}`);
292
+ }
293
+ }
294
+ else if (wt) {
295
+ console.warn('[wu-status-transition] Worktree path not found; skipping removal');
296
+ }
297
+ else {
298
+ console.warn('[wu-status-transition] No worktree path specified; skipping removal');
299
+ }
300
+ }
301
+ /**
302
+ * Handle worktree creation after unblocking
303
+ *
304
+ * @private
305
+ * @param {object} doc - WU document
306
+ * @param {string} [worktreeOverride] - Custom worktree path
307
+ * @param {object} gitAdapter - Git adapter
308
+ */
309
+ function handleWorktreeCreation(doc, worktreeOverride, gitAdapter) {
310
+ const worktreePath = worktreeOverride || defaultWorktreeFrom(doc);
311
+ const branchName = defaultBranchFrom(doc);
312
+ if (!branchName) {
313
+ console.warn('[wu-status-transition] Cannot derive branch name; skipping worktree creation');
314
+ return;
315
+ }
316
+ if (!worktreePath) {
317
+ console.warn('[wu-status-transition] Worktree path required; skipping creation');
318
+ return;
319
+ }
320
+ if (existsSync(worktreePath)) {
321
+ console.warn(`[wu-status-transition] Worktree ${worktreePath} already exists; skipping creation`);
322
+ return;
323
+ }
324
+ gitAdapter.run(`git fetch ${REMOTES.ORIGIN} ${BRANCHES.MAIN}`);
325
+ if (branchExists(branchName, gitAdapter)) {
326
+ gitAdapter.run(`git worktree add ${JSON.stringify(worktreePath)} ${JSON.stringify(branchName)}`);
327
+ }
328
+ else {
329
+ gitAdapter.run(`git worktree add ${JSON.stringify(worktreePath)} -b ${JSON.stringify(branchName)} ${REMOTES.ORIGIN}/${BRANCHES.MAIN}`);
330
+ }
331
+ }
332
+ /**
333
+ * Derive default worktree path from WU document
334
+ *
335
+ * @private
336
+ * @param {object} doc - WU document
337
+ * @returns {string | null} Worktree path
338
+ */
339
+ function defaultWorktreeFrom(doc) {
340
+ const lane = (doc.lane || '').toString();
341
+ const laneK = lane
342
+ .trim()
343
+ .toLowerCase()
344
+ .replace(/[^a-z0-9]+/g, '-')
345
+ .replace(/^-+|-+$/g, '');
346
+ const idK = (doc.id || '').toLowerCase();
347
+ if (!laneK || !idK)
348
+ return null;
349
+ return `worktrees/${laneK}-${idK}`;
350
+ }
351
+ /**
352
+ * Derive default branch name from WU document
353
+ *
354
+ * @private
355
+ * @param {object} doc - WU document
356
+ * @returns {string | null} Branch name
357
+ */
358
+ function defaultBranchFrom(doc) {
359
+ const lane = (doc.lane || '').toString();
360
+ const laneK = toKebab(lane);
361
+ const idK = (doc.id || '').toLowerCase();
362
+ if (!laneK || !idK)
363
+ return null;
364
+ return `lane/${laneK}/${idK}`;
365
+ }
366
+ /**
367
+ * Check if git branch exists
368
+ *
369
+ * @private
370
+ * @param {string} branch - Branch name
371
+ * @param {object} gitAdapter - Git adapter
372
+ * @returns {boolean} True if branch exists
373
+ */
374
+ function branchExists(branch, gitAdapter) {
375
+ try {
376
+ gitAdapter.run(`git rev-parse --verify ${JSON.stringify(branch)}`);
377
+ return true;
378
+ }
379
+ catch {
380
+ return false;
381
+ }
382
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Status.md Update Utilities
3
+ *
4
+ * Centralized status.md update functions (extracted from wu-done.mjs)
5
+ * Refactored to use frontmatter-based section headings (no magic strings)
6
+ *
7
+ * Used by both main wu:done flow AND recovery mode (DRY principle)
8
+ */
9
+ /**
10
+ * Remove WU from In Progress section (idempotent)
11
+ * Refactored from wu-done.mjs line 471 to use frontmatter headings
12
+ *
13
+ * @param {string} statusPath - Path to status.md
14
+ * @param {string} id - WU ID
15
+ */
16
+ export declare function updateStatusRemoveInProgress(statusPath: any, id: any): void;
17
+ /**
18
+ * Add WU to Completed section (idempotent - checks for duplicates)
19
+ * Refactored from wu-done.mjs line 499 to use frontmatter headings
20
+ *
21
+ * @param {string} statusPath - Path to status.md
22
+ * @param {string} id - WU ID
23
+ * @param {string} title - WU title
24
+ */
25
+ export declare function addToStatusCompleted(statusPath: any, id: any, title: any): void;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Status.md Update Utilities
3
+ *
4
+ * Centralized status.md update functions (extracted from wu-done.mjs)
5
+ * Refactored to use frontmatter-based section headings (no magic strings)
6
+ *
7
+ * Used by both main wu:done flow AND recovery mode (DRY principle)
8
+ */
9
+ /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
10
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
11
+ import { parseBacklogFrontmatter } from './backlog-parser.js';
12
+ import { getSectionHeadingsWithDefaults } from './section-headings.js';
13
+ import { todayISO } from './date-utils.js';
14
+ import { die, createError, ErrorCodes } from './error-handler.js';
15
+ import { STRING_LITERALS } from './wu-constants.js';
16
+ /**
17
+ * Remove WU from In Progress section (idempotent)
18
+ * Refactored from wu-done.mjs line 471 to use frontmatter headings
19
+ *
20
+ * @param {string} statusPath - Path to status.md
21
+ * @param {string} id - WU ID
22
+ */
23
+ export function updateStatusRemoveInProgress(statusPath, id) {
24
+ if (!existsSync(statusPath)) {
25
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Status file not found: ${statusPath}`, {
26
+ path: statusPath,
27
+ function: 'updateStatusRemoveInProgress',
28
+ });
29
+ }
30
+ // Use frontmatter parser to get configured section headings
31
+ const { frontmatter, markdown } = parseBacklogFrontmatter(statusPath);
32
+ const headings = getSectionHeadingsWithDefaults(frontmatter, 'status');
33
+ const rel = `wu/${id}.yaml`;
34
+ const lines = markdown.split(STRING_LITERALS.NEWLINE);
35
+ // Find In Progress section using configured heading
36
+ const startIdx = lines.findIndex((l) => l.trim() === headings.in_progress);
37
+ if (startIdx === -1) {
38
+ throw createError(ErrorCodes.SECTION_NOT_FOUND, `Could not find "${headings.in_progress}" section in ${statusPath}`, { path: statusPath, section: headings.in_progress, function: 'updateStatusRemoveInProgress' });
39
+ }
40
+ // Find section boundaries
41
+ let endIdx = lines.slice(startIdx + 1).findIndex((l) => l.startsWith('## '));
42
+ endIdx = endIdx === -1 ? lines.length - startIdx - 1 : startIdx + 1 + endIdx;
43
+ // Remove WU entry (idempotent - safe to call if already removed)
44
+ let removed = false;
45
+ for (let i = startIdx + 1; i < endIdx; i++) {
46
+ if (lines[i] && (lines[i].includes(rel) || lines[i].includes(`[${id}`))) {
47
+ lines.splice(i, 1);
48
+ removed = true;
49
+ endIdx--;
50
+ i--; // Adjust index after splice
51
+ }
52
+ }
53
+ // Add placeholder if section is now empty
54
+ if (removed) {
55
+ const section = lines.slice(startIdx + 1, endIdx).filter((l) => l.trim() !== '');
56
+ if (section.length === 0) {
57
+ lines.splice(endIdx, 0, '', '(No items currently in progress)', '');
58
+ }
59
+ }
60
+ // Reconstruct file with frontmatter preservation
61
+ const raw = readFileSync(statusPath, { encoding: 'utf-8' });
62
+ const frontmatterMatch = raw.match(/^---\n[\s\S]*?\n---\n/);
63
+ const frontmatterText = frontmatterMatch ? frontmatterMatch[0] : '';
64
+ writeFileSync(statusPath, frontmatterText + lines.join(STRING_LITERALS.NEWLINE), {
65
+ encoding: 'utf-8',
66
+ });
67
+ }
68
+ /**
69
+ * Add WU to Completed section (idempotent - checks for duplicates)
70
+ * Refactored from wu-done.mjs line 499 to use frontmatter headings
71
+ *
72
+ * @param {string} statusPath - Path to status.md
73
+ * @param {string} id - WU ID
74
+ * @param {string} title - WU title
75
+ */
76
+ export function addToStatusCompleted(statusPath, id, title) {
77
+ if (!existsSync(statusPath)) {
78
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Status file not found: ${statusPath}`, {
79
+ path: statusPath,
80
+ function: 'addToStatusCompleted',
81
+ });
82
+ }
83
+ // Use frontmatter parser to get configured section headings
84
+ const { frontmatter, markdown } = parseBacklogFrontmatter(statusPath);
85
+ const headings = getSectionHeadingsWithDefaults(frontmatter, 'status');
86
+ const rel = `wu/${id}.yaml`;
87
+ const date = todayISO();
88
+ const completedEntry = `- [${id} — ${title}](${rel}) — ${date}`;
89
+ const lines = markdown.split(STRING_LITERALS.NEWLINE);
90
+ // Find Completed section using configured heading
91
+ const completedIdx = lines.findIndex((l) => l.trim() === headings.completed);
92
+ if (completedIdx === -1) {
93
+ die(`Could not find "${headings.completed}" section in ${statusPath}`);
94
+ }
95
+ // Idempotent check: skip if already in Completed section
96
+ const nextSectionIdx = lines.slice(completedIdx + 1).findIndex((l) => l.startsWith('## '));
97
+ const completedEndIdx = nextSectionIdx === -1 ? lines.length : completedIdx + 1 + nextSectionIdx;
98
+ const completedSection = lines.slice(completedIdx, completedEndIdx).join(STRING_LITERALS.NEWLINE);
99
+ if (completedSection.includes(`[${id}`)) {
100
+ console.log(`[wu-status-updater] ${id} already in Completed section (idempotent skip)`);
101
+ return;
102
+ }
103
+ // Insert at top of Completed section (after header, skipping empty lines)
104
+ let insertIdx = completedIdx + 1;
105
+ while (insertIdx < lines.length && lines[insertIdx].trim() === '') {
106
+ insertIdx++;
107
+ }
108
+ lines.splice(insertIdx, 0, completedEntry);
109
+ // Reconstruct file with frontmatter preservation
110
+ const raw = readFileSync(statusPath, { encoding: 'utf-8' });
111
+ const frontmatterMatch = raw.match(/^---\n[\s\S]*?\n---\n/);
112
+ const frontmatterText = frontmatterMatch ? frontmatterMatch[0] : '';
113
+ writeFileSync(statusPath, frontmatterText + lines.join(STRING_LITERALS.NEWLINE), {
114
+ encoding: 'utf-8',
115
+ });
116
+ }