@nerviq/cli 1.14.0 → 1.15.0
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/README.md +1 -1
- package/bin/cli.js +65 -22
- package/package.json +1 -1
- package/src/audit/recommendations.js +46 -0
- package/src/context.js +23 -1
- package/src/mcp-server.js +631 -314
- package/src/techniques/hygiene.js +2 -2
- package/src/techniques/quality.js +6 -5
package/README.md
CHANGED
package/bin/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ const { getGovernanceSummary, printGovernanceSummary, ensureWritableProfile, ren
|
|
|
8
8
|
const { runBenchmark, printBenchmark, writeBenchmarkReport } = require('../src/benchmark');
|
|
9
9
|
const { writeSnapshotArtifact, writeRollbackArtifact, recordRecommendationOutcome, formatRecommendationOutcomeSummary, getRecommendationOutcomeSummary } = require('../src/activity');
|
|
10
10
|
const { collectFeedback } = require('../src/feedback');
|
|
11
|
+
const { collectAnonymousEvent } = require('../src/telemetry');
|
|
11
12
|
const { recordPattern, getPriorityAdjustment, formatUsageSummary } = require('../src/usage-patterns');
|
|
12
13
|
const { startServer } = require('../src/server');
|
|
13
14
|
const { auditWorkspaces } = require('../src/workspace');
|
|
@@ -104,6 +105,7 @@ function parseArgs(rawArgs) {
|
|
|
104
105
|
let format = null;
|
|
105
106
|
let port = null;
|
|
106
107
|
let workspace = null;
|
|
108
|
+
let targetDir = null;
|
|
107
109
|
let webhookUrl = null;
|
|
108
110
|
let webhookHeaders = [];
|
|
109
111
|
let webhookRetries = null;
|
|
@@ -134,7 +136,7 @@ function parseArgs(rawArgs) {
|
|
|
134
136
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
135
137
|
const arg = rawArgs[i];
|
|
136
138
|
|
|
137
|
-
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--webhook-header' || arg === '--webhook-retries' || arg === '--external' || arg === '--team-profile' || arg === '--lang' || arg === '--tag' || arg === '--milestone' || arg === '--campaign' || arg === '--diff-base' || arg === '--diff-head' || arg === '--drift-mode' || arg === '--owner' || arg === '--reason' || arg === '--expires' || arg === '--scope' || arg === '--class') {
|
|
139
|
+
if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--dir' || arg === '--format' || arg === '--from' || arg === '--to' || arg === '--port' || arg === '--workspace' || arg === '--check-version' || arg === '--webhook' || arg === '--webhook-header' || arg === '--webhook-retries' || arg === '--external' || arg === '--team-profile' || arg === '--lang' || arg === '--tag' || arg === '--milestone' || arg === '--campaign' || arg === '--diff-base' || arg === '--diff-head' || arg === '--drift-mode' || arg === '--owner' || arg === '--reason' || arg === '--expires' || arg === '--scope' || arg === '--class') {
|
|
138
140
|
const value = rawArgs[i + 1];
|
|
139
141
|
if (!value || value.startsWith('--')) {
|
|
140
142
|
throw new Error(`${arg} requires a value`);
|
|
@@ -153,6 +155,7 @@ function parseArgs(rawArgs) {
|
|
|
153
155
|
if (arg === '--source') feedbackSource = value.trim();
|
|
154
156
|
if (arg === '--score-delta') feedbackScoreDelta = value.trim();
|
|
155
157
|
if (arg === '--platform') { platform = value.trim().toLowerCase(); platformExplicit = true; }
|
|
158
|
+
if (arg === '--dir') targetDir = require('path').resolve(value.trim());
|
|
156
159
|
if (arg === '--format') format = value.trim().toLowerCase();
|
|
157
160
|
if (arg === '--from') { convertFrom = value.trim(); migrateFrom = value.trim(); }
|
|
158
161
|
if (arg === '--to') { convertTo = value.trim(); migrateTo = value.trim(); }
|
|
@@ -337,6 +340,11 @@ function parseArgs(rawArgs) {
|
|
|
337
340
|
continue;
|
|
338
341
|
}
|
|
339
342
|
|
|
343
|
+
if (arg.startsWith('--dir=')) {
|
|
344
|
+
targetDir = require('path').resolve(arg.split('=').slice(1).join('=').trim());
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
340
348
|
if (arg.startsWith('--format=')) {
|
|
341
349
|
format = arg.split('=').slice(1).join('=').trim().toLowerCase();
|
|
342
350
|
continue;
|
|
@@ -388,7 +396,7 @@ function parseArgs(rawArgs) {
|
|
|
388
396
|
|
|
389
397
|
const normalizedCommand = COMMAND_ALIASES[command] || command;
|
|
390
398
|
|
|
391
|
-
return { flags, command, commandExplicit, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, platformExplicit, format, port, workspace, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, webhookHeaders, webhookRetries, external, repos, teamProfile, lang, snapshotTags, snapshotMilestone, campaigns, diffBase, diffHead, driftMode, exceptionOwner, exceptionReason, exceptionExpires, exceptionScope, exceptionClass };
|
|
399
|
+
return { flags, command, commandExplicit, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, platformExplicit, format, port, workspace, targetDir, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo, checkVersion, webhookUrl, webhookHeaders, webhookRetries, external, repos, teamProfile, lang, snapshotTags, snapshotMilestone, campaigns, diffBase, diffHead, driftMode, exceptionOwner, exceptionReason, exceptionExpires, exceptionScope, exceptionClass };
|
|
392
400
|
}
|
|
393
401
|
|
|
394
402
|
function printWorkspaceSummary(summary, options) {
|
|
@@ -663,6 +671,7 @@ const HELP = `
|
|
|
663
671
|
|
|
664
672
|
OPTIONS
|
|
665
673
|
--platform NAME Platform: claude (default), codex, cursor, copilot, gemini, windsurf, aider, opencode
|
|
674
|
+
--dir PATH Target directory to audit (default: current directory)
|
|
666
675
|
--threshold N Exit code 1 if score < N (CI gate)
|
|
667
676
|
--require A,B Exit code 1 if named checks fail
|
|
668
677
|
--out FILE Write output to file (JSON or markdown)
|
|
@@ -694,6 +703,7 @@ const HELP = `
|
|
|
694
703
|
--verbose Full audit + medium-priority recommendations
|
|
695
704
|
--show-deprecated Show deprecated checks (excluded from scoring)
|
|
696
705
|
--json Output as JSON
|
|
706
|
+
--agent-mode Non-interactive JSON output for AI agents (setup/audit)
|
|
697
707
|
--auto Apply all generated files without prompting
|
|
698
708
|
--beginner Show only the 5 starter commands for first-time users
|
|
699
709
|
--key NAME Feedback: recommendation key (e.g. permissionDeny)
|
|
@@ -735,8 +745,9 @@ const HELP = `
|
|
|
735
745
|
npx nerviq feedback --key permissionDeny --status accepted --effect positive
|
|
736
746
|
|
|
737
747
|
EXIT CODES
|
|
738
|
-
0 Success
|
|
739
|
-
1
|
|
748
|
+
0 Success (score meets threshold, or no threshold set)
|
|
749
|
+
1 Threshold not met (score below --threshold)
|
|
750
|
+
2 Runtime error (unknown command, missing files, crash)
|
|
740
751
|
`;
|
|
741
752
|
|
|
742
753
|
const BEGINNER_HELP = `
|
|
@@ -770,7 +781,7 @@ async function main() {
|
|
|
770
781
|
parsed = parseArgs(args);
|
|
771
782
|
} catch (err) {
|
|
772
783
|
console.error(`\n Error: ${err.message}\n`);
|
|
773
|
-
process.exit(
|
|
784
|
+
process.exit(2);
|
|
774
785
|
}
|
|
775
786
|
|
|
776
787
|
const { flags, command, commandExplicit, normalizedCommand } = parsed;
|
|
@@ -807,6 +818,7 @@ async function main() {
|
|
|
807
818
|
fix: flags.includes('--fix'),
|
|
808
819
|
badge: flags.includes('--badge'),
|
|
809
820
|
quiet: flags.includes('--quiet'),
|
|
821
|
+
agentMode: flags.includes('--agent-mode'),
|
|
810
822
|
autoSync: flags.includes('--auto-sync'),
|
|
811
823
|
dryRun: flags.includes('--dry-run'),
|
|
812
824
|
configOnly: flags.includes('--config-only'),
|
|
@@ -842,32 +854,32 @@ async function main() {
|
|
|
842
854
|
exceptionExpires: parsed.exceptionExpires || null,
|
|
843
855
|
exceptionScope: parsed.exceptionScope || null,
|
|
844
856
|
exceptionClass: parsed.exceptionClass || null,
|
|
845
|
-
dir: process.cwd()
|
|
857
|
+
dir: parsed.targetDir || process.cwd()
|
|
846
858
|
};
|
|
847
859
|
|
|
848
860
|
if (options.snapshotTags.length > 0 && !options.snapshot) {
|
|
849
861
|
console.error('\n Error: --tag requires --snapshot.\n');
|
|
850
|
-
process.exit(
|
|
862
|
+
process.exit(2);
|
|
851
863
|
}
|
|
852
864
|
|
|
853
865
|
if (options.snapshotMilestone && !options.snapshot) {
|
|
854
866
|
console.error('\n Error: --milestone requires --snapshot.\n');
|
|
855
|
-
process.exit(
|
|
867
|
+
process.exit(2);
|
|
856
868
|
}
|
|
857
869
|
|
|
858
870
|
if (options.snapshotMilestone && !SNAPSHOT_MILESTONES.includes(options.snapshotMilestone)) {
|
|
859
871
|
console.error(`\n Error: Unsupported milestone '${options.snapshotMilestone}'. Use one of: ${SNAPSHOT_MILESTONES.join(', ')}.\n`);
|
|
860
|
-
process.exit(
|
|
872
|
+
process.exit(2);
|
|
861
873
|
}
|
|
862
874
|
|
|
863
875
|
if (options.diffOnly && options.snapshot) {
|
|
864
876
|
console.error('\n Error: --diff-only cannot be combined with --snapshot because diff-only scores are not comparable to full audit snapshots.\n');
|
|
865
|
-
process.exit(
|
|
877
|
+
process.exit(2);
|
|
866
878
|
}
|
|
867
879
|
|
|
868
880
|
if (options.driftMode && !['ci', 'pr', 'watch'].includes(options.driftMode)) {
|
|
869
881
|
console.error(`\n Error: Unsupported drift mode '${options.driftMode}'. Use ci, pr, or watch.\n`);
|
|
870
|
-
process.exit(
|
|
882
|
+
process.exit(2);
|
|
871
883
|
}
|
|
872
884
|
|
|
873
885
|
if (parsed.checkVersion) {
|
|
@@ -967,7 +979,7 @@ async function main() {
|
|
|
967
979
|
console.error(' Fix: Run nerviq --help to see all available commands.');
|
|
968
980
|
}
|
|
969
981
|
console.error(' Docs: https://github.com/nerviq/nerviq#readme\n');
|
|
970
|
-
process.exit(
|
|
982
|
+
process.exit(2);
|
|
971
983
|
}
|
|
972
984
|
|
|
973
985
|
if (!require('fs').existsSync(options.dir)) {
|
|
@@ -975,7 +987,7 @@ async function main() {
|
|
|
975
987
|
console.error(' Why: The current working directory does not exist or is not accessible.');
|
|
976
988
|
console.error(' Fix: cd into your project directory first, then run nerviq.');
|
|
977
989
|
console.error(' Docs: https://github.com/nerviq/nerviq#getting-started\n');
|
|
978
|
-
process.exit(
|
|
990
|
+
process.exit(2);
|
|
979
991
|
}
|
|
980
992
|
|
|
981
993
|
if (['setup', 'apply', 'benchmark'].includes(normalizedCommand)) {
|
|
@@ -1002,7 +1014,7 @@ async function main() {
|
|
|
1002
1014
|
if (!FULL_COMMAND_SET.has(normalizedCommand)) {
|
|
1003
1015
|
console.error(`\n Error: '${normalizedCommand}' is not supported for --platform codex.`);
|
|
1004
1016
|
console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
|
|
1005
|
-
process.exit(
|
|
1017
|
+
process.exit(2);
|
|
1006
1018
|
}
|
|
1007
1019
|
}
|
|
1008
1020
|
|
|
@@ -1010,7 +1022,7 @@ async function main() {
|
|
|
1010
1022
|
if (!FULL_COMMAND_SET.has(normalizedCommand)) {
|
|
1011
1023
|
console.error(`\n Error: '${normalizedCommand}' is not supported for --platform gemini.`);
|
|
1012
1024
|
console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
|
|
1013
|
-
process.exit(
|
|
1025
|
+
process.exit(2);
|
|
1014
1026
|
}
|
|
1015
1027
|
}
|
|
1016
1028
|
|
|
@@ -1018,7 +1030,7 @@ async function main() {
|
|
|
1018
1030
|
if (!FULL_COMMAND_SET.has(normalizedCommand)) {
|
|
1019
1031
|
console.error(`\n Error: '${normalizedCommand}' is not supported for --platform copilot.`);
|
|
1020
1032
|
console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
|
|
1021
|
-
process.exit(
|
|
1033
|
+
process.exit(2);
|
|
1022
1034
|
}
|
|
1023
1035
|
}
|
|
1024
1036
|
|
|
@@ -1026,7 +1038,7 @@ async function main() {
|
|
|
1026
1038
|
if (!FULL_COMMAND_SET.has(normalizedCommand)) {
|
|
1027
1039
|
console.error(`\n Error: '${normalizedCommand}' is not supported for --platform cursor.`);
|
|
1028
1040
|
console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
|
|
1029
|
-
process.exit(
|
|
1041
|
+
process.exit(2);
|
|
1030
1042
|
}
|
|
1031
1043
|
}
|
|
1032
1044
|
|
|
@@ -1035,7 +1047,7 @@ async function main() {
|
|
|
1035
1047
|
if (!FULL_COMMAND_SET.has(normalizedCommand)) {
|
|
1036
1048
|
console.error(`\n Error: '${normalizedCommand}' is not supported for --platform ${plat}.`);
|
|
1037
1049
|
console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
|
|
1038
|
-
process.exit(
|
|
1050
|
+
process.exit(2);
|
|
1039
1051
|
}
|
|
1040
1052
|
}
|
|
1041
1053
|
}
|
|
@@ -1045,7 +1057,7 @@ async function main() {
|
|
|
1045
1057
|
if (scanDirs.length === 0) {
|
|
1046
1058
|
console.error('\n Error: scan requires at least one directory argument.');
|
|
1047
1059
|
console.error(' Usage: npx nerviq scan dir1 dir2 dir3\n');
|
|
1048
|
-
process.exit(
|
|
1060
|
+
process.exit(2);
|
|
1049
1061
|
}
|
|
1050
1062
|
const summary = await scanOrg(scanDirs, options);
|
|
1051
1063
|
printScanDetail(summary, options);
|
|
@@ -1073,7 +1085,7 @@ async function main() {
|
|
|
1073
1085
|
console.error('\n Error: org requires `scan` or `policy`.');
|
|
1074
1086
|
console.error(' Usage: npx nerviq org scan dir1 dir2 dir3');
|
|
1075
1087
|
console.error(' npx nerviq org policy [dir]\n');
|
|
1076
|
-
process.exit(
|
|
1088
|
+
process.exit(2);
|
|
1077
1089
|
}
|
|
1078
1090
|
const summary = await scanOrg(scanDirs, options);
|
|
1079
1091
|
if (options.json) {
|
|
@@ -1617,6 +1629,7 @@ async function main() {
|
|
|
1617
1629
|
return;
|
|
1618
1630
|
} else if (normalizedCommand === 'harmony-audit') {
|
|
1619
1631
|
const { runHarmonyAudit } = require('../src/harmony/cli');
|
|
1632
|
+
collectAnonymousEvent('harmony-audit', { dir: options.dir });
|
|
1620
1633
|
await runHarmonyAudit(options);
|
|
1621
1634
|
process.exit(0);
|
|
1622
1635
|
} else if (normalizedCommand === 'harmony-sync') {
|
|
@@ -2448,7 +2461,29 @@ async function main() {
|
|
|
2448
2461
|
await runInit(options.dir, flags);
|
|
2449
2462
|
process.exit(0);
|
|
2450
2463
|
} else if (normalizedCommand === 'setup') {
|
|
2451
|
-
|
|
2464
|
+
collectAnonymousEvent('setup', { platform: options.platform, dir: options.dir });
|
|
2465
|
+
const setupResult = await setup({ ...options, silent: options.agentMode || options.json });
|
|
2466
|
+
if (options.agentMode) {
|
|
2467
|
+
// Agent-mode: structured JSON output with next steps
|
|
2468
|
+
const postAudit = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2469
|
+
const agentOutput = {
|
|
2470
|
+
status: setupResult.created > 0 ? 'files_created' : 'already_configured',
|
|
2471
|
+
created: setupResult.created,
|
|
2472
|
+
skipped: setupResult.skipped,
|
|
2473
|
+
written_files: setupResult.writtenFiles,
|
|
2474
|
+
preserved_files: setupResult.preservedFiles,
|
|
2475
|
+
detected_stacks: setupResult.stacks.map(s => s.key),
|
|
2476
|
+
rollback_id: setupResult.rollbackId,
|
|
2477
|
+
post_setup_score: postAudit.score,
|
|
2478
|
+
next_commands: [
|
|
2479
|
+
'npx @nerviq/cli audit --json',
|
|
2480
|
+
setupResult.created > 0 ? `npx @nerviq/cli augment --platform ${options.platform}` : null,
|
|
2481
|
+
postAudit.score < 70 ? 'npx @nerviq/cli plan' : null,
|
|
2482
|
+
].filter(Boolean),
|
|
2483
|
+
};
|
|
2484
|
+
console.log(JSON.stringify(agentOutput, null, 2));
|
|
2485
|
+
process.exit(0);
|
|
2486
|
+
}
|
|
2452
2487
|
if (options.snapshot) {
|
|
2453
2488
|
const postSetupResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
|
|
2454
2489
|
const snapshot = writeSnapshotArtifact(options.dir, 'audit', postSetupResult, {
|
|
@@ -2485,6 +2520,14 @@ async function main() {
|
|
|
2485
2520
|
: await audit(options);
|
|
2486
2521
|
}
|
|
2487
2522
|
|
|
2523
|
+
// ── Telemetry (opt-in, local only) ──
|
|
2524
|
+
collectAnonymousEvent('audit', {
|
|
2525
|
+
platform: result.platform || options.platform,
|
|
2526
|
+
score: result.score,
|
|
2527
|
+
checkCount: Array.isArray(result.results) ? result.results.length : null,
|
|
2528
|
+
dir: options.dir,
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2488
2531
|
if (options.driftMode) {
|
|
2489
2532
|
const { buildContinuousStatus, formatContinuousStatus } = require('../src/continuous-ops');
|
|
2490
2533
|
let campaigns = [];
|
|
@@ -2664,7 +2707,7 @@ async function main() {
|
|
|
2664
2707
|
} catch (err) {
|
|
2665
2708
|
console.error(`\n Error: ${err.message}`);
|
|
2666
2709
|
console.error(' Fix: Run `npx nerviq doctor` to diagnose common issues, or check https://github.com/nerviq/nerviq#troubleshooting');
|
|
2667
|
-
process.exit(
|
|
2710
|
+
process.exit(2);
|
|
2668
2711
|
}
|
|
2669
2712
|
}
|
|
2670
2713
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -379,6 +379,7 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
|
|
|
379
379
|
sourceUrl,
|
|
380
380
|
module: CATEGORY_MODULES[category] || category,
|
|
381
381
|
fix,
|
|
382
|
+
remediation_command: getRemediationCommand(key, category, options.platform),
|
|
382
383
|
priorityScore,
|
|
383
384
|
why: ACTION_RATIONALES[key] || fix,
|
|
384
385
|
risk: riskFromImpact(impact),
|
|
@@ -399,6 +400,51 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
|
|
|
399
400
|
});
|
|
400
401
|
}
|
|
401
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Map check keys/categories to a shell command an agent can run to fix the issue.
|
|
405
|
+
* Returns null when no automated fix is available.
|
|
406
|
+
*/
|
|
407
|
+
function getRemediationCommand(key, category, platform) {
|
|
408
|
+
const plat = platform || 'claude';
|
|
409
|
+
|
|
410
|
+
// Key-specific remediation commands
|
|
411
|
+
const KEY_COMMANDS = {
|
|
412
|
+
claudeMd: 'npx @nerviq/cli setup',
|
|
413
|
+
agentsMd: 'npx @nerviq/cli setup --platform codex',
|
|
414
|
+
geminiMd: 'npx @nerviq/cli setup --platform gemini',
|
|
415
|
+
copilotInstructions: 'npx @nerviq/cli setup --platform copilot',
|
|
416
|
+
cursorRules: 'npx @nerviq/cli setup --platform cursor',
|
|
417
|
+
windsurfRules: 'npx @nerviq/cli setup --platform windsurf',
|
|
418
|
+
aiderConfig: 'npx @nerviq/cli setup --platform aider',
|
|
419
|
+
opencodeConfig: 'npx @nerviq/cli setup --platform opencode',
|
|
420
|
+
settingsPermissions: 'npx @nerviq/cli plan --only permissions',
|
|
421
|
+
permissionDeny: 'npx @nerviq/cli plan --only permissions',
|
|
422
|
+
noBypassPermissions: 'npx @nerviq/cli plan --only permissions',
|
|
423
|
+
secretsProtection: 'npx @nerviq/cli plan --only permissions',
|
|
424
|
+
verificationLoop: `npx @nerviq/cli augment --platform ${plat}`,
|
|
425
|
+
lintCommand: `npx @nerviq/cli augment --platform ${plat}`,
|
|
426
|
+
testCommand: `npx @nerviq/cli augment --platform ${plat}`,
|
|
427
|
+
buildCommand: `npx @nerviq/cli augment --platform ${plat}`,
|
|
428
|
+
hookExists: 'npx @nerviq/cli plan --only hooks',
|
|
429
|
+
preCommitHook: 'npx @nerviq/cli plan --only hooks',
|
|
430
|
+
commandsExist: 'npx @nerviq/cli plan --only commands',
|
|
431
|
+
mcpServers: 'npx @nerviq/cli plan --mcp-pack context7',
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
if (KEY_COMMANDS[key]) return KEY_COMMANDS[key];
|
|
435
|
+
|
|
436
|
+
// Category-level fallback
|
|
437
|
+
const CATEGORY_COMMANDS = {
|
|
438
|
+
memory: `npx @nerviq/cli setup --platform ${plat}`,
|
|
439
|
+
security: `npx @nerviq/cli plan --only permissions --platform ${plat}`,
|
|
440
|
+
automation: `npx @nerviq/cli plan --only hooks --platform ${plat}`,
|
|
441
|
+
workflow: `npx @nerviq/cli plan --only commands --platform ${plat}`,
|
|
442
|
+
tools: `npx @nerviq/cli plan --mcp-pack context7 --platform ${plat}`,
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
return CATEGORY_COMMANDS[category] || `npx @nerviq/cli augment --platform ${plat}`;
|
|
446
|
+
}
|
|
447
|
+
|
|
402
448
|
function getNextScoreMilestone(score) {
|
|
403
449
|
return SCORE_MILESTONES.find((milestone) => score < milestone) || null;
|
|
404
450
|
}
|
package/src/context.js
CHANGED
|
@@ -74,10 +74,32 @@ class ProjectContext {
|
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Return the contents of the project's CLAUDE.md (root or .claude/ location).
|
|
77
|
+
* If CLAUDE.md contains only a reference to another file (e.g., "AGENTS.md"),
|
|
78
|
+
* follows that reference and returns the referenced file's content appended.
|
|
77
79
|
* @returns {string|null} File content or null if not found.
|
|
78
80
|
*/
|
|
79
81
|
claudeMdContent() {
|
|
80
|
-
|
|
82
|
+
const raw = this.fileContent('CLAUDE.md') || this.fileContent('.claude/CLAUDE.md');
|
|
83
|
+
if (!raw) return null;
|
|
84
|
+
|
|
85
|
+
// If the file is very short and looks like a file reference, follow it.
|
|
86
|
+
// Pattern: a single line that is just a filename (e.g., "AGENTS.md" or "docs/CODING.md")
|
|
87
|
+
const trimmed = raw.trim();
|
|
88
|
+
if (trimmed.length < 200 && /^[a-zA-Z0-9_./-]+\.(md|txt|rst)$/m.test(trimmed)) {
|
|
89
|
+
const lines = trimmed.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
90
|
+
let combined = raw;
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
if (/^[a-zA-Z0-9_./-]+\.(md|txt|rst)$/.test(line)) {
|
|
93
|
+
const referenced = this.fileContent(line);
|
|
94
|
+
if (referenced) {
|
|
95
|
+
combined += '\n' + referenced;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return combined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return raw;
|
|
81
103
|
}
|
|
82
104
|
|
|
83
105
|
/**
|