@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,45 @@
1
+ /**
2
+ * Parse YAML frontmatter from backlog.md
3
+ * @param {string} backlogPath - Path to backlog.md file
4
+ * @returns {{frontmatter: object|null, markdown: string}} Parsed frontmatter and markdown body
5
+ * @throws {Error} If file not found or YAML parsing fails
6
+ */
7
+ export declare function parseBacklogFrontmatter(backlogPath: any): {
8
+ frontmatter: {
9
+ [key: string]: any;
10
+ };
11
+ markdown: string;
12
+ };
13
+ /**
14
+ * Section configuration from frontmatter
15
+ */
16
+ interface SectionConfig {
17
+ heading?: string;
18
+ }
19
+ /**
20
+ * Backlog frontmatter structure
21
+ */
22
+ interface BacklogFrontmatter {
23
+ sections?: Record<string, SectionConfig>;
24
+ }
25
+ /**
26
+ * Section boundary definition
27
+ */
28
+ interface SectionBoundary {
29
+ start: number;
30
+ end: number | null;
31
+ }
32
+ /**
33
+ * Extract section headings from frontmatter
34
+ * @param {object|null} frontmatter - Parsed frontmatter object
35
+ * @returns {object} Map of section names to heading strings (e.g., {ready: "## 🚀 Ready"})
36
+ */
37
+ export declare function getSectionHeadings(frontmatter: BacklogFrontmatter | null): Record<string, string>;
38
+ /**
39
+ * Find section boundaries in backlog content
40
+ * @param {string[]} lines - Lines of backlog content
41
+ * @param {object|null} frontmatter - Parsed frontmatter object
42
+ * @returns {object} Map of section names to {start, end} boundary indices
43
+ */
44
+ export declare function findSectionBoundaries(lines: string[], frontmatter: BacklogFrontmatter | null): Record<string, SectionBoundary | null>;
45
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Backlog Parser (WU-1065, WU-1211)
3
+ * Shared module for parsing YAML frontmatter in backlog.md
4
+ * Uses gray-matter library for robust frontmatter parsing
5
+ */
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import matter from 'gray-matter';
8
+ import yaml from 'yaml';
9
+ import { createError, ErrorCodes } from './error-handler.js';
10
+ /**
11
+ * Parse YAML frontmatter from backlog.md
12
+ * @param {string} backlogPath - Path to backlog.md file
13
+ * @returns {{frontmatter: object|null, markdown: string}} Parsed frontmatter and markdown body
14
+ * @throws {Error} If file not found or YAML parsing fails
15
+ */
16
+ export function parseBacklogFrontmatter(backlogPath) {
17
+ if (!existsSync(backlogPath)) {
18
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `Backlog not found: ${backlogPath}`, {
19
+ path: backlogPath,
20
+ });
21
+ }
22
+ const content = readFileSync(backlogPath, { encoding: 'utf-8' });
23
+ try {
24
+ // Configure gray-matter to use modern yaml library instead of deprecated js-yaml
25
+ const { data, content: markdown } = matter(content, {
26
+ engines: {
27
+ yaml: {
28
+ parse: yaml.parse.bind(yaml),
29
+ stringify: yaml.stringify.bind(yaml),
30
+ },
31
+ },
32
+ });
33
+ // gray-matter returns {} for no frontmatter; normalize to null for consistency
34
+ const frontmatter = Object.keys(data).length > 0 ? data : null;
35
+ return { frontmatter, markdown };
36
+ }
37
+ catch (err) {
38
+ throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse frontmatter in ${backlogPath}:\n\n${err.message}\n\n` +
39
+ `Ensure frontmatter is valid YAML between --- delimiters.`, { path: backlogPath, originalError: err.message });
40
+ }
41
+ }
42
+ /**
43
+ * Extract section headings from frontmatter
44
+ * @param {object|null} frontmatter - Parsed frontmatter object
45
+ * @returns {object} Map of section names to heading strings (e.g., {ready: "## 🚀 Ready"})
46
+ */
47
+ export function getSectionHeadings(frontmatter) {
48
+ if (!frontmatter || !frontmatter.sections) {
49
+ return {};
50
+ }
51
+ const headings = {};
52
+ for (const [sectionName, sectionConfig] of Object.entries(frontmatter.sections)) {
53
+ if (sectionConfig.heading) {
54
+ headings[sectionName] = sectionConfig.heading;
55
+ }
56
+ }
57
+ return headings;
58
+ }
59
+ /**
60
+ * Find section boundaries in backlog content
61
+ * @param {string[]} lines - Lines of backlog content
62
+ * @param {object|null} frontmatter - Parsed frontmatter object
63
+ * @returns {object} Map of section names to {start, end} boundary indices
64
+ */
65
+ export function findSectionBoundaries(lines, frontmatter) {
66
+ if (!frontmatter || !frontmatter.sections) {
67
+ return {};
68
+ }
69
+ const headings = getSectionHeadings(frontmatter);
70
+ const boundaries = {};
71
+ // Initialize all sections as null (not found)
72
+ for (const sectionName of Object.keys(headings)) {
73
+ boundaries[sectionName] = null;
74
+ }
75
+ // Find start indices for each section (exact match)
76
+ for (let i = 0; i < lines.length; i++) {
77
+ const line = lines[i];
78
+ for (const [sectionName, heading] of Object.entries(headings)) {
79
+ if (line === heading) {
80
+ boundaries[sectionName] = { start: i, end: null };
81
+ }
82
+ }
83
+ }
84
+ // Calculate end indices (last line before next section or EOF)
85
+ const sectionStarts = Object.values(boundaries)
86
+ .filter((b) => b !== null)
87
+ .map((b) => b.start)
88
+ .sort((a, b) => a - b);
89
+ for (const boundary of Object.values(boundaries)) {
90
+ if (boundary === null)
91
+ continue;
92
+ const currentStart = boundary.start;
93
+ const nextStart = sectionStarts.find((s) => s > currentStart);
94
+ if (nextStart !== undefined) {
95
+ boundary.end = nextStart - 1;
96
+ }
97
+ else {
98
+ boundary.end = lines.length - 1;
99
+ }
100
+ }
101
+ return boundaries;
102
+ }
@@ -0,0 +1,78 @@
1
+ export declare function validateBacklogSync(backlogPath: any): {
2
+ valid: boolean;
3
+ errors: any[];
4
+ stats?: undefined;
5
+ } | {
6
+ valid: boolean;
7
+ errors: any[];
8
+ stats: {
9
+ ready: number;
10
+ inProgress: number;
11
+ blocked: number;
12
+ done: number;
13
+ duplicates: number;
14
+ parentOnlyInReady: number;
15
+ };
16
+ };
17
+ /**
18
+ * Options for fixing backlog duplicates
19
+ */
20
+ export interface FixBacklogDuplicatesOptions {
21
+ /** If true, report changes without writing */
22
+ dryRun?: boolean;
23
+ /** If true, return fixed content without writing (WU-1506) */
24
+ returnContent?: boolean;
25
+ }
26
+ /**
27
+ * Fix backlog duplicates by removing WUs from non-authoritative sections
28
+ * - If WU in Done AND Ready: remove from Ready (Done is authoritative)
29
+ * - If WU in Done AND InProgress: remove from InProgress (Done is authoritative)
30
+ *
31
+ * Part of WU-1303: Backlog duplicate entries after rebase conflicts
32
+ * WU-1506: Added returnContent option for atomic in-memory validation
33
+ *
34
+ * @param {string} backlogPath - Path to backlog.md file
35
+ * @param {FixBacklogDuplicatesOptions} options - Fix options
36
+ * @returns {{fixed: boolean, removed: Array<{wu: string, section: string}>, backupPath?: string, content?: string}}
37
+ */
38
+ export declare function fixBacklogDuplicates(backlogPath: any, options?: FixBacklogDuplicatesOptions): {
39
+ fixed: boolean;
40
+ removed: any[];
41
+ error: any;
42
+ message?: undefined;
43
+ content?: undefined;
44
+ dryRun?: undefined;
45
+ backupPath?: undefined;
46
+ } | {
47
+ fixed: boolean;
48
+ removed: any[];
49
+ message: string;
50
+ error?: undefined;
51
+ content?: undefined;
52
+ dryRun?: undefined;
53
+ backupPath?: undefined;
54
+ } | {
55
+ fixed: boolean;
56
+ removed: any[];
57
+ content: string;
58
+ message: string;
59
+ error?: undefined;
60
+ dryRun?: undefined;
61
+ backupPath?: undefined;
62
+ } | {
63
+ fixed: boolean;
64
+ removed: any[];
65
+ dryRun: boolean;
66
+ message: string;
67
+ error?: undefined;
68
+ content?: undefined;
69
+ backupPath?: undefined;
70
+ } | {
71
+ fixed: boolean;
72
+ removed: any[];
73
+ backupPath: string;
74
+ message: string;
75
+ error?: undefined;
76
+ content?: undefined;
77
+ dryRun?: undefined;
78
+ };
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Backlog Sync Validator (WU-672, WU-1065, WU-1137, WU-1303)
3
+ * Detects WUs present in multiple sections (Done+Ready, Done+InProgress, etc.)
4
+ * Uses frontmatter-driven section detection (WU-1065) instead of brittle string matching
5
+ * Flags parent-only WUs in Ready section (WU-1137) - sub-lane format is preferred
6
+ * Supports --fix mode to automatically remove duplicates (WU-1303)
7
+ */
8
+ import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs';
9
+ import path from 'node:path';
10
+ import yaml from 'js-yaml';
11
+ import { parseBacklogFrontmatter, getSectionHeadings } from './backlog-parser.js';
12
+ import { extractParent } from './lane-checker.js';
13
+ import { CONFIG_FILES, STRING_LITERALS, getProjectRoot, } from './wu-constants.js';
14
+ /**
15
+ * Check if parent lane has sub-lane taxonomy in .lumenflow.lane-inference.yaml
16
+ * @param {string} parent - Parent lane name
17
+ * @param {string} projectRoot - Path to project root
18
+ * @returns {boolean} True if parent has sub-lanes defined
19
+ */
20
+ function hasSubLaneTaxonomy(parent, projectRoot) {
21
+ const taxonomyPath = path.join(projectRoot, CONFIG_FILES.LANE_INFERENCE);
22
+ if (!existsSync(taxonomyPath)) {
23
+ return false;
24
+ }
25
+ try {
26
+ const taxonomyContent = readFileSync(taxonomyPath, { encoding: 'utf-8' });
27
+ const taxonomy = yaml.load(taxonomyContent);
28
+ const normalizedParent = parent.trim().toLowerCase();
29
+ return Object.keys(taxonomy).some((key) => key.toLowerCase().trim() === normalizedParent);
30
+ }
31
+ catch {
32
+ // If taxonomy file is malformed, assume no taxonomy (fail safe)
33
+ return false;
34
+ }
35
+ }
36
+ export function validateBacklogSync(backlogPath) {
37
+ // Parse frontmatter to get configured section headings
38
+ let frontmatter, markdown;
39
+ try {
40
+ ({ frontmatter, markdown } = parseBacklogFrontmatter(backlogPath));
41
+ }
42
+ catch (err) {
43
+ return { valid: false, errors: [err.message] };
44
+ }
45
+ // If no frontmatter, fall back to empty sections (backlog without frontmatter is valid)
46
+ const headings = frontmatter ? getSectionHeadings(frontmatter) : {};
47
+ const lines = markdown.split(/\r?\n/);
48
+ // Parse sections using frontmatter headings
49
+ const sections = {
50
+ ready: new Set(),
51
+ in_progress: new Set(),
52
+ blocked: new Set(),
53
+ done: new Set(),
54
+ };
55
+ let currentSection = null;
56
+ // Build heading-to-section map for efficient lookup
57
+ const headingMap = new Map();
58
+ for (const [sectionName, heading] of Object.entries(headings)) {
59
+ headingMap.set(heading, sectionName);
60
+ }
61
+ // WU-1334: Pattern to match WU IDs only in backlog list items
62
+ // Matches lines starting with:
63
+ // - `- WU-123` (bullet list)
64
+ // - `* WU-123` (asterisk list)
65
+ // - `- [ ] WU-123` (unchecked checkbox)
66
+ // - `- [x] WU-123` (checked checkbox)
67
+ // - `- [WU-123 - title](...)` (markdown link)
68
+ // Does NOT match prose like "See WU-123 for details" or "WU-123 → WU-124"
69
+ const BACKLOG_ITEM_PATTERN = /^\s*[-*]\s*(?:\[[ x]\]\s*)?\[?(WU-\d+)/i;
70
+ for (const line of lines) {
71
+ // Check if line matches any configured section heading (exact match)
72
+ if (headingMap.has(line)) {
73
+ currentSection = headingMap.get(line);
74
+ continue;
75
+ }
76
+ // Reset section when encountering other ## headings (not subsections ###)
77
+ if (line.trim().startsWith('## ') && !line.trim().startsWith('### ')) {
78
+ currentSection = null;
79
+ continue;
80
+ }
81
+ if (currentSection) {
82
+ const match = line.match(BACKLOG_ITEM_PATTERN);
83
+ if (match) {
84
+ sections[currentSection].add(match[1].toUpperCase());
85
+ }
86
+ }
87
+ }
88
+ // Detect duplicates
89
+ const errors = [];
90
+ // Done + Ready
91
+ const doneAndReady = [...sections.done].filter((wu) => sections.ready.has(wu));
92
+ if (doneAndReady.length > 0) {
93
+ errors.push(`❌ ${doneAndReady.length} WU(s) are in BOTH Done and Ready sections:${STRING_LITERALS.NEWLINE}${doneAndReady
94
+ .map((wu) => ` - ${wu}`)
95
+ .join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
96
+ ` Fix: Remove from Ready section (they are already complete)${STRING_LITERALS.NEWLINE}` +
97
+ ` Command: Edit docs/04-operations/tasks/backlog.md and remove duplicate entries`);
98
+ }
99
+ // Done + In Progress (should never happen, but check anyway)
100
+ const doneAndInProgress = [...sections.done].filter((wu) => sections.in_progress.has(wu));
101
+ if (doneAndInProgress.length > 0) {
102
+ errors.push(`❌ ${doneAndInProgress.length} WU(s) are in BOTH Done and In Progress sections:${STRING_LITERALS.NEWLINE}${doneAndInProgress
103
+ .map((wu) => ` - ${wu}`)
104
+ .join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
105
+ ` Fix: Remove from In Progress section (they are already complete)${STRING_LITERALS.NEWLINE}` +
106
+ ` Or: If reopened, remove from Done and update WU YAML status to in_progress`);
107
+ }
108
+ // Ready + In Progress (legitimate during claim, but flag for awareness)
109
+ const readyAndInProgress = [...sections.ready].filter((wu) => sections.in_progress.has(wu));
110
+ if (readyAndInProgress.length > 0) {
111
+ errors.push(`⚠️ ${readyAndInProgress.length} WU(s) are in BOTH Ready and In Progress sections:${STRING_LITERALS.NEWLINE}${readyAndInProgress
112
+ .map((wu) => ` - ${wu}`)
113
+ .join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
114
+ ` This is normal during wu:claim before commit.${STRING_LITERALS.NEWLINE}` +
115
+ ` If you see this error after commit, wu:claim did not remove from Ready.${STRING_LITERALS.NEWLINE}` +
116
+ ` Fix: Remove from Ready section`);
117
+ }
118
+ // WU-1137: Check for parent-only WUs in Ready section (sub-lane format preferred)
119
+ const projectRoot = getProjectRoot(import.meta.url);
120
+ const wuDir = path.join(path.dirname(backlogPath), 'wu');
121
+ const parentOnlyWUs = [];
122
+ for (const wuId of sections.ready) {
123
+ const wuPath = path.join(wuDir, `${wuId}.yaml`);
124
+ if (!existsSync(wuPath)) {
125
+ continue; // Skip missing WU files (handled by other validators)
126
+ }
127
+ try {
128
+ const wuContent = readFileSync(wuPath, { encoding: 'utf-8' });
129
+ const wuDoc = yaml.load(wuContent);
130
+ if (wuDoc && wuDoc.lane) {
131
+ const lane = wuDoc.lane.toString().trim();
132
+ const hasColon = lane.includes(':');
133
+ // If parent-only format (no colon) and parent has sub-lane taxonomy, flag it
134
+ if (!hasColon) {
135
+ const parent = extractParent(lane);
136
+ if (hasSubLaneTaxonomy(parent, projectRoot)) {
137
+ parentOnlyWUs.push({ wuId, lane, parent });
138
+ }
139
+ }
140
+ }
141
+ }
142
+ catch {
143
+ // Skip WU files that can't be parsed (handled by other validators)
144
+ continue;
145
+ }
146
+ }
147
+ if (parentOnlyWUs.length > 0) {
148
+ errors.push(`⚠️ ${parentOnlyWUs.length} WU(s) in Ready section use parent-only lane format (sub-lane format preferred):${STRING_LITERALS.NEWLINE}${parentOnlyWUs
149
+ .map((wu) => ` - ${wu.wuId}: "${wu.lane}" (parent has sub-lanes)`)
150
+ .join(STRING_LITERALS.NEWLINE)}${STRING_LITERALS.DOUBLE_NEWLINE}` +
151
+ ` Fix: Migrate to sub-lane format using:${STRING_LITERALS.NEWLINE}` +
152
+ ` pnpm wu:infer-lane --id WU-123 # Suggest a sub-lane${STRING_LITERALS.NEWLINE}` +
153
+ ` pnpm wu:edit --id WU-123 --lane "Parent: Sub"${STRING_LITERALS.NEWLINE}` +
154
+ ` See: docs/04-operations/_frameworks/lumenflow/sub-lanes.md`);
155
+ }
156
+ return {
157
+ valid: errors.length === 0,
158
+ errors,
159
+ stats: {
160
+ ready: sections.ready.size,
161
+ inProgress: sections.in_progress.size,
162
+ blocked: sections.blocked.size,
163
+ done: sections.done.size,
164
+ duplicates: doneAndReady.length + doneAndInProgress.length + readyAndInProgress.length,
165
+ parentOnlyInReady: parentOnlyWUs.length,
166
+ },
167
+ };
168
+ }
169
+ /**
170
+ * Fix backlog duplicates by removing WUs from non-authoritative sections
171
+ * - If WU in Done AND Ready: remove from Ready (Done is authoritative)
172
+ * - If WU in Done AND InProgress: remove from InProgress (Done is authoritative)
173
+ *
174
+ * Part of WU-1303: Backlog duplicate entries after rebase conflicts
175
+ * WU-1506: Added returnContent option for atomic in-memory validation
176
+ *
177
+ * @param {string} backlogPath - Path to backlog.md file
178
+ * @param {FixBacklogDuplicatesOptions} options - Fix options
179
+ * @returns {{fixed: boolean, removed: Array<{wu: string, section: string}>, backupPath?: string, content?: string}}
180
+ */
181
+ export function fixBacklogDuplicates(backlogPath, options = {}) {
182
+ const { dryRun = false, returnContent = false } = options;
183
+ // Parse frontmatter to get configured section headings
184
+ let frontmatter, markdown;
185
+ try {
186
+ ({ frontmatter, markdown } = parseBacklogFrontmatter(backlogPath));
187
+ }
188
+ catch (err) {
189
+ return { fixed: false, removed: [], error: err.message };
190
+ }
191
+ const headings = frontmatter ? getSectionHeadings(frontmatter) : {};
192
+ const lines = markdown.split(/\r?\n/);
193
+ // Track section boundaries and WU locations
194
+ const sections = {
195
+ ready: { wus: new Set(), lineNumbers: new Map() },
196
+ in_progress: { wus: new Set(), lineNumbers: new Map() },
197
+ blocked: { wus: new Set(), lineNumbers: new Map() },
198
+ done: { wus: new Set(), lineNumbers: new Map() },
199
+ };
200
+ let currentSection = null;
201
+ // Build heading-to-section map
202
+ const headingMap = new Map();
203
+ for (const [sectionName, heading] of Object.entries(headings)) {
204
+ headingMap.set(heading, sectionName);
205
+ }
206
+ // WU-1334: Same pattern as validateBacklogSync - only match list items
207
+ const BACKLOG_ITEM_PATTERN = /^\s*[-*]\s*(?:\[[ x]\]\s*)?\[?(WU-\d+)/i;
208
+ // Parse sections and track line numbers for each WU
209
+ for (let i = 0; i < lines.length; i++) {
210
+ const line = lines[i];
211
+ if (headingMap.has(line)) {
212
+ currentSection = headingMap.get(line);
213
+ continue;
214
+ }
215
+ // Reset section on other ## headings
216
+ if (line.trim().startsWith('## ') && !line.trim().startsWith('### ')) {
217
+ currentSection = null;
218
+ continue;
219
+ }
220
+ if (currentSection && sections[currentSection]) {
221
+ const match = line.match(BACKLOG_ITEM_PATTERN);
222
+ if (match) {
223
+ const wuId = match[1].toUpperCase();
224
+ sections[currentSection].wus.add(wuId);
225
+ // Store line number (can have multiple lines per WU, but we just need one to identify the entry)
226
+ if (!sections[currentSection].lineNumbers.has(wuId)) {
227
+ sections[currentSection].lineNumbers.set(wuId, i);
228
+ }
229
+ }
230
+ }
231
+ }
232
+ // Find duplicates that need removal
233
+ const linesToRemove = new Set();
234
+ const removed = [];
235
+ // Done + Ready: remove from Ready
236
+ for (const wu of sections.done.wus) {
237
+ if (sections.ready.wus.has(wu)) {
238
+ const lineNum = sections.ready.lineNumbers.get(wu);
239
+ if (lineNum !== undefined) {
240
+ linesToRemove.add(lineNum);
241
+ removed.push({ wu, section: 'ready' });
242
+ }
243
+ }
244
+ }
245
+ // Done + InProgress: remove from InProgress
246
+ for (const wu of sections.done.wus) {
247
+ if (sections.in_progress.wus.has(wu)) {
248
+ const lineNum = sections.in_progress.lineNumbers.get(wu);
249
+ if (lineNum !== undefined) {
250
+ linesToRemove.add(lineNum);
251
+ removed.push({ wu, section: 'in_progress' });
252
+ }
253
+ }
254
+ }
255
+ // No duplicates to fix
256
+ if (removed.length === 0) {
257
+ return { fixed: false, removed: [], message: 'No duplicates found' };
258
+ }
259
+ // Remove duplicate lines (filter out the lines to remove)
260
+ const newLines = lines.filter((_, index) => !linesToRemove.has(index));
261
+ // Reconstruct file with frontmatter
262
+ const originalContent = readFileSync(backlogPath, { encoding: 'utf-8' });
263
+ const frontmatterMatch = originalContent.match(/^---\n[\s\S]*?\n---\n/);
264
+ const frontmatterContent = frontmatterMatch ? frontmatterMatch[0] : STRING_LITERALS.EMPTY;
265
+ const newContent = frontmatterContent + newLines.join(STRING_LITERALS.NEWLINE);
266
+ // WU-1506: Return content without writing when returnContent is true
267
+ if (returnContent) {
268
+ return {
269
+ fixed: true,
270
+ removed,
271
+ content: newContent,
272
+ message: `Would remove ${removed.length} duplicate(s)`,
273
+ };
274
+ }
275
+ // Dry run - report what would change
276
+ if (dryRun) {
277
+ return {
278
+ fixed: false,
279
+ removed,
280
+ dryRun: true,
281
+ message: `Would remove ${removed.length} duplicate(s)`,
282
+ };
283
+ }
284
+ // Create backup before modifying
285
+ const backupPath = `${backlogPath}.bak`;
286
+ copyFileSync(backlogPath, backupPath);
287
+ writeFileSync(backlogPath, newContent, { encoding: 'utf-8' });
288
+ return {
289
+ fixed: true,
290
+ removed,
291
+ backupPath,
292
+ message: `Removed ${removed.length} duplicate(s). Backup at ${backupPath}`,
293
+ };
294
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Branch drift detection and graduated warning system
3
+ *
4
+ * WU-1370: Graduated warnings for branch drift
5
+ * - < 10 commits behind: OK (no message)
6
+ * - 10-14 commits behind: INFO ("Consider rebasing")
7
+ * - 15-19 commits behind: WARNING ("Rebase recommended")
8
+ * - >= 20 commits behind: ERROR (hard block, "Must rebase")
9
+ *
10
+ * @see {@link THRESHOLDS} for configurable threshold values
11
+ */
12
+ /**
13
+ * Drift level constants
14
+ * Used as return values from getDriftLevel()
15
+ */
16
+ export declare const DRIFT_LEVELS: {
17
+ OK: string;
18
+ INFO: string;
19
+ WARNING: string;
20
+ ERROR: string;
21
+ };
22
+ /**
23
+ * Calculate drift level based on number of commits behind main
24
+ *
25
+ * @param {number} commitsBehind - Number of commits behind main (from git rev-list --count)
26
+ * @returns {'ok' | 'info' | 'warning' | 'error'} Drift severity level
27
+ *
28
+ * @example
29
+ * getDriftLevel(5) // 'ok' - no warning needed
30
+ * getDriftLevel(10) // 'info' - suggest rebasing
31
+ * getDriftLevel(15) // 'warning' - recommend rebasing
32
+ * getDriftLevel(20) // 'error' - hard block, must rebase
33
+ */
34
+ export declare function getDriftLevel(commitsBehind: any): string;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Branch drift detection and graduated warning system
3
+ *
4
+ * WU-1370: Graduated warnings for branch drift
5
+ * - < 10 commits behind: OK (no message)
6
+ * - 10-14 commits behind: INFO ("Consider rebasing")
7
+ * - 15-19 commits behind: WARNING ("Rebase recommended")
8
+ * - >= 20 commits behind: ERROR (hard block, "Must rebase")
9
+ *
10
+ * @see {@link THRESHOLDS} for configurable threshold values
11
+ */
12
+ import { THRESHOLDS } from './wu-constants.js';
13
+ /**
14
+ * Drift level constants
15
+ * Used as return values from getDriftLevel()
16
+ */
17
+ export const DRIFT_LEVELS = {
18
+ OK: 'ok',
19
+ INFO: 'info',
20
+ WARNING: 'warning',
21
+ ERROR: 'error',
22
+ };
23
+ /**
24
+ * Calculate drift level based on number of commits behind main
25
+ *
26
+ * @param {number} commitsBehind - Number of commits behind main (from git rev-list --count)
27
+ * @returns {'ok' | 'info' | 'warning' | 'error'} Drift severity level
28
+ *
29
+ * @example
30
+ * getDriftLevel(5) // 'ok' - no warning needed
31
+ * getDriftLevel(10) // 'info' - suggest rebasing
32
+ * getDriftLevel(15) // 'warning' - recommend rebasing
33
+ * getDriftLevel(20) // 'error' - hard block, must rebase
34
+ */
35
+ export function getDriftLevel(commitsBehind) {
36
+ // Handle edge cases: negative or non-integer values
37
+ const commits = Math.floor(commitsBehind);
38
+ if (commits < 0) {
39
+ return DRIFT_LEVELS.OK;
40
+ }
41
+ if (commits >= THRESHOLDS.BRANCH_DRIFT_MAX) {
42
+ return DRIFT_LEVELS.ERROR;
43
+ }
44
+ if (commits >= THRESHOLDS.BRANCH_DRIFT_WARNING) {
45
+ return DRIFT_LEVELS.WARNING;
46
+ }
47
+ if (commits >= THRESHOLDS.BRANCH_DRIFT_INFO) {
48
+ return DRIFT_LEVELS.INFO;
49
+ }
50
+ return DRIFT_LEVELS.OK;
51
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * WU-2278: Cleanup Install Configuration
3
+ *
4
+ * Provides configuration for pnpm install during wu:done cleanup.
5
+ * - 60 second timeout to prevent indefinite hangs
6
+ * - CI=true for non-interactive mode
7
+ * - frozen-lockfile to prevent lockfile mutations
8
+ *
9
+ * @module cleanup-install-config
10
+ */
11
+ /**
12
+ * Timeout for cleanup install operation (ms)
13
+ * @constant {number}
14
+ */
15
+ export declare const CLEANUP_INSTALL_TIMEOUT_MS = 60000;
16
+ /**
17
+ * Get configuration for cleanup pnpm install
18
+ *
19
+ * Returns command and options suitable for execAsync:
20
+ * - CI=true environment variable for non-interactive mode
21
+ * - 60 second timeout to prevent hangs
22
+ * - frozen-lockfile flag to prevent mutations
23
+ *
24
+ * @returns {{ command: string, timeout: number, env: object }}
25
+ */
26
+ export declare function getCleanupInstallConfig(): {
27
+ command: string;
28
+ timeout: number;
29
+ env: {
30
+ CI: string;
31
+ TZ?: string | undefined;
32
+ };
33
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * WU-2278: Cleanup Install Configuration
3
+ *
4
+ * Provides configuration for pnpm install during wu:done cleanup.
5
+ * - 60 second timeout to prevent indefinite hangs
6
+ * - CI=true for non-interactive mode
7
+ * - frozen-lockfile to prevent lockfile mutations
8
+ *
9
+ * @module cleanup-install-config
10
+ */
11
+ import { PKG_MANAGER, PKG_COMMANDS, PKG_FLAGS } from './wu-constants.js';
12
+ /**
13
+ * Timeout for cleanup install operation (ms)
14
+ * @constant {number}
15
+ */
16
+ export const CLEANUP_INSTALL_TIMEOUT_MS = 60000; // 60 seconds
17
+ /**
18
+ * Get configuration for cleanup pnpm install
19
+ *
20
+ * Returns command and options suitable for execAsync:
21
+ * - CI=true environment variable for non-interactive mode
22
+ * - 60 second timeout to prevent hangs
23
+ * - frozen-lockfile flag to prevent mutations
24
+ *
25
+ * @returns {{ command: string, timeout: number, env: object }}
26
+ */
27
+ export function getCleanupInstallConfig() {
28
+ const command = `${PKG_MANAGER} ${PKG_COMMANDS.INSTALL} ${PKG_FLAGS.FROZEN_LOCKFILE}`;
29
+ return {
30
+ command,
31
+ timeout: CLEANUP_INSTALL_TIMEOUT_MS,
32
+ env: {
33
+ ...process.env,
34
+ CI: 'true', // Non-interactive mode
35
+ },
36
+ };
37
+ }