sentinelayer-cli 0.25.0 → 0.25.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,3 +1,5 @@
1
+ import { isAuditSourceFile } from "./path-classification.js";
2
+
1
3
  function normalizeString(value) {
2
4
  return String(value || "").trim();
3
5
  }
@@ -21,6 +23,7 @@ function summarizeSeverity(findings = []) {
21
23
  function buildHotspots(ingest = {}) {
22
24
  const indexed = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
23
25
  return indexed
26
+ .filter((file) => isAuditSourceFile(file))
24
27
  .filter((file) => Number(file.loc || 0) >= 300)
25
28
  .sort((left, right) => Number(right.loc || 0) - Number(left.loc || 0))
26
29
  .slice(0, 20)
@@ -50,7 +53,7 @@ function deriveArchitectureFindings({ findings = [], hotspots = [] } = {}) {
50
53
  continue;
51
54
  }
52
55
  derived.push({
53
- severity: hotspot.risk === "high" ? "P1" : "P2",
56
+ severity: "P2",
54
57
  file: hotspot.path,
55
58
  line: 1,
56
59
  message: `Large architectural hotspot detected (${hotspot.loc} LOC).`,
@@ -1,3 +1,5 @@
1
+ import { isAuditSourceFile } from "./path-classification.js";
2
+
1
3
  function normalizeString(value) {
2
4
  return String(value || "").trim();
3
5
  }
@@ -26,9 +28,7 @@ function buildDocumentationInventory(ingest = {}) {
26
28
  language: file.language,
27
29
  }));
28
30
  const docFiles = normalized.filter((file) => /(^|\/)(docs?|guides?|adr|readme)/i.test(file.path));
29
- const codeFiles = normalized.filter(
30
- (file) => file.loc > 0 && !/(^|\/)(docs?|guides?|adr|readme)/i.test(file.path)
31
- );
31
+ const codeFiles = normalized.filter((file) => file.loc > 0 && isAuditSourceFile(file));
32
32
  const docDensity = codeFiles.length > 0 ? docFiles.length / codeFiles.length : 0;
33
33
  const undocumentedHotspots = codeFiles
34
34
  .filter((file) => file.loc >= 320)
@@ -42,7 +42,7 @@ function buildDocumentationInventory(ingest = {}) {
42
42
  path: file.path,
43
43
  loc: file.loc,
44
44
  language: file.language,
45
- severity: file.loc >= 800 ? "P1" : "P2",
45
+ severity: "P2",
46
46
  }));
47
47
 
48
48
  return {
@@ -0,0 +1,120 @@
1
+ const SOURCE_EXTENSIONS = new Set([
2
+ ".c",
3
+ ".cc",
4
+ ".cpp",
5
+ ".cs",
6
+ ".css",
7
+ ".go",
8
+ ".graphql",
9
+ ".h",
10
+ ".hpp",
11
+ ".java",
12
+ ".js",
13
+ ".jsx",
14
+ ".kt",
15
+ ".mjs",
16
+ ".php",
17
+ ".py",
18
+ ".rb",
19
+ ".rs",
20
+ ".scss",
21
+ ".sql",
22
+ ".svelte",
23
+ ".swift",
24
+ ".tf",
25
+ ".ts",
26
+ ".tsx",
27
+ ".vue",
28
+ ]);
29
+
30
+ const SOURCE_LANGUAGES = new Set([
31
+ "c",
32
+ "c++",
33
+ "c#",
34
+ "css",
35
+ "dockerfile",
36
+ "go",
37
+ "graphql",
38
+ "java",
39
+ "javascript",
40
+ "jsx",
41
+ "kotlin",
42
+ "php",
43
+ "python",
44
+ "ruby",
45
+ "rust",
46
+ "scss",
47
+ "sql",
48
+ "svelte",
49
+ "swift",
50
+ "terraform",
51
+ "typescript",
52
+ "tsx",
53
+ "vue",
54
+ ]);
55
+
56
+ const LOCKFILE_NAMES = new Set([
57
+ "cargo.lock",
58
+ "composer.lock",
59
+ "go.sum",
60
+ "package-lock.json",
61
+ "pipfile.lock",
62
+ "pnpm-lock.yaml",
63
+ "poetry.lock",
64
+ "yarn.lock",
65
+ ]);
66
+
67
+ function normalizePath(value) {
68
+ return String(value || "").replace(/\\/g, "/").replace(/^\.\/+/, "").toLowerCase();
69
+ }
70
+
71
+ function basename(filePath) {
72
+ const normalized = normalizePath(filePath);
73
+ return normalized.slice(normalized.lastIndexOf("/") + 1);
74
+ }
75
+
76
+ function extension(filePath) {
77
+ const name = basename(filePath);
78
+ const dot = name.lastIndexOf(".");
79
+ return dot > 0 ? name.slice(dot) : "";
80
+ }
81
+
82
+ export function isAuditTestPath(filePath) {
83
+ const normalized = normalizePath(filePath);
84
+ return /(^|\/)(__tests__|tests?|specs?)(\/|$)|\.(test|spec)\.[^.\/]+$/i.test(normalized);
85
+ }
86
+
87
+ export function isAuditSupportPath(filePath) {
88
+ const normalized = normalizePath(filePath);
89
+ if (!normalized) {
90
+ return true;
91
+ }
92
+ if (LOCKFILE_NAMES.has(basename(normalized))) {
93
+ return true;
94
+ }
95
+ if (/(^|\/)(node_modules|vendor|fixtures?|__fixtures__|mocks?|__mocks__|snapshots?|__snapshots__)(\/|$)/i.test(normalized)) {
96
+ return true;
97
+ }
98
+ if (/(^|\/)(generated|__generated__|auto-generated|coverage|dist|build|out|\.next|\.nuxt|\.turbo|\.cache)(\/|$)/i.test(normalized)) {
99
+ return true;
100
+ }
101
+ if (/^(docs?|guides?|adr|tasks?|\.github|\.vscode|\.husky|public)(\/|$)/i.test(normalized)) {
102
+ return true;
103
+ }
104
+ if (/(\.min|\.bundle|generated)\.[^.\/]+$/i.test(normalized)) {
105
+ return true;
106
+ }
107
+ return false;
108
+ }
109
+
110
+ export function isAuditSourceFile(file = {}) {
111
+ const filePath = typeof file === "string" ? file : file.path;
112
+ const normalized = normalizePath(filePath);
113
+ if (!normalized || isAuditTestPath(normalized) || isAuditSupportPath(normalized)) {
114
+ return false;
115
+ }
116
+ if (SOURCE_EXTENSIONS.has(extension(normalized))) {
117
+ return true;
118
+ }
119
+ return SOURCE_LANGUAGES.has(String(file.language || "").trim().toLowerCase());
120
+ }
@@ -1,3 +1,5 @@
1
+ import { isAuditSourceFile } from "./path-classification.js";
2
+
1
3
  function normalizeString(value) {
2
4
  return String(value || "").trim();
3
5
  }
@@ -21,6 +23,7 @@ function summarizeSeverity(findings = []) {
21
23
  function buildRuntimeHotspots(ingest = {}) {
22
24
  const indexed = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
23
25
  return indexed
26
+ .filter((file) => isAuditSourceFile(file))
24
27
  .filter((file) => Number(file.loc || 0) >= 280)
25
28
  .sort((left, right) => Number(right.loc || 0) - Number(left.loc || 0))
26
29
  .slice(0, 20)
@@ -28,7 +31,7 @@ function buildRuntimeHotspots(ingest = {}) {
28
31
  path: toPosixPath(file.path),
29
32
  loc: Number(file.loc || 0),
30
33
  language: file.language,
31
- severity: Number(file.loc || 0) >= 900 ? "P1" : "P2",
34
+ severity: "P2",
32
35
  }));
33
36
  }
34
37
 
@@ -1,3 +1,5 @@
1
+ import { isAuditSourceFile, isAuditTestPath } from "./path-classification.js";
2
+
1
3
  function normalizeString(value) {
2
4
  return String(value || "").trim();
3
5
  }
@@ -18,10 +20,6 @@ function summarizeSeverity(findings = []) {
18
20
  return summary;
19
21
  }
20
22
 
21
- function isTestPath(filePath) {
22
- return /(^|\/)(__tests__|tests?|spec)(\/|$)|\.(test|spec)\.[^.]+$/i.test(filePath);
23
- }
24
-
25
23
  function buildCoverageInventory(ingest = {}) {
26
24
  const indexedFiles = Array.isArray(ingest.indexedFiles?.files) ? ingest.indexedFiles.files : [];
27
25
  const normalized = indexedFiles.map((file) => ({
@@ -30,8 +28,8 @@ function buildCoverageInventory(ingest = {}) {
30
28
  loc: Number(file.loc || 0),
31
29
  }));
32
30
 
33
- const testFiles = normalized.filter((file) => isTestPath(file.path));
34
- const nonTestCodeFiles = normalized.filter((file) => !isTestPath(file.path) && file.loc > 0);
31
+ const testFiles = normalized.filter((file) => isAuditTestPath(file.path));
32
+ const nonTestCodeFiles = normalized.filter((file) => isAuditSourceFile(file) && file.loc > 0);
35
33
  const ratio = nonTestCodeFiles.length > 0 ? testFiles.length / nonTestCodeFiles.length : 0;
36
34
  const likelyGaps = nonTestCodeFiles
37
35
  .filter((file) => file.loc >= 260)
@@ -45,7 +43,7 @@ function buildCoverageInventory(ingest = {}) {
45
43
  path: file.path,
46
44
  loc: file.loc,
47
45
  language: file.language,
48
- severity: file.loc >= 550 ? "P1" : "P2",
46
+ severity: "P2",
49
47
  }));
50
48
 
51
49
  return {
@@ -0,0 +1,27 @@
1
+ export function resolveActionOptions(options = {}, command = null) {
2
+ const localOptions = options && typeof options.opts === "function" ? options.opts() : { ...options };
3
+ const parent = command?.parent;
4
+ if (!parent || typeof parent.opts !== "function") {
5
+ return localOptions;
6
+ }
7
+
8
+ const parentOptions = parent.opts();
9
+ const merged = {
10
+ ...parentOptions,
11
+ ...localOptions,
12
+ };
13
+
14
+ for (const [key, value] of Object.entries(parentOptions)) {
15
+ const parentSource =
16
+ typeof parent.getOptionValueSource === "function" ? parent.getOptionValueSource(key) : null;
17
+ const localSource =
18
+ typeof command?.getOptionValueSource === "function" ? command.getOptionValueSource(key) : null;
19
+ const parentWasProvided = parentSource && parentSource !== "default";
20
+ const localIsOnlyDefault = localSource === "default" || localOptions[key] === undefined;
21
+ if (parentWasProvided && localIsOnlyDefault) {
22
+ merged[key] = value;
23
+ }
24
+ }
25
+
26
+ return merged;
27
+ }
@@ -10,6 +10,7 @@ import { loadAuditRegistry, selectAuditAgents } from "../audit/registry.js";
10
10
  import { resolveOutputRoot } from "../config/service.js";
11
11
  import { createAgentEvent } from "../events/schema.js";
12
12
  import { createAuditSessionReporter, resolveAuditSessionId } from "../session/audit-reporter.js";
13
+ import { resolveActionOptions } from "./action-options.js";
13
14
  import { buildLegacyArgs } from "./legacy-args.js";
14
15
 
15
16
  function shouldEmitJson(options, command) {
@@ -213,16 +214,17 @@ export function registerAuditCommand(program, invokeLegacy) {
213
214
  .option("--output-dir <path>", "Optional artifact output root override")
214
215
  .option("--json", "Emit machine-readable output")
215
216
  .action(async (options, command) => {
216
- const emitJson = shouldEmitJson(options, command);
217
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
217
+ const actionOptions = resolveActionOptions(options, command);
218
+ const emitJson = shouldEmitJson(actionOptions, command);
219
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
218
220
  const outputRoot = await resolveOutputRoot({
219
221
  cwd: targetPath,
220
- outputDirOverride: options.outputDir,
222
+ outputDirOverride: actionOptions.outputDir,
221
223
  env: process.env,
222
224
  });
223
225
  const runDirectory = await resolveAuditRunDirectory({
224
226
  outputRoot,
225
- runId: options.runId,
227
+ runId: actionOptions.runId,
226
228
  });
227
229
  const { report } = await loadAuditRunReport(runDirectory);
228
230
  const ddPackage = await writeDdPackage({
@@ -264,11 +266,12 @@ export function registerAuditCommand(program, invokeLegacy) {
264
266
  .option("--refresh", "Refresh CODEBASE_INGEST before replay")
265
267
  .option("--json", "Emit machine-readable output")
266
268
  .action(async (runId, options, command) => {
267
- const emitJson = shouldEmitJson(options, command);
268
- const initialTargetPath = path.resolve(process.cwd(), String(options.path || "."));
269
+ const actionOptions = resolveActionOptions(options, command);
270
+ const emitJson = shouldEmitJson(actionOptions, command);
271
+ const initialTargetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
269
272
  const outputRoot = await resolveOutputRoot({
270
273
  cwd: initialTargetPath,
271
- outputDirOverride: options.outputDir,
274
+ outputDirOverride: actionOptions.outputDir,
272
275
  env: process.env,
273
276
  });
274
277
  const baseRunDirectory = await resolveAuditRunDirectory({
@@ -276,12 +279,12 @@ export function registerAuditCommand(program, invokeLegacy) {
276
279
  runId,
277
280
  });
278
281
  const { report: baseReport } = await loadAuditRunReport(baseRunDirectory);
279
- const targetPath = options.path
280
- ? path.resolve(process.cwd(), String(options.path || "."))
282
+ const targetPath = actionOptions.path
283
+ ? path.resolve(process.cwd(), String(actionOptions.path || "."))
281
284
  : path.resolve(String(baseReport.targetPath || "."));
282
285
 
283
286
  const registry = await loadAuditRegistry({
284
- registryFile: options.registryFile,
287
+ registryFile: actionOptions.registryFile,
285
288
  });
286
289
  const selected = selectAuditAgents(registry.agents, (baseReport.selectedAgents || []).join(","));
287
290
  if (selected.selected.length === 0) {
@@ -292,9 +295,9 @@ export function registerAuditCommand(program, invokeLegacy) {
292
295
  targetPath,
293
296
  agents: selected.selected,
294
297
  maxParallel: Number(baseReport.maxParallel || 1),
295
- outputDir: options.outputDir,
298
+ outputDir: actionOptions.outputDir,
296
299
  dryRun: Boolean(baseReport.dryRun),
297
- refreshIngest: Boolean(options.refresh),
300
+ refreshIngest: Boolean(actionOptions.refresh),
298
301
  isolation: baseReport.isolation || "strict",
299
302
  seedFromDeterministic: baseReport.seedFromDeterministic !== false,
300
303
  });
@@ -341,11 +344,12 @@ export function registerAuditCommand(program, invokeLegacy) {
341
344
  .option("--output-dir <path>", "Optional artifact output root override")
342
345
  .option("--json", "Emit machine-readable output")
343
346
  .action(async (baseRunId, candidateRunId, options, command) => {
344
- const emitJson = shouldEmitJson(options, command);
345
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
347
+ const actionOptions = resolveActionOptions(options, command);
348
+ const emitJson = shouldEmitJson(actionOptions, command);
349
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
346
350
  const outputRoot = await resolveOutputRoot({
347
351
  cwd: targetPath,
348
- outputDirOverride: options.outputDir,
352
+ outputDirOverride: actionOptions.outputDir,
349
353
  env: process.env,
350
354
  });
351
355
  const baseRunDirectory = await resolveAuditRunDirectory({
@@ -388,9 +392,10 @@ export function registerAuditCommand(program, invokeLegacy) {
388
392
  .option("--registry-file <path>", "Optional custom audit registry file")
389
393
  .option("--json", "Emit machine-readable output")
390
394
  .action(async (options, command) => {
391
- const emitJson = shouldEmitJson(options, command);
395
+ const actionOptions = resolveActionOptions(options, command);
396
+ const emitJson = shouldEmitJson(actionOptions, command);
392
397
  const registry = await loadAuditRegistry({
393
- registryFile: options.registryFile,
398
+ registryFile: actionOptions.registryFile,
394
399
  });
395
400
  const payload = {
396
401
  command: "audit registry",
@@ -422,10 +427,11 @@ export function registerAuditCommand(program, invokeLegacy) {
422
427
  .option("--dry-run", "Skip deterministic baseline and run security planning only")
423
428
  .option("--json", "Emit machine-readable output")
424
429
  .action(async (options, command) => {
425
- const emitJson = shouldEmitJson(options, command);
426
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
430
+ const actionOptions = resolveActionOptions(options, command);
431
+ const emitJson = shouldEmitJson(actionOptions, command);
432
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
427
433
  const registry = await loadAuditRegistry({
428
- registryFile: options.registryFile,
434
+ registryFile: actionOptions.registryFile,
429
435
  });
430
436
  const selected = selectAuditAgents(registry.agents, "security");
431
437
  if (selected.selected.length !== 1) {
@@ -436,9 +442,9 @@ export function registerAuditCommand(program, invokeLegacy) {
436
442
  targetPath,
437
443
  agents: selected.selected,
438
444
  maxParallel: 1,
439
- outputDir: options.outputDir,
440
- dryRun: Boolean(options.dryRun),
441
- refreshIngest: Boolean(options.refresh),
445
+ outputDir: actionOptions.outputDir,
446
+ dryRun: Boolean(actionOptions.dryRun),
447
+ refreshIngest: Boolean(actionOptions.refresh),
442
448
  });
443
449
  const securityAgent = result.agentResults.find((agent) => agent.agentId === "security") || null;
444
450
 
@@ -486,10 +492,11 @@ export function registerAuditCommand(program, invokeLegacy) {
486
492
  .option("--dry-run", "Skip deterministic baseline and run architecture planning only")
487
493
  .option("--json", "Emit machine-readable output")
488
494
  .action(async (options, command) => {
489
- const emitJson = shouldEmitJson(options, command);
490
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
495
+ const actionOptions = resolveActionOptions(options, command);
496
+ const emitJson = shouldEmitJson(actionOptions, command);
497
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
491
498
  const registry = await loadAuditRegistry({
492
- registryFile: options.registryFile,
499
+ registryFile: actionOptions.registryFile,
493
500
  });
494
501
  const selected = selectAuditAgents(registry.agents, "architecture");
495
502
  if (selected.selected.length !== 1) {
@@ -500,9 +507,9 @@ export function registerAuditCommand(program, invokeLegacy) {
500
507
  targetPath,
501
508
  agents: selected.selected,
502
509
  maxParallel: 1,
503
- outputDir: options.outputDir,
504
- dryRun: Boolean(options.dryRun),
505
- refreshIngest: Boolean(options.refresh),
510
+ outputDir: actionOptions.outputDir,
511
+ dryRun: Boolean(actionOptions.dryRun),
512
+ refreshIngest: Boolean(actionOptions.refresh),
506
513
  });
507
514
  const architectureAgent =
508
515
  result.agentResults.find((agent) => agent.agentId === "architecture") || null;
@@ -551,10 +558,11 @@ export function registerAuditCommand(program, invokeLegacy) {
551
558
  .option("--dry-run", "Skip deterministic baseline and run testing planning only")
552
559
  .option("--json", "Emit machine-readable output")
553
560
  .action(async (options, command) => {
554
- const emitJson = shouldEmitJson(options, command);
555
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
561
+ const actionOptions = resolveActionOptions(options, command);
562
+ const emitJson = shouldEmitJson(actionOptions, command);
563
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
556
564
  const registry = await loadAuditRegistry({
557
- registryFile: options.registryFile,
565
+ registryFile: actionOptions.registryFile,
558
566
  });
559
567
  const selected = selectAuditAgents(registry.agents, "testing");
560
568
  if (selected.selected.length !== 1) {
@@ -565,9 +573,9 @@ export function registerAuditCommand(program, invokeLegacy) {
565
573
  targetPath,
566
574
  agents: selected.selected,
567
575
  maxParallel: 1,
568
- outputDir: options.outputDir,
569
- dryRun: Boolean(options.dryRun),
570
- refreshIngest: Boolean(options.refresh),
576
+ outputDir: actionOptions.outputDir,
577
+ dryRun: Boolean(actionOptions.dryRun),
578
+ refreshIngest: Boolean(actionOptions.refresh),
571
579
  });
572
580
  const testingAgent = result.agentResults.find((agent) => agent.agentId === "testing") || null;
573
581
 
@@ -615,10 +623,11 @@ export function registerAuditCommand(program, invokeLegacy) {
615
623
  .option("--dry-run", "Skip deterministic baseline and run performance planning only")
616
624
  .option("--json", "Emit machine-readable output")
617
625
  .action(async (options, command) => {
618
- const emitJson = shouldEmitJson(options, command);
619
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
626
+ const actionOptions = resolveActionOptions(options, command);
627
+ const emitJson = shouldEmitJson(actionOptions, command);
628
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
620
629
  const registry = await loadAuditRegistry({
621
- registryFile: options.registryFile,
630
+ registryFile: actionOptions.registryFile,
622
631
  });
623
632
  const selected = selectAuditAgents(registry.agents, "performance");
624
633
  if (selected.selected.length !== 1) {
@@ -629,9 +638,9 @@ export function registerAuditCommand(program, invokeLegacy) {
629
638
  targetPath,
630
639
  agents: selected.selected,
631
640
  maxParallel: 1,
632
- outputDir: options.outputDir,
633
- dryRun: Boolean(options.dryRun),
634
- refreshIngest: Boolean(options.refresh),
641
+ outputDir: actionOptions.outputDir,
642
+ dryRun: Boolean(actionOptions.dryRun),
643
+ refreshIngest: Boolean(actionOptions.refresh),
635
644
  });
636
645
  const performanceAgent =
637
646
  result.agentResults.find((agent) => agent.agentId === "performance") || null;
@@ -680,10 +689,11 @@ export function registerAuditCommand(program, invokeLegacy) {
680
689
  .option("--dry-run", "Skip deterministic baseline and run compliance planning only")
681
690
  .option("--json", "Emit machine-readable output")
682
691
  .action(async (options, command) => {
683
- const emitJson = shouldEmitJson(options, command);
684
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
692
+ const actionOptions = resolveActionOptions(options, command);
693
+ const emitJson = shouldEmitJson(actionOptions, command);
694
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
685
695
  const registry = await loadAuditRegistry({
686
- registryFile: options.registryFile,
696
+ registryFile: actionOptions.registryFile,
687
697
  });
688
698
  const selected = selectAuditAgents(registry.agents, "compliance");
689
699
  if (selected.selected.length !== 1) {
@@ -694,9 +704,9 @@ export function registerAuditCommand(program, invokeLegacy) {
694
704
  targetPath,
695
705
  agents: selected.selected,
696
706
  maxParallel: 1,
697
- outputDir: options.outputDir,
698
- dryRun: Boolean(options.dryRun),
699
- refreshIngest: Boolean(options.refresh),
707
+ outputDir: actionOptions.outputDir,
708
+ dryRun: Boolean(actionOptions.dryRun),
709
+ refreshIngest: Boolean(actionOptions.refresh),
700
710
  });
701
711
  const complianceAgent =
702
712
  result.agentResults.find((agent) => agent.agentId === "compliance") || null;
@@ -745,10 +755,11 @@ export function registerAuditCommand(program, invokeLegacy) {
745
755
  .option("--dry-run", "Skip deterministic baseline and run documentation planning only")
746
756
  .option("--json", "Emit machine-readable output")
747
757
  .action(async (options, command) => {
748
- const emitJson = shouldEmitJson(options, command);
749
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
758
+ const actionOptions = resolveActionOptions(options, command);
759
+ const emitJson = shouldEmitJson(actionOptions, command);
760
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
750
761
  const registry = await loadAuditRegistry({
751
- registryFile: options.registryFile,
762
+ registryFile: actionOptions.registryFile,
752
763
  });
753
764
  const selected = selectAuditAgents(registry.agents, "documentation");
754
765
  if (selected.selected.length !== 1) {
@@ -759,9 +770,9 @@ export function registerAuditCommand(program, invokeLegacy) {
759
770
  targetPath,
760
771
  agents: selected.selected,
761
772
  maxParallel: 1,
762
- outputDir: options.outputDir,
763
- dryRun: Boolean(options.dryRun),
764
- refreshIngest: Boolean(options.refresh),
773
+ outputDir: actionOptions.outputDir,
774
+ dryRun: Boolean(actionOptions.dryRun),
775
+ refreshIngest: Boolean(actionOptions.refresh),
765
776
  });
766
777
  const documentationAgent =
767
778
  result.agentResults.find((agent) => agent.agentId === "documentation") || null;
@@ -808,8 +819,9 @@ export function registerAuditCommand(program, invokeLegacy) {
808
819
  .option("--reuse-omargate <runId>", "Reuse deterministic findings from an OmarGate run id or latest")
809
820
  .option("--json", "Emit machine-readable output")
810
821
  .action(async (options, command) => {
822
+ const actionOptions = resolveActionOptions(options, command);
811
823
  const legacyArgs = buildLegacyArgs(["/audit"], {
812
- commandOptions: options,
824
+ commandOptions: actionOptions,
813
825
  command,
814
826
  });
815
827
  await invokeLegacy(legacyArgs);
@@ -848,9 +860,10 @@ export function registerAuditCommand(program, invokeLegacy) {
848
860
  .option("--output-dir <path>", "Artifact output root override")
849
861
  .option("--json", "Emit machine-readable final output")
850
862
  .action(async (options, command) => {
851
- const emitJson = shouldEmitJson(options, command);
852
- const emitStream = Boolean(options.stream);
853
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
863
+ const actionOptions = resolveActionOptions(options, command);
864
+ const emitJson = shouldEmitJson(actionOptions, command);
865
+ const emitStream = Boolean(actionOptions.stream);
866
+ const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
854
867
 
855
868
  // Lazy-load modules
856
869
  const { julesAuditLoop } = await import("../agents/jules/loop.js");
@@ -861,7 +874,7 @@ export function registerAuditCommand(program, invokeLegacy) {
861
874
  const { runDeterministicReviewPipeline } = await import("../review/local-review.js");
862
875
  const fsp = await import("node:fs/promises");
863
876
 
864
- const outputRoot = await resolveOut({ targetPath, outputDir: options.outputDir });
877
+ const outputRoot = await resolveOut({ targetPath, outputDir: actionOptions.outputDir });
865
878
  const artifactDir = path.join(outputRoot, "reports", "jules-" + Date.now());
866
879
  await fsp.mkdir(artifactDir, { recursive: true });
867
880
 
@@ -876,7 +889,7 @@ export function registerAuditCommand(program, invokeLegacy) {
876
889
  try {
877
890
  ingest = await generateCodebaseIngest({
878
891
  rootPath: targetPath, outputDir: outputRoot,
879
- refresh: Boolean(options.refresh),
892
+ refresh: Boolean(actionOptions.refresh),
880
893
  });
881
894
  } catch {
882
895
  ingest = await collectCodebaseIngest({ rootPath: targetPath });
@@ -887,7 +900,7 @@ export function registerAuditCommand(program, invokeLegacy) {
887
900
 
888
901
  // ── [2] OMAR BASELINE (blind — Jules won't see until reconciliation) ──
889
902
  let omarBaseline = null;
890
- if (!options.skipBaseline) {
903
+ if (!actionOptions.skipBaseline) {
891
904
  emitProgress(onEvent, JULES_DEFINITION, "Running Omar 7-layer deterministic baseline...");
892
905
  try {
893
906
  omarBaseline = await runDeterministicReviewPipeline({
@@ -925,14 +938,14 @@ export function registerAuditCommand(program, invokeLegacy) {
925
938
 
926
939
  // ── [5] PROVIDER ──────────────────────────────────────────────
927
940
  const providerConfig = {};
928
- if (options.provider) providerConfig.provider = options.provider;
929
- if (options.model) providerConfig.model = options.model;
930
- if (options.apiKey) providerConfig.apiKey = options.apiKey;
941
+ if (actionOptions.provider) providerConfig.provider = actionOptions.provider;
942
+ if (actionOptions.model) providerConfig.model = actionOptions.model;
943
+ if (actionOptions.apiKey) providerConfig.apiKey = actionOptions.apiKey;
931
944
 
932
945
  // ── [6] SYSTEM PROMPT (full production prompt) ─────────────────
933
946
  const { buildJulesProductionPrompt } = await import("../agents/jules/config/system-prompt.js");
934
947
  const systemPrompt = buildJulesProductionPrompt({
935
- mode: options.mode,
948
+ mode: actionOptions.mode,
936
949
  framework: ingest?.frameworks?.[0] || "unknown",
937
950
  componentCount: ingest?.indexedFiles?.files?.filter(
938
951
  f => /\.(tsx|jsx|vue|svelte)$/.test(f.path || ""),
@@ -952,14 +965,14 @@ export function registerAuditCommand(program, invokeLegacy) {
952
965
  blackboard,
953
966
  memory: memoryIndex,
954
967
  budget: {
955
- maxCostUsd: parseFloat(options.maxCost) || 5.0,
968
+ maxCostUsd: parseFloat(actionOptions.maxCost) || 5.0,
956
969
  maxOutputTokens: JULES_DEFINITION.budget.maxOutputTokens,
957
970
  maxRuntimeMs: JULES_DEFINITION.budget.maxRuntimeMs,
958
971
  maxToolCalls: JULES_DEFINITION.budget.maxToolCalls,
959
972
  },
960
973
  provider: providerConfig,
961
- mode: options.mode,
962
- maxTurns: parseInt(options.maxTurns) || 25,
974
+ mode: actionOptions.mode,
975
+ maxTurns: parseInt(actionOptions.maxTurns) || 25,
963
976
  onEvent,
964
977
  });
965
978
 
@@ -973,8 +986,8 @@ export function registerAuditCommand(program, invokeLegacy) {
973
986
 
974
987
  // ── [7.5] RUNTIME AUDIT (if --url provided or URL detected) ────
975
988
  let runtimeResult = null;
976
- const runtimeUrl = options.url || null;
977
- if (runtimeUrl || !options.skipRuntime) {
989
+ const runtimeUrl = actionOptions.url || null;
990
+ if (runtimeUrl || !actionOptions.skipRuntime) {
978
991
  emitProgress(onEvent, JULES_DEFINITION, "Checking for deployed URL...");
979
992
  try {
980
993
  const { runtimeAudit: runRT } = await import("../agents/jules/tools/runtime-audit.js");
@@ -1057,7 +1070,7 @@ export function registerAuditCommand(program, invokeLegacy) {
1057
1070
  command: "audit frontend",
1058
1071
  persona: JULES_DEFINITION.persona,
1059
1072
  targetPath,
1060
- mode: options.mode,
1073
+ mode: actionOptions.mode,
1061
1074
  framework: ingest?.frameworks?.[0] || "unknown",
1062
1075
  findings: allFindings,
1063
1076
  summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
@@ -1109,7 +1122,7 @@ export function registerAuditCommand(program, invokeLegacy) {
1109
1122
  const syncResult = await syncRunToDashboard({
1110
1123
  command: "audit frontend",
1111
1124
  persona: JULES_DEFINITION.persona,
1112
- mode: options.mode,
1125
+ mode: actionOptions.mode,
1113
1126
  framework: ingest?.frameworks?.[0] || "unknown",
1114
1127
  summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
1115
1128
  usage: report?.usage || {},
@@ -25,6 +25,7 @@ import {
25
25
  writeReviewComparisonArtifact,
26
26
  writeReviewRunContext,
27
27
  } from "../review/replay.js";
28
+ import { resolveActionOptions } from "./action-options.js";
28
29
 
29
30
  function shouldEmitJson(options, command) {
30
31
  const local = Boolean(options && options.json);
@@ -236,16 +237,17 @@ function registerVerdictCommand(review, verdict) {
236
237
  .option("--actor <id>", "Operator identifier for audit trail", "")
237
238
  .option("--json", "Emit machine-readable output")
238
239
  .action(async (findingId, options, command) => {
239
- const emitJson = shouldEmitJson(options, command);
240
- const targetPath = resolveTargetPath(".", options);
240
+ const actionOptions = resolveActionOptions(options, command);
241
+ const emitJson = shouldEmitJson(actionOptions, command);
242
+ const targetPath = resolveTargetPath(".", actionOptions);
241
243
  const result = await recordReviewDecision({
242
244
  targetPath,
243
- runId: options.runId,
244
- outputDir: options.outputDir,
245
+ runId: actionOptions.runId,
246
+ outputDir: actionOptions.outputDir,
245
247
  findingId,
246
248
  verdict,
247
- note: options.note,
248
- actor: options.actor,
249
+ note: actionOptions.note,
250
+ actor: actionOptions.actor,
249
251
  env: process.env,
250
252
  });
251
253
 
@@ -374,12 +376,13 @@ export function registerReviewCommand(program) {
374
376
  .option("--output-dir <path>", "Optional artifact output root override")
375
377
  .option("--json", "Emit machine-readable output")
376
378
  .action(async (options, command) => {
377
- const emitJson = shouldEmitJson(options, command);
378
- const targetPath = resolveTargetPath(".", options);
379
+ const actionOptions = resolveActionOptions(options, command);
380
+ const emitJson = shouldEmitJson(actionOptions, command);
381
+ const targetPath = resolveTargetPath(".", actionOptions);
379
382
  const loaded = await loadUnifiedReviewReport({
380
383
  targetPath,
381
- runId: options.runId,
382
- outputDir: options.outputDir,
384
+ runId: actionOptions.runId,
385
+ outputDir: actionOptions.outputDir,
383
386
  env: process.env,
384
387
  });
385
388
 
@@ -412,14 +415,15 @@ export function registerReviewCommand(program) {
412
415
  .option("--output-file <path>", "Optional custom export output path")
413
416
  .option("--json", "Emit machine-readable output")
414
417
  .action(async (options, command) => {
415
- const emitJson = shouldEmitJson(options, command);
416
- const targetPath = resolveTargetPath(".", options);
418
+ const actionOptions = resolveActionOptions(options, command);
419
+ const emitJson = shouldEmitJson(actionOptions, command);
420
+ const targetPath = resolveTargetPath(".", actionOptions);
417
421
  const exported = await exportUnifiedReviewReport({
418
422
  targetPath,
419
- runId: options.runId,
420
- outputDir: options.outputDir,
421
- format: options.format,
422
- outputFile: options.outputFile,
423
+ runId: actionOptions.runId,
424
+ outputDir: actionOptions.outputDir,
425
+ format: actionOptions.format,
426
+ outputFile: actionOptions.outputFile,
423
427
  env: process.env,
424
428
  });
425
429
 
@@ -453,12 +457,13 @@ export function registerReviewCommand(program) {
453
457
  .option("--ai-dry-run", "Force AI dry-run during replay")
454
458
  .option("--json", "Emit machine-readable output")
455
459
  .action(async (runId, options, command) => {
456
- const emitJson = shouldEmitJson(options, command);
457
- const lookupPath = resolveTargetPath(".", options);
460
+ const actionOptions = resolveActionOptions(options, command);
461
+ const emitJson = shouldEmitJson(actionOptions, command);
462
+ const lookupPath = resolveTargetPath(".", actionOptions);
458
463
  const source = await loadUnifiedReviewReport({
459
464
  targetPath: lookupPath,
460
465
  runId,
461
- outputDir: options.outputDir,
466
+ outputDir: actionOptions.outputDir,
462
467
  env: process.env,
463
468
  });
464
469
 
@@ -470,20 +475,20 @@ export function registerReviewCommand(program) {
470
475
  }
471
476
 
472
477
  const invocation = sourceContext?.context?.invocation || {};
473
- const replayTargetPath = options.path
474
- ? resolveTargetPath(".", options)
478
+ const replayTargetPath = actionOptions.path
479
+ ? resolveTargetPath(".", actionOptions)
475
480
  : path.resolve(sourceContext?.context?.targetPath || source.report.targetPath || lookupPath);
476
- const aiEnabled = options.noAi ? false : Boolean(invocation.aiEnabled);
481
+ const aiEnabled = actionOptions.noAi ? false : Boolean(invocation.aiEnabled);
477
482
 
478
483
  const replayOutcome = await executeReviewRun({
479
484
  targetPath: replayTargetPath,
480
485
  mode: sourceContext?.context?.mode || source.report.mode || "full",
481
- outputDir: options.outputDir || invocation.outputDir || "",
482
- specFile: options.spec || invocation.specFile || "",
483
- refreshIngest: Boolean(options.refresh || invocation.refreshIngest),
486
+ outputDir: actionOptions.outputDir || invocation.outputDir || "",
487
+ specFile: actionOptions.spec || invocation.specFile || "",
488
+ refreshIngest: Boolean(actionOptions.refresh || invocation.refreshIngest),
484
489
  aiConfig: {
485
490
  enable: aiEnabled,
486
- aiDryRun: Boolean(options.aiDryRun || invocation.aiDryRun),
491
+ aiDryRun: Boolean(actionOptions.aiDryRun || invocation.aiDryRun),
487
492
  provider: invocation.provider,
488
493
  model: invocation.model,
489
494
  sessionId: invocation.sessionId,
@@ -542,18 +547,19 @@ export function registerReviewCommand(program) {
542
547
  .option("--output-file <path>", "Optional custom output path for comparison JSON")
543
548
  .option("--json", "Emit machine-readable output")
544
549
  .action(async (baseRunId, candidateRunId, options, command) => {
545
- const emitJson = shouldEmitJson(options, command);
546
- const targetPath = resolveTargetPath(".", options);
550
+ const actionOptions = resolveActionOptions(options, command);
551
+ const emitJson = shouldEmitJson(actionOptions, command);
552
+ const targetPath = resolveTargetPath(".", actionOptions);
547
553
  const base = await loadUnifiedReviewReport({
548
554
  targetPath,
549
555
  runId: baseRunId,
550
- outputDir: options.outputDir,
556
+ outputDir: actionOptions.outputDir,
551
557
  env: process.env,
552
558
  });
553
559
  const candidate = await loadUnifiedReviewReport({
554
560
  targetPath,
555
561
  runId: candidateRunId,
556
- outputDir: options.outputDir,
562
+ outputDir: actionOptions.outputDir,
557
563
  env: process.env,
558
564
  });
559
565
 
@@ -566,8 +572,8 @@ export function registerReviewCommand(program) {
566
572
  });
567
573
 
568
574
  let outputPath = artifact.artifactPath;
569
- if (options.outputFile) {
570
- outputPath = path.resolve(targetPath, String(options.outputFile));
575
+ if (actionOptions.outputFile) {
576
+ outputPath = path.resolve(targetPath, String(actionOptions.outputFile));
571
577
  await fsp.mkdir(path.dirname(outputPath), { recursive: true });
572
578
  await fsp.writeFile(outputPath, `${JSON.stringify(artifact.payload, null, 2)}\n`, "utf-8");
573
579
  }
@@ -611,13 +617,14 @@ export function registerReviewCommand(program) {
611
617
  .option("--refresh", "Refresh CODEBASE_INGEST prior to compatibility scan")
612
618
  .option("--json", "Emit machine-readable output")
613
619
  .action(async (options, command) => {
614
- const emitJson = shouldEmitJson(options, command);
615
- const targetPath = resolveTargetPath(".", options);
620
+ const actionOptions = resolveActionOptions(options, command);
621
+ const emitJson = shouldEmitJson(actionOptions, command);
622
+ const targetPath = resolveTargetPath(".", actionOptions);
616
623
  let ingestRefresh = null;
617
- if (options.refresh) {
624
+ if (actionOptions.refresh) {
618
625
  const ingestResolution = await resolveCodebaseIngest({
619
626
  rootPath: targetPath,
620
- outputDir: options.outputDir,
627
+ outputDir: actionOptions.outputDir,
621
628
  refresh: true,
622
629
  });
623
630
  ingestRefresh = {
@@ -630,13 +637,13 @@ export function registerReviewCommand(program) {
630
637
  contentHash: ingestResolution.fingerprint?.contentHash || "",
631
638
  };
632
639
  }
633
- const mode = resolveModeFromOptions(options, {
634
- defaultMode: String(options.mode || "full").trim().toLowerCase() || "full",
640
+ const mode = resolveModeFromOptions(actionOptions, {
641
+ defaultMode: String(actionOptions.mode || "full").trim().toLowerCase() || "full",
635
642
  });
636
643
  const scan = await runLocalReviewScan({
637
644
  targetPath,
638
645
  mode,
639
- specFile: options.spec,
646
+ specFile: actionOptions.spec,
640
647
  });
641
648
 
642
649
  const report = `# Local Review Scan
@@ -660,7 +667,7 @@ ${formatFindingsMarkdown(scan.findings)}
660
667
  const reportPath = await writeReviewReport({
661
668
  targetPath: scan.targetPath,
662
669
  mode: scan.mode,
663
- outputDir: options.outputDir,
670
+ outputDir: actionOptions.outputDir,
664
671
  reportMarkdown: report,
665
672
  });
666
673