@lumenflow/cli 2.6.0 → 2.8.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.
Files changed (88) hide show
  1. package/README.md +120 -105
  2. package/dist/__tests__/agent-spawn-coordination.test.js +451 -0
  3. package/dist/__tests__/commands/integrate.test.js +165 -0
  4. package/dist/__tests__/gates-config.test.js +0 -1
  5. package/dist/__tests__/hooks/enforcement.test.js +279 -0
  6. package/dist/__tests__/init-greenfield.test.js +247 -0
  7. package/dist/__tests__/init-quick-ref.test.js +0 -1
  8. package/dist/__tests__/init-template-portability.test.js +0 -1
  9. package/dist/__tests__/init.test.js +27 -0
  10. package/dist/__tests__/initiative-e2e.test.js +442 -0
  11. package/dist/__tests__/initiative-plan-replacement.test.js +0 -1
  12. package/dist/__tests__/memory-integration.test.js +333 -0
  13. package/dist/__tests__/release.test.js +1 -1
  14. package/dist/__tests__/safe-git.test.js +0 -1
  15. package/dist/__tests__/state-doctor.test.js +54 -0
  16. package/dist/__tests__/sync-templates.test.js +255 -0
  17. package/dist/__tests__/wu-create-required-fields.test.js +121 -0
  18. package/dist/__tests__/wu-done-auto-cleanup.test.js +135 -0
  19. package/dist/__tests__/wu-lifecycle-integration.test.js +388 -0
  20. package/dist/__tests__/wu-proto.test.js +97 -0
  21. package/dist/backlog-prune.js +0 -1
  22. package/dist/cli-entry-point.js +0 -1
  23. package/dist/commands/integrate.js +229 -0
  24. package/dist/docs-sync.js +46 -0
  25. package/dist/doctor.js +0 -2
  26. package/dist/gates.js +0 -7
  27. package/dist/hooks/enforcement-checks.js +209 -0
  28. package/dist/hooks/enforcement-generator.js +365 -0
  29. package/dist/hooks/enforcement-sync.js +243 -0
  30. package/dist/hooks/index.js +7 -0
  31. package/dist/init.js +266 -11
  32. package/dist/initiative-add-wu.js +0 -2
  33. package/dist/initiative-create.js +0 -3
  34. package/dist/initiative-edit.js +0 -5
  35. package/dist/initiative-plan.js +0 -1
  36. package/dist/initiative-remove-wu.js +0 -2
  37. package/dist/lane-health.js +0 -2
  38. package/dist/lane-suggest.js +0 -1
  39. package/dist/mem-checkpoint.js +0 -2
  40. package/dist/mem-cleanup.js +0 -2
  41. package/dist/mem-context.js +0 -3
  42. package/dist/mem-create.js +0 -2
  43. package/dist/mem-delete.js +0 -3
  44. package/dist/mem-inbox.js +0 -2
  45. package/dist/mem-index.js +0 -1
  46. package/dist/mem-init.js +0 -2
  47. package/dist/mem-profile.js +0 -1
  48. package/dist/mem-promote.js +0 -1
  49. package/dist/mem-ready.js +0 -2
  50. package/dist/mem-signal.js +0 -2
  51. package/dist/mem-start.js +0 -2
  52. package/dist/mem-summarize.js +0 -2
  53. package/dist/metrics-cli.js +1 -1
  54. package/dist/metrics-snapshot.js +1 -1
  55. package/dist/onboarding-smoke-test.js +0 -5
  56. package/dist/orchestrate-init-status.js +0 -1
  57. package/dist/orchestrate-initiative.js +0 -1
  58. package/dist/orchestrate-monitor.js +0 -1
  59. package/dist/plan-create.js +0 -2
  60. package/dist/plan-edit.js +0 -2
  61. package/dist/plan-link.js +0 -2
  62. package/dist/plan-promote.js +0 -2
  63. package/dist/signal-cleanup.js +0 -4
  64. package/dist/state-bootstrap.js +0 -1
  65. package/dist/state-cleanup.js +0 -4
  66. package/dist/state-doctor-fix.js +5 -8
  67. package/dist/state-doctor.js +0 -11
  68. package/dist/sync-templates.js +188 -34
  69. package/dist/wu-block.js +100 -48
  70. package/dist/wu-claim.js +1 -22
  71. package/dist/wu-cleanup.js +0 -1
  72. package/dist/wu-create.js +0 -2
  73. package/dist/wu-done-auto-cleanup.js +139 -0
  74. package/dist/wu-done.js +11 -4
  75. package/dist/wu-edit.js +0 -12
  76. package/dist/wu-preflight.js +0 -1
  77. package/dist/wu-prep.js +0 -1
  78. package/dist/wu-proto.js +329 -0
  79. package/dist/wu-spawn.js +0 -3
  80. package/dist/wu-unblock.js +0 -2
  81. package/dist/wu-validate.js +0 -1
  82. package/package.json +9 -7
  83. package/templates/core/.husky/pre-commit.template +93 -0
  84. package/templates/core/ai/onboarding/quick-ref-commands.md.template +27 -0
  85. package/templates/core/ai/onboarding/rapid-prototyping.md +143 -0
  86. package/templates/core/ai/onboarding/starting-prompt.md.template +3 -3
  87. package/templates/vendors/claude/.claude/CLAUDE.md.template +25 -0
  88. package/templates/vendors/claude/.claude/hooks/enforce-worktree.sh +135 -0
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @file wu-proto.test.ts
3
+ * Test suite for wu:proto command (WU-1359)
4
+ *
5
+ * WU-1359: Add wu:proto convenience command for rapid prototyping
6
+ *
7
+ * Tests:
8
+ * - wu:proto creates WU with type: prototype
9
+ * - wu:proto has relaxed validation (no --acceptance required)
10
+ * - wu:proto immediately claims the WU
11
+ * - wu:proto prints cd command to worktree
12
+ */
13
+ import { describe, it, expect } from 'vitest';
14
+ // WU-1359: Import wu:proto validation and helpers
15
+ import { validateProtoSpec } from '../wu-proto.js';
16
+ /** Test constants to avoid duplicate string literals (sonarjs/no-duplicate-string) */
17
+ const TEST_WU_ID = 'WU-9999';
18
+ const TEST_LANE = 'Framework: CLI';
19
+ const TEST_TITLE = 'Quick prototype';
20
+ const TEST_DESCRIPTION = 'Context: testing.\nProblem: need to test.\nSolution: add tests.';
21
+ describe('wu:proto command (WU-1359)', () => {
22
+ describe('validateProtoSpec relaxed validation', () => {
23
+ it('should pass validation without --acceptance', () => {
24
+ const result = validateProtoSpec({
25
+ id: TEST_WU_ID,
26
+ lane: TEST_LANE,
27
+ title: TEST_TITLE,
28
+ opts: {
29
+ description: TEST_DESCRIPTION,
30
+ codePaths: ['packages/@lumenflow/cli/src/wu-proto.ts'],
31
+ },
32
+ });
33
+ expect(result.valid).toBe(true);
34
+ expect(result.errors).toHaveLength(0);
35
+ });
36
+ it('should pass validation without --exposure', () => {
37
+ const result = validateProtoSpec({
38
+ id: TEST_WU_ID,
39
+ lane: TEST_LANE,
40
+ title: TEST_TITLE,
41
+ opts: {
42
+ description: TEST_DESCRIPTION,
43
+ },
44
+ });
45
+ expect(result.valid).toBe(true);
46
+ expect(result.errors).toHaveLength(0);
47
+ });
48
+ it('should pass validation without --code-paths', () => {
49
+ const result = validateProtoSpec({
50
+ id: TEST_WU_ID,
51
+ lane: TEST_LANE,
52
+ title: TEST_TITLE,
53
+ opts: {
54
+ description: TEST_DESCRIPTION,
55
+ },
56
+ });
57
+ expect(result.valid).toBe(true);
58
+ expect(result.errors).toHaveLength(0);
59
+ });
60
+ it('should require lane', () => {
61
+ const result = validateProtoSpec({
62
+ id: TEST_WU_ID,
63
+ lane: '',
64
+ title: TEST_TITLE,
65
+ opts: {},
66
+ });
67
+ expect(result.valid).toBe(false);
68
+ expect(result.errors.some((e) => e.toLowerCase().includes('lane'))).toBe(true);
69
+ });
70
+ it('should require title', () => {
71
+ const result = validateProtoSpec({
72
+ id: TEST_WU_ID,
73
+ lane: TEST_LANE,
74
+ title: '',
75
+ opts: {},
76
+ });
77
+ expect(result.valid).toBe(false);
78
+ expect(result.errors.some((e) => e.toLowerCase().includes('title'))).toBe(true);
79
+ });
80
+ });
81
+ describe('prototype WU type', () => {
82
+ it('should set type to prototype', () => {
83
+ // The buildProtoWUContent function should return type: prototype
84
+ // We test this indirectly through the validateProtoSpec which checks content
85
+ const result = validateProtoSpec({
86
+ id: TEST_WU_ID,
87
+ lane: TEST_LANE,
88
+ title: TEST_TITLE,
89
+ opts: {
90
+ description: TEST_DESCRIPTION,
91
+ },
92
+ });
93
+ // Prototype WUs should always be valid with minimal input
94
+ expect(result.valid).toBe(true);
95
+ });
96
+ });
97
+ });
@@ -17,7 +17,6 @@ import path from 'node:path';
17
17
  import { readWURaw, writeWU, appendNote } from '@lumenflow/core/dist/wu-yaml.js';
18
18
  import { WU_PATHS } from '@lumenflow/core/dist/wu-paths.js';
19
19
  import { CLI_FLAGS, EXIT_CODES, EMOJI, WU_STATUS, STRING_LITERALS, } from '@lumenflow/core/dist/wu-constants.js';
20
- /* eslint-disable security/detect-non-literal-fs-filename */
21
20
  /** Log prefix for consistent output */
22
21
  const LOG_PREFIX = '[backlog-prune]';
23
22
  /**
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console -- CLI entry point uses console for error output */
2
1
  /**
3
2
  * Shared CLI entry point wrapper
4
3
  *
@@ -0,0 +1,229 @@
1
+ /**
2
+ * @file integrate.ts
3
+ * Integrate LumenFlow with Claude Code (WU-1367)
4
+ *
5
+ * This command generates enforcement hooks and updates Claude Code
6
+ * configuration based on .lumenflow.config.yaml settings.
7
+ *
8
+ * Usage:
9
+ * pnpm lumenflow:integrate --client claude-code
10
+ */
11
+ // CLI tool - console output is intentional for user feedback
12
+ // fs operations use runtime-provided paths from LumenFlow configuration
13
+ // Object injection sink warnings are false positives for array indexing
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import * as yaml from 'yaml';
17
+ import { createWUParser, WU_OPTIONS } from '@lumenflow/core';
18
+ import { generateEnforcementHooks, generateEnforceWorktreeScript, generateRequireWuScript, generateWarnIncompleteScript, } from '../hooks/enforcement-generator.js';
19
+ /**
20
+ * CLI options for integrate command
21
+ */
22
+ const INTEGRATE_OPTIONS = {
23
+ client: {
24
+ name: 'client',
25
+ flags: '--client <type>',
26
+ description: 'Client type to integrate (claude-code)',
27
+ },
28
+ force: WU_OPTIONS.force,
29
+ };
30
+ /**
31
+ * Parse command line options
32
+ */
33
+ export function parseIntegrateOptions() {
34
+ const opts = createWUParser({
35
+ name: 'lumenflow-integrate',
36
+ description: 'Integrate LumenFlow enforcement with AI client tools',
37
+ options: Object.values(INTEGRATE_OPTIONS),
38
+ });
39
+ return {
40
+ client: opts.client ?? 'claude-code',
41
+ force: opts.force ?? false,
42
+ };
43
+ }
44
+ /**
45
+ * Read existing Claude settings.json
46
+ */
47
+ function readClaudeSettings(projectDir) {
48
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
49
+ if (!fs.existsSync(settingsPath)) {
50
+ return {
51
+ $schema: 'https://json.schemastore.org/claude-code-settings.json',
52
+ permissions: {
53
+ allow: ['Bash', 'Read', 'Write', 'Edit', 'WebFetch', 'WebSearch', 'Skill'],
54
+ },
55
+ };
56
+ }
57
+ try {
58
+ const content = fs.readFileSync(settingsPath, 'utf-8');
59
+ return JSON.parse(content);
60
+ }
61
+ catch {
62
+ return {
63
+ $schema: 'https://json.schemastore.org/claude-code-settings.json',
64
+ };
65
+ }
66
+ }
67
+ /**
68
+ * Merge generated hooks into existing settings
69
+ */
70
+ // Complexity is acceptable for hook merging logic - alternative would over-abstract
71
+ // eslint-disable-next-line sonarjs/cognitive-complexity
72
+ function mergeHooksIntoSettings(existing, generated) {
73
+ const result = { ...existing };
74
+ if (!result.hooks) {
75
+ result.hooks = {};
76
+ }
77
+ // Merge PreToolUse hooks
78
+ if (generated.preToolUse) {
79
+ if (!result.hooks.PreToolUse) {
80
+ result.hooks.PreToolUse = [];
81
+ }
82
+ for (const newHook of generated.preToolUse) {
83
+ const existingIndex = result.hooks.PreToolUse.findIndex((h) => h.matcher === newHook.matcher);
84
+ if (existingIndex >= 0) {
85
+ const existing = result.hooks.PreToolUse[existingIndex];
86
+ for (const hook of newHook.hooks) {
87
+ const isDuplicate = existing.hooks.some((h) => h.command === hook.command);
88
+ if (!isDuplicate) {
89
+ existing.hooks.push(hook);
90
+ }
91
+ }
92
+ }
93
+ else {
94
+ result.hooks.PreToolUse.push(newHook);
95
+ }
96
+ }
97
+ }
98
+ // Merge Stop hooks
99
+ if (generated.stop) {
100
+ if (!result.hooks.Stop) {
101
+ result.hooks.Stop = [];
102
+ }
103
+ for (const newHook of generated.stop) {
104
+ const existingIndex = result.hooks.Stop.findIndex((h) => h.matcher === newHook.matcher);
105
+ if (existingIndex >= 0) {
106
+ const existing = result.hooks.Stop[existingIndex];
107
+ for (const hook of newHook.hooks) {
108
+ const isDuplicate = existing.hooks.some((h) => h.command === hook.command);
109
+ if (!isDuplicate) {
110
+ existing.hooks.push(hook);
111
+ }
112
+ }
113
+ }
114
+ else {
115
+ result.hooks.Stop.push(newHook);
116
+ }
117
+ }
118
+ }
119
+ return result;
120
+ }
121
+ /**
122
+ * Integrate Claude Code with LumenFlow enforcement hooks.
123
+ *
124
+ * This function:
125
+ * 1. Creates .claude/hooks directory if needed
126
+ * 2. Generates enforcement hook scripts
127
+ * 3. Updates .claude/settings.json with hook configuration
128
+ *
129
+ * @param projectDir - Project directory
130
+ * @param config - Client configuration with enforcement settings
131
+ */
132
+ export async function integrateClaudeCode(projectDir, config) {
133
+ const enforcement = config.enforcement;
134
+ // Skip if enforcement not enabled
135
+ if (!enforcement?.hooks) {
136
+ console.log('[integrate] Enforcement hooks not enabled, skipping');
137
+ return;
138
+ }
139
+ const claudeDir = path.join(projectDir, '.claude');
140
+ const hooksDir = path.join(claudeDir, 'hooks');
141
+ // Create directories
142
+ if (!fs.existsSync(hooksDir)) {
143
+ fs.mkdirSync(hooksDir, { recursive: true });
144
+ console.log('[integrate] Created .claude/hooks directory');
145
+ }
146
+ // Generate hooks based on config
147
+ const generatedHooks = generateEnforcementHooks({
148
+ block_outside_worktree: enforcement.block_outside_worktree ?? false,
149
+ require_wu_for_edits: enforcement.require_wu_for_edits ?? false,
150
+ warn_on_stop_without_wu_done: enforcement.warn_on_stop_without_wu_done ?? false,
151
+ });
152
+ // Write hook scripts
153
+ if (enforcement.block_outside_worktree) {
154
+ const scriptPath = path.join(hooksDir, 'enforce-worktree.sh');
155
+ fs.writeFileSync(scriptPath, generateEnforceWorktreeScript(), { mode: 0o755 });
156
+ console.log('[integrate] Generated enforce-worktree.sh');
157
+ }
158
+ if (enforcement.require_wu_for_edits) {
159
+ const scriptPath = path.join(hooksDir, 'require-wu.sh');
160
+ fs.writeFileSync(scriptPath, generateRequireWuScript(), { mode: 0o755 });
161
+ console.log('[integrate] Generated require-wu.sh');
162
+ }
163
+ if (enforcement.warn_on_stop_without_wu_done) {
164
+ const scriptPath = path.join(hooksDir, 'warn-incomplete.sh');
165
+ fs.writeFileSync(scriptPath, generateWarnIncompleteScript(), { mode: 0o755 });
166
+ console.log('[integrate] Generated warn-incomplete.sh');
167
+ }
168
+ // Update settings.json
169
+ const existingSettings = readClaudeSettings(projectDir);
170
+ const updatedSettings = mergeHooksIntoSettings(existingSettings, generatedHooks);
171
+ const settingsPath = path.join(claudeDir, 'settings.json');
172
+ fs.writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2) + '\n', 'utf-8');
173
+ console.log('[integrate] Updated .claude/settings.json');
174
+ }
175
+ /**
176
+ * Read enforcement config from .lumenflow.config.yaml
177
+ */
178
+ function readEnforcementConfig(projectDir) {
179
+ const configPath = path.join(projectDir, '.lumenflow.config.yaml');
180
+ if (!fs.existsSync(configPath)) {
181
+ return null;
182
+ }
183
+ try {
184
+ const content = fs.readFileSync(configPath, 'utf-8');
185
+ const config = yaml.parse(content);
186
+ return config?.agents?.clients?.['claude-code']?.enforcement ?? null;
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
192
+ /**
193
+ * Main entry point for integrate command
194
+ */
195
+ export async function main() {
196
+ const opts = parseIntegrateOptions();
197
+ if (opts.client !== 'claude-code') {
198
+ console.error(`[integrate] Unsupported client: ${opts.client}`);
199
+ console.error('[integrate] Currently only "claude-code" is supported');
200
+ process.exit(1);
201
+ }
202
+ const projectDir = process.cwd();
203
+ // Read enforcement config from .lumenflow.config.yaml
204
+ const enforcement = readEnforcementConfig(projectDir);
205
+ if (!enforcement) {
206
+ console.log('[integrate] No enforcement config found in .lumenflow.config.yaml');
207
+ console.log('[integrate] Add this to your config to enable enforcement hooks:');
208
+ console.log(`
209
+ agents:
210
+ clients:
211
+ claude-code:
212
+ enforcement:
213
+ hooks: true
214
+ block_outside_worktree: true
215
+ require_wu_for_edits: true
216
+ warn_on_stop_without_wu_done: true
217
+ `);
218
+ return;
219
+ }
220
+ await integrateClaudeCode(projectDir, { enforcement });
221
+ console.log('[integrate] Claude Code integration complete');
222
+ }
223
+ // Run if executed directly
224
+ if (import.meta.url === `file://${process.argv[1]}`) {
225
+ main().catch((err) => {
226
+ console.error('[integrate] Error:', err.message);
227
+ process.exit(1);
228
+ });
229
+ }
package/dist/docs-sync.js CHANGED
@@ -3,11 +3,14 @@
3
3
  * LumenFlow docs:sync command for syncing agent docs to existing projects (WU-1083)
4
4
  * WU-1085: Added createWUParser for proper --help support
5
5
  * WU-1124: Refactored to read templates from bundled files (INIT-004 Phase 2)
6
+ * WU-1362: Added branch guard to check branch before writing tracked files
6
7
  */
7
8
  import * as fs from 'node:fs';
8
9
  import * as path from 'node:path';
9
10
  import { fileURLToPath } from 'node:url';
10
11
  import { createWUParser, WU_OPTIONS } from '@lumenflow/core';
12
+ // WU-1362: Import worktree guard utilities for branch checking
13
+ import { isMainBranch, isInWorktree } from '@lumenflow/core/dist/core/worktree-guard.js';
11
14
  /**
12
15
  * WU-1085: CLI option definitions for docs-sync command
13
16
  */
@@ -182,9 +185,45 @@ export async function syncSkills(targetDir, options) {
182
185
  }
183
186
  return result;
184
187
  }
188
+ /**
189
+ * WU-1362: Check branch guard before writing tracked files
190
+ *
191
+ * Warns (but does not block) if:
192
+ * - On main branch AND
193
+ * - Not in a worktree directory AND
194
+ * - Git repository exists (has .git)
195
+ *
196
+ * @param targetDir - Directory where files will be written
197
+ * @returns Array of warning messages
198
+ */
199
+ async function checkBranchGuard(targetDir) {
200
+ const warnings = [];
201
+ // Only check if target is a git repository
202
+ const gitDir = path.join(targetDir, '.git');
203
+ if (!fs.existsSync(gitDir)) {
204
+ return warnings;
205
+ }
206
+ // Check if we're in a worktree (always allow)
207
+ if (isInWorktree({ cwd: targetDir })) {
208
+ return warnings;
209
+ }
210
+ // Check if on main branch
211
+ try {
212
+ const onMain = await isMainBranch();
213
+ if (onMain) {
214
+ warnings.push('Running docs:sync on main branch in main checkout. ' +
215
+ 'Consider using a worktree for changes to tracked files.');
216
+ }
217
+ }
218
+ catch {
219
+ // Git error - silently allow
220
+ }
221
+ return warnings;
222
+ }
185
223
  /**
186
224
  * CLI entry point for docs:sync command
187
225
  * WU-1085: Updated to use parseDocsSyncOptions for proper --help support
226
+ * WU-1362: Added branch guard check
188
227
  */
189
228
  export async function main() {
190
229
  const opts = parseDocsSyncOptions();
@@ -192,10 +231,13 @@ export async function main() {
192
231
  console.log('[lumenflow docs:sync] Syncing agent documentation...');
193
232
  console.log(` Vendor: ${opts.vendor}`);
194
233
  console.log(` Force: ${opts.force}`);
234
+ // WU-1362: Check branch guard before writing files
235
+ const branchWarnings = await checkBranchGuard(targetDir);
195
236
  const docsResult = await syncAgentDocs(targetDir, { force: opts.force });
196
237
  const skillsResult = await syncSkills(targetDir, { force: opts.force, vendor: opts.vendor });
197
238
  const created = [...docsResult.created, ...skillsResult.created];
198
239
  const skipped = [...docsResult.skipped, ...skillsResult.skipped];
240
+ const warnings = [...branchWarnings];
199
241
  if (created.length > 0) {
200
242
  console.log('\nCreated:');
201
243
  created.forEach((f) => console.log(` + ${f}`));
@@ -204,6 +246,10 @@ export async function main() {
204
246
  console.log('\nSkipped (already exists, use --force to overwrite):');
205
247
  skipped.forEach((f) => console.log(` - ${f}`));
206
248
  }
249
+ if (warnings.length > 0) {
250
+ console.log('\nWarnings:');
251
+ warnings.forEach((w) => console.log(` ! ${w}`));
252
+ }
207
253
  console.log('\n[lumenflow docs:sync] Done!');
208
254
  }
209
255
  // CLI entry point (WU-1071 pattern: import.meta.main)
package/dist/doctor.js CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console -- CLI command uses console for status output */
2
1
  /**
3
2
  * @file doctor.ts
4
3
  * LumenFlow health check command (WU-1177)
@@ -167,7 +166,6 @@ function getCommandVersion(command, args) {
167
166
  * Parse semver version string to compare
168
167
  */
169
168
  function parseVersion(versionStr) {
170
- // eslint-disable-next-line sonarjs/slow-regex, sonarjs/prefer-regexp-exec -- Simple semver extraction, no backtracking risk
171
169
  const match = versionStr.match(/(\d+)\.(\d+)\.?(\d+)?/);
172
170
  if (!match) {
173
171
  return [0, 0, 0];
package/dist/gates.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /* eslint-disable no-console -- Gates runner uses console for status output; refactoring to logger is tracked for future work */
3
2
  /**
4
3
  * Quality Gates Runner
5
4
  *
@@ -469,7 +468,6 @@ export function formatFormatCheckGuidance(files) {
469
468
  function collectPrettierListDifferent(cwd, files = []) {
470
469
  const filesArg = files.length > 0 ? quoteShellArgs(files) : '.';
471
470
  const cmd = pnpmCmd(SCRIPTS.PRETTIER, PRETTIER_ARGS.LIST_DIFFERENT, filesArg);
472
- // eslint-disable-next-line sonarjs/os-command -- Pre-existing: executes trusted pnpm prettier command
473
471
  const result = spawnSync(cmd, [], {
474
472
  shell: true,
475
473
  cwd,
@@ -534,7 +532,6 @@ function run(cmd, { agentLog } = {}) {
534
532
  if (!agentLog) {
535
533
  console.log(`\n> ${cmd}\n`);
536
534
  try {
537
- // eslint-disable-next-line sonarjs/os-command -- Pre-existing: cmd is built from trusted constants
538
535
  execSync(cmd, { stdio: 'inherit', encoding: FILE_SYSTEM.ENCODING });
539
536
  return { ok: true, duration: Date.now() - start };
540
537
  }
@@ -543,7 +540,6 @@ function run(cmd, { agentLog } = {}) {
543
540
  }
544
541
  }
545
542
  writeSync(agentLog.logFd, `\n> ${cmd}\n\n`);
546
- // eslint-disable-next-line sonarjs/os-command -- Pre-existing: cmd is built from trusted constants
547
543
  const result = spawnSync(cmd, [], {
548
544
  shell: true,
549
545
  stdio: ['ignore', agentLog.logFd, agentLog.logFd],
@@ -704,13 +700,11 @@ async function runFormatCheckGate({ agentLog, useAgentMode }) {
704
700
  return { ok: true, duration: Date.now() - start, fileCount: 0, filesChecked: [] };
705
701
  }
706
702
  if (plan.mode === 'full') {
707
- /* eslint-disable sonarjs/no-nested-conditional -- Pre-existing: simple reason mapping, readable as-is */
708
703
  const reason = plan.reason === 'prettier-config'
709
704
  ? ' (prettier config changed)'
710
705
  : plan.reason === 'file-list-error'
711
706
  ? ' (file list unavailable)'
712
707
  : '';
713
- /* eslint-enable sonarjs/no-nested-conditional */
714
708
  logLine(`📋 Running full format check${reason}`);
715
709
  const result = run(pnpmCmd(SCRIPTS.FORMAT_CHECK), { agentLog });
716
710
  return { ...result, duration: Date.now() - start, fileCount: -1 };
@@ -1408,7 +1402,6 @@ async function executeGates(opts) {
1408
1402
  // The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
1409
1403
  // path but import.meta.url resolves to the real path - they never match
1410
1404
  if (import.meta.main) {
1411
- // eslint-disable-next-line sonarjs/deprecation -- Pre-existing: parseGatesArgs kept for backwards compatibility
1412
1405
  const opts = parseGatesArgs();
1413
1406
  executeGates({ ...opts, argv: process.argv.slice(2) })
1414
1407
  .then((ok) => {