@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,116 @@
1
+ /**
2
+ * WU Transaction Collectors - Compute file content for atomic writes
3
+ *
4
+ * WU-1369: These functions compute the new file content WITHOUT writing.
5
+ * This allows collection of all changes in memory before atomic commit.
6
+ *
7
+ * Each function returns:
8
+ * - The new file content (string)
9
+ * - Does NOT write to disk
10
+ *
11
+ * Usage:
12
+ * ```js
13
+ * const tx = new WUTransaction(id);
14
+ *
15
+ * // Collect all content
16
+ * tx.addWrite(wuPath, computeWUYAMLContent(doc), 'WU YAML');
17
+ * tx.addWrite(statusPath, computeStatusContent(statusPath, id, title), 'status.md');
18
+ * tx.addWrite(backlogPath, computeBacklogContent(backlogPath, id, title), 'backlog.md');
19
+ * tx.addWrite(stampPath, computeStampContent(id, title), 'stamp');
20
+ *
21
+ * // Validate and commit
22
+ * tx.commit();
23
+ * ```
24
+ */
25
+ /**
26
+ * Compute WU YAML content for done state
27
+ *
28
+ * Updates the document in-place and returns the YAML string.
29
+ * Does NOT write to disk.
30
+ *
31
+ * @param {object} doc - WU YAML document (will be mutated)
32
+ * @returns {string} YAML content string
33
+ */
34
+ export declare function computeWUYAMLContent(doc: any): string;
35
+ /**
36
+ * Compute updated status.md content
37
+ *
38
+ * Removes WU from In Progress section and adds to Completed section.
39
+ * Returns the new file content without writing.
40
+ *
41
+ * @param {string} statusPath - Path to status.md
42
+ * @param {string} id - WU ID
43
+ * @param {string} title - WU title
44
+ * @returns {string} New status.md content
45
+ * @throws {Error} If file not found or section not found
46
+ */
47
+ export declare function computeStatusContent(statusPath: any, id: any, title: any): string;
48
+ export declare function computeWUEventsContentAfterComplete(backlogPath: any, wuId: any): Promise<{
49
+ eventsPath: string;
50
+ content: string;
51
+ }>;
52
+ /**
53
+ * Compute updated backlog.md content
54
+ * WU-1574: Simplified to generate from state store
55
+ *
56
+ * @param {string} backlogPath - Path to backlog.md
57
+ * @param {string} id - WU ID to mark complete
58
+ * @param {string} _title - WU title (unused - state store has it)
59
+ * @returns {Promise<string>} New backlog.md content
60
+ */
61
+ export declare function computeBacklogContent(backlogPath: any, id: any, _title: any): Promise<string>;
62
+ /**
63
+ * Compute stamp file content
64
+ *
65
+ * @param {string} id - WU ID
66
+ * @param {string} title - WU title
67
+ * @returns {string} Stamp content
68
+ */
69
+ export declare function computeStampContent(id: any, title: any): string;
70
+ /**
71
+ * Collect all metadata updates for a transaction
72
+ * WU-1574: Made async for computeBacklogContent
73
+ *
74
+ * Convenience function that computes all file contents at once.
75
+ * Returns an object with all computed content.
76
+ *
77
+ * @param {object} params - Parameters
78
+ * @param {object} params.doc - WU YAML document (will be mutated)
79
+ * @param {string} params.id - WU ID
80
+ * @param {string} params.title - WU title
81
+ * @param {string} params.wuPath - Path to WU YAML
82
+ * @param {string} params.statusPath - Path to status.md
83
+ * @param {string} params.backlogPath - Path to backlog.md
84
+ * @param {string} params.stampPath - Path to stamp file
85
+ * @returns {Promise<object>} Object with content for each file
86
+ */
87
+ export declare function collectMetadataUpdates({ doc, id, title, wuPath, statusPath, backlogPath, stampPath, }: {
88
+ doc: any;
89
+ id: any;
90
+ title: any;
91
+ wuPath: any;
92
+ statusPath: any;
93
+ backlogPath: any;
94
+ stampPath: any;
95
+ }): Promise<{
96
+ wuYAML: {
97
+ path: any;
98
+ content: string;
99
+ description: string;
100
+ };
101
+ status: {
102
+ path: any;
103
+ content: string;
104
+ description: string;
105
+ };
106
+ backlog: {
107
+ path: any;
108
+ content: string;
109
+ description: string;
110
+ };
111
+ stamp: {
112
+ path: any;
113
+ content: string;
114
+ description: string;
115
+ };
116
+ }>;
@@ -0,0 +1,272 @@
1
+ /**
2
+ * WU Transaction Collectors - Compute file content for atomic writes
3
+ *
4
+ * WU-1369: These functions compute the new file content WITHOUT writing.
5
+ * This allows collection of all changes in memory before atomic commit.
6
+ *
7
+ * Each function returns:
8
+ * - The new file content (string)
9
+ * - Does NOT write to disk
10
+ *
11
+ * Usage:
12
+ * ```js
13
+ * const tx = new WUTransaction(id);
14
+ *
15
+ * // Collect all content
16
+ * tx.addWrite(wuPath, computeWUYAMLContent(doc), 'WU YAML');
17
+ * tx.addWrite(statusPath, computeStatusContent(statusPath, id, title), 'status.md');
18
+ * tx.addWrite(backlogPath, computeBacklogContent(backlogPath, id, title), 'backlog.md');
19
+ * tx.addWrite(stampPath, computeStampContent(id, title), 'stamp');
20
+ *
21
+ * // Validate and commit
22
+ * tx.commit();
23
+ * ```
24
+ */
25
+ /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
26
+ import { existsSync, readFileSync } from 'node:fs';
27
+ import path from 'node:path';
28
+ import { stringifyYAML } from './wu-yaml.js';
29
+ import { parseBacklogFrontmatter } from './backlog-parser.js';
30
+ import { getSectionHeadingsWithDefaults } from './section-headings.js';
31
+ import { todayISO } from './date-utils.js';
32
+ import { createError, ErrorCodes } from './error-handler.js';
33
+ import { STRING_LITERALS } from './wu-constants.js';
34
+ // WU-1574: BacklogManager removed - using state store + generator
35
+ import { WUStateStore, WU_EVENTS_FILE_NAME } from './wu-state-store.js';
36
+ import { generateBacklog } from './backlog-generator.js';
37
+ // WU-1734: Import proper path resolution utility
38
+ import { getStateStoreDirFromBacklog } from './wu-paths.js';
39
+ /**
40
+ * Compute WU YAML content for done state
41
+ *
42
+ * Updates the document in-place and returns the YAML string.
43
+ * Does NOT write to disk.
44
+ *
45
+ * @param {object} doc - WU YAML document (will be mutated)
46
+ * @returns {string} YAML content string
47
+ */
48
+ export function computeWUYAMLContent(doc) {
49
+ // Apply done state updates
50
+ doc.status = 'done';
51
+ doc.locked = true;
52
+ doc.completed_at = new Date().toISOString();
53
+ // Serialize to YAML
54
+ return stringifyYAML(doc);
55
+ }
56
+ /**
57
+ * Find section in lines array
58
+ * @param {string[]} lines - Array of lines
59
+ * @param {string} heading - Section heading to find
60
+ * @returns {number} Index of section, or -1 if not found
61
+ */
62
+ function findSection(lines, heading) {
63
+ return lines.findIndex((l) => l.trim() === heading);
64
+ }
65
+ /**
66
+ * Find end of section (next ## heading or end of file)
67
+ * @param {string[]} lines - Array of lines
68
+ * @param {number} startIdx - Start index of section
69
+ * @returns {number} End index of section
70
+ */
71
+ function findSectionEnd(lines, startIdx) {
72
+ const nextHeadingIdx = lines.slice(startIdx + 1).findIndex((l) => l.startsWith('## '));
73
+ return nextHeadingIdx === -1 ? lines.length : startIdx + 1 + nextHeadingIdx;
74
+ }
75
+ /**
76
+ * Remove WU entry from a section
77
+ * @param {string[]} lines - Array of lines (mutated)
78
+ * @param {number} startIdx - Start index of section
79
+ * @param {number} endIdx - End index of section
80
+ * @param {string} rel - Relative path to WU file
81
+ * @param {string} id - WU ID
82
+ * @returns {{ removed: boolean, newEndIdx: number }}
83
+ */
84
+ function removeWUFromSection(lines, startIdx, endIdx, rel, id) {
85
+ let removed = false;
86
+ let newEndIdx = endIdx;
87
+ for (let i = startIdx + 1; i < newEndIdx; i++) {
88
+ if (lines[i] && (lines[i].includes(rel) || lines[i].includes(`[${id}`))) {
89
+ lines.splice(i, 1);
90
+ removed = true;
91
+ newEndIdx--;
92
+ i--;
93
+ }
94
+ }
95
+ return { removed, newEndIdx };
96
+ }
97
+ /**
98
+ * Insert completed entry into Completed section
99
+ * @param {string[]} lines - Array of lines (mutated)
100
+ * @param {number} completedIdx - Index of Completed section
101
+ * @param {string} entry - Entry to insert
102
+ * @param {string} id - WU ID
103
+ * @param {number} sectionEndIdx - End index of Completed section
104
+ */
105
+ function insertIntoCompleted(lines, completedIdx, entry, id, sectionEndIdx) {
106
+ const completedSection = lines.slice(completedIdx, sectionEndIdx).join(STRING_LITERALS.NEWLINE);
107
+ if (completedSection.includes(`[${id}`)) {
108
+ return; // Already present (idempotent)
109
+ }
110
+ let insertIdx = completedIdx + 1;
111
+ while (insertIdx < lines.length && lines[insertIdx].trim() === '') {
112
+ insertIdx++;
113
+ }
114
+ lines.splice(insertIdx, 0, entry);
115
+ }
116
+ /**
117
+ * Compute updated status.md content
118
+ *
119
+ * Removes WU from In Progress section and adds to Completed section.
120
+ * Returns the new file content without writing.
121
+ *
122
+ * @param {string} statusPath - Path to status.md
123
+ * @param {string} id - WU ID
124
+ * @param {string} title - WU title
125
+ * @returns {string} New status.md content
126
+ * @throws {Error} If file not found or section not found
127
+ */
128
+ export function computeStatusContent(statusPath, id, title) {
129
+ if (!existsSync(statusPath)) {
130
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Status file not found: ${statusPath}`, {
131
+ path: statusPath,
132
+ function: 'computeStatusContent',
133
+ });
134
+ }
135
+ const { frontmatter, markdown } = parseBacklogFrontmatter(statusPath);
136
+ const headings = getSectionHeadingsWithDefaults(frontmatter, 'status');
137
+ const rel = `wu/${id}.yaml`;
138
+ const completedEntry = `- [${id} — ${title}](${rel}) — ${todayISO()}`;
139
+ const lines = markdown.split(/\r?\n/);
140
+ // Find and process In Progress section
141
+ const inProgressIdx = findSection(lines, headings.in_progress);
142
+ if (inProgressIdx === -1) {
143
+ throw createError(ErrorCodes.SECTION_NOT_FOUND, `Could not find "${headings.in_progress}" section in ${statusPath}`, { path: statusPath, section: headings.in_progress, function: 'computeStatusContent' });
144
+ }
145
+ let inProgressEndIdx = findSectionEnd(lines, inProgressIdx);
146
+ const { removed, newEndIdx } = removeWUFromSection(lines, inProgressIdx, inProgressEndIdx, rel, id);
147
+ inProgressEndIdx = newEndIdx;
148
+ // Add placeholder if section is now empty after removal
149
+ if (removed) {
150
+ const sectionContent = lines
151
+ .slice(inProgressIdx + 1, inProgressEndIdx)
152
+ .filter((l) => l.trim() !== '');
153
+ if (sectionContent.length === 0) {
154
+ lines.splice(inProgressEndIdx, 0, '', '(No items currently in progress)', '');
155
+ }
156
+ }
157
+ // Find and process Completed section
158
+ const completedIdx = findSection(lines, headings.completed);
159
+ if (completedIdx === -1) {
160
+ throw createError(ErrorCodes.SECTION_NOT_FOUND, `Could not find "${headings.completed}" section in ${statusPath}`, { path: statusPath, section: headings.completed, function: 'computeStatusContent' });
161
+ }
162
+ const completedEndIdx = findSectionEnd(lines, completedIdx);
163
+ insertIntoCompleted(lines, completedIdx, completedEntry, id, completedEndIdx);
164
+ // Reconstruct with frontmatter
165
+ const raw = readFileSync(statusPath, { encoding: 'utf-8' });
166
+ const frontmatterMatch = raw.match(/^---\n[\s\S]*?\n---\n/);
167
+ const frontmatterText = frontmatterMatch ? frontmatterMatch[0] : '';
168
+ return frontmatterText + lines.join(STRING_LITERALS.NEWLINE);
169
+ }
170
+ function ensureTrailingNewline(content) {
171
+ if (content === '')
172
+ return content;
173
+ if (content.endsWith(STRING_LITERALS.NEWLINE))
174
+ return content;
175
+ return content + STRING_LITERALS.NEWLINE;
176
+ }
177
+ async function computeCompletionUpdatesFromStateStore(backlogPath, wuId) {
178
+ const stateDir = getStateStoreDirFromBacklog(backlogPath);
179
+ const store = new WUStateStore(stateDir);
180
+ await store.load();
181
+ const current = store.getWUState(wuId);
182
+ if (!current) {
183
+ throw new Error(`WU ${wuId} is not in_progress`);
184
+ }
185
+ if (current.status === 'done') {
186
+ return { store, stateDir, shouldAppendCompleteEvent: false, completeEvent: null };
187
+ }
188
+ if (current.status !== 'in_progress') {
189
+ throw new Error(`WU ${wuId} is not in_progress`);
190
+ }
191
+ const completeEvent = store.createCompleteEvent(wuId);
192
+ store.applyEvent(completeEvent);
193
+ return { store, stateDir, shouldAppendCompleteEvent: true, completeEvent };
194
+ }
195
+ export async function computeWUEventsContentAfterComplete(backlogPath, wuId) {
196
+ const { stateDir, shouldAppendCompleteEvent, completeEvent } = await computeCompletionUpdatesFromStateStore(backlogPath, wuId);
197
+ if (!shouldAppendCompleteEvent) {
198
+ return null;
199
+ }
200
+ const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
201
+ const existing = existsSync(eventsPath) ? readFileSync(eventsPath, { encoding: 'utf-8' }) : '';
202
+ const withNewline = ensureTrailingNewline(existing);
203
+ return {
204
+ eventsPath,
205
+ content: withNewline + JSON.stringify(completeEvent) + STRING_LITERALS.NEWLINE,
206
+ };
207
+ }
208
+ /**
209
+ * Compute updated backlog.md content
210
+ * WU-1574: Simplified to generate from state store
211
+ *
212
+ * @param {string} backlogPath - Path to backlog.md
213
+ * @param {string} id - WU ID to mark complete
214
+ * @param {string} _title - WU title (unused - state store has it)
215
+ * @returns {Promise<string>} New backlog.md content
216
+ */
217
+ export async function computeBacklogContent(backlogPath, id, _title) {
218
+ const { store } = await computeCompletionUpdatesFromStateStore(backlogPath, id);
219
+ return generateBacklog(store);
220
+ }
221
+ /**
222
+ * Compute stamp file content
223
+ *
224
+ * @param {string} id - WU ID
225
+ * @param {string} title - WU title
226
+ * @returns {string} Stamp content
227
+ */
228
+ export function computeStampContent(id, title) {
229
+ const timestamp = todayISO();
230
+ return `WU ${id} — ${title}\nCompleted: ${timestamp}\n`;
231
+ }
232
+ /**
233
+ * Collect all metadata updates for a transaction
234
+ * WU-1574: Made async for computeBacklogContent
235
+ *
236
+ * Convenience function that computes all file contents at once.
237
+ * Returns an object with all computed content.
238
+ *
239
+ * @param {object} params - Parameters
240
+ * @param {object} params.doc - WU YAML document (will be mutated)
241
+ * @param {string} params.id - WU ID
242
+ * @param {string} params.title - WU title
243
+ * @param {string} params.wuPath - Path to WU YAML
244
+ * @param {string} params.statusPath - Path to status.md
245
+ * @param {string} params.backlogPath - Path to backlog.md
246
+ * @param {string} params.stampPath - Path to stamp file
247
+ * @returns {Promise<object>} Object with content for each file
248
+ */
249
+ export async function collectMetadataUpdates({ doc, id, title, wuPath, statusPath, backlogPath, stampPath, }) {
250
+ return {
251
+ wuYAML: {
252
+ path: wuPath,
253
+ content: computeWUYAMLContent(doc),
254
+ description: 'WU YAML',
255
+ },
256
+ status: {
257
+ path: statusPath,
258
+ content: computeStatusContent(statusPath, id, title),
259
+ description: 'status.md',
260
+ },
261
+ backlog: {
262
+ path: backlogPath,
263
+ content: await computeBacklogContent(backlogPath, id, title),
264
+ description: 'backlog.md',
265
+ },
266
+ stamp: {
267
+ path: stampPath,
268
+ content: computeStampContent(id, title),
269
+ description: 'completion stamp',
270
+ },
271
+ };
272
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * WU Transaction - Atomic write operations for wu:done
3
+ *
4
+ * WU-1369: Implements transactional pattern for metadata updates.
5
+ * Collects all changes in memory, validates, then writes atomically.
6
+ *
7
+ * Pattern:
8
+ * 1. Create transaction
9
+ * 2. Collect all pending writes (in memory)
10
+ * 3. Validate pending state
11
+ * 4. Commit (write all files) or abort (discard)
12
+ *
13
+ * This ensures no partial state on validation failure:
14
+ * - If validation fails → no files written
15
+ * - If any write fails → error with cleanup info
16
+ */
17
+ /**
18
+ * Represents a pending file write operation
19
+ */
20
+ interface PendingWrite {
21
+ /** Absolute or relative file path */
22
+ path: string;
23
+ /** File content to write */
24
+ content: string;
25
+ /** Human-readable description (e.g., "WU YAML", "status.md") */
26
+ description: string;
27
+ }
28
+ /**
29
+ * Transaction for atomic metadata updates
30
+ *
31
+ * Usage:
32
+ * ```js
33
+ * const tx = new WUTransaction(id);
34
+ *
35
+ * // Collect pending changes
36
+ * tx.addWrite(wuPath, yamlContent, 'WU YAML');
37
+ * tx.addWrite(statusPath, statusContent, 'status.md');
38
+ *
39
+ * // Validate (no writes happen yet)
40
+ * const validation = tx.validate();
41
+ * if (!validation.valid) {
42
+ * tx.abort();
43
+ * return;
44
+ * }
45
+ *
46
+ * // Commit all changes atomically
47
+ * const result = tx.commit();
48
+ * ```
49
+ */
50
+ export declare class WUTransaction {
51
+ private readonly wuId;
52
+ private readonly pendingWrites;
53
+ private committed;
54
+ private aborted;
55
+ private readonly createdAt;
56
+ /**
57
+ * Create a new transaction
58
+ * @param {string} wuId - WU ID for logging context
59
+ */
60
+ constructor(wuId: string);
61
+ /**
62
+ * Add a pending file write operation
63
+ *
64
+ * @param {string} filePath - File path to write
65
+ * @param {string} content - Content to write
66
+ * @param {string} description - Human-readable description
67
+ * @throws {Error} If transaction already committed or aborted
68
+ */
69
+ addWrite(filePath: string, content: string, description: string): void;
70
+ /**
71
+ * Get pending writes for inspection
72
+ * @returns {PendingWrite[]}
73
+ */
74
+ getPendingWrites(): PendingWrite[];
75
+ /**
76
+ * Get count of pending writes
77
+ * @returns {number}
78
+ */
79
+ get size(): number;
80
+ /**
81
+ * Check if transaction has been committed
82
+ * @returns {boolean}
83
+ */
84
+ get isCommitted(): boolean;
85
+ /**
86
+ * Check if transaction has been aborted
87
+ * @returns {boolean}
88
+ */
89
+ get isAborted(): boolean;
90
+ /**
91
+ * Validate pending writes (pre-commit checks)
92
+ *
93
+ * Checks:
94
+ * - All parent directories can be created
95
+ * - No duplicate paths with different content
96
+ * - Content is valid (non-empty for critical files)
97
+ *
98
+ * @returns {{ valid: boolean, errors: string[] }}
99
+ */
100
+ validate(): {
101
+ valid: boolean;
102
+ errors: string[];
103
+ };
104
+ /**
105
+ * Commit all pending writes atomically
106
+ *
107
+ * Writes all files in a single batch. If any write fails,
108
+ * reports which files were written vs failed.
109
+ *
110
+ * @returns {{ success: boolean, written: string[], failed: { path: string, error: string }[] }}
111
+ * @throws {Error} If transaction already committed or aborted
112
+ */
113
+ commit(): {
114
+ success: boolean;
115
+ written: string[];
116
+ failed: {
117
+ path: string;
118
+ error: string;
119
+ }[];
120
+ };
121
+ /**
122
+ * Abort transaction (discard pending writes)
123
+ *
124
+ * Since no writes have been made, this just clears pending changes.
125
+ * This is the key benefit of the transactional pattern.
126
+ */
127
+ abort(): void;
128
+ /**
129
+ * Get transaction state for debugging
130
+ */
131
+ getState(): {
132
+ wuId: string;
133
+ committed: boolean;
134
+ aborted: boolean;
135
+ pendingCount: number;
136
+ files: string[];
137
+ createdAt: string;
138
+ };
139
+ }
140
+ /**
141
+ * Read file content for transaction (pre-compute backup)
142
+ *
143
+ * @param {string} filePath - File to read
144
+ * @returns {string|null} - File content or null if doesn't exist
145
+ */
146
+ export declare function readFileForTransaction(filePath: string): string | null;
147
+ /**
148
+ * Create a transaction state snapshot for rollback
149
+ *
150
+ * Captures current file contents before any modifications.
151
+ * Used if git operations fail after transaction commit.
152
+ *
153
+ * @param {string[]} filePaths - Paths to snapshot
154
+ * @returns {Map<string, string|null>} - Map of path to content (null = didn't exist)
155
+ */
156
+ export declare function createTransactionSnapshot(filePaths: string[]): Map<string, string | null>;
157
+ /**
158
+ * Restore files from snapshot (for rollback after commit)
159
+ *
160
+ * @param {Map<string, string|null>} snapshot - Snapshot from createTransactionSnapshot
161
+ * @returns {{ restored: string[], errors: { path: string, error: string }[] }}
162
+ */
163
+ export declare function restoreFromSnapshot(snapshot: Map<string, string | null>): {
164
+ restored: string[];
165
+ errors: {
166
+ path: string;
167
+ error: string;
168
+ }[];
169
+ };
170
+ export {};