@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,433 @@
1
+ /**
2
+ * Rebase Artifact Cleanup
3
+ *
4
+ * Detects and cleans up completion artifacts (stamps, status=done)
5
+ * that appear in worktree after rebasing from main.
6
+ *
7
+ * This prevents contradictory state where an in_progress WU has
8
+ * completion markers from a previous completion cycle on main.
9
+ *
10
+ * Part of WU-1371: Post-rebase artifact cleanup
11
+ * WU-1449: Extended to handle backlog/status duplicates after rebase
12
+ *
13
+ * @see {@link tools/wu-done.mjs} - Creates completion artifacts
14
+ * @see {@link tools/lib/stamp-utils.mjs} - Stamp file utilities
15
+ * @see {@link tools/lib/wu-recovery.mjs} - Related zombie state handling
16
+ */
17
+ import { readFile, writeFile, unlink, access } from 'node:fs/promises';
18
+ import { existsSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import yaml from 'js-yaml';
21
+ import { WU_PATHS } from './wu-paths.js';
22
+ import { WU_STATUS, LOG_PREFIX, EMOJI, YAML_OPTIONS, BACKLOG_SECTIONS, STATUS_SECTIONS, REMOTES, BRANCHES, } from './wu-constants.js';
23
+ import { findSectionBounds, removeBulletFromSection } from './backlog-editor.js';
24
+ /** @constant {string} FRONTMATTER_DELIMITER - YAML frontmatter delimiter */
25
+ const FRONTMATTER_DELIMITER = '---';
26
+ /**
27
+ * Check if a file exists (async)
28
+ * @param {string} filePath - Path to check
29
+ * @returns {Promise<boolean>} True if file exists
30
+ */
31
+ async function fileExists(filePath) {
32
+ try {
33
+ await access(filePath);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ /**
41
+ * Check if a file exists on origin/main using git show
42
+ * WU-1817: Used to verify artifacts are truly rebased from main
43
+ *
44
+ * @param {object} gitAdapter - Git adapter instance with raw() method
45
+ * @param {string} relativePath - Path relative to repo root
46
+ * @returns {Promise<boolean>} True if file exists on origin/main
47
+ */
48
+ async function fileExistsOnMain(gitAdapter, relativePath) {
49
+ try {
50
+ await gitAdapter.raw(['show', `${REMOTES.ORIGIN}/${BRANCHES.MAIN}:${relativePath}`]);
51
+ return true;
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * Check if YAML on origin/main has status=done
59
+ * WU-1817: Used to verify done status is truly rebased from main
60
+ *
61
+ * @param {object} gitAdapter - Git adapter instance with raw() method
62
+ * @param {string} wuId - WU ID (e.g., 'WU-1817')
63
+ * @returns {Promise<boolean>} True if YAML on main has status=done
64
+ */
65
+ async function yamlIsDoneOnMain(gitAdapter, wuId) {
66
+ try {
67
+ const content = await gitAdapter.raw([
68
+ 'show',
69
+ `${REMOTES.ORIGIN}/${BRANCHES.MAIN}:${WU_PATHS.WU(wuId)}`,
70
+ ]);
71
+ const doc = yaml.load(content);
72
+ return doc && doc.status === WU_STATUS.DONE;
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ /**
79
+ * Read markdown file and separate frontmatter from content
80
+ * Simplified version that doesn't require gray-matter (which has js-yaml compatibility issues)
81
+ *
82
+ * @param {string} filePath - Path to file
83
+ * @returns {{frontmatter: string, lines: string[]}} Frontmatter and content lines
84
+ */
85
+ function readMarkdownFile(filePath) {
86
+ const { readFileSync } = require('node:fs');
87
+ const raw = readFileSync(filePath, { encoding: 'utf-8' });
88
+ const allLines = raw.split('\n');
89
+ // Check for frontmatter (starts with ---)
90
+ if (allLines[0]?.trim() === FRONTMATTER_DELIMITER) {
91
+ // Find closing ---
92
+ let endIdx = -1;
93
+ for (let i = 1; i < allLines.length; i++) {
94
+ if (allLines[i]?.trim() === FRONTMATTER_DELIMITER) {
95
+ endIdx = i;
96
+ break;
97
+ }
98
+ }
99
+ if (endIdx > 0) {
100
+ // Extract frontmatter and content
101
+ const frontmatterLines = allLines.slice(0, endIdx + 1);
102
+ const contentLines = allLines.slice(endIdx + 1);
103
+ return {
104
+ frontmatter: `${frontmatterLines.join('\n')}\n`,
105
+ lines: contentLines,
106
+ };
107
+ }
108
+ }
109
+ // No frontmatter
110
+ return {
111
+ frontmatter: '',
112
+ lines: allLines,
113
+ };
114
+ }
115
+ /**
116
+ * Write markdown file with frontmatter and content
117
+ *
118
+ * @param {string} filePath - Path to file
119
+ * @param {string} frontmatter - Frontmatter text (including --- markers)
120
+ * @param {string[]} lines - Content lines
121
+ */
122
+ function writeMarkdownFile(filePath, frontmatter, lines) {
123
+ const { writeFileSync } = require('node:fs');
124
+ const content = frontmatter + lines.join('\n');
125
+ writeFileSync(filePath, content, { encoding: 'utf-8' });
126
+ }
127
+ /**
128
+ * Detect rebased completion artifacts in a worktree
129
+ *
130
+ * WU-1817: Now verifies artifacts exist on origin/main before flagging.
131
+ * Only artifacts that exist on BOTH worktree AND origin/main are true
132
+ * rebased artifacts. Artifacts that exist only locally (created by the
133
+ * lane branch itself) should NOT be cleaned - this was the WU-1816 bug.
134
+ *
135
+ * Checks for:
136
+ * 1. Stamp files (.beacon/stamps/WU-{id}.done) that exist on origin/main
137
+ * 2. WU YAML with status=done that also has status=done on origin/main
138
+ *
139
+ * @param {string} worktreePath - Path to the worktree directory
140
+ * @param {string} wuId - WU ID (e.g., 'WU-1371')
141
+ * @param {object} gitAdapter - Git adapter instance with raw() method
142
+ * @returns {Promise<object>} Detection result
143
+ * @returns {string[]} result.stamps - Array of detected stamp file paths (only if on origin/main)
144
+ * @returns {boolean} result.yamlStatusDone - True if YAML has status=done AND origin/main has done
145
+ * @returns {boolean} result.hasArtifacts - True if any rebased artifacts detected
146
+ *
147
+ * @example
148
+ * const result = await detectRebasedArtifacts(worktreePath, wuId, gitAdapter);
149
+ * if (result.hasArtifacts) {
150
+ * console.log('Found rebased artifacts, cleaning up...');
151
+ * }
152
+ */
153
+ export async function detectRebasedArtifacts(worktreePath, wuId, gitAdapter) {
154
+ const stamps = [];
155
+ let yamlStatusDone = false;
156
+ // Check for stamp file in worktree
157
+ const stampPath = join(worktreePath, WU_PATHS.STAMP(wuId));
158
+ const localStampExists = await fileExists(stampPath);
159
+ // Check YAML status in worktree
160
+ const wuYamlPath = join(worktreePath, WU_PATHS.WU(wuId));
161
+ let localYamlDone = false;
162
+ if (await fileExists(wuYamlPath)) {
163
+ try {
164
+ const content = await readFile(wuYamlPath, { encoding: 'utf-8' });
165
+ const doc = yaml.load(content);
166
+ if (doc && doc.status === WU_STATUS.DONE) {
167
+ localYamlDone = true;
168
+ }
169
+ }
170
+ catch {
171
+ // YAML read error - treat as no artifact (will be caught elsewhere)
172
+ }
173
+ }
174
+ // WU-1817: Verify artifacts also exist on origin/main
175
+ // Only flag as rebased artifact if exists on BOTH worktree AND main
176
+ const stampOnMain = localStampExists
177
+ ? await fileExistsOnMain(gitAdapter, WU_PATHS.STAMP(wuId))
178
+ : false;
179
+ const yamlDoneOnMain = localYamlDone ? await yamlIsDoneOnMain(gitAdapter, wuId) : false;
180
+ // Only include artifacts that exist on both
181
+ if (stampOnMain) {
182
+ stamps.push(stampPath);
183
+ }
184
+ yamlStatusDone = yamlDoneOnMain;
185
+ const hasArtifacts = stamps.length > 0 || yamlStatusDone;
186
+ return {
187
+ stamps,
188
+ yamlStatusDone,
189
+ hasArtifacts,
190
+ };
191
+ }
192
+ /**
193
+ * Clean up rebased completion artifacts from a worktree
194
+ *
195
+ * Actions:
196
+ * 1. Remove stamp files that shouldn't exist
197
+ * 2. Reset YAML status from done to in_progress
198
+ * 3. Remove locked and completed_at fields from YAML
199
+ * 4. Log warnings explaining cleanup actions
200
+ *
201
+ * Idempotent: Safe to call multiple times, won't throw if artifacts don't exist.
202
+ *
203
+ * @param {string} worktreePath - Path to the worktree directory
204
+ * @param {string} wuId - WU ID (e.g., 'WU-1371')
205
+ * @returns {Promise<object>} Cleanup result
206
+ * @returns {string[]} result.stampsCleaned - WU IDs whose stamps were removed
207
+ * @returns {boolean} result.yamlReset - True if YAML status was reset
208
+ * @returns {string[]} result.errors - Any errors encountered (non-fatal)
209
+ * @returns {boolean} result.cleaned - True if any cleanup was performed
210
+ *
211
+ * @example
212
+ * const result = await cleanupRebasedArtifacts(worktreePath, wuId);
213
+ * if (result.cleaned) {
214
+ * console.log('Cleaned rebased artifacts:', result);
215
+ * }
216
+ */
217
+ export async function cleanupRebasedArtifacts(worktreePath, wuId) {
218
+ const stampsCleaned = [];
219
+ let yamlReset = false;
220
+ const errors = [];
221
+ // Clean stamp file
222
+ const stampPath = join(worktreePath, WU_PATHS.STAMP(wuId));
223
+ try {
224
+ if (await fileExists(stampPath)) {
225
+ await unlink(stampPath);
226
+ stampsCleaned.push(wuId);
227
+ console.log(LOG_PREFIX.CLEANUP, `${EMOJI.WARNING} Removed rebased stamp file for ${wuId} (artifact from main rebase)`);
228
+ }
229
+ }
230
+ catch (error) {
231
+ const stampErrMessage = error instanceof Error ? error.message : String(error);
232
+ errors.push(`Failed to remove stamp: ${stampErrMessage}`);
233
+ }
234
+ // Reset YAML status
235
+ const wuYamlPath = join(worktreePath, WU_PATHS.WU(wuId));
236
+ try {
237
+ if (await fileExists(wuYamlPath)) {
238
+ const content = await readFile(wuYamlPath, { encoding: 'utf-8' });
239
+ const doc = yaml.load(content);
240
+ if (doc && doc.status === WU_STATUS.DONE) {
241
+ // Reset status
242
+ doc.status = WU_STATUS.IN_PROGRESS;
243
+ // Remove completion fields
244
+ delete doc.locked;
245
+ delete doc.completed_at;
246
+ // Write back
247
+ const updatedContent = yaml.dump(doc, { lineWidth: YAML_OPTIONS.LINE_WIDTH });
248
+ await writeFile(wuYamlPath, updatedContent, { encoding: 'utf-8' });
249
+ yamlReset = true;
250
+ console.log(LOG_PREFIX.CLEANUP, `${EMOJI.WARNING} Reset YAML status from done to in_progress for ${wuId} (artifact from main rebase)`);
251
+ }
252
+ }
253
+ }
254
+ catch (error) {
255
+ const message = error instanceof Error ? error.message : String(error);
256
+ errors.push(`Failed to reset YAML status: ${message}`);
257
+ }
258
+ // WU-1449: Also clean backlog/status duplicates
259
+ const backlogDedup = await deduplicateBacklogAfterRebase(worktreePath, wuId);
260
+ const cleaned = stampsCleaned.length > 0 || yamlReset || backlogDedup.cleaned;
261
+ return {
262
+ stampsCleaned,
263
+ yamlReset,
264
+ backlogCleaned: backlogDedup.backlogCleaned,
265
+ statusCleaned: backlogDedup.statusCleaned,
266
+ errors: [...errors, ...backlogDedup.errors],
267
+ cleaned,
268
+ };
269
+ }
270
+ /**
271
+ * Detect WU duplicates in backlog/status files after rebase
272
+ *
273
+ * Checks if a WU appears in both:
274
+ * - "In Progress" AND "Done" sections of backlog.md
275
+ * - "In Progress" AND "Completed" sections of status.md
276
+ *
277
+ * This state occurs when main advanced with WU completion,
278
+ * then rebase merged main's "Done" state into the worktree
279
+ * while the worktree already had the WU in "In Progress".
280
+ *
281
+ * Part of WU-1449: Extend rebase cleanup to remove backlog/status duplicates
282
+ *
283
+ * @param {string} worktreePath - Path to the worktree directory
284
+ * @param {string} wuId - WU ID (e.g., 'WU-1449')
285
+ * @returns {Promise<object>} Detection result
286
+ * @returns {boolean} result.backlogDuplicate - True if WU in both In Progress and Done in backlog.md
287
+ * @returns {boolean} result.statusDuplicate - True if WU in both In Progress and Completed in status.md
288
+ * @returns {boolean} result.hasDuplicates - True if any duplicates detected
289
+ *
290
+ * @example
291
+ * const result = await detectBacklogDuplicates(worktreePath, wuId);
292
+ * if (result.hasDuplicates) {
293
+ * console.log('Found backlog duplicates, cleaning up...');
294
+ * }
295
+ */
296
+ export async function detectBacklogDuplicates(worktreePath, wuId) {
297
+ let backlogDuplicate = false;
298
+ let statusDuplicate = false;
299
+ // Check backlog.md for duplicate (WU in both In Progress and Done)
300
+ const backlogPath = join(worktreePath, WU_PATHS.BACKLOG());
301
+ if (existsSync(backlogPath)) {
302
+ try {
303
+ const { lines } = readMarkdownFile(backlogPath);
304
+ const inProgressBounds = findSectionBounds(lines, BACKLOG_SECTIONS.IN_PROGRESS);
305
+ const doneBounds = findSectionBounds(lines, BACKLOG_SECTIONS.DONE);
306
+ if (inProgressBounds && doneBounds) {
307
+ const inProgressSection = lines.slice(inProgressBounds.start, inProgressBounds.end);
308
+ const doneSection = lines.slice(doneBounds.start, doneBounds.end);
309
+ const inProgressHasWU = inProgressSection.some((line) => line.includes(wuId));
310
+ const doneHasWU = doneSection.some((line) => line.includes(wuId));
311
+ backlogDuplicate = inProgressHasWU && doneHasWU;
312
+ }
313
+ }
314
+ catch {
315
+ // File read error - treat as no duplicate (will be caught elsewhere)
316
+ }
317
+ }
318
+ // Check status.md for duplicate (WU in both In Progress and Completed)
319
+ const statusPath = join(worktreePath, WU_PATHS.STATUS());
320
+ if (existsSync(statusPath)) {
321
+ try {
322
+ const { lines } = readMarkdownFile(statusPath);
323
+ const inProgressBounds = findSectionBounds(lines, STATUS_SECTIONS.IN_PROGRESS);
324
+ const completedBounds = findSectionBounds(lines, STATUS_SECTIONS.COMPLETED);
325
+ if (inProgressBounds && completedBounds) {
326
+ const inProgressSection = lines.slice(inProgressBounds.start, inProgressBounds.end);
327
+ const completedSection = lines.slice(completedBounds.start, completedBounds.end);
328
+ const inProgressHasWU = inProgressSection.some((line) => line.includes(wuId));
329
+ const completedHasWU = completedSection.some((line) => line.includes(wuId));
330
+ statusDuplicate = inProgressHasWU && completedHasWU;
331
+ }
332
+ }
333
+ catch {
334
+ // File read error - treat as no duplicate (will be caught elsewhere)
335
+ }
336
+ }
337
+ const hasDuplicates = backlogDuplicate || statusDuplicate;
338
+ return {
339
+ backlogDuplicate,
340
+ statusDuplicate,
341
+ hasDuplicates,
342
+ };
343
+ }
344
+ /**
345
+ * Clean duplicates from a single markdown file
346
+ *
347
+ * Helper function to reduce cognitive complexity of deduplicateBacklogAfterRebase.
348
+ *
349
+ * @param {string} filePath - Path to the markdown file
350
+ * @param {string} wuId - WU ID to check for duplicates
351
+ * @param {string} inProgressSection - Section name for in-progress items
352
+ * @param {string} completedSection - Section name for completed items
353
+ * @param {string} fileLabel - Label for log messages (e.g., 'backlog.md')
354
+ * @param {string} completedLabel - Label for completed section (e.g., 'Done' or 'Completed')
355
+ * @returns {{cleaned: boolean, error: string|null}} Result
356
+ */
357
+ function cleanDuplicatesFromFile(filePath, wuId, inProgressSection, completedSection, fileLabel, completedLabel) {
358
+ if (!existsSync(filePath)) {
359
+ return { cleaned: false, error: null };
360
+ }
361
+ try {
362
+ const { frontmatter, lines } = readMarkdownFile(filePath);
363
+ const inProgressBounds = findSectionBounds(lines, inProgressSection);
364
+ const completedBounds = findSectionBounds(lines, completedSection);
365
+ if (!inProgressBounds || !completedBounds) {
366
+ return { cleaned: false, error: null };
367
+ }
368
+ const inProgressLines = lines.slice(inProgressBounds.start, inProgressBounds.end);
369
+ const completedLines = lines.slice(completedBounds.start, completedBounds.end);
370
+ const inProgressHasWU = inProgressLines.some((line) => line.includes(wuId));
371
+ const completedHasWU = completedLines.some((line) => line.includes(wuId));
372
+ // Only clean if WU is in BOTH sections (duplicate state)
373
+ if (!inProgressHasWU || !completedHasWU) {
374
+ return { cleaned: false, error: null };
375
+ }
376
+ removeBulletFromSection(lines, inProgressBounds.start, inProgressBounds.end, wuId);
377
+ writeMarkdownFile(filePath, frontmatter, lines);
378
+ console.log(LOG_PREFIX.CLEANUP, `${EMOJI.WARNING} Removed ${wuId} from In Progress section in ${fileLabel} (already in ${completedLabel} after rebase)`);
379
+ return { cleaned: true, error: null };
380
+ }
381
+ catch (error) {
382
+ return { cleaned: false, error: `Failed to clean ${fileLabel}: ${error.message}` };
383
+ }
384
+ }
385
+ /**
386
+ * Remove WU from In Progress sections when already in Done/Completed after rebase
387
+ *
388
+ * This handles the specific case where:
389
+ * 1. WU is completing (wu:done in progress)
390
+ * 2. Auto-rebase pulls main's completion state
391
+ * 3. WU now appears in BOTH In Progress AND Done sections
392
+ * 4. This function removes the duplicate from In Progress (keeps Done)
393
+ *
394
+ * Applies to both backlog.md and status.md.
395
+ * Idempotent: Safe to call multiple times.
396
+ *
397
+ * Part of WU-1449: Extend rebase cleanup to remove backlog/status duplicates
398
+ *
399
+ * @param {string} worktreePath - Path to the worktree directory
400
+ * @param {string} wuId - WU ID (e.g., 'WU-1449')
401
+ * @returns {Promise<object>} Cleanup result
402
+ * @returns {boolean} result.backlogCleaned - True if WU removed from backlog.md In Progress
403
+ * @returns {boolean} result.statusCleaned - True if WU removed from status.md In Progress
404
+ * @returns {boolean} result.cleaned - True if any cleanup was performed
405
+ * @returns {string[]} result.errors - Any errors encountered (non-fatal)
406
+ *
407
+ * @example
408
+ * const result = await deduplicateBacklogAfterRebase(worktreePath, wuId);
409
+ * if (result.cleaned) {
410
+ * console.log('Cleaned backlog duplicates:', result);
411
+ * }
412
+ */
413
+ export async function deduplicateBacklogAfterRebase(worktreePath, wuId) {
414
+ const errors = [];
415
+ // Clean backlog.md duplicates
416
+ const backlogPath = join(worktreePath, WU_PATHS.BACKLOG());
417
+ const backlogResult = cleanDuplicatesFromFile(backlogPath, wuId, BACKLOG_SECTIONS.IN_PROGRESS, BACKLOG_SECTIONS.DONE, 'backlog.md', 'Done');
418
+ if (backlogResult.error) {
419
+ errors.push(backlogResult.error);
420
+ }
421
+ // Clean status.md duplicates
422
+ const statusPath = join(worktreePath, WU_PATHS.STATUS());
423
+ const statusResult = cleanDuplicatesFromFile(statusPath, wuId, STATUS_SECTIONS.IN_PROGRESS, STATUS_SECTIONS.COMPLETED, 'status.md', 'Completed');
424
+ if (statusResult.error) {
425
+ errors.push(statusResult.error);
426
+ }
427
+ return {
428
+ backlogCleaned: backlogResult.cleaned,
429
+ statusCleaned: statusResult.cleaned,
430
+ cleaned: backlogResult.cleaned || statusResult.cleaned,
431
+ errors,
432
+ };
433
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * WU-1747: Retry Strategy Module
3
+ *
4
+ * Provides exponential backoff retry mechanism with configurable parameters
5
+ * for wu:done concurrent load resilience.
6
+ *
7
+ * Features:
8
+ * - Configurable max attempts, base delay, multiplier
9
+ * - Exponential backoff with optional jitter
10
+ * - Presets for common scenarios (wu_done, recovery)
11
+ * - Callback hooks for retry events
12
+ * - Conditional retry based on error type
13
+ *
14
+ * @module retry-strategy
15
+ */
16
+ /**
17
+ * Error message patterns that are considered retryable for wu:done operations
18
+ * Exported for test consistency
19
+ */
20
+ export declare const RETRYABLE_ERROR_PATTERNS: Readonly<{
21
+ FAST_FORWARD: "fast-forward";
22
+ NOT_POSSIBLE: "not possible";
23
+ CANNOT_LOCK_REF: "Cannot lock ref";
24
+ FETCH: "fetch";
25
+ PUSH: "push";
26
+ ETIMEDOUT: "ETIMEDOUT";
27
+ ECONNRESET: "ECONNRESET";
28
+ }>;
29
+ /**
30
+ * @typedef {Object} RetryConfig
31
+ * @property {number} maxAttempts - Maximum number of attempts (default: 5)
32
+ * @property {number} baseDelayMs - Base delay in milliseconds (default: 1000)
33
+ * @property {number} maxDelayMs - Maximum delay cap in milliseconds (default: 30000)
34
+ * @property {number} multiplier - Exponential multiplier (default: 2)
35
+ * @property {number} jitter - Jitter factor 0-1 (default: 0.1 for 10%)
36
+ * @property {function} [shouldRetry] - Optional function(error) => boolean to determine if error is retryable
37
+ * @property {function} [onRetry] - Optional callback(attempt, error, delay) before each retry
38
+ */
39
+ /**
40
+ * Default retry configuration
41
+ * Balanced for typical wu:done operations
42
+ */
43
+ export declare const DEFAULT_RETRY_CONFIG: Readonly<{
44
+ maxAttempts: 5;
45
+ baseDelayMs: 1000;
46
+ maxDelayMs: 30000;
47
+ multiplier: 2;
48
+ jitter: 0.1;
49
+ shouldRetry: (_error?: unknown) => true;
50
+ onRetry: any;
51
+ }>;
52
+ /**
53
+ * Pre-configured retry presets for common scenarios
54
+ */
55
+ export declare const RETRY_PRESETS: Readonly<{
56
+ /**
57
+ * Preset for wu:done merge operations
58
+ * Higher attempts and longer delays for handling concurrent load
59
+ */
60
+ wu_done: Readonly<{
61
+ maxAttempts: 6;
62
+ baseDelayMs: 2000;
63
+ maxDelayMs: 60000;
64
+ multiplier: 2;
65
+ jitter: 0.15;
66
+ shouldRetry: (error: any) => boolean;
67
+ onRetry: any;
68
+ }>;
69
+ /**
70
+ * Preset for zombie state recovery
71
+ * More attempts with shorter delays
72
+ */
73
+ recovery: Readonly<{
74
+ maxAttempts: 4;
75
+ baseDelayMs: 500;
76
+ maxDelayMs: 10000;
77
+ multiplier: 2;
78
+ jitter: 0.1;
79
+ shouldRetry: () => true;
80
+ onRetry: any;
81
+ }>;
82
+ /**
83
+ * Preset for quick operations (file I/O, local git)
84
+ * Fast retries for transient errors
85
+ */
86
+ quick: Readonly<{
87
+ maxAttempts: 3;
88
+ baseDelayMs: 100;
89
+ maxDelayMs: 2000;
90
+ multiplier: 2;
91
+ jitter: 0.05;
92
+ shouldRetry: () => true;
93
+ onRetry: any;
94
+ }>;
95
+ }>;
96
+ /**
97
+ * Create a retry configuration by merging defaults with custom options
98
+ *
99
+ * @param {string|Object} [presetOrOptions] - Preset name or custom options
100
+ * @param {Object} [options] - Additional options to merge (when first arg is preset name)
101
+ * @returns {RetryConfig} Complete retry configuration
102
+ *
103
+ * @example
104
+ * // Use defaults
105
+ * const config = createRetryConfig();
106
+ *
107
+ * @example
108
+ * // Customize defaults
109
+ * const config = createRetryConfig({ maxAttempts: 10 });
110
+ *
111
+ * @example
112
+ * // Use preset
113
+ * const config = createRetryConfig('wu_done');
114
+ *
115
+ * @example
116
+ * // Customize preset
117
+ * const config = createRetryConfig('wu_done', { maxAttempts: 10 });
118
+ */
119
+ export declare function createRetryConfig(presetOrOptions: any, options: any): any;
120
+ /**
121
+ * Calculate the backoff delay for a given attempt number
122
+ *
123
+ * Uses exponential backoff formula: baseDelay * (multiplier ^ attempt)
124
+ * with optional jitter to spread concurrent retry attempts
125
+ *
126
+ * @param {number} attempt - Zero-based attempt number (0 = first attempt)
127
+ * @param {RetryConfig} config - Retry configuration
128
+ * @returns {number} Delay in milliseconds
129
+ */
130
+ export declare function calculateBackoffDelay(attempt: any, config: any): number;
131
+ /**
132
+ * Execute a function with retry logic and exponential backoff
133
+ *
134
+ * @template T
135
+ * @param {function(): Promise<T>} fn - Async function to execute
136
+ * @param {RetryConfig} [config] - Retry configuration (uses defaults if not provided)
137
+ * @returns {Promise<T>} Result of successful execution
138
+ * @throws {Error} Last error if all attempts fail
139
+ *
140
+ * @example
141
+ * const result = await withRetry(
142
+ * async () => await mergeBranch(),
143
+ * createRetryConfig('wu_done')
144
+ * );
145
+ */
146
+ export declare function withRetry(fn: any, config?: Readonly<{
147
+ maxAttempts: 5;
148
+ baseDelayMs: 1000;
149
+ maxDelayMs: 30000;
150
+ multiplier: 2;
151
+ jitter: 0.1;
152
+ shouldRetry: (_error?: unknown) => true;
153
+ onRetry: any;
154
+ }>): Promise<any>;
155
+ /**
156
+ * Higher-order function to wrap a function with retry logic
157
+ *
158
+ * @template T
159
+ * @param {function(...args): Promise<T>} fn - Function to wrap
160
+ * @param {RetryConfig} [config] - Retry configuration
161
+ * @returns {function(...args): Promise<T>} Wrapped function with retry logic
162
+ *
163
+ * @example
164
+ * const retryableMerge = withRetryWrapper(mergeBranch, createRetryConfig('wu_done'));
165
+ * await retryableMerge(branch);
166
+ */
167
+ export declare function withRetryWrapper(fn: any, config?: Readonly<{
168
+ maxAttempts: 5;
169
+ baseDelayMs: 1000;
170
+ maxDelayMs: 30000;
171
+ multiplier: 2;
172
+ jitter: 0.1;
173
+ shouldRetry: (_error?: unknown) => true;
174
+ onRetry: any;
175
+ }>): (...args: any[]) => Promise<any>;
176
+ /**
177
+ * Determine if an error is a git conflict error (non-retryable)
178
+ *
179
+ * @param {Error} error - Error to check
180
+ * @returns {boolean} True if conflict error
181
+ */
182
+ export declare function isConflictError(error: any): any;
183
+ /**
184
+ * Determine if an error is a network/transient error (retryable)
185
+ *
186
+ * @param {Error} error - Error to check
187
+ * @returns {boolean} True if likely transient
188
+ */
189
+ export declare function isTransientError(error: any): any;