@lumenflow/cli 1.1.0 โ†’ 1.3.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/gates.js CHANGED
@@ -42,6 +42,7 @@ 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 { fileURLToPath } from 'node:url';
45
46
  import { emitGateEvent, getCurrentWU, getCurrentLane } from '@lumenflow/core/dist/telemetry.js';
46
47
  import { die } from '@lumenflow/core/dist/error-handler.js';
47
48
  import { getChangedLintableFiles, convertToPackageRelativePaths, } from '@lumenflow/core/dist/incremental-lint.js';
@@ -55,42 +56,35 @@ import { detectRiskTier, RISK_TIERS, } from '@lumenflow/core/dist/risk-detector.
55
56
  // WU-2252: Import invariants runner for first-check validation
56
57
  import { runInvariants } from '@lumenflow/core/dist/invariants-runner.js';
57
58
  import { Command } from 'commander';
58
- import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, TOOL_PATHS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, } from '@lumenflow/core/dist/wu-constants.js';
59
+ import { BRANCHES, PACKAGES, PKG_MANAGER, PKG_FLAGS, ESLINT_FLAGS, ESLINT_COMMANDS, ESLINT_DEFAULTS, SCRIPTS, CACHE_STRATEGIES, DIRECTORIES, GATE_NAMES, GATE_COMMANDS, TOOL_PATHS, CLI_MODES, EXIT_CODES, FILE_SYSTEM, PRETTIER_ARGS, PRETTIER_FLAGS, } from '@lumenflow/core/dist/wu-constants.js';
59
60
  // WU-2457: Add Commander.js for --help support
60
- // WU-2465: Pre-filter argv to handle pnpm's `--` separator
61
- // When invoked via `pnpm gates -- --docs-only`, pnpm passes ["--", "--docs-only"]
62
- // Commander treats `--` as "everything after is positional", causing errors.
63
- // Solution: Remove standalone `--` from argv before parsing.
64
- const filteredArgv = process.argv.filter((arg, index, arr) => {
65
- // Keep `--` only if it's followed by a non-option (actual positional arg)
66
- // Remove it if it's followed by an option (starts with -)
67
- if (arg === '--') {
68
- const nextArg = arr[index + 1];
69
- return nextArg && !nextArg.startsWith('-');
70
- }
71
- return true;
72
- });
73
- const program = new Command()
74
- .name('gates')
75
- .description('Run quality gates with support for docs-only mode, incremental linting, and tiered testing')
76
- .option('--docs-only', 'Run docs-only gates (format, spec-linter, prompts-lint, backlog-sync)')
77
- .option('--full-lint', 'Run full lint instead of incremental')
78
- .option('--full-tests', 'Run full test suite instead of incremental')
79
- .option('--full-coverage', 'Force full test suite and coverage gate (implies --full-tests)')
80
- .option('--coverage-mode <mode>', 'Coverage gate mode: "warn" logs warnings, "block" fails gate (default)', 'block')
81
- .option('--verbose', 'Stream output in agent mode instead of logging to file')
82
- .helpOption('-h, --help', 'Display help for command');
83
- program.parse(filteredArgv);
84
- const opts = program.opts();
85
- // Parse command line arguments (now via Commander)
86
- const isDocsOnly = opts.docsOnly || false;
87
- const isFullLint = opts.fullLint || false;
88
- const isFullTests = opts.fullTests || false;
89
- // WU-2244: Full coverage flag forces full test suite and coverage gate (deterministic)
90
- const isFullCoverage = opts.fullCoverage || false;
91
- // WU-1433: Coverage gate mode (warn or block)
92
- // WU-2334: Default changed from WARN to BLOCK for TDD enforcement
93
- const coverageMode = opts.coverageMode || COVERAGE_GATE_MODES.BLOCK;
61
+ function parseGatesArgs(argv = process.argv) {
62
+ // WU-2465: Pre-filter argv to handle pnpm's `--` separator
63
+ // When invoked via `pnpm gates -- --docs-only`, pnpm passes ["--", "--docs-only"]
64
+ // Commander treats `--` as "everything after is positional", causing errors.
65
+ // Solution: Remove standalone `--` from argv before parsing.
66
+ const filteredArgv = argv.filter((arg, index, arr) => {
67
+ // Keep `--` only if it's followed by a non-option (actual positional arg)
68
+ // Remove it if it's followed by an option (starts with -)
69
+ if (arg === '--') {
70
+ const nextArg = arr[index + 1];
71
+ return nextArg && !nextArg.startsWith('-');
72
+ }
73
+ return true;
74
+ });
75
+ const program = new Command()
76
+ .name('gates')
77
+ .description('Run quality gates with support for docs-only mode, incremental linting, and tiered testing')
78
+ .option('--docs-only', 'Run docs-only gates (format, spec-linter, prompts-lint, backlog-sync)')
79
+ .option('--full-lint', 'Run full lint instead of incremental')
80
+ .option('--full-tests', 'Run full test suite instead of incremental')
81
+ .option('--full-coverage', 'Force full test suite and coverage gate (implies --full-tests)')
82
+ .option('--coverage-mode <mode>', 'Coverage gate mode: "warn" logs warnings, "block" fails gate (default)', 'block')
83
+ .option('--verbose', 'Stream output in agent mode instead of logging to file')
84
+ .helpOption('-h, --help', 'Display help for command');
85
+ program.parse(filteredArgv);
86
+ return program.opts();
87
+ }
94
88
  /**
95
89
  * Build a pnpm command string
96
90
  */
@@ -104,6 +98,60 @@ function pnpmRun(script, ...args) {
104
98
  const argsStr = args.length > 0 ? ` ${args.join(' ')}` : '';
105
99
  return `${PKG_MANAGER} ${SCRIPTS.RUN} ${script}${argsStr}`;
106
100
  }
101
+ export function parsePrettierListOutput(output) {
102
+ if (!output)
103
+ return [];
104
+ return output
105
+ .split(/\r?\n/)
106
+ .map((line) => line.trim())
107
+ .filter(Boolean)
108
+ .map((line) => line.replace(/^\[error\]\s*/i, '').trim())
109
+ .filter((line) => !line.toLowerCase().includes('code style issues found') &&
110
+ !line.toLowerCase().includes('all matched files use prettier') &&
111
+ !line.toLowerCase().includes('checking formatting'));
112
+ }
113
+ export function buildPrettierWriteCommand(files) {
114
+ const quotedFiles = files.map((file) => `"${file}"`).join(' ');
115
+ const base = pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_FLAGS.WRITE);
116
+ return quotedFiles ? `${base} ${quotedFiles}` : base;
117
+ }
118
+ export function formatFormatCheckGuidance(files) {
119
+ if (!files.length)
120
+ return [];
121
+ const command = buildPrettierWriteCommand(files);
122
+ return [
123
+ '',
124
+ 'โŒ format:check failed',
125
+ 'Fix with:',
126
+ ` ${command}`,
127
+ '',
128
+ 'Affected files:',
129
+ ...files.map((file) => ` - ${file}`),
130
+ '',
131
+ ];
132
+ }
133
+ function collectPrettierListDifferent(cwd) {
134
+ const cmd = pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_ARGS.LIST_DIFFERENT, '.');
135
+ const result = spawnSync(cmd, [], {
136
+ shell: true,
137
+ cwd,
138
+ encoding: FILE_SYSTEM.ENCODING,
139
+ });
140
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
141
+ return parsePrettierListOutput(output);
142
+ }
143
+ function emitFormatCheckGuidance({ agentLog, useAgentMode, }) {
144
+ const files = collectPrettierListDifferent(process.cwd());
145
+ if (!files.length)
146
+ return;
147
+ const lines = formatFormatCheckGuidance(files);
148
+ const logLine = useAgentMode && agentLog
149
+ ? (line) => writeSync(agentLog.logFd, `${line}\n`)
150
+ : (line) => console.log(line);
151
+ for (const line of lines) {
152
+ logLine(line);
153
+ }
154
+ }
107
155
  /**
108
156
  * Build a pnpm --filter command string
109
157
  */
@@ -480,6 +528,16 @@ const agentLog = useAgentMode ? createAgentLogContext({ wuId: wu_id, lane }) : n
480
528
  // Main execution
481
529
  // eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing: main() orchestrates multi-step gate workflow
482
530
  async function main() {
531
+ const opts = parseGatesArgs();
532
+ // Parse command line arguments (now via Commander)
533
+ const isDocsOnly = opts.docsOnly || false;
534
+ const isFullLint = opts.fullLint || false;
535
+ const isFullTests = opts.fullTests || false;
536
+ // WU-2244: Full coverage flag forces full test suite and coverage gate (deterministic)
537
+ const isFullCoverage = opts.fullCoverage || false;
538
+ // WU-1433: Coverage gate mode (warn or block)
539
+ // WU-2334: Default changed from WARN to BLOCK for TDD enforcement
540
+ const coverageMode = opts.coverageMode || COVERAGE_GATE_MODES.BLOCK;
483
541
  if (useAgentMode) {
484
542
  console.log(`๐Ÿงพ gates (agent mode): output -> ${agentLog.logPath} (use --verbose for streaming)\n`);
485
543
  }
@@ -670,6 +728,9 @@ async function main() {
670
728
  }
671
729
  continue;
672
730
  }
731
+ if (gate.name === GATE_NAMES.FORMAT_CHECK) {
732
+ emitFormatCheckGuidance({ agentLog, useAgentMode });
733
+ }
673
734
  if (useAgentMode) {
674
735
  const tail = readLogTail(agentLog.logPath);
675
736
  console.error(`\nโŒ ${gate.name} failed (agent mode). Log: ${agentLog.logPath}\n`);
@@ -692,7 +753,9 @@ async function main() {
692
753
  }
693
754
  process.exit(EXIT_CODES.SUCCESS);
694
755
  }
695
- main().catch((error) => {
696
- console.error('Gates failed:', error);
697
- process.exit(EXIT_CODES.ERROR);
698
- });
756
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
757
+ main().catch((error) => {
758
+ console.error('Gates failed:', error);
759
+ process.exit(EXIT_CODES.ERROR);
760
+ });
761
+ }