@nerviq/cli 1.23.0 → 1.24.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 CHANGED
@@ -223,8 +223,8 @@ All successful operational responses are wrapped in a JSON envelope:
223
223
  {
224
224
  "data": {},
225
225
  "meta": {
226
- "version": "1.23.0",
227
- "timestamp": "2026-04-15T02:00:00.000Z"
226
+ "version": "1.24.0",
227
+ "timestamp": "2026-04-15T06:00:00.000Z"
228
228
  }
229
229
  }
230
230
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "1.23.0",
3
+ "version": "1.24.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": {
@@ -8,6 +8,27 @@ const {
8
8
  readProjectFiles,
9
9
  } = require('./shared');
10
10
 
11
+ // PP-06 recalibration helpers: opt-in signals. Repos with no infra/hooks
12
+ // signal at all get N/A instead of a hard fail on opt-in advisories.
13
+ function _repoHasInfraSignal(ctx) {
14
+ return ctx.files.some(f => /^Dockerfile/i.test(f))
15
+ || ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f))
16
+ || ctx.files.some(f => /\.tf$/.test(f))
17
+ || ctx.files.includes('main.tf')
18
+ || ctx.hasDir('k8s')
19
+ || ctx.hasDir('kubernetes')
20
+ || ctx.hasDir('infra')
21
+ || ctx.hasDir('terraform')
22
+ || ctx.hasDir('deploy');
23
+ }
24
+
25
+ function _repoHasHooksBlock(ctx) {
26
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
27
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
28
+ return !!((shared.hooks && Object.keys(shared.hooks).length > 0)
29
+ || (local.hooks && Object.keys(local.hooks).length > 0));
30
+ }
31
+
11
32
  module.exports = {
12
33
  hooks: {
13
34
  id: 19,
@@ -91,7 +112,11 @@ module.exports = {
91
112
  dockerfile: {
92
113
  id: 399,
93
114
  name: 'Has Dockerfile',
94
- check: (ctx) => ctx.files.some(f => /^Dockerfile/i.test(f)),
115
+ check: (ctx) => {
116
+ if (ctx.files.some(f => /^Dockerfile/i.test(f))) return true;
117
+ // PP-06 recalibration: N/A on repos with no infra signal at all.
118
+ return _repoHasInfraSignal(ctx) ? false : null;
119
+ },
95
120
  impact: 'medium',
96
121
  rating: 3,
97
122
  category: 'devops',
@@ -102,7 +127,11 @@ module.exports = {
102
127
  dockerCompose: {
103
128
  id: 39901,
104
129
  name: 'Has docker-compose.yml',
105
- check: (ctx) => ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f)),
130
+ check: (ctx) => {
131
+ if (ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f))) return true;
132
+ // PP-06 recalibration: N/A on repos with no infra signal at all.
133
+ return _repoHasInfraSignal(ctx) ? false : null;
134
+ },
106
135
  impact: 'medium',
107
136
  rating: 3,
108
137
  category: 'devops',
@@ -126,7 +155,11 @@ module.exports = {
126
155
  terraformFiles: {
127
156
  id: 397,
128
157
  name: 'Infrastructure as Code (Terraform)',
129
- check: (ctx) => ctx.files.some(f => /\.tf$/.test(f)) || ctx.files.includes('main.tf'),
158
+ check: (ctx) => {
159
+ if (ctx.files.some(f => /\.tf$/.test(f)) || ctx.files.includes('main.tf')) return true;
160
+ // PP-06 recalibration: N/A on repos with no infra signal at all.
161
+ return _repoHasInfraSignal(ctx) ? false : null;
162
+ },
130
163
  impact: 'medium',
131
164
  rating: 3,
132
165
  category: 'devops',
@@ -290,7 +323,9 @@ module.exports = {
290
323
  check: (ctx) => {
291
324
  const shared = ctx.jsonFile('.claude/settings.json') || {};
292
325
  const local = ctx.jsonFile('.claude/settings.local.json') || {};
293
- return !!(shared.hooks?.Notification || local.hooks?.Notification);
326
+ if (shared.hooks?.Notification || local.hooks?.Notification) return true;
327
+ // PP-06 recalibration: N/A unless settings define a hooks block at all.
328
+ return _repoHasHooksBlock(ctx) ? false : null;
294
329
  },
295
330
  impact: 'low',
296
331
  rating: 2,
@@ -305,7 +340,9 @@ module.exports = {
305
340
  check: (ctx) => {
306
341
  const shared = ctx.jsonFile('.claude/settings.json') || {};
307
342
  const local = ctx.jsonFile('.claude/settings.local.json') || {};
308
- return !!(shared.hooks?.SubagentStop || local.hooks?.SubagentStop);
343
+ if (shared.hooks?.SubagentStop || local.hooks?.SubagentStop) return true;
344
+ // PP-06 recalibration: N/A unless settings define a hooks block at all.
345
+ return _repoHasHooksBlock(ctx) ? false : null;
309
346
  },
310
347
  impact: 'low',
311
348
  rating: 2,
@@ -50,8 +50,17 @@ module.exports = {
50
50
  name: 'CLAUDE.md uses @path imports for modularity',
51
51
  check: (ctx) => {
52
52
  const md = ctx.claudeMdContent() || '';
53
- // Current syntax is @path/to/file (no "import" keyword)
54
- return /@\S+\.(md|txt|json|yml|yaml|toml)/i.test(md) || /@\w+\//.test(md);
53
+ // Positive-signal check (PP-06 recalibration): N/A when no CLAUDE.md
54
+ // surface exists, so we don't fail every repo that happens to have a
55
+ // short CLAUDE.md. Only fire as an advisory on long CLAUDE.md files
56
+ // where modular @-imports would genuinely help.
57
+ if (!md) return null;
58
+ const hasImport = /@\S+\.(md|txt|json|yml|yaml|toml)/i.test(md) || /@\w+\//.test(md);
59
+ if (hasImport) return true;
60
+ // Only advise splitting when the CLAUDE.md is long enough to warrant it.
61
+ const lineCount = md.split('\n').length;
62
+ if (lineCount < 80) return null;
63
+ return false;
55
64
  },
56
65
  impact: 'medium',
57
66
  rating: 4,
@@ -140,8 +149,17 @@ module.exports = {
140
149
  id: 2002,
141
150
  name: 'CLAUDE.local.md for personal overrides',
142
151
  check: (ctx) => {
143
- // CLAUDE.local.md is for personal, non-committed overrides
144
- return ctx.files.includes('CLAUDE.local.md') || ctx.files.includes('.claude/CLAUDE.local.md');
152
+ // CLAUDE.local.md is for personal, non-committed overrides.
153
+ const hasLocal = ctx.files.includes('CLAUDE.local.md') || ctx.files.includes('.claude/CLAUDE.local.md');
154
+ if (hasLocal) return true;
155
+ // PP-06 recalibration: N/A when the repo has no personal-overrides
156
+ // convention at all. Only advise creating CLAUDE.local.md when the
157
+ // repo explicitly opts in to that convention (references it in
158
+ // .gitignore or in CLAUDE.md).
159
+ const gitignore = ctx.fileContent('.gitignore') || '';
160
+ const md = ctx.claudeMdContent() || '';
161
+ const mentioned = /CLAUDE\.local\.md/i.test(gitignore) || /CLAUDE\.local\.md/i.test(md);
162
+ return mentioned ? false : null;
145
163
  },
146
164
  impact: 'low',
147
165
  rating: 2,
@@ -155,7 +173,12 @@ module.exports = {
155
173
  name: 'Auto-memory or memory management mentioned',
156
174
  check: (ctx) => {
157
175
  const md = ctx.claudeMdContent() || '';
158
- return /auto.?memory|memory.*manage|remember|persistent.*context/i.test(md);
176
+ if (/auto.?memory|memory.*manage|remember|persistent.*context/i.test(md)) return true;
177
+ // PP-06 recalibration: N/A on repos that don't use Claude Code memory
178
+ // at all. Only fire the advisory when the repo opts in (mentions memory
179
+ // or has a memory directory under .claude/).
180
+ const opts_in = /\bmemory\b/i.test(md) || ctx.hasDir('.claude/memory');
181
+ return opts_in ? false : null;
159
182
  },
160
183
  impact: 'low', rating: 3, category: 'memory',
161
184
  fix: 'Claude Code supports auto-memory for cross-session learning. Mention your memory strategy if relevant.',
@@ -6,6 +6,20 @@
6
6
  const {
7
7
  } = require('./shared');
8
8
 
9
+ // PP-06 recalibration: opt-in signal for MCP. A repo "opts in" to MCP checks if
10
+ // it has any MCP file (even empty/partial) or mentions MCP in its Claude
11
+ // instructions. Repos with no MCP signal at all get N/A on MCP advisories
12
+ // instead of a hard fail.
13
+ function _repoOptsInToMcp(ctx) {
14
+ if (ctx.files.includes('.mcp.json')) return true;
15
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
16
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
17
+ if (shared.mcpServers || local.mcpServers) return true;
18
+ const md = ctx.claudeMdContent() || '';
19
+ if (/\bMCP\b|mcpServers|model[\s-]?context[\s-]?protocol/i.test(md)) return true;
20
+ return false;
21
+ }
22
+
9
23
  module.exports = {
10
24
  mcpServers: {
11
25
  id: 18,
@@ -16,7 +30,9 @@ module.exports = {
16
30
  if (mcpJson && mcpJson.mcpServers && Object.keys(mcpJson.mcpServers).length > 0) return true;
17
31
  // Fallback: check settings for legacy format
18
32
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
19
- return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0);
33
+ if (settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0) return true;
34
+ // PP-06 recalibration: N/A on repos that don't reference MCP at all.
35
+ return _repoOptsInToMcp(ctx) ? false : null;
20
36
  },
21
37
  impact: 'medium',
22
38
  rating: 3,
@@ -34,7 +50,9 @@ module.exports = {
34
50
  if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
35
51
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
36
52
  if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
37
- return count >= 2;
53
+ if (count >= 2) return true;
54
+ // PP-06 recalibration: N/A on repos that don't reference MCP at all.
55
+ return _repoOptsInToMcp(ctx) ? false : null;
38
56
  },
39
57
  impact: 'medium',
40
58
  rating: 4,
@@ -51,7 +69,10 @@ module.exports = {
51
69
  const local = ctx.jsonFile('.claude/settings.local.json') || {};
52
70
  const mcp = ctx.jsonFile('.mcp.json') || {};
53
71
  const all = { ...(shared.mcpServers || {}), ...(local.mcpServers || {}), ...(mcp.mcpServers || {}) };
54
- if (Object.keys(all).length === 0) return false;
72
+ if (Object.keys(all).length === 0) {
73
+ // PP-06 recalibration: N/A on repos that don't reference MCP at all.
74
+ return _repoOptsInToMcp(ctx) ? false : null;
75
+ }
55
76
  return Object.keys(all).some(k => /context7/i.test(k));
56
77
  },
57
78
  impact: 'medium',