@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,242 @@
1
+ /**
2
+ * @file worktree-guard.mjs
3
+ * @description WU context validation and main branch protection (WU-1396)
4
+ *
5
+ * Provides runtime guards to enforce worktree discipline:
6
+ * - Detect if current directory is inside a worktree
7
+ * - Extract WU ID and lane from worktree path or git branch
8
+ * - Throw descriptive error when write operations attempted outside worktree
9
+ * - Check if on main/master branch
10
+ *
11
+ * Used by wu- scripts to prevent writes to main checkout when worktrees exist.
12
+ * Complements .claude/hooks/user-prompt-submit-hook (human agent protection).
13
+ *
14
+ * @see {@link .claude/hooks/user-prompt-submit-hook} - Agent blocking hook
15
+ * @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - Worktree discipline
16
+ */
17
+ import path from 'node:path';
18
+ import { createGitForPath } from '../git-adapter.js';
19
+ import { BRANCHES } from '../wu-constants.js';
20
+ /**
21
+ * Worktree path pattern: worktrees/<lane-kebab>-wu-<id>
22
+ * Captures: lane (kebab-case) and WU ID number
23
+ *
24
+ * Examples:
25
+ * - worktrees/operations-tooling-wu-1396
26
+ * - worktrees/intelligence-wu-789
27
+ * - worktrees/core-systems-api-wu-456
28
+ */
29
+ const WORKTREE_PATH_PATTERN = /worktrees\/([\w-]+)-wu-(\d+)/;
30
+ /**
31
+ * Lane branch pattern: lane/<lane-kebab>/wu-<id>
32
+ * Captures: lane (kebab-case) and WU ID number
33
+ *
34
+ * Examples:
35
+ * - lane/operations-tooling/wu-1396
36
+ * - lane/intelligence/wu-789
37
+ * - lane/core-systems-api/wu-456
38
+ */
39
+ const LANE_BRANCH_PATTERN = /^lane\/([\w-]+)\/wu-(\d+)$/;
40
+ /**
41
+ * Check if on main or master branch
42
+ *
43
+ * @param {GitOptions} [options] - Options
44
+ * @returns {Promise<boolean>} True if on main/master branch
45
+ *
46
+ * @example
47
+ * if (await isMainBranch()) {
48
+ * console.log('On main branch');
49
+ * }
50
+ */
51
+ export async function isMainBranch(options = {}) {
52
+ const git = options.git || createGitForPath(process.cwd());
53
+ const branch = await git.getCurrentBranch();
54
+ return branch === BRANCHES.MAIN || branch === BRANCHES.MASTER;
55
+ }
56
+ /**
57
+ * Normalize path separators to forward slashes
58
+ *
59
+ * Handles both Unix and Windows path separators for cross-platform compatibility.
60
+ *
61
+ * @param {string} p - Path to normalize
62
+ * @returns {string} Path with forward slashes
63
+ * @private
64
+ */
65
+ function normalizePath(p) {
66
+ // Replace both backslashes and path.sep with forward slashes
67
+ // This handles Windows paths on Linux during testing
68
+ return p.replace(/\\/g, '/').split(path.sep).join('/');
69
+ }
70
+ /**
71
+ * Check if current directory is inside a worktree
72
+ *
73
+ * Detects worktree by checking if path contains worktrees/<lane>-wu-<id> pattern.
74
+ * Works correctly from nested directories within worktree.
75
+ *
76
+ * @param {Object} [options] - Options
77
+ * @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
78
+ * @returns {boolean} True if inside a worktree directory
79
+ *
80
+ * @example
81
+ * if (isInWorktree()) {
82
+ * console.log('Working in a worktree');
83
+ * }
84
+ *
85
+ * // From nested directory
86
+ * isInWorktree({ cwd: '/project/worktrees/operations-wu-123/tools/lib' }); // true
87
+ */
88
+ export function isInWorktree(options = {}) {
89
+ const cwd = options.cwd || process.cwd();
90
+ // Normalize path separators for cross-platform compatibility
91
+ const normalizedPath = normalizePath(cwd);
92
+ return WORKTREE_PATH_PATTERN.test(normalizedPath);
93
+ }
94
+ /**
95
+ * Extract WU context from worktree path
96
+ *
97
+ * @param {string} cwd - Current working directory
98
+ * @returns {Object|null} Context object or null if not a worktree path
99
+ * @private
100
+ */
101
+ function extractFromWorktreePath(cwd) {
102
+ const normalizedPath = normalizePath(cwd);
103
+ const match = normalizedPath.match(WORKTREE_PATH_PATTERN);
104
+ if (!match) {
105
+ return null;
106
+ }
107
+ const [fullMatch, lane, wuIdNumber] = match;
108
+ const wuId = `WU-${wuIdNumber}`;
109
+ // Extract just the worktrees/<lane>-wu-<id> part (not full absolute path)
110
+ const worktreePathMatch = fullMatch.match(/(worktrees\/[\w-]+-wu-\d+)/);
111
+ const worktreePath = worktreePathMatch ? worktreePathMatch[1] : null;
112
+ return {
113
+ wuId,
114
+ lane,
115
+ worktreePath,
116
+ };
117
+ }
118
+ /**
119
+ * Extract WU context from git branch name
120
+ *
121
+ * @param {Object} git - GitAdapter instance
122
+ * @returns {Promise<Object|null>} Context object or null if not a lane branch
123
+ * @private
124
+ */
125
+ async function extractFromBranch(git) {
126
+ const branch = await git.getCurrentBranch();
127
+ const match = branch.match(LANE_BRANCH_PATTERN);
128
+ if (!match) {
129
+ return null;
130
+ }
131
+ const [, lane, wuIdNumber] = match;
132
+ const wuId = `WU-${wuIdNumber}`;
133
+ return {
134
+ wuId,
135
+ lane,
136
+ worktreePath: null, // Not in worktree, on lane branch
137
+ };
138
+ }
139
+ /**
140
+ * Get WU context from current directory or git branch
141
+ *
142
+ * Extracts WU ID, lane, and worktree path from:
143
+ * 1. Worktree directory path (priority) - works from nested directories
144
+ * 2. Git branch name (fallback) - lane/operations-tooling/wu-1396
145
+ *
146
+ * @param {Object} [options] - Options
147
+ * @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
148
+ * @param {Object} [options.git] - GitAdapter instance (for testing)
149
+ * @returns {Promise<Object|null>} WU context or null if not in WU workspace
150
+ *
151
+ * @example
152
+ * // From worktree
153
+ * const ctx = await getWUContext();
154
+ * // { wuId: 'WU-1396', lane: 'operations-tooling', worktreePath: 'worktrees/operations-tooling-wu-1396' }
155
+ *
156
+ * // From lane branch (not in worktree)
157
+ * const ctx = await getWUContext();
158
+ * // { wuId: 'WU-1396', lane: 'operations-tooling', worktreePath: null }
159
+ *
160
+ * // From main checkout on main branch
161
+ * const ctx = await getWUContext();
162
+ * // null
163
+ */
164
+ export async function getWUContext(options = {}) {
165
+ const cwd = options.cwd || process.cwd();
166
+ // Fast path: Try worktree path first (no git operations needed)
167
+ const worktreeContext = extractFromWorktreePath(cwd);
168
+ if (worktreeContext) {
169
+ return worktreeContext;
170
+ }
171
+ // Fallback to git branch detection (requires git operations)
172
+ // Only create git adapter if we have one provided or if we need to check branch
173
+ if (options.git) {
174
+ return await extractFromBranch(options.git);
175
+ }
176
+ // Create git adapter only if needed (path wasn't a worktree)
177
+ const git = createGitForPath(cwd);
178
+ return await extractFromBranch(git);
179
+ }
180
+ /**
181
+ * Assert that current context is inside a worktree or on a lane branch
182
+ *
183
+ * Throws descriptive error if:
184
+ * - On main/master branch AND
185
+ * - Not in a worktree directory
186
+ *
187
+ * Used by write operations to prevent modifications to main checkout.
188
+ *
189
+ * @param {Object} [options] - Options
190
+ * @param {string} [options.cwd] - Current working directory (defaults to process.cwd())
191
+ * @param {Object} [options.git] - GitAdapter instance (for testing)
192
+ * @param {string} [options.operation] - Operation name for error message
193
+ * @throws {Error} If not in worktree and on main branch
194
+ *
195
+ * @example
196
+ * // In a wu- script write operation
197
+ * await assertWorktreeRequired({ operation: 'wu:claim' });
198
+ *
199
+ * // Will throw if on main in main checkout:
200
+ * // Error: BLOCKED: Operation 'wu:claim' requires a worktree.
201
+ * // You are on 'main' branch in main checkout.
202
+ * // ...
203
+ */
204
+ export async function assertWorktreeRequired(options = {}) {
205
+ const cwd = options.cwd || process.cwd();
206
+ const operation = options.operation || 'this operation';
207
+ // Fast path: Check worktree path first (no git operations needed)
208
+ const worktreeContext = extractFromWorktreePath(cwd);
209
+ if (worktreeContext) {
210
+ return; // In worktree, allow operation
211
+ }
212
+ // Need git operations to check branch
213
+ const git = options.git || createGitForPath(cwd);
214
+ // Check if on lane branch
215
+ const branchContext = await extractFromBranch(git);
216
+ if (branchContext) {
217
+ return; // On lane branch, allow operation
218
+ }
219
+ // Check if on main branch
220
+ const onMain = await isMainBranch({ git });
221
+ if (onMain) {
222
+ throw new Error(`❌ BLOCKED: Operation '${operation}' requires a worktree.
223
+
224
+ You are on 'main' branch in main checkout.
225
+
226
+ To fix:
227
+ 1. Claim a WU first:
228
+ pnpm wu:claim --id WU-1234 --lane "Operations: Tooling"
229
+
230
+ 2. Navigate to the worktree:
231
+ cd worktrees/operations-tooling-wu-1234/
232
+
233
+ 3. Run your operation from the worktree.
234
+
235
+ For more information:
236
+ See CLAUDE.md §2 (Worktree Discipline)
237
+ See .claude/skills/worktree-discipline/SKILL.md
238
+ `);
239
+ }
240
+ // Pass if on a non-main branch (e.g., feature branch) - allow for flexibility
241
+ // This case is less strict since it's not the main branch
242
+ }
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Coverage Gate for Quality Gates
4
+ *
5
+ * WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
6
+ * Enforces ≥90% coverage on hex core files (application layer).
7
+ *
8
+ * Mode flag allows gradual rollout:
9
+ * - warn: Log failures but don't block (default)
10
+ * - block: Fail the gate if thresholds not met
11
+ *
12
+ * @see {@link tools/gates.mjs} - Integration point
13
+ * @see {@link vitest.config.ts} - Coverage thresholds
14
+ */
15
+ /**
16
+ * Coverage gate modes
17
+ * @constant
18
+ */
19
+ export declare const COVERAGE_GATE_MODES: Readonly<{
20
+ /** Log warnings but don't fail the gate */
21
+ WARN: "warn";
22
+ /** Fail the gate if thresholds not met */
23
+ BLOCK: "block";
24
+ }>;
25
+ /**
26
+ * Glob patterns for hex core files that require ≥90% coverage.
27
+ * These are the critical application layer files.
28
+ *
29
+ * @constant {string[]}
30
+ */
31
+ export declare const HEX_CORE_PATTERNS: readonly string[];
32
+ /**
33
+ * Coverage threshold for hex core files (percentage)
34
+ * @constant {number}
35
+ */
36
+ export declare const COVERAGE_THRESHOLD = 90;
37
+ /**
38
+ * Default path to coverage summary JSON
39
+ * @constant {string}
40
+ */
41
+ export declare const DEFAULT_COVERAGE_PATH = "coverage/coverage-summary.json";
42
+ /**
43
+ * Check if a file path is in the hex core layer.
44
+ *
45
+ * WU-2448: Coverage reporters may emit absolute paths (e.g., /home/.../packages/...)
46
+ * or file:// URLs; use substring matching so hex-core checks still apply.
47
+ *
48
+ * @param {string|null|undefined} filePath - File path to check
49
+ * @returns {boolean} True if file is in hex core layer
50
+ */
51
+ export declare function isHexCoreFile(filePath: any): boolean;
52
+ /**
53
+ * Parse coverage JSON file.
54
+ *
55
+ * @param {string} coveragePath - Path to coverage-summary.json
56
+ * @returns {object|null} Parsed coverage data or null if invalid
57
+ */
58
+ export declare function parseCoverageJson(coveragePath: any): {
59
+ total: any;
60
+ files: {};
61
+ };
62
+ /**
63
+ * Check if coverage meets thresholds for hex core files.
64
+ *
65
+ * @param {object|null} coverageData - Parsed coverage data
66
+ * @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
67
+ */
68
+ export declare function checkCoverageThresholds(coverageData: any): {
69
+ pass: boolean;
70
+ failures: any[];
71
+ };
72
+ /**
73
+ * Format coverage data for display.
74
+ *
75
+ * @param {object|null} coverageData - Parsed coverage data
76
+ * @returns {string} Formatted output string
77
+ */
78
+ export declare function formatCoverageDelta(coverageData: any): string;
79
+ /**
80
+ * Logger interface for coverage gate output
81
+ */
82
+ interface CoverageGateLogger {
83
+ log: (...args: unknown[]) => void;
84
+ }
85
+ /**
86
+ * Options for running coverage gate
87
+ */
88
+ export interface CoverageGateOptions {
89
+ /** Gate mode ('warn' or 'block') */
90
+ mode?: string;
91
+ /** Path to coverage JSON */
92
+ coveragePath?: string;
93
+ /** Logger for output */
94
+ logger?: CoverageGateLogger;
95
+ }
96
+ /**
97
+ * Run coverage gate.
98
+ *
99
+ * @param {CoverageGateOptions} options - Gate options
100
+ * @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
101
+ */
102
+ export declare function runCoverageGate(options?: CoverageGateOptions): Promise<{
103
+ ok: boolean;
104
+ mode: string;
105
+ duration: number;
106
+ message: string;
107
+ }>;
108
+ export {};
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Coverage Gate for Quality Gates
4
+ *
5
+ * WU-1433: Adds coverage checking to gates with configurable mode (warn/block).
6
+ * Enforces ≥90% coverage on hex core files (application layer).
7
+ *
8
+ * Mode flag allows gradual rollout:
9
+ * - warn: Log failures but don't block (default)
10
+ * - block: Fail the gate if thresholds not met
11
+ *
12
+ * @see {@link tools/gates.mjs} - Integration point
13
+ * @see {@link vitest.config.ts} - Coverage thresholds
14
+ */
15
+ /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
16
+ import { readFileSync, existsSync } from 'node:fs';
17
+ import { EMOJI, STRING_LITERALS } from './wu-constants.js';
18
+ /**
19
+ * Coverage gate modes
20
+ * @constant
21
+ */
22
+ export const COVERAGE_GATE_MODES = Object.freeze({
23
+ /** Log warnings but don't fail the gate */
24
+ WARN: 'warn',
25
+ /** Fail the gate if thresholds not met */
26
+ BLOCK: 'block',
27
+ });
28
+ /**
29
+ * Glob patterns for hex core files that require ≥90% coverage.
30
+ * These are the critical application layer files.
31
+ *
32
+ * @constant {string[]}
33
+ */
34
+ export const HEX_CORE_PATTERNS = Object.freeze([
35
+ 'packages/@patientpath/application/',
36
+ 'packages/@patientpath/prompts/',
37
+ ]);
38
+ /**
39
+ * Coverage threshold for hex core files (percentage)
40
+ * @constant {number}
41
+ */
42
+ export const COVERAGE_THRESHOLD = 90;
43
+ /**
44
+ * Default path to coverage summary JSON
45
+ * @constant {string}
46
+ */
47
+ export const DEFAULT_COVERAGE_PATH = 'coverage/coverage-summary.json';
48
+ /**
49
+ * Check if a file path is in the hex core layer.
50
+ *
51
+ * WU-2448: Coverage reporters may emit absolute paths (e.g., /home/.../packages/...)
52
+ * or file:// URLs; use substring matching so hex-core checks still apply.
53
+ *
54
+ * @param {string|null|undefined} filePath - File path to check
55
+ * @returns {boolean} True if file is in hex core layer
56
+ */
57
+ export function isHexCoreFile(filePath) {
58
+ if (!filePath || typeof filePath !== 'string') {
59
+ return false;
60
+ }
61
+ // Normalize backslashes to forward slashes for cross-platform compatibility
62
+ const normalizedPath = filePath.replace(/\\/g, '/');
63
+ return HEX_CORE_PATTERNS.some((pattern) => normalizedPath.includes(pattern));
64
+ }
65
+ /**
66
+ * Parse coverage JSON file.
67
+ *
68
+ * @param {string} coveragePath - Path to coverage-summary.json
69
+ * @returns {object|null} Parsed coverage data or null if invalid
70
+ */
71
+ export function parseCoverageJson(coveragePath) {
72
+ if (!existsSync(coveragePath)) {
73
+ return null;
74
+ }
75
+ try {
76
+ const content = readFileSync(coveragePath, { encoding: 'utf-8' });
77
+ const data = JSON.parse(content);
78
+ // Transform to consistent format
79
+ const files = {};
80
+ for (const [key, value] of Object.entries(data)) {
81
+ if (key === 'total')
82
+ continue;
83
+ files[key] = value;
84
+ }
85
+ return {
86
+ total: data.total,
87
+ files,
88
+ };
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ /**
95
+ * Check if coverage meets thresholds for hex core files.
96
+ *
97
+ * @param {object|null} coverageData - Parsed coverage data
98
+ * @returns {{ pass: boolean, failures: Array<{ file: string, actual: number, threshold: number, metric: string }> }}
99
+ */
100
+ export function checkCoverageThresholds(coverageData) {
101
+ if (!coverageData || !coverageData.files) {
102
+ return { pass: true, failures: [] };
103
+ }
104
+ const failures = [];
105
+ for (const [file, metricsValue] of Object.entries(coverageData.files)) {
106
+ if (!isHexCoreFile(file)) {
107
+ continue;
108
+ }
109
+ // Check lines coverage (primary metric)
110
+ const metrics = metricsValue;
111
+ const linesCoverage = metrics.lines?.pct ?? 0;
112
+ if (linesCoverage < COVERAGE_THRESHOLD) {
113
+ failures.push({
114
+ file,
115
+ actual: linesCoverage,
116
+ threshold: COVERAGE_THRESHOLD,
117
+ metric: 'lines',
118
+ });
119
+ }
120
+ }
121
+ return {
122
+ pass: failures.length === 0,
123
+ failures,
124
+ };
125
+ }
126
+ /**
127
+ * Format coverage data for display.
128
+ *
129
+ * @param {object|null} coverageData - Parsed coverage data
130
+ * @returns {string} Formatted output string
131
+ */
132
+ export function formatCoverageDelta(coverageData) {
133
+ if (!coverageData) {
134
+ return '';
135
+ }
136
+ const lines = [];
137
+ const totalPct = coverageData.total?.lines?.pct ?? 0;
138
+ lines.push(`${STRING_LITERALS.NEWLINE}Coverage Summary: ${totalPct.toFixed(1)}% lines${STRING_LITERALS.NEWLINE}`);
139
+ // Show hex core files
140
+ const hexCoreFiles = Object.entries(coverageData.files || {}).filter(([file]) => isHexCoreFile(file));
141
+ if (hexCoreFiles.length > 0) {
142
+ lines.push('Hex Core Files:');
143
+ for (const [file, metricsValue] of hexCoreFiles) {
144
+ const metrics = metricsValue;
145
+ const pct = metrics.lines?.pct ?? 0;
146
+ const status = pct >= COVERAGE_THRESHOLD ? EMOJI.SUCCESS : EMOJI.FAILURE;
147
+ const shortFile = file.replace('packages/@patientpath/', '');
148
+ lines.push(` ${status} ${shortFile}: ${pct.toFixed(1)}%`);
149
+ }
150
+ }
151
+ return lines.join(STRING_LITERALS.NEWLINE);
152
+ }
153
+ /**
154
+ * Run coverage gate.
155
+ *
156
+ * @param {CoverageGateOptions} options - Gate options
157
+ * @returns {Promise<{ ok: boolean, mode: string, duration: number, message: string }>}
158
+ */
159
+ export async function runCoverageGate(options = {}) {
160
+ const start = Date.now();
161
+ const mode = options.mode || COVERAGE_GATE_MODES.WARN;
162
+ const coveragePath = options.coveragePath || DEFAULT_COVERAGE_PATH;
163
+ const logger = options.logger && typeof options.logger.log === 'function' ? options.logger : console;
164
+ // Parse coverage data
165
+ const coverageData = parseCoverageJson(coveragePath);
166
+ if (!coverageData) {
167
+ const duration = Date.now() - start;
168
+ logger.log(`\n${EMOJI.WARNING} Coverage gate: No coverage data found at ${coveragePath}`);
169
+ logger.log(' Run tests with coverage first: pnpm test:coverage\n');
170
+ return { ok: true, mode, duration, message: 'No coverage data' };
171
+ }
172
+ // Check thresholds
173
+ const { pass, failures } = checkCoverageThresholds(coverageData);
174
+ // Format and display
175
+ const output = formatCoverageDelta(coverageData);
176
+ logger.log(output);
177
+ const duration = Date.now() - start;
178
+ if (!pass) {
179
+ logger.log(`\n${EMOJI.FAILURE} Coverage below ${COVERAGE_THRESHOLD}% for hex core files:`);
180
+ for (const failure of failures) {
181
+ const shortFile = failure.file.replace('packages/@patientpath/', '');
182
+ logger.log(` - ${shortFile}: ${failure.actual.toFixed(1)}% (requires ${failure.threshold}%)`);
183
+ }
184
+ if (mode === COVERAGE_GATE_MODES.BLOCK) {
185
+ logger.log(`\n${EMOJI.FAILURE} Coverage gate FAILED (mode: block)\n`);
186
+ return { ok: false, mode, duration, message: 'Coverage threshold not met' };
187
+ }
188
+ else {
189
+ logger.log(`\n${EMOJI.WARNING} Coverage gate WARNING (mode: warn)\n`);
190
+ logger.log(' Note: This will become blocking in future. Fix coverage now.\n');
191
+ return { ok: true, mode, duration, message: 'Coverage warning' };
192
+ }
193
+ }
194
+ logger.log(`\n${EMOJI.SUCCESS} Coverage gate passed\n`);
195
+ return { ok: true, mode, duration, message: 'Coverage OK' };
196
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @file date-utils.mjs
3
+ * @description Date formatting utilities using date-fns library
4
+ * WU-1082: Extract shared utilities (eliminate date formatting duplication)
5
+ *
6
+ * Replaces manual date formatting in:
7
+ * - tools/wu-block.mjs (todayISO)
8
+ * - tools/wu-unblock.mjs (todayISO)
9
+ * - tools/wu-done.mjs (todayISO - already uses date-fns)
10
+ */
11
+ /**
12
+ * Get current date in ISO format (YYYY-MM-DD)
13
+ * @returns {string} Current date in YYYY-MM-DD format
14
+ * @example
15
+ * todayISO(); // "2025-11-12"
16
+ */
17
+ export declare function todayISO(): string;
18
+ /**
19
+ * Format a date with a custom format string
20
+ * @param {Date|string|number} date - Date to format
21
+ * @param {string} formatString - date-fns format string
22
+ * @returns {string} Formatted date string
23
+ * @example
24
+ * formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss');
25
+ * formatDate('2025-11-12', 'MMMM d, yyyy'); // "November 12, 2025"
26
+ */
27
+ export declare function formatDate(date: any, formatString: any): string;
28
+ /**
29
+ * Normalize a Date object or ISO timestamp string to YYYY-MM-DD format
30
+ *
31
+ * WU-1442: Fix date corruption when js-yaml parses YYYY-MM-DD as Date objects
32
+ * Library-First: Uses date-fns for date formatting (no manual parsing)
33
+ *
34
+ * Use case: js-yaml parses `created: 2025-12-04` (unquoted) as a Date object.
35
+ * When yaml.dump() serializes it back, it outputs `2025-12-04T00:00:00.000Z`.
36
+ * This function normalizes Date objects back to YYYY-MM-DD string format.
37
+ *
38
+ * Handles:
39
+ * - Date objects → YYYY-MM-DD string
40
+ * - ISO timestamp strings → YYYY-MM-DD string (date portion)
41
+ * - YYYY-MM-DD strings → preserved as-is
42
+ * - undefined/null → preserved as undefined
43
+ *
44
+ * @param {Date|string|undefined|null} value - Date value to normalize
45
+ * @returns {string|undefined} Date in YYYY-MM-DD format or undefined
46
+ *
47
+ * @example
48
+ * normalizeToDateString(new Date('2025-12-04')); // '2025-12-04'
49
+ * normalizeToDateString('2025-12-04T00:00:00.000Z'); // '2025-12-04'
50
+ * normalizeToDateString('2025-12-04'); // '2025-12-04'
51
+ * normalizeToDateString(undefined); // undefined
52
+ */
53
+ export declare function normalizeToDateString(value: any): string;
54
+ /**
55
+ * Normalize various date formats to ISO 8601 datetime (YYYY-MM-DDTHH:mm:ss.sssZ)
56
+ *
57
+ * WU-1337: Auto-repair date fields in WU YAML to consistent format
58
+ * Library-First: Uses date-fns for date handling (no manual parsing)
59
+ *
60
+ * Handles:
61
+ * - ISO date strings (YYYY-MM-DD) → midnight UTC
62
+ * - ISO datetime strings (already valid) → preserved
63
+ * - Unix timestamps (milliseconds) → converted
64
+ * - undefined/null → preserved as undefined
65
+ *
66
+ * @param {string|number|undefined|null} value - Date value to normalize
67
+ * @returns {string|undefined} ISO datetime string or undefined
68
+ *
69
+ * @example
70
+ * normalizeISODateTime('2025-11-29'); // '2025-11-29T00:00:00.000Z'
71
+ * normalizeISODateTime('2025-11-29T14:30:00.000Z'); // '2025-11-29T14:30:00.000Z'
72
+ * normalizeISODateTime(1732896000000); // '2024-11-29T16:00:00.000Z'
73
+ * normalizeISODateTime(undefined); // undefined
74
+ */
75
+ export declare function normalizeISODateTime(value: any): string;