@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,97 @@
1
+ /**
2
+ * WU YAML Auto-Fixer
3
+ *
4
+ * Detects and auto-repairs common YAML validation issues that would fail at wu:done.
5
+ * Part of WU-1359: Early YAML validation at wu:claim.
6
+ *
7
+ * Common issues fixed:
8
+ * - ISO timestamp dates → YYYY-MM-DD format
9
+ * - Username → email format (with configurable domain)
10
+ * - docs → documentation (type field)
11
+ * - String numbers → number (phase field)
12
+ *
13
+ * @see {@link tools/lib/wu-schema.mjs} - WU schema definition
14
+ * @see {@link tools/wu-claim.mjs} - Consumer
15
+ */
16
+ /**
17
+ * Issue types that can be detected and auto-fixed
18
+ */
19
+ export declare const FIXABLE_ISSUES: {
20
+ DATE_ISO_TIMESTAMP: string;
21
+ USERNAME_NOT_EMAIL: string;
22
+ TYPE_ALIAS: string;
23
+ PHASE_STRING: string;
24
+ PRIORITY_LOWERCASE: string;
25
+ };
26
+ /**
27
+ * Detects fixable issues in WU YAML data
28
+ *
29
+ * @param {object} doc - Parsed WU YAML data
30
+ * @returns {Array<{type: string, field: string, current: unknown, suggested: unknown, description: string}>}
31
+ */
32
+ export declare function detectFixableIssues(doc: any): {
33
+ type: string;
34
+ field: string;
35
+ current: any;
36
+ suggested: any;
37
+ description: string;
38
+ }[];
39
+ /**
40
+ * Applies fixes to WU YAML data in-place
41
+ *
42
+ * @param {object} doc - Parsed WU YAML data (will be modified)
43
+ * @param {Array<{type: string, field: string, suggested: unknown}>} issues - Issues to fix
44
+ * @returns {number} Number of fixes applied
45
+ */
46
+ export declare function applyFixes(doc: any, issues: any): number;
47
+ /**
48
+ * Options for auto-fixing WU YAML
49
+ */
50
+ export interface AutoFixWUYamlOptions {
51
+ /** If true, report issues without fixing */
52
+ dryRun?: boolean;
53
+ /** If true, create .bak file before fixing */
54
+ backup?: boolean;
55
+ }
56
+ /**
57
+ * Auto-fix WU YAML file
58
+ *
59
+ * @param {string} wuPath - Path to WU YAML file
60
+ * @param {AutoFixWUYamlOptions} options - Options
61
+ * @returns {{fixed: number, issues: Array, backupPath?: string}}
62
+ */
63
+ export declare function autoFixWUYaml(wuPath: any, options?: AutoFixWUYamlOptions): {
64
+ fixed: number;
65
+ issues: any[];
66
+ wouldFix?: undefined;
67
+ backupPath?: undefined;
68
+ } | {
69
+ fixed: number;
70
+ issues: {
71
+ type: string;
72
+ field: string;
73
+ current: any;
74
+ suggested: any;
75
+ description: string;
76
+ }[];
77
+ wouldFix: number;
78
+ backupPath?: undefined;
79
+ } | {
80
+ fixed: number;
81
+ issues: {
82
+ type: string;
83
+ field: string;
84
+ current: any;
85
+ suggested: any;
86
+ description: string;
87
+ }[];
88
+ backupPath: any;
89
+ wouldFix?: undefined;
90
+ };
91
+ /**
92
+ * Format issues for CLI output
93
+ *
94
+ * @param {Array<{field: string, description: string}>} issues
95
+ * @returns {string}
96
+ */
97
+ export declare function formatIssues(issues: any): any;
@@ -0,0 +1,264 @@
1
+ /**
2
+ * WU YAML Auto-Fixer
3
+ *
4
+ * Detects and auto-repairs common YAML validation issues that would fail at wu:done.
5
+ * Part of WU-1359: Early YAML validation at wu:claim.
6
+ *
7
+ * Common issues fixed:
8
+ * - ISO timestamp dates → YYYY-MM-DD format
9
+ * - Username → email format (with configurable domain)
10
+ * - docs → documentation (type field)
11
+ * - String numbers → number (phase field)
12
+ *
13
+ * @see {@link tools/lib/wu-schema.mjs} - WU schema definition
14
+ * @see {@link tools/wu-claim.mjs} - Consumer
15
+ */
16
+ import { readFileSync, writeFileSync, copyFileSync } from 'node:fs';
17
+ import { parseYAML, stringifyYAML } from './wu-yaml.js';
18
+ import { STRING_LITERALS } from './wu-constants.js';
19
+ // Valid type values from wu-schema.mjs
20
+ const VALID_TYPES = ['feature', 'bug', 'documentation', 'process', 'tooling', 'chore', 'refactor'];
21
+ // Common type aliases that should be auto-fixed
22
+ const TYPE_ALIASES = {
23
+ docs: 'documentation',
24
+ doc: 'documentation',
25
+ feat: 'feature',
26
+ fix: 'bug',
27
+ bugfix: 'bug',
28
+ tool: 'tooling',
29
+ tools: 'tooling',
30
+ ref: 'refactor',
31
+ proc: 'process',
32
+ };
33
+ // Default email domain for username → email conversion
34
+ const DEFAULT_EMAIL_DOMAIN = 'patientpath.co.uk';
35
+ /**
36
+ * Issue types that can be detected and auto-fixed
37
+ */
38
+ export const FIXABLE_ISSUES = {
39
+ DATE_ISO_TIMESTAMP: 'DATE_ISO_TIMESTAMP',
40
+ USERNAME_NOT_EMAIL: 'USERNAME_NOT_EMAIL',
41
+ TYPE_ALIAS: 'TYPE_ALIAS',
42
+ PHASE_STRING: 'PHASE_STRING',
43
+ PRIORITY_LOWERCASE: 'PRIORITY_LOWERCASE',
44
+ };
45
+ /**
46
+ * Check created field for ISO timestamp issues
47
+ * @param {object} doc - Parsed WU YAML data
48
+ * @returns {object|null} Issue object or null
49
+ */
50
+ function checkCreatedField(doc) {
51
+ if (!doc.created)
52
+ return null;
53
+ const createdStr = String(doc.created);
54
+ // Match ISO 8601 timestamp (2025-12-02T00:00:00.000Z or similar)
55
+ if (/^\d{4}-\d{2}-\d{2}T/.test(createdStr)) {
56
+ const dateOnly = createdStr.slice(0, 10);
57
+ return {
58
+ type: FIXABLE_ISSUES.DATE_ISO_TIMESTAMP,
59
+ field: 'created',
60
+ current: createdStr,
61
+ suggested: dateOnly,
62
+ description: `ISO timestamp should be YYYY-MM-DD: ${createdStr} → ${dateOnly}`,
63
+ };
64
+ }
65
+ // Handle Date objects that YAML parser may create
66
+ if (doc.created instanceof Date) {
67
+ const dateOnly = doc.created.toISOString().slice(0, 10);
68
+ return {
69
+ type: FIXABLE_ISSUES.DATE_ISO_TIMESTAMP,
70
+ field: 'created',
71
+ current: doc.created.toISOString(),
72
+ suggested: dateOnly,
73
+ description: `Date object should be YYYY-MM-DD string: ${doc.created.toISOString()} → ${dateOnly}`,
74
+ };
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Check assigned_to field for username without email domain
80
+ * @param {object} doc - Parsed WU YAML data
81
+ * @returns {object|null} Issue object or null
82
+ */
83
+ function checkAssignedToField(doc) {
84
+ if (!doc.assigned_to || typeof doc.assigned_to !== 'string')
85
+ return null;
86
+ const assignee = doc.assigned_to.trim();
87
+ if (assignee && !assignee.includes('@')) {
88
+ const suggested = `${assignee}@${DEFAULT_EMAIL_DOMAIN}`;
89
+ return {
90
+ type: FIXABLE_ISSUES.USERNAME_NOT_EMAIL,
91
+ field: 'assigned_to',
92
+ current: assignee,
93
+ suggested,
94
+ description: `Username should be email format: ${assignee} → ${suggested}`,
95
+ };
96
+ }
97
+ return null;
98
+ }
99
+ /**
100
+ * Check type field for common aliases or typos
101
+ * @param {object} doc - Parsed WU YAML data
102
+ * @returns {object|null} Issue object or null
103
+ */
104
+ function checkTypeField(doc) {
105
+ if (!doc.type || typeof doc.type !== 'string')
106
+ return null;
107
+ const typeLower = doc.type.toLowerCase();
108
+ if (TYPE_ALIASES[typeLower]) {
109
+ return {
110
+ type: FIXABLE_ISSUES.TYPE_ALIAS,
111
+ field: 'type',
112
+ current: doc.type,
113
+ suggested: TYPE_ALIASES[typeLower],
114
+ description: `Type alias should use canonical form: ${doc.type} → ${TYPE_ALIASES[typeLower]}`,
115
+ };
116
+ }
117
+ // Check for invalid type not in aliases - try fuzzy match
118
+ if (!VALID_TYPES.includes(typeLower)) {
119
+ const closest = VALID_TYPES.find((t) => t.startsWith(typeLower.slice(0, 3)) || typeLower.startsWith(t.slice(0, 3)));
120
+ if (closest) {
121
+ return {
122
+ type: FIXABLE_ISSUES.TYPE_ALIAS,
123
+ field: 'type',
124
+ current: doc.type,
125
+ suggested: closest,
126
+ description: `Invalid type "${doc.type}" - did you mean "${closest}"?`,
127
+ };
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * Check phase field for string instead of number
134
+ * @param {object} doc - Parsed WU YAML data
135
+ * @returns {object|null} Issue object or null
136
+ */
137
+ function checkPhaseField(doc) {
138
+ if (doc.phase == null || typeof doc.phase !== 'string')
139
+ return null;
140
+ const num = parseInt(doc.phase, 10);
141
+ if (!isNaN(num) && num > 0) {
142
+ return {
143
+ type: FIXABLE_ISSUES.PHASE_STRING,
144
+ field: 'phase',
145
+ current: doc.phase,
146
+ suggested: num,
147
+ description: `Phase should be number: "${doc.phase}" → ${num}`,
148
+ };
149
+ }
150
+ return null;
151
+ }
152
+ /**
153
+ * Check priority field for lowercase
154
+ * @param {object} doc - Parsed WU YAML data
155
+ * @returns {object|null} Issue object or null
156
+ */
157
+ function checkPriorityField(doc) {
158
+ if (!doc.priority || typeof doc.priority !== 'string')
159
+ return null;
160
+ const upper = doc.priority.toUpperCase();
161
+ if (doc.priority !== upper && /^P[0-3]$/i.test(doc.priority)) {
162
+ return {
163
+ type: FIXABLE_ISSUES.PRIORITY_LOWERCASE,
164
+ field: 'priority',
165
+ current: doc.priority,
166
+ suggested: upper,
167
+ description: `Priority should be uppercase: ${doc.priority} → ${upper}`,
168
+ };
169
+ }
170
+ return null;
171
+ }
172
+ /**
173
+ * Detects fixable issues in WU YAML data
174
+ *
175
+ * @param {object} doc - Parsed WU YAML data
176
+ * @returns {Array<{type: string, field: string, current: unknown, suggested: unknown, description: string}>}
177
+ */
178
+ export function detectFixableIssues(doc) {
179
+ const checks = [
180
+ checkCreatedField,
181
+ checkAssignedToField,
182
+ checkTypeField,
183
+ checkPhaseField,
184
+ checkPriorityField,
185
+ ];
186
+ return checks.map((check) => check(doc)).filter((issue) => issue !== null);
187
+ }
188
+ /**
189
+ * Applies fixes to WU YAML data in-place
190
+ *
191
+ * @param {object} doc - Parsed WU YAML data (will be modified)
192
+ * @param {Array<{type: string, field: string, suggested: unknown}>} issues - Issues to fix
193
+ * @returns {number} Number of fixes applied
194
+ */
195
+ export function applyFixes(doc, issues) {
196
+ let fixed = 0;
197
+ for (const issue of issues) {
198
+ switch (issue.type) {
199
+ case FIXABLE_ISSUES.DATE_ISO_TIMESTAMP:
200
+ doc[issue.field] = issue.suggested;
201
+ fixed++;
202
+ break;
203
+ case FIXABLE_ISSUES.USERNAME_NOT_EMAIL:
204
+ doc[issue.field] = issue.suggested;
205
+ fixed++;
206
+ break;
207
+ case FIXABLE_ISSUES.TYPE_ALIAS:
208
+ doc[issue.field] = issue.suggested;
209
+ fixed++;
210
+ break;
211
+ case FIXABLE_ISSUES.PHASE_STRING:
212
+ doc[issue.field] = issue.suggested;
213
+ fixed++;
214
+ break;
215
+ case FIXABLE_ISSUES.PRIORITY_LOWERCASE:
216
+ doc[issue.field] = issue.suggested;
217
+ fixed++;
218
+ break;
219
+ }
220
+ }
221
+ return fixed;
222
+ }
223
+ /**
224
+ * Auto-fix WU YAML file
225
+ *
226
+ * @param {string} wuPath - Path to WU YAML file
227
+ * @param {AutoFixWUYamlOptions} options - Options
228
+ * @returns {{fixed: number, issues: Array, backupPath?: string}}
229
+ */
230
+ export function autoFixWUYaml(wuPath, options = {}) {
231
+ const { dryRun = false, backup = true } = options;
232
+ // Read and parse
233
+ const text = readFileSync(wuPath, { encoding: 'utf-8' });
234
+ const doc = parseYAML(text);
235
+ // Detect issues
236
+ const issues = detectFixableIssues(doc);
237
+ if (issues.length === 0) {
238
+ return { fixed: 0, issues: [] };
239
+ }
240
+ if (dryRun) {
241
+ return { fixed: 0, issues, wouldFix: issues.length };
242
+ }
243
+ // Create backup if requested
244
+ let backupPath;
245
+ if (backup) {
246
+ backupPath = `${wuPath}.bak`;
247
+ copyFileSync(wuPath, backupPath);
248
+ }
249
+ // Apply fixes
250
+ const fixed = applyFixes(doc, issues);
251
+ // Write back
252
+ const newText = stringifyYAML(doc);
253
+ writeFileSync(wuPath, newText, { encoding: 'utf-8' });
254
+ return { fixed, issues, backupPath };
255
+ }
256
+ /**
257
+ * Format issues for CLI output
258
+ *
259
+ * @param {Array<{field: string, description: string}>} issues
260
+ * @returns {string}
261
+ */
262
+ export function formatIssues(issues) {
263
+ return issues.map((i) => ` - ${i.field}: ${i.description}`).join(STRING_LITERALS.NEWLINE);
264
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * WU-1352: Standardized YAML stringify options.
3
+ *
4
+ * Ensures consistent output across all WU tools:
5
+ * - lineWidth: 100 (wrap long lines)
6
+ * - singleQuote: true (prefer 'single' over "double")
7
+ * - defaultKeyType: PLAIN (unquoted keys when safe)
8
+ *
9
+ * @type {import('yaml').ToStringOptions}
10
+ */
11
+ export declare const YAML_STRINGIFY_OPTIONS: Readonly<{
12
+ lineWidth: 100;
13
+ singleQuote: true;
14
+ defaultKeyType: "PLAIN";
15
+ }>;
16
+ /**
17
+ * Read and parse WU YAML file.
18
+ *
19
+ * Validates:
20
+ * - File exists
21
+ * - YAML is valid
22
+ * - WU ID matches expected ID
23
+ *
24
+ * @param {string} wuPath - Path to WU YAML file
25
+ * @param {string} expectedId - Expected WU ID (e.g., 'WU-123')
26
+ * @returns {object} Parsed YAML document
27
+ * @throws {Error} If file not found, YAML invalid, or ID mismatch
28
+ */
29
+ export declare function readWU(wuPath: any, expectedId: any): any;
30
+ /**
31
+ * Parse YAML string to object.
32
+ * WU-1352: Centralized YAML parsing for consistency.
33
+ *
34
+ * @param {string} text - YAML string to parse
35
+ * @returns {object} Parsed object
36
+ * @throws {Error} If YAML is invalid
37
+ */
38
+ export declare function parseYAML(text: any): any;
39
+ /**
40
+ * Stringify object to YAML with standardized options.
41
+ * WU-1352: Centralized YAML serialization for consistency.
42
+ *
43
+ * @param {object} doc - Object to stringify
44
+ * @param {object} [options] - Additional stringify options (merged with YAML_STRINGIFY_OPTIONS)
45
+ * @returns {string} YAML string
46
+ */
47
+ export declare function stringifyYAML(doc: any, options?: {}): string;
48
+ /**
49
+ * Read and parse YAML file without ID validation.
50
+ * WU-1352: For cases where you don't know/need to validate the WU ID.
51
+ *
52
+ * @param {string} yamlPath - Path to YAML file
53
+ * @returns {object} Parsed YAML document
54
+ * @throws {Error} If file not found or YAML invalid
55
+ */
56
+ export declare function readWURaw(yamlPath: any): any;
57
+ /**
58
+ * Write WU YAML file with consistent formatting.
59
+ * WU-1352: Uses YAML_STRINGIFY_OPTIONS for consistent output.
60
+ *
61
+ * @param {string} wuPath - Path to WU YAML file
62
+ * @param {object} doc - YAML document to write
63
+ */
64
+ export declare function writeWU(wuPath: any, doc: any): void;
65
+ /**
66
+ * Append note to WU document's notes field.
67
+ *
68
+ * Always outputs a string (Zod schema requires string type).
69
+ * Handles various input formats:
70
+ * - undefined/null/empty: Set note directly as string
71
+ * - string: Append with newline separator
72
+ * - array: Convert to newline-separated string, then append
73
+ * - other: Replace with note
74
+ *
75
+ * @param {object} doc - WU document
76
+ * @param {string} note - Note to append
77
+ */
78
+ export declare function appendNote(doc: any, note: any): void;
79
+ /**
80
+ * Append an agent session entry to a WU's agent_sessions[] array
81
+ *
82
+ * @param {string} wuId - WU ID (e.g., "WU-1234")
83
+ * @param {object} sessionData - Session summary from endSession()
84
+ * @throws {Error} if WU file not found
85
+ */
86
+ export declare function appendAgentSession(wuId: any, sessionData: any): void;
@@ -0,0 +1,222 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { parse, stringify } from 'yaml';
3
+ import { createError, ErrorCodes } from './error-handler.js';
4
+ import { STRING_LITERALS } from './wu-constants.js';
5
+ /**
6
+ * Unified WU YAML I/O module.
7
+ *
8
+ * Replaces 4 duplicate read/write functions scattered across wu-claim, wu-done,
9
+ * wu-block, and wu-unblock. Single source of truth for WU YAML operations.
10
+ *
11
+ * WU-1352: Standardized on yaml v2.8.1 (eemeli) with consistent stringify options.
12
+ *
13
+ * Functions:
14
+ * - readWU(path, id): Read and validate WU YAML
15
+ * - readWURaw(path): Read YAML without ID validation
16
+ * - parseYAML(text): Parse YAML string to object
17
+ * - writeWU(path, doc): Write WU YAML with consistent formatting
18
+ * - stringifyYAML(doc): Stringify object to YAML with consistent formatting
19
+ * - appendNote(doc, note): Append note to doc.notes field
20
+ *
21
+ * @example
22
+ * import { readWU, writeWU, appendNote } from './lib/wu-yaml.js';
23
+ *
24
+ * // Read WU
25
+ * const doc = readWU('docs/04-operations/tasks/wu/WU-123.yaml', 'WU-123');
26
+ *
27
+ * // Modify doc
28
+ * doc.status = 'in_progress';
29
+ * appendNote(doc, 'Started work on this WU');
30
+ *
31
+ * // Write back
32
+ * writeWU('docs/04-operations/tasks/wu/WU-123.yaml', doc);
33
+ */
34
+ /**
35
+ * WU-1352: YAML scalar type constants (from yaml library).
36
+ * These match the string values expected by the yaml package.
37
+ * @see https://github.com/eemeli/yaml/blob/main/docs/03_options.md
38
+ */
39
+ const YAML_SCALAR_TYPES = Object.freeze({
40
+ /** Unquoted string (when safe) */
41
+ PLAIN: 'PLAIN',
42
+ /** Single-quoted string */
43
+ QUOTE_SINGLE: 'QUOTE_SINGLE',
44
+ /** Double-quoted string */
45
+ QUOTE_DOUBLE: 'QUOTE_DOUBLE',
46
+ /** Block literal (|) */
47
+ BLOCK_LITERAL: 'BLOCK_LITERAL',
48
+ /** Block folded (>) */
49
+ BLOCK_FOLDED: 'BLOCK_FOLDED',
50
+ });
51
+ /** Standard line width for WU YAML files */
52
+ const YAML_LINE_WIDTH = 100;
53
+ /**
54
+ * WU-1352: Standardized YAML stringify options.
55
+ *
56
+ * Ensures consistent output across all WU tools:
57
+ * - lineWidth: 100 (wrap long lines)
58
+ * - singleQuote: true (prefer 'single' over "double")
59
+ * - defaultKeyType: PLAIN (unquoted keys when safe)
60
+ *
61
+ * @type {import('yaml').ToStringOptions}
62
+ */
63
+ export const YAML_STRINGIFY_OPTIONS = Object.freeze({
64
+ lineWidth: YAML_LINE_WIDTH,
65
+ singleQuote: true,
66
+ defaultKeyType: YAML_SCALAR_TYPES.PLAIN,
67
+ });
68
+ /**
69
+ * Read and parse WU YAML file.
70
+ *
71
+ * Validates:
72
+ * - File exists
73
+ * - YAML is valid
74
+ * - WU ID matches expected ID
75
+ *
76
+ * @param {string} wuPath - Path to WU YAML file
77
+ * @param {string} expectedId - Expected WU ID (e.g., 'WU-123')
78
+ * @returns {object} Parsed YAML document
79
+ * @throws {Error} If file not found, YAML invalid, or ID mismatch
80
+ */
81
+ export function readWU(wuPath, expectedId) {
82
+ if (!existsSync(wuPath)) {
83
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `WU file not found: ${wuPath}`, {
84
+ path: wuPath,
85
+ expectedId,
86
+ });
87
+ }
88
+ const text = readFileSync(wuPath, { encoding: 'utf-8' });
89
+ let doc;
90
+ try {
91
+ doc = parse(text);
92
+ }
93
+ catch (e) {
94
+ throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse YAML ${wuPath}: ${e.message}`, {
95
+ path: wuPath,
96
+ originalError: e.message,
97
+ });
98
+ }
99
+ // Validate ID matches
100
+ if (!doc || doc.id !== expectedId) {
101
+ throw createError(ErrorCodes.WU_NOT_FOUND, `WU YAML id mismatch. Expected ${expectedId}, found ${doc && doc.id}`, { path: wuPath, expectedId, foundId: doc && doc.id });
102
+ }
103
+ return doc;
104
+ }
105
+ /**
106
+ * Parse YAML string to object.
107
+ * WU-1352: Centralized YAML parsing for consistency.
108
+ *
109
+ * @param {string} text - YAML string to parse
110
+ * @returns {object} Parsed object
111
+ * @throws {Error} If YAML is invalid
112
+ */
113
+ export function parseYAML(text) {
114
+ return parse(text);
115
+ }
116
+ /**
117
+ * Stringify object to YAML with standardized options.
118
+ * WU-1352: Centralized YAML serialization for consistency.
119
+ *
120
+ * @param {object} doc - Object to stringify
121
+ * @param {object} [options] - Additional stringify options (merged with YAML_STRINGIFY_OPTIONS)
122
+ * @returns {string} YAML string
123
+ */
124
+ export function stringifyYAML(doc, options = {}) {
125
+ return stringify(doc, { ...YAML_STRINGIFY_OPTIONS, ...options });
126
+ }
127
+ /**
128
+ * Read and parse YAML file without ID validation.
129
+ * WU-1352: For cases where you don't know/need to validate the WU ID.
130
+ *
131
+ * @param {string} yamlPath - Path to YAML file
132
+ * @returns {object} Parsed YAML document
133
+ * @throws {Error} If file not found or YAML invalid
134
+ */
135
+ export function readWURaw(yamlPath) {
136
+ if (!existsSync(yamlPath)) {
137
+ throw createError(ErrorCodes.FILE_NOT_FOUND, `YAML file not found: ${yamlPath}`, {
138
+ path: yamlPath,
139
+ });
140
+ }
141
+ const text = readFileSync(yamlPath, { encoding: 'utf-8' });
142
+ try {
143
+ return parse(text);
144
+ }
145
+ catch (e) {
146
+ throw createError(ErrorCodes.YAML_PARSE_ERROR, `Failed to parse YAML ${yamlPath}: ${e.message}`, {
147
+ path: yamlPath,
148
+ originalError: e.message,
149
+ });
150
+ }
151
+ }
152
+ /**
153
+ * Write WU YAML file with consistent formatting.
154
+ * WU-1352: Uses YAML_STRINGIFY_OPTIONS for consistent output.
155
+ *
156
+ * @param {string} wuPath - Path to WU YAML file
157
+ * @param {object} doc - YAML document to write
158
+ */
159
+ export function writeWU(wuPath, doc) {
160
+ const out = stringify(doc, YAML_STRINGIFY_OPTIONS);
161
+ writeFileSync(wuPath, out, { encoding: 'utf-8' });
162
+ }
163
+ /**
164
+ * Append note to WU document's notes field.
165
+ *
166
+ * Always outputs a string (Zod schema requires string type).
167
+ * Handles various input formats:
168
+ * - undefined/null/empty: Set note directly as string
169
+ * - string: Append with newline separator
170
+ * - array: Convert to newline-separated string, then append
171
+ * - other: Replace with note
172
+ *
173
+ * @param {object} doc - WU document
174
+ * @param {string} note - Note to append
175
+ */
176
+ export function appendNote(doc, note) {
177
+ // Do nothing if note is falsy
178
+ if (!note)
179
+ return;
180
+ const existing = doc.notes;
181
+ if (existing === undefined || existing === null || existing === '') {
182
+ // No existing notes: set directly as string
183
+ doc.notes = note;
184
+ }
185
+ else if (Array.isArray(existing)) {
186
+ // Array notes: convert to string first (schema requires string), then append
187
+ const joined = existing.filter(Boolean).join(STRING_LITERALS.NEWLINE).trimEnd();
188
+ doc.notes = joined ? `${joined}${STRING_LITERALS.NEWLINE}${note}` : note;
189
+ }
190
+ else if (typeof existing === 'string') {
191
+ // String notes: append with newline
192
+ const trimmed = existing.trimEnd();
193
+ doc.notes = trimmed ? `${trimmed}${STRING_LITERALS.NEWLINE}${note}` : note;
194
+ }
195
+ else {
196
+ // Invalid type: replace with note
197
+ doc.notes = note;
198
+ }
199
+ }
200
+ /**
201
+ * Append an agent session entry to a WU's agent_sessions[] array
202
+ *
203
+ * @param {string} wuId - WU ID (e.g., "WU-1234")
204
+ * @param {object} sessionData - Session summary from endSession()
205
+ * @throws {Error} if WU file not found
206
+ */
207
+ export function appendAgentSession(wuId, sessionData) {
208
+ const wuPath = `docs/04-operations/tasks/wu/${wuId}.yaml`;
209
+ if (!existsSync(wuPath)) {
210
+ throw new Error(`WU file not found: ${wuPath}`);
211
+ }
212
+ // Parse WU YAML
213
+ const doc = readWU(wuPath, wuId);
214
+ // Initialize agent_sessions array if needed
215
+ if (!doc.agent_sessions) {
216
+ doc.agent_sessions = [];
217
+ }
218
+ // Append session
219
+ doc.agent_sessions.push(sessionData);
220
+ // Write back
221
+ writeWU(wuPath, doc);
222
+ }