@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,669 @@
1
+ /**
2
+ * WU Repair Core Module (WU-1826)
3
+ *
4
+ * Unified repair logic for all WU repair operations:
5
+ * - Consistency repair (default mode): detect/repair state inconsistencies
6
+ * - Claim repair (--claim mode): repair missing claim metadata in worktrees
7
+ * - Admin repair (--admin mode): administrative fixes for done WUs
8
+ *
9
+ * This module consolidates logic from:
10
+ * - wu-consistency-checker.mjs (consistency checks)
11
+ * - wu-repair-claim.mjs (claim metadata repair)
12
+ * - wu-admin-repair.mjs (admin fixes)
13
+ * - wu-recovery.mjs (zombie state recovery)
14
+ *
15
+ * @see {@link ../wu-repair.mjs} - Unified CLI interface
16
+ */
17
+ import path from 'node:path';
18
+ import { existsSync, writeFileSync, appendFileSync, mkdirSync, readFileSync } from 'node:fs';
19
+ import { checkWUConsistency, checkAllWUConsistency, repairWUInconsistency, } from './wu-consistency-checker.js';
20
+ import { readWU, writeWU, parseYAML, stringifyYAML } from './wu-yaml.js';
21
+ import { WU_PATHS } from './wu-paths.js';
22
+ import { WUStateStore, WU_EVENTS_FILE_NAME } from './wu-state-store.js';
23
+ import { getGitForCwd, createGitForPath } from './git-adapter.js';
24
+ import { EXIT_CODES, LOG_PREFIX, EMOJI, WU_STATUS } from './wu-constants.js';
25
+ import { die } from './error-handler.js';
26
+ import { ensureOnMain, ensureMainUpToDate, validateWUIDFormat } from './wu-helpers.js';
27
+ import { withMicroWorktree } from './micro-worktree.js';
28
+ import { validateLaneFormat } from './lane-checker.js';
29
+ import { normalizeToDateString } from './date-utils.js';
30
+ // Re-export for backwards compatibility
31
+ export { checkWUConsistency, checkAllWUConsistency, repairWUInconsistency };
32
+ // Re-export recovery utilities from wu-recovery.mjs
33
+ export { detectZombieState, recoverZombieState, resetWorktreeYAMLForRecovery, getRecoveryMarkerPath, getRecoveryAttemptCount, incrementRecoveryAttempt, clearRecoveryAttempts, shouldEscalateToManualIntervention, MAX_RECOVERY_ATTEMPTS, } from './wu-recovery.js';
34
+ const PREFIX = LOG_PREFIX.REPAIR;
35
+ // ============================================================================
36
+ // CLAIM REPAIR MODE
37
+ // ============================================================================
38
+ /**
39
+ * Detect worktree path from WU ID using git worktree list
40
+ *
41
+ * @param {string} id - WU ID (e.g., 'WU-1804')
42
+ * @returns {Promise<string|null>} Worktree path or null if not found
43
+ */
44
+ export async function findWorktreePathForWU(id) {
45
+ try {
46
+ const git = getGitForCwd();
47
+ const worktreeOutput = await git.worktreeList();
48
+ const lines = worktreeOutput.split('\n');
49
+ // Look for worktree with matching WU ID in branch name
50
+ const idLower = id.toLowerCase();
51
+ for (const line of lines) {
52
+ // Line format: "worktree <path>" followed by "branch refs/heads/lane/<lane>/<id>"
53
+ if (line.includes(idLower)) {
54
+ const worktreeMatch = line.match(/^worktree\s+(.+)$/);
55
+ if (worktreeMatch) {
56
+ return worktreeMatch[1].trim();
57
+ }
58
+ }
59
+ }
60
+ // Try porcelain format
61
+ const porcelainOutput = await git.raw(['worktree', 'list', '--porcelain']);
62
+ const entries = porcelainOutput.split('\n\n');
63
+ for (const entry of entries) {
64
+ if (entry.toLowerCase().includes(idLower)) {
65
+ const pathMatch = entry.match(/^worktree\s+(.+)$/m);
66
+ if (pathMatch) {
67
+ return pathMatch[1].trim();
68
+ }
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ /**
78
+ * Check claim metadata state for a WU worktree
79
+ *
80
+ * @param {string} id - WU ID
81
+ * @param {string} worktreePath - Path to the worktree
82
+ * @returns {Promise<{valid: boolean, errors: string[], yamlStatus: string|null, stateStoreHasClaim: boolean}>}
83
+ */
84
+ export async function checkClaimMetadata(id, worktreePath) {
85
+ const errors = [];
86
+ let yamlStatus = null;
87
+ let stateStoreHasClaim = false;
88
+ // Check worktree YAML status
89
+ const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
90
+ if (existsSync(wuPath)) {
91
+ try {
92
+ const doc = readWU(wuPath, id);
93
+ yamlStatus = doc.status;
94
+ if (yamlStatus !== WU_STATUS.IN_PROGRESS) {
95
+ errors.push(`WU YAML status is '${yamlStatus}', expected '${WU_STATUS.IN_PROGRESS}'`);
96
+ }
97
+ }
98
+ catch (err) {
99
+ errors.push(`Failed to read WU YAML: ${err.message}`);
100
+ }
101
+ }
102
+ else {
103
+ errors.push(`WU YAML not found at: ${wuPath}`);
104
+ }
105
+ // Check state store
106
+ const stateDir = path.join(worktreePath, '.beacon', 'state');
107
+ const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
108
+ if (existsSync(eventsPath)) {
109
+ try {
110
+ const store = new WUStateStore(stateDir);
111
+ await store.load();
112
+ const inProgress = store.getByStatus(WU_STATUS.IN_PROGRESS);
113
+ stateStoreHasClaim = inProgress.has(id);
114
+ if (!stateStoreHasClaim) {
115
+ errors.push(`State store does not show ${id} as in_progress`);
116
+ }
117
+ }
118
+ catch (err) {
119
+ errors.push(`Failed to read state store: ${err.message}`);
120
+ }
121
+ }
122
+ else {
123
+ errors.push(`State store not found at: ${eventsPath}`);
124
+ }
125
+ return {
126
+ valid: errors.length === 0,
127
+ errors,
128
+ yamlStatus,
129
+ stateStoreHasClaim,
130
+ };
131
+ }
132
+ /**
133
+ * Repair claim metadata in worktree
134
+ *
135
+ * SAFETY: Only modifies files inside the worktree, never main.
136
+ *
137
+ * @param {string} id - WU ID
138
+ * @param {string} worktreePath - Path to the worktree
139
+ * @param {object} checkResult - Result from checkClaimMetadata
140
+ * @returns {Promise<{success: boolean, repaired: string[], errors: string[]}>}
141
+ */
142
+ export async function repairClaimMetadata(id, worktreePath, checkResult) {
143
+ const repaired = [];
144
+ const errors = [];
145
+ // Read current WU YAML to get lane and title
146
+ const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
147
+ let doc;
148
+ try {
149
+ doc = readWU(wuPath, id);
150
+ }
151
+ catch (err) {
152
+ return {
153
+ success: false,
154
+ repaired: [],
155
+ errors: [`Cannot read WU YAML to repair: ${err.message}`],
156
+ };
157
+ }
158
+ const lane = doc.lane || '';
159
+ const title = doc.title || `WU ${id}`;
160
+ // Repair 1: Fix YAML status if needed
161
+ if (checkResult.yamlStatus !== WU_STATUS.IN_PROGRESS) {
162
+ try {
163
+ doc.status = WU_STATUS.IN_PROGRESS;
164
+ // Remove done-state fields that shouldn't be present
165
+ delete doc.locked;
166
+ delete doc.completed_at;
167
+ writeWU(wuPath, doc);
168
+ repaired.push(`WU YAML status set to '${WU_STATUS.IN_PROGRESS}'`);
169
+ }
170
+ catch (err) {
171
+ errors.push(`Failed to update WU YAML status: ${err.message}`);
172
+ }
173
+ }
174
+ // Repair 2: Add claim event to state store if missing
175
+ if (!checkResult.stateStoreHasClaim) {
176
+ try {
177
+ const stateDir = path.join(worktreePath, '.beacon', 'state');
178
+ const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
179
+ // Ensure directory exists
180
+ mkdirSync(stateDir, { recursive: true });
181
+ // Create claim event
182
+ const claimEvent = {
183
+ type: 'claim',
184
+ wuId: id,
185
+ lane: lane,
186
+ title: title,
187
+ timestamp: new Date().toISOString(),
188
+ };
189
+ // Append to events file
190
+ const line = `${JSON.stringify(claimEvent)}\n`;
191
+ appendFileSync(eventsPath, line, 'utf-8');
192
+ repaired.push(`Claim event added to state store`);
193
+ }
194
+ catch (err) {
195
+ errors.push(`Failed to add claim event to state store: ${err.message}`);
196
+ }
197
+ }
198
+ // Stage and commit the repairs
199
+ if (repaired.length > 0) {
200
+ try {
201
+ const gitWorktree = createGitForPath(worktreePath);
202
+ // Stage repaired files
203
+ const filesToStage = [wuPath];
204
+ const stateDir = path.join(worktreePath, '.beacon', 'state');
205
+ const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
206
+ if (existsSync(eventsPath)) {
207
+ filesToStage.push(eventsPath);
208
+ }
209
+ await gitWorktree.add(filesToStage);
210
+ // Commit with repair message
211
+ const commitMsg = `wu(${id.toLowerCase()}): repair-claim - restore missing claim metadata`;
212
+ await gitWorktree.commit(commitMsg);
213
+ repaired.push(`Committed repair: ${commitMsg}`);
214
+ // Push to remote
215
+ const currentBranch = await gitWorktree.getCurrentBranch();
216
+ await gitWorktree.push('origin', currentBranch);
217
+ repaired.push(`Pushed to origin/${currentBranch}`);
218
+ }
219
+ catch (err) {
220
+ // Don't fail the entire repair if commit/push fails
221
+ errors.push(`Git operations failed: ${err.message}. Manual commit may be required.`);
222
+ }
223
+ }
224
+ return {
225
+ success: errors.length === 0,
226
+ repaired,
227
+ errors,
228
+ };
229
+ }
230
+ /**
231
+ * Run claim repair mode
232
+ *
233
+ * @param {object} options - CLI options
234
+ * @param {string} options.id - WU ID
235
+ * @param {boolean} [options.check] - Check only, no repair
236
+ * @param {string} [options.worktree] - Override worktree path
237
+ * @returns {Promise<{success: boolean, exitCode: number}>}
238
+ */
239
+ export async function runClaimRepairMode(options) {
240
+ const { id, check, worktree } = options;
241
+ console.log(`${PREFIX} Checking claim metadata for ${id}...`);
242
+ // Find worktree path
243
+ let worktreePath = worktree;
244
+ if (!worktreePath) {
245
+ worktreePath = await findWorktreePathForWU(id);
246
+ }
247
+ if (!worktreePath) {
248
+ console.error(`${PREFIX} Error: Could not find worktree for ${id}`);
249
+ console.error(`${PREFIX} Ensure the worktree exists, or specify with --worktree <path>`);
250
+ return { success: false, exitCode: EXIT_CODES.FAILURE };
251
+ }
252
+ if (!existsSync(worktreePath)) {
253
+ console.error(`${PREFIX} Error: Worktree path does not exist: ${worktreePath}`);
254
+ return { success: false, exitCode: EXIT_CODES.FAILURE };
255
+ }
256
+ console.log(`${PREFIX} Found worktree: ${worktreePath}`);
257
+ // Check claim metadata state
258
+ const checkResult = await checkClaimMetadata(id, worktreePath);
259
+ if (checkResult.valid) {
260
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} Claim metadata is valid for ${id}`);
261
+ console.log(`${PREFIX} - YAML status: ${checkResult.yamlStatus}`);
262
+ console.log(`${PREFIX} - State store: has claim event`);
263
+ return { success: true, exitCode: EXIT_CODES.SUCCESS };
264
+ }
265
+ // Report issues
266
+ console.log(`${PREFIX} ${EMOJI.WARNING} Found ${checkResult.errors.length} issue(s):`);
267
+ for (const error of checkResult.errors) {
268
+ console.log(`${PREFIX} - ${error}`);
269
+ }
270
+ // Check-only mode
271
+ if (check) {
272
+ console.log(`${PREFIX} --check mode: no changes made`);
273
+ return { success: false, exitCode: EXIT_CODES.ERROR };
274
+ }
275
+ // Repair
276
+ console.log(`${PREFIX} Repairing claim metadata...`);
277
+ const repairResult = await repairClaimMetadata(id, worktreePath, checkResult);
278
+ // Report repairs
279
+ if (repairResult.repaired.length > 0) {
280
+ console.log(`${PREFIX} ${EMOJI.SUCCESS} Repairs applied:`);
281
+ for (const repair of repairResult.repaired) {
282
+ console.log(`${PREFIX} - ${repair}`);
283
+ }
284
+ }
285
+ // Report repair errors
286
+ if (repairResult.errors.length > 0) {
287
+ console.log(`${PREFIX} ${EMOJI.WARNING} Repair warnings:`);
288
+ for (const error of repairResult.errors) {
289
+ console.log(`${PREFIX} - ${error}`);
290
+ }
291
+ }
292
+ if (repairResult.success) {
293
+ console.log(`\n${PREFIX} ${EMOJI.SUCCESS} Repair complete!`);
294
+ console.log(`${PREFIX} You can now retry: pnpm wu:done --id ${id}`);
295
+ return { success: true, exitCode: EXIT_CODES.SUCCESS };
296
+ }
297
+ else {
298
+ console.error(`\n${PREFIX} ${EMOJI.FAILURE} Repair failed. Manual intervention required.`);
299
+ return { success: false, exitCode: EXIT_CODES.FAILURE };
300
+ }
301
+ }
302
+ // ============================================================================
303
+ // ADMIN REPAIR MODE
304
+ // ============================================================================
305
+ const ADMIN_PREFIX = '[wu:admin-repair]';
306
+ const OPERATION_NAME = 'wu-admin-repair';
307
+ const VALID_STATUSES = Object.values(WU_STATUS);
308
+ /**
309
+ * Validate status value against WU_STATUS enum
310
+ *
311
+ * @param {string} status - Status value to validate
312
+ */
313
+ function validateStatus(status) {
314
+ if (!VALID_STATUSES.includes(status)) {
315
+ die(`Invalid status: '${status}'\n\n` + `Valid statuses: ${VALID_STATUSES.join(', ')}`);
316
+ }
317
+ }
318
+ /**
319
+ * Check WU exists (does NOT block on done status unlike wu:edit)
320
+ *
321
+ * @param {string} id - WU ID
322
+ * @returns {object} WU object
323
+ */
324
+ function validateWUExists(id) {
325
+ const wuPath = WU_PATHS.WU(id);
326
+ if (!existsSync(wuPath)) {
327
+ die(`WU ${id} not found at ${wuPath}\n\nEnsure the WU exists and you're in the repo root.`);
328
+ }
329
+ const content = readFileSync(wuPath, { encoding: 'utf-8' });
330
+ const wu = parseYAML(content);
331
+ // Admin repair ALLOWS editing done WUs (key difference from wu:edit)
332
+ return wu;
333
+ }
334
+ /**
335
+ * Ensure working tree is clean
336
+ */
337
+ async function ensureCleanWorkingTree() {
338
+ const status = await getGitForCwd().getStatus();
339
+ if (status.trim()) {
340
+ die(`Working tree is not clean. Cannot run admin-repair.\n\nUncommitted changes:\n${status}\n\nCommit or stash changes before running admin-repair:\n git add . && git commit -m "..."\n`);
341
+ }
342
+ }
343
+ /**
344
+ * Generate audit trail entry for repairs
345
+ *
346
+ * @param {string[]} changes - List of changes made
347
+ * @returns {string} Audit trail entry
348
+ */
349
+ function generateAuditEntry(changes) {
350
+ const date = new Date().toISOString().split('T')[0];
351
+ return `\n\n[ADMIN-REPAIR ${date}]: ${changes.join('; ')}`;
352
+ }
353
+ /**
354
+ * Normalize date fields in WU object to prevent date corruption
355
+ *
356
+ * @param {object} wu - WU object from yaml.load()
357
+ * @returns {object} WU object with normalized date fields
358
+ */
359
+ function normalizeWUDates(wu) {
360
+ if (wu.created !== undefined) {
361
+ wu.created = normalizeToDateString(wu.created);
362
+ }
363
+ if (wu.completed !== undefined) {
364
+ wu.completed = normalizeToDateString(wu.completed);
365
+ }
366
+ return wu;
367
+ }
368
+ /**
369
+ * Apply lane repair and return changes
370
+ */
371
+ function applyLaneRepair(wu, updated, opts, changes) {
372
+ if (!opts.lane)
373
+ return;
374
+ validateLaneFormat(opts.lane);
375
+ if (wu.lane === opts.lane)
376
+ return;
377
+ changes.push(`lane changed from '${wu.lane}' to '${opts.lane}'`);
378
+ updated.lane = opts.lane;
379
+ }
380
+ /**
381
+ * Apply status repair and return changes
382
+ */
383
+ function applyStatusRepair(wu, updated, opts, changes) {
384
+ if (!opts.status)
385
+ return;
386
+ validateStatus(opts.status);
387
+ if (wu.status === opts.status)
388
+ return;
389
+ changes.push(`status changed from '${wu.status}' to '${opts.status}'`);
390
+ updated.status = opts.status;
391
+ // Update locked flag based on status
392
+ if (opts.status === WU_STATUS.DONE) {
393
+ updated.locked = true;
394
+ }
395
+ else if (wu.locked === true) {
396
+ // Unlock if moving away from done
397
+ updated.locked = false;
398
+ changes.push('locked changed from true to false');
399
+ }
400
+ }
401
+ /**
402
+ * Apply initiative repair and return changes
403
+ */
404
+ function applyInitiativeRepair(wu, updated, opts, changes) {
405
+ if (!opts.initiative || wu.initiative === opts.initiative)
406
+ return;
407
+ const oldVal = wu.initiative || '(none)';
408
+ changes.push(`initiative changed from '${oldVal}' to '${opts.initiative}'`);
409
+ updated.initiative = opts.initiative;
410
+ }
411
+ /**
412
+ * Apply notes update and return changes
413
+ */
414
+ function applyNotesUpdate(wu, updated, opts, changes) {
415
+ if (!opts.notes)
416
+ return;
417
+ const existingNotes = wu.notes || '';
418
+ changes.push(`notes updated`);
419
+ updated.notes = `${existingNotes}\n\n${opts.notes}`;
420
+ }
421
+ /**
422
+ * Append audit trail to notes based on changes made
423
+ */
424
+ function appendAuditTrail(updated, opts, changes) {
425
+ if (changes.length === 0)
426
+ return;
427
+ if (opts.notes) {
428
+ // If notes were explicitly provided, add audit for non-notes changes only
429
+ const nonNotesChanges = changes.filter((c) => c !== 'notes updated');
430
+ if (nonNotesChanges.length > 0) {
431
+ updated.notes = `${updated.notes}${generateAuditEntry(nonNotesChanges)}`;
432
+ }
433
+ }
434
+ else {
435
+ // All changes get audit trail
436
+ const existingNotes = updated.notes || '';
437
+ updated.notes = `${existingNotes}${generateAuditEntry(changes)}`;
438
+ }
439
+ }
440
+ /**
441
+ * Apply repairs to WU and track changes for audit
442
+ *
443
+ * @param {object} wu - Original WU object
444
+ * @param {object} opts - CLI options
445
+ * @returns {{ updated: object, changes: string[] }} Updated WU and list of changes
446
+ */
447
+ export function applyAdminRepairs(wu, opts) {
448
+ const updated = { ...wu };
449
+ const changes = [];
450
+ applyLaneRepair(wu, updated, opts, changes);
451
+ applyStatusRepair(wu, updated, opts, changes);
452
+ applyInitiativeRepair(wu, updated, opts, changes);
453
+ applyNotesUpdate(wu, updated, opts, changes);
454
+ appendAuditTrail(updated, opts, changes);
455
+ return { updated, changes };
456
+ }
457
+ /**
458
+ * Generate commit message for admin repair
459
+ *
460
+ * @param {string} id - WU ID
461
+ * @param {string[]} changes - List of changes made
462
+ * @returns {string} Commit message
463
+ */
464
+ function generateAdminCommitMessage(id, changes) {
465
+ // Extract field names from changes
466
+ const fields = changes.map((c) => c.split(' ')[0]).filter((f) => f !== 'notes');
467
+ const uniqueFields = [...new Set(fields)];
468
+ const fieldSummary = uniqueFields.length > 0 ? uniqueFields.join(', ') : 'notes';
469
+ return `fix(${id.toLowerCase()}): admin-repair ${fieldSummary}`;
470
+ }
471
+ /**
472
+ * Run admin repair mode
473
+ *
474
+ * @param {object} options - CLI options
475
+ * @param {string} options.id - WU ID
476
+ * @param {string} [options.lane] - New lane assignment
477
+ * @param {string} [options.status] - New status value
478
+ * @param {string} [options.notes] - Notes to add
479
+ * @param {string} [options.initiative] - New initiative reference
480
+ * @returns {Promise<{success: boolean, exitCode: number}>}
481
+ */
482
+ export async function runAdminRepairMode(options) {
483
+ const { id } = options;
484
+ console.log(`${ADMIN_PREFIX} Starting admin repair for ${id}`);
485
+ // Validate inputs
486
+ validateWUIDFormat(id);
487
+ // Check we have at least one field to repair BEFORE checking WU existence
488
+ // (no point in looking up the WU if no repair fields are provided)
489
+ const hasRepairs = options.lane || options.status || options.notes || options.initiative;
490
+ if (!hasRepairs) {
491
+ die('No repairs specified.\n\n' +
492
+ 'Provide at least one of:\n' +
493
+ ' --lane <lane> Fix lane assignment (e.g., "Operations: Tooling")\n' +
494
+ ' --status <status> Fix status value (ready, in_progress, blocked, done, cancelled)\n' +
495
+ ' --notes <text> Add repair notes (appends with audit trail)\n' +
496
+ ' --initiative <ref> Fix initiative reference (e.g., INIT-001)\n\n' +
497
+ 'Example:\n' +
498
+ ' pnpm wu:repair --admin --id WU-123 --lane "Operations: Tooling"');
499
+ }
500
+ // Now validate WU exists
501
+ const originalWU = validateWUExists(id);
502
+ // Apply repairs
503
+ const { updated: updatedWU, changes } = applyAdminRepairs(originalWU, options);
504
+ // Check if any actual changes were made
505
+ if (changes.length === 0) {
506
+ console.log(`${ADMIN_PREFIX} No changes needed - WU already has specified values`);
507
+ return { success: true, exitCode: EXIT_CODES.SUCCESS };
508
+ }
509
+ console.log(`${ADMIN_PREFIX} Changes to apply:`);
510
+ for (const change of changes) {
511
+ console.log(`${ADMIN_PREFIX} • ${change}`);
512
+ }
513
+ // Pre-flight checks for micro-worktree operation
514
+ await ensureOnMain(getGitForCwd());
515
+ await ensureCleanWorkingTree();
516
+ await ensureMainUpToDate(getGitForCwd(), 'wu:repair --admin');
517
+ console.log(`${ADMIN_PREFIX} Applying repairs via micro-worktree...`);
518
+ try {
519
+ await withMicroWorktree({
520
+ operation: OPERATION_NAME,
521
+ id: id,
522
+ logPrefix: ADMIN_PREFIX,
523
+ execute: async ({ worktreePath }) => {
524
+ // Write updated WU to micro-worktree
525
+ const wuPath = path.join(worktreePath, WU_PATHS.WU(id));
526
+ // Normalize dates before dumping to prevent ISO timestamp corruption
527
+ normalizeWUDates(updatedWU);
528
+ const yamlContent = stringifyYAML(updatedWU);
529
+ writeFileSync(wuPath, yamlContent, { encoding: 'utf-8' });
530
+ console.log(`${ADMIN_PREFIX} ${EMOJI.SUCCESS} Updated ${id}.yaml in micro-worktree`);
531
+ return {
532
+ commitMessage: generateAdminCommitMessage(id, changes),
533
+ files: [WU_PATHS.WU(id)],
534
+ };
535
+ },
536
+ });
537
+ console.log(`${ADMIN_PREFIX} ${EMOJI.SUCCESS} Successfully repaired ${id}`);
538
+ console.log(`${ADMIN_PREFIX} Changes pushed to origin/main`);
539
+ console.log(`${ADMIN_PREFIX} Audit trail logged to WU notes`);
540
+ return { success: true, exitCode: EXIT_CODES.SUCCESS };
541
+ }
542
+ catch (err) {
543
+ console.error(`${ADMIN_PREFIX} ${EMOJI.FAILURE} ${err.message}`);
544
+ return { success: false, exitCode: EXIT_CODES.ERROR };
545
+ }
546
+ }
547
+ // ============================================================================
548
+ // CONSISTENCY REPAIR MODE (default)
549
+ // ============================================================================
550
+ /**
551
+ * Format error for display
552
+ * @param {object} error - Error object
553
+ * @returns {string} Formatted error string
554
+ */
555
+ function formatError(error) {
556
+ return ` - ${error.type}: ${error.description}`;
557
+ }
558
+ /**
559
+ * Print consistency report
560
+ * @param {object} report - Consistency report
561
+ */
562
+ function printReport(report) {
563
+ if (report.valid) {
564
+ console.log(`${PREFIX} ${report.id}: No inconsistencies detected`);
565
+ return;
566
+ }
567
+ console.log(`${PREFIX} ${report.id}: ${report.errors.length} inconsistency(ies) found`);
568
+ for (const error of report.errors) {
569
+ console.log(formatError(error));
570
+ }
571
+ }
572
+ /**
573
+ * Repair a single WU for consistency issues
574
+ *
575
+ * @param {string} id - WU ID
576
+ * @param {object} options - CLI options
577
+ * @returns {Promise<{success: boolean, repaired: number, failed: number}>}
578
+ */
579
+ export async function repairSingleWU(id, options) {
580
+ console.log(`${PREFIX} Checking ${id}...`);
581
+ const report = await checkWUConsistency(id);
582
+ if (report.valid) {
583
+ console.log(`${PREFIX} ${id}: No inconsistencies detected`);
584
+ return { success: true, repaired: 0, failed: 0 };
585
+ }
586
+ printReport(report);
587
+ if (options.check) {
588
+ return { success: false, repaired: 0, failed: report.errors.length };
589
+ }
590
+ console.log(`${PREFIX} Repairing ${id}...`);
591
+ const result = await repairWUInconsistency(report);
592
+ if (result.failed > 0) {
593
+ console.error(`${PREFIX} Repair partially failed: ${result.repaired} repaired, ${result.failed} failed`);
594
+ return { success: false, repaired: result.repaired, failed: result.failed };
595
+ }
596
+ console.log(`${PREFIX} Successfully repaired ${result.repaired} issue(s)`);
597
+ return { success: true, repaired: result.repaired, failed: 0 };
598
+ }
599
+ /**
600
+ * Repair all WUs for consistency issues
601
+ *
602
+ * @param {object} options - CLI options
603
+ * @returns {Promise<{success: boolean, repaired: number, failed: number}>}
604
+ */
605
+ export async function repairAllWUs(options = {}) {
606
+ console.log(`${PREFIX} Checking all WUs...`);
607
+ const report = await checkAllWUConsistency();
608
+ if (report.valid) {
609
+ console.log(`${PREFIX} All ${report.checked} WUs are consistent`);
610
+ return { success: true, repaired: 0, failed: 0 };
611
+ }
612
+ console.log(`${PREFIX} Found ${report.errors.length} inconsistency issue(s) out of ${report.checked} WUs checked`);
613
+ console.log();
614
+ // Print all errors
615
+ for (const error of report.errors) {
616
+ console.log(` - ${error.type}: ${error.description}`);
617
+ }
618
+ console.log();
619
+ if (options.dryRun) {
620
+ return { success: false, repaired: 0, failed: report.errors.length };
621
+ }
622
+ // Repair the inconsistencies
623
+ console.log(`${PREFIX} Repairing inconsistencies...`);
624
+ const result = await repairWUInconsistency(report);
625
+ if (result.failed > 0) {
626
+ console.error(`${PREFIX} Partial failure - ${result.repaired} repaired, ${result.failed} failed`);
627
+ }
628
+ else {
629
+ console.log(`${PREFIX} Repaired ${result.repaired} issue(s)`);
630
+ }
631
+ console.log();
632
+ console.log(`${PREFIX} Summary: ${result.repaired} repaired, ${result.failed} failed`);
633
+ return { success: result.failed === 0, repaired: result.repaired, failed: result.failed };
634
+ }
635
+ /**
636
+ * Run consistency repair mode (default)
637
+ *
638
+ * @param {object} options - CLI options
639
+ * @param {string} [options.id] - WU ID to check/repair
640
+ * @param {boolean} [options.all] - Check/repair all WUs
641
+ * @param {boolean} [options.check] - Audit only, no changes
642
+ * @returns {Promise<{success: boolean, exitCode: number}>}
643
+ */
644
+ export async function runConsistencyRepairMode(options) {
645
+ let result;
646
+ try {
647
+ if (options.all) {
648
+ result = await repairAllWUs(options);
649
+ }
650
+ else {
651
+ result = await repairSingleWU(options.id, options);
652
+ }
653
+ }
654
+ catch (error) {
655
+ console.error(`${PREFIX} Fatal error: ${error.message}`);
656
+ return { success: false, exitCode: EXIT_CODES.FAILURE };
657
+ }
658
+ // Exit codes:
659
+ // 0: Success (no issues or all repaired)
660
+ // 1: Issues detected (--check mode)
661
+ // 2: Repair failed
662
+ if (!result.success) {
663
+ return {
664
+ success: false,
665
+ exitCode: options.check ? EXIT_CODES.ERROR : EXIT_CODES.FAILURE,
666
+ };
667
+ }
668
+ return { success: true, exitCode: EXIT_CODES.SUCCESS };
669
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file wu-schema-normalization.mjs
3
+ * @description Schema normalization for legacy WU YAML formats (WU-2004)
4
+ *
5
+ * Handles migration of legacy fields to current schema:
6
+ * - summary → description
7
+ * - string risks → array
8
+ * - test_paths → tests
9
+ * - ISO/Date created → YYYY-MM-DD
10
+ * - Removes deprecated fields: owner, context, spec_refs
11
+ */
12
+ /**
13
+ * Normalize a WU schema object, converting legacy formats to current schema.
14
+ * @param {Object} wu - Raw WU object (from YAML parse)
15
+ * @returns {Object} Normalized WU object
16
+ */
17
+ export declare function normalizeWUSchema(wu: any): any;