@lumenflow/cli 2.2.1 → 2.2.2

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.
@@ -146,11 +146,39 @@ describe('release command integration', () => {
146
146
  describe('WU-1077: release script bug fixes', () => {
147
147
  describe('hasNpmAuth - ~/.npmrc detection', () => {
148
148
  let testDir;
149
+ let originalUserConfig;
150
+ let originalNpmToken;
151
+ let originalNodeAuthToken;
149
152
  beforeEach(() => {
150
153
  testDir = join(tmpdir(), `release-npmrc-test-${Date.now()}`);
151
154
  mkdirSync(testDir, { recursive: true });
155
+ originalUserConfig = process.env.NPM_CONFIG_USERCONFIG;
156
+ originalNpmToken = process.env.NPM_TOKEN;
157
+ originalNodeAuthToken = process.env.NODE_AUTH_TOKEN;
158
+ process.env.NPM_CONFIG_USERCONFIG = join(testDir, 'user.npmrc');
159
+ writeFileSync(process.env.NPM_CONFIG_USERCONFIG, '');
160
+ delete process.env.NPM_TOKEN;
161
+ delete process.env.NODE_AUTH_TOKEN;
152
162
  });
153
163
  afterEach(() => {
164
+ if (originalUserConfig === undefined) {
165
+ delete process.env.NPM_CONFIG_USERCONFIG;
166
+ }
167
+ else {
168
+ process.env.NPM_CONFIG_USERCONFIG = originalUserConfig;
169
+ }
170
+ if (originalNpmToken === undefined) {
171
+ delete process.env.NPM_TOKEN;
172
+ }
173
+ else {
174
+ process.env.NPM_TOKEN = originalNpmToken;
175
+ }
176
+ if (originalNodeAuthToken === undefined) {
177
+ delete process.env.NODE_AUTH_TOKEN;
178
+ }
179
+ else {
180
+ process.env.NODE_AUTH_TOKEN = originalNodeAuthToken;
181
+ }
154
182
  rmSync(testDir, { recursive: true, force: true });
155
183
  });
156
184
  it('should detect auth from ~/.npmrc authToken line', async () => {
package/dist/gates.js CHANGED
@@ -55,7 +55,10 @@ import { detectRiskTier, RISK_TIERS, } from '@lumenflow/core/dist/risk-detector.
55
55
  // WU-2252: Import invariants runner for first-check validation
56
56
  import { runInvariants } from '@lumenflow/core/dist/invariants-runner.js';
57
57
  import { createWUParser } from '@lumenflow/core/dist/arg-parser.js';
58
- import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, TOOL_PATHS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
58
+ import { validateBacklogSync } from '@lumenflow/core/dist/validators/backlog-sync.js';
59
+ import { runSupabaseDocsLinter } from '@lumenflow/core/dist/validators/supabase-docs-linter.js';
60
+ import { runSystemMapValidation } from '@lumenflow/core/dist/system-map-validator.js';
61
+ import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
59
62
  /**
60
63
  * WU-1087: Gates-specific option definitions for createWUParser.
61
64
  * Exported for testing and consistency with other CLI commands.
@@ -274,6 +277,71 @@ function run(cmd, { agentLog } = {}) {
274
277
  });
275
278
  return { ok: result.status === EXIT_CODES.SUCCESS, duration: Date.now() - start };
276
279
  }
280
+ function makeGateLogger({ agentLog, useAgentMode }) {
281
+ return (line) => {
282
+ if (!useAgentMode) {
283
+ console.log(line);
284
+ return;
285
+ }
286
+ if (agentLog) {
287
+ writeSync(agentLog.logFd, `${line}\n`);
288
+ }
289
+ };
290
+ }
291
+ async function runBacklogSyncGate({ agentLog, useAgentMode }) {
292
+ const start = Date.now();
293
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
294
+ logLine('\n> Backlog sync\n');
295
+ const result = await validateBacklogSync({ cwd: process.cwd() });
296
+ if (result.errors.length > 0) {
297
+ logLine('❌ Backlog sync errors:');
298
+ result.errors.forEach((error) => logLine(` - ${error}`));
299
+ }
300
+ if (result.warnings.length > 0) {
301
+ logLine('⚠️ Backlog sync warnings:');
302
+ result.warnings.forEach((warning) => logLine(` - ${warning}`));
303
+ }
304
+ logLine(`Backlog sync summary: WU files=${result.wuCount}, Backlog refs=${result.backlogCount}`);
305
+ return { ok: result.valid, duration: Date.now() - start };
306
+ }
307
+ async function runSupabaseDocsGate({ agentLog, useAgentMode }) {
308
+ const start = Date.now();
309
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
310
+ logLine('\n> Supabase docs linter\n');
311
+ const result = await runSupabaseDocsLinter({ cwd: process.cwd(), logger: { log: logLine } });
312
+ if (result.skipped) {
313
+ logLine(`⚠️ ${result.message ?? 'Supabase docs linter skipped.'}`);
314
+ }
315
+ else if (!result.ok) {
316
+ logLine('❌ Supabase docs linter failed.');
317
+ (result.errors ?? []).forEach((error) => logLine(` - ${error}`));
318
+ }
319
+ else {
320
+ logLine(result.message ?? 'Supabase docs linter passed.');
321
+ }
322
+ return { ok: result.ok, duration: Date.now() - start };
323
+ }
324
+ async function runSystemMapGate({ agentLog, useAgentMode }) {
325
+ const start = Date.now();
326
+ const logLine = makeGateLogger({ agentLog, useAgentMode });
327
+ logLine('\n> System map validation\n');
328
+ const result = await runSystemMapValidation({
329
+ cwd: process.cwd(),
330
+ logger: { log: logLine, warn: logLine, error: logLine },
331
+ });
332
+ if (!result.valid) {
333
+ logLine('❌ System map validation failed');
334
+ (result.pathErrors ?? []).forEach((error) => logLine(` - ${error}`));
335
+ (result.orphanDocs ?? []).forEach((error) => logLine(` - ${error}`));
336
+ (result.audienceErrors ?? []).forEach((error) => logLine(` - ${error}`));
337
+ (result.queryErrors ?? []).forEach((error) => logLine(` - ${error}`));
338
+ (result.classificationErrors ?? []).forEach((error) => logLine(` - ${error}`));
339
+ }
340
+ else {
341
+ logLine('System map validation passed.');
342
+ }
343
+ return { ok: result.valid, duration: Date.now() - start };
344
+ }
277
345
  /**
278
346
  * Run incremental ESLint on changed files only
279
347
  * Falls back to full lint if on main branch or if incremental fails
@@ -576,15 +644,36 @@ async function getAllChangedFiles(options = {}) {
576
644
  return [];
577
645
  }
578
646
  }
579
- // Get context for telemetry
580
- const wu_id = getCurrentWU();
581
- const lane = getCurrentLane();
582
- const useAgentMode = shouldUseGatesAgentMode({ argv: process.argv.slice(2), env: process.env });
583
- const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : null;
647
+ export async function runGates(options = {}) {
648
+ const originalCwd = process.cwd();
649
+ const targetCwd = options.cwd ?? originalCwd;
650
+ if (targetCwd !== originalCwd) {
651
+ process.chdir(targetCwd);
652
+ }
653
+ try {
654
+ return await executeGates({
655
+ ...options,
656
+ coverageMode: options.coverageMode ?? COVERAGE_GATE_MODES.BLOCK,
657
+ });
658
+ }
659
+ catch {
660
+ return false;
661
+ }
662
+ finally {
663
+ if (targetCwd !== originalCwd) {
664
+ process.chdir(originalCwd);
665
+ }
666
+ }
667
+ }
584
668
  // Main execution
585
669
  // eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing: main() orchestrates multi-step gate workflow
586
- async function main() {
587
- const opts = parseGatesArgs();
670
+ async function executeGates(opts) {
671
+ const argv = opts.argv ?? process.argv.slice(2);
672
+ // Get context for telemetry
673
+ const wu_id = getCurrentWU();
674
+ const lane = getCurrentLane();
675
+ const useAgentMode = shouldUseGatesAgentMode({ argv, env: process.env });
676
+ const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : null;
588
677
  // Parse command line arguments (now via Commander)
589
678
  const isDocsOnly = opts.docsOnly || false;
590
679
  const isFullLint = opts.fullLint || false;
@@ -637,11 +726,11 @@ async function main() {
637
726
  name: GATE_NAMES.PROMPTS_LINT,
638
727
  cmd: pnpmRun(SCRIPTS.PROMPTS_LINT, CLI_MODES.LOCAL, '--quiet'),
639
728
  },
640
- { name: GATE_NAMES.BACKLOG_SYNC, cmd: TOOL_PATHS.VALIDATE_BACKLOG_SYNC },
729
+ { name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
641
730
  // WU-2315: System map validation (warn-only until orphan docs are indexed)
642
731
  {
643
732
  name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
644
- cmd: TOOL_PATHS.SYSTEM_MAP_VALIDATE,
733
+ run: runSystemMapGate,
645
734
  warnOnly: true,
646
735
  },
647
736
  ]
@@ -659,12 +748,12 @@ async function main() {
659
748
  name: GATE_NAMES.PROMPTS_LINT,
660
749
  cmd: pnpmRun(SCRIPTS.PROMPTS_LINT, CLI_MODES.LOCAL, '--quiet'),
661
750
  },
662
- { name: GATE_NAMES.BACKLOG_SYNC, cmd: TOOL_PATHS.VALIDATE_BACKLOG_SYNC },
663
- { name: GATE_NAMES.SUPABASE_DOCS_LINTER, cmd: TOOL_PATHS.SUPABASE_DOCS_LINTER },
751
+ { name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
752
+ { name: GATE_NAMES.SUPABASE_DOCS_LINTER, run: runSupabaseDocsGate },
664
753
  // WU-2315: System map validation (warn-only until orphan docs are indexed)
665
754
  {
666
755
  name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
667
- cmd: TOOL_PATHS.SYSTEM_MAP_VALIDATE,
756
+ run: runSystemMapGate,
668
757
  warnOnly: true,
669
758
  },
670
759
  // WU-2062: Safety-critical tests ALWAYS run
@@ -697,7 +786,10 @@ async function main() {
697
786
  let lastTestResult = null;
698
787
  for (const gate of gates) {
699
788
  let result;
700
- if (gate.cmd === GATE_COMMANDS.INVARIANTS) {
789
+ if (gate.run) {
790
+ result = await gate.run({ agentLog, useAgentMode });
791
+ }
792
+ else if (gate.cmd === GATE_COMMANDS.INVARIANTS) {
701
793
  // WU-2252: Invariants check runs first (non-bypassable)
702
794
  const logLine = useAgentMode
703
795
  ? (line) => writeSync(agentLog.logFd, `${line}\n`)
@@ -807,13 +899,18 @@ async function main() {
807
899
  else {
808
900
  console.log(`✅ All gates passed (agent mode). Log: ${agentLog.logPath}\n`);
809
901
  }
810
- process.exit(EXIT_CODES.SUCCESS);
902
+ return true;
811
903
  }
812
904
  // WU-1071: Use import.meta.main instead of process.argv[1] comparison
813
905
  // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
814
906
  // path but import.meta.url resolves to the real path - they never match
815
907
  if (import.meta.main) {
816
- main().catch((error) => {
908
+ const opts = parseGatesArgs();
909
+ executeGates({ ...opts, argv: process.argv.slice(2) })
910
+ .then((ok) => {
911
+ process.exit(ok ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
912
+ })
913
+ .catch((error) => {
817
914
  console.error('Gates failed:', error);
818
915
  process.exit(EXIT_CODES.ERROR);
819
916
  });
@@ -15,89 +15,11 @@
15
15
  *
16
16
  * @see {@link docs/04-operations/tasks/backlog.md} - Backlog file
17
17
  */
18
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
19
- import path from 'node:path';
20
18
  import { fileURLToPath } from 'node:url';
21
- import { FILE_SYSTEM, EMOJI } from '@lumenflow/core/dist/wu-constants.js';
19
+ import { validateBacklogSync, } from '@lumenflow/core/dist/validators/backlog-sync.js';
20
+ import { EMOJI } from '@lumenflow/core/dist/wu-constants.js';
22
21
  const LOG_PREFIX = '[validate-backlog-sync]';
23
- /**
24
- * Extract WU IDs from backlog.md content
25
- *
26
- * @param content - backlog.md content
27
- * @returns Array of WU IDs found
28
- */
29
- function extractWUIDsFromBacklog(content) {
30
- const wuIds = [];
31
- const pattern = /WU-\d+/gi;
32
- let match;
33
- while ((match = pattern.exec(content)) !== null) {
34
- const wuId = match[0].toUpperCase();
35
- if (!wuIds.includes(wuId)) {
36
- wuIds.push(wuId);
37
- }
38
- }
39
- return wuIds;
40
- }
41
- /**
42
- * Get all WU IDs from YAML files
43
- *
44
- * @param wuDir - Path to WU directory
45
- * @returns Array of WU IDs
46
- */
47
- function getWUIDsFromFiles(wuDir) {
48
- if (!existsSync(wuDir)) {
49
- return [];
50
- }
51
- return readdirSync(wuDir)
52
- .filter((f) => f.endsWith('.yaml'))
53
- .map((f) => f.replace('.yaml', '').toUpperCase());
54
- }
55
- /**
56
- * Validate that backlog.md is in sync with WU YAML files
57
- *
58
- * @param options - Validation options
59
- * @param options.cwd - Working directory (default: process.cwd())
60
- * @returns Validation result
61
- */
62
- export async function validateBacklogSync(options = {}) {
63
- const { cwd = process.cwd() } = options;
64
- const errors = [];
65
- const warnings = [];
66
- // Paths
67
- const backlogPath = path.join(cwd, 'docs', '04-operations', 'tasks', 'backlog.md');
68
- const wuDir = path.join(cwd, 'docs', '04-operations', 'tasks', 'wu');
69
- // Check backlog.md exists
70
- if (!existsSync(backlogPath)) {
71
- errors.push(`Backlog file not found: ${backlogPath}`);
72
- return { valid: false, errors, warnings, wuCount: 0, backlogCount: 0 };
73
- }
74
- // Get WU IDs from files
75
- const wuIdsFromFiles = getWUIDsFromFiles(wuDir);
76
- // Get WU IDs from backlog
77
- const backlogContent = readFileSync(backlogPath, {
78
- encoding: FILE_SYSTEM.UTF8,
79
- });
80
- const wuIdsFromBacklog = extractWUIDsFromBacklog(backlogContent);
81
- // Check for WUs in files but not in backlog
82
- for (const wuId of wuIdsFromFiles) {
83
- if (!wuIdsFromBacklog.includes(wuId)) {
84
- errors.push(`${wuId} not found in backlog.md (exists as ${wuId}.yaml)`);
85
- }
86
- }
87
- // Check for WUs in backlog but not as files (warning only)
88
- for (const wuId of wuIdsFromBacklog) {
89
- if (!wuIdsFromFiles.includes(wuId)) {
90
- warnings.push(`${wuId} referenced in backlog.md but ${wuId}.yaml not found`);
91
- }
92
- }
93
- return {
94
- valid: errors.length === 0,
95
- errors,
96
- warnings,
97
- wuCount: wuIdsFromFiles.length,
98
- backlogCount: wuIdsFromBacklog.length,
99
- };
100
- }
22
+ export { validateBacklogSync };
101
23
  /**
102
24
  * Main CLI entry point
103
25
  */
package/dist/validate.js CHANGED
@@ -19,113 +19,12 @@
19
19
  * @see {@link wu-validate.ts} - Detailed WU validation with schema
20
20
  * @see {@link docs/04-operations/_frameworks/lumenflow/lumenflow-complete.md} - WU lifecycle
21
21
  */
22
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
23
22
  import { fileURLToPath } from 'node:url';
23
+ import { validateSingleWU, validateAllWUs, } from '@lumenflow/core/dist/validators/wu-tasks.js';
24
24
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
25
- import { parseYAML } from '@lumenflow/core/dist/wu-yaml.js';
26
- import { validateWU, validateWUCompleteness } from '@lumenflow/core/dist/wu-schema.js';
27
- import { FILE_SYSTEM, EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
25
+ import { EMOJI, PATTERNS } from '@lumenflow/core/dist/wu-constants.js';
28
26
  const LOG_PREFIX = '[validate]';
29
- /**
30
- * Validate a single WU file
31
- *
32
- * @param wuPath - Path to WU YAML file
33
- * @param options - Validation options
34
- * @returns Validation result
35
- */
36
- export function validateSingleWU(wuPath, options = {}) {
37
- const { strict = false } = options;
38
- const errors = [];
39
- const warnings = [];
40
- // Check file exists
41
- if (!existsSync(wuPath)) {
42
- errors.push(`WU file not found: ${wuPath}`);
43
- return { valid: false, warnings, errors };
44
- }
45
- // Read and parse YAML
46
- let doc;
47
- try {
48
- const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
49
- doc = parseYAML(text);
50
- }
51
- catch (e) {
52
- errors.push(`Failed to parse YAML: ${e.message}`);
53
- return { valid: false, warnings, errors };
54
- }
55
- // Schema validation
56
- const schemaResult = validateWU(doc);
57
- if (!schemaResult.success) {
58
- const schemaErrors = schemaResult.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`);
59
- errors.push(...schemaErrors);
60
- return { valid: false, warnings, errors };
61
- }
62
- // Completeness validation (soft warnings)
63
- const completenessResult = validateWUCompleteness(schemaResult.data);
64
- warnings.push(...completenessResult.warnings);
65
- // In strict mode, warnings become errors
66
- if (strict && warnings.length > 0) {
67
- errors.push(...warnings.map((w) => `[STRICT] ${w}`));
68
- return { valid: false, warnings: [], errors };
69
- }
70
- return { valid: true, warnings, errors };
71
- }
72
- /**
73
- * Validate all WU files in the WU directory
74
- *
75
- * @param options - Validation options
76
- * @returns Summary of all validations
77
- */
78
- export function validateAllWUs(options = {}) {
79
- const { strict = false, doneOnly = false } = options;
80
- const wuDir = WU_PATHS.WU_DIR();
81
- if (!existsSync(wuDir)) {
82
- return {
83
- totalValid: 0,
84
- totalInvalid: 1,
85
- totalWarnings: 0,
86
- results: [
87
- {
88
- wuId: 'DIRECTORY',
89
- valid: false,
90
- warnings: [],
91
- errors: [`WU directory not found: ${wuDir}`],
92
- },
93
- ],
94
- };
95
- }
96
- const files = readdirSync(wuDir).filter((f) => f.endsWith('.yaml'));
97
- const results = [];
98
- let totalValid = 0;
99
- let totalInvalid = 0;
100
- let totalWarnings = 0;
101
- for (const file of files) {
102
- const wuPath = `${wuDir}/${file}`;
103
- const wuId = file.replace('.yaml', '');
104
- // Skip if only validating done WUs
105
- if (doneOnly) {
106
- try {
107
- const text = readFileSync(wuPath, { encoding: FILE_SYSTEM.UTF8 });
108
- const doc = parseYAML(text);
109
- if (doc.status !== 'done') {
110
- continue;
111
- }
112
- }
113
- catch {
114
- // If we can't read, still validate to catch the error
115
- }
116
- }
117
- const result = validateSingleWU(wuPath, { strict });
118
- results.push({ wuId, ...result });
119
- if (result.valid) {
120
- totalValid++;
121
- totalWarnings += result.warnings.length;
122
- }
123
- else {
124
- totalInvalid++;
125
- }
126
- }
127
- return { totalValid, totalInvalid, totalWarnings, results };
128
- }
27
+ export { validateSingleWU, validateAllWUs };
129
28
  /**
130
29
  * Main CLI entry point
131
30
  */
package/dist/wu-done.js CHANGED
@@ -33,6 +33,7 @@
33
33
  */
34
34
  import { execSync } from 'node:child_process';
35
35
  import prettyMs from 'pretty-ms';
36
+ import { runGates } from './gates.js';
36
37
  import { getGitForCwd } from '@lumenflow/core/dist/git-adapter.js';
37
38
  import { die } from '@lumenflow/core/dist/error-handler.js';
38
39
  import { existsSync, readFileSync, mkdirSync, appendFileSync, unlinkSync, statSync } from 'node:fs';
@@ -915,14 +916,13 @@ function checkNodeModulesStaleness(worktreePath) {
915
916
  * @param {boolean} options.isDocsOnly - Auto-detected docs-only from code_paths
916
917
  * @param {boolean} options.docsOnly - Explicit --docs-only flag from CLI
917
918
  */
918
- function runGatesInWorktree(worktreePath, id, options = {}) {
919
+ async function runGatesInWorktree(worktreePath, id, options = {}) {
919
920
  const { isDocsOnly = false, docsOnly = false } = options;
920
921
  console.log(`\n${LOG_PREFIX.DONE} Running gates in worktree: ${worktreePath}`);
921
922
  // Check for stale node_modules before running gates (prevents confusing failures)
922
923
  checkNodeModulesStaleness(worktreePath);
923
924
  // WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
924
925
  const useDocsOnlyGates = docsOnly || isDocsOnly;
925
- const gatesCmd = buildGatesCommand({ docsOnly, isDocsOnly });
926
926
  if (useDocsOnlyGates) {
927
927
  console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
928
928
  if (docsOnly) {
@@ -931,12 +931,14 @@ function runGatesInWorktree(worktreePath, id, options = {}) {
931
931
  }
932
932
  const startTime = Date.now();
933
933
  try {
934
- // WU-1230: Pass WU_ID to validator for context-aware validation
935
- execSync(gatesCmd, {
934
+ const ok = Boolean(await runGates({
936
935
  cwd: worktreePath,
937
- stdio: 'inherit',
938
- env: { ...process.env, WU_ID: id },
939
- });
936
+ docsOnly: useDocsOnlyGates,
937
+ coverageMode: undefined,
938
+ }));
939
+ if (!ok) {
940
+ throw new Error('Gates failed');
941
+ }
940
942
  const duration = Date.now() - startTime;
941
943
  console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
942
944
  emitTelemetry({ script: 'wu-done', wu_id: id, step: 'gates', ok: true, duration_ms: duration });
@@ -1774,8 +1776,7 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
1774
1776
  // Branch-Only mode: run gates in-place (current directory on lane branch)
1775
1777
  console.log(`\n${LOG_PREFIX.DONE} Running gates in Branch-Only mode (in-place on lane branch)`);
1776
1778
  // WU-1012: Use docs-only gates if explicit --docs-only flag OR auto-detected
1777
- const useDocsOnlyGates = args.docsOnly || isDocsOnly;
1778
- const gatesCmd = buildGatesCommand({ docsOnly: Boolean(args.docsOnly), isDocsOnly });
1779
+ const useDocsOnlyGates = Boolean(args.docsOnly) || Boolean(isDocsOnly);
1779
1780
  if (useDocsOnlyGates) {
1780
1781
  console.log(`${LOG_PREFIX.DONE} Using docs-only gates (skipping lint/typecheck/tests)`);
1781
1782
  if (args.docsOnly) {
@@ -1784,7 +1785,10 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
1784
1785
  }
1785
1786
  const startTime = Date.now();
1786
1787
  try {
1787
- execSync(gatesCmd, { stdio: 'inherit' });
1788
+ const ok = Boolean(await runGates({ docsOnly: useDocsOnlyGates }));
1789
+ if (!ok) {
1790
+ throw new Error('Gates failed');
1791
+ }
1788
1792
  const duration = Date.now() - startTime;
1789
1793
  console.log(`${LOG_PREFIX.DONE} ${EMOJI.SUCCESS} Gates passed in ${prettyMs(duration)}`);
1790
1794
  emitTelemetry({
@@ -1818,7 +1822,10 @@ async function executeGates({ id, args, isBranchOnly, isDocsOnly, worktreePath,
1818
1822
  else if (worktreePath && existsSync(worktreePath)) {
1819
1823
  // Worktree mode: run gates in the dedicated worktree
1820
1824
  // WU-1012: Pass both auto-detected and explicit docs-only flags
1821
- runGatesInWorktree(worktreePath, id, { isDocsOnly, docsOnly: Boolean(args.docsOnly) });
1825
+ await runGatesInWorktree(worktreePath, id, {
1826
+ isDocsOnly,
1827
+ docsOnly: Boolean(args.docsOnly),
1828
+ });
1822
1829
  }
1823
1830
  else {
1824
1831
  die(`Worktree not found (${worktreePath || 'unknown'}). Gates must run in the lane worktree.\n` +
@@ -1977,7 +1984,9 @@ async function main() {
1977
1984
  // WU-2308: Pass worktreePath to run audit from worktree (checks fixed deps, not stale main deps)
1978
1985
  // WU-1145: Skip pre-flight when skipGates is true (pre-flight runs gates which was already skipped)
1979
1986
  if (!args.skipGates) {
1980
- const hookResult = validateAllPreCommitHooks(id, worktreePath);
1987
+ const hookResult = await validateAllPreCommitHooks(id, worktreePath, {
1988
+ runGates: ({ cwd }) => runGates({ cwd, docsOnly: false }),
1989
+ });
1981
1990
  if (!hookResult.valid) {
1982
1991
  die('Pre-flight validation failed. Fix hook issues and try again.');
1983
1992
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/cli",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -133,11 +133,11 @@
133
133
  "pretty-ms": "^9.2.0",
134
134
  "simple-git": "^3.30.0",
135
135
  "yaml": "^2.8.2",
136
- "@lumenflow/metrics": "2.2.1",
137
- "@lumenflow/core": "2.2.1",
138
- "@lumenflow/memory": "2.2.1",
139
- "@lumenflow/initiatives": "2.2.1",
140
- "@lumenflow/agent": "2.2.1"
136
+ "@lumenflow/core": "2.2.2",
137
+ "@lumenflow/metrics": "2.2.2",
138
+ "@lumenflow/memory": "2.2.2",
139
+ "@lumenflow/initiatives": "2.2.2",
140
+ "@lumenflow/agent": "2.2.2"
141
141
  },
142
142
  "devDependencies": {
143
143
  "@vitest/coverage-v8": "^4.0.17",