code-auditor-mcp 3.7.0 → 3.8.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.
Files changed (129) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +71 -1
  3. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  4. package/dist/analyzers/cross-language/SchemaValidator.js +122 -43
  5. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  6. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
  7. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +6 -7
  8. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  9. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  10. package/dist/analyzers/ruleRegistry.js +105 -18
  11. package/dist/analyzers/ruleRegistry.js.map +1 -1
  12. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +9 -1
  13. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  14. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +63 -44
  15. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  16. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  17. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +76 -28
  18. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  19. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +73 -13
  20. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  21. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +12 -2
  22. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  23. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +30 -18
  24. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  25. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -8
  26. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  27. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +54 -45
  28. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  29. package/dist/analyzers/universal/functionConcerns.d.ts +9 -4
  30. package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
  31. package/dist/analyzers/universal/functionConcerns.js +25 -11
  32. package/dist/analyzers/universal/functionConcerns.js.map +1 -1
  33. package/dist/analyzers/universal/schema/codeAnalysis.js +4 -4
  34. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  35. package/dist/analyzers/universal/schema/jsonSchema.js +124 -11
  36. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
  37. package/dist/auditRouter.d.ts.map +1 -1
  38. package/dist/auditRouter.js +4 -6
  39. package/dist/auditRouter.js.map +1 -1
  40. package/dist/auditRunner.js +1 -1
  41. package/dist/auditRunner.js.map +1 -1
  42. package/dist/cli.js +284 -75
  43. package/dist/cli.js.map +1 -1
  44. package/dist/codeIndexDB.d.ts +6 -0
  45. package/dist/codeIndexDB.d.ts.map +1 -1
  46. package/dist/codeIndexDB.js +54 -0
  47. package/dist/codeIndexDB.js.map +1 -1
  48. package/dist/config/configLoader.d.ts.map +1 -1
  49. package/dist/config/configLoader.js +23 -0
  50. package/dist/config/configLoader.js.map +1 -1
  51. package/dist/config/defaults.d.ts +3 -0
  52. package/dist/config/defaults.d.ts.map +1 -1
  53. package/dist/config/defaults.js +30 -8
  54. package/dist/config/defaults.js.map +1 -1
  55. package/dist/conventions/conventionMiner.d.ts +18 -3
  56. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  57. package/dist/conventions/conventionMiner.js +130 -12
  58. package/dist/conventions/conventionMiner.js.map +1 -1
  59. package/dist/daemon/args.d.ts +24 -0
  60. package/dist/daemon/args.d.ts.map +1 -0
  61. package/dist/daemon/args.js +60 -0
  62. package/dist/daemon/args.js.map +1 -0
  63. package/dist/daemon/core.d.ts +128 -0
  64. package/dist/daemon/core.d.ts.map +1 -0
  65. package/dist/daemon/core.js +700 -0
  66. package/dist/daemon/core.js.map +1 -0
  67. package/dist/daemon/lspServer.d.ts +51 -0
  68. package/dist/daemon/lspServer.d.ts.map +1 -0
  69. package/dist/daemon/lspServer.js +168 -0
  70. package/dist/daemon/lspServer.js.map +1 -0
  71. package/dist/daemon/main.d.ts +34 -0
  72. package/dist/daemon/main.d.ts.map +1 -0
  73. package/dist/daemon/main.js +136 -0
  74. package/dist/daemon/main.js.map +1 -0
  75. package/dist/daemon/resolve.d.ts +41 -0
  76. package/dist/daemon/resolve.d.ts.map +1 -0
  77. package/dist/daemon/resolve.js +90 -0
  78. package/dist/daemon/resolve.js.map +1 -0
  79. package/dist/daemon/socketClient.d.ts +21 -0
  80. package/dist/daemon/socketClient.d.ts.map +1 -0
  81. package/dist/daemon/socketClient.js +60 -0
  82. package/dist/daemon/socketClient.js.map +1 -0
  83. package/dist/daemon/socketServer.d.ts +33 -0
  84. package/dist/daemon/socketServer.d.ts.map +1 -0
  85. package/dist/daemon/socketServer.js +106 -0
  86. package/dist/daemon/socketServer.js.map +1 -0
  87. package/dist/daemon/types.d.ts +78 -0
  88. package/dist/daemon/types.d.ts.map +1 -0
  89. package/dist/daemon/types.js +10 -0
  90. package/dist/daemon/types.js.map +1 -0
  91. package/dist/dataPaths.d.ts +21 -0
  92. package/dist/dataPaths.d.ts.map +1 -1
  93. package/dist/dataPaths.js +27 -0
  94. package/dist/dataPaths.js.map +1 -1
  95. package/dist/hooks/core.d.ts.map +1 -1
  96. package/dist/hooks/core.js +21 -4
  97. package/dist/hooks/core.js.map +1 -1
  98. package/dist/languages/RuntimeManager.js +10 -0
  99. package/dist/languages/RuntimeManager.js.map +1 -1
  100. package/dist/languages/go/analyzer +0 -0
  101. package/dist/languages/go/analyzer-src/analyzer.go +166 -35
  102. package/dist/languages/go/analyzer-src/solid.go +80 -172
  103. package/dist/languages/go/analyzer-src/types.go +18 -18
  104. package/dist/ledger.d.ts +1 -1
  105. package/dist/ledger.d.ts.map +1 -1
  106. package/dist/ledger.js +3 -3
  107. package/dist/ledger.js.map +1 -1
  108. package/dist/mcpAuditJobs.js +1 -1
  109. package/dist/mcpAuditJobs.js.map +1 -1
  110. package/dist/nextFileIncremental.d.ts +9 -3
  111. package/dist/nextFileIncremental.d.ts.map +1 -1
  112. package/dist/nextFileIncremental.js +32 -6
  113. package/dist/nextFileIncremental.js.map +1 -1
  114. package/dist/pipeline.d.ts +26 -0
  115. package/dist/pipeline.d.ts.map +1 -1
  116. package/dist/pipeline.js +86 -10
  117. package/dist/pipeline.js.map +1 -1
  118. package/dist/pipelineAdapters.d.ts +1 -1
  119. package/dist/pipelineAdapters.js +1 -1
  120. package/dist/reporting/csvReportGenerator.js +1 -1
  121. package/dist/ruleAliases.d.ts.map +1 -1
  122. package/dist/ruleAliases.js +45 -5
  123. package/dist/ruleAliases.js.map +1 -1
  124. package/dist/types.d.ts +23 -1
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js.map +1 -1
  127. package/package.json +5 -2
  128. package/plugin/.claude-plugin/plugin.json +1 -1
  129. package/plugin/skills/code-auditor/SKILL.md +6 -6
package/dist/cli.js CHANGED
@@ -406,7 +406,6 @@ program
406
406
  .option('-p, --path <projectPath>', 'Project root path', process.cwd())
407
407
  .action(async (paths, options) => {
408
408
  try {
409
- await initParsers();
410
409
  const fileSet = new Set();
411
410
  // Collect paths from stdin if requested
412
411
  if (options.stdin) {
@@ -427,18 +426,43 @@ program
427
426
  }
428
427
  // Resolve scope
429
428
  let scope = 'changed';
429
+ let resolvedPaths = [];
430
430
  if (fileSet.size > 0) {
431
431
  // Convert to absolute paths
432
- const resolved = [...fileSet].map((f) => isAbsolute(f) ? f : resolve(process.cwd(), f));
433
- scope = resolved;
432
+ resolvedPaths = [...fileSet].map((f) => isAbsolute(f) ? f : resolve(process.cwd(), f));
433
+ scope = resolvedPaths;
434
+ }
435
+ // R2 — resolve to a live daemon first (fast path); fall back in-process.
436
+ // The daemon serves per-file diagnostics for exactly the changed set. It is
437
+ // only trusted when ready and none of the queried files are stale (R4): a
438
+ // just-edited file whose re-audit hasn't landed falls through in-process so
439
+ // the gate never misses a finding. SARIF needs full run metadata, so it
440
+ // always runs in-process.
441
+ const projectRoot = resolve(options.path || process.cwd());
442
+ let result;
443
+ if (resolvedPaths.length > 0 && options.format !== 'sarif') {
444
+ const { resolveDaemon, readDaemonDiagnostics } = await import('./daemon/resolve.js');
445
+ const daemon = await resolveDaemon(projectRoot);
446
+ if (daemon.mode === 'not-ready') {
447
+ reportDaemonIndexing(daemon.state, !!options.json);
448
+ return;
449
+ }
450
+ if (daemon.mode === 'ready') {
451
+ const diag = await readDaemonDiagnostics(daemon.socketPath, resolvedPaths);
452
+ if (diag && diag.status === 'ready' && diag.staleFiles.length === 0) {
453
+ result = await buildChangedResultFromDiagnostics(diag.diagnostics, projectRoot);
454
+ }
455
+ }
456
+ }
457
+ if (!result) {
458
+ await initParsers();
459
+ const runner = createAuditRunner({
460
+ projectRoot: options.path,
461
+ scope,
462
+ analyzerConcurrency: 4
463
+ });
464
+ result = await runner.run();
434
465
  }
435
- // Create runner
436
- const runner = createAuditRunner({
437
- projectRoot: options.path,
438
- scope,
439
- analyzerConcurrency: 4
440
- });
441
- const result = await runner.run();
442
466
  // Collect all violations
443
467
  const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
444
468
  // Blocking gate decision (Spec 45 R1/R2/R4), computed once so the
@@ -473,7 +497,7 @@ program
473
497
  const col = v.column ?? v.start?.column ?? 1;
474
498
  return {
475
499
  analyzer: v.analyzer || '',
476
- rule: v.rule || v.type || '',
500
+ rule: v.rule,
477
501
  severity: v.severity,
478
502
  message: v.message,
479
503
  file: filePath,
@@ -586,7 +610,7 @@ function summarizeFindings(violations) {
586
610
  bySeverity.set(severity, (bySeverity.get(severity) ?? 0) + 1);
587
611
  const analyzer = v.analyzer ?? 'unknown';
588
612
  byAnalyzer.set(analyzer, (byAnalyzer.get(analyzer) ?? 0) + 1);
589
- const rule = v.rule || v.type || 'unknown';
613
+ const rule = v.rule;
590
614
  byRule.set(rule, (byRule.get(rule) ?? 0) + 1);
591
615
  }
592
616
  return { bySeverity, byAnalyzer, byRule };
@@ -604,6 +628,32 @@ function printCountSummary(violations) {
604
628
  console.log(`by analyzer: ${groupedCounts(byAnalyzer)}`);
605
629
  console.log(`by rule: ${groupedCounts(byRule)}`);
606
630
  }
631
+ /**
632
+ * Reconstruct a `changed`-compatible result from daemon-served diagnostics so the
633
+ * gate/output logic below stays identical to the in-process path. Gate severities
634
+ * are read from the project config (one JSON file, no parsing) so a custom
635
+ * `gateSeverities` never diverges from what the in-process run would have used.
636
+ */
637
+ async function buildChangedResultFromDiagnostics(violations, projectRoot) {
638
+ const analyzerResults = {};
639
+ for (const v of violations) {
640
+ const key = v.analyzer || 'unknown';
641
+ (analyzerResults[key] = analyzerResults[key] || { violations: [] }).violations.push(v);
642
+ }
643
+ let configUsed;
644
+ try {
645
+ const { findConfigFileUp, loadConfig } = await import('./config/configLoader.js');
646
+ const configPath = await findConfigFileUp(projectRoot);
647
+ const config = await loadConfig({ configPath: configPath ?? undefined });
648
+ if (Array.isArray(config.gateSeverities) && config.gateSeverities.length > 0) {
649
+ configUsed = { gateSeverities: config.gateSeverities };
650
+ }
651
+ }
652
+ catch {
653
+ // No config — the gate falls back to DEFAULT_BLOCKING_SEVERITIES, matching in-process.
654
+ }
655
+ return { analyzerResults, metadata: { configUsed, diagnostics: [] } };
656
+ }
607
657
  // Self-audit gate (Spec 33 Item 15 + Spec 44 remediation). Runs the full
608
658
  // analyzer pipeline over the tool's own production source and asserts zero
609
659
  // *blocking* (critical/warning) findings in the self-audit scope — the same
@@ -699,7 +749,7 @@ program
699
749
  }
700
750
  return {
701
751
  analyzer: v.analyzer || '',
702
- rule: v.rule || v.type || '',
752
+ rule: v.rule,
703
753
  severity: v.severity,
704
754
  message: v.message,
705
755
  file: filePath,
@@ -752,78 +802,100 @@ program
752
802
  .option('--json', 'Output a single JSON object (or {done:true}) to stdout')
753
803
  .action(async (options) => {
754
804
  try {
805
+ const projectDir = resolve(options.path || process.cwd());
806
+ // R2 — resolve to a live daemon first; fall back in-process when absent.
807
+ // The daemon is an optimisation: a ready daemon serves the current ranking
808
+ // without re-auditing, and an indexing daemon reports its status (R3).
809
+ const { resolveDaemon, readDaemonFindings } = await import('./daemon/resolve.js');
810
+ const daemon = await resolveDaemon(projectDir);
811
+ if (daemon.mode === 'not-ready') {
812
+ reportDaemonIndexing(daemon.state, !!options.json);
813
+ return;
814
+ }
815
+ if (daemon.mode === 'ready') {
816
+ const findings = await readDaemonFindings(daemon.socketPath);
817
+ if (findings && findings.status === 'ready' && findings.staleFiles.length === 0) {
818
+ const { summarizeViolations } = await import('./nextFileIncremental.js');
819
+ printNextFile(findings.violations, summarizeViolations(findings.violations), projectDir, !!options.json);
820
+ return;
821
+ }
822
+ // Daemon fell behind (R4) or vanished — fall through to in-process.
823
+ }
755
824
  await initParsers();
756
825
  const { violations, summary } = await runNextFile({
757
826
  projectRoot: options.path,
758
827
  configName: options.config,
759
828
  });
760
- // Rank files worst-first from the (incrementally-merged) violation set.
761
- const ranked = rankFilesByPriority(violations);
762
- if (ranked.length === 0) {
763
- if (options.json) {
764
- process.stdout.write(JSON.stringify({ done: true, summary }, null, 2) + '\n');
765
- }
766
- else {
767
- console.log(chalk.green('\n✓ No readings — nothing left to refactor.'));
768
- }
769
- return;
770
- }
771
- const top = ranked[0];
772
- // Every issue on the file, ordered critical → warning → suggestion.
773
- const ordered = orderFindingsWithinFile(top.violations);
774
- const projectDir = resolve(options.path || process.cwd());
775
- const relativize = (filePath) => {
776
- if (!filePath)
777
- return '';
778
- if (filePath.startsWith('/') || filePath.startsWith('\\\\')) {
779
- const rel = relative(projectDir, filePath);
780
- if (!rel.startsWith('..') && !isAbsolute(rel))
781
- return rel;
782
- }
783
- return filePath;
784
- };
785
- if (options.json) {
786
- const output = {
787
- done: false,
788
- file: relativize(top.file),
789
- remainingFiles: ranked.length - 1,
790
- remainingFindings: violations.length - top.count,
791
- summary,
792
- findings: ordered.map((v) => ({
793
- analyzer: v.analyzer || '',
794
- rule: v.rule || v.type || '',
795
- severity: v.severity,
796
- message: v.message,
797
- file: relativize(v.file || ''),
798
- line: v.line ?? v.start?.line,
799
- column: v.column ?? v.start?.column ?? 1,
800
- endLine: v.end?.line,
801
- endColumn: v.end?.column,
802
- enclosingSymbol: v.symbol || v.enclosingFunction || '',
803
- suggestion: v.suggestion || '',
804
- details: v.details || '',
805
- ...(v.new !== undefined && { new: v.new }),
806
- })),
807
- };
808
- process.stdout.write(JSON.stringify(output, null, 2) + '\n');
809
- }
810
- else {
811
- console.log(chalk.blue('🔍 Next File to Refactor'));
812
- console.log(chalk.gray('══════════════════════════════════════════════════'));
813
- console.log(`\n${chalk.bold(relativize(top.file))} — ${top.count} reading(s), highest severity ${top.maxSeverity}`);
814
- console.log(chalk.gray(`${ranked.length - 1} more file(s) with readings · ${violations.length - top.count} remaining reading(s)`));
815
- console.log(chalk.gray('\n── Readings ────────────────────────────────────────'));
816
- for (const v of ordered) {
817
- const icon = v.severity === 'critical' ? '🔴' : v.severity === 'warning' ? '🟡' : '🔵';
818
- console.log(`${icon} ${chalk.bold(relativize(v.file || ''))}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.rule} — ${v.message}`);
819
- }
820
- }
829
+ printNextFile(violations, summary, projectDir, !!options.json);
821
830
  }
822
831
  catch (error) {
823
832
  console.error(chalk.red('Error:'), error);
824
833
  process.exit(1);
825
834
  }
826
835
  });
836
+ /** Rank the violation set and emit the `next-file` result (shared by both paths). */
837
+ function printNextFile(violations, summary, projectDir, json) {
838
+ // Rank files worst-first from the (incrementally-merged) violation set.
839
+ const ranked = rankFilesByPriority(violations);
840
+ if (ranked.length === 0) {
841
+ if (json) {
842
+ process.stdout.write(JSON.stringify({ done: true, summary }, null, 2) + '\n');
843
+ }
844
+ else {
845
+ console.log(chalk.green('\n✓ No readings — nothing left to refactor.'));
846
+ }
847
+ return;
848
+ }
849
+ const top = ranked[0];
850
+ // Every issue on the file, ordered critical → warning → suggestion.
851
+ const ordered = orderFindingsWithinFile(top.violations);
852
+ const relativize = (filePath) => {
853
+ if (!filePath)
854
+ return '';
855
+ if (filePath.startsWith('/') || filePath.startsWith('\\\\')) {
856
+ const rel = relative(projectDir, filePath);
857
+ if (!rel.startsWith('..') && !isAbsolute(rel))
858
+ return rel;
859
+ }
860
+ return filePath;
861
+ };
862
+ if (json) {
863
+ const output = {
864
+ done: false,
865
+ file: relativize(top.file),
866
+ remainingFiles: ranked.length - 1,
867
+ remainingFindings: violations.length - top.count,
868
+ summary,
869
+ findings: ordered.map((v) => ({
870
+ analyzer: v.analyzer || '',
871
+ rule: v.rule,
872
+ severity: v.severity,
873
+ message: v.message,
874
+ file: relativize(v.file || ''),
875
+ line: v.line ?? v.start?.line,
876
+ column: v.column ?? v.start?.column ?? 1,
877
+ endLine: v.end?.line,
878
+ endColumn: v.end?.column,
879
+ enclosingSymbol: v.symbol || v.enclosingFunction || '',
880
+ suggestion: v.suggestion || '',
881
+ details: v.details || '',
882
+ ...(v.new !== undefined && { new: v.new }),
883
+ })),
884
+ };
885
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
886
+ }
887
+ else {
888
+ console.log(chalk.blue('🔍 Next File to Refactor'));
889
+ console.log(chalk.gray('══════════════════════════════════════════════════'));
890
+ console.log(`\n${chalk.bold(relativize(top.file))} — ${top.count} reading(s), highest severity ${top.maxSeverity}`);
891
+ console.log(chalk.gray(`${ranked.length - 1} more file(s) with readings · ${violations.length - top.count} remaining reading(s)`));
892
+ console.log(chalk.gray('\n── Readings ────────────────────────────────────────'));
893
+ for (const v of ordered) {
894
+ const icon = v.severity === 'critical' ? '🔴' : v.severity === 'warning' ? '🟡' : '🔵';
895
+ console.log(`${icon} ${chalk.bold(relativize(v.file || ''))}${v.line ? `:${v.line}` : ''} [${v.severity}] ${v.rule} — ${v.message}`);
896
+ }
897
+ }
898
+ }
827
899
  // Baseline command (Spec 18 R1)
828
900
  program
829
901
  .command('baseline')
@@ -2999,7 +3071,7 @@ program
2999
3071
  filePath = rel;
3000
3072
  }
3001
3073
  return {
3002
- rule: v.rule || v.type || '',
3074
+ rule: v.rule,
3003
3075
  analyzer: v.analyzer || '',
3004
3076
  severity: v.severity,
3005
3077
  file: filePath,
@@ -3034,6 +3106,143 @@ function renderBar(value, width) {
3034
3106
  const color = value > 0.8 ? chalk.green : value > 0.5 ? chalk.yellow : chalk.red;
3035
3107
  return color('█'.repeat(filled) + '░'.repeat(empty));
3036
3108
  }
3109
+ // ── Daemon command (Spec 50) ────────────────────────────────────────────
3110
+ // The daemon is an optimisation, never a dependency (R2). These commands start,
3111
+ // stop, and inspect it. The hot-path reads — `changed` (the hook gate) and
3112
+ // `next-file` (the refactor loop) — resolve to a live daemon and fall back
3113
+ // in-process when it is absent. `audit` stays in-process on purpose: it emits
3114
+ // full reports (baseline, recommendations, per-category SARIF/HTML) that need
3115
+ // the complete analyzer results, which the daemon does not serve over its
3116
+ // findings face (it serves the flattened violation set).
3117
+ const DAEMON_NOT_READY_EXIT_CODE = 3;
3118
+ /** R3 — a query while the seed is still running: the status is the answer. */
3119
+ function reportDaemonIndexing(state, json) {
3120
+ const progress = state?.progress;
3121
+ const filesIndexed = progress?.filesIndexed ?? 0;
3122
+ const filesTotal = progress?.filesTotal ?? 0;
3123
+ const phase = progress?.phase ?? 'files';
3124
+ const retryAfterMs = state?.retryAfterMs ?? null;
3125
+ const throughputUnknown = state?.throughputUnknown ?? false;
3126
+ if (json) {
3127
+ process.stdout.write(JSON.stringify({
3128
+ status: 'indexing',
3129
+ progress: { filesIndexed, filesTotal, phase },
3130
+ retryAfterMs,
3131
+ throughputUnknown,
3132
+ }, null, 2) + '\n');
3133
+ }
3134
+ else {
3135
+ const retry = retryAfterMs != null
3136
+ ? `try again in ~${Math.max(1, Math.round(retryAfterMs / 1000))}s`
3137
+ : 'check back shortly';
3138
+ const phaseDetail = phase === 'files'
3139
+ ? `${filesIndexed} of ${filesTotal} files`
3140
+ : `${phase} phase`;
3141
+ console.log(chalk.yellow(`⏳ Daemon indexing — ${phaseDetail}; ${retry}.`));
3142
+ }
3143
+ process.exit(DAEMON_NOT_READY_EXIT_CODE);
3144
+ }
3145
+ const daemonCmd = program
3146
+ .command('daemon')
3147
+ .description('Manage the per-project code-auditor daemon (Spec 50)')
3148
+ .action(() => {
3149
+ console.log(chalk.yellow('Use a daemon subcommand:'));
3150
+ console.log(chalk.gray(' code-audit daemon start [--foreground] Start the daemon for this project'));
3151
+ console.log(chalk.gray(' code-audit daemon stop Ask the daemon to shut down'));
3152
+ console.log(chalk.gray(' code-audit daemon status Show daemon status'));
3153
+ });
3154
+ daemonCmd
3155
+ .command('start')
3156
+ .description('Start the daemon for this project (detached by default)')
3157
+ .option('-p, --path <path>', 'Project root', process.cwd())
3158
+ .option('-c, --config <config>', 'Configuration name')
3159
+ .option('--foreground', 'Run in the foreground (blocking; useful for debugging)')
3160
+ .action(async (options) => {
3161
+ try {
3162
+ const { resolveDaemonSocketPath } = await import('./dataPaths.js');
3163
+ const { isDaemonListening } = await import('./daemon/socketClient.js');
3164
+ const projectRoot = resolve(options.path);
3165
+ const socketPath = resolveDaemonSocketPath(projectRoot);
3166
+ if (await isDaemonListening(socketPath)) {
3167
+ console.log(chalk.green('✓ Daemon already running.'));
3168
+ return;
3169
+ }
3170
+ if (options.foreground) {
3171
+ const { runDaemonForeground } = await import('./daemon/main.js');
3172
+ await runDaemonForeground({ projectRoot, configName: options.config, foreground: true });
3173
+ return;
3174
+ }
3175
+ const { startDaemonDetached } = await import('./daemon/resolve.js');
3176
+ startDaemonDetached(projectRoot, options.config);
3177
+ console.log(chalk.green('✓ Daemon started (detached).'));
3178
+ console.log(chalk.gray(` Socket: ${socketPath}`));
3179
+ console.log(chalk.gray(' It indexes on startup — `code-audit daemon status` to watch.'));
3180
+ }
3181
+ catch (error) {
3182
+ console.error(chalk.red('Error:'), error);
3183
+ process.exit(1);
3184
+ }
3185
+ });
3186
+ daemonCmd
3187
+ .command('stop')
3188
+ .description('Ask the daemon to shut down')
3189
+ .option('-p, --path <path>', 'Project root', process.cwd())
3190
+ .action(async (options) => {
3191
+ try {
3192
+ const { resolveDaemonSocketPath } = await import('./dataPaths.js');
3193
+ const { isDaemonListening } = await import('./daemon/socketClient.js');
3194
+ const { requestDaemonShutdown } = await import('./daemon/resolve.js');
3195
+ const socketPath = resolveDaemonSocketPath(resolve(options.path));
3196
+ if (!(await isDaemonListening(socketPath))) {
3197
+ console.log(chalk.gray('No daemon running.'));
3198
+ return;
3199
+ }
3200
+ await requestDaemonShutdown(socketPath);
3201
+ console.log(chalk.green('✓ Shutdown requested.'));
3202
+ }
3203
+ catch (error) {
3204
+ console.error(chalk.red('Error:'), error);
3205
+ process.exit(1);
3206
+ }
3207
+ });
3208
+ daemonCmd
3209
+ .command('status')
3210
+ .description('Show daemon status')
3211
+ .option('-p, --path <path>', 'Project root', process.cwd())
3212
+ .option('--json', 'Output as JSON')
3213
+ .action(async (options) => {
3214
+ try {
3215
+ const { resolveDaemon } = await import('./daemon/resolve.js');
3216
+ const { resolveDaemonSocketPath } = await import('./dataPaths.js');
3217
+ const projectRoot = resolve(options.path);
3218
+ const resolved = await resolveDaemon(projectRoot);
3219
+ const socketPath = resolveDaemonSocketPath(projectRoot);
3220
+ if (options.json) {
3221
+ process.stdout.write(JSON.stringify({
3222
+ running: resolved.mode !== 'absent',
3223
+ mode: resolved.mode,
3224
+ socketPath,
3225
+ ...(resolved.state && { state: resolved.state }),
3226
+ }, null, 2) + '\n');
3227
+ return;
3228
+ }
3229
+ if (resolved.mode === 'absent') {
3230
+ console.log(chalk.gray('No daemon running.'));
3231
+ console.log(chalk.gray('Start one: code-audit daemon start'));
3232
+ return;
3233
+ }
3234
+ if (resolved.mode === 'not-ready') {
3235
+ reportDaemonIndexing(resolved.state, false);
3236
+ return;
3237
+ }
3238
+ console.log(chalk.green('✓ Daemon ready.'));
3239
+ console.log(chalk.gray(` Socket: ${socketPath}`));
3240
+ }
3241
+ catch (error) {
3242
+ console.error(chalk.red('Error:'), error);
3243
+ process.exit(1);
3244
+ }
3245
+ });
3037
3246
  // Parse command line arguments
3038
3247
  program.parse(process.argv);
3039
3248
  // If no command was provided, show help