@lumenflow/cli 2.15.2 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-issues-query.js +5 -5
- package/dist/agent-issues-query.js.map +1 -1
- package/dist/agent-log-issue.js +5 -4
- package/dist/agent-log-issue.js.map +1 -1
- package/dist/agent-session-end.js +3 -2
- package/dist/agent-session-end.js.map +1 -1
- package/dist/agent-session.js +3 -2
- package/dist/agent-session.js.map +1 -1
- package/dist/backlog-prune.js +5 -5
- package/dist/backlog-prune.js.map +1 -1
- package/dist/cli-entry-point.js +56 -3
- package/dist/cli-entry-point.js.map +1 -1
- package/dist/commands/integrate.js +4 -5
- package/dist/commands/integrate.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/deps-add.js +65 -24
- package/dist/deps-add.js.map +1 -1
- package/dist/deps-remove.js +19 -11
- package/dist/deps-remove.js.map +1 -1
- package/dist/docs-sync.js +3 -2
- package/dist/docs-sync.js.map +1 -1
- package/dist/doctor.js +9 -4
- package/dist/doctor.js.map +1 -1
- package/dist/file-delete.js +1 -1
- package/dist/file-delete.js.map +1 -1
- package/dist/file-edit.js +1 -1
- package/dist/file-edit.js.map +1 -1
- package/dist/file-read.js +1 -1
- package/dist/file-read.js.map +1 -1
- package/dist/file-write.js +2 -2
- package/dist/file-write.js.map +1 -1
- package/dist/flow-bottlenecks.js +5 -6
- package/dist/flow-bottlenecks.js.map +1 -1
- package/dist/flow-report.js +4 -5
- package/dist/flow-report.js.map +1 -1
- package/dist/gate-defaults.js +149 -0
- package/dist/gate-defaults.js.map +1 -0
- package/dist/gate-registry.js +82 -0
- package/dist/gate-registry.js.map +1 -0
- package/dist/gates-types.js +10 -0
- package/dist/gates-types.js.map +1 -0
- package/dist/gates.js +161 -234
- package/dist/gates.js.map +1 -1
- package/dist/git-branch.js +1 -1
- package/dist/git-branch.js.map +1 -1
- package/dist/git-diff.js +1 -1
- package/dist/git-diff.js.map +1 -1
- package/dist/git-log.js +1 -1
- package/dist/git-log.js.map +1 -1
- package/dist/git-status.js +1 -1
- package/dist/git-status.js.map +1 -1
- package/dist/guard-locked.js +6 -7
- package/dist/guard-locked.js.map +1 -1
- package/dist/guard-main-branch.js +3 -2
- package/dist/guard-main-branch.js.map +1 -1
- package/dist/guard-worktree-commit.js +4 -5
- package/dist/guard-worktree-commit.js.map +1 -1
- package/dist/hooks/enforcement-generator.js +1 -1
- package/dist/hooks/enforcement-generator.js.map +1 -1
- package/dist/init.js +14 -6
- package/dist/init.js.map +1 -1
- package/dist/initiative-add-wu.js +12 -12
- package/dist/initiative-add-wu.js.map +1 -1
- package/dist/initiative-bulk-assign-wus.js +8 -9
- package/dist/initiative-bulk-assign-wus.js.map +1 -1
- package/dist/initiative-create.js +11 -11
- package/dist/initiative-create.js.map +1 -1
- package/dist/initiative-edit.js +9 -9
- package/dist/initiative-edit.js.map +1 -1
- package/dist/initiative-list.js +4 -4
- package/dist/initiative-list.js.map +1 -1
- package/dist/initiative-plan.js +12 -12
- package/dist/initiative-plan.js.map +1 -1
- package/dist/initiative-remove-wu.js +11 -11
- package/dist/initiative-remove-wu.js.map +1 -1
- package/dist/initiative-status.js +6 -6
- package/dist/initiative-status.js.map +1 -1
- package/dist/lane-health.js +2 -2
- package/dist/lane-health.js.map +1 -1
- package/dist/lane-suggest.js +2 -2
- package/dist/lane-suggest.js.map +1 -1
- package/dist/lifecycle-regression-harness.js +5 -5
- package/dist/lifecycle-regression-harness.js.map +1 -1
- package/dist/lumenflow-upgrade.js +4 -4
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/mem-checkpoint.js +8 -7
- package/dist/mem-checkpoint.js.map +1 -1
- package/dist/mem-cleanup.js +12 -14
- package/dist/mem-cleanup.js.map +1 -1
- package/dist/mem-context.js +8 -7
- package/dist/mem-context.js.map +1 -1
- package/dist/mem-create.js +9 -8
- package/dist/mem-create.js.map +1 -1
- package/dist/mem-delete.js +8 -7
- package/dist/mem-delete.js.map +1 -1
- package/dist/mem-export.js +9 -8
- package/dist/mem-export.js.map +1 -1
- package/dist/mem-inbox.js +40 -16
- package/dist/mem-inbox.js.map +1 -1
- package/dist/mem-index.js +8 -7
- package/dist/mem-index.js.map +1 -1
- package/dist/mem-init.js +8 -7
- package/dist/mem-init.js.map +1 -1
- package/dist/mem-profile.js +8 -7
- package/dist/mem-profile.js.map +1 -1
- package/dist/mem-promote.js +8 -7
- package/dist/mem-promote.js.map +1 -1
- package/dist/mem-ready.js +9 -8
- package/dist/mem-ready.js.map +1 -1
- package/dist/mem-recover.js +8 -7
- package/dist/mem-recover.js.map +1 -1
- package/dist/mem-signal.js +9 -8
- package/dist/mem-signal.js.map +1 -1
- package/dist/mem-start.js +8 -7
- package/dist/mem-start.js.map +1 -1
- package/dist/mem-summarize.js +8 -7
- package/dist/mem-summarize.js.map +1 -1
- package/dist/mem-triage.js +8 -7
- package/dist/mem-triage.js.map +1 -1
- package/dist/metrics-cli.js +5 -6
- package/dist/metrics-cli.js.map +1 -1
- package/dist/metrics-snapshot.js +6 -6
- package/dist/metrics-snapshot.js.map +1 -1
- package/dist/onboarding-smoke-test.js +3 -0
- package/dist/onboarding-smoke-test.js.map +1 -1
- package/dist/orchestrate-init-status.js +2 -2
- package/dist/orchestrate-init-status.js.map +1 -1
- package/dist/orchestrate-initiative.js +2 -2
- package/dist/orchestrate-initiative.js.map +1 -1
- package/dist/orchestrate-monitor.js +8 -1
- package/dist/orchestrate-monitor.js.map +1 -1
- package/dist/plan-create.js +8 -8
- package/dist/plan-create.js.map +1 -1
- package/dist/plan-edit.js +7 -7
- package/dist/plan-edit.js.map +1 -1
- package/dist/plan-link.js +9 -9
- package/dist/plan-link.js.map +1 -1
- package/dist/plan-promote.js +8 -8
- package/dist/plan-promote.js.map +1 -1
- package/dist/release.js +10 -9
- package/dist/release.js.map +1 -1
- package/dist/rotate-progress.js +4 -3
- package/dist/rotate-progress.js.map +1 -1
- package/dist/session-coordinator.js +2 -2
- package/dist/session-coordinator.js.map +1 -1
- package/dist/signal-cleanup.js +10 -13
- package/dist/signal-cleanup.js.map +1 -1
- package/dist/spawn-list.js +9 -10
- package/dist/spawn-list.js.map +1 -1
- package/dist/state-bootstrap.js +3 -3
- package/dist/state-bootstrap.js.map +1 -1
- package/dist/state-cleanup.js +13 -16
- package/dist/state-cleanup.js.map +1 -1
- package/dist/state-doctor-fix.js +14 -20
- package/dist/state-doctor-fix.js.map +1 -1
- package/dist/state-doctor-stamps.js +21 -0
- package/dist/state-doctor-stamps.js.map +1 -0
- package/dist/state-doctor.js +17 -26
- package/dist/state-doctor.js.map +1 -1
- package/dist/sync-templates.js +1 -0
- package/dist/sync-templates.js.map +1 -1
- package/dist/trace-gen.js +74 -10
- package/dist/trace-gen.js.map +1 -1
- package/dist/validate-agent-skills.js +6 -5
- package/dist/validate-agent-skills.js.map +1 -1
- package/dist/validate-agent-sync.js +4 -5
- package/dist/validate-agent-sync.js.map +1 -1
- package/dist/validate-backlog-sync.js +5 -6
- package/dist/validate-backlog-sync.js.map +1 -1
- package/dist/validate-skills-spec.js +6 -5
- package/dist/validate-skills-spec.js.map +1 -1
- package/dist/validate.js +6 -7
- package/dist/validate.js.map +1 -1
- package/dist/validator-defaults.js +119 -0
- package/dist/validator-defaults.js.map +1 -0
- package/dist/validator-registry.js +81 -0
- package/dist/validator-registry.js.map +1 -0
- package/dist/wu-block.js +20 -19
- package/dist/wu-block.js.map +1 -1
- package/dist/wu-claim-mode.js +1 -1
- package/dist/wu-claim-mode.js.map +1 -1
- package/dist/wu-claim.js +33 -32
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-cleanup.js +11 -11
- package/dist/wu-cleanup.js.map +1 -1
- package/dist/wu-create.js +27 -27
- package/dist/wu-create.js.map +1 -1
- package/dist/wu-delete.js +16 -17
- package/dist/wu-delete.js.map +1 -1
- package/dist/wu-deps.js +7 -7
- package/dist/wu-deps.js.map +1 -1
- package/dist/wu-done-auto-cleanup.js +19 -20
- package/dist/wu-done-auto-cleanup.js.map +1 -1
- package/dist/wu-done-check.js +3 -2
- package/dist/wu-done-check.js.map +1 -1
- package/dist/wu-done-decay.js +3 -7
- package/dist/wu-done-decay.js.map +1 -1
- package/dist/wu-done.js +124 -63
- package/dist/wu-done.js.map +1 -1
- package/dist/wu-edit.js +22 -23
- package/dist/wu-edit.js.map +1 -1
- package/dist/wu-infer-lane.js +6 -6
- package/dist/wu-infer-lane.js.map +1 -1
- package/dist/wu-preflight.js +7 -7
- package/dist/wu-preflight.js.map +1 -1
- package/dist/wu-prep.js +10 -8
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-proto.js +14 -14
- package/dist/wu-proto.js.map +1 -1
- package/dist/wu-prune.js +7 -7
- package/dist/wu-prune.js.map +1 -1
- package/dist/wu-recover.js +13 -13
- package/dist/wu-recover.js.map +1 -1
- package/dist/wu-release.js +17 -16
- package/dist/wu-release.js.map +1 -1
- package/dist/wu-repair.js +4 -4
- package/dist/wu-repair.js.map +1 -1
- package/dist/wu-spawn.js +20 -20
- package/dist/wu-spawn.js.map +1 -1
- package/dist/wu-status.js +5 -5
- package/dist/wu-status.js.map +1 -1
- package/dist/wu-unblock.js +23 -22
- package/dist/wu-unblock.js.map +1 -1
- package/dist/wu-unlock-lane.js +5 -5
- package/dist/wu-unlock-lane.js.map +1 -1
- package/dist/wu-validate.js +8 -8
- package/dist/wu-validate.js.map +1 -1
- package/package.json +7 -6
- package/templates/core/ai/onboarding/docs-generation.md.template +1 -1
package/dist/gates.js
CHANGED
|
@@ -42,37 +42,41 @@ import { execSync, spawnSync } from 'node:child_process';
|
|
|
42
42
|
import { closeSync, mkdirSync, openSync, readSync, statSync, writeSync } from 'node:fs';
|
|
43
43
|
import { access } from 'node:fs/promises';
|
|
44
44
|
import path from 'node:path';
|
|
45
|
-
import { emitGateEvent, getCurrentWU, getCurrentLane } from '@lumenflow/core/
|
|
46
|
-
import { die } from '@lumenflow/core/
|
|
45
|
+
import { emitGateEvent, getCurrentWU, getCurrentLane } from '@lumenflow/core/telemetry';
|
|
46
|
+
import { die } from '@lumenflow/core/error-handler';
|
|
47
47
|
// WU-1299: Import WU YAML reader to get code_paths for docs-only filtering
|
|
48
|
-
import { readWURaw } from '@lumenflow/core/
|
|
49
|
-
import { createWuPaths } from '@lumenflow/core/
|
|
50
|
-
import { getChangedLintableFiles, isLintableFile } from '@lumenflow/core/
|
|
51
|
-
import { buildVitestChangedArgs, isCodeFilePath } from '@lumenflow/core/
|
|
52
|
-
import {
|
|
53
|
-
import { runCoverageGate, COVERAGE_GATE_MODES } from '@lumenflow/core/
|
|
54
|
-
import { buildGatesLogPath, shouldUseGatesAgentMode, updateGatesLatestSymlink, } from '@lumenflow/core/
|
|
48
|
+
import { readWURaw } from '@lumenflow/core/wu-yaml';
|
|
49
|
+
import { createWuPaths } from '@lumenflow/core/wu-paths';
|
|
50
|
+
import { getChangedLintableFiles, isLintableFile } from '@lumenflow/core/incremental-lint';
|
|
51
|
+
import { buildVitestChangedArgs, isCodeFilePath } from '@lumenflow/core/incremental-test';
|
|
52
|
+
import { createGitForPath } from '@lumenflow/core/git-adapter';
|
|
53
|
+
import { runCoverageGate, COVERAGE_GATE_MODES } from '@lumenflow/core/coverage-gate';
|
|
54
|
+
import { buildGatesLogPath, shouldUseGatesAgentMode, updateGatesLatestSymlink, } from '@lumenflow/core/gates-agent-mode';
|
|
55
55
|
// WU-2062: Import risk detector for tiered test execution
|
|
56
|
-
import { detectRiskTier, RISK_TIERS } from '@lumenflow/core/
|
|
56
|
+
import { detectRiskTier, RISK_TIERS } from '@lumenflow/core/risk-detector';
|
|
57
57
|
// WU-2252: Import invariants runner for first-check validation
|
|
58
|
-
import { runInvariants } from '@lumenflow/core/
|
|
59
|
-
import { createWUParser } from '@lumenflow/core/
|
|
60
|
-
import { validateBacklogSync } from '@lumenflow/core/
|
|
61
|
-
import { runSupabaseDocsLinter } from '@lumenflow/core/
|
|
62
|
-
import { runSystemMapValidation } from '@lumenflow/core/
|
|
58
|
+
import { runInvariants } from '@lumenflow/core/invariants-runner';
|
|
59
|
+
import { createWUParser } from '@lumenflow/core/arg-parser';
|
|
60
|
+
import { validateBacklogSync } from '@lumenflow/core/validators/backlog-sync';
|
|
61
|
+
import { runSupabaseDocsLinter } from '@lumenflow/core/validators/supabase-docs-linter';
|
|
62
|
+
import { runSystemMapValidation } from '@lumenflow/core/system-map-validator';
|
|
63
63
|
// WU-1067: Config-driven gates support (partial implementation - unused imports removed)
|
|
64
64
|
// WU-1191: Lane health gate configuration
|
|
65
65
|
// WU-1262: Coverage config from methodology policy
|
|
66
66
|
// WU-1280: Test policy for tests_required (warn vs block on test failures)
|
|
67
67
|
// WU-1356: Configurable package manager and test commands
|
|
68
|
-
import { loadLaneHealthConfig, resolveTestPolicy, resolveGatesCommands, resolveTestRunner, } from '@lumenflow/core/
|
|
68
|
+
import { loadLaneHealthConfig, resolveTestPolicy, resolveGatesCommands, resolveTestRunner, } from '@lumenflow/core/gates-config';
|
|
69
69
|
// WU-1191: Lane health check
|
|
70
70
|
import { runLaneHealthCheck } from './lane-health.js';
|
|
71
71
|
// WU-1315: Onboarding smoke test
|
|
72
72
|
import { runOnboardingSmokeTestGate } from './onboarding-smoke-test.js';
|
|
73
|
-
import { BRANCHES, PACKAGES, PKG_MANAGER, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS,
|
|
73
|
+
import { BRANCHES, PACKAGES, PKG_MANAGER, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/wu-constants';
|
|
74
74
|
// WU-1520: Gates graceful degradation for missing optional scripts
|
|
75
|
-
import {
|
|
75
|
+
import { buildMissingScriptWarning, loadPackageJsonScripts, resolveGateAction, formatGateSummary, } from './gates-graceful-degradation.js';
|
|
76
|
+
import { runCLI } from './cli-entry-point.js';
|
|
77
|
+
// WU-1550: Gate registry for declarative gate registration
|
|
78
|
+
import { GateRegistry } from './gate-registry.js';
|
|
79
|
+
import { registerDocsOnlyGates, registerCodeGates } from './gate-defaults.js';
|
|
76
80
|
/**
|
|
77
81
|
* WU-1087: Gates-specific option definitions for createWUParser.
|
|
78
82
|
* Exported for testing and consistency with other CLI commands.
|
|
@@ -209,6 +213,7 @@ const TEST_CONFIG_PATTERNS = [
|
|
|
209
213
|
/^vitest\.config\.(ts|mts|js|mjs|cjs)$/i,
|
|
210
214
|
/^jest\.config\.(ts|js|mjs|cjs|json)$/i,
|
|
211
215
|
/^\.mocharc\.(js|json|yaml|yml)$/i,
|
|
216
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- static tsconfig pattern; no backtracking risk
|
|
212
217
|
/^tsconfig(\..+)?\.json$/i,
|
|
213
218
|
];
|
|
214
219
|
function normalizePath(filePath) {
|
|
@@ -410,19 +415,19 @@ export function loadCurrentWUCodePaths(options = {}) {
|
|
|
410
415
|
* @param options - Options including packages to test and agent log context
|
|
411
416
|
* @returns Result object with ok status and duration
|
|
412
417
|
*/
|
|
413
|
-
async function runDocsOnlyFilteredTests({ packages, agentLog, }) {
|
|
418
|
+
async function runDocsOnlyFilteredTests({ packages, agentLog, cwd = process.cwd(), }) {
|
|
414
419
|
const start = Date.now();
|
|
415
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode: !!agentLog });
|
|
420
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode: !!agentLog, cwd });
|
|
416
421
|
if (packages.length === 0) {
|
|
417
422
|
logLine('📝 docs-only mode: no packages to test, skipping');
|
|
418
423
|
return { ok: true, duration: Date.now() - start };
|
|
419
424
|
}
|
|
420
425
|
logLine(`\n> Tests (docs-only filtered: ${packages.join(', ')})\n`);
|
|
421
426
|
// WU-1356: Use configured test command with filter
|
|
422
|
-
const gatesCommands = resolveGatesCommands(
|
|
427
|
+
const gatesCommands = resolveGatesCommands(cwd);
|
|
423
428
|
// If there's a configured test_docs_only command, use it
|
|
424
429
|
if (gatesCommands.test_docs_only) {
|
|
425
|
-
const result = run(gatesCommands.test_docs_only, { agentLog });
|
|
430
|
+
const result = run(gatesCommands.test_docs_only, { agentLog, cwd });
|
|
426
431
|
return { ok: result.ok, duration: Date.now() - start };
|
|
427
432
|
}
|
|
428
433
|
// Otherwise, use the full test command with filter args
|
|
@@ -481,8 +486,8 @@ function collectPrettierListDifferent(cwd, files = []) {
|
|
|
481
486
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
482
487
|
return parsePrettierListOutput(output);
|
|
483
488
|
}
|
|
484
|
-
function emitFormatCheckGuidance({ agentLog, useAgentMode, files, }) {
|
|
485
|
-
const formattedFiles = collectPrettierListDifferent(
|
|
489
|
+
function emitFormatCheckGuidance({ agentLog, useAgentMode, files, cwd, }) {
|
|
490
|
+
const formattedFiles = collectPrettierListDifferent(cwd, files ?? []);
|
|
486
491
|
if (!formattedFiles.length)
|
|
487
492
|
return;
|
|
488
493
|
const lines = formatFormatCheckGuidance(formattedFiles);
|
|
@@ -514,8 +519,7 @@ function readLogTail(logPath, { maxLines = 40, maxBytes = 64 * 1024 } = {}) {
|
|
|
514
519
|
return '';
|
|
515
520
|
}
|
|
516
521
|
}
|
|
517
|
-
function createAgentLogContext({ wuId, lane }) {
|
|
518
|
-
const cwd = process.cwd();
|
|
522
|
+
function createAgentLogContext({ wuId, lane, cwd, }) {
|
|
519
523
|
const logPath = buildGatesLogPath({ cwd, env: process.env, wuId, lane });
|
|
520
524
|
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
521
525
|
const logFd = openSync(logPath, 'a');
|
|
@@ -532,12 +536,16 @@ function createAgentLogContext({ wuId, lane }) {
|
|
|
532
536
|
});
|
|
533
537
|
return { logPath, logFd };
|
|
534
538
|
}
|
|
535
|
-
function run(cmd, { agentLog } = {}) {
|
|
539
|
+
function run(cmd, { agentLog, cwd = process.cwd(), } = {}) {
|
|
536
540
|
const start = Date.now();
|
|
537
541
|
if (!agentLog) {
|
|
538
542
|
console.log(`\n> ${cmd}\n`);
|
|
539
543
|
try {
|
|
540
|
-
execSync(cmd, {
|
|
544
|
+
execSync(cmd, {
|
|
545
|
+
stdio: 'inherit',
|
|
546
|
+
encoding: FILE_SYSTEM.ENCODING,
|
|
547
|
+
cwd,
|
|
548
|
+
});
|
|
541
549
|
return { ok: true, duration: Date.now() - start };
|
|
542
550
|
}
|
|
543
551
|
catch {
|
|
@@ -548,17 +556,17 @@ function run(cmd, { agentLog } = {}) {
|
|
|
548
556
|
const result = spawnSync(cmd, [], {
|
|
549
557
|
shell: true,
|
|
550
558
|
stdio: ['ignore', agentLog.logFd, agentLog.logFd],
|
|
551
|
-
cwd
|
|
559
|
+
cwd,
|
|
552
560
|
encoding: FILE_SYSTEM.ENCODING,
|
|
553
561
|
});
|
|
554
562
|
return { ok: result.status === EXIT_CODES.SUCCESS, duration: Date.now() - start };
|
|
555
563
|
}
|
|
556
|
-
async function runSpecLinterGate({ agentLog, useAgentMode }) {
|
|
564
|
+
async function runSpecLinterGate({ agentLog, useAgentMode, cwd }) {
|
|
557
565
|
const start = Date.now();
|
|
558
566
|
const wuId = getCurrentWU();
|
|
559
567
|
if (wuId) {
|
|
560
568
|
const scopedCmd = pnpmCmd('wu:validate', '--id', wuId);
|
|
561
|
-
const scopedResult = run(scopedCmd, { agentLog });
|
|
569
|
+
const scopedResult = run(scopedCmd, { agentLog, cwd });
|
|
562
570
|
if (!scopedResult.ok) {
|
|
563
571
|
return { ok: false, duration: Date.now() - start };
|
|
564
572
|
}
|
|
@@ -569,7 +577,7 @@ async function runSpecLinterGate({ agentLog, useAgentMode }) {
|
|
|
569
577
|
else if (agentLog) {
|
|
570
578
|
writeSync(agentLog.logFd, '⚠️ Unable to detect current WU; skipping scoped validation.\n');
|
|
571
579
|
}
|
|
572
|
-
const globalResult = run(pnpmRun(SCRIPTS.SPEC_LINTER), { agentLog });
|
|
580
|
+
const globalResult = run(pnpmRun(SCRIPTS.SPEC_LINTER), { agentLog, cwd });
|
|
573
581
|
return { ok: globalResult.ok, duration: Date.now() - start };
|
|
574
582
|
}
|
|
575
583
|
function makeGateLogger({ agentLog, useAgentMode }) {
|
|
@@ -583,11 +591,11 @@ function makeGateLogger({ agentLog, useAgentMode }) {
|
|
|
583
591
|
}
|
|
584
592
|
};
|
|
585
593
|
}
|
|
586
|
-
async function runBacklogSyncGate({ agentLog, useAgentMode }) {
|
|
594
|
+
async function runBacklogSyncGate({ agentLog, useAgentMode, cwd }) {
|
|
587
595
|
const start = Date.now();
|
|
588
596
|
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
589
597
|
logLine('\n> Backlog sync\n');
|
|
590
|
-
const result = await validateBacklogSync({ cwd
|
|
598
|
+
const result = await validateBacklogSync({ cwd });
|
|
591
599
|
if (result.errors.length > 0) {
|
|
592
600
|
logLine('❌ Backlog sync errors:');
|
|
593
601
|
result.errors.forEach((error) => logLine(` - ${error}`));
|
|
@@ -599,11 +607,11 @@ async function runBacklogSyncGate({ agentLog, useAgentMode }) {
|
|
|
599
607
|
logLine(`Backlog sync summary: WU files=${result.wuCount}, Backlog refs=${result.backlogCount}`);
|
|
600
608
|
return { ok: result.valid, duration: Date.now() - start };
|
|
601
609
|
}
|
|
602
|
-
async function runSupabaseDocsGate({ agentLog, useAgentMode }) {
|
|
610
|
+
async function runSupabaseDocsGate({ agentLog, useAgentMode, cwd }) {
|
|
603
611
|
const start = Date.now();
|
|
604
612
|
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
605
613
|
logLine('\n> Supabase docs linter\n');
|
|
606
|
-
const result = await runSupabaseDocsLinter({ cwd
|
|
614
|
+
const result = await runSupabaseDocsLinter({ cwd, logger: { log: logLine } });
|
|
607
615
|
if (result.skipped) {
|
|
608
616
|
logLine(`⚠️ ${result.message ?? 'Supabase docs linter skipped.'}`);
|
|
609
617
|
}
|
|
@@ -616,12 +624,12 @@ async function runSupabaseDocsGate({ agentLog, useAgentMode }) {
|
|
|
616
624
|
}
|
|
617
625
|
return { ok: result.ok, duration: Date.now() - start };
|
|
618
626
|
}
|
|
619
|
-
async function runSystemMapGate({ agentLog, useAgentMode }) {
|
|
627
|
+
async function runSystemMapGate({ agentLog, useAgentMode, cwd }) {
|
|
620
628
|
const start = Date.now();
|
|
621
629
|
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
622
630
|
logLine('\n> System map validation\n');
|
|
623
631
|
const result = await runSystemMapValidation({
|
|
624
|
-
cwd
|
|
632
|
+
cwd,
|
|
625
633
|
logger: { log: logLine, warn: logLine, error: logLine },
|
|
626
634
|
});
|
|
627
635
|
if (!result.valid) {
|
|
@@ -646,7 +654,7 @@ async function runSystemMapGate({ agentLog, useAgentMode }) {
|
|
|
646
654
|
* - 'error': Fail the gate if issues detected
|
|
647
655
|
* - 'off': Skip the check entirely
|
|
648
656
|
*/
|
|
649
|
-
async function runLaneHealthGate({ agentLog, useAgentMode, mode, }) {
|
|
657
|
+
async function runLaneHealthGate({ agentLog, useAgentMode, mode, cwd, }) {
|
|
650
658
|
const start = Date.now();
|
|
651
659
|
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
652
660
|
// Skip if mode is 'off'
|
|
@@ -655,7 +663,7 @@ async function runLaneHealthGate({ agentLog, useAgentMode, mode, }) {
|
|
|
655
663
|
return { ok: true, duration: Date.now() - start };
|
|
656
664
|
}
|
|
657
665
|
logLine(`\n> Lane health check (mode: ${mode})\n`);
|
|
658
|
-
const report = runLaneHealthCheck({ projectRoot:
|
|
666
|
+
const report = runLaneHealthCheck({ projectRoot: cwd });
|
|
659
667
|
if (!report.healthy) {
|
|
660
668
|
logLine('⚠️ Lane health issues detected:');
|
|
661
669
|
if (report.overlaps.hasOverlaps) {
|
|
@@ -688,24 +696,24 @@ async function filterExistingFiles(files) {
|
|
|
688
696
|
}));
|
|
689
697
|
return existingFiles.filter((file) => Boolean(file));
|
|
690
698
|
}
|
|
691
|
-
async function runFormatCheckGate({ agentLog, useAgentMode }) {
|
|
699
|
+
async function runFormatCheckGate({ agentLog, useAgentMode, cwd }) {
|
|
692
700
|
const start = Date.now();
|
|
693
701
|
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
694
702
|
let git;
|
|
695
703
|
let isMainBranch = false;
|
|
696
704
|
try {
|
|
697
|
-
git =
|
|
705
|
+
git = createGitForPath(cwd);
|
|
698
706
|
const currentBranch = await git.getCurrentBranch();
|
|
699
707
|
isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
|
|
700
708
|
}
|
|
701
709
|
catch (error) {
|
|
702
710
|
logLine(`⚠️ Failed to determine branch for format check: ${error.message}`);
|
|
703
|
-
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog });
|
|
711
|
+
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd });
|
|
704
712
|
return { ...result, duration: Date.now() - start, fileCount: -1 };
|
|
705
713
|
}
|
|
706
714
|
if (isMainBranch) {
|
|
707
715
|
logLine('📋 On main branch - running full format check');
|
|
708
|
-
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog });
|
|
716
|
+
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd });
|
|
709
717
|
return { ...result, duration: Date.now() - start, fileCount: -1 };
|
|
710
718
|
}
|
|
711
719
|
let changedFiles = [];
|
|
@@ -730,7 +738,7 @@ async function runFormatCheckGate({ agentLog, useAgentMode }) {
|
|
|
730
738
|
? ' (file list unavailable)'
|
|
731
739
|
: '';
|
|
732
740
|
logLine(`📋 Running full format check${reason}`);
|
|
733
|
-
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog });
|
|
741
|
+
const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog, cwd });
|
|
734
742
|
return { ...result, duration: Date.now() - start, fileCount: -1 };
|
|
735
743
|
}
|
|
736
744
|
const existingFiles = await filterExistingFiles(plan.files);
|
|
@@ -740,7 +748,7 @@ async function runFormatCheckGate({ agentLog, useAgentMode }) {
|
|
|
740
748
|
return { ok: true, duration: Date.now() - start, fileCount: 0, filesChecked: [] };
|
|
741
749
|
}
|
|
742
750
|
logLine(`\n> format:check (incremental: ${existingFiles.length} files)\n`);
|
|
743
|
-
const result = run(buildPrettierCheckCommand(existingFiles), { agentLog });
|
|
751
|
+
const result = run(buildPrettierCheckCommand(existingFiles), { agentLog, cwd });
|
|
744
752
|
return {
|
|
745
753
|
...result,
|
|
746
754
|
duration: Date.now() - start,
|
|
@@ -753,7 +761,7 @@ async function runFormatCheckGate({ agentLog, useAgentMode }) {
|
|
|
753
761
|
* Falls back to full lint if on main branch or if incremental fails
|
|
754
762
|
* @returns {{ ok: boolean, duration: number, fileCount: number }}
|
|
755
763
|
*/
|
|
756
|
-
async function runIncrementalLint({ agentLog,
|
|
764
|
+
async function runIncrementalLint({ agentLog, cwd, }) {
|
|
757
765
|
const start = Date.now();
|
|
758
766
|
const logLine = (line) => {
|
|
759
767
|
if (!agentLog) {
|
|
@@ -764,12 +772,12 @@ async function runIncrementalLint({ agentLog, } = {}) {
|
|
|
764
772
|
};
|
|
765
773
|
try {
|
|
766
774
|
// Check if we're on main branch
|
|
767
|
-
const git =
|
|
775
|
+
const git = createGitForPath(cwd);
|
|
768
776
|
const currentBranch = await git.getCurrentBranch();
|
|
769
777
|
const isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
|
|
770
778
|
if (isMainBranch) {
|
|
771
779
|
logLine('📋 On main branch - running full lint');
|
|
772
|
-
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog });
|
|
780
|
+
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
|
|
773
781
|
return { ...result, fileCount: -1 };
|
|
774
782
|
}
|
|
775
783
|
const changedFiles = await getChangedLintableFiles({ git });
|
|
@@ -781,7 +789,7 @@ async function runIncrementalLint({ agentLog, } = {}) {
|
|
|
781
789
|
}
|
|
782
790
|
if (plan.mode === 'full') {
|
|
783
791
|
logLine('📋 Running full lint (incremental plan forced full)');
|
|
784
|
-
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog });
|
|
792
|
+
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
|
|
785
793
|
return { ...result, fileCount: -1 };
|
|
786
794
|
}
|
|
787
795
|
const existingFiles = await filterExistingFiles(plan.files);
|
|
@@ -808,12 +816,12 @@ async function runIncrementalLint({ agentLog, } = {}) {
|
|
|
808
816
|
? {
|
|
809
817
|
stdio: ['ignore', agentLog.logFd, agentLog.logFd],
|
|
810
818
|
encoding: FILE_SYSTEM.ENCODING,
|
|
811
|
-
cwd
|
|
819
|
+
cwd,
|
|
812
820
|
}
|
|
813
821
|
: {
|
|
814
822
|
stdio: 'inherit',
|
|
815
823
|
encoding: FILE_SYSTEM.ENCODING,
|
|
816
|
-
cwd
|
|
824
|
+
cwd,
|
|
817
825
|
});
|
|
818
826
|
const duration = Date.now() - start;
|
|
819
827
|
return {
|
|
@@ -824,7 +832,7 @@ async function runIncrementalLint({ agentLog, } = {}) {
|
|
|
824
832
|
}
|
|
825
833
|
catch (error) {
|
|
826
834
|
console.error('⚠️ Incremental lint failed, falling back to full lint:', error.message);
|
|
827
|
-
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog });
|
|
835
|
+
const result = run(pnpmCmd(SCRIPTS.LINT), { agentLog, cwd });
|
|
828
836
|
return { ...result, fileCount: -1 };
|
|
829
837
|
}
|
|
830
838
|
}
|
|
@@ -835,7 +843,7 @@ async function runIncrementalLint({ agentLog, } = {}) {
|
|
|
835
843
|
*
|
|
836
844
|
* @returns {{ ok: boolean, duration: number, isIncremental: boolean }}
|
|
837
845
|
*/
|
|
838
|
-
async function runChangedTests({ agentLog,
|
|
846
|
+
async function runChangedTests({ agentLog, cwd, }) {
|
|
839
847
|
const start = Date.now();
|
|
840
848
|
// eslint-disable-next-line sonarjs/no-identical-functions -- Pre-existing: logLine helper duplicated across gate runners
|
|
841
849
|
const logLine = (line) => {
|
|
@@ -846,15 +854,15 @@ async function runChangedTests({ agentLog, } = {}) {
|
|
|
846
854
|
writeSync(agentLog.logFd, `${line}\n`);
|
|
847
855
|
};
|
|
848
856
|
// WU-1356: Get configured commands
|
|
849
|
-
const gatesCommands = resolveGatesCommands(
|
|
850
|
-
const testRunner = resolveTestRunner(
|
|
857
|
+
const gatesCommands = resolveGatesCommands(cwd);
|
|
858
|
+
const testRunner = resolveTestRunner(cwd);
|
|
851
859
|
try {
|
|
852
|
-
const git =
|
|
860
|
+
const git = createGitForPath(cwd);
|
|
853
861
|
const currentBranch = await git.getCurrentBranch();
|
|
854
862
|
const isMainBranch = currentBranch === BRANCHES.MAIN || currentBranch === BRANCHES.MASTER;
|
|
855
863
|
if (isMainBranch) {
|
|
856
864
|
logLine('📋 On main branch - running full test suite');
|
|
857
|
-
const result = run(gatesCommands.test_full, { agentLog });
|
|
865
|
+
const result = run(gatesCommands.test_full, { agentLog, cwd });
|
|
858
866
|
return { ...result, isIncremental: false };
|
|
859
867
|
}
|
|
860
868
|
let changedFiles = [];
|
|
@@ -892,29 +900,29 @@ async function runChangedTests({ agentLog, } = {}) {
|
|
|
892
900
|
logLine('⚠️ Changed file list unavailable - running full test suite');
|
|
893
901
|
}
|
|
894
902
|
logLine('📋 Running full test suite to avoid missing coverage');
|
|
895
|
-
const result = run(gatesCommands.test_full, { agentLog });
|
|
903
|
+
const result = run(gatesCommands.test_full, { agentLog, cwd });
|
|
896
904
|
return { ...result, duration: Date.now() - start, isIncremental: false };
|
|
897
905
|
}
|
|
898
906
|
// WU-1356: Use configured incremental test command
|
|
899
907
|
logLine(`\n> Running tests (${testRunner} --changed)\n`);
|
|
900
908
|
// If test_incremental is configured, use it directly
|
|
901
909
|
if (gatesCommands.test_incremental) {
|
|
902
|
-
const result = run(gatesCommands.test_incremental, { agentLog });
|
|
910
|
+
const result = run(gatesCommands.test_incremental, { agentLog, cwd });
|
|
903
911
|
return { ...result, duration: Date.now() - start, isIncremental: true };
|
|
904
912
|
}
|
|
905
913
|
// Fallback: For vitest, use the built-in changed args helper
|
|
906
914
|
if (testRunner === 'vitest') {
|
|
907
|
-
const result = run(pnpmCmd('vitest', 'run', ...buildVitestChangedArgs({ baseBranch: 'origin/main' })), { agentLog });
|
|
915
|
+
const result = run(pnpmCmd('vitest', 'run', ...buildVitestChangedArgs({ baseBranch: 'origin/main' })), { agentLog, cwd });
|
|
908
916
|
return { ...result, duration: Date.now() - start, isIncremental: true };
|
|
909
917
|
}
|
|
910
918
|
// For other runners without configured incremental, fall back to full
|
|
911
919
|
logLine('⚠️ No incremental test command configured, running full suite');
|
|
912
|
-
const result = run(gatesCommands.test_full, { agentLog });
|
|
920
|
+
const result = run(gatesCommands.test_full, { agentLog, cwd });
|
|
913
921
|
return { ...result, duration: Date.now() - start, isIncremental: false };
|
|
914
922
|
}
|
|
915
923
|
catch (error) {
|
|
916
924
|
console.error('⚠️ Changed tests failed, falling back to full suite:', error.message);
|
|
917
|
-
const result = run(gatesCommands.test_full, { agentLog });
|
|
925
|
+
const result = run(gatesCommands.test_full, { agentLog, cwd });
|
|
918
926
|
return { ...result, isIncremental: false };
|
|
919
927
|
}
|
|
920
928
|
}
|
|
@@ -953,7 +961,7 @@ const SAFETY_CRITICAL_TEST_FILES = [
|
|
|
953
961
|
* @param {object} [options.agentLog] - Agent log context
|
|
954
962
|
* @returns {Promise<{ ok: boolean, duration: number, testCount: number }>}
|
|
955
963
|
*/
|
|
956
|
-
async function runSafetyCriticalTests({ agentLog,
|
|
964
|
+
async function runSafetyCriticalTests({ agentLog, cwd, }) {
|
|
957
965
|
const start = Date.now();
|
|
958
966
|
// eslint-disable-next-line sonarjs/no-identical-functions -- Pre-existing: logLine helper duplicated across gate runners
|
|
959
967
|
const logLine = (line) => {
|
|
@@ -964,7 +972,7 @@ async function runSafetyCriticalTests({ agentLog, } = {}) {
|
|
|
964
972
|
writeSync(agentLog.logFd, `${line}\n`);
|
|
965
973
|
};
|
|
966
974
|
// WU-1006: Skip safety-critical tests if apps/web doesn't exist (repo-agnostic)
|
|
967
|
-
const webDir = path.join(
|
|
975
|
+
const webDir = path.join(cwd, DIRECTORIES.APPS_WEB);
|
|
968
976
|
try {
|
|
969
977
|
await access(webDir);
|
|
970
978
|
}
|
|
@@ -989,12 +997,12 @@ async function runSafetyCriticalTests({ agentLog, } = {}) {
|
|
|
989
997
|
? {
|
|
990
998
|
stdio: ['ignore', agentLog.logFd, agentLog.logFd],
|
|
991
999
|
encoding: FILE_SYSTEM.ENCODING,
|
|
992
|
-
cwd
|
|
1000
|
+
cwd,
|
|
993
1001
|
}
|
|
994
1002
|
: {
|
|
995
1003
|
stdio: 'inherit',
|
|
996
1004
|
encoding: FILE_SYSTEM.ENCODING,
|
|
997
|
-
cwd
|
|
1005
|
+
cwd,
|
|
998
1006
|
});
|
|
999
1007
|
const duration = Date.now() - start;
|
|
1000
1008
|
return {
|
|
@@ -1016,7 +1024,7 @@ async function runSafetyCriticalTests({ agentLog, } = {}) {
|
|
|
1016
1024
|
* @param {object} [options.agentLog] - Agent log context
|
|
1017
1025
|
* @returns {Promise<{ ok: boolean, duration: number }>}
|
|
1018
1026
|
*/
|
|
1019
|
-
async function runIntegrationTests({ agentLog,
|
|
1027
|
+
async function runIntegrationTests({ agentLog, cwd, }) {
|
|
1020
1028
|
const start = Date.now();
|
|
1021
1029
|
// eslint-disable-next-line sonarjs/no-identical-functions -- Pre-existing: logLine helper duplicated across gate runners
|
|
1022
1030
|
const logLine = (line) => {
|
|
@@ -1030,7 +1038,7 @@ async function runIntegrationTests({ agentLog, } = {}) {
|
|
|
1030
1038
|
logLine('\n> Integration tests (high-risk changes detected)\n');
|
|
1031
1039
|
// WU-1415: vitest doesn't support --include flag
|
|
1032
1040
|
// Pass glob patterns as positional arguments instead
|
|
1033
|
-
const result = run(`RUN_INTEGRATION_TESTS=1 ${pnpmCmd('vitest', 'run', "'**/*.integration.*'", "'**/golden-*.test.*'")}`, { agentLog });
|
|
1041
|
+
const result = run(`RUN_INTEGRATION_TESTS=1 ${pnpmCmd('vitest', 'run', "'**/*.integration.*'", "'**/golden-*.test.*'")}`, { agentLog, cwd });
|
|
1034
1042
|
const duration = Date.now() - start;
|
|
1035
1043
|
return {
|
|
1036
1044
|
ok: result.ok,
|
|
@@ -1062,7 +1070,7 @@ async function getChangedFilesForIncremental({ git, baseBranch = 'origin/main',
|
|
|
1062
1070
|
return [...new Set([...committedFiles, ...unstagedFiles, ...untrackedFiles])];
|
|
1063
1071
|
}
|
|
1064
1072
|
async function getAllChangedFiles(options = {}) {
|
|
1065
|
-
const { git =
|
|
1073
|
+
const { git = createGitForPath(options.cwd ?? process.cwd()) } = options;
|
|
1066
1074
|
try {
|
|
1067
1075
|
return await getChangedFilesForIncremental({ git });
|
|
1068
1076
|
}
|
|
@@ -1072,41 +1080,30 @@ async function getAllChangedFiles(options = {}) {
|
|
|
1072
1080
|
}
|
|
1073
1081
|
}
|
|
1074
1082
|
/**
|
|
1075
|
-
*
|
|
1076
|
-
* The executeGates function and ~30 sub-functions use process.cwd() extensively.
|
|
1077
|
-
* A full refactor to pass explicit cwd through all gate functions would be a separate WU.
|
|
1078
|
-
* The try/finally pattern here is safe: chdir is always restored even on error.
|
|
1083
|
+
* Run gates for a specific working directory without mutating global process cwd.
|
|
1079
1084
|
*/
|
|
1080
1085
|
export async function runGates(options = {}) {
|
|
1081
|
-
const originalCwd = process.cwd();
|
|
1082
|
-
const targetCwd = options.cwd ?? originalCwd;
|
|
1083
|
-
if (targetCwd !== originalCwd) {
|
|
1084
|
-
process.chdir(targetCwd);
|
|
1085
|
-
}
|
|
1086
1086
|
try {
|
|
1087
1087
|
return await executeGates({
|
|
1088
1088
|
...options,
|
|
1089
|
+
cwd: options.cwd ?? process.cwd(),
|
|
1089
1090
|
coverageMode: options.coverageMode ?? COVERAGE_GATE_MODES.BLOCK,
|
|
1090
1091
|
});
|
|
1091
1092
|
}
|
|
1092
1093
|
catch {
|
|
1093
1094
|
return false;
|
|
1094
1095
|
}
|
|
1095
|
-
finally {
|
|
1096
|
-
if (targetCwd !== originalCwd) {
|
|
1097
|
-
process.chdir(originalCwd);
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
1096
|
}
|
|
1101
1097
|
// Main execution
|
|
1102
1098
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing: main() orchestrates multi-step gate workflow
|
|
1103
1099
|
async function executeGates(opts) {
|
|
1100
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
1104
1101
|
const argv = opts.argv ?? process.argv.slice(2);
|
|
1105
1102
|
// Get context for telemetry
|
|
1106
1103
|
const wu_id = getCurrentWU();
|
|
1107
1104
|
const lane = getCurrentLane();
|
|
1108
1105
|
const useAgentMode = shouldUseGatesAgentMode({ argv, env: process.env });
|
|
1109
|
-
const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : null;
|
|
1106
|
+
const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane, cwd }) : null;
|
|
1110
1107
|
// Parse command line arguments (now via Commander)
|
|
1111
1108
|
const isDocsOnly = opts.docsOnly || false;
|
|
1112
1109
|
const isFullLint = opts.fullLint || false;
|
|
@@ -1116,7 +1113,7 @@ async function executeGates(opts) {
|
|
|
1116
1113
|
// WU-1262: Resolve coverage config from methodology policy
|
|
1117
1114
|
// This derives coverage threshold and mode from methodology.testing setting
|
|
1118
1115
|
// WU-1280: Use resolveTestPolicy to also get tests_required for test failure handling
|
|
1119
|
-
const resolvedTestPolicy = resolveTestPolicy(
|
|
1116
|
+
const resolvedTestPolicy = resolveTestPolicy(cwd);
|
|
1120
1117
|
// WU-1433: Coverage gate mode (warn or block)
|
|
1121
1118
|
// WU-2334: Default changed from WARN to BLOCK for TDD enforcement
|
|
1122
1119
|
// WU-1262: CLI flag overrides resolved policy, which overrides methodology defaults
|
|
@@ -1126,12 +1123,12 @@ async function executeGates(opts) {
|
|
|
1126
1123
|
// When tests_required=false (methodology.testing: none), test failures produce warnings only
|
|
1127
1124
|
const testsRequired = resolvedTestPolicy.tests_required;
|
|
1128
1125
|
// WU-1191: Lane health gate mode (warn, error, or off)
|
|
1129
|
-
const laneHealthMode = loadLaneHealthConfig(
|
|
1126
|
+
const laneHealthMode = loadLaneHealthConfig(cwd);
|
|
1130
1127
|
// WU-1356: Resolve configured gates commands for test execution
|
|
1131
|
-
const configuredGatesCommands = resolveGatesCommands(
|
|
1128
|
+
const configuredGatesCommands = resolveGatesCommands(cwd);
|
|
1132
1129
|
// WU-1520: Strict mode and script existence checking for graceful degradation
|
|
1133
1130
|
const isStrict = opts.strict || false;
|
|
1134
|
-
const packageJsonScripts = loadPackageJsonScripts(
|
|
1131
|
+
const packageJsonScripts = loadPackageJsonScripts(cwd);
|
|
1135
1132
|
// WU-1520: Track gate results for summary
|
|
1136
1133
|
const gateResults = [];
|
|
1137
1134
|
if (useAgentMode) {
|
|
@@ -1142,7 +1139,7 @@ async function executeGates(opts) {
|
|
|
1142
1139
|
let changedFiles = [];
|
|
1143
1140
|
if (!isDocsOnly) {
|
|
1144
1141
|
try {
|
|
1145
|
-
changedFiles = await getAllChangedFiles();
|
|
1142
|
+
changedFiles = await getAllChangedFiles({ cwd });
|
|
1146
1143
|
riskTier = detectRiskTier({ changedFiles });
|
|
1147
1144
|
const logLine = useAgentMode
|
|
1148
1145
|
? (line) => writeSync(agentLog.logFd, `${line}\n`)
|
|
@@ -1168,132 +1165,62 @@ async function executeGates(opts) {
|
|
|
1168
1165
|
// WU-1299: Load code_paths and compute docs-only test plan
|
|
1169
1166
|
let docsOnlyTestPlan = null;
|
|
1170
1167
|
if (effectiveDocsOnly) {
|
|
1171
|
-
const codePaths = loadCurrentWUCodePaths({ cwd
|
|
1168
|
+
const codePaths = loadCurrentWUCodePaths({ cwd });
|
|
1172
1169
|
docsOnlyTestPlan = resolveDocsOnlyTestPlan({ codePaths });
|
|
1173
1170
|
}
|
|
1174
|
-
//
|
|
1171
|
+
// WU-1550: Build gate list via GateRegistry (declarative, Open-Closed Principle)
|
|
1172
|
+
// New gates can be added by calling registry.register() without modifying this function.
|
|
1175
1173
|
// WU-2252: Invariants gate runs FIRST and is included in both docs-only and regular modes
|
|
1176
1174
|
// WU-1520: scriptName field maps gates to their package.json script for existence checking
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
: [
|
|
1227
|
-
// WU-2252: Invariants check runs first (non-bypassable)
|
|
1228
|
-
{ name: GATE_NAMES.INVARIANTS, cmd: GATE_COMMANDS.INVARIANTS },
|
|
1229
|
-
{
|
|
1230
|
-
name: GATE_NAMES.FORMAT_CHECK,
|
|
1231
|
-
run: runFormatCheckGate,
|
|
1232
|
-
scriptName: SCRIPTS.FORMAT_CHECK,
|
|
1233
|
-
},
|
|
1234
|
-
{
|
|
1235
|
-
name: GATE_NAMES.LINT,
|
|
1236
|
-
cmd: isFullLint ? pnpmCmd(SCRIPTS.LINT) : GATE_COMMANDS.INCREMENTAL,
|
|
1237
|
-
scriptName: SCRIPTS.LINT,
|
|
1238
|
-
},
|
|
1239
|
-
{
|
|
1240
|
-
name: GATE_NAMES.TYPECHECK,
|
|
1241
|
-
cmd: pnpmCmd(SCRIPTS.TYPECHECK),
|
|
1242
|
-
scriptName: SCRIPTS.TYPECHECK,
|
|
1243
|
-
},
|
|
1244
|
-
{ name: GATE_NAMES.SPEC_LINTER, run: runSpecLinterGate, scriptName: SCRIPTS.SPEC_LINTER },
|
|
1245
|
-
// WU-1467: prompts:lint removed -- was a stub (exit 0), not an authoritative gate
|
|
1246
|
-
{ name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
|
|
1247
|
-
{ name: GATE_NAMES.SUPABASE_DOCS_LINTER, run: runSupabaseDocsGate },
|
|
1248
|
-
// WU-2315: System map validation (warn-only until orphan docs are indexed)
|
|
1249
|
-
{
|
|
1250
|
-
name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
|
|
1251
|
-
run: runSystemMapGate,
|
|
1252
|
-
warnOnly: true,
|
|
1253
|
-
},
|
|
1254
|
-
// WU-1191: Lane health check (configurable: warn/error/off)
|
|
1255
|
-
{
|
|
1256
|
-
name: GATE_NAMES.LANE_HEALTH,
|
|
1257
|
-
run: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode }),
|
|
1258
|
-
warnOnly: laneHealthMode !== 'error',
|
|
1259
|
-
},
|
|
1260
|
-
// WU-1315: Onboarding smoke test (init + wu:create validation)
|
|
1261
|
-
{
|
|
1262
|
-
name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
|
|
1263
|
-
cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST,
|
|
1264
|
-
},
|
|
1265
|
-
// WU-2062: Safety-critical tests ALWAYS run
|
|
1266
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1267
|
-
{
|
|
1268
|
-
name: GATE_NAMES.SAFETY_CRITICAL_TEST,
|
|
1269
|
-
cmd: GATE_COMMANDS.SAFETY_CRITICAL_TEST,
|
|
1270
|
-
warnOnly: !testsRequired,
|
|
1271
|
-
},
|
|
1272
|
-
// WU-1920: Use changed tests by default, full suite with --full-tests
|
|
1273
|
-
// WU-2244: --full-coverage implies --full-tests for accurate coverage
|
|
1274
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1275
|
-
// WU-1356: Use configured test command instead of hard-coded turbo
|
|
1276
|
-
{
|
|
1277
|
-
name: GATE_NAMES.TEST,
|
|
1278
|
-
cmd: isFullTests || isFullCoverage
|
|
1279
|
-
? configuredGatesCommands.test_full
|
|
1280
|
-
: GATE_COMMANDS.INCREMENTAL_TEST,
|
|
1281
|
-
warnOnly: !testsRequired,
|
|
1282
|
-
},
|
|
1283
|
-
// WU-2062: Integration tests only for high-risk changes
|
|
1284
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1285
|
-
...(riskTier && riskTier.shouldRunIntegration
|
|
1286
|
-
? [
|
|
1287
|
-
{
|
|
1288
|
-
name: GATE_NAMES.INTEGRATION_TEST,
|
|
1289
|
-
cmd: GATE_COMMANDS.TIERED_TEST,
|
|
1290
|
-
warnOnly: !testsRequired,
|
|
1291
|
-
},
|
|
1292
|
-
]
|
|
1293
|
-
: []),
|
|
1294
|
-
// WU-1433: Coverage gate with configurable mode (warn/block)
|
|
1295
|
-
{ name: GATE_NAMES.COVERAGE, cmd: GATE_COMMANDS.COVERAGE_GATE },
|
|
1296
|
-
];
|
|
1175
|
+
const gateRegistry = new GateRegistry();
|
|
1176
|
+
if (effectiveDocsOnly) {
|
|
1177
|
+
registerDocsOnlyGates(gateRegistry, {
|
|
1178
|
+
laneHealthMode,
|
|
1179
|
+
testsRequired,
|
|
1180
|
+
docsOnlyTestPlan,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
registerCodeGates(gateRegistry, {
|
|
1185
|
+
isFullLint,
|
|
1186
|
+
isFullTests,
|
|
1187
|
+
isFullCoverage,
|
|
1188
|
+
laneHealthMode,
|
|
1189
|
+
testsRequired,
|
|
1190
|
+
shouldRunIntegration: !!(riskTier && riskTier.shouldRunIntegration),
|
|
1191
|
+
configuredTestFullCmd: configuredGatesCommands.test_full,
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
// WU-1550: Inject run functions for gates that need them.
|
|
1195
|
+
// The registry stores declarative metadata; run functions are bound here
|
|
1196
|
+
// because they depend on local gate-runner functions in this module.
|
|
1197
|
+
const gateRunFunctions = {
|
|
1198
|
+
[GATE_NAMES.FORMAT_CHECK]: runFormatCheckGate,
|
|
1199
|
+
[GATE_NAMES.SPEC_LINTER]: runSpecLinterGate,
|
|
1200
|
+
[GATE_NAMES.BACKLOG_SYNC]: runBacklogSyncGate,
|
|
1201
|
+
[GATE_NAMES.SUPABASE_DOCS_LINTER]: runSupabaseDocsGate,
|
|
1202
|
+
[GATE_NAMES.SYSTEM_MAP_VALIDATE]: runSystemMapGate,
|
|
1203
|
+
[GATE_NAMES.LANE_HEALTH]: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode }),
|
|
1204
|
+
};
|
|
1205
|
+
// WU-1299: Docs-only filtered tests get a custom run function
|
|
1206
|
+
if (docsOnlyTestPlan && docsOnlyTestPlan.mode === 'filtered') {
|
|
1207
|
+
gateRunFunctions[GATE_NAMES.TEST] = (ctx) => {
|
|
1208
|
+
const pkgs = docsOnlyTestPlan.packages;
|
|
1209
|
+
return runDocsOnlyFilteredTests({
|
|
1210
|
+
packages: pkgs,
|
|
1211
|
+
agentLog: ctx.agentLog,
|
|
1212
|
+
cwd: ctx.cwd,
|
|
1213
|
+
});
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
// Apply run functions to registered gates
|
|
1217
|
+
const gates = gateRegistry.getAll().map((gate) => {
|
|
1218
|
+
const runFn = gateRunFunctions[gate.name];
|
|
1219
|
+
if (runFn && !gate.run) {
|
|
1220
|
+
return { ...gate, run: runFn };
|
|
1221
|
+
}
|
|
1222
|
+
return gate;
|
|
1223
|
+
});
|
|
1297
1224
|
if (effectiveDocsOnly) {
|
|
1298
1225
|
// WU-1299: Show clear messaging about what's being skipped/run in docs-only mode
|
|
1299
1226
|
const docsOnlyMessage = docsOnlyTestPlan && docsOnlyTestPlan.mode === 'filtered'
|
|
@@ -1316,7 +1243,7 @@ async function executeGates(opts) {
|
|
|
1316
1243
|
const gateScriptName = gate.scriptName ?? null;
|
|
1317
1244
|
const gateAction = resolveGateAction(gate.name, gateScriptName, packageJsonScripts, isStrict);
|
|
1318
1245
|
if (gateAction === 'skip') {
|
|
1319
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1246
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1320
1247
|
const warningMsg = buildMissingScriptWarning(gateScriptName);
|
|
1321
1248
|
logLine(`\n${warningMsg}\n`);
|
|
1322
1249
|
gateResults.push({
|
|
@@ -1328,7 +1255,7 @@ async function executeGates(opts) {
|
|
|
1328
1255
|
continue;
|
|
1329
1256
|
}
|
|
1330
1257
|
if (gateAction === 'fail') {
|
|
1331
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1258
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1332
1259
|
logLine(`\n❌ "${gateScriptName}" script not found in package.json (--strict mode)\n`);
|
|
1333
1260
|
gateResults.push({
|
|
1334
1261
|
name: gate.name,
|
|
@@ -1339,7 +1266,7 @@ async function executeGates(opts) {
|
|
|
1339
1266
|
die(`${gate.name} failed: missing script "${gateScriptName}" in package.json (--strict mode requires all gate scripts)`);
|
|
1340
1267
|
}
|
|
1341
1268
|
if (gate.run) {
|
|
1342
|
-
result = await gate.run({ agentLog, useAgentMode });
|
|
1269
|
+
result = await gate.run({ agentLog, useAgentMode, cwd });
|
|
1343
1270
|
if (gate.name === GATE_NAMES.FORMAT_CHECK) {
|
|
1344
1271
|
lastFormatCheckFiles = result.filesChecked ?? null;
|
|
1345
1272
|
}
|
|
@@ -1350,7 +1277,7 @@ async function executeGates(opts) {
|
|
|
1350
1277
|
? (line) => writeSync(agentLog.logFd, `${line}\n`)
|
|
1351
1278
|
: (line) => console.log(line);
|
|
1352
1279
|
logLine('\n> Invariants check\n');
|
|
1353
|
-
const invariantsResult = runInvariants({ baseDir:
|
|
1280
|
+
const invariantsResult = runInvariants({ baseDir: cwd, silent: false });
|
|
1354
1281
|
result = {
|
|
1355
1282
|
ok: invariantsResult.success,
|
|
1356
1283
|
duration: 0, // runInvariants doesn't track duration
|
|
@@ -1362,20 +1289,20 @@ async function executeGates(opts) {
|
|
|
1362
1289
|
}
|
|
1363
1290
|
else if (gate.cmd === GATE_COMMANDS.INCREMENTAL) {
|
|
1364
1291
|
// Special handling for incremental lint
|
|
1365
|
-
result = await runIncrementalLint({ agentLog });
|
|
1292
|
+
result = await runIncrementalLint({ agentLog, cwd });
|
|
1366
1293
|
}
|
|
1367
1294
|
else if (gate.cmd === GATE_COMMANDS.SAFETY_CRITICAL_TEST) {
|
|
1368
1295
|
// WU-2062: Safety-critical tests always run
|
|
1369
|
-
result = await runSafetyCriticalTests({ agentLog });
|
|
1296
|
+
result = await runSafetyCriticalTests({ agentLog, cwd });
|
|
1370
1297
|
}
|
|
1371
1298
|
else if (gate.cmd === GATE_COMMANDS.INCREMENTAL_TEST) {
|
|
1372
1299
|
// WU-1920: Special handling for changed tests
|
|
1373
|
-
result = await runChangedTests({ agentLog });
|
|
1300
|
+
result = await runChangedTests({ agentLog, cwd });
|
|
1374
1301
|
lastTestResult = result;
|
|
1375
1302
|
}
|
|
1376
1303
|
else if (gate.cmd === GATE_COMMANDS.TIERED_TEST) {
|
|
1377
1304
|
// WU-2062: Integration tests for high-risk changes
|
|
1378
|
-
result = await runIntegrationTests({ agentLog });
|
|
1305
|
+
result = await runIntegrationTests({ agentLog, cwd });
|
|
1379
1306
|
}
|
|
1380
1307
|
else if (gate.cmd === GATE_COMMANDS.COVERAGE_GATE) {
|
|
1381
1308
|
// WU-1920: Skip coverage gate when tests were changed (partial coverage)
|
|
@@ -1429,7 +1356,7 @@ async function executeGates(opts) {
|
|
|
1429
1356
|
});
|
|
1430
1357
|
}
|
|
1431
1358
|
else {
|
|
1432
|
-
result = run(gate.cmd, { agentLog });
|
|
1359
|
+
result = run(gate.cmd, { agentLog, cwd });
|
|
1433
1360
|
}
|
|
1434
1361
|
// Emit telemetry event
|
|
1435
1362
|
emitGateEvent({
|
|
@@ -1458,7 +1385,7 @@ async function executeGates(opts) {
|
|
|
1458
1385
|
continue;
|
|
1459
1386
|
}
|
|
1460
1387
|
if (gate.name === GATE_NAMES.FORMAT_CHECK) {
|
|
1461
|
-
emitFormatCheckGuidance({ agentLog, useAgentMode, files: lastFormatCheckFiles });
|
|
1388
|
+
emitFormatCheckGuidance({ agentLog, useAgentMode, files: lastFormatCheckFiles, cwd });
|
|
1462
1389
|
}
|
|
1463
1390
|
// WU-1520: Track failed gate before dying
|
|
1464
1391
|
gateResults.push({
|
|
@@ -1467,7 +1394,7 @@ async function executeGates(opts) {
|
|
|
1467
1394
|
durationMs: result.duration,
|
|
1468
1395
|
});
|
|
1469
1396
|
// WU-1520: Print summary before failing
|
|
1470
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1397
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1471
1398
|
logLine(`\n${formatGateSummary(gateResults)}\n`);
|
|
1472
1399
|
if (useAgentMode) {
|
|
1473
1400
|
const tail = readLogTail(agentLog.logPath);
|
|
@@ -1487,10 +1414,10 @@ async function executeGates(opts) {
|
|
|
1487
1414
|
}
|
|
1488
1415
|
// WU-2064: Create/update gates-latest.log symlink for easy agent access
|
|
1489
1416
|
if (agentLog) {
|
|
1490
|
-
updateGatesLatestSymlink({ logPath: agentLog.logPath, cwd
|
|
1417
|
+
updateGatesLatestSymlink({ logPath: agentLog.logPath, cwd, env: process.env });
|
|
1491
1418
|
}
|
|
1492
1419
|
// WU-1520: Print gate summary showing passed/skipped/failed/warned
|
|
1493
|
-
const summaryLogLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1420
|
+
const summaryLogLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1494
1421
|
summaryLogLine(`\n${formatGateSummary(gateResults)}`);
|
|
1495
1422
|
if (!useAgentMode) {
|
|
1496
1423
|
console.log('\n✅ All gates passed!\n');
|
|
@@ -1500,18 +1427,18 @@ async function executeGates(opts) {
|
|
|
1500
1427
|
}
|
|
1501
1428
|
return true;
|
|
1502
1429
|
}
|
|
1430
|
+
// WU-1537: Wrap executeGates in a standard main() for runCLI consistency
|
|
1431
|
+
async function main() {
|
|
1432
|
+
const opts = parseGatesArgs();
|
|
1433
|
+
const ok = await executeGates({ ...opts, argv: process.argv.slice(2) });
|
|
1434
|
+
if (!ok) {
|
|
1435
|
+
process.exit(EXIT_CODES.ERROR);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1503
1438
|
// WU-1071: Use import.meta.main instead of process.argv[1] comparison
|
|
1504
1439
|
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
1505
1440
|
// path but import.meta.url resolves to the real path - they never match
|
|
1506
1441
|
if (import.meta.main) {
|
|
1507
|
-
|
|
1508
|
-
executeGates({ ...opts, argv: process.argv.slice(2) })
|
|
1509
|
-
.then((ok) => {
|
|
1510
|
-
process.exit(ok ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
|
|
1511
|
-
})
|
|
1512
|
-
.catch((error) => {
|
|
1513
|
-
console.error('Gates failed:', error);
|
|
1514
|
-
process.exit(EXIT_CODES.ERROR);
|
|
1515
|
-
});
|
|
1442
|
+
void runCLI(main);
|
|
1516
1443
|
}
|
|
1517
1444
|
//# sourceMappingURL=gates.js.map
|