@nerviq/cli 1.29.0 → 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 -1493
  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 -317
  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 -648
  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
@@ -1,49 +1,49 @@
1
- 'use strict';
2
-
3
- const { SHALLOW_RISK_DOC_URL, getAgentConfigEntries } = require('../shared');
4
-
5
- const SECRET_PATTERNS = [
6
- { label: 'AWS access key', pattern: /\bAKIA[0-9A-Z]{16}\b/g },
7
- { label: 'Stripe live key', pattern: /\bsk_live_[A-Za-z0-9]{24,}\b/g },
8
- { label: 'GitHub personal access token', pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
9
- { label: 'SSH private key header', pattern: /-----BEGIN (?:OPENSSH|RSA|DSA|EC) PRIVATE KEY-----/g },
10
- ];
11
-
12
- function looksLikePlaceholder(line, matchText) {
13
- return /\b(example|sample|placeholder|replace[-_ ]?me|your[_-]?key|fake|dummy)\b/i.test(line) ||
14
- /\bEXAMPLE\b/.test(matchText);
15
- }
16
-
17
- module.exports = {
18
- key: 'agent-config-secret-literal',
19
- name: 'Agent config contains secret literal',
20
- severity: 'critical',
21
- layer: 'shallow-risk',
22
- sourceUrl: SHALLOW_RISK_DOC_URL,
23
- run(ctx) {
24
- const findings = [];
25
-
26
- for (const entry of getAgentConfigEntries(ctx)) {
27
- const lines = entry.content.split(/\r?\n/);
28
- for (let index = 0; index < lines.length; index++) {
29
- const line = lines[index];
30
- for (const secret of SECRET_PATTERNS) {
31
- secret.pattern.lastIndex = 0;
32
- let match = secret.pattern.exec(line);
33
- while (match) {
34
- if (!looksLikePlaceholder(line, match[0])) {
35
- findings.push({
36
- file: entry.path,
37
- line: index + 1,
38
- fix: `${entry.path} contains a ${secret.label} shape. Rotate the secret, remove it from the agent config, and scrub it from git history if it was real.`,
39
- });
40
- }
41
- match = secret.pattern.exec(line);
42
- }
43
- }
44
- }
45
- }
46
-
47
- return findings;
48
- },
49
- };
1
+ 'use strict';
2
+
3
+ const { SHALLOW_RISK_DOC_URL, getAgentConfigEntries } = require('../shared');
4
+
5
+ const SECRET_PATTERNS = [
6
+ { label: 'AWS access key', pattern: /\bAKIA[0-9A-Z]{16}\b/g },
7
+ { label: 'Stripe live key', pattern: /\bsk_live_[A-Za-z0-9]{24,}\b/g },
8
+ { label: 'GitHub personal access token', pattern: /\bghp_[A-Za-z0-9]{36}\b/g },
9
+ { label: 'SSH private key header', pattern: /-----BEGIN (?:OPENSSH|RSA|DSA|EC) PRIVATE KEY-----/g },
10
+ ];
11
+
12
+ function looksLikePlaceholder(line, matchText) {
13
+ return /\b(example|sample|placeholder|replace[-_ ]?me|your[_-]?key|fake|dummy)\b/i.test(line) ||
14
+ /\bEXAMPLE\b/.test(matchText);
15
+ }
16
+
17
+ module.exports = {
18
+ key: 'agent-config-secret-literal',
19
+ name: 'Agent config contains secret literal',
20
+ severity: 'critical',
21
+ layer: 'shallow-risk',
22
+ sourceUrl: SHALLOW_RISK_DOC_URL,
23
+ run(ctx) {
24
+ const findings = [];
25
+
26
+ for (const entry of getAgentConfigEntries(ctx)) {
27
+ const lines = entry.content.split(/\r?\n/);
28
+ for (let index = 0; index < lines.length; index++) {
29
+ const line = lines[index];
30
+ for (const secret of SECRET_PATTERNS) {
31
+ secret.pattern.lastIndex = 0;
32
+ let match = secret.pattern.exec(line);
33
+ while (match) {
34
+ if (!looksLikePlaceholder(line, match[0])) {
35
+ findings.push({
36
+ file: entry.path,
37
+ line: index + 1,
38
+ fix: `${entry.path} contains a ${secret.label} shape. Rotate the secret, remove it from the agent config, and scrub it from git history if it was real.`,
39
+ });
40
+ }
41
+ match = secret.pattern.exec(line);
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ return findings;
48
+ },
49
+ };
@@ -1,34 +1,34 @@
1
- 'use strict';
2
-
3
- const {
4
- SHALLOW_RISK_DOC_URL,
5
- collectStackClaims,
6
- findFirstStackEvidence,
7
- getDetectedStackEvidence,
8
- } = require('../shared');
9
-
10
- module.exports = {
11
- key: 'agent-config-stack-contradiction',
12
- name: 'Agent config contradicts actual stack',
13
- severity: 'high',
14
- layer: 'shallow-risk',
15
- sourceUrl: SHALLOW_RISK_DOC_URL,
16
- run(ctx) {
17
- const claims = collectStackClaims(ctx);
18
- const distinctClaims = [...new Set(claims.map((claim) => claim.key))];
19
- if (distinctClaims.length !== 1) return [];
20
-
21
- const declared = claims[0];
22
- const hasDeclaredEvidence = declared.stackKeys.some((stackKey) => Boolean(findFirstStackEvidence(ctx, stackKey)));
23
- if (hasDeclaredEvidence) return [];
24
-
25
- const actual = getDetectedStackEvidence(ctx).find((item) => !declared.stackKeys.includes(item.key));
26
- if (!actual) return [];
27
-
28
- return [{
29
- file: declared.file,
30
- line: declared.line,
31
- fix: `${declared.file} declares the primary stack as "${declared.label}", but the repo shows ${actual.label} signals (${actual.file}) and no ${declared.label} evidence. Align the agent guidance with the actual stack or document a real migration plan.`,
32
- }];
33
- },
34
- };
1
+ 'use strict';
2
+
3
+ const {
4
+ SHALLOW_RISK_DOC_URL,
5
+ collectStackClaims,
6
+ findFirstStackEvidence,
7
+ getDetectedStackEvidence,
8
+ } = require('../shared');
9
+
10
+ module.exports = {
11
+ key: 'agent-config-stack-contradiction',
12
+ name: 'Agent config contradicts actual stack',
13
+ severity: 'high',
14
+ layer: 'shallow-risk',
15
+ sourceUrl: SHALLOW_RISK_DOC_URL,
16
+ run(ctx) {
17
+ const claims = collectStackClaims(ctx);
18
+ const distinctClaims = [...new Set(claims.map((claim) => claim.key))];
19
+ if (distinctClaims.length !== 1) return [];
20
+
21
+ const declared = claims[0];
22
+ const hasDeclaredEvidence = declared.stackKeys.some((stackKey) => Boolean(findFirstStackEvidence(ctx, stackKey)));
23
+ if (hasDeclaredEvidence) return [];
24
+
25
+ const actual = getDetectedStackEvidence(ctx).find((item) => !declared.stackKeys.includes(item.key));
26
+ if (!actual) return [];
27
+
28
+ return [{
29
+ file: declared.file,
30
+ line: declared.line,
31
+ fix: `${declared.file} declares the primary stack as "${declared.label}", but the repo shows ${actual.label} signals (${actual.file}) and no ${declared.label} evidence. Align the agent guidance with the actual stack or document a real migration plan.`,
32
+ }];
33
+ },
34
+ };
@@ -1,70 +1,70 @@
1
- 'use strict';
2
-
3
- const {
4
- SHALLOW_RISK_DOC_URL,
5
- escapeRegExp,
6
- getHookCommandPath,
7
- resolveRepoPath,
8
- } = require('../shared');
9
-
10
- const HOOK_EVENTS = new Set([
11
- 'PreToolUse',
12
- 'PostToolUse',
13
- 'Stop',
14
- 'UserPromptSubmit',
15
- 'SessionStart',
16
- ]);
17
-
18
- function collectHookCommands(node, output = []) {
19
- if (Array.isArray(node)) {
20
- for (const item of node) collectHookCommands(item, output);
21
- return output;
22
- }
23
-
24
- if (!node || typeof node !== 'object') {
25
- return output;
26
- }
27
-
28
- if (node.type === 'command' && typeof node.command === 'string') {
29
- output.push(node.command);
30
- }
31
-
32
- for (const value of Object.values(node)) {
33
- collectHookCommands(value, output);
34
- }
35
-
36
- return output;
37
- }
38
-
39
- module.exports = {
40
- key: 'hook-script-missing',
41
- name: 'Configured hook script is missing',
42
- severity: 'high',
43
- layer: 'shallow-risk',
44
- sourceUrl: SHALLOW_RISK_DOC_URL,
45
- run(ctx) {
46
- const file = '.claude/settings.json';
47
- const config = ctx.jsonFile(file);
48
- if (!config || !config.hooks || typeof config.hooks !== 'object') {
49
- return [];
50
- }
51
-
52
- const findings = [];
53
- for (const [eventName, entries] of Object.entries(config.hooks)) {
54
- if (!HOOK_EVENTS.has(eventName)) continue;
55
- for (const command of collectHookCommands(entries)) {
56
- const scriptPath = getHookCommandPath(command);
57
- if (!scriptPath) continue;
58
- const resolvedPath = resolveRepoPath(ctx, file, scriptPath, 'repo-root');
59
- if (!resolvedPath || ctx.fileContent(resolvedPath) !== null) continue;
60
- findings.push({
61
- file,
62
- line: ctx.lineNumber(file, new RegExp(escapeRegExp(command))) || ctx.lineNumber(file, new RegExp(`"${escapeRegExp(eventName)}"`)) || 1,
63
- fix: `${file} declares a ${eventName} hook at \`${resolvedPath}\`, but the script is missing. Create the hook file or remove the dead hook reference.`,
64
- });
65
- }
66
- }
67
-
68
- return findings;
69
- },
70
- };
1
+ 'use strict';
2
+
3
+ const {
4
+ SHALLOW_RISK_DOC_URL,
5
+ escapeRegExp,
6
+ getHookCommandPath,
7
+ resolveRepoPath,
8
+ } = require('../shared');
9
+
10
+ const HOOK_EVENTS = new Set([
11
+ 'PreToolUse',
12
+ 'PostToolUse',
13
+ 'Stop',
14
+ 'UserPromptSubmit',
15
+ 'SessionStart',
16
+ ]);
17
+
18
+ function collectHookCommands(node, output = []) {
19
+ if (Array.isArray(node)) {
20
+ for (const item of node) collectHookCommands(item, output);
21
+ return output;
22
+ }
23
+
24
+ if (!node || typeof node !== 'object') {
25
+ return output;
26
+ }
27
+
28
+ if (node.type === 'command' && typeof node.command === 'string') {
29
+ output.push(node.command);
30
+ }
31
+
32
+ for (const value of Object.values(node)) {
33
+ collectHookCommands(value, output);
34
+ }
35
+
36
+ return output;
37
+ }
38
+
39
+ module.exports = {
40
+ key: 'hook-script-missing',
41
+ name: 'Configured hook script is missing',
42
+ severity: 'high',
43
+ layer: 'shallow-risk',
44
+ sourceUrl: SHALLOW_RISK_DOC_URL,
45
+ run(ctx) {
46
+ const file = '.claude/settings.json';
47
+ const config = ctx.jsonFile(file);
48
+ if (!config || !config.hooks || typeof config.hooks !== 'object') {
49
+ return [];
50
+ }
51
+
52
+ const findings = [];
53
+ for (const [eventName, entries] of Object.entries(config.hooks)) {
54
+ if (!HOOK_EVENTS.has(eventName)) continue;
55
+ for (const command of collectHookCommands(entries)) {
56
+ const scriptPath = getHookCommandPath(command);
57
+ if (!scriptPath) continue;
58
+ const resolvedPath = resolveRepoPath(ctx, file, scriptPath, 'repo-root');
59
+ if (!resolvedPath || ctx.fileContent(resolvedPath) !== null) continue;
60
+ findings.push({
61
+ file,
62
+ line: ctx.lineNumber(file, new RegExp(escapeRegExp(command))) || ctx.lineNumber(file, new RegExp(`"${escapeRegExp(eventName)}"`)) || 1,
63
+ fix: `${file} declares a ${eventName} hook at \`${resolvedPath}\`, but the script is missing. Create the hook file or remove the dead hook reference.`,
64
+ });
65
+ }
66
+ }
67
+
68
+ return findings;
69
+ },
70
+ };
@@ -1,52 +1,52 @@
1
- 'use strict';
2
-
3
- const {
4
- SHALLOW_RISK_DOC_URL,
5
- escapeRegExp,
6
- isClearlyLocalMcpBinary,
7
- } = require('../shared');
8
-
9
- function hasBroadOpenPermissions(server) {
10
- if (!server || typeof server !== 'object') return false;
11
- if (Array.isArray(server.permissions) && server.permissions.length === 0) return true;
12
- if (server.allow === '*') return true;
13
- if (Array.isArray(server.allow) && server.allow.includes('*')) return true;
14
- if (server.permissions && typeof server.permissions === 'object') {
15
- if (server.permissions.allow === '*') return true;
16
- if (Array.isArray(server.permissions.allow) && server.permissions.allow.includes('*')) return true;
17
- }
18
- return false;
19
- }
20
-
21
- module.exports = {
22
- key: 'mcp-server-no-allowlist',
23
- name: 'MCP server has no allowlist',
24
- severity: 'critical',
25
- layer: 'shallow-risk',
26
- sourceUrl: SHALLOW_RISK_DOC_URL,
27
- run(ctx) {
28
- const findings = [];
29
- const candidates = ['.claude/settings.json', '.mcp.json'];
30
-
31
- for (const file of candidates) {
32
- const config = ctx.jsonFile(file);
33
- const servers = config && config.mcpServers && typeof config.mcpServers === 'object'
34
- ? config.mcpServers
35
- : null;
36
- if (!servers) continue;
37
-
38
- for (const [serverName, server] of Object.entries(servers)) {
39
- if (!hasBroadOpenPermissions(server)) continue;
40
- const command = typeof server.command === 'string' ? server.command : '';
41
- findings.push({
42
- severity: isClearlyLocalMcpBinary(command) ? 'high' : 'critical',
43
- file,
44
- line: ctx.lineNumber(file, new RegExp(`"${escapeRegExp(serverName)}"`)) || 1,
45
- fix: `MCP server "${serverName}" in ${file} has broad access without an allowlist. Add a narrow allow/permissions list before relying on it in CI or production repos.`,
46
- });
47
- }
48
- }
49
-
50
- return findings;
51
- },
52
- };
1
+ 'use strict';
2
+
3
+ const {
4
+ SHALLOW_RISK_DOC_URL,
5
+ escapeRegExp,
6
+ isClearlyLocalMcpBinary,
7
+ } = require('../shared');
8
+
9
+ function hasBroadOpenPermissions(server) {
10
+ if (!server || typeof server !== 'object') return false;
11
+ if (Array.isArray(server.permissions) && server.permissions.length === 0) return true;
12
+ if (server.allow === '*') return true;
13
+ if (Array.isArray(server.allow) && server.allow.includes('*')) return true;
14
+ if (server.permissions && typeof server.permissions === 'object') {
15
+ if (server.permissions.allow === '*') return true;
16
+ if (Array.isArray(server.permissions.allow) && server.permissions.allow.includes('*')) return true;
17
+ }
18
+ return false;
19
+ }
20
+
21
+ module.exports = {
22
+ key: 'mcp-server-no-allowlist',
23
+ name: 'MCP server has no allowlist',
24
+ severity: 'critical',
25
+ layer: 'shallow-risk',
26
+ sourceUrl: SHALLOW_RISK_DOC_URL,
27
+ run(ctx) {
28
+ const findings = [];
29
+ const candidates = ['.claude/settings.json', '.mcp.json'];
30
+
31
+ for (const file of candidates) {
32
+ const config = ctx.jsonFile(file);
33
+ const servers = config && config.mcpServers && typeof config.mcpServers === 'object'
34
+ ? config.mcpServers
35
+ : null;
36
+ if (!servers) continue;
37
+
38
+ for (const [serverName, server] of Object.entries(servers)) {
39
+ if (!hasBroadOpenPermissions(server)) continue;
40
+ const command = typeof server.command === 'string' ? server.command : '';
41
+ findings.push({
42
+ severity: isClearlyLocalMcpBinary(command) ? 'high' : 'critical',
43
+ file,
44
+ line: ctx.lineNumber(file, new RegExp(`"${escapeRegExp(serverName)}"`)) || 1,
45
+ fix: `MCP server "${serverName}" in ${file} has broad access without an allowlist. Add a narrow allow/permissions list before relying on it in CI or production repos.`,
46
+ });
47
+ }
48
+ }
49
+
50
+ return findings;
51
+ },
52
+ };