@lumenflow/cli 3.17.7 → 3.18.1

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 (156) hide show
  1. package/README.md +44 -43
  2. package/dist/chunk-2D2VOCA4.js +37 -0
  3. package/dist/chunk-2D5KFYGX.js +284 -0
  4. package/dist/chunk-2GXVIN57.js +14072 -0
  5. package/dist/chunk-2MQ7HZWZ.js +26 -0
  6. package/dist/chunk-2UFQ3A3C.js +643 -0
  7. package/dist/chunk-3RG5ZIWI.js +10 -0
  8. package/dist/chunk-4N74J3UT.js +15 -0
  9. package/dist/chunk-5GTOXFYR.js +392 -0
  10. package/dist/chunk-5VY6MQMC.js +240 -0
  11. package/dist/chunk-67XVPMRY.js +1297 -0
  12. package/dist/chunk-6HO4GWJE.js +164 -0
  13. package/dist/chunk-6W5XHWYV.js +1890 -0
  14. package/dist/chunk-6X4EMYJQ.js +64 -0
  15. package/dist/chunk-6XYXI2NQ.js +772 -0
  16. package/dist/chunk-7ANSOV6Q.js +285 -0
  17. package/dist/chunk-A624LFLB.js +1380 -0
  18. package/dist/chunk-ADN5NHG4.js +126 -0
  19. package/dist/chunk-B7YJYJKG.js +33 -0
  20. package/dist/chunk-CCLHCPKG.js +210 -0
  21. package/dist/chunk-CK36VROC.js +1584 -0
  22. package/dist/chunk-D3UOFRSB.js +81 -0
  23. package/dist/chunk-DFR4DJBM.js +230 -0
  24. package/dist/chunk-DSYBDHYH.js +79 -0
  25. package/dist/chunk-DWMLTXKQ.js +1176 -0
  26. package/dist/chunk-E3REJTAJ.js +28 -0
  27. package/dist/chunk-EA3IVO64.js +633 -0
  28. package/dist/chunk-EK2AKZKD.js +55 -0
  29. package/dist/chunk-ELD7JTTT.js +343 -0
  30. package/dist/chunk-EX6TT2XI.js +195 -0
  31. package/dist/chunk-EXINSFZE.js +82 -0
  32. package/dist/chunk-EZ6ZBYBM.js +510 -0
  33. package/dist/chunk-FBKAPTJ2.js +16 -0
  34. package/dist/chunk-FVLV5RYH.js +1118 -0
  35. package/dist/chunk-GDNSBQVK.js +2485 -0
  36. package/dist/chunk-GPQHMBNN.js +278 -0
  37. package/dist/chunk-GTFJB67L.js +68 -0
  38. package/dist/chunk-HANJXVKW.js +1127 -0
  39. package/dist/chunk-HEVS5YLD.js +269 -0
  40. package/dist/chunk-HMEVZKPQ.js +9 -0
  41. package/dist/chunk-HRGSYNLM.js +3511 -0
  42. package/dist/chunk-ISZR5N4K.js +60 -0
  43. package/dist/chunk-J6SUPR2C.js +226 -0
  44. package/dist/chunk-JERYVEIZ.js +244 -0
  45. package/dist/chunk-JHHWGL2N.js +87 -0
  46. package/dist/chunk-JONWQUB5.js +775 -0
  47. package/dist/chunk-K2DIWWDM.js +1766 -0
  48. package/dist/chunk-KY4PGL5V.js +969 -0
  49. package/dist/chunk-L737LQ4C.js +1285 -0
  50. package/dist/chunk-LFTWYIB2.js +497 -0
  51. package/dist/chunk-LV47RFNJ.js +41 -0
  52. package/dist/chunk-MKSAITI7.js +15 -0
  53. package/dist/chunk-MZ7RKIX4.js +212 -0
  54. package/dist/chunk-NAP6CFSO.js +84 -0
  55. package/dist/chunk-ND6MY37M.js +16 -0
  56. package/dist/chunk-NMG736UR.js +683 -0
  57. package/dist/chunk-NRAXROED.js +32 -0
  58. package/dist/chunk-NRIZR3A7.js +690 -0
  59. package/dist/chunk-NX43BG3M.js +233 -0
  60. package/dist/chunk-O645XLSI.js +297 -0
  61. package/dist/chunk-OMJD6A3S.js +235 -0
  62. package/dist/chunk-QB6SJD4T.js +430 -0
  63. package/dist/chunk-QFSTL4J3.js +276 -0
  64. package/dist/chunk-QLGDFMFX.js +212 -0
  65. package/dist/chunk-RIAAGL2E.js +13 -0
  66. package/dist/chunk-RWO5XMZ6.js +86 -0
  67. package/dist/chunk-RXRKBBSM.js +149 -0
  68. package/dist/chunk-RZOZMML6.js +363 -0
  69. package/dist/chunk-U7I7FS7T.js +113 -0
  70. package/dist/chunk-UI42RODY.js +717 -0
  71. package/dist/chunk-UTVMVSCO.js +519 -0
  72. package/dist/chunk-V6OJGLBA.js +1746 -0
  73. package/dist/chunk-W2JHVH7D.js +152 -0
  74. package/dist/chunk-WD3Y7VQN.js +280 -0
  75. package/dist/chunk-WOCTQ5MS.js +303 -0
  76. package/dist/chunk-WZR3ZUNN.js +696 -0
  77. package/dist/chunk-XGI665H7.js +150 -0
  78. package/dist/chunk-XKY65P2T.js +304 -0
  79. package/dist/chunk-Y4CQZY65.js +57 -0
  80. package/dist/chunk-YFEXKLVE.js +194 -0
  81. package/dist/chunk-YHO3HS5X.js +287 -0
  82. package/dist/chunk-YLS7AZSX.js +738 -0
  83. package/dist/chunk-ZE473AO6.js +49 -0
  84. package/dist/chunk-ZF747T3O.js +644 -0
  85. package/dist/chunk-ZHCZHZH3.js +43 -0
  86. package/dist/chunk-ZZNZX2XY.js +87 -0
  87. package/dist/config-set.js +10 -1
  88. package/dist/config-set.js.map +1 -1
  89. package/dist/constants-7QAP3VQ4.js +23 -0
  90. package/dist/dist-IY3UUMWK.js +33 -0
  91. package/dist/gate-co-change.js +5 -2
  92. package/dist/gate-co-change.js.map +1 -1
  93. package/dist/init-detection.js +5 -3
  94. package/dist/init-detection.js.map +1 -1
  95. package/dist/init-templates.js +4 -4
  96. package/dist/init-templates.js.map +1 -1
  97. package/dist/initiative-edit.js +8 -3
  98. package/dist/initiative-edit.js.map +1 -1
  99. package/dist/initiative-plan.js +1 -1
  100. package/dist/initiative-plan.js.map +1 -1
  101. package/dist/invariants-runner-W5RGHCSU.js +27 -0
  102. package/dist/lane-lock-6J36HD5O.js +35 -0
  103. package/dist/lumenflow-upgrade.js +49 -0
  104. package/dist/lumenflow-upgrade.js.map +1 -1
  105. package/dist/mem-checkpoint-core-EANG2GVN.js +14 -0
  106. package/dist/mem-signal-core-2LZ2WYHW.js +19 -0
  107. package/dist/memory-store-OLB5FO7K.js +18 -0
  108. package/dist/pre-commit-check.js +1 -1
  109. package/dist/pre-commit-check.js.map +1 -1
  110. package/dist/service-6BYCOCO5.js +13 -0
  111. package/dist/spawn-policy-resolver-NTSZYQ6R.js +17 -0
  112. package/dist/spawn-task-builder-R4E2BHSW.js +22 -0
  113. package/dist/wu-done-pr-WLFFFEPJ.js +25 -0
  114. package/dist/wu-done-validation-3J5E36FE.js +30 -0
  115. package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
  116. package/dist/wu-edit-operations.js +4 -0
  117. package/dist/wu-edit-operations.js.map +1 -1
  118. package/dist/wu-edit-validators.js +4 -0
  119. package/dist/wu-edit-validators.js.map +1 -1
  120. package/dist/wu-edit.js +11 -0
  121. package/dist/wu-edit.js.map +1 -1
  122. package/dist/wu-spawn-strategy-resolver.js +13 -1
  123. package/dist/wu-spawn-strategy-resolver.js.map +1 -1
  124. package/package.json +8 -8
  125. package/packs/agent-runtime/.turbo/turbo-build.log +4 -0
  126. package/packs/agent-runtime/README.md +147 -0
  127. package/packs/agent-runtime/capability-factory.ts +104 -0
  128. package/packs/agent-runtime/config.schema.json +87 -0
  129. package/packs/agent-runtime/constants.ts +21 -0
  130. package/packs/agent-runtime/index.ts +11 -0
  131. package/packs/agent-runtime/manifest.ts +207 -0
  132. package/packs/agent-runtime/manifest.yaml +193 -0
  133. package/packs/agent-runtime/orchestration.ts +1787 -0
  134. package/packs/agent-runtime/pack-registration.ts +110 -0
  135. package/packs/agent-runtime/package.json +57 -0
  136. package/packs/agent-runtime/policy-factory.ts +165 -0
  137. package/packs/agent-runtime/tool-impl/agent-turn-tools.ts +793 -0
  138. package/packs/agent-runtime/tool-impl/index.ts +5 -0
  139. package/packs/agent-runtime/tool-impl/provider-adapters.ts +1245 -0
  140. package/packs/agent-runtime/tools/index.ts +4 -0
  141. package/packs/agent-runtime/tools/types.ts +47 -0
  142. package/packs/agent-runtime/tsconfig.json +20 -0
  143. package/packs/agent-runtime/types.ts +128 -0
  144. package/packs/agent-runtime/vitest.config.ts +11 -0
  145. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  146. package/packs/sidekick/.turbo/turbo-test.log +12 -0
  147. package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
  148. package/packs/sidekick/package.json +1 -1
  149. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  150. package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
  151. package/packs/software-delivery/package.json +1 -1
  152. package/templates/core/.lumenflow/rules/wu-workflow.md.template +1 -1
  153. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +2 -2
  154. package/templates/core/ai/onboarding/quick-ref-commands.md.template +1 -1
  155. package/templates/core/ai/onboarding/starting-prompt.md.template +1 -1
  156. package/templates/vendors/claude/.claude/skills/frontend-design/SKILL.md.template +1 -1
@@ -0,0 +1,2485 @@
1
+ import {
2
+ scaffoldProject
3
+ } from "./chunk-A624LFLB.js";
4
+ import {
5
+ runLaneHealthCheck
6
+ } from "./chunk-XKY65P2T.js";
7
+ import {
8
+ LOG_TAIL_MAX_BYTES,
9
+ LOG_TAIL_MAX_LINES
10
+ } from "./chunk-Y4CQZY65.js";
11
+ import {
12
+ createWUParser,
13
+ runCLI,
14
+ validateClaimValidation
15
+ } from "./chunk-2GXVIN57.js";
16
+ import {
17
+ createGitForPath,
18
+ getGitForCwd
19
+ } from "./chunk-2UFQ3A3C.js";
20
+ import {
21
+ emitGateEvent,
22
+ getCurrentLane,
23
+ getCurrentWU,
24
+ syncNdjsonTelemetryToCloud
25
+ } from "./chunk-6XYXI2NQ.js";
26
+ import {
27
+ runInvariants
28
+ } from "./chunk-EZ6ZBYBM.js";
29
+ import {
30
+ readWURaw
31
+ } from "./chunk-NRIZR3A7.js";
32
+ import {
33
+ createWuPaths
34
+ } from "./chunk-6HO4GWJE.js";
35
+ import {
36
+ BRANCHES,
37
+ CACHE_STRATEGIES,
38
+ DIRECTORIES,
39
+ EMOJI,
40
+ ESLINT_COMMANDS,
41
+ ESLINT_DEFAULTS,
42
+ ESLINT_FLAGS,
43
+ EXIT_CODES,
44
+ FILE_SYSTEM,
45
+ GATE_COMMANDS,
46
+ GATE_NAMES,
47
+ GIT_REFS,
48
+ PACKAGES,
49
+ PKG_MANAGER,
50
+ PRETTIER_ARGS,
51
+ PRETTIER_FLAGS,
52
+ SCRIPTS,
53
+ STRING_LITERALS
54
+ } from "./chunk-DWMLTXKQ.js";
55
+ import {
56
+ CoChangeRuleConfigSchema,
57
+ DEFAULT_MIN_COVERAGE,
58
+ WORKSPACE_CONFIG_FILE_NAME,
59
+ WORKSPACE_V2_KEYS,
60
+ getGatesSection,
61
+ loadLaneHealthConfig,
62
+ resolveGatesCommands,
63
+ resolveTestPolicy,
64
+ resolveTestRunner
65
+ } from "./chunk-V6OJGLBA.js";
66
+ import {
67
+ ErrorCodes,
68
+ createError,
69
+ die
70
+ } from "./chunk-RXRKBBSM.js";
71
+
72
+ // src/gates.ts
73
+ import { writeSync as writeSync3 } from "fs";
74
+
75
+ // ../core/dist/gates-agent-mode.js
76
+ import path from "path";
77
+ import { existsSync, unlinkSync, symlinkSync } from "fs";
78
+ function shouldUseGatesAgentMode({ argv, env, stdout } = {}) {
79
+ const isVerbose = Array.isArray(argv) && argv.includes("--verbose");
80
+ if (isVerbose) {
81
+ return false;
82
+ }
83
+ const hasClaudeProjectDir = Boolean(env?.CLAUDE_PROJECT_DIR);
84
+ if (hasClaudeProjectDir) {
85
+ return true;
86
+ }
87
+ const isCI = Boolean(env?.CI);
88
+ if (isCI) {
89
+ return false;
90
+ }
91
+ const stdoutStream = stdout ?? process.stdout;
92
+ const isTTY = stdoutStream?.isTTY ?? false;
93
+ return !isTTY;
94
+ }
95
+ function getGatesLogDir({ cwd, env }) {
96
+ const configured = env?.LUMENFLOW_LOG_DIR;
97
+ return path.resolve(cwd, configured || ".logs");
98
+ }
99
+ function buildGatesLogPath({ cwd, env, wuId, lane, now = /* @__PURE__ */ new Date() }) {
100
+ const logDir = getGatesLogDir({ cwd, env });
101
+ const safeLane = (lane || "unknown").toLowerCase().replace(/[^a-z0-9]+/g, "-");
102
+ const safeWu = (wuId || "unknown").toLowerCase().replace(/[^a-z0-9]+/g, "-");
103
+ const stamp = now.toISOString().replace(/[:.]/g, "-");
104
+ return path.join(logDir, `gates-${safeLane}-${safeWu}-${stamp}.log`);
105
+ }
106
+ function getGatesLatestSymlinkPath({ cwd, env }) {
107
+ const logDir = getGatesLogDir({ cwd, env });
108
+ return path.join(logDir, "gates-latest.log");
109
+ }
110
+ function updateGatesLatestSymlink({ logPath, cwd, env }) {
111
+ const symlinkPath = getGatesLatestSymlinkPath({ cwd, env });
112
+ try {
113
+ if (existsSync(symlinkPath)) {
114
+ unlinkSync(symlinkPath);
115
+ }
116
+ const logDir = path.dirname(symlinkPath);
117
+ const relativePath = path.relative(logDir, logPath);
118
+ symlinkSync(relativePath, symlinkPath);
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ var WU_DONE_PRE_COMMIT_GATE_DECISION_REASONS = {
125
+ SKIP_GATES_FLAG: "skip-gates-flag",
126
+ REUSE_STEP_ZERO: "reuse-step-zero",
127
+ REUSE_CHECKPOINT: "reuse-checkpoint",
128
+ RUN_REQUIRED: "run-required"
129
+ };
130
+ function resolveWuDonePreCommitGateDecision(input) {
131
+ if (input.skipGates) {
132
+ return {
133
+ runPreCommitFullSuite: false,
134
+ reason: WU_DONE_PRE_COMMIT_GATE_DECISION_REASONS.SKIP_GATES_FLAG,
135
+ message: "Pre-flight hook validation skipped because --skip-gates is active."
136
+ };
137
+ }
138
+ if (input.fullGatesRanInCurrentRun) {
139
+ return {
140
+ runPreCommitFullSuite: false,
141
+ reason: WU_DONE_PRE_COMMIT_GATE_DECISION_REASONS.REUSE_STEP_ZERO,
142
+ message: "Pre-flight hook validation reuses Step 0 gate results; duplicate full-suite run skipped."
143
+ };
144
+ }
145
+ if (input.skippedByCheckpoint) {
146
+ const checkpointSuffix = input.checkpointId ? ` (${input.checkpointId})` : "";
147
+ return {
148
+ runPreCommitFullSuite: false,
149
+ reason: WU_DONE_PRE_COMMIT_GATE_DECISION_REASONS.REUSE_CHECKPOINT,
150
+ message: `Pre-flight hook validation reuses checkpoint gate attestation${checkpointSuffix}; duplicate full-suite run skipped.`
151
+ };
152
+ }
153
+ return {
154
+ runPreCommitFullSuite: true,
155
+ reason: WU_DONE_PRE_COMMIT_GATE_DECISION_REASONS.RUN_REQUIRED,
156
+ message: "No gate attestation found for this wu:done run; executing pre-flight hook gate suite."
157
+ };
158
+ }
159
+
160
+ // ../core/dist/coverage-gate.js
161
+ import { readFileSync, existsSync as existsSync2 } from "fs";
162
+ var COVERAGE_GATE_MODES = Object.freeze({
163
+ /** Log warnings but don't fail the gate */
164
+ WARN: "warn",
165
+ /** Fail the gate if thresholds not met */
166
+ BLOCK: "block"
167
+ });
168
+ var HEX_CORE_PATTERNS = Object.freeze([
169
+ "packages/@lumenflow/core/",
170
+ "packages/@lumenflow/cli/"
171
+ ]);
172
+ var COVERAGE_THRESHOLD = DEFAULT_MIN_COVERAGE;
173
+ var DEFAULT_COVERAGE_PATH = "coverage/coverage-summary.json";
174
+ function isHexCoreFile(filePath) {
175
+ if (!filePath || typeof filePath !== "string") {
176
+ return false;
177
+ }
178
+ const normalizedPath = filePath.replace(/\\/g, "/");
179
+ return HEX_CORE_PATTERNS.some((pattern) => normalizedPath.includes(pattern));
180
+ }
181
+ function parseCoverageJson(coveragePath) {
182
+ if (!existsSync2(coveragePath)) {
183
+ return null;
184
+ }
185
+ try {
186
+ const content = readFileSync(coveragePath, { encoding: "utf-8" });
187
+ const data = JSON.parse(content);
188
+ const files = {};
189
+ for (const [key, value] of Object.entries(data)) {
190
+ if (key === "total")
191
+ continue;
192
+ files[key] = value;
193
+ }
194
+ return {
195
+ total: data.total,
196
+ files
197
+ };
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+ function checkCoverageThresholds(coverageData, threshold) {
203
+ if (!coverageData || !coverageData.files) {
204
+ return { pass: true, failures: [] };
205
+ }
206
+ const effectiveThreshold = threshold ?? COVERAGE_THRESHOLD;
207
+ const failures = [];
208
+ for (const [file, metricsValue] of Object.entries(coverageData.files)) {
209
+ if (!isHexCoreFile(file)) {
210
+ continue;
211
+ }
212
+ const metrics = metricsValue;
213
+ const linesCoverage = metrics.lines?.pct ?? 0;
214
+ if (linesCoverage < effectiveThreshold) {
215
+ failures.push({
216
+ file,
217
+ actual: linesCoverage,
218
+ threshold: effectiveThreshold,
219
+ metric: "lines"
220
+ });
221
+ }
222
+ }
223
+ return {
224
+ pass: failures.length === 0,
225
+ failures
226
+ };
227
+ }
228
+ function formatCoverageDelta(coverageData) {
229
+ if (!coverageData) {
230
+ return "";
231
+ }
232
+ const lines = [];
233
+ const totalPct = coverageData.total?.lines?.pct ?? 0;
234
+ lines.push(`${STRING_LITERALS.NEWLINE}Coverage Summary: ${totalPct.toFixed(1)}% lines${STRING_LITERALS.NEWLINE}`);
235
+ const hexCoreFiles = Object.entries(coverageData.files || {}).filter(([file]) => isHexCoreFile(file));
236
+ if (hexCoreFiles.length > 0) {
237
+ lines.push("Hex Core Files:");
238
+ for (const [file, metricsValue] of hexCoreFiles) {
239
+ const metrics = metricsValue;
240
+ const pct = metrics.lines?.pct ?? 0;
241
+ const status = pct >= COVERAGE_THRESHOLD ? EMOJI.SUCCESS : EMOJI.FAILURE;
242
+ const shortFile = file.replace("packages/@lumenflow/", "");
243
+ lines.push(` ${status} ${shortFile}: ${pct.toFixed(1)}%`);
244
+ }
245
+ }
246
+ return lines.join(STRING_LITERALS.NEWLINE);
247
+ }
248
+ async function runCoverageGate(options = {}) {
249
+ const start = Date.now();
250
+ const mode = options.mode || COVERAGE_GATE_MODES.WARN;
251
+ const coveragePath = options.coveragePath || DEFAULT_COVERAGE_PATH;
252
+ const logger = options.logger && typeof options.logger.log === "function" ? options.logger : console;
253
+ const threshold = options.threshold ?? COVERAGE_THRESHOLD;
254
+ const coverageData = parseCoverageJson(coveragePath);
255
+ if (!coverageData) {
256
+ const duration2 = Date.now() - start;
257
+ logger.log(`
258
+ ${EMOJI.WARNING} Coverage gate: No coverage data found at ${coveragePath}`);
259
+ logger.log(" Run tests with coverage first: pnpm test:coverage\n");
260
+ return { ok: true, mode, duration: duration2, message: "No coverage data" };
261
+ }
262
+ const { pass, failures } = checkCoverageThresholds(coverageData, threshold);
263
+ const output = formatCoverageDelta(coverageData);
264
+ logger.log(output);
265
+ const duration = Date.now() - start;
266
+ if (!pass) {
267
+ logger.log(`
268
+ ${EMOJI.FAILURE} Coverage below ${threshold}% for hex core files:`);
269
+ for (const failure of failures) {
270
+ const shortFile = failure.file.replace("packages/@lumenflow/", "");
271
+ logger.log(` - ${shortFile}: ${failure.actual.toFixed(1)}% (requires ${failure.threshold}%)`);
272
+ }
273
+ if (mode === COVERAGE_GATE_MODES.BLOCK) {
274
+ logger.log(`
275
+ ${EMOJI.FAILURE} Coverage gate FAILED (mode: block)
276
+ `);
277
+ return { ok: false, mode, duration, message: "Coverage threshold not met" };
278
+ } else {
279
+ logger.log(`
280
+ ${EMOJI.WARNING} Coverage gate WARNING (mode: warn)
281
+ `);
282
+ logger.log(" Note: This will become blocking in future. Fix coverage now.\n");
283
+ return { ok: true, mode, duration, message: "Coverage warning" };
284
+ }
285
+ }
286
+ logger.log(`
287
+ ${EMOJI.SUCCESS} Coverage gate passed
288
+ `);
289
+ return { ok: true, mode, duration, message: "Coverage OK" };
290
+ }
291
+
292
+ // src/gates-graceful-degradation.ts
293
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
294
+ import path2 from "path";
295
+ var NON_SKIPPABLE_GATES = ["invariants"];
296
+ function checkScriptExists(scriptName, scripts) {
297
+ if (!scripts) return false;
298
+ return Object.prototype.hasOwnProperty.call(scripts, scriptName);
299
+ }
300
+ function loadPackageJsonScripts(projectRoot) {
301
+ const packageJsonPath = path2.join(projectRoot, "package.json");
302
+ if (!existsSync3(packageJsonPath)) return void 0;
303
+ try {
304
+ const content = readFileSync2(packageJsonPath, "utf8");
305
+ const pkg = JSON.parse(content);
306
+ return pkg.scripts;
307
+ } catch {
308
+ return void 0;
309
+ }
310
+ }
311
+ function buildMissingScriptWarning(scriptName) {
312
+ const suggestions = {
313
+ "format:check": '"format:check": "prettier --check ."',
314
+ lint: '"lint": "eslint ."',
315
+ typecheck: '"typecheck": "tsc --noEmit"',
316
+ "spec:linter": '"spec:linter": "node tools/spec-linter.js"'
317
+ };
318
+ const suggestion = suggestions[scriptName] ?? `"${scriptName}": "<your-command>"`;
319
+ return [
320
+ `Warning: "${scriptName}" script not found in package.json - skipping gate.`,
321
+ ` To enable this gate, add to your package.json scripts:`,
322
+ ` ${suggestion}`
323
+ ].join("\n");
324
+ }
325
+ function resolveGateAction(gateName, scriptName, scripts, strict) {
326
+ if (NON_SKIPPABLE_GATES.includes(gateName)) {
327
+ return "run";
328
+ }
329
+ if (!scriptName) {
330
+ return "run";
331
+ }
332
+ if (checkScriptExists(scriptName, scripts)) {
333
+ return "run";
334
+ }
335
+ if (strict) {
336
+ return "fail";
337
+ }
338
+ return "skip";
339
+ }
340
+ function formatGateSummary(results) {
341
+ if (results.length === 0) {
342
+ return "No gates were executed.";
343
+ }
344
+ const passed = results.filter((r) => r.status === "passed");
345
+ const skipped = results.filter((r) => r.status === "skipped");
346
+ const failed = results.filter((r) => r.status === "failed");
347
+ const warned = results.filter((r) => r.status === "warned");
348
+ const lines = [];
349
+ lines.push("Gate Summary:");
350
+ lines.push("");
351
+ for (const result of results) {
352
+ const statusIcon = result.status === "passed" ? "PASS" : result.status === "skipped" ? "SKIP" : result.status === "warned" ? "WARN" : "FAIL";
353
+ const duration = result.durationMs > 0 ? ` (${result.durationMs}ms)` : "";
354
+ const reason = result.reason ? ` - ${result.reason}` : "";
355
+ lines.push(` [${statusIcon}] ${result.name}${duration}${reason}`);
356
+ }
357
+ lines.push("");
358
+ const parts = [];
359
+ if (passed.length > 0) parts.push(`${passed.length} passed`);
360
+ if (skipped.length > 0) parts.push(`${skipped.length} skipped`);
361
+ if (warned.length > 0) parts.push(`${warned.length} warned`);
362
+ if (failed.length > 0) parts.push(`${failed.length} failed`);
363
+ lines.push(parts.join(", "));
364
+ return lines.join("\n");
365
+ }
366
+
367
+ // src/gates.ts
368
+ import chalk from "chalk";
369
+
370
+ // src/gate-registry.ts
371
+ var GateRegistry = class {
372
+ gates = [];
373
+ nameIndex = /* @__PURE__ */ new Map();
374
+ /**
375
+ * Register a single gate definition.
376
+ *
377
+ * @param gate - Gate definition to register
378
+ * @throws Error if a gate with the same name is already registered
379
+ */
380
+ register(gate) {
381
+ if (this.nameIndex.has(gate.name)) {
382
+ throw createError(
383
+ ErrorCodes.TOOL_ALREADY_REGISTERED,
384
+ `Gate "${gate.name}" is already registered`
385
+ );
386
+ }
387
+ this.nameIndex.set(gate.name, this.gates.length);
388
+ this.gates.push(gate);
389
+ }
390
+ /**
391
+ * Register multiple gate definitions at once.
392
+ *
393
+ * @param gates - Array of gate definitions to register
394
+ */
395
+ registerAll(gates) {
396
+ for (const gate of gates) {
397
+ this.register(gate);
398
+ }
399
+ }
400
+ /**
401
+ * Get all registered gates in insertion order.
402
+ *
403
+ * @returns Copy of the gates array
404
+ */
405
+ getAll() {
406
+ return [...this.gates];
407
+ }
408
+ /**
409
+ * Get a gate by name.
410
+ *
411
+ * @param name - Gate name to look up
412
+ * @returns Gate definition or undefined if not found
413
+ */
414
+ get(name) {
415
+ const index = this.nameIndex.get(name);
416
+ if (index === void 0) return void 0;
417
+ return this.gates[index];
418
+ }
419
+ /**
420
+ * Check if a gate with the given name is registered.
421
+ *
422
+ * @param name - Gate name to check
423
+ * @returns true if the gate exists
424
+ */
425
+ has(name) {
426
+ return this.nameIndex.has(name);
427
+ }
428
+ /**
429
+ * Remove all registered gates.
430
+ */
431
+ clear() {
432
+ this.gates.length = 0;
433
+ this.nameIndex.clear();
434
+ }
435
+ };
436
+
437
+ // src/gates-runners.ts
438
+ import { spawnSync as spawnSync2 } from "child_process";
439
+ import { writeSync as writeSync2 } from "fs";
440
+ import { access as access2 } from "fs/promises";
441
+ import { createRequire } from "module";
442
+ import path7 from "path";
443
+
444
+ // ../core/dist/incremental-lint.js
445
+ function ensureTrailingSlash(value) {
446
+ const normalized = value.replace(/\\/g, "/");
447
+ return normalized.endsWith("/") ? normalized : `${normalized}/`;
448
+ }
449
+ function getConfiguredWorktreesDir() {
450
+ return ensureTrailingSlash(createWuPaths({ projectRoot: process.cwd() }).WORKTREES_DIR());
451
+ }
452
+ var LINTABLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
453
+ var IGNORED_DIRECTORIES = [
454
+ "node_modules/",
455
+ ".next/",
456
+ ".expo/",
457
+ "dist/",
458
+ "build/",
459
+ ".turbo/",
460
+ "coverage/"
461
+ ];
462
+ function isLintableFile(filePath) {
463
+ const ignoredDirectories = [...IGNORED_DIRECTORIES, getConfiguredWorktreesDir()];
464
+ for (const ignored of ignoredDirectories) {
465
+ if (filePath.includes(ignored)) {
466
+ return false;
467
+ }
468
+ }
469
+ for (const ext of LINTABLE_EXTENSIONS) {
470
+ if (filePath.endsWith(ext)) {
471
+ return true;
472
+ }
473
+ }
474
+ return false;
475
+ }
476
+ function parseGitFileList(output) {
477
+ return output.split(STRING_LITERALS.NEWLINE).map((f) => f.trim()).filter((f) => f.length > 0);
478
+ }
479
+ async function getChangedLintableFiles(options = {}) {
480
+ const { git = getGitForCwd(), baseBranch = GIT_REFS.ORIGIN_MAIN, filterPath } = options;
481
+ const mergeBase = await git.mergeBase("HEAD", baseBranch);
482
+ const committedOutput = await git.raw(["diff", "--name-only", `${mergeBase}...HEAD`]);
483
+ const committedFiles = parseGitFileList(committedOutput);
484
+ const unstagedOutput = await git.raw(["diff", "--name-only"]);
485
+ const unstagedFiles = parseGitFileList(unstagedOutput);
486
+ const untrackedOutput = await git.raw(["ls-files", "--others", "--exclude-standard"]);
487
+ const untrackedFiles = parseGitFileList(untrackedOutput);
488
+ const allFiles = [.../* @__PURE__ */ new Set([...committedFiles, ...unstagedFiles, ...untrackedFiles])];
489
+ let lintableFiles = allFiles.filter(isLintableFile);
490
+ if (filterPath) {
491
+ lintableFiles = lintableFiles.filter((f) => f.startsWith(filterPath));
492
+ }
493
+ return lintableFiles;
494
+ }
495
+
496
+ // ../core/dist/incremental-test.js
497
+ var VITEST_CHANGED_EXCLUDES = Object.freeze(["**/*.integration.*", "**/golden-*.test.*"]);
498
+ var CODE_FILE_EXTENSIONS = Object.freeze([
499
+ ".ts",
500
+ ".tsx",
501
+ ".js",
502
+ ".jsx",
503
+ ".js",
504
+ ".cjs",
505
+ ".mts",
506
+ ".cts"
507
+ ]);
508
+ function isCodeFilePath(filePath) {
509
+ if (!filePath || typeof filePath !== "string") {
510
+ return false;
511
+ }
512
+ const normalized = filePath.replace(/\\/g, "/").toLowerCase();
513
+ return CODE_FILE_EXTENSIONS.some((ext) => normalized.endsWith(ext));
514
+ }
515
+ function buildVitestChangedArgs(options = {}) {
516
+ const { baseBranch = GIT_REFS.ORIGIN_MAIN } = options;
517
+ const args = [
518
+ "--changed",
519
+ baseBranch,
520
+ "--run",
521
+ "--passWithNoTests",
522
+ "--maxWorkers=1",
523
+ "--teardownTimeout=30000"
524
+ ];
525
+ for (const pattern of VITEST_CHANGED_EXCLUDES) {
526
+ args.push(`--exclude='${pattern}'`);
527
+ }
528
+ return args;
529
+ }
530
+
531
+ // ../core/dist/validators/backlog-sync.js
532
+ import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync } from "fs";
533
+ import path3 from "path";
534
+ function extractWUIDsFromBacklog(content) {
535
+ const wuIds = [];
536
+ const pattern = /WU-\d+/gi;
537
+ let match;
538
+ while ((match = pattern.exec(content)) !== null) {
539
+ const wuId = match[0].toUpperCase();
540
+ if (!wuIds.includes(wuId)) {
541
+ wuIds.push(wuId);
542
+ }
543
+ }
544
+ return wuIds;
545
+ }
546
+ function getWUIDsFromFiles(wuDir) {
547
+ if (!existsSync4(wuDir)) {
548
+ return [];
549
+ }
550
+ return readdirSync(wuDir).filter((f) => f.endsWith(".yaml")).map((f) => f.replace(".yaml", "").toUpperCase());
551
+ }
552
+ async function validateBacklogSync(options = {}) {
553
+ const { cwd = process.cwd() } = options;
554
+ const errors = [];
555
+ const warnings = [];
556
+ const paths = createWuPaths({ projectRoot: cwd });
557
+ const backlogPath = path3.join(cwd, paths.BACKLOG());
558
+ const wuDir = path3.join(cwd, paths.WU_DIR());
559
+ if (!existsSync4(backlogPath)) {
560
+ errors.push(`Backlog file not found: ${backlogPath}`);
561
+ return { valid: false, errors, warnings, wuCount: 0, backlogCount: 0 };
562
+ }
563
+ const wuIdsFromFiles = getWUIDsFromFiles(wuDir);
564
+ const backlogContent = readFileSync3(backlogPath, {
565
+ encoding: FILE_SYSTEM.UTF8
566
+ });
567
+ const wuIdsFromBacklog = extractWUIDsFromBacklog(backlogContent);
568
+ for (const wuId of wuIdsFromFiles) {
569
+ if (!wuIdsFromBacklog.includes(wuId)) {
570
+ errors.push(`${wuId} not found in backlog.md (exists as ${wuId}.yaml)`);
571
+ }
572
+ }
573
+ for (const wuId of wuIdsFromBacklog) {
574
+ if (!wuIdsFromFiles.includes(wuId)) {
575
+ warnings.push(`${wuId} referenced in backlog.md but ${wuId}.yaml not found`);
576
+ }
577
+ }
578
+ return {
579
+ valid: errors.length === 0,
580
+ errors,
581
+ warnings,
582
+ wuCount: wuIdsFromFiles.length,
583
+ backlogCount: wuIdsFromBacklog.length
584
+ };
585
+ }
586
+
587
+ // ../core/dist/validators/supabase-docs-linter.js
588
+ import { existsSync as existsSync5 } from "fs";
589
+ import path4 from "path";
590
+ import { pathToFileURL } from "url";
591
+ async function runSupabaseDocsLinter(options = {}) {
592
+ const { cwd = process.cwd(), logger = console } = options;
593
+ const linterPath = path4.join(cwd, "packages", "linters", "supabase-docs-linter.js");
594
+ if (!existsSync5(linterPath)) {
595
+ return {
596
+ ok: true,
597
+ skipped: true,
598
+ message: "Supabase docs linter not found; skipping."
599
+ };
600
+ }
601
+ const moduleUrl = pathToFileURL(linterPath).href;
602
+ const module = await import(moduleUrl);
603
+ const runFn = module.runSupabaseDocsLinter ?? module.default;
604
+ if (typeof runFn !== "function") {
605
+ return {
606
+ ok: false,
607
+ skipped: false,
608
+ errors: ["Supabase docs linter does not export runSupabaseDocsLinter."]
609
+ };
610
+ }
611
+ const result = await runFn({ cwd, logger });
612
+ if (result && typeof result === "object" && "ok" in result) {
613
+ return {
614
+ ok: Boolean(result.ok),
615
+ skipped: Boolean(result.skipped),
616
+ message: result.message,
617
+ errors: result.errors
618
+ };
619
+ }
620
+ return {
621
+ ok: true,
622
+ skipped: false,
623
+ message: "Supabase docs linter completed."
624
+ };
625
+ }
626
+
627
+ // src/gates-utils.ts
628
+ import { execSync, spawnSync } from "child_process";
629
+ import { closeSync, mkdirSync, openSync, readSync, statSync, writeSync } from "fs";
630
+ import { access } from "fs/promises";
631
+ import path5 from "path";
632
+ function pnpmCmd(...parts) {
633
+ return `${PKG_MANAGER} ${parts.join(" ")}`;
634
+ }
635
+ function pnpmRun(script, ...args) {
636
+ const argsStr = args.length > 0 ? ` ${args.join(" ")}` : "";
637
+ return `${PKG_MANAGER} ${SCRIPTS.RUN} ${script}${argsStr}`;
638
+ }
639
+ function normalizePath(filePath) {
640
+ return filePath.replace(/\\/g, "/");
641
+ }
642
+ function getBasename(filePath) {
643
+ const normalized = normalizePath(filePath);
644
+ const parts = normalized.split("/");
645
+ return parts[parts.length - 1] || normalized;
646
+ }
647
+ function quoteShellArgs(files) {
648
+ return files.map((file) => `"${file}"`).join(" ");
649
+ }
650
+ var PRETTIER_NON_FILE_OUTPUT_MARKERS = [
651
+ "code style issues found",
652
+ "all matched files use prettier",
653
+ "checking formatting",
654
+ "is a symbolic link"
655
+ ];
656
+ function isNonFilePrettierOutputLine(line) {
657
+ const normalizedLine = line.toLowerCase();
658
+ return PRETTIER_NON_FILE_OUTPUT_MARKERS.some((marker) => normalizedLine.includes(marker));
659
+ }
660
+ function parsePrettierListOutput(output) {
661
+ if (!output) return [];
662
+ return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => line.replace(/^\[error\]\s*/i, "").trim()).filter((line) => !isNonFilePrettierOutputLine(line));
663
+ }
664
+ function buildPrettierWriteCommand(files) {
665
+ const quotedFiles = files.map((file) => `"${file}"`).join(" ");
666
+ const base = pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_FLAGS.WRITE);
667
+ return quotedFiles ? `${base} ${quotedFiles}` : base;
668
+ }
669
+ function buildPrettierCheckCommand(files) {
670
+ const filesArg = files.length > 0 ? quoteShellArgs(files) : ".";
671
+ return pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_ARGS.CHECK, filesArg);
672
+ }
673
+ function formatFormatCheckGuidance(files) {
674
+ if (!files.length) return [];
675
+ const command = buildPrettierWriteCommand(files);
676
+ return [
677
+ "",
678
+ "\u274C format:check failed",
679
+ "Fix with:",
680
+ ` ${command}`,
681
+ "",
682
+ "Affected files:",
683
+ ...files.map((file) => ` - ${file}`),
684
+ ""
685
+ ];
686
+ }
687
+ function collectPrettierListDifferent(cwd, files = []) {
688
+ const filesArg = files.length > 0 ? quoteShellArgs(files) : ".";
689
+ const cmd = pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_ARGS.LIST_DIFFERENT, filesArg);
690
+ const result = spawnSync(cmd, [], {
691
+ shell: true,
692
+ cwd,
693
+ encoding: FILE_SYSTEM.ENCODING
694
+ });
695
+ const output = `${result.stdout || ""}
696
+ ${result.stderr || ""}`;
697
+ return parsePrettierListOutput(output);
698
+ }
699
+ function emitFormatCheckGuidance({
700
+ agentLog,
701
+ useAgentMode,
702
+ files,
703
+ cwd
704
+ }) {
705
+ const formattedFiles = collectPrettierListDifferent(cwd, files ?? []);
706
+ if (!formattedFiles.length) return;
707
+ const lines = formatFormatCheckGuidance(formattedFiles);
708
+ const logLine = useAgentMode && agentLog ? (line) => writeSync(agentLog.logFd, `${line}
709
+ `) : (line) => console.log(line);
710
+ for (const line of lines) {
711
+ logLine(line);
712
+ }
713
+ }
714
+ function run(cmd, {
715
+ agentLog,
716
+ cwd = process.cwd()
717
+ } = {}) {
718
+ const start = Date.now();
719
+ if (!agentLog) {
720
+ console.log(`
721
+ > ${cmd}
722
+ `);
723
+ try {
724
+ execSync(cmd, {
725
+ stdio: "inherit",
726
+ encoding: FILE_SYSTEM.ENCODING,
727
+ cwd
728
+ });
729
+ return { ok: true, duration: Date.now() - start };
730
+ } catch {
731
+ return { ok: false, duration: Date.now() - start };
732
+ }
733
+ }
734
+ writeSync(agentLog.logFd, `
735
+ > ${cmd}
736
+
737
+ `);
738
+ const result = spawnSync(cmd, [], {
739
+ shell: true,
740
+ stdio: ["ignore", agentLog.logFd, agentLog.logFd],
741
+ cwd,
742
+ encoding: FILE_SYSTEM.ENCODING
743
+ });
744
+ return { ok: result.status === EXIT_CODES.SUCCESS, duration: Date.now() - start };
745
+ }
746
+ function makeGateLogger({ agentLog, useAgentMode }) {
747
+ return (line) => {
748
+ if (!useAgentMode) {
749
+ console.log(line);
750
+ return;
751
+ }
752
+ if (agentLog) {
753
+ writeSync(agentLog.logFd, `${line}
754
+ `);
755
+ }
756
+ };
757
+ }
758
+ function readLogTail(logPath, { maxLines = LOG_TAIL_MAX_LINES, maxBytes = LOG_TAIL_MAX_BYTES } = {}) {
759
+ try {
760
+ const stats = statSync(logPath);
761
+ const startPos = Math.max(0, stats.size - maxBytes);
762
+ const bytesToRead = stats.size - startPos;
763
+ const fd = openSync(logPath, "r");
764
+ try {
765
+ const buffer = Buffer.alloc(bytesToRead);
766
+ readSync(fd, buffer, 0, bytesToRead, startPos);
767
+ const text = buffer.toString(FILE_SYSTEM.ENCODING);
768
+ const lines = text.split(/\r?\n/).filter(Boolean);
769
+ return lines.slice(-maxLines).join("\n");
770
+ } finally {
771
+ closeSync(fd);
772
+ }
773
+ } catch {
774
+ return "";
775
+ }
776
+ }
777
+ function createAgentLogContext({
778
+ wuId,
779
+ lane,
780
+ cwd
781
+ }) {
782
+ const logPath = buildGatesLogPath({
783
+ cwd,
784
+ env: process.env,
785
+ wuId: wuId ?? void 0,
786
+ lane: lane ?? void 0
787
+ });
788
+ mkdirSync(path5.dirname(logPath), { recursive: true });
789
+ const logFd = openSync(logPath, "a");
790
+ const header = `# gates log
791
+ # lane: ${lane || "unknown"}
792
+ # wu: ${wuId || "unknown"}
793
+ # started: ${(/* @__PURE__ */ new Date()).toISOString()}
794
+
795
+ `;
796
+ writeSync(logFd, header);
797
+ process.on("exit", () => {
798
+ try {
799
+ closeSync(logFd);
800
+ } catch {
801
+ }
802
+ });
803
+ return { logPath, logFd };
804
+ }
805
+ async function filterExistingFiles(files, cwd = process.cwd()) {
806
+ const existingFiles = await Promise.all(
807
+ files.map(async (file) => {
808
+ const filePath = path5.isAbsolute(file) ? file : path5.resolve(cwd, file);
809
+ try {
810
+ await access(filePath);
811
+ return file;
812
+ } catch {
813
+ return null;
814
+ }
815
+ })
816
+ );
817
+ return existingFiles.filter((file) => Boolean(file));
818
+ }
819
+ async function getChangedFilesForIncremental({
820
+ git,
821
+ baseBranch = GIT_REFS.ORIGIN_MAIN
822
+ }) {
823
+ const mergeBase = await git.mergeBase("HEAD", baseBranch);
824
+ const committedOutput = await git.raw(["diff", "--name-only", `${mergeBase}...HEAD`]);
825
+ const committedFiles = committedOutput.split("\n").map((f) => f.trim()).filter(Boolean);
826
+ const unstagedOutput = await git.raw(["diff", "--name-only"]);
827
+ const unstagedFiles = unstagedOutput.split("\n").map((f) => f.trim()).filter(Boolean);
828
+ const untrackedOutput = await git.raw(["ls-files", "--others", "--exclude-standard"]);
829
+ const untrackedFiles = untrackedOutput.split("\n").map((f) => f.trim()).filter(Boolean);
830
+ return [.../* @__PURE__ */ new Set([...committedFiles, ...unstagedFiles, ...untrackedFiles])];
831
+ }
832
+ function parseWUFromBranchName(branchName) {
833
+ if (!branchName) {
834
+ return null;
835
+ }
836
+ const match = branchName.match(/wu-(\d+)/i);
837
+ if (!match) {
838
+ return null;
839
+ }
840
+ return `WU-${match[1]}`.toUpperCase();
841
+ }
842
+ async function detectCurrentWUForCwd(cwd) {
843
+ const workingDir = cwd ?? process.cwd();
844
+ try {
845
+ const branch = await createGitForPath(workingDir).getCurrentBranch();
846
+ const parsed = parseWUFromBranchName(branch);
847
+ if (parsed) {
848
+ return parsed;
849
+ }
850
+ } catch {
851
+ }
852
+ return getCurrentWU();
853
+ }
854
+ function extractPackageFromPath(codePath) {
855
+ if (!codePath || typeof codePath !== "string") {
856
+ return null;
857
+ }
858
+ const normalized = codePath.replace(/\\/g, "/");
859
+ if (normalized.startsWith("packages/")) {
860
+ const parts = normalized.slice("packages/".length).split("/");
861
+ if (parts[0]?.startsWith("@") && parts[1]) {
862
+ return `${parts[0]}/${parts[1]}`;
863
+ }
864
+ if (parts[0]) {
865
+ return parts[0];
866
+ }
867
+ }
868
+ return null;
869
+ }
870
+ function extractPackagesFromCodePaths(codePaths) {
871
+ if (!codePaths || !Array.isArray(codePaths) || codePaths.length === 0) {
872
+ return [];
873
+ }
874
+ const packages = /* @__PURE__ */ new Set();
875
+ for (const codePath of codePaths) {
876
+ const pkg = extractPackageFromPath(codePath);
877
+ if (pkg) {
878
+ packages.add(pkg);
879
+ }
880
+ }
881
+ return Array.from(packages);
882
+ }
883
+ function loadCurrentWUCodePaths(options = {}) {
884
+ const cwd = options.cwd ?? process.cwd();
885
+ const wuId = getCurrentWU();
886
+ if (!wuId) {
887
+ return [];
888
+ }
889
+ try {
890
+ const wuPaths = createWuPaths({ projectRoot: cwd });
891
+ const wuYamlPath = wuPaths.WU(wuId);
892
+ const wuDoc = readWURaw(wuYamlPath);
893
+ if (wuDoc && Array.isArray(wuDoc.code_paths)) {
894
+ return wuDoc.code_paths.filter((p) => typeof p === "string");
895
+ }
896
+ } catch {
897
+ }
898
+ return [];
899
+ }
900
+
901
+ // src/gates-plan-resolvers.ts
902
+ import { existsSync as existsSync6 } from "fs";
903
+ import path6 from "path";
904
+ var PRETTIER_CONFIG_FILES = /* @__PURE__ */ new Set([
905
+ ".prettierrc",
906
+ ".prettierrc.json",
907
+ ".prettierrc.yaml",
908
+ ".prettierrc.yml",
909
+ ".prettierrc.js",
910
+ ".prettierrc.cjs",
911
+ ".prettierrc.ts",
912
+ "prettier.config.js",
913
+ "prettier.config.cjs",
914
+ "prettier.config.ts",
915
+ "prettier.config.mjs",
916
+ ".prettierignore"
917
+ ]);
918
+ var WORKSPACE_ARTIFACT_ROOT_PREFIXES = ["packages/", "apps/", "tools/"];
919
+ var DIST_ARTIFACT_ROOT_SUFFIX = "/dist";
920
+ var DIST_ARTIFACT_PATH_SEGMENT = "/dist/";
921
+ var TEST_CONFIG_BASENAMES = /* @__PURE__ */ new Set([
922
+ "turbo.json",
923
+ // Turborepo
924
+ "nx.json",
925
+ // Nx
926
+ "lerna.json",
927
+ // Lerna
928
+ "pnpm-lock.yaml",
929
+ "package-lock.json",
930
+ "yarn.lock",
931
+ "bun.lockb",
932
+ "package.json"
933
+ ]);
934
+ var TEST_CONFIG_PATTERNS = [
935
+ /^vitest\.config\.(ts|mts|js|mjs|cjs)$/i,
936
+ /^jest\.config\.(ts|js|mjs|cjs|json)$/i,
937
+ /^\.mocharc\.(js|json|yaml|yml)$/i,
938
+ // eslint-disable-next-line security/detect-unsafe-regex -- static tsconfig pattern; no backtracking risk
939
+ /^tsconfig(\..+)?\.json$/i
940
+ ];
941
+ function isPrettierConfigFile(filePath) {
942
+ if (!filePath) return false;
943
+ const basename = getBasename(filePath);
944
+ return PRETTIER_CONFIG_FILES.has(basename);
945
+ }
946
+ function trimTrailingPathSeparators(filePath) {
947
+ let trimmed = filePath;
948
+ while (trimmed.endsWith("/")) {
949
+ trimmed = trimmed.slice(0, -1);
950
+ }
951
+ return trimmed;
952
+ }
953
+ function isWorkspaceDistArtifactRoot(filePath) {
954
+ if (!filePath) {
955
+ return false;
956
+ }
957
+ const normalizedPath = trimTrailingPathSeparators(normalizePath(filePath));
958
+ if (!normalizedPath) {
959
+ return false;
960
+ }
961
+ const hasWorkspacePrefix = WORKSPACE_ARTIFACT_ROOT_PREFIXES.some(
962
+ (prefix) => normalizedPath.startsWith(prefix)
963
+ );
964
+ const isDistArtifactPath = normalizedPath.endsWith(DIST_ARTIFACT_ROOT_SUFFIX) || normalizedPath.includes(DIST_ARTIFACT_PATH_SEGMENT);
965
+ return hasWorkspacePrefix && isDistArtifactPath;
966
+ }
967
+ function filterFormatCheckChangedFiles(changedFiles) {
968
+ return changedFiles.filter((filePath) => !isWorkspaceDistArtifactRoot(filePath));
969
+ }
970
+ function filterExistingFormatCheckFiles(changedFiles, cwd) {
971
+ return changedFiles.filter((filePath) => {
972
+ const resolvedPath = path6.isAbsolute(filePath) ? filePath : path6.resolve(cwd, filePath);
973
+ return existsSync6(resolvedPath);
974
+ });
975
+ }
976
+ function isTestConfigFile(filePath) {
977
+ if (!filePath) return false;
978
+ const basename = getBasename(filePath);
979
+ if (TEST_CONFIG_BASENAMES.has(basename)) {
980
+ return true;
981
+ }
982
+ return TEST_CONFIG_PATTERNS.some((pattern) => pattern.test(basename));
983
+ }
984
+ function resolveFormatCheckPlan({
985
+ changedFiles,
986
+ fileListError = false,
987
+ cwd
988
+ }) {
989
+ const filteredChangedFiles = filterFormatCheckChangedFiles(changedFiles);
990
+ if (fileListError) {
991
+ return { mode: "full", files: [], reason: "file-list-error" };
992
+ }
993
+ if (filteredChangedFiles.some(isPrettierConfigFile)) {
994
+ return { mode: "full", files: [], reason: "prettier-config" };
995
+ }
996
+ const existingChangedFiles = cwd ? filterExistingFormatCheckFiles(filteredChangedFiles, cwd) : filteredChangedFiles;
997
+ if (existingChangedFiles.length === 0) {
998
+ return { mode: "skip", files: [] };
999
+ }
1000
+ return { mode: "incremental", files: existingChangedFiles };
1001
+ }
1002
+ function resolveLintPlan({
1003
+ isMainBranch,
1004
+ changedFiles
1005
+ }) {
1006
+ if (isMainBranch) {
1007
+ return { mode: "full", files: [] };
1008
+ }
1009
+ const lintTargets = changedFiles.filter((filePath) => {
1010
+ const normalized = normalizePath(filePath);
1011
+ return (normalized.startsWith("apps/") || normalized.startsWith("packages/")) && isLintableFile(normalized);
1012
+ });
1013
+ if (lintTargets.length === 0) {
1014
+ return { mode: "skip", files: [] };
1015
+ }
1016
+ return { mode: "incremental", files: lintTargets };
1017
+ }
1018
+ function resolveTestPlan({
1019
+ isMainBranch,
1020
+ hasUntrackedCode,
1021
+ hasConfigChange,
1022
+ fileListError
1023
+ }) {
1024
+ if (fileListError) {
1025
+ return { mode: "full", reason: "file-list-error" };
1026
+ }
1027
+ if (hasUntrackedCode) {
1028
+ return { mode: "full", reason: "untracked-code" };
1029
+ }
1030
+ if (hasConfigChange) {
1031
+ return { mode: "full", reason: "test-config" };
1032
+ }
1033
+ if (isMainBranch) {
1034
+ return { mode: "full" };
1035
+ }
1036
+ return { mode: "incremental" };
1037
+ }
1038
+ function resolveDocsOnlyTestPlan({ codePaths }) {
1039
+ const packages = extractPackagesFromCodePaths(codePaths);
1040
+ if (packages.length === 0) {
1041
+ return {
1042
+ mode: "skip",
1043
+ packages: [],
1044
+ reason: "no-code-packages"
1045
+ };
1046
+ }
1047
+ return {
1048
+ mode: "filtered",
1049
+ packages
1050
+ };
1051
+ }
1052
+ function formatDocsOnlySkipMessage(plan) {
1053
+ if (plan.mode === "skip") {
1054
+ return "\u{1F4DD} docs-only mode: skipping all tests (no code packages in code_paths)";
1055
+ }
1056
+ const packageList = plan.packages.join(", ");
1057
+ return `\u{1F4DD} docs-only mode: running tests only for packages in code_paths: ${packageList}`;
1058
+ }
1059
+ function resolveSpecLinterPlan(wuId) {
1060
+ if (wuId) {
1061
+ return {
1062
+ scopedWuId: wuId,
1063
+ runGlobal: false
1064
+ };
1065
+ }
1066
+ return {
1067
+ scopedWuId: null,
1068
+ runGlobal: true
1069
+ };
1070
+ }
1071
+
1072
+ // src/gates-runners.ts
1073
+ var require2 = createRequire(import.meta.url);
1074
+ var micromatch = require2("micromatch");
1075
+ var CO_CHANGE_SEVERITY = {
1076
+ WARN: "warn",
1077
+ ERROR: "error",
1078
+ OFF: "off"
1079
+ };
1080
+ async function runFormatCheckGate({ agentLog, useAgentMode, cwd }) {
1081
+ const start = Date.now();
1082
+ const effectiveCwd = cwd ?? process.cwd();
1083
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1084
+ let git;
1085
+ let isMainBranch;
1086
+ try {
1087
+ git = createGitForPath(effectiveCwd);
1088
+ const currentBranch = await git.getCurrentBranch();
1089
+ isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
1090
+ } catch (error) {
1091
+ logLine(`\u26A0\uFE0F Failed to determine branch for format check: ${error.message}`);
1092
+ const result2 = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd: effectiveCwd });
1093
+ return { ...result2, duration: Date.now() - start, fileCount: -1 };
1094
+ }
1095
+ if (isMainBranch) {
1096
+ logLine("\u{1F4CB} On main branch - running full format check");
1097
+ const result2 = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd: effectiveCwd });
1098
+ return { ...result2, duration: Date.now() - start, fileCount: -1 };
1099
+ }
1100
+ let changedFiles = [];
1101
+ let fileListError = false;
1102
+ try {
1103
+ changedFiles = await getChangedFilesForIncremental({ git });
1104
+ } catch (error) {
1105
+ fileListError = true;
1106
+ logLine(`\u26A0\uFE0F Failed to determine changed files for format check: ${error.message}`);
1107
+ }
1108
+ const plan = resolveFormatCheckPlan({ changedFiles, fileListError, cwd: effectiveCwd });
1109
+ if (plan.mode === "skip") {
1110
+ logLine("\n> format:check (incremental)\n");
1111
+ logLine("\u2705 No files changed - skipping format check");
1112
+ return { ok: true, duration: Date.now() - start, fileCount: 0, filesChecked: [] };
1113
+ }
1114
+ if (plan.mode === "full") {
1115
+ const reason = plan.reason === "prettier-config" ? " (prettier config changed)" : plan.reason === "file-list-error" ? " (file list unavailable)" : "";
1116
+ logLine(`\u{1F4CB} Running full format check${reason}`);
1117
+ const result2 = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd: effectiveCwd });
1118
+ return { ...result2, duration: Date.now() - start, fileCount: -1 };
1119
+ }
1120
+ const existingFiles = await filterExistingFiles(plan.files, effectiveCwd);
1121
+ if (existingFiles.length === 0) {
1122
+ logLine("\n> format:check (incremental)\n");
1123
+ logLine("\u2705 All changed files were deleted - skipping format check");
1124
+ return { ok: true, duration: Date.now() - start, fileCount: 0, filesChecked: [] };
1125
+ }
1126
+ logLine(`
1127
+ > format:check (incremental: ${existingFiles.length} files)
1128
+ `);
1129
+ const result = run(buildPrettierCheckCommand(existingFiles), { agentLog, cwd: effectiveCwd });
1130
+ return {
1131
+ ...result,
1132
+ duration: Date.now() - start,
1133
+ fileCount: existingFiles.length,
1134
+ filesChecked: existingFiles
1135
+ };
1136
+ }
1137
+ async function runIncrementalLint({
1138
+ agentLog,
1139
+ cwd
1140
+ }) {
1141
+ const start = Date.now();
1142
+ const logLine = (line) => {
1143
+ if (!agentLog) {
1144
+ console.log(line);
1145
+ return;
1146
+ }
1147
+ writeSync2(agentLog.logFd, `${line}
1148
+ `);
1149
+ };
1150
+ try {
1151
+ const git = createGitForPath(cwd);
1152
+ const currentBranch = await git.getCurrentBranch();
1153
+ const isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
1154
+ if (isMainBranch) {
1155
+ logLine("\u{1F4CB} On main branch - running full lint");
1156
+ const result2 = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
1157
+ return { ...result2, fileCount: -1 };
1158
+ }
1159
+ const changedFiles = await getChangedLintableFiles({ git });
1160
+ const plan = resolveLintPlan({ isMainBranch, changedFiles });
1161
+ if (plan.mode === "skip") {
1162
+ logLine("\n> ESLint (incremental)\n");
1163
+ logLine("\u2705 No lintable files changed - skipping lint");
1164
+ return { ok: true, duration: Date.now() - start, fileCount: 0 };
1165
+ }
1166
+ if (plan.mode === "full") {
1167
+ logLine("\u{1F4CB} Running full lint (incremental plan forced full)");
1168
+ const result2 = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
1169
+ return { ...result2, fileCount: -1 };
1170
+ }
1171
+ const existingFiles = await filterExistingFiles(plan.files, cwd);
1172
+ if (existingFiles.length === 0) {
1173
+ logLine("\n> ESLint (incremental)\n");
1174
+ logLine("\u2705 All changed files were deleted - skipping lint");
1175
+ return { ok: true, duration: Date.now() - start, fileCount: 0 };
1176
+ }
1177
+ logLine(`
1178
+ > ESLint (incremental: ${existingFiles.length} files)
1179
+ `);
1180
+ logLine(`Files to lint:
1181
+ ${existingFiles.join("\n ")}
1182
+ `);
1183
+ const result = spawnSync2(
1184
+ PKG_MANAGER,
1185
+ [
1186
+ ESLINT_COMMANDS.ESLINT,
1187
+ ESLINT_FLAGS.MAX_WARNINGS,
1188
+ ESLINT_DEFAULTS.MAX_WARNINGS,
1189
+ ESLINT_FLAGS.NO_WARN_IGNORED,
1190
+ ESLINT_FLAGS.CACHE,
1191
+ ESLINT_FLAGS.CACHE_STRATEGY,
1192
+ CACHE_STRATEGIES.CONTENT,
1193
+ ESLINT_FLAGS.CACHE_LOCATION,
1194
+ ".eslintcache",
1195
+ ESLINT_FLAGS.PASS_ON_UNPRUNED,
1196
+ ...existingFiles
1197
+ ],
1198
+ agentLog ? {
1199
+ stdio: ["ignore", agentLog.logFd, agentLog.logFd],
1200
+ encoding: FILE_SYSTEM.ENCODING,
1201
+ cwd
1202
+ } : {
1203
+ stdio: "inherit",
1204
+ encoding: FILE_SYSTEM.ENCODING,
1205
+ cwd
1206
+ }
1207
+ );
1208
+ const duration = Date.now() - start;
1209
+ return {
1210
+ ok: result.status === EXIT_CODES.SUCCESS,
1211
+ duration,
1212
+ fileCount: existingFiles.length
1213
+ };
1214
+ } catch (error) {
1215
+ console.error(
1216
+ "\u26A0\uFE0F Incremental lint failed, falling back to full lint:",
1217
+ error.message
1218
+ );
1219
+ const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
1220
+ return { ...result, fileCount: -1 };
1221
+ }
1222
+ }
1223
+ var DEFAULT_INCREMENTAL_BASE_BRANCH = GIT_REFS.ORIGIN_MAIN;
1224
+ function buildStableVitestIncrementalCommand(baseBranch = DEFAULT_INCREMENTAL_BASE_BRANCH) {
1225
+ return pnpmCmd("vitest", "run", ...buildVitestChangedArgs({ baseBranch }));
1226
+ }
1227
+ function resolveIncrementalTestCommand({
1228
+ testRunner,
1229
+ configuredIncrementalCommand,
1230
+ baseBranch = DEFAULT_INCREMENTAL_BASE_BRANCH
1231
+ }) {
1232
+ const normalizedConfiguredCommand = configuredIncrementalCommand?.trim();
1233
+ if (testRunner === "vitest") {
1234
+ if (!normalizedConfiguredCommand) {
1235
+ return buildStableVitestIncrementalCommand(baseBranch);
1236
+ }
1237
+ const isVitestChangedCommand = normalizedConfiguredCommand.includes("vitest") && normalizedConfiguredCommand.includes("--changed");
1238
+ if (isVitestChangedCommand) {
1239
+ return buildStableVitestIncrementalCommand(baseBranch);
1240
+ }
1241
+ }
1242
+ return normalizedConfiguredCommand ?? null;
1243
+ }
1244
+ async function runChangedTests({
1245
+ agentLog,
1246
+ cwd,
1247
+ scopedTestPaths = []
1248
+ }) {
1249
+ const start = Date.now();
1250
+ const logLine = (line) => {
1251
+ if (!agentLog) {
1252
+ console.log(line);
1253
+ return;
1254
+ }
1255
+ writeSync2(agentLog.logFd, `${line}
1256
+ `);
1257
+ };
1258
+ const gatesCommands = resolveGatesCommands(cwd);
1259
+ const testRunner = resolveTestRunner(cwd);
1260
+ const normalizedScopedTestPaths = scopedTestPaths.filter((testPath) => typeof testPath === "string").map((testPath) => testPath.trim()).filter(Boolean);
1261
+ try {
1262
+ if (normalizedScopedTestPaths.length > 0) {
1263
+ const testPathsArg = quoteShellArgs(normalizedScopedTestPaths);
1264
+ logLine(
1265
+ `
1266
+ > Running scoped tests from WU tests.unit (${normalizedScopedTestPaths.length})
1267
+ `
1268
+ );
1269
+ const result2 = run(pnpmCmd("vitest", "run", testPathsArg, "--passWithNoTests"), {
1270
+ agentLog,
1271
+ cwd
1272
+ });
1273
+ return { ...result2, duration: Date.now() - start, isIncremental: true };
1274
+ }
1275
+ const git = createGitForPath(cwd);
1276
+ const currentBranch = await git.getCurrentBranch();
1277
+ const isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
1278
+ if (isMainBranch) {
1279
+ logLine("\u{1F4CB} On main branch - running full test suite");
1280
+ const result2 = run(gatesCommands.test_full, { agentLog, cwd });
1281
+ return { ...result2, isIncremental: false };
1282
+ }
1283
+ let changedFiles = [];
1284
+ let fileListError = false;
1285
+ try {
1286
+ changedFiles = await getChangedFilesForIncremental({ git });
1287
+ } catch (error) {
1288
+ fileListError = true;
1289
+ logLine(`\u26A0\uFE0F Failed to determine changed files for tests: ${error.message}`);
1290
+ }
1291
+ const hasConfigChange = !fileListError && changedFiles.some(isTestConfigFile);
1292
+ const untrackedOutput = await git.raw(["ls-files", "--others", "--exclude-standard"]);
1293
+ const untrackedFiles = untrackedOutput.split(/\r?\n/).map((f) => f.trim()).filter(Boolean);
1294
+ const untrackedCodeFiles = untrackedFiles.filter(isCodeFilePath);
1295
+ const hasUntrackedCode = untrackedCodeFiles.length > 0;
1296
+ const plan = resolveTestPlan({
1297
+ isMainBranch,
1298
+ hasUntrackedCode,
1299
+ hasConfigChange,
1300
+ fileListError
1301
+ });
1302
+ if (plan.mode === "full") {
1303
+ if (plan.reason === "untracked-code") {
1304
+ const preview = untrackedCodeFiles.slice(0, 5).join(", ");
1305
+ logLine(
1306
+ `\u26A0\uFE0F Untracked code files detected (${untrackedCodeFiles.length}): ${preview}${untrackedCodeFiles.length > 5 ? "..." : ""}`
1307
+ );
1308
+ } else if (plan.reason === "test-config") {
1309
+ logLine("\u26A0\uFE0F Test config changes detected - running full test suite");
1310
+ } else if (plan.reason === "file-list-error") {
1311
+ logLine("\u26A0\uFE0F Changed file list unavailable - running full test suite");
1312
+ }
1313
+ logLine("\u{1F4CB} Running full test suite to avoid missing coverage");
1314
+ const result2 = run(gatesCommands.test_full, { agentLog, cwd });
1315
+ return { ...result2, duration: Date.now() - start, isIncremental: false };
1316
+ }
1317
+ logLine(`
1318
+ > Running tests (${testRunner} --changed)
1319
+ `);
1320
+ const incrementalCommand = resolveIncrementalTestCommand({
1321
+ testRunner,
1322
+ configuredIncrementalCommand: gatesCommands.test_incremental
1323
+ });
1324
+ if (incrementalCommand) {
1325
+ if (testRunner === "vitest" && incrementalCommand !== gatesCommands.test_incremental?.trim()) {
1326
+ logLine("\u2139\uFE0F Using hardened vitest incremental command for worker stability");
1327
+ }
1328
+ const result2 = run(incrementalCommand, { agentLog, cwd });
1329
+ return { ...result2, duration: Date.now() - start, isIncremental: true };
1330
+ }
1331
+ logLine("\u26A0\uFE0F No incremental test command configured, running full suite");
1332
+ const result = run(gatesCommands.test_full, { agentLog, cwd });
1333
+ return { ...result, duration: Date.now() - start, isIncremental: false };
1334
+ } catch (error) {
1335
+ console.error("\u26A0\uFE0F Changed tests failed, falling back to full suite:", error.message);
1336
+ const result = run(gatesCommands.test_full, { agentLog, cwd });
1337
+ return { ...result, isIncremental: false };
1338
+ }
1339
+ }
1340
+ var SAFETY_CRITICAL_TEST_FILES = [
1341
+ // Privacy detection tests
1342
+ "src/lib/llm/__tests__/privacyDetector.test.ts",
1343
+ // Escalation trigger tests
1344
+ "src/lib/llm/__tests__/escalationTrigger.test.ts",
1345
+ "src/components/escalation/__tests__/EscalationHistory.test.tsx",
1346
+ // Constitutional enforcer tests
1347
+ "src/lib/llm/__tests__/constitutionalEnforcer.test.ts",
1348
+ // Safe prompt wrapper tests
1349
+ "src/lib/llm/__tests__/safePromptWrapper.test.ts",
1350
+ // Crisis/emergency handling tests
1351
+ "src/lib/prompts/__tests__/golden-crisis.test.ts"
1352
+ ];
1353
+ async function runSafetyCriticalTests({
1354
+ agentLog,
1355
+ cwd
1356
+ }) {
1357
+ const start = Date.now();
1358
+ const logLine = (line) => {
1359
+ if (!agentLog) {
1360
+ console.log(line);
1361
+ return;
1362
+ }
1363
+ writeSync2(agentLog.logFd, `${line}
1364
+ `);
1365
+ };
1366
+ const webDir = path7.join(cwd, DIRECTORIES.APPS_WEB);
1367
+ try {
1368
+ await access2(webDir);
1369
+ } catch {
1370
+ logLine("\n> Safety-critical tests skipped (apps/web not present)\n");
1371
+ return { ok: true, duration: Date.now() - start, testCount: 0 };
1372
+ }
1373
+ try {
1374
+ logLine("\n> Safety-critical tests (always run)\n");
1375
+ logLine(`Test files: ${SAFETY_CRITICAL_TEST_FILES.length} files
1376
+ `);
1377
+ const result = spawnSync2(
1378
+ PKG_MANAGER,
1379
+ [
1380
+ "vitest",
1381
+ "run",
1382
+ "--project",
1383
+ PACKAGES.WEB,
1384
+ "--reporter=verbose",
1385
+ ...SAFETY_CRITICAL_TEST_FILES,
1386
+ "--passWithNoTests"
1387
+ ],
1388
+ agentLog ? {
1389
+ stdio: ["ignore", agentLog.logFd, agentLog.logFd],
1390
+ encoding: FILE_SYSTEM.ENCODING,
1391
+ cwd
1392
+ } : {
1393
+ stdio: "inherit",
1394
+ encoding: FILE_SYSTEM.ENCODING,
1395
+ cwd
1396
+ }
1397
+ );
1398
+ const duration = Date.now() - start;
1399
+ return {
1400
+ ok: result.status === EXIT_CODES.SUCCESS,
1401
+ duration,
1402
+ testCount: SAFETY_CRITICAL_TEST_FILES.length
1403
+ };
1404
+ } catch (error) {
1405
+ console.error("\u26A0\uFE0F Safety-critical tests failed:", error.message);
1406
+ return { ok: false, duration: Date.now() - start, testCount: 0 };
1407
+ }
1408
+ }
1409
+ async function runIntegrationTests({
1410
+ agentLog,
1411
+ cwd
1412
+ }) {
1413
+ const start = Date.now();
1414
+ const logLine = (line) => {
1415
+ if (!agentLog) {
1416
+ console.log(line);
1417
+ return;
1418
+ }
1419
+ writeSync2(agentLog.logFd, `${line}
1420
+ `);
1421
+ };
1422
+ try {
1423
+ logLine("\n> Integration tests (high-risk changes detected)\n");
1424
+ const result = run(
1425
+ `RUN_INTEGRATION_TESTS=1 ${pnpmCmd(
1426
+ "vitest",
1427
+ "run",
1428
+ "'**/*.integration.*'",
1429
+ "'**/golden-*.test.*'"
1430
+ )}`,
1431
+ { agentLog, cwd }
1432
+ );
1433
+ const duration = Date.now() - start;
1434
+ return {
1435
+ ok: result.ok,
1436
+ duration
1437
+ };
1438
+ } catch (error) {
1439
+ console.error("\u26A0\uFE0F Integration tests failed:", error.message);
1440
+ return { ok: false, duration: Date.now() - start };
1441
+ }
1442
+ }
1443
+ var CoChangeRulesConfigSchema = CoChangeRuleConfigSchema.array();
1444
+ function hasPatternMatch(changedFiles, patterns) {
1445
+ return changedFiles.some((filePath) => micromatch.isMatch(filePath, patterns));
1446
+ }
1447
+ function evaluateCoChangeRules({
1448
+ changedFiles,
1449
+ rules
1450
+ }) {
1451
+ const errors = [];
1452
+ const warnings = [];
1453
+ for (const rule of rules) {
1454
+ if (rule.severity === CO_CHANGE_SEVERITY.OFF) {
1455
+ continue;
1456
+ }
1457
+ const triggerMatched = hasPatternMatch(changedFiles, rule.trigger_patterns);
1458
+ if (!triggerMatched) {
1459
+ continue;
1460
+ }
1461
+ const requireMatched = hasPatternMatch(changedFiles, rule.require_patterns);
1462
+ if (requireMatched) {
1463
+ continue;
1464
+ }
1465
+ const message = `co-change "${rule.name}" violated: trigger matched (${rule.trigger_patterns.join(", ")}) but required patterns missing (${rule.require_patterns.join(", ")})`;
1466
+ if (rule.severity === CO_CHANGE_SEVERITY.WARN) {
1467
+ warnings.push(message);
1468
+ continue;
1469
+ }
1470
+ errors.push(message);
1471
+ }
1472
+ return { errors, warnings };
1473
+ }
1474
+ async function runCoChangeGate({ agentLog, useAgentMode, cwd }) {
1475
+ const start = Date.now();
1476
+ const effectiveCwd = cwd ?? process.cwd();
1477
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1478
+ logLine("\n> Co-change check\n");
1479
+ const gatesSection = getGatesSection(effectiveCwd);
1480
+ const parsedRules = CoChangeRulesConfigSchema.safeParse(gatesSection?.co_change ?? []);
1481
+ if (!parsedRules.success) {
1482
+ logLine("\u26A0\uFE0F Invalid gates.co_change config; skipping check.");
1483
+ return { ok: true, duration: Date.now() - start };
1484
+ }
1485
+ const rules = parsedRules.data;
1486
+ if (rules.length === 0) {
1487
+ logLine("\u2139\uFE0F No co-change rules configured; skipping.");
1488
+ return { ok: true, duration: Date.now() - start };
1489
+ }
1490
+ const git = createGitForPath(effectiveCwd);
1491
+ const changedFiles = await getChangedFilesForIncremental({ git });
1492
+ if (changedFiles.length === 0) {
1493
+ logLine("\u2139\uFE0F No changed files detected; skipping co-change checks.");
1494
+ return { ok: true, duration: Date.now() - start };
1495
+ }
1496
+ const evaluation = evaluateCoChangeRules({ changedFiles, rules });
1497
+ for (const warning of evaluation.warnings) {
1498
+ logLine(`\u26A0\uFE0F ${warning}`);
1499
+ }
1500
+ for (const error of evaluation.errors) {
1501
+ logLine(`\u274C ${error}`);
1502
+ }
1503
+ if (evaluation.errors.length > 0) {
1504
+ logLine("co-change check failed.");
1505
+ return { ok: false, duration: Date.now() - start };
1506
+ }
1507
+ logLine("co-change check passed.");
1508
+ return { ok: true, duration: Date.now() - start };
1509
+ }
1510
+ async function runSpecLinterGate({ agentLog, useAgentMode, cwd }) {
1511
+ const start = Date.now();
1512
+ const wuId = await detectCurrentWUForCwd(cwd);
1513
+ const plan = resolveSpecLinterPlan(wuId);
1514
+ if (plan.scopedWuId) {
1515
+ const scopedCmd = pnpmCmd("wu:validate", "--id", plan.scopedWuId);
1516
+ const scopedResult = run(scopedCmd, { agentLog, cwd });
1517
+ if (!scopedResult.ok) {
1518
+ return { ok: false, duration: Date.now() - start };
1519
+ }
1520
+ return { ok: true, duration: Date.now() - start };
1521
+ }
1522
+ if (!useAgentMode) {
1523
+ console.log("\u26A0\uFE0F Unable to detect current WU; skipping scoped validation.");
1524
+ } else if (agentLog) {
1525
+ writeSync2(
1526
+ agentLog.logFd,
1527
+ "\u26A0\uFE0F Unable to detect current WU; skipping scoped validation.\n"
1528
+ );
1529
+ }
1530
+ if (!plan.runGlobal) {
1531
+ return { ok: true, duration: Date.now() - start };
1532
+ }
1533
+ const fallbackResult = run(pnpmRun(SCRIPTS.SPEC_LINTER), { agentLog, cwd });
1534
+ return { ok: fallbackResult.ok, duration: Date.now() - start };
1535
+ }
1536
+ async function runClaimValidationGate({ agentLog, useAgentMode, cwd }) {
1537
+ const start = Date.now();
1538
+ const effectiveCwd = cwd ?? process.cwd();
1539
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1540
+ logLine("\n> Claim validation\n");
1541
+ const wuId = await detectCurrentWUForCwd(effectiveCwd);
1542
+ if (!wuId) {
1543
+ logLine("\u26A0\uFE0F Unable to detect current WU; skipping claim validation.");
1544
+ return { ok: true, duration: Date.now() - start };
1545
+ }
1546
+ const result = await validateClaimValidation({ cwd: effectiveCwd, wuId });
1547
+ if (result.warnings.length > 0) {
1548
+ for (const warning of result.warnings) {
1549
+ logLine(`\u26A0\uFE0F ${warning}`);
1550
+ }
1551
+ }
1552
+ if (result.ok) {
1553
+ logLine(
1554
+ `Claim validation passed (${result.checkedClaims} checked claim${result.checkedClaims === 1 ? "" : "s"}).`
1555
+ );
1556
+ return { ok: true, duration: Date.now() - start };
1557
+ }
1558
+ logLine("\u274C Claim validation mismatches detected:");
1559
+ for (const mismatch of result.mismatches) {
1560
+ const specPath = path7.relative(effectiveCwd, mismatch.specReference.filePath).replaceAll(path7.sep, "/");
1561
+ logLine(` - Claim (${mismatch.claimId}): ${mismatch.claimText}`);
1562
+ logLine(
1563
+ ` Spec: ${specPath}:${mismatch.specReference.line} [${mismatch.specReference.id} ${mismatch.specReference.section}]`
1564
+ );
1565
+ for (const evidence of mismatch.evidence) {
1566
+ logLine(` Evidence: ${evidence.filePath}:${evidence.line} ${evidence.lineText}`);
1567
+ }
1568
+ logLine(` Hint: ${mismatch.remediationHint}`);
1569
+ }
1570
+ return { ok: false, duration: Date.now() - start };
1571
+ }
1572
+ async function runBacklogSyncGate({ agentLog, useAgentMode, cwd }) {
1573
+ const start = Date.now();
1574
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1575
+ logLine("\n> Backlog sync\n");
1576
+ const result = await validateBacklogSync({ cwd });
1577
+ if (result.errors.length > 0) {
1578
+ logLine("\u274C Backlog sync errors:");
1579
+ result.errors.forEach((error) => logLine(` - ${error}`));
1580
+ }
1581
+ if (result.warnings.length > 0) {
1582
+ logLine("\u26A0\uFE0F Backlog sync warnings:");
1583
+ result.warnings.forEach((warning) => logLine(` - ${warning}`));
1584
+ }
1585
+ logLine(`Backlog sync summary: WU files=${result.wuCount}, Backlog refs=${result.backlogCount}`);
1586
+ return { ok: result.valid, duration: Date.now() - start };
1587
+ }
1588
+ async function runSupabaseDocsGate({ agentLog, useAgentMode, cwd }) {
1589
+ const start = Date.now();
1590
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1591
+ logLine("\n> Supabase docs linter\n");
1592
+ const result = await runSupabaseDocsLinter({ cwd, logger: { log: logLine } });
1593
+ if (result.skipped) {
1594
+ logLine(`\u26A0\uFE0F ${result.message ?? "Supabase docs linter skipped."}`);
1595
+ } else if (!result.ok) {
1596
+ logLine("\u274C Supabase docs linter failed.");
1597
+ (result.errors ?? []).forEach((error) => logLine(` - ${error}`));
1598
+ } else {
1599
+ logLine(result.message ?? "Supabase docs linter passed.");
1600
+ }
1601
+ return { ok: result.ok, duration: Date.now() - start };
1602
+ }
1603
+ async function runLaneHealthGate({
1604
+ agentLog,
1605
+ useAgentMode,
1606
+ mode,
1607
+ cwd
1608
+ }) {
1609
+ const start = Date.now();
1610
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
1611
+ if (mode === "off") {
1612
+ logLine("\n> Lane health check (skipped - mode: off)\n");
1613
+ return { ok: true, duration: Date.now() - start };
1614
+ }
1615
+ logLine(`
1616
+ > Lane health check (mode: ${mode})
1617
+ `);
1618
+ const report = runLaneHealthCheck({ projectRoot: cwd });
1619
+ if (!report.healthy) {
1620
+ logLine("\u26A0\uFE0F Lane health issues detected:");
1621
+ if (report.overlaps.hasOverlaps) {
1622
+ logLine(` - ${report.overlaps.overlaps.length} overlapping code_paths`);
1623
+ }
1624
+ if (report.gaps.hasGaps) {
1625
+ logLine(` - ${report.gaps.uncoveredFiles.length} uncovered files`);
1626
+ }
1627
+ logLine(` Run 'pnpm lane:health' for full report.`);
1628
+ if (mode === "error") {
1629
+ return { ok: false, duration: Date.now() - start };
1630
+ }
1631
+ logLine(" (mode: warn - not blocking)");
1632
+ } else {
1633
+ logLine("Lane health check passed.");
1634
+ }
1635
+ return { ok: true, duration: Date.now() - start };
1636
+ }
1637
+ async function runDocsOnlyFilteredTests({
1638
+ packages,
1639
+ agentLog,
1640
+ cwd = process.cwd()
1641
+ }) {
1642
+ const start = Date.now();
1643
+ const logLine = makeGateLogger({ agentLog, useAgentMode: !!agentLog, cwd });
1644
+ if (packages.length === 0) {
1645
+ logLine("\u{1F4DD} docs-only mode: no packages to test, skipping");
1646
+ return { ok: true, duration: Date.now() - start };
1647
+ }
1648
+ logLine(`
1649
+ > Tests (docs-only filtered: ${packages.join(", ")})
1650
+ `);
1651
+ const gatesCommands = resolveGatesCommands(cwd);
1652
+ if (gatesCommands.test_docs_only) {
1653
+ const result2 = run(gatesCommands.test_docs_only, { agentLog, cwd });
1654
+ return { ok: result2.ok, duration: Date.now() - start };
1655
+ }
1656
+ const filterArgs = packages.map((pkg) => `--filter=${pkg}`);
1657
+ const baseCmd = gatesCommands.test_full;
1658
+ const filteredCmd = `${baseCmd} ${filterArgs.join(" ")}`;
1659
+ const result = run(filteredCmd, { agentLog });
1660
+ return { ok: result.ok, duration: Date.now() - start };
1661
+ }
1662
+
1663
+ // src/gate-defaults.ts
1664
+ function registerDocsOnlyGates(registry, options) {
1665
+ const { laneHealthMode, testsRequired, docsOnlyTestPlan } = options;
1666
+ registry.register({
1667
+ name: GATE_NAMES.INVARIANTS,
1668
+ cmd: GATE_COMMANDS.INVARIANTS
1669
+ });
1670
+ registry.register({
1671
+ name: GATE_NAMES.FORMAT_CHECK,
1672
+ scriptName: SCRIPTS.FORMAT_CHECK,
1673
+ // run function is injected by the gate runner (runFormatCheckGate)
1674
+ // We use a sentinel to indicate this gate needs its run function set
1675
+ cmd: void 0,
1676
+ run: void 0
1677
+ });
1678
+ registry.register({
1679
+ name: GATE_NAMES.SPEC_LINTER,
1680
+ scriptName: SCRIPTS.SPEC_LINTER
1681
+ });
1682
+ registry.register({
1683
+ name: GATE_NAMES.BACKLOG_SYNC
1684
+ });
1685
+ registry.register({
1686
+ name: GATE_NAMES.CLAIM_VALIDATION
1687
+ });
1688
+ registry.register({
1689
+ name: GATE_NAMES.LANE_HEALTH,
1690
+ warnOnly: laneHealthMode !== "error"
1691
+ });
1692
+ registry.register({
1693
+ name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
1694
+ cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST
1695
+ });
1696
+ if (docsOnlyTestPlan && docsOnlyTestPlan.mode === "filtered") {
1697
+ registry.register({
1698
+ name: GATE_NAMES.TEST,
1699
+ warnOnly: !testsRequired
1700
+ });
1701
+ }
1702
+ }
1703
+ function registerCodeGates(registry, options) {
1704
+ const {
1705
+ isFullLint,
1706
+ isFullTests,
1707
+ isFullCoverage,
1708
+ laneHealthMode,
1709
+ testsRequired,
1710
+ shouldRunIntegration,
1711
+ configuredTestFullCmd
1712
+ } = options;
1713
+ registry.register({
1714
+ name: GATE_NAMES.INVARIANTS,
1715
+ cmd: GATE_COMMANDS.INVARIANTS
1716
+ });
1717
+ registry.register({
1718
+ name: GATE_NAMES.FORMAT_CHECK,
1719
+ scriptName: SCRIPTS.FORMAT_CHECK
1720
+ });
1721
+ registry.register({
1722
+ name: GATE_NAMES.LINT,
1723
+ cmd: isFullLint ? `pnpm ${SCRIPTS.LINT}` : GATE_COMMANDS.INCREMENTAL,
1724
+ scriptName: SCRIPTS.LINT
1725
+ });
1726
+ registry.register({
1727
+ name: GATE_NAMES.CO_CHANGE,
1728
+ run: runCoChangeGate
1729
+ });
1730
+ registry.register({
1731
+ name: GATE_NAMES.TYPECHECK,
1732
+ cmd: `pnpm ${SCRIPTS.TYPECHECK}`,
1733
+ scriptName: SCRIPTS.TYPECHECK
1734
+ });
1735
+ registry.register({
1736
+ name: GATE_NAMES.SPEC_LINTER,
1737
+ scriptName: SCRIPTS.SPEC_LINTER
1738
+ });
1739
+ registry.register({
1740
+ name: GATE_NAMES.BACKLOG_SYNC
1741
+ });
1742
+ registry.register({
1743
+ name: GATE_NAMES.CLAIM_VALIDATION
1744
+ });
1745
+ registry.register({
1746
+ name: GATE_NAMES.SUPABASE_DOCS_LINTER
1747
+ });
1748
+ registry.register({
1749
+ name: GATE_NAMES.LANE_HEALTH,
1750
+ warnOnly: laneHealthMode !== "error"
1751
+ });
1752
+ registry.register({
1753
+ name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
1754
+ cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST
1755
+ });
1756
+ registry.register({
1757
+ name: GATE_NAMES.SAFETY_CRITICAL_TEST,
1758
+ cmd: GATE_COMMANDS.SAFETY_CRITICAL_TEST,
1759
+ warnOnly: !testsRequired
1760
+ });
1761
+ registry.register({
1762
+ name: GATE_NAMES.TEST,
1763
+ cmd: isFullTests || isFullCoverage ? configuredTestFullCmd : GATE_COMMANDS.INCREMENTAL_TEST,
1764
+ warnOnly: !testsRequired
1765
+ });
1766
+ if (shouldRunIntegration) {
1767
+ registry.register({
1768
+ name: GATE_NAMES.INTEGRATION_TEST,
1769
+ cmd: GATE_COMMANDS.TIERED_TEST,
1770
+ warnOnly: !testsRequired
1771
+ });
1772
+ }
1773
+ registry.register({
1774
+ name: GATE_NAMES.COVERAGE,
1775
+ cmd: GATE_COMMANDS.COVERAGE_GATE
1776
+ });
1777
+ }
1778
+
1779
+ // src/onboarding-smoke-test.ts
1780
+ import * as fs from "fs";
1781
+ import * as path8 from "path";
1782
+ import * as os from "os";
1783
+ import * as yaml from "yaml";
1784
+ import { execFileSync } from "child_process";
1785
+ var PACKAGE_JSON_FILE = "package.json";
1786
+ var WORKSPACE_CONFIG_FILE = WORKSPACE_CONFIG_FILE_NAME;
1787
+ var SOFTWARE_DELIVERY_KEY = WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY;
1788
+ var GIT_BINARY = "git";
1789
+ var REQUIRED_SCRIPTS = ["wu:claim", "wu:done", "wu:create", "gates"];
1790
+ function asRecord(value) {
1791
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
1792
+ }
1793
+ function validateInitScripts(options) {
1794
+ const { projectDir } = options;
1795
+ const packageJsonPath = path8.join(projectDir, PACKAGE_JSON_FILE);
1796
+ if (!fs.existsSync(packageJsonPath)) {
1797
+ return {
1798
+ valid: false,
1799
+ missingScripts: [],
1800
+ invalidScripts: [],
1801
+ error: `${PACKAGE_JSON_FILE} not found in ${projectDir}`
1802
+ };
1803
+ }
1804
+ let packageJson;
1805
+ try {
1806
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1807
+ } catch (err) {
1808
+ return {
1809
+ valid: false,
1810
+ missingScripts: [],
1811
+ invalidScripts: [],
1812
+ error: `Failed to parse ${PACKAGE_JSON_FILE}: ${err instanceof Error ? err.message : String(err)}`
1813
+ };
1814
+ }
1815
+ const scripts = packageJson.scripts ?? {};
1816
+ const missingScripts = [];
1817
+ const invalidScripts = [];
1818
+ for (const script of REQUIRED_SCRIPTS) {
1819
+ if (!scripts[script]) {
1820
+ missingScripts.push(script);
1821
+ } else {
1822
+ const value = scripts[script];
1823
+ if (value.includes("pnpm exec") || value.includes("npx lumenflow")) {
1824
+ invalidScripts.push(script);
1825
+ }
1826
+ }
1827
+ }
1828
+ return {
1829
+ valid: missingScripts.length === 0 && invalidScripts.length === 0,
1830
+ missingScripts,
1831
+ invalidScripts
1832
+ };
1833
+ }
1834
+ function validateWorkspaceLaneScaffold(options) {
1835
+ const { projectDir } = options;
1836
+ const configPath = path8.join(projectDir, WORKSPACE_CONFIG_FILE);
1837
+ if (!fs.existsSync(configPath)) {
1838
+ return {
1839
+ valid: false,
1840
+ errors: [],
1841
+ error: `${WORKSPACE_CONFIG_FILE} not found in ${projectDir}`
1842
+ };
1843
+ }
1844
+ const legacyLaneInferencePath = path8.join(projectDir, ".lumenflow.lane-inference.yaml");
1845
+ if (fs.existsSync(legacyLaneInferencePath)) {
1846
+ return {
1847
+ valid: false,
1848
+ errors: [],
1849
+ error: `.lumenflow.lane-inference.yaml should not be scaffolded in ${projectDir}`
1850
+ };
1851
+ }
1852
+ let content;
1853
+ try {
1854
+ const rawContent = fs.readFileSync(configPath, "utf-8");
1855
+ content = yaml.parse(rawContent) ?? {};
1856
+ } catch (err) {
1857
+ return {
1858
+ valid: false,
1859
+ errors: [],
1860
+ error: `Failed to parse ${WORKSPACE_CONFIG_FILE}: ${err instanceof Error ? err.message : String(err)}`
1861
+ };
1862
+ }
1863
+ const errors = [];
1864
+ const lifecycleStatus = content?.software_delivery?.lanes?.lifecycle?.status;
1865
+ if (lifecycleStatus !== "unconfigured") {
1866
+ errors.push(
1867
+ `Expected ${WORKSPACE_CONFIG_FILE} lane lifecycle to remain "unconfigured" after init.`
1868
+ );
1869
+ }
1870
+ return {
1871
+ valid: errors.length === 0,
1872
+ errors
1873
+ };
1874
+ }
1875
+ function initializeGitRepo(projectDir) {
1876
+ execFileSync(GIT_BINARY, ["init"], { cwd: projectDir, stdio: "pipe" });
1877
+ execFileSync(GIT_BINARY, ["config", "user.email", "test@example.com"], {
1878
+ cwd: projectDir,
1879
+ stdio: "pipe"
1880
+ });
1881
+ execFileSync(GIT_BINARY, ["config", "user.name", "Test User"], {
1882
+ cwd: projectDir,
1883
+ stdio: "pipe"
1884
+ });
1885
+ execFileSync(GIT_BINARY, ["add", "-A"], { cwd: projectDir, stdio: "pipe" });
1886
+ execFileSync(GIT_BINARY, ["commit", "-m", "Initial commit", "--allow-empty"], {
1887
+ cwd: projectDir,
1888
+ stdio: "pipe"
1889
+ });
1890
+ }
1891
+ function createSampleWuYaml(projectDir) {
1892
+ const wuDir = path8.join(projectDir, createWuPaths({ projectRoot: projectDir }).WU_DIR());
1893
+ fs.mkdirSync(wuDir, { recursive: true });
1894
+ const wuYaml = `id: WU-TEST-001
1895
+ title: Test WU
1896
+ lane: 'Framework: Core'
1897
+ type: feature
1898
+ status: ready
1899
+ priority: P3
1900
+ created: 2026-02-02
1901
+ code_paths:
1902
+ - 'src/**'
1903
+ acceptance:
1904
+ - Test passes
1905
+ `;
1906
+ fs.writeFileSync(path8.join(wuDir, "WU-TEST-001.yaml"), wuYaml);
1907
+ }
1908
+ async function validateWuCreate(options) {
1909
+ const { projectDir } = options;
1910
+ const configPath = path8.join(projectDir, WORKSPACE_CONFIG_FILE);
1911
+ const existingWorkspace = fs.existsSync(configPath) ? asRecord(yaml.parse(fs.readFileSync(configPath, "utf-8"))) : null;
1912
+ const workspace = existingWorkspace ?? {};
1913
+ const softwareDelivery = asRecord(workspace[SOFTWARE_DELIVERY_KEY]) ?? {};
1914
+ const gitConfig = asRecord(softwareDelivery.git) ?? {};
1915
+ gitConfig.requireRemote = false;
1916
+ softwareDelivery.git = gitConfig;
1917
+ workspace[SOFTWARE_DELIVERY_KEY] = softwareDelivery;
1918
+ fs.writeFileSync(configPath, yaml.stringify(workspace));
1919
+ try {
1920
+ initializeGitRepo(projectDir);
1921
+ } catch (err) {
1922
+ return {
1923
+ success: false,
1924
+ error: `Failed to initialize git repo: ${err instanceof Error ? err.message : String(err)}`
1925
+ };
1926
+ }
1927
+ createSampleWuYaml(projectDir);
1928
+ return { success: true };
1929
+ }
1930
+ function collectScriptsErrors(scriptsResult) {
1931
+ const errors = [];
1932
+ if (scriptsResult.error) {
1933
+ errors.push(`Init scripts validation error: ${scriptsResult.error}`);
1934
+ }
1935
+ if (scriptsResult.missingScripts.length > 0) {
1936
+ errors.push(`Missing scripts: ${scriptsResult.missingScripts.join(", ")}`);
1937
+ }
1938
+ if (scriptsResult.invalidScripts.length > 0) {
1939
+ errors.push(`Invalid script format: ${scriptsResult.invalidScripts.join(", ")}`);
1940
+ }
1941
+ return errors;
1942
+ }
1943
+ function collectLaneErrors(laneResult) {
1944
+ const errors = [];
1945
+ if (laneResult.error) {
1946
+ errors.push(`Workspace lane validation error: ${laneResult.error}`);
1947
+ }
1948
+ errors.push(...laneResult.errors);
1949
+ return errors;
1950
+ }
1951
+ async function runValidations(tempDir, skipWuCreate) {
1952
+ const errors = [];
1953
+ await scaffoldProject(tempDir, { force: true, full: true });
1954
+ const scriptsResult = validateInitScripts({ projectDir: tempDir });
1955
+ if (!scriptsResult.valid) {
1956
+ errors.push(...collectScriptsErrors(scriptsResult));
1957
+ }
1958
+ const laneResult = validateWorkspaceLaneScaffold({ projectDir: tempDir });
1959
+ if (!laneResult.valid) {
1960
+ errors.push(...collectLaneErrors(laneResult));
1961
+ }
1962
+ let wuResult;
1963
+ if (!skipWuCreate) {
1964
+ wuResult = await validateWuCreate({ projectDir: tempDir });
1965
+ if (!wuResult.success && wuResult.error) {
1966
+ errors.push(`wu:create validation error: ${wuResult.error}`);
1967
+ }
1968
+ }
1969
+ return { scriptsResult, laneResult, wuResult, errors };
1970
+ }
1971
+ function cleanupTempDir(tempDir) {
1972
+ if (tempDir && fs.existsSync(tempDir)) {
1973
+ try {
1974
+ fs.rmSync(tempDir, { recursive: true, force: true });
1975
+ } catch {
1976
+ }
1977
+ }
1978
+ }
1979
+ async function runOnboardingSmokeTest(options = {}) {
1980
+ const { cleanup = true, skipWuCreate = false } = options;
1981
+ let { tempDir } = options;
1982
+ const result = {
1983
+ success: false,
1984
+ errors: []
1985
+ };
1986
+ if (!tempDir) {
1987
+ tempDir = fs.mkdtempSync(path8.join(os.tmpdir(), "lumenflow-smoke-test-"));
1988
+ result.tempDir = tempDir;
1989
+ }
1990
+ if (!fs.existsSync(tempDir)) {
1991
+ try {
1992
+ fs.mkdirSync(tempDir, { recursive: true });
1993
+ } catch (err) {
1994
+ return {
1995
+ success: false,
1996
+ errors: [
1997
+ `Failed to create temp directory: ${err instanceof Error ? err.message : String(err)}`
1998
+ ]
1999
+ };
2000
+ }
2001
+ }
2002
+ try {
2003
+ const { scriptsResult, laneResult, wuResult, errors } = await runValidations(
2004
+ tempDir,
2005
+ skipWuCreate
2006
+ );
2007
+ result.initScriptsValidation = scriptsResult;
2008
+ result.workspaceLaneValidation = laneResult;
2009
+ result.wuCreateValidation = wuResult;
2010
+ result.errors = errors;
2011
+ result.success = errors.length === 0;
2012
+ } catch (err) {
2013
+ result.errors = [`Smoke test failed: ${err instanceof Error ? err.message : String(err)}`];
2014
+ result.success = false;
2015
+ } finally {
2016
+ if (cleanup) {
2017
+ cleanupTempDir(tempDir);
2018
+ }
2019
+ }
2020
+ return result;
2021
+ }
2022
+ async function runOnboardingSmokeTestGate(options) {
2023
+ const start = Date.now();
2024
+ const logger = options.logger ?? console;
2025
+ logger.log("Running onboarding smoke test...");
2026
+ const result = await runOnboardingSmokeTest({ cleanup: true });
2027
+ if (result.success) {
2028
+ logger.log("Onboarding smoke test passed.");
2029
+ } else {
2030
+ logger.log("Onboarding smoke test failed:");
2031
+ for (const error of result.errors) {
2032
+ logger.log(` - ${error}`);
2033
+ }
2034
+ }
2035
+ return {
2036
+ ok: result.success,
2037
+ duration: Date.now() - start
2038
+ };
2039
+ }
2040
+
2041
+ // src/gates.ts
2042
+ var GATES_OPTIONS = {
2043
+ docsOnly: {
2044
+ name: "docsOnly",
2045
+ flags: "--docs-only",
2046
+ description: "Run docs-only gates (format, spec-linter, backlog-sync)"
2047
+ },
2048
+ fullLint: {
2049
+ name: "fullLint",
2050
+ flags: "--full-lint",
2051
+ description: "Run full lint instead of incremental"
2052
+ },
2053
+ fullTests: {
2054
+ name: "fullTests",
2055
+ flags: "--full-tests",
2056
+ description: "Run full test suite instead of incremental"
2057
+ },
2058
+ fullCoverage: {
2059
+ name: "fullCoverage",
2060
+ flags: "--full-coverage",
2061
+ description: "Force full test suite and coverage gate (implies --full-tests)"
2062
+ },
2063
+ coverageMode: {
2064
+ name: "coverageMode",
2065
+ flags: "--coverage-mode <mode>",
2066
+ description: 'Coverage gate mode: "warn" logs warnings, "block" fails gate',
2067
+ default: "block"
2068
+ },
2069
+ verbose: {
2070
+ name: "verbose",
2071
+ flags: "--verbose",
2072
+ description: "Stream output in agent mode instead of logging to file"
2073
+ },
2074
+ // WU-1520: --strict flag makes missing scripts a hard failure for CI
2075
+ strict: {
2076
+ name: "strict",
2077
+ flags: "--strict",
2078
+ description: "Fail on missing gate scripts instead of skipping (for CI enforcement)"
2079
+ }
2080
+ };
2081
+ function parseGatesOptions() {
2082
+ const originalArgv = process.argv;
2083
+ const filteredArgv = originalArgv.filter((arg, index, arr) => {
2084
+ if (arg === "--") {
2085
+ const nextArg = arr[index + 1];
2086
+ return Boolean(nextArg && !nextArg.startsWith("-"));
2087
+ }
2088
+ return true;
2089
+ });
2090
+ process.argv = filteredArgv;
2091
+ try {
2092
+ const opts = createWUParser({
2093
+ name: "gates",
2094
+ description: "Run quality gates with support for docs-only mode, incremental linting, and tiered testing",
2095
+ options: Object.values(GATES_OPTIONS)
2096
+ });
2097
+ return {
2098
+ docsOnly: opts.docsOnly,
2099
+ fullLint: opts.fullLint,
2100
+ fullTests: opts.fullTests,
2101
+ fullCoverage: opts.fullCoverage,
2102
+ coverageMode: opts.coverageMode ?? "block",
2103
+ verbose: opts.verbose,
2104
+ strict: opts.strict
2105
+ };
2106
+ } finally {
2107
+ process.argv = originalArgv;
2108
+ }
2109
+ }
2110
+ async function runGates(options = {}) {
2111
+ try {
2112
+ return await executeGates({
2113
+ ...options,
2114
+ cwd: options.cwd ?? process.cwd(),
2115
+ coverageMode: options.coverageMode ?? COVERAGE_GATE_MODES.BLOCK
2116
+ });
2117
+ } catch {
2118
+ return false;
2119
+ }
2120
+ }
2121
+ async function syncGatesTelemetryToCloud(input = {}) {
2122
+ return syncNdjsonTelemetryToCloud({
2123
+ workspaceRoot: input.cwd ?? process.cwd(),
2124
+ fetchFn: input.fetchFn,
2125
+ logger: input.logger,
2126
+ now: input.now,
2127
+ environment: input.environment
2128
+ });
2129
+ }
2130
+ async function executeGates(opts) {
2131
+ const cwd = opts.cwd ?? process.cwd();
2132
+ const argv = opts.argv ?? process.argv.slice(2);
2133
+ const wu_id = getCurrentWU();
2134
+ const lane = getCurrentLane();
2135
+ const useAgentMode = shouldUseGatesAgentMode({ argv, env: process.env });
2136
+ const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane, cwd }) : null;
2137
+ if (useAgentMode && !agentLog) {
2138
+ die("Failed to initialize agent-mode gate log context");
2139
+ }
2140
+ const isDocsOnly = opts.docsOnly || false;
2141
+ const isFullLint = opts.fullLint || false;
2142
+ const isFullTests = opts.fullTests || false;
2143
+ const isFullCoverage = opts.fullCoverage || false;
2144
+ const resolvedTestPolicy = resolveTestPolicy(cwd);
2145
+ const coverageMode = opts.coverageMode || resolvedTestPolicy.mode || COVERAGE_GATE_MODES.BLOCK;
2146
+ const coverageThreshold = resolvedTestPolicy.threshold;
2147
+ const testsRequired = resolvedTestPolicy.tests_required;
2148
+ const laneHealthMode = loadLaneHealthConfig(cwd);
2149
+ const configuredGatesCommands = resolveGatesCommands(cwd);
2150
+ const isStrict = opts.strict || false;
2151
+ const packageJsonScripts = loadPackageJsonScripts(cwd);
2152
+ const gateResults = [];
2153
+ const telemetrySyncLogger = {
2154
+ warn: (message) => {
2155
+ if (useAgentMode) {
2156
+ writeSync3(agentLog.logFd, `${message}
2157
+ `);
2158
+ return;
2159
+ }
2160
+ console.warn(message);
2161
+ }
2162
+ };
2163
+ async function flushTelemetryToCloud() {
2164
+ try {
2165
+ await syncGatesTelemetryToCloud({
2166
+ cwd,
2167
+ logger: telemetrySyncLogger
2168
+ });
2169
+ } catch (error) {
2170
+ const message = error instanceof Error ? error.message : String(error);
2171
+ telemetrySyncLogger.warn(`[gates] cloud telemetry sync failed unexpectedly: ${message}`);
2172
+ }
2173
+ }
2174
+ if (useAgentMode) {
2175
+ console.log(
2176
+ `\u{1F9FE} gates (agent mode): output -> ${agentLog.logPath} (use --verbose for streaming)
2177
+ `
2178
+ );
2179
+ }
2180
+ let riskTier = null;
2181
+ if (!isDocsOnly) {
2182
+ riskTier = {
2183
+ tier: "standard",
2184
+ isDocsOnly: false,
2185
+ shouldRunIntegration: false,
2186
+ highRiskPaths: []
2187
+ };
2188
+ }
2189
+ const effectiveDocsOnly = isDocsOnly || riskTier && riskTier.isDocsOnly;
2190
+ let docsOnlyTestPlan = null;
2191
+ if (effectiveDocsOnly) {
2192
+ const codePaths = loadCurrentWUCodePaths({ cwd });
2193
+ docsOnlyTestPlan = resolveDocsOnlyTestPlan({ codePaths });
2194
+ }
2195
+ const gateRegistry = new GateRegistry();
2196
+ if (effectiveDocsOnly) {
2197
+ registerDocsOnlyGates(gateRegistry, {
2198
+ laneHealthMode,
2199
+ testsRequired,
2200
+ docsOnlyTestPlan
2201
+ });
2202
+ } else {
2203
+ registerCodeGates(gateRegistry, {
2204
+ isFullLint,
2205
+ isFullTests,
2206
+ isFullCoverage,
2207
+ laneHealthMode,
2208
+ testsRequired,
2209
+ shouldRunIntegration: !!(riskTier && riskTier.shouldRunIntegration),
2210
+ configuredTestFullCmd: configuredGatesCommands.test_full
2211
+ });
2212
+ }
2213
+ const gateRunFunctions = {
2214
+ [GATE_NAMES.FORMAT_CHECK]: runFormatCheckGate,
2215
+ [GATE_NAMES.SPEC_LINTER]: runSpecLinterGate,
2216
+ [GATE_NAMES.CLAIM_VALIDATION]: runClaimValidationGate,
2217
+ [GATE_NAMES.BACKLOG_SYNC]: runBacklogSyncGate,
2218
+ [GATE_NAMES.SUPABASE_DOCS_LINTER]: runSupabaseDocsGate,
2219
+ [GATE_NAMES.LANE_HEALTH]: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode })
2220
+ };
2221
+ if (docsOnlyTestPlan && docsOnlyTestPlan.mode === "filtered") {
2222
+ gateRunFunctions[GATE_NAMES.TEST] = (ctx) => {
2223
+ const pkgs = docsOnlyTestPlan.packages;
2224
+ return runDocsOnlyFilteredTests({
2225
+ packages: pkgs,
2226
+ agentLog: ctx.agentLog,
2227
+ cwd: ctx.cwd
2228
+ });
2229
+ };
2230
+ }
2231
+ const gates = gateRegistry.getAll().map((gate) => {
2232
+ const runFn = gateRunFunctions[gate.name];
2233
+ if (runFn && !gate.run) {
2234
+ return { ...gate, run: runFn };
2235
+ }
2236
+ return gate;
2237
+ });
2238
+ if (effectiveDocsOnly) {
2239
+ const docsOnlyMessage = docsOnlyTestPlan && docsOnlyTestPlan.mode === "filtered" ? formatDocsOnlySkipMessage(docsOnlyTestPlan) : "\u{1F4DD} Docs-only mode: skipping lint, typecheck, and all tests (no code packages in code_paths)";
2240
+ if (!useAgentMode) {
2241
+ console.log(`${docsOnlyMessage}
2242
+ `);
2243
+ } else {
2244
+ writeSync3(agentLog.logFd, `${docsOnlyMessage}
2245
+ `);
2246
+ }
2247
+ }
2248
+ let lastTestResult = null;
2249
+ let lastFormatCheckFiles = null;
2250
+ for (const gate of gates) {
2251
+ let result;
2252
+ const gateScriptName = gate.scriptName ?? null;
2253
+ const gateAction = resolveGateAction(gate.name, gateScriptName, packageJsonScripts, isStrict);
2254
+ if (gateAction === "skip") {
2255
+ const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
2256
+ const warningMsg = buildMissingScriptWarning(gateScriptName);
2257
+ logLine(`
2258
+ ${warningMsg}
2259
+ `);
2260
+ gateResults.push({
2261
+ name: gate.name,
2262
+ status: "skipped",
2263
+ durationMs: 0,
2264
+ reason: "script not found in package.json"
2265
+ });
2266
+ continue;
2267
+ }
2268
+ if (gateAction === "fail") {
2269
+ const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
2270
+ logLine(`
2271
+ \u274C "${gateScriptName}" script not found in package.json (--strict mode)
2272
+ `);
2273
+ gateResults.push({
2274
+ name: gate.name,
2275
+ status: "failed",
2276
+ durationMs: 0,
2277
+ reason: "script not found in package.json (strict mode)"
2278
+ });
2279
+ die(
2280
+ `${gate.name} failed: missing script "${gateScriptName}" in package.json (--strict mode requires all gate scripts)`
2281
+ );
2282
+ }
2283
+ if (gate.run) {
2284
+ result = await gate.run({ agentLog, useAgentMode, cwd });
2285
+ if (gate.name === GATE_NAMES.FORMAT_CHECK) {
2286
+ lastFormatCheckFiles = result.filesChecked ?? null;
2287
+ }
2288
+ } else if (gate.cmd === GATE_COMMANDS.INVARIANTS) {
2289
+ const logLine = useAgentMode ? (line) => writeSync3(agentLog.logFd, `${line}
2290
+ `) : (line) => console.log(line);
2291
+ logLine("\n> Invariants check\n");
2292
+ const invariantsResult = runInvariants({ baseDir: cwd, silent: false });
2293
+ result = {
2294
+ ok: invariantsResult.success,
2295
+ duration: 0
2296
+ // runInvariants doesn't track duration
2297
+ };
2298
+ if (!result.ok) {
2299
+ logLine("");
2300
+ logLine(invariantsResult.formatted);
2301
+ }
2302
+ } else if (gate.cmd === GATE_COMMANDS.INCREMENTAL) {
2303
+ result = await runIncrementalLint({ agentLog, cwd });
2304
+ } else if (gate.cmd === GATE_COMMANDS.SAFETY_CRITICAL_TEST) {
2305
+ result = await runSafetyCriticalTests({ agentLog, cwd });
2306
+ } else if (gate.cmd === GATE_COMMANDS.INCREMENTAL_TEST) {
2307
+ result = await runChangedTests({
2308
+ agentLog,
2309
+ cwd,
2310
+ scopedTestPaths: opts.scopedTestPaths
2311
+ });
2312
+ lastTestResult = result;
2313
+ } else if (gate.cmd === GATE_COMMANDS.TIERED_TEST) {
2314
+ result = await runIntegrationTests({ agentLog, cwd });
2315
+ } else if (gate.cmd === GATE_COMMANDS.COVERAGE_GATE) {
2316
+ if (!isFullCoverage && lastTestResult?.isIncremental) {
2317
+ const msg = "\u23ED\uFE0F Skipping coverage gate (changed tests - coverage is partial)";
2318
+ if (!useAgentMode) {
2319
+ console.log(`
2320
+ ${msg}
2321
+ `);
2322
+ } else {
2323
+ writeSync3(agentLog.logFd, `
2324
+ ${msg}
2325
+
2326
+ `);
2327
+ }
2328
+ gateResults.push({
2329
+ name: gate.name,
2330
+ status: "skipped",
2331
+ durationMs: 0,
2332
+ reason: "changed tests - coverage is partial"
2333
+ });
2334
+ continue;
2335
+ }
2336
+ if (!useAgentMode) {
2337
+ console.log(
2338
+ `
2339
+ > Coverage gate (mode: ${coverageMode}, threshold: ${coverageThreshold}%)
2340
+ `
2341
+ );
2342
+ } else {
2343
+ writeSync3(
2344
+ agentLog.logFd,
2345
+ `
2346
+ > Coverage gate (mode: ${coverageMode}, threshold: ${coverageThreshold}%)
2347
+
2348
+ `
2349
+ );
2350
+ }
2351
+ result = await runCoverageGate({
2352
+ mode: coverageMode,
2353
+ // WU-1262: Pass resolved threshold from methodology policy
2354
+ threshold: coverageThreshold,
2355
+ logger: useAgentMode ? {
2356
+ log: (msg) => {
2357
+ writeSync3(agentLog.logFd, `${msg}
2358
+ `);
2359
+ }
2360
+ } : console
2361
+ });
2362
+ } else if (gate.cmd === GATE_COMMANDS.ONBOARDING_SMOKE_TEST) {
2363
+ const logLine = useAgentMode ? (line) => writeSync3(agentLog.logFd, `${line}
2364
+ `) : (line) => console.log(line);
2365
+ logLine("\n> Onboarding smoke test\n");
2366
+ result = await runOnboardingSmokeTestGate({
2367
+ logger: { log: logLine }
2368
+ });
2369
+ } else {
2370
+ if (!gate.cmd) {
2371
+ die(`${gate.name} failed: gate command is not configured`);
2372
+ }
2373
+ result = run(gate.cmd, { agentLog, cwd });
2374
+ }
2375
+ emitGateEvent({
2376
+ wu_id,
2377
+ lane,
2378
+ gate_name: gate.name,
2379
+ passed: result.ok,
2380
+ duration_ms: result.duration
2381
+ });
2382
+ if (!result.ok) {
2383
+ if (gate.warnOnly) {
2384
+ const warnMsg = `\u26A0\uFE0F ${gate.name} failed (warn-only, not blocking)`;
2385
+ if (!useAgentMode) {
2386
+ console.log(`
2387
+ ${warnMsg}
2388
+ `);
2389
+ } else {
2390
+ writeSync3(agentLog.logFd, `
2391
+ ${warnMsg}
2392
+
2393
+ `);
2394
+ }
2395
+ gateResults.push({
2396
+ name: gate.name,
2397
+ status: "warned",
2398
+ durationMs: result.duration
2399
+ });
2400
+ continue;
2401
+ }
2402
+ if (gate.name === GATE_NAMES.FORMAT_CHECK) {
2403
+ emitFormatCheckGuidance({ agentLog, useAgentMode, files: lastFormatCheckFiles, cwd });
2404
+ }
2405
+ gateResults.push({
2406
+ name: gate.name,
2407
+ status: "failed",
2408
+ durationMs: result.duration
2409
+ });
2410
+ const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
2411
+ logLine(`
2412
+ ${formatGateSummary(gateResults)}
2413
+ `);
2414
+ if (useAgentMode) {
2415
+ const tail = readLogTail(agentLog.logPath);
2416
+ console.error(`
2417
+ \u274C ${gate.name} failed (agent mode). Log: ${agentLog.logPath}
2418
+ `);
2419
+ if (tail) {
2420
+ console.error(`Last log lines:
2421
+ ${tail}
2422
+ `);
2423
+ }
2424
+ }
2425
+ await flushTelemetryToCloud();
2426
+ die(`${gate.name} failed`);
2427
+ }
2428
+ gateResults.push({
2429
+ name: gate.name,
2430
+ status: "passed",
2431
+ durationMs: result.duration
2432
+ });
2433
+ }
2434
+ if (agentLog) {
2435
+ updateGatesLatestSymlink({ logPath: agentLog.logPath, cwd, env: process.env });
2436
+ }
2437
+ const summaryLogLine = makeGateLogger({ agentLog, useAgentMode, cwd });
2438
+ summaryLogLine(`
2439
+ ${formatGateSummary(gateResults)}`);
2440
+ if (!useAgentMode) {
2441
+ console.log(`
2442
+ ${chalk.green("\u2705 All gates passed!")}
2443
+ `);
2444
+ } else {
2445
+ console.log(
2446
+ `${chalk.green("\u2705 All gates passed")} (agent mode). Log: ${agentLog.logPath}
2447
+ `
2448
+ );
2449
+ }
2450
+ await flushTelemetryToCloud();
2451
+ return true;
2452
+ }
2453
+ async function main() {
2454
+ const opts = parseGatesOptions();
2455
+ const ok = await executeGates({ ...opts, argv: process.argv.slice(2) });
2456
+ if (!ok) {
2457
+ process.exit(EXIT_CODES.ERROR);
2458
+ }
2459
+ }
2460
+ if (import.meta.main) {
2461
+ void runCLI(main);
2462
+ }
2463
+
2464
+ export {
2465
+ resolveWuDonePreCommitGateDecision,
2466
+ parsePrettierListOutput,
2467
+ buildPrettierWriteCommand,
2468
+ formatFormatCheckGuidance,
2469
+ parseWUFromBranchName,
2470
+ extractPackagesFromCodePaths,
2471
+ loadCurrentWUCodePaths,
2472
+ isPrettierConfigFile,
2473
+ isTestConfigFile,
2474
+ resolveFormatCheckPlan,
2475
+ resolveLintPlan,
2476
+ resolveTestPlan,
2477
+ resolveDocsOnlyTestPlan,
2478
+ formatDocsOnlySkipMessage,
2479
+ resolveSpecLinterPlan,
2480
+ GATES_OPTIONS,
2481
+ parseGatesOptions,
2482
+ runGates,
2483
+ syncGatesTelemetryToCloud,
2484
+ main
2485
+ };