@nerviq/cli 1.27.1 → 1.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +1527 -1407
  2. package/README.md +550 -538
  3. package/SECURITY.md +82 -82
  4. package/bin/cli.js +2562 -2558
  5. package/docs/api-reference.md +356 -356
  6. package/docs/audit-fix.md +109 -0
  7. package/docs/autofix.md +3 -62
  8. package/docs/getting-started.md +1 -1
  9. package/docs/index.html +592 -592
  10. package/docs/integration-contracts.md +287 -287
  11. package/docs/maintenance.md +128 -128
  12. package/docs/new-platform-guide.md +202 -202
  13. package/docs/release-process.md +63 -0
  14. package/docs/shallow-risk.md +244 -244
  15. package/docs/why-nerviq.md +82 -82
  16. package/package.json +67 -67
  17. package/src/aider/activity.js +226 -226
  18. package/src/aider/context.js +162 -162
  19. package/src/aider/freshness.js +123 -123
  20. package/src/aider/techniques.js +3465 -3465
  21. package/src/audit/layers.js +180 -180
  22. package/src/audit.js +1032 -1032
  23. package/src/benchmark.js +299 -299
  24. package/src/codex/activity.js +324 -324
  25. package/src/codex/freshness.js +142 -142
  26. package/src/codex/techniques.js +4895 -4895
  27. package/src/context.js +326 -326
  28. package/src/continuous-ops.js +11 -1
  29. package/src/convert.js +340 -340
  30. package/src/copilot/config-parser.js +280 -280
  31. package/src/copilot/context.js +218 -218
  32. package/src/copilot/freshness.js +177 -177
  33. package/src/copilot/patch.js +238 -238
  34. package/src/copilot/techniques.js +3578 -3578
  35. package/src/cursor/freshness.js +194 -194
  36. package/src/cursor/patch.js +243 -243
  37. package/src/cursor/techniques.js +3735 -3735
  38. package/src/doctor.js +201 -201
  39. package/src/fix-engine.js +511 -8
  40. package/src/formatters/csv.js +86 -86
  41. package/src/formatters/junit.js +123 -123
  42. package/src/formatters/markdown.js +164 -164
  43. package/src/formatters/otel.js +151 -151
  44. package/src/freshness.js +156 -156
  45. package/src/gemini/activity.js +402 -402
  46. package/src/gemini/context.js +290 -290
  47. package/src/gemini/freshness.js +183 -183
  48. package/src/gemini/patch.js +229 -229
  49. package/src/gemini/techniques.js +3811 -3811
  50. package/src/governance.js +533 -533
  51. package/src/harmony/audit.js +306 -306
  52. package/src/i18n.js +63 -63
  53. package/src/insights.js +119 -119
  54. package/src/integrations.js +134 -134
  55. package/src/locales/en.json +33 -33
  56. package/src/locales/es.json +33 -33
  57. package/src/migrate.js +354 -354
  58. package/src/opencode/activity.js +286 -286
  59. package/src/opencode/freshness.js +137 -137
  60. package/src/opencode/techniques.js +3450 -3450
  61. package/src/setup/analysis.js +12 -12
  62. package/src/setup.js +7 -6
  63. package/src/shallow-risk/index.js +56 -56
  64. package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +50 -50
  65. package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +46 -46
  66. package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +46 -46
  67. package/src/shallow-risk/patterns/agent-config-missing-file.js +317 -72
  68. package/src/shallow-risk/patterns/agent-config-secret-literal.js +49 -49
  69. package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +34 -34
  70. package/src/shallow-risk/patterns/hook-script-missing.js +70 -70
  71. package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +52 -52
  72. package/src/shallow-risk/shared.js +648 -520
  73. package/src/source-urls.js +295 -295
  74. package/src/state-paths.js +85 -85
  75. package/src/supplemental-checks.js +805 -805
  76. package/src/telemetry.js +160 -160
  77. package/src/windsurf/context.js +359 -359
  78. package/src/windsurf/freshness.js +194 -194
  79. package/src/windsurf/patch.js +231 -231
  80. package/src/windsurf/techniques.js +3779 -3779
package/src/doctor.js CHANGED
@@ -1,10 +1,10 @@
1
- /**
2
- * Nerviq Doctor
3
- *
4
- * Self-diagnostics for the nerviq CLI and the current project environment.
5
- * Checks: Node version, dependencies, platform detection, freshness gates.
6
- */
7
-
1
+ /**
2
+ * Nerviq Doctor
3
+ *
4
+ * Self-diagnostics for the nerviq CLI and the current project environment.
5
+ * Checks: Node version, dependencies, platform detection, freshness gates.
6
+ */
7
+
8
8
  'use strict';
9
9
 
10
10
  const fs = require('fs');
@@ -12,168 +12,168 @@ const path = require('path');
12
12
  const { version } = require('../package.json');
13
13
  const { validateDeclaredMcpServers } = require('./mcp-validation');
14
14
  const { validateDeclaredHooks } = require('./hook-validation');
15
-
16
- const COLORS = {
17
- reset: '\x1b[0m',
18
- bold: '\x1b[1m',
19
- dim: '\x1b[2m',
20
- red: '\x1b[31m',
21
- green: '\x1b[32m',
22
- yellow: '\x1b[33m',
23
- blue: '\x1b[36m',
24
- };
25
-
26
- function c(text, color) {
27
- return `${COLORS[color] || ''}${text}${COLORS.reset}`;
28
- }
29
-
15
+
16
+ const COLORS = {
17
+ reset: '\x1b[0m',
18
+ bold: '\x1b[1m',
19
+ dim: '\x1b[2m',
20
+ red: '\x1b[31m',
21
+ green: '\x1b[32m',
22
+ yellow: '\x1b[33m',
23
+ blue: '\x1b[36m',
24
+ };
25
+
26
+ function c(text, color) {
27
+ return `${COLORS[color] || ''}${text}${COLORS.reset}`;
28
+ }
29
+
30
30
  const PLATFORM_SIGNALS = {
31
31
  claude: ['CLAUDE.md', '.claude/CLAUDE.md', '.claude/settings.json', '.mcp.json'],
32
32
  codex: ['AGENTS.md', '.codex/', '.codex/config.toml'],
33
33
  cursor: ['.cursor/rules/', '.cursor/mcp.json', '.cursorrules'],
34
34
  copilot: ['.github/copilot-instructions.md', '.github/', '.vscode/mcp.json'],
35
- gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
36
- windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
37
- aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
38
- opencode: ['opencode.json', '.opencode/'],
39
- };
40
-
41
- const FRESHNESS_MODULES = {
42
- claude: './freshness',
43
- codex: './codex/freshness',
44
- cursor: './cursor/freshness',
45
- copilot: './copilot/freshness',
46
- gemini: './gemini/freshness',
47
- windsurf: './windsurf/freshness',
48
- aider: './aider/freshness',
49
- opencode: './opencode/freshness',
50
- };
51
-
52
- // ─── Individual checks ───────────────────────────────────────────────────────
53
-
54
- function checkNodeVersion() {
55
- const raw = process.version.replace('v', '');
56
- const [major] = raw.split('.').map(Number);
57
- const ok = major >= 18;
58
- return {
59
- label: 'Node.js version',
60
- status: ok ? 'pass' : 'fail',
61
- detail: `${process.version} (${ok ? 'meets' : 'below'} minimum v18)`,
62
- fix: ok ? null : 'Upgrade Node.js to v18 or later: https://nodejs.org',
63
- };
64
- }
65
-
66
- function checkDeps() {
67
- const pkgPath = path.join(__dirname, '..', 'node_modules');
68
- const exists = fs.existsSync(pkgPath);
69
- return {
70
- label: 'node_modules installed',
71
- status: exists ? 'pass' : 'fail',
72
- detail: exists ? `${pkgPath}` : 'node_modules missing',
73
- fix: exists ? null : 'Run: npm install',
74
- };
75
- }
76
-
77
- function checkJestInstalled() {
78
- const jestPath = path.join(__dirname, '..', 'node_modules', 'jest', 'package.json');
79
- const exists = fs.existsSync(jestPath);
80
- let jestVersion = null;
81
- if (exists) {
82
- try {
83
- jestVersion = require(jestPath).version;
84
- } catch {}
85
- }
86
- return {
87
- label: 'Jest test runner',
88
- status: exists ? 'pass' : 'warn',
89
- detail: exists ? `jest@${jestVersion}` : 'jest not found in node_modules',
90
- fix: exists ? null : 'Run: npm install --save-dev jest',
91
- };
92
- }
93
-
94
- function checkPlatformDetection(dir) {
95
- const detected = [];
96
- for (const [platform, signals] of Object.entries(PLATFORM_SIGNALS)) {
97
- for (const signal of signals) {
98
- const signalPath = path.join(dir, signal);
99
- if (fs.existsSync(signalPath)) {
100
- detected.push(platform);
101
- break;
102
- }
103
- }
104
- }
105
-
106
- return {
107
- label: 'Platform detection',
108
- status: detected.length > 0 ? 'pass' : 'warn',
109
- detail: detected.length > 0
110
- ? `Detected: ${detected.join(', ')}`
111
- : 'No platform config files found in current directory',
112
- detected,
113
- fix: detected.length === 0
114
- ? 'Run `nerviq setup` to generate baseline config files for your platform'
115
- : null,
116
- };
117
- }
118
-
119
- function checkFreshnessGates() {
120
- const results = [];
121
- for (const [platform, modulePath] of Object.entries(FRESHNESS_MODULES)) {
122
- try {
123
- const freshness = require(modulePath);
124
- const gate = freshness.checkReleaseGate({});
125
- const staleCount = (gate.stale || []).length;
126
- const freshCount = (gate.fresh || []).length;
127
- const totalCount = (gate.results || []).length;
128
- results.push({
129
- platform,
130
- status: staleCount === 0 ? 'pass' : 'warn',
131
- fresh: freshCount,
132
- total: totalCount,
133
- stale: staleCount,
134
- detail: staleCount === 0
135
- ? `All ${totalCount} P0 sources fresh`
136
- : `${staleCount}/${totalCount} P0 sources unverified/stale`,
137
- });
138
- } catch (e) {
139
- results.push({ platform, status: 'error', detail: e.message });
140
- }
141
- }
142
- return results;
143
- }
144
-
145
- function checkCliPermissions() {
146
- const cliBin = path.join(__dirname, '..', 'bin', 'cli.js');
147
- const exists = fs.existsSync(cliBin);
148
- if (!exists) {
149
- return { label: 'CLI binary (bin/cli.js)', status: 'fail', detail: 'bin/cli.js not found', fix: null };
150
- }
151
- return { label: 'CLI binary (bin/cli.js)', status: 'pass', detail: cliBin, fix: null };
152
- }
153
-
154
- function checkGitRepo(dir) {
155
- const gitPath = path.join(dir, '.git');
156
- const exists = fs.existsSync(gitPath);
157
- return {
158
- label: 'Git repository',
159
- status: exists ? 'pass' : 'warn',
160
- detail: exists ? '.git/ found' : 'Not a git repository',
161
- fix: exists ? null : 'Run: git init (recommended for safety)',
162
- };
163
- }
164
-
165
- // ─── Main doctor function ────────────────────────────────────────────────────
166
-
35
+ gemini: ['GEMINI.md', '.gemini/', '.gemini/settings.json'],
36
+ windsurf: ['.windsurf/', '.windsurfrules', '.windsurf/rules/'],
37
+ aider: ['.aider.conf.yml', '.aider.model.settings.yml'],
38
+ opencode: ['opencode.json', '.opencode/'],
39
+ };
40
+
41
+ const FRESHNESS_MODULES = {
42
+ claude: './freshness',
43
+ codex: './codex/freshness',
44
+ cursor: './cursor/freshness',
45
+ copilot: './copilot/freshness',
46
+ gemini: './gemini/freshness',
47
+ windsurf: './windsurf/freshness',
48
+ aider: './aider/freshness',
49
+ opencode: './opencode/freshness',
50
+ };
51
+
52
+ // ─── Individual checks ───────────────────────────────────────────────────────
53
+
54
+ function checkNodeVersion() {
55
+ const raw = process.version.replace('v', '');
56
+ const [major] = raw.split('.').map(Number);
57
+ const ok = major >= 18;
58
+ return {
59
+ label: 'Node.js version',
60
+ status: ok ? 'pass' : 'fail',
61
+ detail: `${process.version} (${ok ? 'meets' : 'below'} minimum v18)`,
62
+ fix: ok ? null : 'Upgrade Node.js to v18 or later: https://nodejs.org',
63
+ };
64
+ }
65
+
66
+ function checkDeps() {
67
+ const pkgPath = path.join(__dirname, '..', 'node_modules');
68
+ const exists = fs.existsSync(pkgPath);
69
+ return {
70
+ label: 'node_modules installed',
71
+ status: exists ? 'pass' : 'fail',
72
+ detail: exists ? `${pkgPath}` : 'node_modules missing',
73
+ fix: exists ? null : 'Run: npm install',
74
+ };
75
+ }
76
+
77
+ function checkJestInstalled() {
78
+ const jestPath = path.join(__dirname, '..', 'node_modules', 'jest', 'package.json');
79
+ const exists = fs.existsSync(jestPath);
80
+ let jestVersion = null;
81
+ if (exists) {
82
+ try {
83
+ jestVersion = require(jestPath).version;
84
+ } catch {}
85
+ }
86
+ return {
87
+ label: 'Jest test runner',
88
+ status: exists ? 'pass' : 'warn',
89
+ detail: exists ? `jest@${jestVersion}` : 'jest not found in node_modules',
90
+ fix: exists ? null : 'Run: npm install --save-dev jest',
91
+ };
92
+ }
93
+
94
+ function checkPlatformDetection(dir) {
95
+ const detected = [];
96
+ for (const [platform, signals] of Object.entries(PLATFORM_SIGNALS)) {
97
+ for (const signal of signals) {
98
+ const signalPath = path.join(dir, signal);
99
+ if (fs.existsSync(signalPath)) {
100
+ detected.push(platform);
101
+ break;
102
+ }
103
+ }
104
+ }
105
+
106
+ return {
107
+ label: 'Platform detection',
108
+ status: detected.length > 0 ? 'pass' : 'warn',
109
+ detail: detected.length > 0
110
+ ? `Detected: ${detected.join(', ')}`
111
+ : 'No platform config files found in current directory',
112
+ detected,
113
+ fix: detected.length === 0
114
+ ? 'Run `nerviq setup` to generate baseline config files for your platform'
115
+ : null,
116
+ };
117
+ }
118
+
119
+ function checkFreshnessGates() {
120
+ const results = [];
121
+ for (const [platform, modulePath] of Object.entries(FRESHNESS_MODULES)) {
122
+ try {
123
+ const freshness = require(modulePath);
124
+ const gate = freshness.checkReleaseGate({});
125
+ const staleCount = (gate.stale || []).length;
126
+ const freshCount = (gate.fresh || []).length;
127
+ const totalCount = (gate.results || []).length;
128
+ results.push({
129
+ platform,
130
+ status: staleCount === 0 ? 'pass' : 'warn',
131
+ fresh: freshCount,
132
+ total: totalCount,
133
+ stale: staleCount,
134
+ detail: staleCount === 0
135
+ ? `All ${totalCount} P0 sources fresh`
136
+ : `${staleCount}/${totalCount} P0 sources unverified/stale`,
137
+ });
138
+ } catch (e) {
139
+ results.push({ platform, status: 'error', detail: e.message });
140
+ }
141
+ }
142
+ return results;
143
+ }
144
+
145
+ function checkCliPermissions() {
146
+ const cliBin = path.join(__dirname, '..', 'bin', 'cli.js');
147
+ const exists = fs.existsSync(cliBin);
148
+ if (!exists) {
149
+ return { label: 'CLI binary (bin/cli.js)', status: 'fail', detail: 'bin/cli.js not found', fix: null };
150
+ }
151
+ return { label: 'CLI binary (bin/cli.js)', status: 'pass', detail: cliBin, fix: null };
152
+ }
153
+
154
+ function checkGitRepo(dir) {
155
+ const gitPath = path.join(dir, '.git');
156
+ const exists = fs.existsSync(gitPath);
157
+ return {
158
+ label: 'Git repository',
159
+ status: exists ? 'pass' : 'warn',
160
+ detail: exists ? '.git/ found' : 'Not a git repository',
161
+ fix: exists ? null : 'Run: git init (recommended for safety)',
162
+ };
163
+ }
164
+
165
+ // ─── Main doctor function ────────────────────────────────────────────────────
166
+
167
167
  async function runDoctor({ dir = process.cwd(), json = false, verbose = false } = {}) {
168
168
  const startMs = Date.now();
169
169
 
170
170
  const checks = [
171
- checkNodeVersion(),
172
- checkDeps(),
173
- checkJestInstalled(),
174
- checkCliPermissions(),
175
- checkGitRepo(dir),
176
- checkPlatformDetection(dir),
171
+ checkNodeVersion(),
172
+ checkDeps(),
173
+ checkJestInstalled(),
174
+ checkCliPermissions(),
175
+ checkGitRepo(dir),
176
+ checkPlatformDetection(dir),
177
177
  ];
178
178
 
179
179
  const detectedPlatforms = (checks.find(c => c.detected) || {}).detected || [];
@@ -184,7 +184,7 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
184
184
  const totalPass = checks.filter(c => c.status === 'pass').length;
185
185
  const totalWarn = checks.filter(c => c.status === 'warn').length;
186
186
  const totalFail = checks.filter(c => c.status === 'fail').length;
187
-
187
+
188
188
  const freshPass = freshnessChecks.filter(f => f.status === 'pass').length;
189
189
  const freshWarn = freshnessChecks.filter(f => f.status !== 'pass').length;
190
190
 
@@ -194,10 +194,10 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
194
194
  if (json) {
195
195
  return JSON.stringify({
196
196
  nerviq: version,
197
- node: process.version,
198
- dir,
199
- overallOk,
200
- checks,
197
+ node: process.version,
198
+ dir,
199
+ overallOk,
200
+ checks,
201
201
  freshnessChecks,
202
202
  mcpChecks: mcpSummary.checks,
203
203
  hookChecks: hookSummary.checks,
@@ -217,34 +217,34 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
217
217
  elapsed,
218
218
  }, null, 2);
219
219
  }
220
-
221
- const lines = [''];
222
- lines.push(c(` nerviq doctor v${version}`, 'bold'));
223
- lines.push(c(' ═══════════════════════════════════════', 'dim'));
224
- lines.push('');
225
-
226
- // Environment checks
227
- lines.push(c(' Environment', 'bold'));
228
- for (const chk of checks) {
229
- const icon = chk.status === 'pass' ? c('✓', 'green') : chk.status === 'warn' ? c('⚠', 'yellow') : c('✗', 'red');
230
- lines.push(` ${icon} ${chk.label.padEnd(32)} ${c(chk.detail, chk.status === 'pass' ? 'dim' : 'reset')}`);
231
- if (chk.fix && (verbose || chk.status === 'fail')) {
232
- lines.push(c(` Fix: ${chk.fix}`, 'yellow'));
233
- }
234
- }
235
-
236
- // Platform detection detail
220
+
221
+ const lines = [''];
222
+ lines.push(c(` nerviq doctor v${version}`, 'bold'));
223
+ lines.push(c(' ═══════════════════════════════════════', 'dim'));
224
+ lines.push('');
225
+
226
+ // Environment checks
227
+ lines.push(c(' Environment', 'bold'));
228
+ for (const chk of checks) {
229
+ const icon = chk.status === 'pass' ? c('✓', 'green') : chk.status === 'warn' ? c('⚠', 'yellow') : c('✗', 'red');
230
+ lines.push(` ${icon} ${chk.label.padEnd(32)} ${c(chk.detail, chk.status === 'pass' ? 'dim' : 'reset')}`);
231
+ if (chk.fix && (verbose || chk.status === 'fail')) {
232
+ lines.push(c(` Fix: ${chk.fix}`, 'yellow'));
233
+ }
234
+ }
235
+
236
+ // Platform detection detail
237
237
  if (detectedPlatforms.length > 0) {
238
238
  lines.push('');
239
239
  lines.push(c(' Detected Platforms', 'bold'));
240
- for (const p of detectedPlatforms) {
241
- lines.push(` ${c('✓', 'green')} ${p}`);
242
- }
243
- }
244
-
245
- // Freshness
246
- lines.push('');
247
- lines.push(c(' Freshness Gates', 'bold'));
240
+ for (const p of detectedPlatforms) {
241
+ lines.push(` ${c('✓', 'green')} ${p}`);
242
+ }
243
+ }
244
+
245
+ // Freshness
246
+ lines.push('');
247
+ lines.push(c(' Freshness Gates', 'bold'));
248
248
  for (const f of freshnessChecks) {
249
249
  const icon = f.status === 'pass' ? c('✓', 'green') : c('⚠', 'yellow');
250
250
  const label = f.platform.padEnd(12);
@@ -306,14 +306,14 @@ async function runDoctor({ dir = process.cwd(), json = false, verbose = false }
306
306
  lines.push(` Hooks: ${c(String(hookSummary.pass), 'green')} pass ${hookSummary.warn > 0 ? c(String(hookSummary.warn), 'yellow') + ' warn ' : ''}${hookSummary.fail > 0 ? c(String(hookSummary.fail), 'red') + ' fail' : ''}${c(`(${hookSummary.declared} declared)`, 'dim')}`);
307
307
  lines.push(` Status: ${overallOk ? c('✓ Healthy', 'green') : c('✗ Issues found', 'red')}`);
308
308
  lines.push(` Duration: ${elapsed}ms`);
309
- lines.push('');
310
-
311
- if (!overallOk) {
312
- lines.push(c(' Run with --verbose for fix suggestions.', 'dim'));
313
- lines.push('');
314
- }
315
-
316
- return lines.join('\n');
317
- }
318
-
319
- module.exports = { runDoctor };
309
+ lines.push('');
310
+
311
+ if (!overallOk) {
312
+ lines.push(c(' Run with --verbose for fix suggestions.', 'dim'));
313
+ lines.push('');
314
+ }
315
+
316
+ return lines.join('\n');
317
+ }
318
+
319
+ module.exports = { runDoctor };