@nerviq/cli 1.29.0 → 1.30.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +1764 -1493
  2. package/README.md +568 -538
  3. package/SECURITY.md +78 -82
  4. package/bin/cli.js +2838 -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 +75 -67
  17. package/sdk/README.md +12 -3
  18. package/sdk/examples/langchain-integration.md +128 -0
  19. package/sdk/examples/self-governing-agent.js +135 -0
  20. package/sdk/index.d.ts +115 -0
  21. package/sdk/index.js +94 -0
  22. package/sdk/package.json +11 -0
  23. package/src/activity.js +13 -0
  24. package/src/aider/activity.js +226 -226
  25. package/src/aider/context.js +162 -162
  26. package/src/aider/freshness.js +123 -123
  27. package/src/aider/techniques.js +3465 -3465
  28. package/src/audit/layers.js +180 -180
  29. package/src/audit.js +1133 -1032
  30. package/src/auto-suggest.js +9 -2
  31. package/src/behavioral-drift.js +37 -2
  32. package/src/benchmark.js +299 -299
  33. package/src/codex/activity.js +324 -324
  34. package/src/codex/freshness.js +149 -142
  35. package/src/codex/techniques.js +4895 -4895
  36. package/src/context.js +326 -326
  37. package/src/continuous-ops.js +11 -1
  38. package/src/convert.js +340 -340
  39. package/src/copilot/config-parser.js +280 -280
  40. package/src/copilot/context.js +218 -218
  41. package/src/copilot/freshness.js +184 -177
  42. package/src/copilot/patch.js +238 -238
  43. package/src/copilot/techniques.js +3578 -3578
  44. package/src/cursor/freshness.js +194 -194
  45. package/src/cursor/patch.js +243 -243
  46. package/src/cursor/techniques.js +3735 -3735
  47. package/src/doctor.js +201 -201
  48. package/src/fix-engine.js +511 -8
  49. package/src/formatters/csv.js +86 -86
  50. package/src/formatters/junit.js +123 -123
  51. package/src/formatters/markdown.js +164 -164
  52. package/src/formatters/otel.js +151 -151
  53. package/src/freshness.js +163 -156
  54. package/src/gemini/activity.js +402 -402
  55. package/src/gemini/context.js +290 -290
  56. package/src/gemini/freshness.js +188 -188
  57. package/src/gemini/patch.js +229 -229
  58. package/src/gemini/techniques.js +3811 -3811
  59. package/src/governance.js +533 -533
  60. package/src/harmony/audit.js +306 -306
  61. package/src/i18n.js +63 -63
  62. package/src/insights.js +119 -119
  63. package/src/integrations.js +134 -134
  64. package/src/locales/en.json +33 -33
  65. package/src/locales/es.json +33 -33
  66. package/src/migrate.js +354 -354
  67. package/src/opencode/activity.js +286 -286
  68. package/src/opencode/freshness.js +137 -137
  69. package/src/opencode/techniques.js +3450 -3450
  70. package/src/safe-glyph.js +97 -0
  71. package/src/setup/analysis.js +12 -12
  72. package/src/setup.js +13 -6
  73. package/src/shallow-risk/index.js +113 -56
  74. package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +51 -50
  75. package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +47 -46
  76. package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +47 -46
  77. package/src/shallow-risk/patterns/agent-config-framework-version-mismatch.js +138 -0
  78. package/src/shallow-risk/patterns/agent-config-missing-file.js +318 -317
  79. package/src/shallow-risk/patterns/agent-config-script-not-in-package-json.js +108 -0
  80. package/src/shallow-risk/patterns/agent-config-secret-literal.js +52 -49
  81. package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +35 -34
  82. package/src/shallow-risk/patterns/hook-script-missing.js +71 -70
  83. package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +53 -52
  84. package/src/shallow-risk/shared.js +653 -648
  85. package/src/source-urls.js +295 -295
  86. package/src/state-paths.js +85 -85
  87. package/src/supplemental-checks.js +805 -805
  88. package/src/telemetry.js +160 -160
  89. package/src/watch.js +46 -0
  90. package/src/windsurf/context.js +359 -359
  91. package/src/windsurf/freshness.js +194 -194
  92. package/src/windsurf/patch.js +231 -231
  93. 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 };