@lumenflow/cli 2.15.1 → 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 +163 -230
- 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 +46 -21
- 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 +132 -66
- 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
|
}
|
|
@@ -1071,36 +1079,31 @@ async function getAllChangedFiles(options = {}) {
|
|
|
1071
1079
|
return [];
|
|
1072
1080
|
}
|
|
1073
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Run gates for a specific working directory without mutating global process cwd.
|
|
1084
|
+
*/
|
|
1074
1085
|
export async function runGates(options = {}) {
|
|
1075
|
-
const originalCwd = process.cwd();
|
|
1076
|
-
const targetCwd = options.cwd ?? originalCwd;
|
|
1077
|
-
if (targetCwd !== originalCwd) {
|
|
1078
|
-
process.chdir(targetCwd);
|
|
1079
|
-
}
|
|
1080
1086
|
try {
|
|
1081
1087
|
return await executeGates({
|
|
1082
1088
|
...options,
|
|
1089
|
+
cwd: options.cwd ?? process.cwd(),
|
|
1083
1090
|
coverageMode: options.coverageMode ?? COVERAGE_GATE_MODES.BLOCK,
|
|
1084
1091
|
});
|
|
1085
1092
|
}
|
|
1086
1093
|
catch {
|
|
1087
1094
|
return false;
|
|
1088
1095
|
}
|
|
1089
|
-
finally {
|
|
1090
|
-
if (targetCwd !== originalCwd) {
|
|
1091
|
-
process.chdir(originalCwd);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
1096
|
}
|
|
1095
1097
|
// Main execution
|
|
1096
1098
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing: main() orchestrates multi-step gate workflow
|
|
1097
1099
|
async function executeGates(opts) {
|
|
1100
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
1098
1101
|
const argv = opts.argv ?? process.argv.slice(2);
|
|
1099
1102
|
// Get context for telemetry
|
|
1100
1103
|
const wu_id = getCurrentWU();
|
|
1101
1104
|
const lane = getCurrentLane();
|
|
1102
1105
|
const useAgentMode = shouldUseGatesAgentMode({ argv, env: process.env });
|
|
1103
|
-
const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : null;
|
|
1106
|
+
const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane, cwd }) : null;
|
|
1104
1107
|
// Parse command line arguments (now via Commander)
|
|
1105
1108
|
const isDocsOnly = opts.docsOnly || false;
|
|
1106
1109
|
const isFullLint = opts.fullLint || false;
|
|
@@ -1110,7 +1113,7 @@ async function executeGates(opts) {
|
|
|
1110
1113
|
// WU-1262: Resolve coverage config from methodology policy
|
|
1111
1114
|
// This derives coverage threshold and mode from methodology.testing setting
|
|
1112
1115
|
// WU-1280: Use resolveTestPolicy to also get tests_required for test failure handling
|
|
1113
|
-
const resolvedTestPolicy = resolveTestPolicy(
|
|
1116
|
+
const resolvedTestPolicy = resolveTestPolicy(cwd);
|
|
1114
1117
|
// WU-1433: Coverage gate mode (warn or block)
|
|
1115
1118
|
// WU-2334: Default changed from WARN to BLOCK for TDD enforcement
|
|
1116
1119
|
// WU-1262: CLI flag overrides resolved policy, which overrides methodology defaults
|
|
@@ -1120,12 +1123,12 @@ async function executeGates(opts) {
|
|
|
1120
1123
|
// When tests_required=false (methodology.testing: none), test failures produce warnings only
|
|
1121
1124
|
const testsRequired = resolvedTestPolicy.tests_required;
|
|
1122
1125
|
// WU-1191: Lane health gate mode (warn, error, or off)
|
|
1123
|
-
const laneHealthMode = loadLaneHealthConfig(
|
|
1126
|
+
const laneHealthMode = loadLaneHealthConfig(cwd);
|
|
1124
1127
|
// WU-1356: Resolve configured gates commands for test execution
|
|
1125
|
-
const configuredGatesCommands = resolveGatesCommands(
|
|
1128
|
+
const configuredGatesCommands = resolveGatesCommands(cwd);
|
|
1126
1129
|
// WU-1520: Strict mode and script existence checking for graceful degradation
|
|
1127
1130
|
const isStrict = opts.strict || false;
|
|
1128
|
-
const packageJsonScripts = loadPackageJsonScripts(
|
|
1131
|
+
const packageJsonScripts = loadPackageJsonScripts(cwd);
|
|
1129
1132
|
// WU-1520: Track gate results for summary
|
|
1130
1133
|
const gateResults = [];
|
|
1131
1134
|
if (useAgentMode) {
|
|
@@ -1136,7 +1139,7 @@ async function executeGates(opts) {
|
|
|
1136
1139
|
let changedFiles = [];
|
|
1137
1140
|
if (!isDocsOnly) {
|
|
1138
1141
|
try {
|
|
1139
|
-
changedFiles = await getAllChangedFiles();
|
|
1142
|
+
changedFiles = await getAllChangedFiles({ cwd });
|
|
1140
1143
|
riskTier = detectRiskTier({ changedFiles });
|
|
1141
1144
|
const logLine = useAgentMode
|
|
1142
1145
|
? (line) => writeSync(agentLog.logFd, `${line}\n`)
|
|
@@ -1162,132 +1165,62 @@ async function executeGates(opts) {
|
|
|
1162
1165
|
// WU-1299: Load code_paths and compute docs-only test plan
|
|
1163
1166
|
let docsOnlyTestPlan = null;
|
|
1164
1167
|
if (effectiveDocsOnly) {
|
|
1165
|
-
const codePaths = loadCurrentWUCodePaths({ cwd
|
|
1168
|
+
const codePaths = loadCurrentWUCodePaths({ cwd });
|
|
1166
1169
|
docsOnlyTestPlan = resolveDocsOnlyTestPlan({ codePaths });
|
|
1167
1170
|
}
|
|
1168
|
-
//
|
|
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.
|
|
1169
1173
|
// WU-2252: Invariants gate runs FIRST and is included in both docs-only and regular modes
|
|
1170
1174
|
// WU-1520: scriptName field maps gates to their package.json script for existence checking
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
-
// WU-2252: Invariants check runs first (non-bypassable)
|
|
1222
|
-
{ name: GATE_NAMES.INVARIANTS, cmd: GATE_COMMANDS.INVARIANTS },
|
|
1223
|
-
{
|
|
1224
|
-
name: GATE_NAMES.FORMAT_CHECK,
|
|
1225
|
-
run: runFormatCheckGate,
|
|
1226
|
-
scriptName: SCRIPTS.FORMAT_CHECK,
|
|
1227
|
-
},
|
|
1228
|
-
{
|
|
1229
|
-
name: GATE_NAMES.LINT,
|
|
1230
|
-
cmd: isFullLint ? pnpmCmd(SCRIPTS.LINT) : GATE_COMMANDS.INCREMENTAL,
|
|
1231
|
-
scriptName: SCRIPTS.LINT,
|
|
1232
|
-
},
|
|
1233
|
-
{
|
|
1234
|
-
name: GATE_NAMES.TYPECHECK,
|
|
1235
|
-
cmd: pnpmCmd(SCRIPTS.TYPECHECK),
|
|
1236
|
-
scriptName: SCRIPTS.TYPECHECK,
|
|
1237
|
-
},
|
|
1238
|
-
{ name: GATE_NAMES.SPEC_LINTER, run: runSpecLinterGate, scriptName: SCRIPTS.SPEC_LINTER },
|
|
1239
|
-
// WU-1467: prompts:lint removed -- was a stub (exit 0), not an authoritative gate
|
|
1240
|
-
{ name: GATE_NAMES.BACKLOG_SYNC, run: runBacklogSyncGate },
|
|
1241
|
-
{ name: GATE_NAMES.SUPABASE_DOCS_LINTER, run: runSupabaseDocsGate },
|
|
1242
|
-
// WU-2315: System map validation (warn-only until orphan docs are indexed)
|
|
1243
|
-
{
|
|
1244
|
-
name: GATE_NAMES.SYSTEM_MAP_VALIDATE,
|
|
1245
|
-
run: runSystemMapGate,
|
|
1246
|
-
warnOnly: true,
|
|
1247
|
-
},
|
|
1248
|
-
// WU-1191: Lane health check (configurable: warn/error/off)
|
|
1249
|
-
{
|
|
1250
|
-
name: GATE_NAMES.LANE_HEALTH,
|
|
1251
|
-
run: (ctx) => runLaneHealthGate({ ...ctx, mode: laneHealthMode }),
|
|
1252
|
-
warnOnly: laneHealthMode !== 'error',
|
|
1253
|
-
},
|
|
1254
|
-
// WU-1315: Onboarding smoke test (init + wu:create validation)
|
|
1255
|
-
{
|
|
1256
|
-
name: GATE_NAMES.ONBOARDING_SMOKE_TEST,
|
|
1257
|
-
cmd: GATE_COMMANDS.ONBOARDING_SMOKE_TEST,
|
|
1258
|
-
},
|
|
1259
|
-
// WU-2062: Safety-critical tests ALWAYS run
|
|
1260
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1261
|
-
{
|
|
1262
|
-
name: GATE_NAMES.SAFETY_CRITICAL_TEST,
|
|
1263
|
-
cmd: GATE_COMMANDS.SAFETY_CRITICAL_TEST,
|
|
1264
|
-
warnOnly: !testsRequired,
|
|
1265
|
-
},
|
|
1266
|
-
// WU-1920: Use changed tests by default, full suite with --full-tests
|
|
1267
|
-
// WU-2244: --full-coverage implies --full-tests for accurate coverage
|
|
1268
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1269
|
-
// WU-1356: Use configured test command instead of hard-coded turbo
|
|
1270
|
-
{
|
|
1271
|
-
name: GATE_NAMES.TEST,
|
|
1272
|
-
cmd: isFullTests || isFullCoverage
|
|
1273
|
-
? configuredGatesCommands.test_full
|
|
1274
|
-
: GATE_COMMANDS.INCREMENTAL_TEST,
|
|
1275
|
-
warnOnly: !testsRequired,
|
|
1276
|
-
},
|
|
1277
|
-
// WU-2062: Integration tests only for high-risk changes
|
|
1278
|
-
// WU-1280: When tests_required=false (methodology.testing: none), failures only warn
|
|
1279
|
-
...(riskTier && riskTier.shouldRunIntegration
|
|
1280
|
-
? [
|
|
1281
|
-
{
|
|
1282
|
-
name: GATE_NAMES.INTEGRATION_TEST,
|
|
1283
|
-
cmd: GATE_COMMANDS.TIERED_TEST,
|
|
1284
|
-
warnOnly: !testsRequired,
|
|
1285
|
-
},
|
|
1286
|
-
]
|
|
1287
|
-
: []),
|
|
1288
|
-
// WU-1433: Coverage gate with configurable mode (warn/block)
|
|
1289
|
-
{ name: GATE_NAMES.COVERAGE, cmd: GATE_COMMANDS.COVERAGE_GATE },
|
|
1290
|
-
];
|
|
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
|
+
});
|
|
1291
1224
|
if (effectiveDocsOnly) {
|
|
1292
1225
|
// WU-1299: Show clear messaging about what's being skipped/run in docs-only mode
|
|
1293
1226
|
const docsOnlyMessage = docsOnlyTestPlan && docsOnlyTestPlan.mode === 'filtered'
|
|
@@ -1310,7 +1243,7 @@ async function executeGates(opts) {
|
|
|
1310
1243
|
const gateScriptName = gate.scriptName ?? null;
|
|
1311
1244
|
const gateAction = resolveGateAction(gate.name, gateScriptName, packageJsonScripts, isStrict);
|
|
1312
1245
|
if (gateAction === 'skip') {
|
|
1313
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1246
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1314
1247
|
const warningMsg = buildMissingScriptWarning(gateScriptName);
|
|
1315
1248
|
logLine(`\n${warningMsg}\n`);
|
|
1316
1249
|
gateResults.push({
|
|
@@ -1322,7 +1255,7 @@ async function executeGates(opts) {
|
|
|
1322
1255
|
continue;
|
|
1323
1256
|
}
|
|
1324
1257
|
if (gateAction === 'fail') {
|
|
1325
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1258
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1326
1259
|
logLine(`\n❌ "${gateScriptName}" script not found in package.json (--strict mode)\n`);
|
|
1327
1260
|
gateResults.push({
|
|
1328
1261
|
name: gate.name,
|
|
@@ -1333,7 +1266,7 @@ async function executeGates(opts) {
|
|
|
1333
1266
|
die(`${gate.name} failed: missing script "${gateScriptName}" in package.json (--strict mode requires all gate scripts)`);
|
|
1334
1267
|
}
|
|
1335
1268
|
if (gate.run) {
|
|
1336
|
-
result = await gate.run({ agentLog, useAgentMode });
|
|
1269
|
+
result = await gate.run({ agentLog, useAgentMode, cwd });
|
|
1337
1270
|
if (gate.name === GATE_NAMES.FORMAT_CHECK) {
|
|
1338
1271
|
lastFormatCheckFiles = result.filesChecked ?? null;
|
|
1339
1272
|
}
|
|
@@ -1344,7 +1277,7 @@ async function executeGates(opts) {
|
|
|
1344
1277
|
? (line) => writeSync(agentLog.logFd, `${line}\n`)
|
|
1345
1278
|
: (line) => console.log(line);
|
|
1346
1279
|
logLine('\n> Invariants check\n');
|
|
1347
|
-
const invariantsResult = runInvariants({ baseDir:
|
|
1280
|
+
const invariantsResult = runInvariants({ baseDir: cwd, silent: false });
|
|
1348
1281
|
result = {
|
|
1349
1282
|
ok: invariantsResult.success,
|
|
1350
1283
|
duration: 0, // runInvariants doesn't track duration
|
|
@@ -1356,20 +1289,20 @@ async function executeGates(opts) {
|
|
|
1356
1289
|
}
|
|
1357
1290
|
else if (gate.cmd === GATE_COMMANDS.INCREMENTAL) {
|
|
1358
1291
|
// Special handling for incremental lint
|
|
1359
|
-
result = await runIncrementalLint({ agentLog });
|
|
1292
|
+
result = await runIncrementalLint({ agentLog, cwd });
|
|
1360
1293
|
}
|
|
1361
1294
|
else if (gate.cmd === GATE_COMMANDS.SAFETY_CRITICAL_TEST) {
|
|
1362
1295
|
// WU-2062: Safety-critical tests always run
|
|
1363
|
-
result = await runSafetyCriticalTests({ agentLog });
|
|
1296
|
+
result = await runSafetyCriticalTests({ agentLog, cwd });
|
|
1364
1297
|
}
|
|
1365
1298
|
else if (gate.cmd === GATE_COMMANDS.INCREMENTAL_TEST) {
|
|
1366
1299
|
// WU-1920: Special handling for changed tests
|
|
1367
|
-
result = await runChangedTests({ agentLog });
|
|
1300
|
+
result = await runChangedTests({ agentLog, cwd });
|
|
1368
1301
|
lastTestResult = result;
|
|
1369
1302
|
}
|
|
1370
1303
|
else if (gate.cmd === GATE_COMMANDS.TIERED_TEST) {
|
|
1371
1304
|
// WU-2062: Integration tests for high-risk changes
|
|
1372
|
-
result = await runIntegrationTests({ agentLog });
|
|
1305
|
+
result = await runIntegrationTests({ agentLog, cwd });
|
|
1373
1306
|
}
|
|
1374
1307
|
else if (gate.cmd === GATE_COMMANDS.COVERAGE_GATE) {
|
|
1375
1308
|
// WU-1920: Skip coverage gate when tests were changed (partial coverage)
|
|
@@ -1423,7 +1356,7 @@ async function executeGates(opts) {
|
|
|
1423
1356
|
});
|
|
1424
1357
|
}
|
|
1425
1358
|
else {
|
|
1426
|
-
result = run(gate.cmd, { agentLog });
|
|
1359
|
+
result = run(gate.cmd, { agentLog, cwd });
|
|
1427
1360
|
}
|
|
1428
1361
|
// Emit telemetry event
|
|
1429
1362
|
emitGateEvent({
|
|
@@ -1452,7 +1385,7 @@ async function executeGates(opts) {
|
|
|
1452
1385
|
continue;
|
|
1453
1386
|
}
|
|
1454
1387
|
if (gate.name === GATE_NAMES.FORMAT_CHECK) {
|
|
1455
|
-
emitFormatCheckGuidance({ agentLog, useAgentMode, files: lastFormatCheckFiles });
|
|
1388
|
+
emitFormatCheckGuidance({ agentLog, useAgentMode, files: lastFormatCheckFiles, cwd });
|
|
1456
1389
|
}
|
|
1457
1390
|
// WU-1520: Track failed gate before dying
|
|
1458
1391
|
gateResults.push({
|
|
@@ -1461,7 +1394,7 @@ async function executeGates(opts) {
|
|
|
1461
1394
|
durationMs: result.duration,
|
|
1462
1395
|
});
|
|
1463
1396
|
// WU-1520: Print summary before failing
|
|
1464
|
-
const logLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1397
|
+
const logLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1465
1398
|
logLine(`\n${formatGateSummary(gateResults)}\n`);
|
|
1466
1399
|
if (useAgentMode) {
|
|
1467
1400
|
const tail = readLogTail(agentLog.logPath);
|
|
@@ -1481,10 +1414,10 @@ async function executeGates(opts) {
|
|
|
1481
1414
|
}
|
|
1482
1415
|
// WU-2064: Create/update gates-latest.log symlink for easy agent access
|
|
1483
1416
|
if (agentLog) {
|
|
1484
|
-
updateGatesLatestSymlink({ logPath: agentLog.logPath, cwd
|
|
1417
|
+
updateGatesLatestSymlink({ logPath: agentLog.logPath, cwd, env: process.env });
|
|
1485
1418
|
}
|
|
1486
1419
|
// WU-1520: Print gate summary showing passed/skipped/failed/warned
|
|
1487
|
-
const summaryLogLine = makeGateLogger({ agentLog, useAgentMode });
|
|
1420
|
+
const summaryLogLine = makeGateLogger({ agentLog, useAgentMode, cwd });
|
|
1488
1421
|
summaryLogLine(`\n${formatGateSummary(gateResults)}`);
|
|
1489
1422
|
if (!useAgentMode) {
|
|
1490
1423
|
console.log('\n✅ All gates passed!\n');
|
|
@@ -1494,18 +1427,18 @@ async function executeGates(opts) {
|
|
|
1494
1427
|
}
|
|
1495
1428
|
return true;
|
|
1496
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
|
+
}
|
|
1497
1438
|
// WU-1071: Use import.meta.main instead of process.argv[1] comparison
|
|
1498
1439
|
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
|
|
1499
1440
|
// path but import.meta.url resolves to the real path - they never match
|
|
1500
1441
|
if (import.meta.main) {
|
|
1501
|
-
|
|
1502
|
-
executeGates({ ...opts, argv: process.argv.slice(2) })
|
|
1503
|
-
.then((ok) => {
|
|
1504
|
-
process.exit(ok ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
|
|
1505
|
-
})
|
|
1506
|
-
.catch((error) => {
|
|
1507
|
-
console.error('Gates failed:', error);
|
|
1508
|
-
process.exit(EXIT_CODES.ERROR);
|
|
1509
|
-
});
|
|
1442
|
+
void runCLI(main);
|
|
1510
1443
|
}
|
|
1511
1444
|
//# sourceMappingURL=gates.js.map
|