code-auditor-mcp 3.9.4 → 3.9.9

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 (86) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +179 -0
  3. package/README.md +18 -0
  4. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  5. package/dist/analyzers/reactAnalyzer.js +4 -1
  6. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  7. package/dist/analyzers/ruleRegistry.d.ts +8 -0
  8. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  9. package/dist/analyzers/ruleRegistry.js +20 -10
  10. package/dist/analyzers/ruleRegistry.js.map +1 -1
  11. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  12. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +22 -4
  13. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  14. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  15. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +23 -20
  16. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  17. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +1 -0
  18. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
  19. package/dist/analyzers/universal/schema/codeAnalysis.js +20 -2
  20. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
  21. package/dist/auditRouter.d.ts.map +1 -1
  22. package/dist/auditRouter.js +6 -1
  23. package/dist/auditRouter.js.map +1 -1
  24. package/dist/auditRunner.d.ts.map +1 -1
  25. package/dist/auditRunner.js +53 -1
  26. package/dist/auditRunner.js.map +1 -1
  27. package/dist/cli.js +119 -4
  28. package/dist/cli.js.map +1 -1
  29. package/dist/config/defaults.d.ts +1 -0
  30. package/dist/config/defaults.d.ts.map +1 -1
  31. package/dist/config/defaults.js +14 -6
  32. package/dist/config/defaults.js.map +1 -1
  33. package/dist/config/effectiveConfig.d.ts +22 -1
  34. package/dist/config/effectiveConfig.d.ts.map +1 -1
  35. package/dist/config/effectiveConfig.js +69 -2
  36. package/dist/config/effectiveConfig.js.map +1 -1
  37. package/dist/config/lintConfigReader.d.ts +56 -0
  38. package/dist/config/lintConfigReader.d.ts.map +1 -0
  39. package/dist/config/lintConfigReader.js +263 -0
  40. package/dist/config/lintConfigReader.js.map +1 -0
  41. package/dist/dataPaths.d.ts +8 -0
  42. package/dist/dataPaths.d.ts.map +1 -1
  43. package/dist/dataPaths.js +21 -0
  44. package/dist/dataPaths.js.map +1 -1
  45. package/dist/dismissals.d.ts +59 -0
  46. package/dist/dismissals.d.ts.map +1 -0
  47. package/dist/dismissals.js +128 -0
  48. package/dist/dismissals.js.map +1 -0
  49. package/dist/enforcement/gate.d.ts.map +1 -1
  50. package/dist/enforcement/gate.js +5 -0
  51. package/dist/enforcement/gate.js.map +1 -1
  52. package/dist/installConfig.d.ts +40 -0
  53. package/dist/installConfig.d.ts.map +1 -0
  54. package/dist/installConfig.js +84 -0
  55. package/dist/installConfig.js.map +1 -0
  56. package/dist/languages/go/analyzer +0 -0
  57. package/dist/languages/go/analyzer-src/solid.go +14 -6
  58. package/dist/mcp.js +54 -0
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  61. package/dist/reporting/csvReportGenerator.js +1 -0
  62. package/dist/reporting/csvReportGenerator.js.map +1 -1
  63. package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
  64. package/dist/reporting/htmlReportGenerator.js +5 -0
  65. package/dist/reporting/htmlReportGenerator.js.map +1 -1
  66. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  67. package/dist/reporting/jsonReportGenerator.js +6 -0
  68. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  69. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  70. package/dist/reporting/sarifReportGenerator.js +3 -0
  71. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  72. package/dist/structuralSignature.d.ts +41 -0
  73. package/dist/structuralSignature.d.ts.map +1 -0
  74. package/dist/structuralSignature.js +93 -0
  75. package/dist/structuralSignature.js.map +1 -0
  76. package/dist/telemetry.d.ts +80 -0
  77. package/dist/telemetry.d.ts.map +1 -0
  78. package/dist/telemetry.js +148 -0
  79. package/dist/telemetry.js.map +1 -0
  80. package/dist/types.d.ts +34 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js.map +1 -1
  83. package/package.json +1 -1
  84. package/plugin/.claude-plugin/plugin.json +1 -1
  85. package/plugin/skills/code-auditor/SKILL.md +24 -2
  86. package/server.json +2 -2
package/dist/dataPaths.js CHANGED
@@ -104,6 +104,27 @@ function getFallbackCacheRoot() {
104
104
  }
105
105
  return path.join(os.homedir(), '.cache', 'code-auditor');
106
106
  }
107
+ /**
108
+ * OS-specific per-user config root for persistent, user-scoped state (the anonymous
109
+ * install ID and the telemetry opt-in). Deliberately NOT the cache root above: the
110
+ * cache is wiped by clean installs, and a regenerated install ID would break source
111
+ * grouping — this state must survive a cache wipe. This is the OS's designated
112
+ * location for exactly this kind of config, so it adds no new `~/` dotdir.
113
+ */
114
+ export function getUserConfigRoot() {
115
+ const xdg = process.env.XDG_CONFIG_HOME?.trim();
116
+ if (xdg) {
117
+ return path.join(path.resolve(xdg), 'code-auditor');
118
+ }
119
+ if (process.platform === 'darwin') {
120
+ return path.join(os.homedir(), 'Library', 'Application Support', 'code-auditor');
121
+ }
122
+ if (process.platform === 'win32') {
123
+ const local = process.env.LOCALAPPDATA?.trim();
124
+ return path.join(local ? path.resolve(local) : os.homedir(), 'code-auditor');
125
+ }
126
+ return path.join(os.homedir(), '.config', 'code-auditor');
127
+ }
107
128
  /**
108
129
  * Resolve symlinks (and other path aliases) so the same physical project
109
130
  * hashes identically regardless of how it was spelled. On macOS `/tmp` is a
@@ -1 +1 @@
1
- {"version":3,"file":"dataPaths.js","sourceRoot":"","sources":["../src/dataPaths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataPaths.js","sourceRoot":"","sources":["../src/dataPaths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { AuditResult, Violation } from './types.js';
2
+ /** One dismissed finding. `reason` is the product — a dismissal without one is a suppression. */
3
+ export interface DismissalEntry {
4
+ /** SHA-256 fingerprint of [analyzer, rule, file, symbol] — the scoping key. */
5
+ fingerprint: string;
6
+ /** Canonical rule ID, for grouping/readability (R4) and the telemetry payload (R2). */
7
+ rule: string;
8
+ /** File path at dismissal time, for readability (the fingerprint is the identity). */
9
+ file: string;
10
+ /** Canonical symbol at dismissal time, for readability. */
11
+ symbol: string;
12
+ /** Why this finding was dismissed — REQUIRED. The reason is the product. */
13
+ reason: string;
14
+ /** ISO-8601 timestamp of the dismissal. */
15
+ dismissedAt: string;
16
+ /** Tool version (package.json) at dismissal time. */
17
+ toolVersion: string;
18
+ }
19
+ /** The committed dismissals file. */
20
+ export interface DismissalsFile {
21
+ schemaVersion: 1;
22
+ entries: DismissalEntry[];
23
+ }
24
+ /** Result of matching a run's findings against the dismissals file. */
25
+ export interface DismissalMatch {
26
+ /** Findings whose fingerprint is dismissed — they never gate. */
27
+ dismissed: Violation[];
28
+ /** Findings with no matching dismissal — they gate normally. */
29
+ active: Violation[];
30
+ }
31
+ /**
32
+ * Load the dismissals file from the project root. Returns `null` when absent.
33
+ *
34
+ * When the file exists but is structurally invalid (wrong schema version, or an
35
+ * entry missing a non-empty `fingerprint`/`reason`), a config error is reported
36
+ * to stderr and `null` is returned — the run proceeds with zero dismissals
37
+ * rather than silently honouring a malformed suppression list.
38
+ */
39
+ export declare function loadDismissals(projectRoot: string): DismissalsFile | null;
40
+ /** Write the dismissals file to the project root (committed, reviewable in a diff). */
41
+ export declare function saveDismissals(projectRoot: string, dismissals: DismissalsFile): void;
42
+ /** Build a dismissal entry from a live violation and a required reason. */
43
+ export declare function buildDismissalEntry(violation: Violation, reason: string, toolVersion: string): DismissalEntry;
44
+ /**
45
+ * Add (or replace) a dismissal entry, keyed by fingerprint. Idempotent: re-
46
+ * dismissing an already-dismissed finding updates its reason and timestamp.
47
+ */
48
+ export declare function upsertDismissal(projectRoot: string, entry: DismissalEntry): DismissalsFile;
49
+ /**
50
+ * Partition a run's findings into dismissed vs active by fingerprint.
51
+ */
52
+ export declare function matchDismissals(violations: Violation[], dismissals: DismissalsFile): DismissalMatch;
53
+ /**
54
+ * Apply dismissals to a completed audit result: mark dismissed findings so the
55
+ * gate skips them, and record the dismissed count in the summary (never subtracted
56
+ * from `totalViolations`).
57
+ */
58
+ export declare function applyDismissals(result: AuditResult, projectRoot: string): void;
59
+ //# sourceMappingURL=dismissals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismissals.d.ts","sourceRoot":"","sources":["../src/dismissals.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIzD,iGAAiG;AACjG,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAC;IACpB,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,gEAAgE;IAChE,MAAM,EAAE,SAAS,EAAE,CAAC;CACrB;AASD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAyBzE;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI,CAGpF;AAID,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc,CAW7G;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,cAAc,CAU1F;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,EAAE,EACvB,UAAU,EAAE,cAAc,GACzB,cAAc,CAchB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAY9E"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Spec 57 — Dismissals.
3
+ *
4
+ * A dismissal clears exactly one finding, scoped by its fingerprint (rule +
5
+ * file + symbol, via the shared `buildFingerprintInput`/`fingerprint` tuple the
6
+ * baseline, SARIF and tasks already use). It is a *decision about specific code*,
7
+ * not configuration, so it lives in its own committed dotfile — never in
8
+ * `.codeauditor.json`.
9
+ *
10
+ * What distinguishes a dismissal from a baseline (which was repeatedly rejected
11
+ * because it accumulates and nobody revisits it):
12
+ *
13
+ * - a **written reason is required** (a dismissal without one is a config error),
14
+ * - it is **scoped to one finding** (not the rule, not the file), and
15
+ * - it is **counted in every report** ("43 findings, 3 dismissed"), never
16
+ * silently subtracted from the total.
17
+ */
18
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fingerprint, buildFingerprintInput } from './fingerprint.js';
21
+ import { canonicalRuleId } from './ruleAliases.js';
22
+ import { extractSymbol } from './symbols.js';
23
+ // ── Constants ────────────────────────────────────────────────────────────────
24
+ const DISMISSALS_FILENAME = '.codeauditor.dismissals.json';
25
+ const SCHEMA_VERSION = 1;
26
+ // ── Load / save ──────────────────────────────────────────────────────────────
27
+ /**
28
+ * Load the dismissals file from the project root. Returns `null` when absent.
29
+ *
30
+ * When the file exists but is structurally invalid (wrong schema version, or an
31
+ * entry missing a non-empty `fingerprint`/`reason`), a config error is reported
32
+ * to stderr and `null` is returned — the run proceeds with zero dismissals
33
+ * rather than silently honouring a malformed suppression list.
34
+ */
35
+ export function loadDismissals(projectRoot) {
36
+ const filePath = path.join(projectRoot, DISMISSALS_FILENAME);
37
+ try {
38
+ if (!existsSync(filePath))
39
+ return null;
40
+ const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
41
+ if (!parsed || parsed.schemaVersion !== SCHEMA_VERSION || !Array.isArray(parsed.entries)) {
42
+ console.error(`Dismissals file has an unsupported shape (expected schemaVersion ${SCHEMA_VERSION} with an "entries" array): ${filePath}`);
43
+ return null;
44
+ }
45
+ const invalid = parsed.entries.filter((e) => typeof e.fingerprint !== 'string' || !e.fingerprint || typeof e.reason !== 'string' || !e.reason.trim());
46
+ if (invalid.length > 0) {
47
+ console.error(`Dismissals file has ${invalid.length} entry(ies) missing a fingerprint or a non-empty reason ` +
48
+ `(a dismissal without a reason is a config error, not a suppression): ${filePath}`);
49
+ return null;
50
+ }
51
+ return parsed;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ /** Write the dismissals file to the project root (committed, reviewable in a diff). */
58
+ export function saveDismissals(projectRoot, dismissals) {
59
+ const filePath = path.join(projectRoot, DISMISSALS_FILENAME);
60
+ writeFileSync(filePath, JSON.stringify(dismissals, null, 2) + '\n', 'utf-8');
61
+ }
62
+ // ── Mutation ─────────────────────────────────────────────────────────────────
63
+ /** Build a dismissal entry from a live violation and a required reason. */
64
+ export function buildDismissalEntry(violation, reason, toolVersion) {
65
+ const input = buildFingerprintInput(violation);
66
+ return {
67
+ fingerprint: fingerprint(input),
68
+ rule: canonicalRuleId(violation.rule ?? ''),
69
+ file: violation.file ?? '',
70
+ symbol: extractSymbol(violation),
71
+ reason,
72
+ dismissedAt: new Date().toISOString(),
73
+ toolVersion,
74
+ };
75
+ }
76
+ /**
77
+ * Add (or replace) a dismissal entry, keyed by fingerprint. Idempotent: re-
78
+ * dismissing an already-dismissed finding updates its reason and timestamp.
79
+ */
80
+ export function upsertDismissal(projectRoot, entry) {
81
+ const dismissals = loadDismissals(projectRoot) ?? { schemaVersion: SCHEMA_VERSION, entries: [] };
82
+ const existing = dismissals.entries.find((e) => e.fingerprint === entry.fingerprint);
83
+ if (existing) {
84
+ Object.assign(existing, entry);
85
+ }
86
+ else {
87
+ dismissals.entries.push(entry);
88
+ }
89
+ saveDismissals(projectRoot, dismissals);
90
+ return dismissals;
91
+ }
92
+ // ── Matching ─────────────────────────────────────────────────────────────────
93
+ /**
94
+ * Partition a run's findings into dismissed vs active by fingerprint.
95
+ */
96
+ export function matchDismissals(violations, dismissals) {
97
+ const dismissalMap = new Map();
98
+ for (const entry of dismissals.entries)
99
+ dismissalMap.set(entry.fingerprint, entry);
100
+ const dismissed = [];
101
+ const active = [];
102
+ for (const v of violations) {
103
+ const fp = fingerprint(buildFingerprintInput(v));
104
+ if (dismissalMap.has(fp))
105
+ dismissed.push(v);
106
+ else
107
+ active.push(v);
108
+ }
109
+ return { dismissed, active };
110
+ }
111
+ /**
112
+ * Apply dismissals to a completed audit result: mark dismissed findings so the
113
+ * gate skips them, and record the dismissed count in the summary (never subtracted
114
+ * from `totalViolations`).
115
+ */
116
+ export function applyDismissals(result, projectRoot) {
117
+ const dismissals = loadDismissals(projectRoot);
118
+ result.summary.dismissed = 0;
119
+ if (!dismissals)
120
+ return;
121
+ const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations);
122
+ const { dismissed } = matchDismissals(violations, dismissals);
123
+ for (const v of dismissed) {
124
+ v.dismissed = true;
125
+ }
126
+ result.summary.dismissed = dismissed.length;
127
+ }
128
+ //# sourceMappingURL=dismissals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismissals.js","sourceRoot":"","sources":["../src/dismissals.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/enforcement/gate.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,EAAE,EACvB,kBAAkB,EAAE,WAAW,CAAC,QAAQ,CAAC,GACxC,cAAc,CA0BhB"}
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/enforcement/gate.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,aAAa,EAAE,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,EAAE,EACvB,kBAAkB,EAAE,WAAW,CAAC,QAAQ,CAAC,GACxC,cAAc,CA8BhB"}
@@ -34,6 +34,11 @@ export function computeGatingDecision(violations, blockingSeverities) {
34
34
  // A path-profile-excluded file never blocks.
35
35
  if (v.gateExcluded)
36
36
  continue;
37
+ // Spec 57 — a dismissed finding never blocks. Dismissal is a recorded
38
+ // decision about one specific finding, not a narrowing of what is reported:
39
+ // the finding still appears in every report at its real severity.
40
+ if (v.dismissed)
41
+ continue;
37
42
  // R2 — only findings at a blocking severity block.
38
43
  if (!blockingSeverities.has(v.severity))
39
44
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/enforcement/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAwBrE;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAuB,EACvB,kBAAyC;IAEzC,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,cAAc,GAAoB,EAAE,CAAC;IAE3C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,6CAA6C;QAC7C,IAAI,CAAC,CAAC,YAAY;YAAE,SAAS;QAC7B,mDAAmD;QACnD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YAAE,SAAS;QAElD,MAAM,KAAK,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACvC,sEAAsE;QACtE,iEAAiE;QACjE,IAAI,KAAK,EAAE,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;YACvC,cAAc,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,MAAM,EAAE,eAAe;aACxB,CAAC,CAAC;QACL,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACtC,CAAC"}
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/enforcement/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAwBrE;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAuB,EACvB,kBAAyC;IAEzC,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,cAAc,GAAoB,EAAE,CAAC;IAE3C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,6CAA6C;QAC7C,IAAI,CAAC,CAAC,YAAY;YAAE,SAAS;QAC7B,sEAAsE;QACtE,4EAA4E;QAC5E,kEAAkE;QAClE,IAAI,CAAC,CAAC,SAAS;YAAE,SAAS;QAC1B,mDAAmD;QACnD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;YAAE,SAAS;QAElD,MAAM,KAAK,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACvC,sEAAsE;QACtE,iEAAiE;QACjE,IAAI,KAAK,EAAE,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;YACvC,cAAc,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,MAAM,EAAE,eAAe;aACxB,CAAC,CAAC;QACL,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Spec 57 — anonymous install ID + telemetry opt-in, persisted at the user level.
3
+ *
4
+ * The install ID is a random value generated locally (never derived from the
5
+ * machine, the user, the project, or anything else) and sent with each telemetry
6
+ * submission so submissions can be grouped by source. It lives in the OS config
7
+ * dir (see `getUserConfigRoot`), NOT the cache dir — a cache wipe must not
8
+ * regenerate it, or source grouping breaks.
9
+ *
10
+ * The opt-in is a one-time decision recorded here (default OFF), toggled by the
11
+ * `telemetry` MCP tool. It is *not* a default and *not* a per-invocation flag.
12
+ */
13
+ /** Privacy statement shared by the tool, SKILL, and docs. */
14
+ export declare const PRIVACY_MESSAGE = "Data is anonymous, and the ID is generated locally to avoid tracking anything personal.";
15
+ /**
16
+ * The default ingest endpoint (the operator's service). Shipped so opt-in is a
17
+ * one-step toggle with a known destination; overridable at enable time and via
18
+ * the CLI `--telemetry-endpoint` flag. It is a default, not a contract — a
19
+ * domain can be pointed at the service later by changing this one value.
20
+ */
21
+ export declare const DEFAULT_TELEMETRY_ENDPOINT = "https://code-auditor-dismissals.ben-a-hammond.workers.dev/ingest";
22
+ /** Resolve the effective endpoint: an explicit non-blank value wins, else the shipped default. */
23
+ export declare function resolveTelemetryEndpoint(input?: string): string;
24
+ export interface TelemetryOptIn {
25
+ enabled: boolean;
26
+ endpoint: string;
27
+ }
28
+ /** Resolve the user config dir (overridable for tests). */
29
+ export declare function configDir(dir?: string): string;
30
+ /**
31
+ * Return the anonymous install ID, generating and persisting it on first use.
32
+ * Random and carrying nothing. Never throws — a config read/write failure falls
33
+ * back to a fresh random UUID so telemetry can never block on local config I/O.
34
+ */
35
+ export declare function getInstallId(dir?: string): string;
36
+ /** Read the telemetry opt-in. Any read failure is fail-closed (off, no endpoint). */
37
+ export declare function getTelemetryOptIn(dir?: string): TelemetryOptIn;
38
+ /** Persist the telemetry opt-in. Throws on write failure (an explicit user action). */
39
+ export declare function setTelemetryOptIn(optIn: TelemetryOptIn, dir?: string): void;
40
+ //# sourceMappingURL=installConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installConfig.d.ts","sourceRoot":"","sources":["../src/installConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,6DAA6D;AAC7D,eAAO,MAAM,eAAe,4FAC+D,CAAC;AAE5F;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,qEAC6B,CAAC;AAErE,kGAAkG;AAClG,wBAAgB,wBAAwB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/D;AAKD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,2DAA2D;AAC3D,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAejD;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAa9D;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAS3E"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Spec 57 — anonymous install ID + telemetry opt-in, persisted at the user level.
3
+ *
4
+ * The install ID is a random value generated locally (never derived from the
5
+ * machine, the user, the project, or anything else) and sent with each telemetry
6
+ * submission so submissions can be grouped by source. It lives in the OS config
7
+ * dir (see `getUserConfigRoot`), NOT the cache dir — a cache wipe must not
8
+ * regenerate it, or source grouping breaks.
9
+ *
10
+ * The opt-in is a one-time decision recorded here (default OFF), toggled by the
11
+ * `telemetry` MCP tool. It is *not* a default and *not* a per-invocation flag.
12
+ */
13
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
14
+ import path from 'node:path';
15
+ import { randomUUID } from 'node:crypto';
16
+ import { getUserConfigRoot } from './dataPaths.js';
17
+ /** Privacy statement shared by the tool, SKILL, and docs. */
18
+ export const PRIVACY_MESSAGE = 'Data is anonymous, and the ID is generated locally to avoid tracking anything personal.';
19
+ /**
20
+ * The default ingest endpoint (the operator's service). Shipped so opt-in is a
21
+ * one-step toggle with a known destination; overridable at enable time and via
22
+ * the CLI `--telemetry-endpoint` flag. It is a default, not a contract — a
23
+ * domain can be pointed at the service later by changing this one value.
24
+ */
25
+ export const DEFAULT_TELEMETRY_ENDPOINT = 'https://code-auditor-dismissals.ben-a-hammond.workers.dev/ingest';
26
+ /** Resolve the effective endpoint: an explicit non-blank value wins, else the shipped default. */
27
+ export function resolveTelemetryEndpoint(input) {
28
+ const trimmed = (input ?? '').trim();
29
+ return trimmed || DEFAULT_TELEMETRY_ENDPOINT;
30
+ }
31
+ const INSTALL_ID_FILENAME = 'install-id';
32
+ const TELEMETRY_FILENAME = 'telemetry.json';
33
+ /** Resolve the user config dir (overridable for tests). */
34
+ export function configDir(dir) {
35
+ return dir ?? getUserConfigRoot();
36
+ }
37
+ /**
38
+ * Return the anonymous install ID, generating and persisting it on first use.
39
+ * Random and carrying nothing. Never throws — a config read/write failure falls
40
+ * back to a fresh random UUID so telemetry can never block on local config I/O.
41
+ */
42
+ export function getInstallId(dir) {
43
+ const root = configDir(dir);
44
+ const filePath = path.join(root, INSTALL_ID_FILENAME);
45
+ try {
46
+ if (existsSync(filePath)) {
47
+ const existing = readFileSync(filePath, 'utf-8').trim();
48
+ if (existing)
49
+ return existing;
50
+ }
51
+ const id = randomUUID();
52
+ mkdirSync(root, { recursive: true });
53
+ writeFileSync(filePath, id + '\n', { mode: 0o600 });
54
+ return id;
55
+ }
56
+ catch {
57
+ return randomUUID();
58
+ }
59
+ }
60
+ /** Read the telemetry opt-in. Any read failure is fail-closed (off, no endpoint). */
61
+ export function getTelemetryOptIn(dir) {
62
+ const root = configDir(dir);
63
+ const filePath = path.join(root, TELEMETRY_FILENAME);
64
+ try {
65
+ if (!existsSync(filePath))
66
+ return { enabled: false, endpoint: '' };
67
+ const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
68
+ return {
69
+ enabled: parsed?.enabled === true,
70
+ endpoint: typeof parsed?.endpoint === 'string' ? parsed.endpoint : '',
71
+ };
72
+ }
73
+ catch {
74
+ return { enabled: false, endpoint: '' };
75
+ }
76
+ }
77
+ /** Persist the telemetry opt-in. Throws on write failure (an explicit user action). */
78
+ export function setTelemetryOptIn(optIn, dir) {
79
+ const root = configDir(dir);
80
+ const filePath = path.join(root, TELEMETRY_FILENAME);
81
+ mkdirSync(root, { recursive: true });
82
+ writeFileSync(filePath, JSON.stringify({ enabled: optIn.enabled === true, endpoint: optIn.endpoint ?? '' }, null, 2) + '\n', { mode: 0o600 });
83
+ }
84
+ //# sourceMappingURL=installConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installConfig.js","sourceRoot":"","sources":["../src/installConfig.ts"],"names":[],"mappings":"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"}
Binary file
@@ -57,9 +57,12 @@ func (s *SOLIDAnalyzer) analyzeFunctionSize() []Violation {
57
57
 
58
58
  for _, function := range s.functions {
59
59
  returns := strings.Split(function.ReturnType, ",")
60
- complex := function.Complexity > 10
60
+ // 2026-09-12 recalibration: complexity ceiling aligned to the 20-cyclomatic
61
+ // threshold, params aligned to the TS `parameter-count` default of 6. The
62
+ // rule is AND-combined (complexity + returns + params) so it stays conservative.
63
+ complex := function.Complexity > 20
61
64
  multiReturn := len(returns) > 2
62
- manyParams := len(function.Parameters) > 5
65
+ manyParams := len(function.Parameters) > 6
63
66
  if complex && multiReturn && manyParams {
64
67
  violations = append(violations, Violation{
65
68
  File: function.File,
@@ -94,7 +97,8 @@ func (s *SOLIDAnalyzer) analyzeStructSize() []Violation {
94
97
  var violations []Violation
95
98
 
96
99
  for _, structInfo := range s.structs {
97
- if len(structInfo.Fields) > 10 {
100
+ // 2026-09-12 recalibration: 10 flags ordinary config/model structs; 15 is a god-struct.
101
+ if len(structInfo.Fields) > 15 {
98
102
  violations = append(violations, Violation{
99
103
  File: structInfo.File,
100
104
  Line: structInfo.StartLine,
@@ -131,7 +135,9 @@ func (s *SOLIDAnalyzer) analyzeSwitchSize() []Violation {
131
135
  switch node := n.(type) {
132
136
  case *ast.SwitchStmt:
133
137
  caseCount := s.countSwitchCases(node)
134
- if caseCount > 5 {
138
+ // 2026-09-12 recalibration: 5 flags idiomatic dispatch (HTTP status /
139
+ // command maps); 8 suggests table-driven lookup.
140
+ if caseCount > 8 {
135
141
  pos := s.parser.fileSet.Position(node.Pos())
136
142
  violations = append(violations, Violation{
137
143
  File: filePath,
@@ -149,7 +155,7 @@ func (s *SOLIDAnalyzer) analyzeSwitchSize() []Violation {
149
155
  }
150
156
  case *ast.TypeSwitchStmt:
151
157
  caseCount := s.countTypeSwitchCases(node)
152
- if caseCount > 5 {
158
+ if caseCount > 8 {
153
159
  pos := s.parser.fileSet.Position(node.Pos())
154
160
  violations = append(violations, Violation{
155
161
  File: filePath,
@@ -233,7 +239,9 @@ func (s *SOLIDAnalyzer) analyzeISP() []Violation {
233
239
  var violations []Violation
234
240
 
235
241
  for _, interfaceInfo := range s.interfaces {
236
- if len(interfaceInfo.Methods) > 5 {
242
+ // 2026-09-12 recalibration: 5 flags idiomatic Reader/Writer-style interfaces;
243
+ // 10 is where segregation is warranted.
244
+ if len(interfaceInfo.Methods) > 10 {
237
245
  violations = append(violations, Violation{
238
246
  File: interfaceInfo.File,
239
247
  Line: interfaceInfo.StartLine,
package/dist/mcp.js CHANGED
@@ -63,6 +63,7 @@ import { searchFunctions, findDefinition, syncFileIndex, getDatabase } from './c
63
63
  import { CodeMapGenerator } from './services/CodeMapGenerator.js';
64
64
  import { analyzeDocumentation } from './analyzers/documentationAnalyzer.js';
65
65
  import { ConfigGeneratorFactory } from './generators/ConfigGeneratorFactory.js';
66
+ import { getInstallId, getTelemetryOptIn, setTelemetryOptIn, PRIVACY_MESSAGE, resolveTelemetryEndpoint } from './installConfig.js';
66
67
  import { MCP_DEFAULT_ANALYZERS } from './analyzers/ruleRegistry.js';
67
68
  import { DEFAULT_SERVER_URL, IS_DEV_MODE, PACKAGE_VERSION } from './constants.js';
68
69
  import { getAuditJobStatus, getAuditResultsPage, getAuditResultsAsSarif, startAuditJob } from './mcpAuditJobs.js';
@@ -1269,6 +1270,59 @@ function registerAllTools(registry) {
1269
1270
  },
1270
1271
  ],
1271
1272
  });
1273
+ // ── telemetry ──────────────────────────────────────────────────────────────
1274
+ registry.register({
1275
+ name: 'telemetry',
1276
+ description: 'Manage anonymous dismissal feedback opt-in (default OFF). Actions: status, enable, disable. When enabled, a dismissal and its reason are sent to the feedback endpoint, grouped by an anonymous install ID generated locally.',
1277
+ actions: [
1278
+ {
1279
+ name: 'status',
1280
+ description: 'Show whether anonymous dismissal feedback is enabled, its endpoint, and the anonymous install ID.',
1281
+ parameters: [],
1282
+ handler: async () => {
1283
+ const optIn = getTelemetryOptIn();
1284
+ return {
1285
+ enabled: optIn.enabled,
1286
+ endpoint: optIn.endpoint,
1287
+ installId: optIn.enabled ? getInstallId() : null,
1288
+ message: PRIVACY_MESSAGE,
1289
+ };
1290
+ },
1291
+ },
1292
+ {
1293
+ name: 'enable',
1294
+ description: 'Opt in to anonymous dismissal feedback. Defaults to the built-in service endpoint. Generates the anonymous install ID on first use.',
1295
+ parameters: [
1296
+ {
1297
+ name: 'endpoint',
1298
+ type: 'string',
1299
+ required: false,
1300
+ description: 'Optional ingest URL. Defaults to the built-in service endpoint.',
1301
+ },
1302
+ ],
1303
+ handler: async (args) => {
1304
+ const endpoint = resolveTelemetryEndpoint(args.endpoint);
1305
+ setTelemetryOptIn({ enabled: true, endpoint });
1306
+ return {
1307
+ enabled: true,
1308
+ endpoint,
1309
+ installId: getInstallId(),
1310
+ message: PRIVACY_MESSAGE,
1311
+ };
1312
+ },
1313
+ },
1314
+ {
1315
+ name: 'disable',
1316
+ description: 'Turn off anonymous dismissal feedback. The endpoint is remembered; nothing further is sent.',
1317
+ parameters: [],
1318
+ handler: async () => {
1319
+ const current = getTelemetryOptIn();
1320
+ setTelemetryOptIn({ enabled: false, endpoint: current.endpoint });
1321
+ return { enabled: false, message: PRIVACY_MESSAGE };
1322
+ },
1323
+ },
1324
+ ],
1325
+ });
1272
1326
  // ── code_map ───────────────────────────────────────────────────────────────
1273
1327
  registry.register({
1274
1328
  name: 'code_map',