@nerviq/cli 0.9.0-beta.2 → 0.9.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.
package/src/techniques.js CHANGED
@@ -55,15 +55,16 @@ const TECHNIQUES = {
55
55
 
56
56
  importSyntax: {
57
57
  id: 763,
58
- name: 'CLAUDE.md uses @import for modularity',
58
+ name: 'CLAUDE.md uses @path imports for modularity',
59
59
  check: (ctx) => {
60
60
  const md = ctx.claudeMdContent() || '';
61
- return md.includes('@import');
61
+ // Current syntax is @path/to/file (no "import" keyword)
62
+ return /@\S+\.(md|txt|json|yml|yaml|toml)/i.test(md) || /@\w+\//.test(md);
62
63
  },
63
64
  impact: 'medium',
64
65
  rating: 4,
65
66
  category: 'memory',
66
- fix: 'Use @import in CLAUDE.md to split instructions into focused modules (e.g. @import ./docs/coding-style.md).',
67
+ fix: 'Use @path syntax in CLAUDE.md to split instructions into focused modules (e.g. @docs/coding-style.md). You can also use .claude/rules/ for path-specific rules.',
67
68
  template: null
68
69
  },
69
70
 
@@ -271,22 +272,35 @@ const TECHNIQUES = {
271
272
  skills: {
272
273
  id: 21,
273
274
  name: 'Custom skills',
274
- check: (ctx) => ctx.hasDir('.claude/skills') && ctx.dirFiles('.claude/skills').length > 0,
275
+ check: (ctx) => {
276
+ // Skills use directory-per-skill structure: .claude/skills/<name>/SKILL.md
277
+ if (!ctx.hasDir('.claude/skills')) return false;
278
+ const dirs = ctx.dirFiles('.claude/skills');
279
+ // Check for SKILL.md inside skill directories
280
+ for (const d of dirs) {
281
+ if (ctx.fileContent(`.claude/skills/${d}/SKILL.md`)) return true;
282
+ }
283
+ // Fallback: any files in skills dir (legacy .claude/commands/ also works)
284
+ return dirs.length > 0;
285
+ },
275
286
  impact: 'medium',
276
287
  rating: 4,
277
288
  category: 'workflow',
278
- fix: 'Add skills for domain-specific workflows.',
289
+ fix: 'Create skills at .claude/skills/<name>/SKILL.md with YAML frontmatter (name, description). Each skill is a directory with a SKILL.md file.',
279
290
  template: 'skills'
280
291
  },
281
292
 
282
293
  multipleSkills: {
283
294
  id: 2101,
284
295
  name: '2+ skills for specialization',
285
- check: (ctx) => ctx.hasDir('.claude/skills') && ctx.dirFiles('.claude/skills').length >= 2,
296
+ check: (ctx) => {
297
+ if (!ctx.hasDir('.claude/skills')) return false;
298
+ return ctx.dirFiles('.claude/skills').length >= 2;
299
+ },
286
300
  impact: 'medium',
287
301
  rating: 4,
288
302
  category: 'workflow',
289
- fix: 'Add at least 2 skills to cover different domain areas.',
303
+ fix: 'Add at least 2 skills covering different workflows (e.g. code-review, test-writer).',
290
304
  template: 'skills'
291
305
  },
292
306
 
@@ -418,7 +432,7 @@ const TECHNIQUES = {
418
432
  id: 19,
419
433
  name: 'Hooks for automation',
420
434
  check: (ctx) => {
421
- if (ctx.hasDir('.claude/hooks') && ctx.dirFiles('.claude/hooks').length > 0) return true;
435
+ // Hooks are configured in settings.json (not .claude/hooks/ directory)
422
436
  const shared = ctx.jsonFile('.claude/settings.json') || {};
423
437
  const local = ctx.jsonFile('.claude/settings.local.json') || {};
424
438
  return !!(shared.hooks && Object.keys(shared.hooks).length > 0) || !!(local.hooks && Object.keys(local.hooks).length > 0);
@@ -426,7 +440,7 @@ const TECHNIQUES = {
426
440
  impact: 'high',
427
441
  rating: 4,
428
442
  category: 'automation',
429
- fix: 'Add hooks for auto-lint, auto-test, or file change tracking.',
443
+ fix: 'Add hooks in .claude/settings.json under the "hooks" key. Supported events: PreToolUse, PostToolUse, Notification, Stop, StopFailure, SubagentStop, and more.',
430
444
  template: 'hooks'
431
445
  },
432
446
 
@@ -696,13 +710,17 @@ const TECHNIQUES = {
696
710
  id: 18,
697
711
  name: 'MCP servers configured',
698
712
  check: (ctx) => {
713
+ // MCP now lives in .mcp.json (project) and ~/.claude.json (user), NOT settings.json
714
+ const mcpJson = ctx.jsonFile('.mcp.json');
715
+ if (mcpJson && mcpJson.mcpServers && Object.keys(mcpJson.mcpServers).length > 0) return true;
716
+ // Fallback: check settings for legacy format
699
717
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
700
718
  return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0);
701
719
  },
702
720
  impact: 'medium',
703
721
  rating: 3,
704
722
  category: 'tools',
705
- fix: 'Configure MCP servers for external tool integration (database, APIs, etc).',
723
+ fix: 'Configure MCP servers in .mcp.json at project root. Use `claude mcp add` to add servers. Project-level MCP is committed to git for team sharing.',
706
724
  template: null
707
725
  },
708
726
 
@@ -711,10 +729,10 @@ const TECHNIQUES = {
711
729
  name: '2+ MCP servers for rich tooling',
712
730
  check: (ctx) => {
713
731
  let count = 0;
714
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
715
- if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
716
732
  const mcpJson = ctx.jsonFile('.mcp.json');
717
733
  if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
734
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
735
+ if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
718
736
  return count >= 2;
719
737
  },
720
738
  impact: 'medium',
@@ -853,17 +871,17 @@ const TECHNIQUES = {
853
871
 
854
872
  // claudeMdNotOverlong removed — duplicate of underlines200 (id 681)
855
873
 
856
- claudeMdNotOverlong: {
874
+ claudeLocalMd: {
857
875
  id: 2002,
858
- name: 'CLAUDE.md is concise (under 200 lines)',
876
+ name: 'CLAUDE.local.md for personal overrides',
859
877
  check: (ctx) => {
860
- // Defer to underlines200 this check always returns null (skipped)
861
- return null;
878
+ // CLAUDE.local.md is for personal, non-committed overrides
879
+ return ctx.files.includes('CLAUDE.local.md') || ctx.files.includes('.claude/CLAUDE.local.md');
862
880
  },
863
- impact: 'medium',
864
- rating: 4,
865
- category: 'quality-deep',
866
- fix: 'CLAUDE.md over 200 lines wastes tokens every session. Move detailed docs to .claude/rules/ or skills. Keep CLAUDE.md lean.',
881
+ impact: 'low',
882
+ rating: 2,
883
+ category: 'memory',
884
+ fix: 'Create CLAUDE.local.md for personal preferences that should not be committed (add to .gitignore).',
867
885
  template: null
868
886
  },
869
887
 
@@ -935,21 +953,22 @@ const TECHNIQUES = {
935
953
 
936
954
  agentsHaveMaxTurns: {
937
955
  id: 2007,
938
- name: 'Agents have maxTurns limit',
956
+ name: 'Subagents have max-turns limit',
939
957
  check: (ctx) => {
940
958
  if (!ctx.hasDir('.claude/agents')) return null;
941
959
  const files = ctx.dirFiles('.claude/agents');
942
960
  if (files.length === 0) return null;
943
961
  for (const f of files) {
944
962
  const content = ctx.fileContent(`.claude/agents/${f}`) || '';
945
- if (!content.includes('maxTurns')) return false;
963
+ // Current frontmatter uses kebab-case: max-turns (also accept legacy maxTurns)
964
+ if (!content.includes('max-turns') && !content.includes('maxTurns')) return false;
946
965
  }
947
966
  return true;
948
967
  },
949
968
  impact: 'medium',
950
969
  rating: 3,
951
970
  category: 'quality-deep',
952
- fix: 'Agents without maxTurns can run indefinitely. Add "maxTurns: 50" to agent frontmatter.',
971
+ fix: 'Subagents without max-turns can run indefinitely. Add "max-turns: 50" to subagent YAML frontmatter.',
953
972
  template: null
954
973
  },
955
974
 
@@ -986,19 +1005,20 @@ const TECHNIQUES = {
986
1005
  // --- New checks: agent depth ---
987
1006
  agentHasAllowedTools: {
988
1007
  id: 2011,
989
- name: 'At least one agent restricts tools',
1008
+ name: 'At least one subagent restricts tools',
990
1009
  check: (ctx) => {
991
1010
  if (!ctx.hasDir('.claude/agents')) return null;
992
1011
  const files = ctx.dirFiles('.claude/agents');
993
1012
  if (files.length === 0) return null;
994
1013
  for (const f of files) {
995
1014
  const content = ctx.fileContent(`.claude/agents/${f}`) || '';
996
- if (/tools:\s*\[/.test(content)) return true;
1015
+ // Current frontmatter uses allowed-tools (also accept legacy tools:)
1016
+ if (/allowed-tools:/i.test(content) || /tools:\s*\[/.test(content)) return true;
997
1017
  }
998
1018
  return false;
999
1019
  },
1000
1020
  impact: 'medium', rating: 3, category: 'workflow',
1001
- fix: 'Add a tools restriction to agent frontmatter (e.g. tools: [Read, Grep]) for safer delegation.',
1021
+ fix: 'Add allowed-tools to subagent frontmatter (e.g. allowed-tools: Read Grep Bash) for safer delegation.',
1002
1022
  template: null
1003
1023
  },
1004
1024
 
@@ -1179,14 +1199,15 @@ const TECHNIQUES = {
1179
1199
 
1180
1200
  stopFailureHook: {
1181
1201
  id: 2025,
1182
- name: 'StopFailure or error handling hook',
1202
+ name: 'StopFailure hook for error tracking',
1183
1203
  check: (ctx) => {
1184
1204
  const shared = ctx.jsonFile('.claude/settings.json') || {};
1185
1205
  const local = ctx.jsonFile('.claude/settings.local.json') || {};
1186
- return !!(shared.hooks?.StopFailure || shared.hooks?.Stop || local.hooks?.StopFailure || local.hooks?.Stop);
1206
+ // StopFailure = error stop (API errors), Stop = normal completion — both useful but different
1207
+ return !!(shared.hooks?.StopFailure || local.hooks?.StopFailure);
1187
1208
  },
1188
1209
  impact: 'low', rating: 3, category: 'automation',
1189
- fix: 'Add a StopFailure hook to log errors for debugging. Helps track why Claude stops unexpectedly.',
1210
+ fix: 'Add a StopFailure hook to log API errors and unexpected stops. Note: StopFailure (errors) is different from Stop (normal completion).',
1190
1211
  template: null
1191
1212
  },
1192
1213
 
@@ -1282,19 +1303,19 @@ const TECHNIQUES = {
1282
1303
  name: 'No deprecated patterns detected',
1283
1304
  check: (ctx) => {
1284
1305
  const md = ctx.claudeMdContent();
1285
- if (!md) return false; // no CLAUDE.md = not passing
1286
- // Check for patterns deprecated in Claude 4.x
1306
+ if (!md) return false;
1307
+ // Only flag truly deprecated patterns, not valid aliases
1287
1308
  const deprecatedPatterns = [
1288
- /\bprefill\b/i, // deprecated API pattern in 4.6
1289
- /\bclaude-3-opus\b/i, /\bclaude-3-sonnet\b/i, /\bclaude-3-haiku\b/i, // old model names
1290
- /\bhuman_prompt\b/i, /\bassistant_prompt\b/i, // old API format
1309
+ /\bhuman_prompt\b/i, /\bassistant_prompt\b/i, // old completions API format (not Messages API)
1310
+ /\buse model claude-3-opus\b/i, // explicit recommendation to use old name as --model
1311
+ /\buse model claude-3-sonnet\b/i,
1291
1312
  ];
1292
1313
  return !deprecatedPatterns.some(p => p.test(md));
1293
1314
  },
1294
1315
  impact: 'medium',
1295
1316
  rating: 3,
1296
1317
  category: 'quality-deep',
1297
- fix: 'CLAUDE.md references deprecated patterns (old model names or API formats). Update to current Claude 4.x conventions.',
1318
+ fix: 'CLAUDE.md references deprecated API patterns (human_prompt/assistant_prompt). Update to current Messages API conventions.',
1298
1319
  template: null
1299
1320
  },
1300
1321
 
@@ -1315,6 +1336,77 @@ const TECHNIQUES = {
1315
1336
  fix: 'CLAUDE.md exists but lacks substance. Add at least 2 sections (## headings) and include your test/build/lint commands.',
1316
1337
  template: null
1317
1338
  },
1339
+
1340
+ // ============================================================
1341
+ // === NEW CHECKS: Uncovered features (2026-04-05) ============
1342
+ // ============================================================
1343
+
1344
+ mcpJsonProject: {
1345
+ id: 2032,
1346
+ name: 'Project-level .mcp.json exists',
1347
+ check: (ctx) => ctx.files.includes('.mcp.json'),
1348
+ impact: 'medium',
1349
+ rating: 3,
1350
+ category: 'tools',
1351
+ fix: 'Create .mcp.json at project root for team-shared MCP servers. Use `claude mcp add --project` to add servers.',
1352
+ template: null
1353
+ },
1354
+
1355
+ hooksNotificationEvent: {
1356
+ id: 2033,
1357
+ name: 'Notification hook for alerts',
1358
+ check: (ctx) => {
1359
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1360
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1361
+ return !!(shared.hooks?.Notification || local.hooks?.Notification);
1362
+ },
1363
+ impact: 'low',
1364
+ rating: 2,
1365
+ category: 'automation',
1366
+ fix: 'Add a Notification hook to capture alerts and status updates from Claude during long tasks.',
1367
+ template: null
1368
+ },
1369
+
1370
+ subagentStopHook: {
1371
+ id: 2034,
1372
+ name: 'SubagentStop hook for delegation tracking',
1373
+ check: (ctx) => {
1374
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1375
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1376
+ return !!(shared.hooks?.SubagentStop || local.hooks?.SubagentStop);
1377
+ },
1378
+ impact: 'low',
1379
+ rating: 2,
1380
+ category: 'automation',
1381
+ fix: 'Add a SubagentStop hook to track when delegated subagent tasks complete.',
1382
+ template: null
1383
+ },
1384
+
1385
+ rulesDirectory: {
1386
+ id: 2035,
1387
+ name: 'Path-specific rules in .claude/rules/',
1388
+ check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length > 0,
1389
+ impact: 'medium',
1390
+ rating: 3,
1391
+ category: 'workflow',
1392
+ fix: 'Create .claude/rules/ with path-specific rules for different parts of your codebase (e.g. frontend.md, backend.md).',
1393
+ template: null
1394
+ },
1395
+
1396
+ gitignoreClaudeLocal: {
1397
+ id: 2036,
1398
+ name: 'CLAUDE.local.md in .gitignore',
1399
+ check: (ctx) => {
1400
+ const gitignore = ctx.fileContent('.gitignore') || '';
1401
+ return /CLAUDE\.local\.md/i.test(gitignore);
1402
+ },
1403
+ impact: 'medium',
1404
+ rating: 3,
1405
+ category: 'git',
1406
+ fix: 'Add CLAUDE.local.md to .gitignore — it contains personal overrides that should not be committed.',
1407
+ template: null
1408
+ },
1409
+
1318
1410
  };
1319
1411
 
1320
1412
  // Stack detection