@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,94 @@
1
+ import path from 'node:path';
2
+ import { existsSync, unlinkSync, symlinkSync } from 'node:fs';
3
+ /**
4
+ * Determine whether gates should run in low-noise "agent mode".
5
+ *
6
+ * Agent mode is intended for Claude Code sessions, where tool output is injected into the
7
+ * conversation context and can trigger "prompt too long".
8
+ *
9
+ * Detection strategy (WU-1827):
10
+ * 1. --verbose flag always forces full output (returns false)
11
+ * 2. CLAUDE_PROJECT_DIR env var is a strong hint (returns true if set)
12
+ * 3. TTY check: non-TTY + non-CI = likely agent mode (returns true)
13
+ * 4. Interactive TTY = human user (returns false)
14
+ *
15
+ * @param {GatesAgentModeOptions} options
16
+ * @returns {boolean} True if gates should run in agent mode
17
+ */
18
+ export function shouldUseGatesAgentMode({ argv, env, stdout } = {}) {
19
+ // --verbose flag always forces full output
20
+ const isVerbose = Array.isArray(argv) && argv.includes('--verbose');
21
+ if (isVerbose) {
22
+ return false;
23
+ }
24
+ // CLAUDE_PROJECT_DIR is a strong hint that we're in Claude Code
25
+ const hasClaudeProjectDir = Boolean(env?.CLAUDE_PROJECT_DIR);
26
+ if (hasClaudeProjectDir) {
27
+ return true;
28
+ }
29
+ // CI environments should get full output for debugging
30
+ const isCI = Boolean(env?.CI);
31
+ if (isCI) {
32
+ return false;
33
+ }
34
+ // Use provided stdout or fall back to process.stdout
35
+ const stdoutStream = stdout ?? process.stdout;
36
+ // TTY check: non-TTY = likely agent mode (Claude Code Bash tool doesn't have TTY)
37
+ // If stdout is undefined or isTTY is falsy, assume agent mode (safer default)
38
+ const isTTY = stdoutStream?.isTTY ?? false;
39
+ // Non-TTY + non-CI = likely agent mode
40
+ return !isTTY;
41
+ }
42
+ export function getGatesLogDir({ cwd, env }) {
43
+ const configured = env?.LUMENFLOW_LOG_DIR;
44
+ return path.resolve(cwd, configured || '.logs');
45
+ }
46
+ export function buildGatesLogPath({ cwd, env, wuId, lane, now = new Date(), }) {
47
+ const logDir = getGatesLogDir({ cwd, env });
48
+ const safeLane = (lane || 'unknown').toLowerCase().replace(/[^a-z0-9]+/g, '-');
49
+ const safeWu = (wuId || 'unknown').toLowerCase().replace(/[^a-z0-9]+/g, '-');
50
+ const stamp = now.toISOString().replace(/[:.]/g, '-');
51
+ return path.join(logDir, `gates-${safeLane}-${safeWu}-${stamp}.log`);
52
+ }
53
+ /**
54
+ * Get the path to the gates-latest.log symlink (WU-2064)
55
+ *
56
+ * @param {Object} options
57
+ * @param {string} options.cwd - Working directory
58
+ * @param {Object} [options.env] - Environment variables
59
+ * @returns {string} Path to the symlink
60
+ */
61
+ export function getGatesLatestSymlinkPath({ cwd, env }) {
62
+ const logDir = getGatesLogDir({ cwd, env });
63
+ return path.join(logDir, 'gates-latest.log');
64
+ }
65
+ /**
66
+ * Create or update the gates-latest.log symlink to point to the most recent gate run (WU-2064)
67
+ *
68
+ * This provides a stable path for agents to access the most recent gate log
69
+ * without needing to know the timestamp-based filename.
70
+ *
71
+ * @param {Object} options
72
+ * @param {string} options.logPath - Path to the actual gate log file
73
+ * @param {string} options.cwd - Working directory
74
+ * @param {Object} [options.env] - Environment variables
75
+ * @returns {boolean} True if symlink was created/updated successfully
76
+ */
77
+ export function updateGatesLatestSymlink({ logPath, cwd, env }) {
78
+ const symlinkPath = getGatesLatestSymlinkPath({ cwd, env });
79
+ try {
80
+ // Remove existing symlink if present
81
+ if (existsSync(symlinkPath)) {
82
+ unlinkSync(symlinkPath);
83
+ }
84
+ // Create relative symlink (so it works regardless of absolute path)
85
+ const logDir = path.dirname(symlinkPath);
86
+ const relativePath = path.relative(logDir, logPath);
87
+ symlinkSync(relativePath, symlinkPath);
88
+ return true;
89
+ }
90
+ catch {
91
+ // Symlink creation is best-effort, don't fail gates
92
+ return false;
93
+ }
94
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Generate Traceability Library
3
+ *
4
+ * Core functions for generating compliance traceability documentation:
5
+ * - Hazard log parsing
6
+ * - Prompt version extraction
7
+ * - Cross-reference building
8
+ * - Markdown document generation
9
+ *
10
+ * @module tools/lib/generate-traceability
11
+ * @see WU-2035 - INIT-034
12
+ */
13
+ /**
14
+ * @typedef {Object} HazardDefinition
15
+ * @property {string} id
16
+ * @property {string} title
17
+ * @property {number} severity
18
+ * @property {number} likelihood
19
+ * @property {number} riskScore
20
+ * @property {ControlDefinition[]} controls
21
+ * @property {number} residualRiskScore
22
+ * @property {string} acceptability
23
+ */
24
+ /**
25
+ * @typedef {Object} ControlDefinition
26
+ * @property {string} id
27
+ * @property {string} description
28
+ * @property {string} type
29
+ * @property {string} status
30
+ */
31
+ /**
32
+ * @typedef {Object} PromptVersion
33
+ * @property {string} filePath
34
+ * @property {string} fileName
35
+ * @property {string} version
36
+ * @property {string} relativePath
37
+ */
38
+ /**
39
+ * @typedef {Object} GitCommit
40
+ * @property {string} hash
41
+ * @property {string} date
42
+ * @property {string} author
43
+ * @property {string} message
44
+ * @property {string[]} files
45
+ */
46
+ /**
47
+ * @typedef {Object} CrossReference
48
+ * @property {string} source
49
+ * @property {string} target
50
+ * @property {'hazard_to_control' | 'control_to_prompt' | 'control_to_test'} type
51
+ * @property {boolean} valid
52
+ * @property {string} [error]
53
+ */
54
+ /**
55
+ * Parse hazard definitions from hazard-log.md content
56
+ * @param {string} content - Markdown content of hazard log
57
+ * @returns {HazardDefinition[]}
58
+ */
59
+ export declare function parseHazardLog(content: any): any[];
60
+ /**
61
+ * Extract versions from prompt YAML files
62
+ * @param {Array<{path: string, content: string}>} prompts
63
+ * @returns {PromptVersion[]}
64
+ */
65
+ export declare function extractPromptVersions(prompts: any): any[];
66
+ /**
67
+ * Build cross-references between hazards, controls, and tests
68
+ * @param {Array<{id: string, controls: Array<{id: string}>}>} hazards
69
+ * @param {PromptVersion[]} prompts
70
+ * @param {Array<{file: string, mentionsControls: string[]}>} goldenTests
71
+ * @returns {CrossReference[]}
72
+ */
73
+ export declare function buildCrossReferences(hazards: any, prompts: any, goldenTests: any): any[];
74
+ /**
75
+ * Generate version history markdown from git commits
76
+ * @param {GitCommit[]} commits
77
+ * @returns {string}
78
+ */
79
+ export declare function generateVersionHistory(commits: any): string;
80
+ /**
81
+ * Generate prompt inventory markdown
82
+ * @param {PromptVersion[]} prompts
83
+ * @returns {string}
84
+ */
85
+ export declare function generatePromptInventory(prompts: any): string;
86
+ /**
87
+ * Generate hazard matrix markdown
88
+ * @param {HazardDefinition[]} hazards
89
+ * @returns {string}
90
+ */
91
+ export declare function generateHazardMatrix(hazards: any): string;
92
+ /**
93
+ * Generate validation report markdown
94
+ * @param {CrossReference[]} crossRefs
95
+ * @returns {string}
96
+ */
97
+ export declare function generateValidationReport(crossRefs: any): string;
98
+ /**
99
+ * Validate cross-references and return summary
100
+ * @param {CrossReference[]} refs
101
+ * @returns {{valid: boolean, invalidCount: number, errors: string[]}}
102
+ */
103
+ export declare function validateCrossReferences(refs: any): {
104
+ valid: boolean;
105
+ invalidCount: any;
106
+ errors: any;
107
+ };
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Generate Traceability Library
3
+ *
4
+ * Core functions for generating compliance traceability documentation:
5
+ * - Hazard log parsing
6
+ * - Prompt version extraction
7
+ * - Cross-reference building
8
+ * - Markdown document generation
9
+ *
10
+ * @module tools/lib/generate-traceability
11
+ * @see WU-2035 - INIT-034
12
+ */
13
+ import path from 'node:path';
14
+ import yaml from 'yaml';
15
+ /**
16
+ * @typedef {Object} HazardDefinition
17
+ * @property {string} id
18
+ * @property {string} title
19
+ * @property {number} severity
20
+ * @property {number} likelihood
21
+ * @property {number} riskScore
22
+ * @property {ControlDefinition[]} controls
23
+ * @property {number} residualRiskScore
24
+ * @property {string} acceptability
25
+ */
26
+ /**
27
+ * @typedef {Object} ControlDefinition
28
+ * @property {string} id
29
+ * @property {string} description
30
+ * @property {string} type
31
+ * @property {string} status
32
+ */
33
+ /**
34
+ * @typedef {Object} PromptVersion
35
+ * @property {string} filePath
36
+ * @property {string} fileName
37
+ * @property {string} version
38
+ * @property {string} relativePath
39
+ */
40
+ /**
41
+ * @typedef {Object} GitCommit
42
+ * @property {string} hash
43
+ * @property {string} date
44
+ * @property {string} author
45
+ * @property {string} message
46
+ * @property {string[]} files
47
+ */
48
+ /**
49
+ * @typedef {Object} CrossReference
50
+ * @property {string} source
51
+ * @property {string} target
52
+ * @property {'hazard_to_control' | 'control_to_prompt' | 'control_to_test'} type
53
+ * @property {boolean} valid
54
+ * @property {string} [error]
55
+ */
56
+ /**
57
+ * Parse hazard definitions from hazard-log.md content
58
+ * @param {string} content - Markdown content of hazard log
59
+ * @returns {HazardDefinition[]}
60
+ */
61
+ export function parseHazardLog(content) {
62
+ const hazards = [];
63
+ const hazardSections = content.split(/### HAZ-\d{3}:/);
64
+ for (let i = 1; i < hazardSections.length; i++) {
65
+ const section = hazardSections[i];
66
+ const hazard = parseHazardSection(section);
67
+ if (hazard) {
68
+ hazards.push(hazard);
69
+ }
70
+ }
71
+ return hazards;
72
+ }
73
+ /**
74
+ * Parse a single hazard section
75
+ * @param {string} section
76
+ * @returns {HazardDefinition | null}
77
+ */
78
+ function parseHazardSection(section) {
79
+ const idMatch = section.match(/\*\*Hazard ID\*\*\s*\|\s*(HAZ-\d{3})/);
80
+ if (!idMatch)
81
+ return null;
82
+ const titleLine = section.split('\n')[0].trim();
83
+ // Parse initial risk values
84
+ const severityMatch = section.match(/\*\*Initial Severity\*\*\s*\|\s*(\d)/);
85
+ const likelihoodMatch = section.match(/\*\*Initial Likelihood\*\*\s*\|\s*(\d)/);
86
+ const riskScoreMatch = section.match(/\*\*Initial Risk Score\*\*\s*\|\s*(\d+)/);
87
+ // Parse residual assessment
88
+ const residualRiskMatch = section.match(/Residual Risk Score\s*\|\s*(\d+)/);
89
+ const acceptabilityMatch = section.match(/Acceptability\s*\|\s*([^\n|]+)/);
90
+ // Parse controls
91
+ const controls = parseControls(section);
92
+ return {
93
+ id: idMatch[1],
94
+ title: titleLine,
95
+ severity: severityMatch ? parseInt(severityMatch[1], 10) : 0,
96
+ likelihood: likelihoodMatch ? parseInt(likelihoodMatch[1], 10) : 0,
97
+ riskScore: riskScoreMatch ? parseInt(riskScoreMatch[1], 10) : 0,
98
+ controls,
99
+ residualRiskScore: residualRiskMatch ? parseInt(residualRiskMatch[1], 10) : 0,
100
+ acceptability: acceptabilityMatch ? acceptabilityMatch[1].trim() : 'Unknown',
101
+ };
102
+ }
103
+ /**
104
+ * Parse controls from a hazard section
105
+ * @param {string} section
106
+ * @returns {ControlDefinition[]}
107
+ */
108
+ function parseControls(section) {
109
+ const controls = [];
110
+ const controlMatches = section.matchAll(/\|\s*(C-\d{3}[a-z]?)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g);
111
+ for (const match of controlMatches) {
112
+ controls.push({
113
+ id: match[1].trim(),
114
+ description: match[2].trim(),
115
+ type: match[3].trim(),
116
+ status: match[4].trim(),
117
+ });
118
+ }
119
+ return controls;
120
+ }
121
+ /**
122
+ * Extract versions from prompt YAML files
123
+ * @param {Array<{path: string, content: string}>} prompts
124
+ * @returns {PromptVersion[]}
125
+ */
126
+ export function extractPromptVersions(prompts) {
127
+ const versions = [];
128
+ for (const prompt of prompts) {
129
+ try {
130
+ const parsed = yaml.parse(prompt.content);
131
+ if (parsed && parsed.version) {
132
+ versions.push({
133
+ filePath: prompt.path,
134
+ fileName: path.basename(prompt.path),
135
+ version: parsed.version,
136
+ relativePath: prompt.path,
137
+ });
138
+ }
139
+ }
140
+ catch {
141
+ // Skip files that can't be parsed
142
+ }
143
+ }
144
+ return versions.sort((a, b) => a.fileName.localeCompare(b.fileName));
145
+ }
146
+ /**
147
+ * Build cross-references between hazards, controls, and tests
148
+ * @param {Array<{id: string, controls: Array<{id: string}>}>} hazards
149
+ * @param {PromptVersion[]} prompts
150
+ * @param {Array<{file: string, mentionsControls: string[]}>} goldenTests
151
+ * @returns {CrossReference[]}
152
+ */
153
+ export function buildCrossReferences(hazards, prompts, goldenTests) {
154
+ const refs = [];
155
+ // Build set of controls covered by tests
156
+ const coveredControls = new Set();
157
+ for (const test of goldenTests) {
158
+ for (const controlId of test.mentionsControls) {
159
+ coveredControls.add(controlId);
160
+ }
161
+ }
162
+ // Create hazard to control references
163
+ for (const hazard of hazards) {
164
+ for (const control of hazard.controls) {
165
+ refs.push({
166
+ source: hazard.id,
167
+ target: control.id,
168
+ type: 'hazard_to_control',
169
+ valid: true,
170
+ });
171
+ // Create control to test references
172
+ const hasCoverage = coveredControls.has(control.id);
173
+ refs.push({
174
+ source: control.id,
175
+ target: 'golden-tests',
176
+ type: 'control_to_test',
177
+ valid: hasCoverage,
178
+ error: hasCoverage ? undefined : `No golden test coverage for ${control.id}`,
179
+ });
180
+ }
181
+ }
182
+ return refs;
183
+ }
184
+ /**
185
+ * Generate version history markdown from git commits
186
+ * @param {GitCommit[]} commits
187
+ * @returns {string}
188
+ */
189
+ export function generateVersionHistory(commits) {
190
+ const lines = [
191
+ '# Prompt Version History',
192
+ '',
193
+ '> Auto-generated by `tools/generate-traceability.ts`',
194
+ `> Last generated: ${new Date().toISOString()}`,
195
+ '',
196
+ 'This document tracks all changes to prompt files for compliance traceability.',
197
+ '',
198
+ '---',
199
+ '',
200
+ '## Recent Changes',
201
+ '',
202
+ '| Date | Commit | Author | Message | Files Changed |',
203
+ '|------|--------|--------|---------|---------------|',
204
+ ];
205
+ for (const commit of commits.slice(0, 50)) {
206
+ const filesStr = commit.files.length > 3
207
+ ? `${commit.files
208
+ .slice(0, 3)
209
+ .map((f) => path.basename(f))
210
+ .join(', ')}... (+${commit.files.length - 3})`
211
+ : commit.files.map((f) => path.basename(f)).join(', ');
212
+ lines.push(`| ${commit.date} | \`${commit.hash.slice(0, 7)}\` | ${commit.author} | ${commit.message.slice(0, 60)}${commit.message.length > 60 ? '...' : ''} | ${filesStr} |`);
213
+ }
214
+ lines.push('');
215
+ lines.push('---');
216
+ lines.push('');
217
+ lines.push('## Change Categories');
218
+ lines.push('');
219
+ // Group by month
220
+ const byMonth = new Map();
221
+ for (const commit of commits) {
222
+ const month = commit.date.slice(0, 7); // YYYY-MM
223
+ if (!byMonth.has(month)) {
224
+ byMonth.set(month, []);
225
+ }
226
+ byMonth.get(month).push(commit);
227
+ }
228
+ lines.push('| Month | Total Changes | Safety-Related | Feature |');
229
+ lines.push('|-------|---------------|----------------|---------|');
230
+ for (const [month, monthCommits] of [...byMonth.entries()].sort().reverse()) {
231
+ const safetyCount = monthCommits.filter((c) => c.message.toLowerCase().includes('safety') ||
232
+ c.message.toLowerCase().includes('red-flag') ||
233
+ c.message.toLowerCase().includes('emergency')).length;
234
+ const featureCount = monthCommits.length - safetyCount;
235
+ lines.push(`| ${month} | ${monthCommits.length} | ${safetyCount} | ${featureCount} |`);
236
+ }
237
+ return lines.join('\n');
238
+ }
239
+ /**
240
+ * Generate prompt inventory markdown
241
+ * @param {PromptVersion[]} prompts
242
+ * @returns {string}
243
+ */
244
+ export function generatePromptInventory(prompts) {
245
+ const lines = [
246
+ '# Prompt Inventory',
247
+ '',
248
+ '> Auto-generated by `tools/generate-traceability.ts`',
249
+ `> Last generated: ${new Date().toISOString()}`,
250
+ '',
251
+ 'Complete inventory of all versioned prompt files in the system.',
252
+ '',
253
+ '---',
254
+ '',
255
+ '## Prompt Files',
256
+ '',
257
+ '| Prompt | Version | Path |',
258
+ '|--------|---------|------|',
259
+ ];
260
+ for (const prompt of prompts) {
261
+ lines.push(`| ${prompt.fileName} | \`${prompt.version}\` | \`${prompt.relativePath}\` |`);
262
+ }
263
+ lines.push('');
264
+ lines.push('---');
265
+ lines.push('');
266
+ lines.push('## Version Summary');
267
+ lines.push('');
268
+ lines.push(`- **Total Prompts**: ${prompts.length}`);
269
+ const majorVersions = new Map();
270
+ for (const prompt of prompts) {
271
+ const major = prompt.version.split('.')[0];
272
+ majorVersions.set(major, (majorVersions.get(major) || 0) + 1);
273
+ }
274
+ lines.push('- **By Major Version**:');
275
+ for (const [major, count] of [...majorVersions.entries()].sort()) {
276
+ lines.push(` - v${major}.x: ${count} prompts`);
277
+ }
278
+ return lines.join('\n');
279
+ }
280
+ /**
281
+ * Generate hazard matrix markdown
282
+ * @param {HazardDefinition[]} hazards
283
+ * @returns {string}
284
+ */
285
+ export function generateHazardMatrix(hazards) {
286
+ const lines = [
287
+ '# Hazard Traceability Matrix',
288
+ '',
289
+ '> Auto-generated by `tools/generate-traceability.ts`',
290
+ `> Last generated: ${new Date().toISOString()}`,
291
+ '',
292
+ 'Maps hazards to controls for DCB0129 compliance.',
293
+ '',
294
+ '---',
295
+ '',
296
+ '## Hazard Summary',
297
+ '',
298
+ '| Hazard ID | Title | Initial Risk | Residual Risk | Acceptability | Controls |',
299
+ '|-----------|-------|--------------|---------------|---------------|----------|',
300
+ ];
301
+ for (const hazard of hazards) {
302
+ const controlList = hazard.controls.map((c) => c.id).join(', ');
303
+ const title = hazard.title || '';
304
+ lines.push(`| ${hazard.id} | ${title.slice(0, 50)}${title.length > 50 ? '...' : ''} | ${hazard.riskScore} | ${hazard.residualRiskScore} | ${hazard.acceptability} | ${controlList} |`);
305
+ }
306
+ lines.push('');
307
+ lines.push('---');
308
+ lines.push('');
309
+ lines.push('## Control Implementation Status');
310
+ lines.push('');
311
+ lines.push('| Control ID | Hazard | Description | Type | Status |');
312
+ lines.push('|------------|--------|-------------|------|--------|');
313
+ for (const hazard of hazards) {
314
+ for (const control of hazard.controls) {
315
+ const desc = control.description || '';
316
+ lines.push(`| ${control.id} | ${hazard.id} | ${desc.slice(0, 60)}${desc.length > 60 ? '...' : ''} | ${control.type} | ${control.status} |`);
317
+ }
318
+ }
319
+ lines.push('');
320
+ lines.push('---');
321
+ lines.push('');
322
+ lines.push('## Risk Distribution');
323
+ lines.push('');
324
+ const riskLevels = {
325
+ Acceptable: 0,
326
+ Low: 0,
327
+ Medium: 0,
328
+ High: 0,
329
+ Unacceptable: 0,
330
+ Tolerable: 0,
331
+ };
332
+ for (const hazard of hazards) {
333
+ const level = hazard.acceptability.replace(/\s*\(.*\)/, '');
334
+ if (level in riskLevels) {
335
+ riskLevels[level]++;
336
+ }
337
+ }
338
+ lines.push('| Risk Level | Count |');
339
+ lines.push('|------------|-------|');
340
+ for (const [level, count] of Object.entries(riskLevels)) {
341
+ if (count > 0) {
342
+ lines.push(`| ${level} | ${count} |`);
343
+ }
344
+ }
345
+ return lines.join('\n');
346
+ }
347
+ /**
348
+ * Generate validation report markdown
349
+ * @param {CrossReference[]} crossRefs
350
+ * @returns {string}
351
+ */
352
+ export function generateValidationReport(crossRefs) {
353
+ const lines = [
354
+ '# Traceability Validation Report',
355
+ '',
356
+ '> Auto-generated by `tools/generate-traceability.ts`',
357
+ `> Last generated: ${new Date().toISOString()}`,
358
+ '',
359
+ 'Validates cross-references between hazards, controls, and test coverage.',
360
+ '',
361
+ '---',
362
+ '',
363
+ '## Summary',
364
+ '',
365
+ ];
366
+ const valid = crossRefs.filter((r) => r.valid).length;
367
+ const invalid = crossRefs.filter((r) => !r.valid).length;
368
+ const total = crossRefs.length;
369
+ const coverage = total > 0 ? ((valid / total) * 100).toFixed(1) : '0.0';
370
+ lines.push(`- **Total References**: ${total}`);
371
+ lines.push(`- **Valid**: ${valid}`);
372
+ lines.push(`- **Invalid/Missing**: ${invalid}`);
373
+ lines.push(`- **Coverage**: ${coverage}%`);
374
+ lines.push('');
375
+ if (invalid > 0) {
376
+ lines.push('---');
377
+ lines.push('');
378
+ lines.push('## Issues Found');
379
+ lines.push('');
380
+ lines.push('| Source | Target | Type | Issue |');
381
+ lines.push('|--------|--------|------|-------|');
382
+ for (const ref of crossRefs.filter((r) => !r.valid)) {
383
+ lines.push(`| ${ref.source} | ${ref.target} | ${ref.type} | ${ref.error || 'Unknown'} |`);
384
+ }
385
+ }
386
+ lines.push('');
387
+ lines.push('---');
388
+ lines.push('');
389
+ lines.push('## All References');
390
+ lines.push('');
391
+ lines.push('| Source | Target | Type | Valid |');
392
+ lines.push('|--------|--------|------|-------|');
393
+ for (const ref of crossRefs) {
394
+ lines.push(`| ${ref.source} | ${ref.target} | ${ref.type} | ${ref.valid ? 'Yes' : 'No'} |`);
395
+ }
396
+ return lines.join('\n');
397
+ }
398
+ /**
399
+ * Validate cross-references and return summary
400
+ * @param {CrossReference[]} refs
401
+ * @returns {{valid: boolean, invalidCount: number, errors: string[]}}
402
+ */
403
+ export function validateCrossReferences(refs) {
404
+ const invalid = refs.filter((r) => !r.valid);
405
+ const errors = invalid.map((r) => r.error).filter(Boolean);
406
+ return {
407
+ valid: invalid.length === 0,
408
+ invalidCount: invalid.length,
409
+ errors,
410
+ };
411
+ }