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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +71 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +122 -43
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +6 -7
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +105 -18
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +9 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +63 -44
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +76 -28
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +73 -13
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +12 -2
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +30 -18
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -8
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +54 -45
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/functionConcerns.d.ts +9 -4
- package/dist/analyzers/universal/functionConcerns.d.ts.map +1 -1
- package/dist/analyzers/universal/functionConcerns.js +25 -11
- package/dist/analyzers/universal/functionConcerns.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +4 -4
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +124 -11
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/auditRouter.d.ts.map +1 -1
- package/dist/auditRouter.js +4 -6
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.js +1 -1
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +284 -75
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts +6 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +54 -0
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +23 -0
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +3 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +30 -8
- package/dist/config/defaults.js.map +1 -1
- package/dist/conventions/conventionMiner.d.ts +18 -3
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +130 -12
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/daemon/args.d.ts +24 -0
- package/dist/daemon/args.d.ts.map +1 -0
- package/dist/daemon/args.js +60 -0
- package/dist/daemon/args.js.map +1 -0
- package/dist/daemon/core.d.ts +128 -0
- package/dist/daemon/core.d.ts.map +1 -0
- package/dist/daemon/core.js +700 -0
- package/dist/daemon/core.js.map +1 -0
- package/dist/daemon/lspServer.d.ts +51 -0
- package/dist/daemon/lspServer.d.ts.map +1 -0
- package/dist/daemon/lspServer.js +168 -0
- package/dist/daemon/lspServer.js.map +1 -0
- package/dist/daemon/main.d.ts +34 -0
- package/dist/daemon/main.d.ts.map +1 -0
- package/dist/daemon/main.js +136 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/resolve.d.ts +41 -0
- package/dist/daemon/resolve.d.ts.map +1 -0
- package/dist/daemon/resolve.js +90 -0
- package/dist/daemon/resolve.js.map +1 -0
- package/dist/daemon/socketClient.d.ts +21 -0
- package/dist/daemon/socketClient.d.ts.map +1 -0
- package/dist/daemon/socketClient.js +60 -0
- package/dist/daemon/socketClient.js.map +1 -0
- package/dist/daemon/socketServer.d.ts +33 -0
- package/dist/daemon/socketServer.d.ts.map +1 -0
- package/dist/daemon/socketServer.js +106 -0
- package/dist/daemon/socketServer.js.map +1 -0
- package/dist/daemon/types.d.ts +78 -0
- package/dist/daemon/types.d.ts.map +1 -0
- package/dist/daemon/types.js +10 -0
- package/dist/daemon/types.js.map +1 -0
- package/dist/dataPaths.d.ts +21 -0
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +27 -0
- package/dist/dataPaths.js.map +1 -1
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +21 -4
- package/dist/hooks/core.js.map +1 -1
- package/dist/languages/RuntimeManager.js +10 -0
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/go/analyzer +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +166 -35
- package/dist/languages/go/analyzer-src/solid.go +80 -172
- package/dist/languages/go/analyzer-src/types.go +18 -18
- package/dist/ledger.d.ts +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +3 -3
- package/dist/ledger.js.map +1 -1
- package/dist/mcpAuditJobs.js +1 -1
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +9 -3
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +32 -6
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipeline.d.ts +26 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +86 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +1 -1
- package/dist/pipelineAdapters.js +1 -1
- package/dist/reporting/csvReportGenerator.js +1 -1
- package/dist/ruleAliases.d.ts.map +1 -1
- package/dist/ruleAliases.js +45 -5
- package/dist/ruleAliases.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- 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
|
-
|
|
433
|
-
scope =
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|