code-auditor-mcp 3.0.4 ā 3.0.6
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/dist/cli.js +365 -111
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts +1 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +34 -9
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +10 -5
- package/dist/functionScanner.js.map +1 -1
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +3 -2
- package/dist/search/QueryParser.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"name": "code-auditor",
|
|
11
11
|
"source": "./plugin",
|
|
12
12
|
"description": "Diff-scoped code quality auditing on every edit. Indexes your codebase, enforces invariants, and feeds violations back to the agent so fixes happen inline.",
|
|
13
|
-
"version": "3.0.
|
|
13
|
+
"version": "3.0.6",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "Ben Hammond"
|
|
16
16
|
},
|
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,8 @@ import { promises as fs } from 'fs';
|
|
|
12
12
|
import { createInterface } from 'readline';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import { DEFAULT_SERVER_URL, DEFAULT_PORT } from './constants.js';
|
|
15
|
+
import { DEFAULT_PORT } from './constants.js';
|
|
16
|
+
import inquirer from 'inquirer';
|
|
18
17
|
import { CodeMapGenerator } from './services/CodeMapGenerator.js';
|
|
19
18
|
import { initParsers } from './languages/index.js';
|
|
20
19
|
import { queryParser } from './search/QueryParser.js';
|
|
@@ -37,11 +36,19 @@ program
|
|
|
37
36
|
.option('-c, --config <config>', 'Configuration name')
|
|
38
37
|
.option('-o, --output <dir>', 'Output directory for reports')
|
|
39
38
|
.option('-f, --format <format>', 'Report format: html, json, csv, or sarif')
|
|
39
|
+
.option('--fail-on <severity>', 'Exit code 2 when violations at or above this severity exist')
|
|
40
40
|
.action(async (options) => {
|
|
41
41
|
console.log(chalk.blue('š Code Quality Audit Tool'));
|
|
42
42
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
43
43
|
try {
|
|
44
44
|
await initParsers();
|
|
45
|
+
// Validate --fail-on severity
|
|
46
|
+
const validSeverities = ['critical', 'warning', 'suggestion'];
|
|
47
|
+
const failOnSeverity = options.failOn;
|
|
48
|
+
if (failOnSeverity && !validSeverities.includes(failOnSeverity)) {
|
|
49
|
+
console.error(chalk.red(`Invalid --fail-on severity: "${failOnSeverity}". Must be one of: ${validSeverities.join(', ')}`));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
45
52
|
const runner = createAuditRunner({
|
|
46
53
|
projectRoot: options.path,
|
|
47
54
|
configName: options.config,
|
|
@@ -67,6 +74,19 @@ program
|
|
|
67
74
|
await fs.writeFile(reportPath, report, 'utf-8');
|
|
68
75
|
console.log(chalk.green(`\nReport written to ${reportPath}`));
|
|
69
76
|
}
|
|
77
|
+
// Exit code based on --fail-on
|
|
78
|
+
if (failOnSeverity) {
|
|
79
|
+
const violations = Object.values(result.analyzerResults).flatMap((r) => r.violations || []);
|
|
80
|
+
const severityOrder = ['critical', 'warning', 'suggestion'];
|
|
81
|
+
const failIndex = severityOrder.indexOf(failOnSeverity);
|
|
82
|
+
const hasAtOrAbove = violations.some((v) => {
|
|
83
|
+
const vIndex = severityOrder.indexOf(v.severity);
|
|
84
|
+
return vIndex >= 0 && vIndex <= failIndex;
|
|
85
|
+
});
|
|
86
|
+
if (hasAtOrAbove) {
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
70
90
|
}
|
|
71
91
|
catch (error) {
|
|
72
92
|
console.error(chalk.red('Error:'), error);
|
|
@@ -204,15 +224,13 @@ program
|
|
|
204
224
|
program
|
|
205
225
|
.command('generate-config')
|
|
206
226
|
.alias('gen')
|
|
207
|
-
.description('Generate
|
|
208
|
-
.option('-t, --tool <tool>', 'Specific tool (cursor, continue, copilot, awsq, codeium, claude, all)')
|
|
227
|
+
.description('Generate a .codeauditor.json scaffold with invariant rules')
|
|
209
228
|
.option('-o, --output <dir>', 'Output directory', '.')
|
|
210
|
-
.option('-
|
|
211
|
-
.option('-
|
|
212
|
-
.option('-
|
|
213
|
-
.option('-y, --yes', 'Skip all confirmation prompts (same as --force)')
|
|
229
|
+
.option('-i, --interactive', 'Interactive rule builder')
|
|
230
|
+
.option('-f, --force', 'Force overwrite existing file without confirmation')
|
|
231
|
+
.option('-y, --yes', 'Skip confirmation prompts (same as --force)')
|
|
214
232
|
.action(async (options) => {
|
|
215
|
-
console.log(chalk.blue('š ļø
|
|
233
|
+
console.log(chalk.blue('š ļø Code Auditor Config Generator'));
|
|
216
234
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
217
235
|
try {
|
|
218
236
|
await generateConfigurations(options);
|
|
@@ -776,126 +794,362 @@ program.parse(process.argv);
|
|
|
776
794
|
if (!process.argv.slice(2).length) {
|
|
777
795
|
program.outputHelp();
|
|
778
796
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
797
|
+
const RULE_KINDS = [
|
|
798
|
+
{
|
|
799
|
+
kind: 'import-ban',
|
|
800
|
+
label: 'Import Ban ā forbid importing a specific module',
|
|
801
|
+
description: 'No file may import the banned module (e.g. deprecated packages, legacy libraries).',
|
|
802
|
+
requiredFields: ['module'],
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
kind: 'call-constraint',
|
|
806
|
+
label: 'Call Constraint ā restrict who can call a function',
|
|
807
|
+
description: 'Only allow or deny specific callers from invoking a function.',
|
|
808
|
+
requiredFields: ['callee'],
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
kind: 'module-boundary',
|
|
812
|
+
label: 'Module Boundary ā enforce layer isolation',
|
|
813
|
+
description: 'Files matching a "from" glob may not import from files matching a "to" glob.',
|
|
814
|
+
requiredFields: ['from', 'to'],
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
kind: 'naming',
|
|
818
|
+
label: 'Naming ā enforce export naming conventions',
|
|
819
|
+
description: 'Exported symbols in matching files must match a regex pattern.',
|
|
820
|
+
requiredFields: ['path', 'exports'],
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
kind: 'ast-pattern',
|
|
824
|
+
label: 'AST Pattern ā ban syntactic patterns',
|
|
825
|
+
description: 'Match AST nodes using ast-grep patterns (e.g. "new Function($$$)").',
|
|
826
|
+
requiredFields: ['pattern'],
|
|
827
|
+
},
|
|
828
|
+
];
|
|
829
|
+
const SCAFFOLD_CONFIG = {
|
|
830
|
+
$schema: 'https://unpkg.com/code-auditor-mcp/dist/invariant-rules.schema.json',
|
|
831
|
+
rules: [
|
|
832
|
+
{
|
|
833
|
+
id: 'ban-deprecated-lib',
|
|
834
|
+
kind: 'import-ban',
|
|
835
|
+
severity: 'critical',
|
|
836
|
+
message: 'This module is deprecated ā prefer the replacement instead.',
|
|
837
|
+
module: 'deprecated-lib',
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
id: 'data-layer-no-browser',
|
|
841
|
+
kind: 'module-boundary',
|
|
842
|
+
severity: 'critical',
|
|
843
|
+
message: 'Data access layer must not import from browser-only modules.',
|
|
844
|
+
from: 'src/data/**',
|
|
845
|
+
to: 'src/browser/**',
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
id: 'api-routes-naming',
|
|
849
|
+
kind: 'naming',
|
|
850
|
+
severity: 'warning',
|
|
851
|
+
message: 'API route files must export a handler matching the HTTP method.',
|
|
852
|
+
path: 'src/api/**',
|
|
853
|
+
exports: '^(get|post|put|delete|patch)\\b',
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id: 'no-eval',
|
|
857
|
+
kind: 'ast-pattern',
|
|
858
|
+
severity: 'critical',
|
|
859
|
+
pattern: 'eval($$$)',
|
|
860
|
+
message: 'eval() is forbidden in this codebase.',
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
};
|
|
782
864
|
async function generateConfigurations(options) {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
let
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}
|
|
796
|
-
else if (!options.tool) {
|
|
797
|
-
// No tool specified and not interactive - show available tools
|
|
798
|
-
const factory = new ConfigGeneratorFactory(serverUrl);
|
|
799
|
-
const availableTools = factory.getToolInfo();
|
|
800
|
-
console.log(chalk.yellow('No tool specified. Available tools:'));
|
|
801
|
-
availableTools.forEach(tool => {
|
|
802
|
-
console.log(chalk.gray(` ⢠${tool.name} - ${tool.displayName}`));
|
|
865
|
+
const outputDir = resolve(options.output || '.');
|
|
866
|
+
const outputPath = join(outputDir, '.codeauditor.json');
|
|
867
|
+
let config;
|
|
868
|
+
if (options.interactive) {
|
|
869
|
+
// --- Interactive rule builder ---
|
|
870
|
+
console.log(chalk.cyan('\nBuild your .codeauditor.json interactively.\n'));
|
|
871
|
+
const { addRules } = await inquirer.prompt({
|
|
872
|
+
addRules: {
|
|
873
|
+
type: 'confirm',
|
|
874
|
+
message: 'Would you like to add invariant rules?',
|
|
875
|
+
default: true,
|
|
876
|
+
},
|
|
803
877
|
});
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
console.log(chalk.gray(' code-auditor gen --tool all'));
|
|
808
|
-
console.log(chalk.gray(' code-auditor gen --interactive'));
|
|
809
|
-
return;
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
// Command line mode
|
|
813
|
-
if (options.tool === 'all') {
|
|
814
|
-
const factory = new ConfigGeneratorFactory(serverUrl);
|
|
815
|
-
tools = factory.getAvailableTools();
|
|
878
|
+
if (!addRules) {
|
|
879
|
+
console.log(chalk.yellow('No rules selected. Writing empty config.'));
|
|
880
|
+
config = { $schema: SCAFFOLD_CONFIG.$schema, rules: [] };
|
|
816
881
|
}
|
|
817
882
|
else {
|
|
818
|
-
|
|
883
|
+
config = await buildRulesInteractively();
|
|
819
884
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const outputPath = resolve(outputDir, config.filename);
|
|
885
|
+
// Confirm output directory
|
|
886
|
+
if (!options.force && !options.yes) {
|
|
887
|
+
const { dir } = await inquirer.prompt({
|
|
888
|
+
dir: {
|
|
889
|
+
type: 'input',
|
|
890
|
+
message: 'Output directory:',
|
|
891
|
+
default: outputDir,
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
// Re-resolve with the user's choice (they might just hit enter)
|
|
895
|
+
const chosenDir = resolve(dir || outputDir);
|
|
896
|
+
const chosenPath = join(chosenDir, '.codeauditor.json');
|
|
897
|
+
// Check for existing file
|
|
898
|
+
let exists = false;
|
|
835
899
|
try {
|
|
836
|
-
await fs.access(
|
|
837
|
-
|
|
900
|
+
await fs.access(chosenPath);
|
|
901
|
+
exists = true;
|
|
838
902
|
}
|
|
839
|
-
catch {
|
|
840
|
-
|
|
903
|
+
catch { /* ok */ }
|
|
904
|
+
if (exists) {
|
|
905
|
+
const { overwrite } = await inquirer.prompt({
|
|
906
|
+
overwrite: {
|
|
907
|
+
type: 'confirm',
|
|
908
|
+
message: chalk.yellow(`.codeauditor.json already exists at ${chosenPath}. Overwrite?`),
|
|
909
|
+
default: false,
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
if (!overwrite) {
|
|
913
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
841
916
|
}
|
|
917
|
+
await writeConfigFile(chosenPath, config);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
await writeConfigFile(outputPath, config);
|
|
842
921
|
}
|
|
843
922
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
923
|
+
else {
|
|
924
|
+
// --- Non-interactive: scaffold template ---
|
|
925
|
+
let exists = false;
|
|
926
|
+
try {
|
|
927
|
+
await fs.access(outputPath);
|
|
928
|
+
exists = true;
|
|
929
|
+
}
|
|
930
|
+
catch { /* ok */ }
|
|
931
|
+
if (exists && !options.force && !options.yes) {
|
|
932
|
+
console.log(chalk.yellow(`.codeauditor.json already exists at ${outputPath}`));
|
|
933
|
+
console.log(chalk.gray('Use --force or --yes to overwrite, or --interactive to build a custom config.'));
|
|
849
934
|
return;
|
|
850
935
|
}
|
|
936
|
+
if (exists && (options.force || options.yes)) {
|
|
937
|
+
console.log(chalk.yellow('Overwriting existing .codeauditor.json...'));
|
|
938
|
+
}
|
|
939
|
+
config = SCAFFOLD_CONFIG;
|
|
940
|
+
await writeConfigFile(outputPath, config);
|
|
851
941
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
942
|
+
console.log(chalk.blue('\nNext steps:'));
|
|
943
|
+
console.log(chalk.gray(' 1. Edit .codeauditor.json to match your codebase conventions'));
|
|
944
|
+
console.log(chalk.gray(' 2. Run ') + chalk.cyan('code-audit') + chalk.gray(' to enforce your rules'));
|
|
945
|
+
console.log(chalk.gray(' 3. Use ') + chalk.cyan('code-audit changed --fail-on critical') + chalk.gray(' in your agent hook'));
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Interactive rule builder ā walks the user through adding rules one at a time.
|
|
949
|
+
*/
|
|
950
|
+
async function buildRulesInteractively() {
|
|
951
|
+
const rules = [];
|
|
952
|
+
let addMore = true;
|
|
953
|
+
while (addMore) {
|
|
954
|
+
console.log(chalk.gray(`\nāā Rule ${rules.length + 1} āā`));
|
|
955
|
+
// Select rule kind
|
|
956
|
+
const { kind } = await inquirer.prompt({
|
|
957
|
+
kind: {
|
|
958
|
+
type: 'list',
|
|
959
|
+
message: 'Select rule kind:',
|
|
960
|
+
choices: RULE_KINDS.map((k) => ({
|
|
961
|
+
name: k.label,
|
|
962
|
+
value: k.kind,
|
|
963
|
+
})),
|
|
964
|
+
pageSize: 10,
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
const kindInfo = RULE_KINDS.find((k) => k.kind === kind);
|
|
968
|
+
console.log(chalk.dim(kindInfo.description));
|
|
969
|
+
// Common fields
|
|
970
|
+
const common = await inquirer.prompt({
|
|
971
|
+
id: {
|
|
972
|
+
type: 'input',
|
|
973
|
+
message: 'Rule ID (unique kebab-case identifier):',
|
|
974
|
+
validate: (input) => {
|
|
975
|
+
if (!input.trim())
|
|
976
|
+
return 'Rule ID is required';
|
|
977
|
+
if (!/^[a-z][a-z0-9-]*$/.test(input))
|
|
978
|
+
return 'Use kebab-case (lowercase, digits, hyphens)';
|
|
979
|
+
return true;
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
severity: {
|
|
983
|
+
type: 'list',
|
|
984
|
+
message: 'Severity:',
|
|
985
|
+
choices: [
|
|
986
|
+
{ name: chalk.red('Critical ā exit code 2, blocks the agent loop'), value: 'critical' },
|
|
987
|
+
{ name: chalk.yellow('Warning ā visible, non-blocking'), value: 'warning' },
|
|
988
|
+
{ name: chalk.blue('Suggestion ā informational'), value: 'suggestion' },
|
|
989
|
+
],
|
|
990
|
+
default: 'warning',
|
|
991
|
+
},
|
|
992
|
+
message: {
|
|
993
|
+
type: 'input',
|
|
994
|
+
message: 'Violation message (shown when rule is broken):',
|
|
995
|
+
validate: (input) => input.trim() ? true : 'Message is required',
|
|
996
|
+
},
|
|
997
|
+
});
|
|
998
|
+
const rule = {
|
|
999
|
+
id: common.id,
|
|
1000
|
+
kind,
|
|
1001
|
+
severity: common.severity,
|
|
1002
|
+
message: common.message,
|
|
1003
|
+
};
|
|
1004
|
+
// Kind-specific fields
|
|
1005
|
+
switch (kind) {
|
|
1006
|
+
case 'import-ban': {
|
|
1007
|
+
const { module } = await inquirer.prompt({
|
|
1008
|
+
module: {
|
|
1009
|
+
type: 'input',
|
|
1010
|
+
message: 'Banned module specifier (e.g. "lodash" or "@old-lib/*"):',
|
|
1011
|
+
validate: (input) => input.trim() ? true : 'Module specifier is required',
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
rule.module = module;
|
|
1015
|
+
const { addExcept } = await inquirer.prompt({
|
|
1016
|
+
addExcept: {
|
|
1017
|
+
type: 'confirm',
|
|
1018
|
+
message: 'Add exception paths (files allowed to import it)?',
|
|
1019
|
+
default: false,
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
if (addExcept) {
|
|
1023
|
+
const { except } = await inquirer.prompt({
|
|
1024
|
+
except: {
|
|
1025
|
+
type: 'input',
|
|
1026
|
+
message: 'Exception globs (comma-separated, e.g. "src/migration/**"):',
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
const exceptList = except.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1030
|
+
if (exceptList.length > 0)
|
|
1031
|
+
rule.except = exceptList;
|
|
1032
|
+
}
|
|
1033
|
+
break;
|
|
862
1034
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1035
|
+
case 'call-constraint': {
|
|
1036
|
+
const { callee } = await inquirer.prompt({
|
|
1037
|
+
callee: {
|
|
1038
|
+
type: 'input',
|
|
1039
|
+
message: 'Callee (function name, optionally path-qualified as "path/glob#name"):',
|
|
1040
|
+
validate: (input) => input.trim() ? true : 'Callee is required',
|
|
1041
|
+
},
|
|
1042
|
+
});
|
|
1043
|
+
rule.callee = callee;
|
|
1044
|
+
const { mode } = await inquirer.prompt({
|
|
1045
|
+
mode: {
|
|
1046
|
+
type: 'list',
|
|
1047
|
+
message: 'Restriction mode:',
|
|
1048
|
+
choices: [
|
|
1049
|
+
{ name: 'Allow only specific callers (allowFrom)', value: 'allow' },
|
|
1050
|
+
{ name: 'Deny specific callers (denyFrom)', value: 'deny' },
|
|
1051
|
+
],
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
const { paths } = await inquirer.prompt({
|
|
1055
|
+
paths: {
|
|
1056
|
+
type: 'input',
|
|
1057
|
+
message: `Path globs (comma-separated) for ${mode === 'allow' ? 'allowFrom' : 'denyFrom'}:`,
|
|
1058
|
+
validate: (input) => input.trim() ? true : 'At least one path glob is required',
|
|
1059
|
+
},
|
|
1060
|
+
});
|
|
1061
|
+
const pathList = paths.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1062
|
+
if (mode === 'allow') {
|
|
1063
|
+
rule.allowFrom = pathList;
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
rule.denyFrom = pathList;
|
|
877
1067
|
}
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
case 'module-boundary': {
|
|
1071
|
+
const { from, to } = await inquirer.prompt({
|
|
1072
|
+
from: {
|
|
1073
|
+
type: 'input',
|
|
1074
|
+
message: 'From (path glob for files that must not import):',
|
|
1075
|
+
validate: (input) => input.trim() ? true : '"from" path glob is required',
|
|
1076
|
+
},
|
|
1077
|
+
to: {
|
|
1078
|
+
type: 'input',
|
|
1079
|
+
message: 'To (path glob for files that must not be imported):',
|
|
1080
|
+
validate: (input) => input.trim() ? true : '"to" path glob is required',
|
|
1081
|
+
},
|
|
1082
|
+
});
|
|
1083
|
+
rule.from = from;
|
|
1084
|
+
rule.to = to;
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
case 'naming': {
|
|
1088
|
+
const { path, exports: exportPattern } = await inquirer.prompt({
|
|
1089
|
+
path: {
|
|
1090
|
+
type: 'input',
|
|
1091
|
+
message: 'Path (glob for files this rule applies to):',
|
|
1092
|
+
validate: (input) => input.trim() ? true : 'Path glob is required',
|
|
1093
|
+
},
|
|
1094
|
+
exports: {
|
|
1095
|
+
type: 'input',
|
|
1096
|
+
message: 'Exports regex (exported symbols must match, e.g. "^use[A-Z]"):',
|
|
1097
|
+
validate: (input) => input.trim() ? true : 'Exports regex is required',
|
|
1098
|
+
},
|
|
1099
|
+
});
|
|
1100
|
+
rule.path = path;
|
|
1101
|
+
rule.exports = exportPattern;
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
case 'ast-pattern': {
|
|
1105
|
+
const { pattern } = await inquirer.prompt({
|
|
1106
|
+
pattern: {
|
|
1107
|
+
type: 'input',
|
|
1108
|
+
message: 'AST pattern (ast-grep syntax, e.g. "new Function($$$)"):',
|
|
1109
|
+
validate: (input) => input.trim() ? true : 'Pattern is required',
|
|
1110
|
+
},
|
|
1111
|
+
});
|
|
1112
|
+
rule.pattern = pattern;
|
|
1113
|
+
const { addLanguage } = await inquirer.prompt({
|
|
1114
|
+
addLanguage: {
|
|
1115
|
+
type: 'confirm',
|
|
1116
|
+
message: 'Restrict to a specific language? (default: typescript)',
|
|
1117
|
+
default: false,
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
if (addLanguage) {
|
|
1121
|
+
const { language } = await inquirer.prompt({
|
|
1122
|
+
language: {
|
|
1123
|
+
type: 'list',
|
|
1124
|
+
message: 'Language:',
|
|
1125
|
+
choices: ['typescript', 'javascript', 'go'],
|
|
1126
|
+
},
|
|
1127
|
+
});
|
|
1128
|
+
rule.language = language;
|
|
1129
|
+
}
|
|
1130
|
+
break;
|
|
878
1131
|
}
|
|
879
|
-
// Show success and instructions
|
|
880
|
-
console.log(chalk.green(`ā Generated ${generator.getToolName()} configuration: ${config.filename}`));
|
|
881
|
-
console.log(chalk.dim('Instructions:'));
|
|
882
|
-
console.log(chalk.gray(config.instructions.trim()));
|
|
883
|
-
console.log('');
|
|
884
|
-
}
|
|
885
|
-
catch (error) {
|
|
886
|
-
errors.push(`Failed to generate config for ${tool}: ${error}`);
|
|
887
1132
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
console.log(chalk.red(` ⢠${error}`));
|
|
1133
|
+
rules.push(rule);
|
|
1134
|
+
console.log(chalk.green(` ā Added rule "${rule.id}" [${rule.kind}]`));
|
|
1135
|
+
const { cont } = await inquirer.prompt({
|
|
1136
|
+
cont: {
|
|
1137
|
+
type: 'confirm',
|
|
1138
|
+
message: 'Add another rule?',
|
|
1139
|
+
default: true,
|
|
1140
|
+
},
|
|
897
1141
|
});
|
|
1142
|
+
addMore = cont;
|
|
898
1143
|
}
|
|
1144
|
+
return {
|
|
1145
|
+
$schema: 'https://unpkg.com/code-auditor-mcp/dist/invariant-rules.schema.json',
|
|
1146
|
+
rules,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
async function writeConfigFile(outputPath, config) {
|
|
1150
|
+
await fs.mkdir(dirname(outputPath), { recursive: true });
|
|
1151
|
+
await fs.writeFile(outputPath, JSON.stringify(config, null, 2) + '\n');
|
|
1152
|
+
console.log(chalk.green(`\nā Written .codeauditor.json to ${outputPath}`));
|
|
899
1153
|
}
|
|
900
1154
|
/**
|
|
901
1155
|
* Generate code map for a project
|