@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,229 @@
1
+ /**
2
+ * Stamp File Utilities
3
+ *
4
+ * Centralized stamp file operations (create, validate)
5
+ * Eliminates magic string for stamp body template
6
+ *
7
+ * WU-2242: Added format validation for corrupted stamp detection
8
+ *
9
+ * Stamp files (.beacon/stamps/WU-{id}.done) serve as completion markers
10
+ * Used by wu:done, wu:recovery, and validation tools
11
+ */
12
+ /* eslint-disable security/detect-non-literal-fs-filename */
13
+ import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
14
+ import { readFile, access } from 'node:fs/promises';
15
+ import { constants } from 'node:fs';
16
+ import path from 'node:path';
17
+ import { WU_PATHS } from './wu-paths.js';
18
+ import { todayISO } from './date-utils.js';
19
+ /**
20
+ * Stamp format error types (WU-2242)
21
+ * @readonly
22
+ * @enum {string}
23
+ */
24
+ export const STAMP_FORMAT_ERRORS = Object.freeze({
25
+ /** Stamp file is empty or contains only whitespace */
26
+ EMPTY_FILE: 'EMPTY_FILE',
27
+ /** Missing WU identifier line (format: WU WU-123 (em dash) Title) */
28
+ MISSING_WU_LINE: 'MISSING_WU_LINE',
29
+ /** Missing Completed: YYYY-MM-DD line */
30
+ MISSING_COMPLETED_LINE: 'MISSING_COMPLETED_LINE',
31
+ /** Date is not in valid YYYY-MM-DD format or is invalid */
32
+ INVALID_DATE_FORMAT: 'INVALID_DATE_FORMAT',
33
+ /** WU ID in stamp does not match expected ID */
34
+ WU_ID_MISMATCH: 'WU_ID_MISMATCH',
35
+ });
36
+ /**
37
+ * Valid date regex: YYYY-MM-DD format
38
+ * @type {RegExp}
39
+ */
40
+ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
41
+ /**
42
+ * Validate that a date string is a valid ISO date
43
+ * @param {string} dateStr - Date string in YYYY-MM-DD format
44
+ * @returns {boolean} True if date is valid
45
+ */
46
+ function isValidDate(dateStr) {
47
+ const match = dateStr.match(DATE_PATTERN);
48
+ if (!match) {
49
+ return false;
50
+ }
51
+ const year = parseInt(match[1], 10);
52
+ const month = parseInt(match[2], 10);
53
+ const day = parseInt(match[3], 10);
54
+ // Basic validation
55
+ if (month < 1 || month > 12) {
56
+ return false;
57
+ }
58
+ // Check day validity for the month
59
+ const daysInMonth = new Date(year, month, 0).getDate();
60
+ if (day < 1 || day > daysInMonth) {
61
+ return false;
62
+ }
63
+ return true;
64
+ }
65
+ /**
66
+ * Stamp file body template (eliminates magic string)
67
+ * Single source of truth for stamp format
68
+ */
69
+ const STAMP_TEMPLATE = (id, title, timestamp) => `WU ${id} — ${title}\nCompleted: ${timestamp}\n`;
70
+ /**
71
+ * Create stamp file (idempotent - safe to call multiple times)
72
+ *
73
+ * @param {object} params - Parameters
74
+ * @param {string} params.id - WU ID (e.g., 'WU-123')
75
+ * @param {string} params.title - WU title
76
+ * @returns {object} Result { created: boolean, path: string, reason?: string }
77
+ */
78
+ export function createStamp({ id, title }) {
79
+ const stampsDir = WU_PATHS.STAMPS_DIR();
80
+ const stampPath = WU_PATHS.STAMP(id);
81
+ // Ensure stamps directory exists
82
+ if (!existsSync(stampsDir)) {
83
+ mkdirSync(stampsDir, { recursive: true });
84
+ }
85
+ // Idempotent: skip if stamp already exists
86
+ if (existsSync(stampPath)) {
87
+ return { created: false, path: stampPath, reason: 'already_exists' };
88
+ }
89
+ // Create stamp file
90
+ const body = STAMP_TEMPLATE(id, title, todayISO());
91
+ writeFileSync(stampPath, body, { encoding: 'utf-8' });
92
+ return { created: true, path: stampPath };
93
+ }
94
+ /**
95
+ * Validate stamp exists
96
+ *
97
+ * @param {string} stampPath - Path to stamp file
98
+ * @returns {boolean} True if stamp exists
99
+ */
100
+ export function validateStamp(stampPath) {
101
+ return existsSync(stampPath);
102
+ }
103
+ /**
104
+ * Get stamp path using WU_PATHS (consistent with codebase)
105
+ *
106
+ * @param {string} id - WU ID
107
+ * @returns {string} Absolute path to stamp file
108
+ */
109
+ export function getStampPath(id) {
110
+ return WU_PATHS.STAMP(id);
111
+ }
112
+ /**
113
+ * Validate WU line in stamp content
114
+ * Checks for format: "WU WU-123 (em dash) Title"
115
+ * @param {string[]} lines - Stamp file lines
116
+ * @param {string} expectedWuId - Expected WU ID
117
+ * @returns {string|null} Error type or null if valid
118
+ */
119
+ function validateWuLine(lines, expectedWuId) {
120
+ const wuLine = lines.find((line) => line.startsWith('WU '));
121
+ if (!wuLine) {
122
+ return STAMP_FORMAT_ERRORS.MISSING_WU_LINE;
123
+ }
124
+ const wuIdMatch = wuLine.match(/^WU (WU-\d+)/);
125
+ if (!wuIdMatch) {
126
+ return STAMP_FORMAT_ERRORS.MISSING_WU_LINE;
127
+ }
128
+ if (wuIdMatch[1] !== expectedWuId) {
129
+ return STAMP_FORMAT_ERRORS.WU_ID_MISMATCH;
130
+ }
131
+ return null;
132
+ }
133
+ /**
134
+ * Validate Completed line in stamp content
135
+ * @param {string[]} lines - Stamp file lines
136
+ * @returns {string|null} Error type or null if valid
137
+ */
138
+ function validateCompletedLine(lines) {
139
+ const completedLine = lines.find((line) => line.startsWith('Completed:'));
140
+ if (!completedLine) {
141
+ return STAMP_FORMAT_ERRORS.MISSING_COMPLETED_LINE;
142
+ }
143
+ const dateMatch = completedLine.match(/^Completed:\s*(.+)/);
144
+ if (!dateMatch) {
145
+ return STAMP_FORMAT_ERRORS.MISSING_COMPLETED_LINE;
146
+ }
147
+ const dateStr = dateMatch[1].trim();
148
+ if (!isValidDate(dateStr)) {
149
+ return STAMP_FORMAT_ERRORS.INVALID_DATE_FORMAT;
150
+ }
151
+ return null;
152
+ }
153
+ /**
154
+ * Validate stamp file format (WU-2242)
155
+ *
156
+ * Expected format:
157
+ * ```
158
+ * WU WU-123 (em dash) Title here
159
+ * Completed: 2025-12-31
160
+ * ```
161
+ *
162
+ * @param {string} wuId - WU ID (e.g., 'WU-123')
163
+ * @param {string} [projectRoot=process.cwd()] - Project root directory
164
+ * @returns {Promise<{valid: boolean, errors: string[], missing?: boolean}>}
165
+ */
166
+ export async function validateStampFormat(wuId, projectRoot = process.cwd()) {
167
+ const stampPath = path.join(projectRoot, WU_PATHS.STAMP(wuId));
168
+ // Check if stamp file exists
169
+ try {
170
+ await access(stampPath, constants.R_OK);
171
+ }
172
+ catch {
173
+ return { valid: false, errors: [], missing: true };
174
+ }
175
+ // Read stamp content
176
+ let content;
177
+ try {
178
+ content = await readFile(stampPath, { encoding: 'utf-8' });
179
+ }
180
+ catch (err) {
181
+ const message = err instanceof Error ? err.message : String(err);
182
+ return { valid: false, errors: [`Failed to read stamp: ${message}`] };
183
+ }
184
+ // Check for empty file
185
+ if (content.trim() === '') {
186
+ return { valid: false, errors: [STAMP_FORMAT_ERRORS.EMPTY_FILE] };
187
+ }
188
+ const lines = content.split('\n');
189
+ const errors = [];
190
+ // Validate WU line
191
+ const wuLineError = validateWuLine(lines, wuId);
192
+ if (wuLineError) {
193
+ errors.push(wuLineError);
194
+ }
195
+ // Validate Completed line
196
+ const completedError = validateCompletedLine(lines);
197
+ if (completedError) {
198
+ errors.push(completedError);
199
+ }
200
+ return { valid: errors.length === 0, errors };
201
+ }
202
+ /**
203
+ * Parse stamp content to extract metadata
204
+ *
205
+ * @param {string} content - Stamp file content
206
+ * @returns {StampMetadata}
207
+ */
208
+ export function parseStampContent(content) {
209
+ const result = {};
210
+ const lines = content.split('\n');
211
+ // Parse WU line
212
+ const wuLine = lines.find((line) => line.startsWith('WU '));
213
+ if (wuLine) {
214
+ const match = wuLine.match(/^WU (WU-\d+)\s*[—-]\s*(.+)/);
215
+ if (match) {
216
+ result.wuId = match[1];
217
+ result.title = match[2].trim();
218
+ }
219
+ }
220
+ // Parse Completed line
221
+ const completedLine = lines.find((line) => line.startsWith('Completed:'));
222
+ if (completedLine) {
223
+ const match = completedLine.match(/^Completed:\s*(.+)/);
224
+ if (match) {
225
+ result.completedDate = match[1].trim();
226
+ }
227
+ }
228
+ return result;
229
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * State Machine Validation Library
3
+ *
4
+ * Enforces canonical WU state transitions according to LumenFlow §2.4
5
+ * Prevents illegal state changes (e.g., done → in_progress) and ensures workflow integrity.
6
+ *
7
+ * Canonical state machine:
8
+ * - ready → in_progress (claim)
9
+ * - in_progress → blocked (block)
10
+ * - in_progress → waiting (implementation complete, awaiting sign-off)
11
+ * - in_progress → done (direct completion)
12
+ * - blocked → in_progress (unblock)
13
+ * - blocked → done (blocker resolved, direct completion)
14
+ * - waiting → in_progress (changes requested)
15
+ * - waiting → done (approved)
16
+ * - done → (terminal, no transitions)
17
+ */
18
+ /**
19
+ * Validates a state transition and throws if illegal
20
+ *
21
+ * @param {string|null|undefined} from - Current WU status
22
+ * @param {string|null|undefined} to - Desired WU status
23
+ * @param {string} wuid - Work Unit ID (e.g., 'WU-416') for error messages
24
+ * @throws {Error} If transition is illegal or states are invalid
25
+ */
26
+ export declare function assertTransition(from: any, to: any, wuid: any): void;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * State Machine Validation Library
3
+ *
4
+ * Enforces canonical WU state transitions according to LumenFlow §2.4
5
+ * Prevents illegal state changes (e.g., done → in_progress) and ensures workflow integrity.
6
+ *
7
+ * Canonical state machine:
8
+ * - ready → in_progress (claim)
9
+ * - in_progress → blocked (block)
10
+ * - in_progress → waiting (implementation complete, awaiting sign-off)
11
+ * - in_progress → done (direct completion)
12
+ * - blocked → in_progress (unblock)
13
+ * - blocked → done (blocker resolved, direct completion)
14
+ * - waiting → in_progress (changes requested)
15
+ * - waiting → done (approved)
16
+ * - done → (terminal, no transitions)
17
+ */
18
+ import { createError, ErrorCodes } from './error-handler.js';
19
+ /**
20
+ * Valid WU states as defined in LumenFlow §2.4
21
+ */
22
+ const VALID_STATES = new Set(['ready', 'in_progress', 'blocked', 'waiting', 'done']);
23
+ /**
24
+ * Transition table mapping each state to its allowed next states
25
+ * Based on LumenFlow §2.4 Flow States & Lanes
26
+ */
27
+ const TRANSITIONS = {
28
+ ready: ['in_progress'],
29
+ in_progress: ['blocked', 'waiting', 'done'],
30
+ blocked: ['in_progress', 'done'],
31
+ waiting: ['in_progress', 'done'],
32
+ done: [], // Terminal state - no outgoing transitions
33
+ };
34
+ /**
35
+ * Validates a state transition and throws if illegal
36
+ *
37
+ * @param {string|null|undefined} from - Current WU status
38
+ * @param {string|null|undefined} to - Desired WU status
39
+ * @param {string} wuid - Work Unit ID (e.g., 'WU-416') for error messages
40
+ * @throws {Error} If transition is illegal or states are invalid
41
+ */
42
+ export function assertTransition(from, to, wuid) {
43
+ // Validate states exist and are non-empty
44
+ if (from === null || from === undefined || from === '') {
45
+ throw createError(ErrorCodes.STATE_ERROR, `Invalid state: ${from}`, {
46
+ wuid,
47
+ from,
48
+ to,
49
+ reason: 'from state is null/undefined/empty',
50
+ });
51
+ }
52
+ if (to === null || to === undefined || to === '') {
53
+ throw createError(ErrorCodes.STATE_ERROR, `Invalid state: ${to}`, {
54
+ wuid,
55
+ from,
56
+ to,
57
+ reason: 'to state is null/undefined/empty',
58
+ });
59
+ }
60
+ // Validate states are recognized
61
+ if (!VALID_STATES.has(from)) {
62
+ throw createError(ErrorCodes.STATE_ERROR, `Invalid state: ${from}`, {
63
+ wuid,
64
+ from,
65
+ to,
66
+ validStates: Array.from(VALID_STATES),
67
+ });
68
+ }
69
+ if (!VALID_STATES.has(to)) {
70
+ throw createError(ErrorCodes.STATE_ERROR, `Invalid state: ${to}`, {
71
+ wuid,
72
+ from,
73
+ to,
74
+ validStates: Array.from(VALID_STATES),
75
+ });
76
+ }
77
+ // Check if transition is allowed
78
+ const allowedNextStates = TRANSITIONS[from];
79
+ if (!allowedNextStates.includes(to)) {
80
+ const terminalHint = from === 'done' ? ' (done is a terminal state)' : '';
81
+ throw createError(ErrorCodes.STATE_ERROR, `Illegal state transition for ${wuid}: ${from} → ${to}${terminalHint}`, { wuid, from, to, allowedNextStates });
82
+ }
83
+ }
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * System Map Validator Library
4
+ *
5
+ * Validates SYSTEM-MAP.yaml integrity:
6
+ * 1. All paths resolve to existing files/folders
7
+ * 2. No orphan docs not in map
8
+ * 3. Audience tags from canonical list
9
+ * 4. quick_queries resolve correctly
10
+ * 5. No PHI entries tagged for investor/public
11
+ *
12
+ * @module system-map-validator
13
+ */
14
+ /**
15
+ * Canonical list of valid audience tags as defined in SYSTEM-MAP.yaml header
16
+ * @type {string[]}
17
+ */
18
+ export declare const CANONICAL_AUDIENCES: string[];
19
+ /**
20
+ * Canonical list of valid classification levels
21
+ * From least to most restrictive: public < internal < confidential < restricted
22
+ * @type {string[]}
23
+ */
24
+ export declare const CANONICAL_CLASSIFICATIONS: string[];
25
+ /**
26
+ * Validate all paths in system map exist
27
+ *
28
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
29
+ * @param {{exists: (path: string) => boolean}} deps - Dependencies
30
+ * @returns {Promise<string[]>} Array of error messages
31
+ */
32
+ export declare function validatePaths(systemMap: any, deps: any): Promise<any[]>;
33
+ /**
34
+ * Find orphan docs not indexed in system map
35
+ *
36
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
37
+ * @param {{glob: (pattern: string) => Promise<string[]>}} deps - Dependencies
38
+ * @returns {Promise<string[]>} Array of orphan file paths
39
+ */
40
+ export declare function findOrphanDocs(systemMap: any, deps: any): Promise<any[]>;
41
+ /**
42
+ * Validate audience tags against canonical list
43
+ *
44
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
45
+ * @returns {string[]} Array of error messages
46
+ */
47
+ export declare function validateAudienceTags(systemMap: any): any[];
48
+ /**
49
+ * Validate quick_queries reference valid document IDs
50
+ *
51
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
52
+ * @returns {string[]} Array of error messages
53
+ */
54
+ export declare function validateQuickQueries(systemMap: any): any[];
55
+ /**
56
+ * Validate classification prevents PHI routing to investor/public
57
+ *
58
+ * Rule: restricted (PHI) data should NOT be accessible to external audiences
59
+ * - restricted = PHI data, must NOT go to investor/patient/clinician
60
+ * - confidential = sensitive but OK for investor (investor docs ARE confidential)
61
+ *
62
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
63
+ * @returns {string[]} Array of error messages
64
+ */
65
+ export declare function validateClassificationRouting(systemMap: any): any[];
66
+ /**
67
+ * Validate entire system map
68
+ *
69
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
70
+ * @param {{exists: (path: string) => boolean, glob: (pattern: string) => Promise<string[]>}} deps - Dependencies
71
+ * @returns {Promise<{valid: boolean, pathErrors: string[], orphanDocs: string[], audienceErrors: string[], queryErrors: string[], classificationErrors: string[]}>}
72
+ */
73
+ export declare function validateSystemMap(systemMap: any, deps: any): Promise<{
74
+ valid: boolean;
75
+ pathErrors: any[];
76
+ orphanDocs: any[];
77
+ audienceErrors: any[];
78
+ queryErrors: any[];
79
+ classificationErrors: any[];
80
+ }>;
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * System Map Validator Library
4
+ *
5
+ * Validates SYSTEM-MAP.yaml integrity:
6
+ * 1. All paths resolve to existing files/folders
7
+ * 2. No orphan docs not in map
8
+ * 3. Audience tags from canonical list
9
+ * 4. quick_queries resolve correctly
10
+ * 5. No PHI entries tagged for investor/public
11
+ *
12
+ * @module system-map-validator
13
+ */
14
+ /**
15
+ * Canonical list of valid audience tags as defined in SYSTEM-MAP.yaml header
16
+ * @type {string[]}
17
+ */
18
+ export const CANONICAL_AUDIENCES = [
19
+ 'ceo',
20
+ 'cto',
21
+ 'engineer',
22
+ 'compliance',
23
+ 'investor',
24
+ 'agent',
25
+ 'patient',
26
+ 'clinician',
27
+ ];
28
+ /**
29
+ * Canonical list of valid classification levels
30
+ * From least to most restrictive: public < internal < confidential < restricted
31
+ * @type {string[]}
32
+ */
33
+ export const CANONICAL_CLASSIFICATIONS = ['public', 'internal', 'confidential', 'restricted'];
34
+ /**
35
+ * Audiences that should NOT have access to restricted (PHI) data
36
+ * These are considered "external" audiences who should never see PHI
37
+ * Note: investor CAN see confidential (investor materials ARE confidential)
38
+ * @type {string[]}
39
+ */
40
+ const PHI_RESTRICTED_AUDIENCES = ['investor', 'patient', 'clinician', 'public'];
41
+ /**
42
+ * Extract all document entries from system map (flattens all layer arrays)
43
+ *
44
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
45
+ * @returns {Array<{id: string, path?: string, paths?: string[], audiences: string[], classification: string, summary: string}>}
46
+ */
47
+ function extractAllEntries(systemMap) {
48
+ const entries = [];
49
+ const skipKeys = ['quick_queries'];
50
+ for (const [key, value] of Object.entries(systemMap)) {
51
+ if (skipKeys.includes(key))
52
+ continue;
53
+ if (Array.isArray(value)) {
54
+ entries.push(...value);
55
+ }
56
+ }
57
+ return entries;
58
+ }
59
+ /**
60
+ * Get all paths from an entry (handles both path and paths fields)
61
+ *
62
+ * @param {{path?: string, paths?: string[]}} entry - Document entry
63
+ * @returns {string[]}
64
+ */
65
+ function getEntryPaths(entry) {
66
+ const result = [];
67
+ if (entry.path)
68
+ result.push(entry.path);
69
+ if (entry.paths && Array.isArray(entry.paths))
70
+ result.push(...entry.paths);
71
+ return result;
72
+ }
73
+ /**
74
+ * Build a set of all indexed paths from the system map
75
+ *
76
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
77
+ * @returns {Set<string>}
78
+ */
79
+ function buildIndexedPathsSet(systemMap) {
80
+ const indexedPaths = new Set();
81
+ const entries = extractAllEntries(systemMap);
82
+ for (const entry of entries) {
83
+ const paths = getEntryPaths(entry);
84
+ for (const p of paths) {
85
+ indexedPaths.add(p);
86
+ // For directory paths, also add the prefix for matching
87
+ if (p.endsWith('/')) {
88
+ indexedPaths.add(p);
89
+ }
90
+ }
91
+ }
92
+ return indexedPaths;
93
+ }
94
+ /**
95
+ * Build a set of all document IDs from the system map
96
+ *
97
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
98
+ * @returns {Set<string>}
99
+ */
100
+ function buildIdSet(systemMap) {
101
+ const idSet = new Set();
102
+ const entries = extractAllEntries(systemMap);
103
+ for (const entry of entries) {
104
+ if (entry.id) {
105
+ idSet.add(entry.id);
106
+ }
107
+ }
108
+ return idSet;
109
+ }
110
+ /**
111
+ * Validate all paths in system map exist
112
+ *
113
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
114
+ * @param {{exists: (path: string) => boolean}} deps - Dependencies
115
+ * @returns {Promise<string[]>} Array of error messages
116
+ */
117
+ export async function validatePaths(systemMap, deps) {
118
+ const errors = [];
119
+ const entries = extractAllEntries(systemMap);
120
+ for (const entry of entries) {
121
+ const paths = getEntryPaths(entry);
122
+ for (const p of paths) {
123
+ if (!deps.exists(p)) {
124
+ errors.push(`Path not found: ${p} (entry: ${entry.id})`);
125
+ }
126
+ }
127
+ }
128
+ return errors;
129
+ }
130
+ /**
131
+ * Find orphan docs not indexed in system map
132
+ *
133
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
134
+ * @param {{glob: (pattern: string) => Promise<string[]>}} deps - Dependencies
135
+ * @returns {Promise<string[]>} Array of orphan file paths
136
+ */
137
+ export async function findOrphanDocs(systemMap, deps) {
138
+ const indexedPaths = buildIndexedPathsSet(systemMap);
139
+ // Get all docs files
140
+ const allDocs = await deps.glob('docs/**/*.md');
141
+ const orphans = [];
142
+ for (const docPath of allDocs) {
143
+ // Check if this doc is directly indexed
144
+ if (indexedPaths.has(docPath))
145
+ continue;
146
+ // Check if this doc falls under an indexed directory
147
+ let isUnderIndexedDir = false;
148
+ for (const indexedPath of indexedPaths) {
149
+ const indexedPathStr = String(indexedPath);
150
+ if (indexedPathStr.endsWith('/') && docPath.startsWith(indexedPathStr)) {
151
+ isUnderIndexedDir = true;
152
+ break;
153
+ }
154
+ }
155
+ if (!isUnderIndexedDir) {
156
+ orphans.push(docPath);
157
+ }
158
+ }
159
+ return orphans;
160
+ }
161
+ /**
162
+ * Validate audience tags against canonical list
163
+ *
164
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
165
+ * @returns {string[]} Array of error messages
166
+ */
167
+ export function validateAudienceTags(systemMap) {
168
+ const errors = [];
169
+ const entries = extractAllEntries(systemMap);
170
+ for (const entry of entries) {
171
+ if (!entry.audiences || !Array.isArray(entry.audiences)) {
172
+ errors.push(`Entry ${entry.id} missing audiences array`);
173
+ continue;
174
+ }
175
+ if (entry.audiences.length === 0) {
176
+ errors.push(`Entry ${entry.id} has empty audiences array (must have at least one)`);
177
+ continue;
178
+ }
179
+ for (const audience of entry.audiences) {
180
+ if (!CANONICAL_AUDIENCES.includes(audience)) {
181
+ errors.push(`Invalid audience '${audience}' in entry ${entry.id}`);
182
+ }
183
+ }
184
+ }
185
+ return errors;
186
+ }
187
+ /**
188
+ * Validate quick_queries reference valid document IDs
189
+ *
190
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
191
+ * @returns {string[]} Array of error messages
192
+ */
193
+ export function validateQuickQueries(systemMap) {
194
+ const errors = [];
195
+ if (!systemMap.quick_queries) {
196
+ return errors;
197
+ }
198
+ const validIds = buildIdSet(systemMap);
199
+ for (const [queryKey, queryValue] of Object.entries(systemMap.quick_queries)) {
200
+ const query = queryValue;
201
+ // Check primary reference
202
+ if (query.primary && !validIds.has(query.primary)) {
203
+ errors.push(`Quick query '${queryKey}' references non-existent primary: ${query.primary}`);
204
+ }
205
+ // Check related references
206
+ if (query.related && Array.isArray(query.related)) {
207
+ for (const related of query.related) {
208
+ if (!validIds.has(related)) {
209
+ errors.push(`Quick query '${queryKey}' references non-existent related: ${related}`);
210
+ }
211
+ }
212
+ }
213
+ }
214
+ return errors;
215
+ }
216
+ /**
217
+ * Validate classification prevents PHI routing to investor/public
218
+ *
219
+ * Rule: restricted (PHI) data should NOT be accessible to external audiences
220
+ * - restricted = PHI data, must NOT go to investor/patient/clinician
221
+ * - confidential = sensitive but OK for investor (investor docs ARE confidential)
222
+ *
223
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
224
+ * @returns {string[]} Array of error messages
225
+ */
226
+ export function validateClassificationRouting(systemMap) {
227
+ const errors = [];
228
+ const entries = extractAllEntries(systemMap);
229
+ for (const entry of entries) {
230
+ const classification = entry.classification;
231
+ const audiences = entry.audiences || [];
232
+ // Only check restricted classification (PHI data)
233
+ // Confidential is OK for investors (investor materials are confidential by design)
234
+ if (classification !== 'restricted') {
235
+ continue;
236
+ }
237
+ // Check if any PHI-restricted audiences have access
238
+ for (const audience of audiences) {
239
+ if (PHI_RESTRICTED_AUDIENCES.includes(audience)) {
240
+ errors.push(`PHI routing violation: ${entry.id} has restricted (PHI) classification but is accessible to '${audience}'`);
241
+ }
242
+ }
243
+ }
244
+ return errors;
245
+ }
246
+ /**
247
+ * Validate entire system map
248
+ *
249
+ * @param {object} systemMap - Parsed SYSTEM-MAP.yaml
250
+ * @param {{exists: (path: string) => boolean, glob: (pattern: string) => Promise<string[]>}} deps - Dependencies
251
+ * @returns {Promise<{valid: boolean, pathErrors: string[], orphanDocs: string[], audienceErrors: string[], queryErrors: string[], classificationErrors: string[]}>}
252
+ */
253
+ export async function validateSystemMap(systemMap, deps) {
254
+ const pathErrors = await validatePaths(systemMap, deps);
255
+ const orphanDocs = await findOrphanDocs(systemMap, deps);
256
+ const audienceErrors = validateAudienceTags(systemMap);
257
+ const queryErrors = validateQuickQueries(systemMap);
258
+ const classificationErrors = validateClassificationRouting(systemMap);
259
+ const valid = pathErrors.length === 0 &&
260
+ orphanDocs.length === 0 &&
261
+ audienceErrors.length === 0 &&
262
+ queryErrors.length === 0 &&
263
+ classificationErrors.length === 0;
264
+ return {
265
+ valid,
266
+ pathErrors,
267
+ orphanDocs,
268
+ audienceErrors,
269
+ queryErrors,
270
+ classificationErrors,
271
+ };
272
+ }