@nerviq/cli 0.9.3 → 0.9.5

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/bin/cli.js CHANGED
@@ -7,6 +7,7 @@ const { buildProposalBundle, printProposalBundle, writePlanFile, applyProposalBu
7
7
  const { getGovernanceSummary, printGovernanceSummary, ensureWritableProfile, renderGovernanceMarkdown } = require('../src/governance');
8
8
  const { runBenchmark, printBenchmark, writeBenchmarkReport } = require('../src/benchmark');
9
9
  const { writeSnapshotArtifact, recordRecommendationOutcome, formatRecommendationOutcomeSummary, getRecommendationOutcomeSummary } = require('../src/activity');
10
+ const { collectFeedback } = require('../src/feedback');
10
11
  const { version } = require('../package.json');
11
12
 
12
13
  const args = process.argv.slice(2);
@@ -20,7 +21,7 @@ const COMMAND_ALIASES = {
20
21
  gov: 'governance',
21
22
  outcome: 'feedback',
22
23
  };
23
- const KNOWN_COMMANDS = ['audit', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'help', 'version'];
24
+ const KNOWN_COMMANDS = ['audit', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'help', 'version'];
24
25
 
25
26
  function levenshtein(a, b) {
26
27
  const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
@@ -73,11 +74,15 @@ function parseArgs(rawArgs) {
73
74
  let format = null;
74
75
  let commandSet = false;
75
76
  let extraArgs = [];
77
+ let convertFrom = null;
78
+ let convertTo = null;
79
+ let migrateFrom = null;
80
+ let migrateTo = null;
76
81
 
77
82
  for (let i = 0; i < rawArgs.length; i++) {
78
83
  const arg = rawArgs[i];
79
84
 
80
- 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') {
85
+ 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') {
81
86
  const value = rawArgs[i + 1];
82
87
  if (!value || value.startsWith('--')) {
83
88
  throw new Error(`${arg} requires a value`);
@@ -97,6 +102,8 @@ function parseArgs(rawArgs) {
97
102
  if (arg === '--score-delta') feedbackScoreDelta = value.trim();
98
103
  if (arg === '--platform') platform = value.trim().toLowerCase();
99
104
  if (arg === '--format') format = value.trim().toLowerCase();
105
+ if (arg === '--from') { convertFrom = value.trim(); migrateFrom = value.trim(); }
106
+ if (arg === '--to') { convertTo = value.trim(); migrateTo = value.trim(); }
100
107
  i++;
101
108
  continue;
102
109
  }
@@ -191,7 +198,7 @@ function parseArgs(rawArgs) {
191
198
 
192
199
  const normalizedCommand = COMMAND_ALIASES[command] || command;
193
200
 
194
- return { flags, command, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, extraArgs };
201
+ return { flags, command, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, extraArgs, convertFrom, convertTo, migrateFrom, migrateTo };
195
202
  }
196
203
 
197
204
  const HELP = `
@@ -230,6 +237,16 @@ const HELP = `
230
237
  npx nerviq badge Generate shields.io badge markdown
231
238
  npx nerviq feedback Record recommendation outcomes or show local outcome summary
232
239
 
240
+ Catalog:
241
+ npx nerviq catalog Show check catalog summary for all 8 platforms
242
+ npx nerviq catalog --json Full catalog as JSON
243
+ npx nerviq catalog --out catalog.json Write catalog to file
244
+
245
+ Utilities:
246
+ npx nerviq doctor Self-diagnostics: Node version, deps, freshness gates, platform detection
247
+ npx nerviq convert --from claude --to codex Convert config between platforms
248
+ npx nerviq migrate --platform cursor --from v2 --to v3 Migrate platform config to newer version
249
+
233
250
  Options:
234
251
  --threshold N Exit with code 1 if score is below N (useful for CI)
235
252
  --require A,B Exit with code 1 if named checks fail (e.g. --require secretsProtection,permissionDeny)
@@ -246,6 +263,7 @@ const HELP = `
246
263
  --score-delta N Optional observed score delta tied to the outcome
247
264
  --platform NAME Choose platform surface (claude default, codex advisory/build preview)
248
265
  --format NAME Output format for audit results (json, sarif)
266
+ --feedback After audit output, prompt "Was this helpful? (y/n)" for each displayed top action and save answers locally
249
267
  --snapshot Save a normalized snapshot artifact under .claude/nerviq/snapshots/
250
268
  --lite Show a short top-3 quick scan with one clear next command
251
269
  --dry-run Preview apply without writing files
@@ -316,6 +334,7 @@ async function main() {
316
334
  auto: flags.includes('--auto'),
317
335
  lite: flags.includes('--lite'),
318
336
  snapshot: flags.includes('--snapshot'),
337
+ feedback: flags.includes('--feedback'),
319
338
  dryRun: flags.includes('--dry-run'),
320
339
  threshold: parsed.threshold !== null ? Number(parsed.threshold) : null,
321
340
  out: parsed.out,
@@ -377,7 +396,7 @@ async function main() {
377
396
  const FULL_COMMAND_SET = new Set([
378
397
  'audit', 'scan', 'badge', 'augment', 'suggest-only', 'setup', 'plan', 'apply',
379
398
  'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'insights',
380
- 'history', 'compare', 'trend', 'feedback', 'help', 'version',
399
+ 'history', 'compare', 'trend', 'feedback', 'catalog', 'help', 'version',
381
400
  // Harmony + Synergy (cross-platform)
382
401
  'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise',
383
402
  'harmony-watch', 'harmony-governance', 'synergy-report',
@@ -699,6 +718,67 @@ async function main() {
699
718
  } else if (normalizedCommand === 'watch') {
700
719
  const { watch } = require('../src/watch');
701
720
  await watch(options);
721
+ } else if (normalizedCommand === 'catalog') {
722
+ const { generateCatalog, writeCatalogJson } = require('../src/catalog');
723
+ if (options.out) {
724
+ const result = writeCatalogJson(options.out);
725
+ if (options.json) {
726
+ console.log(JSON.stringify({ path: result.path, count: result.count }));
727
+ } else {
728
+ console.log(`\n Catalog written to ${result.path} (${result.count} checks)\n`);
729
+ }
730
+ } else {
731
+ const catalog = generateCatalog();
732
+ if (options.json) {
733
+ console.log(JSON.stringify(catalog, null, 2));
734
+ } else {
735
+ // Print summary table
736
+ const platforms = {};
737
+ for (const entry of catalog) {
738
+ platforms[entry.platform] = (platforms[entry.platform] || 0) + 1;
739
+ }
740
+ console.log('');
741
+ console.log('\x1b[1m nerviq check catalog\x1b[0m');
742
+ console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
743
+ console.log(` Total checks: \x1b[1m${catalog.length}\x1b[0m`);
744
+ console.log('');
745
+ for (const [plat, count] of Object.entries(platforms)) {
746
+ console.log(` ${plat.padEnd(12)} ${count} checks`);
747
+ }
748
+ console.log('');
749
+ console.log(' Use --json for full output or --out catalog.json to write file.');
750
+ console.log('');
751
+ }
752
+ }
753
+ process.exit(0);
754
+ } else if (normalizedCommand === 'doctor') {
755
+ const { runDoctor } = require('../src/doctor');
756
+ const output = await runDoctor({ dir: options.dir, json: options.json, verbose: options.verbose });
757
+ console.log(output);
758
+ process.exit(0);
759
+ } else if (normalizedCommand === 'convert') {
760
+ const { runConvert } = require('../src/convert');
761
+ const output = await runConvert({
762
+ dir: options.dir,
763
+ from: parsed.convertFrom,
764
+ to: parsed.convertTo,
765
+ dryRun: options.dryRun,
766
+ json: options.json,
767
+ });
768
+ console.log(output);
769
+ process.exit(0);
770
+ } else if (normalizedCommand === 'migrate') {
771
+ const { runMigrate } = require('../src/migrate');
772
+ const output = await runMigrate({
773
+ dir: options.dir,
774
+ platform: options.platform || parsed.platform || 'claude',
775
+ from: parsed.migrateFrom,
776
+ to: parsed.migrateTo,
777
+ dryRun: options.dryRun,
778
+ json: options.json,
779
+ });
780
+ console.log(output);
781
+ process.exit(0);
702
782
  } else if (normalizedCommand === 'setup') {
703
783
  await setup(options);
704
784
  if (options.snapshot) {
@@ -712,6 +792,25 @@ async function main() {
712
792
  }
713
793
  } else {
714
794
  const result = await audit(options);
795
+ if (options.feedback && !options.json && options.format === null) {
796
+ const feedbackTargets = options.lite
797
+ ? (result.liteSummary?.topNextActions || [])
798
+ : (result.topNextActions || []);
799
+ const feedbackResult = await collectFeedback(options.dir, {
800
+ findings: feedbackTargets,
801
+ platform: result.platform,
802
+ sourceCommand: normalizedCommand,
803
+ score: result.score,
804
+ });
805
+ if (feedbackResult.mode === 'skipped-noninteractive') {
806
+ console.log(' Feedback prompt skipped: interactive terminal required.');
807
+ console.log('');
808
+ } else if (feedbackResult.saved > 0) {
809
+ console.log(` Feedback saved: ${feedbackResult.relativeDir}`);
810
+ console.log(` Helpful: ${feedbackResult.helpful} | Not helpful: ${feedbackResult.unhelpful}`);
811
+ console.log('');
812
+ }
813
+ }
715
814
  const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'audit', result, {
716
815
  sourceCommand: normalizedCommand,
717
816
  }) : null;
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "The intelligent nervous system for AI coding agents — audit, align, and amplify every platform on every project.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "nerviq": "bin/cli.js"
7
+ "nerviq": "bin/cli.js",
8
+ "nerviq-mcp": "src/mcp-server.js"
8
9
  },
9
10
  "files": [
10
11
  "bin",
@@ -19,7 +20,9 @@
19
20
  "test": "node test/run.js",
20
21
  "test:jest": "jest",
21
22
  "test:coverage": "jest --coverage",
22
- "test:all": "node test/run.js && node test/check-matrix.js && node test/codex-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/security-tests.js && jest"
23
+ "test:all": "node test/run.js && node test/check-matrix.js && node test/codex-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/security-tests.js && jest",
24
+ "benchmark:perf": "node tools/benchmark.js",
25
+ "catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
23
26
  },
24
27
  "keywords": [
25
28
  "nerviq",
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  const { containsEmbeddedSecret } = require('../secret-patterns');
23
+ const { attachSourceUrls } = require('../source-urls');
23
24
 
24
25
  const FILLER_PATTERNS = [
25
26
  /\bbe helpful\b/i,
@@ -1389,6 +1390,8 @@ const AIDER_TECHNIQUES = {
1389
1390
  },
1390
1391
  };
1391
1392
 
1393
+ attachSourceUrls('aider', AIDER_TECHNIQUES);
1394
+
1392
1395
  module.exports = {
1393
1396
  AIDER_TECHNIQUES,
1394
1397
  };
package/src/audit.js CHANGED
@@ -461,7 +461,7 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
461
461
  return scoreB - scoreA;
462
462
  })
463
463
  .slice(0, limit)
464
- .map(({ key, id, name, impact, fix, category }) => {
464
+ .map(({ key, id, name, impact, fix, category, sourceUrl }) => {
465
465
  const feedback = outcomeSummaryByKey[key] || null;
466
466
  const rankingAdjustment = getRecommendationAdjustment(outcomeSummaryByKey, key);
467
467
  const signals = [
@@ -491,6 +491,7 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
491
491
  name,
492
492
  impact,
493
493
  category,
494
+ sourceUrl,
494
495
  module: CATEGORY_MODULES[category] || category,
495
496
  fix,
496
497
  priorityScore,
@@ -810,7 +811,7 @@ async function audit(options) {
810
811
  stacks,
811
812
  results,
812
813
  categoryScores,
813
- quickWins: quickWins.map(({ key, name, impact, fix, category }) => ({ key, name, impact, category, fix })),
814
+ quickWins: quickWins.map(({ key, name, impact, fix, category, sourceUrl }) => ({ key, name, impact, category, fix, sourceUrl })),
814
815
  topNextActions,
815
816
  recommendationOutcomes: {
816
817
  totalEntries: outcomeSummary.totalEntries,
package/src/catalog.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Public Check Catalog Generator
3
+ * Reads ALL technique files from all 8 platforms and generates a unified JSON catalog.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const { TECHNIQUES: CLAUDE_TECHNIQUES } = require('./techniques');
10
+ const { CODEX_TECHNIQUES } = require('./codex/techniques');
11
+ const { GEMINI_TECHNIQUES } = require('./gemini/techniques');
12
+ const { COPILOT_TECHNIQUES } = require('./copilot/techniques');
13
+ const { CURSOR_TECHNIQUES } = require('./cursor/techniques');
14
+ const { WINDSURF_TECHNIQUES } = require('./windsurf/techniques');
15
+ const { AIDER_TECHNIQUES } = require('./aider/techniques');
16
+ const { OPENCODE_TECHNIQUES } = require('./opencode/techniques');
17
+ const { attachSourceUrls } = require('./source-urls');
18
+
19
+ const PLATFORM_MAP = {
20
+ claude: CLAUDE_TECHNIQUES,
21
+ codex: CODEX_TECHNIQUES,
22
+ gemini: GEMINI_TECHNIQUES,
23
+ copilot: COPILOT_TECHNIQUES,
24
+ cursor: CURSOR_TECHNIQUES,
25
+ windsurf: WINDSURF_TECHNIQUES,
26
+ aider: AIDER_TECHNIQUES,
27
+ opencode: OPENCODE_TECHNIQUES,
28
+ };
29
+
30
+ /**
31
+ * Generate a unified catalog array from all platform technique files.
32
+ * Each entry contains:
33
+ * platform, id, key, name, category, impact, rating, fix, sourceUrl,
34
+ * confidence, template, deprecated
35
+ */
36
+ function generateCatalog() {
37
+ const catalog = [];
38
+
39
+ for (const [platform, techniques] of Object.entries(PLATFORM_MAP)) {
40
+ // Clone techniques so we don't mutate the originals
41
+ const cloned = {};
42
+ for (const [key, tech] of Object.entries(techniques)) {
43
+ cloned[key] = { ...tech };
44
+ }
45
+
46
+ // Attach source URLs
47
+ try {
48
+ attachSourceUrls(platform, cloned);
49
+ } catch (_) {
50
+ // If source URLs fail for a platform, continue without them
51
+ }
52
+
53
+ for (const [key, tech] of Object.entries(cloned)) {
54
+ catalog.push({
55
+ platform,
56
+ id: tech.id ?? null,
57
+ key,
58
+ name: tech.name ?? null,
59
+ category: tech.category ?? null,
60
+ impact: tech.impact ?? null,
61
+ rating: tech.rating ?? null,
62
+ fix: tech.fix ?? null,
63
+ sourceUrl: tech.sourceUrl ?? null,
64
+ confidence: tech.confidence ?? null,
65
+ template: tech.template ?? null,
66
+ deprecated: tech.deprecated ?? false,
67
+ });
68
+ }
69
+ }
70
+
71
+ return catalog;
72
+ }
73
+
74
+ /**
75
+ * Write the catalog as formatted JSON to the given output path.
76
+ * @param {string} outputPath - Absolute or relative path for the JSON file
77
+ * @returns {{ path: string, count: number }} Written path and entry count
78
+ */
79
+ function writeCatalogJson(outputPath) {
80
+ const catalog = generateCatalog();
81
+ const resolved = path.resolve(outputPath);
82
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
83
+ fs.writeFileSync(resolved, JSON.stringify(catalog, null, 2) + '\n', 'utf8');
84
+ return { path: resolved, count: catalog.length };
85
+ }
86
+
87
+ module.exports = { generateCatalog, writeCatalogJson };
@@ -1,6 +1,7 @@
1
1
  const os = require('os');
2
2
  const path = require('path');
3
3
  const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
4
+ const { attachSourceUrls } = require('../source-urls');
4
5
 
5
6
  const DEFAULT_PROJECT_DOC_MAX_BYTES = 32768;
6
7
  const SUPPORTED_HOOK_EVENTS = new Set(['SessionStart', 'PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'Stop']);
@@ -3249,6 +3250,8 @@ const CODEX_TECHNIQUES = {
3249
3250
  },
3250
3251
  };
3251
3252
 
3253
+ attachSourceUrls('codex', CODEX_TECHNIQUES);
3254
+
3252
3255
  module.exports = {
3253
3256
  CODEX_TECHNIQUES,
3254
3257
  };