@nerviq/cli 1.20.0 → 1.21.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 (185) hide show
  1. package/LICENSE +23 -23
  2. package/README.md +2 -2
  3. package/bin/cli.js +1 -0
  4. package/package.json +2 -1
  5. package/src/activity.js +1039 -1039
  6. package/src/adoption-advisor.js +299 -299
  7. package/src/aider/config-parser.js +166 -166
  8. package/src/aider/context.js +6 -2
  9. package/src/aider/deep-review.js +316 -316
  10. package/src/aider/domain-packs.js +303 -303
  11. package/src/aider/freshness.js +93 -93
  12. package/src/aider/governance.js +253 -253
  13. package/src/aider/interactive.js +334 -334
  14. package/src/aider/mcp-packs.js +329 -329
  15. package/src/aider/patch.js +214 -214
  16. package/src/aider/plans.js +186 -186
  17. package/src/aider/premium.js +360 -360
  18. package/src/aider/setup.js +404 -404
  19. package/src/aider/techniques.js +312 -67
  20. package/src/analyze.js +951 -951
  21. package/src/anti-patterns.js +485 -485
  22. package/src/audit/instruction-files.js +180 -180
  23. package/src/audit/recommendations.js +577 -577
  24. package/src/auto-suggest.js +154 -154
  25. package/src/badge.js +13 -13
  26. package/src/behavioral-drift.js +801 -801
  27. package/src/benchmark.js +67 -67
  28. package/src/catalog.js +103 -103
  29. package/src/certification.js +128 -128
  30. package/src/codex/config-parser.js +183 -183
  31. package/src/codex/context.js +223 -223
  32. package/src/codex/deep-review.js +493 -493
  33. package/src/codex/domain-packs.js +394 -394
  34. package/src/codex/freshness.js +84 -84
  35. package/src/codex/governance.js +192 -192
  36. package/src/codex/interactive.js +618 -618
  37. package/src/codex/mcp-packs.js +914 -914
  38. package/src/codex/patch.js +209 -209
  39. package/src/codex/plans.js +251 -251
  40. package/src/codex/premium.js +614 -614
  41. package/src/codex/setup.js +591 -591
  42. package/src/context.js +10 -4
  43. package/src/continuous-ops.js +681 -681
  44. package/src/copilot/activity.js +309 -309
  45. package/src/copilot/deep-review.js +346 -346
  46. package/src/copilot/domain-packs.js +372 -372
  47. package/src/copilot/freshness.js +57 -57
  48. package/src/copilot/governance.js +222 -222
  49. package/src/copilot/interactive.js +406 -406
  50. package/src/copilot/mcp-packs.js +826 -826
  51. package/src/copilot/plans.js +253 -253
  52. package/src/copilot/premium.js +451 -451
  53. package/src/copilot/setup.js +488 -488
  54. package/src/cost-tracking.js +61 -61
  55. package/src/cursor/activity.js +301 -301
  56. package/src/cursor/config-parser.js +265 -265
  57. package/src/cursor/context.js +256 -256
  58. package/src/cursor/deep-review.js +334 -334
  59. package/src/cursor/domain-packs.js +368 -368
  60. package/src/cursor/freshness.js +65 -65
  61. package/src/cursor/governance.js +229 -229
  62. package/src/cursor/interactive.js +391 -391
  63. package/src/cursor/mcp-packs.js +828 -828
  64. package/src/cursor/plans.js +254 -254
  65. package/src/cursor/premium.js +469 -469
  66. package/src/cursor/setup.js +488 -488
  67. package/src/dashboard.js +493 -493
  68. package/src/deep-review.js +428 -428
  69. package/src/deprecation.js +98 -98
  70. package/src/diff-only.js +280 -280
  71. package/src/doctor.js +119 -119
  72. package/src/domain-pack-expansion.js +1033 -1033
  73. package/src/domain-packs.js +387 -387
  74. package/src/feedback.js +178 -178
  75. package/src/fix-engine.js +783 -783
  76. package/src/fix-prompts.js +122 -122
  77. package/src/formatters/sarif.js +115 -115
  78. package/src/freshness.js +74 -74
  79. package/src/gemini/config-parser.js +275 -275
  80. package/src/gemini/deep-review.js +559 -559
  81. package/src/gemini/domain-packs.js +393 -393
  82. package/src/gemini/freshness.js +66 -66
  83. package/src/gemini/governance.js +201 -201
  84. package/src/gemini/interactive.js +860 -860
  85. package/src/gemini/mcp-packs.js +915 -915
  86. package/src/gemini/plans.js +269 -269
  87. package/src/gemini/premium.js +760 -760
  88. package/src/gemini/setup.js +692 -692
  89. package/src/governance.js +72 -72
  90. package/src/harmony/add.js +68 -68
  91. package/src/harmony/advisor.js +333 -333
  92. package/src/harmony/canon.js +565 -565
  93. package/src/harmony/cli.js +591 -591
  94. package/src/harmony/drift.js +401 -401
  95. package/src/harmony/governance.js +313 -313
  96. package/src/harmony/memory.js +239 -239
  97. package/src/harmony/sync.js +475 -475
  98. package/src/harmony/watch.js +370 -370
  99. package/src/hook-validation.js +342 -342
  100. package/src/index.js +271 -271
  101. package/src/init.js +184 -184
  102. package/src/instruction-surfaces.js +185 -185
  103. package/src/integrations.js +144 -144
  104. package/src/interactive.js +118 -118
  105. package/src/locales/en.json +1 -1
  106. package/src/locales/es.json +1 -1
  107. package/src/mcp-packs.js +830 -830
  108. package/src/mcp-server.js +726 -726
  109. package/src/mcp-validation.js +337 -337
  110. package/src/nerviq-sync.json +7 -7
  111. package/src/opencode/config-parser.js +109 -109
  112. package/src/opencode/context.js +247 -247
  113. package/src/opencode/deep-review.js +313 -313
  114. package/src/opencode/domain-packs.js +262 -262
  115. package/src/opencode/freshness.js +66 -66
  116. package/src/opencode/governance.js +159 -159
  117. package/src/opencode/interactive.js +392 -392
  118. package/src/opencode/mcp-packs.js +705 -705
  119. package/src/opencode/patch.js +184 -184
  120. package/src/opencode/plans.js +231 -231
  121. package/src/opencode/premium.js +413 -413
  122. package/src/opencode/setup.js +449 -449
  123. package/src/opencode/techniques.js +27 -27
  124. package/src/operating-profile.js +574 -574
  125. package/src/org.js +152 -152
  126. package/src/permission-rules.js +218 -218
  127. package/src/plans.js +839 -839
  128. package/src/platform-change-manifest.js +86 -86
  129. package/src/plugins.js +110 -110
  130. package/src/policy-layers.js +210 -210
  131. package/src/profiles.js +124 -124
  132. package/src/prompt-injection.js +74 -74
  133. package/src/public-api.js +173 -173
  134. package/src/recommendation-rules.js +84 -84
  135. package/src/repo-archetype.js +386 -386
  136. package/src/secret-patterns.js +39 -39
  137. package/src/server.js +527 -527
  138. package/src/setup/analysis.js +607 -607
  139. package/src/setup/runtime.js +172 -172
  140. package/src/setup.js +677 -677
  141. package/src/shared/capabilities.js +194 -194
  142. package/src/source-urls.js +132 -132
  143. package/src/stack-checks.js +565 -565
  144. package/src/supplemental-checks.js +13 -13
  145. package/src/synergy/adaptive.js +261 -261
  146. package/src/synergy/compensation.js +137 -137
  147. package/src/synergy/evidence.js +193 -193
  148. package/src/synergy/learning.js +199 -199
  149. package/src/synergy/patterns.js +227 -227
  150. package/src/synergy/ranking.js +83 -83
  151. package/src/synergy/report.js +165 -165
  152. package/src/synergy/routing.js +146 -146
  153. package/src/techniques/api.js +407 -407
  154. package/src/techniques/automation.js +316 -316
  155. package/src/techniques/compliance.js +257 -257
  156. package/src/techniques/hygiene.js +294 -294
  157. package/src/techniques/instructions.js +243 -243
  158. package/src/techniques/observability.js +226 -226
  159. package/src/techniques/optimization.js +142 -142
  160. package/src/techniques/quality.js +318 -318
  161. package/src/techniques/security.js +237 -237
  162. package/src/techniques/shared.js +443 -443
  163. package/src/techniques/stacks.js +2294 -2294
  164. package/src/techniques/tools.js +106 -106
  165. package/src/techniques/workflow.js +413 -413
  166. package/src/techniques.js +81 -81
  167. package/src/terminology.js +73 -73
  168. package/src/token-estimate.js +35 -35
  169. package/src/usage-patterns.js +99 -99
  170. package/src/verification-metadata.js +145 -145
  171. package/src/watch.js +247 -247
  172. package/src/windsurf/activity.js +302 -302
  173. package/src/windsurf/config-parser.js +267 -267
  174. package/src/windsurf/context.js +120 -10
  175. package/src/windsurf/deep-review.js +337 -337
  176. package/src/windsurf/domain-packs.js +370 -370
  177. package/src/windsurf/freshness.js +36 -36
  178. package/src/windsurf/governance.js +231 -231
  179. package/src/windsurf/interactive.js +388 -388
  180. package/src/windsurf/mcp-packs.js +792 -792
  181. package/src/windsurf/plans.js +247 -247
  182. package/src/windsurf/premium.js +468 -468
  183. package/src/windsurf/setup.js +471 -471
  184. package/src/windsurf/techniques.js +155 -33
  185. package/src/workspace.js +375 -375
@@ -91,6 +91,98 @@ function findFillerLine(content) {
91
91
  return firstLineMatching(content, (line) => FILLER_PATTERNS.some((pattern) => pattern.test(line)));
92
92
  }
93
93
 
94
+ // PP-04: Helpers for N/A gating and broader instruction surfaces ---------------
95
+
96
+ function hasAiderConfig(ctx) {
97
+ // .yml is canonical, .yaml is also accepted by Aider itself
98
+ return Boolean(
99
+ (ctx.fileContent && (ctx.fileContent('.aider.conf.yml') || ctx.fileContent('.aider.conf.yaml')))
100
+ );
101
+ }
102
+
103
+ function hasAiderModelSettings(ctx) {
104
+ return Boolean(
105
+ ctx.fileContent && (ctx.fileContent('.aider.model.settings.yml') || ctx.fileContent('.aider.model.settings.yaml'))
106
+ );
107
+ }
108
+
109
+ function hasAiderignore(ctx) {
110
+ return Boolean(ctx.fileContent && ctx.fileContent('.aiderignore'));
111
+ }
112
+
113
+ function readmeContent(ctx) {
114
+ return (
115
+ (ctx.fileContent && (ctx.fileContent('README.md') || ctx.fileContent('readme.md') || ctx.fileContent('README.rst'))) || ''
116
+ );
117
+ }
118
+
119
+ function contributingContent(ctx) {
120
+ return (
121
+ (ctx.fileContent && (ctx.fileContent('CONTRIBUTING.md') || ctx.fileContent('.github/CONTRIBUTING.md'))) || ''
122
+ );
123
+ }
124
+
125
+ function aidermdContent(ctx) {
126
+ return (
127
+ (ctx.fileContent && (ctx.fileContent('AIDER.md') || ctx.fileContent('AGENTS.md') || ctx.fileContent('CLAUDE.md') || ctx.fileContent('.claude/CLAUDE.md'))) || ''
128
+ );
129
+ }
130
+
131
+ // PP-04: Effective Aider docs surface — Aider has no auto-discovered instructions
132
+ // surface like CLAUDE.md, but real Aider users document Aider workflow across
133
+ // README, CONTRIBUTING, CONVENTIONS, AGENTS.md, CLAUDE.md, .ai/instructions.md.
134
+ function docsBundle(ctx) {
135
+ const parts = [];
136
+ parts.push(readmeContent(ctx));
137
+ parts.push(contributingContent(ctx));
138
+ parts.push(conventionContent(ctx));
139
+ parts.push(aidermdContent(ctx));
140
+ if (ctx.fileContent) {
141
+ parts.push(ctx.fileContent('.ai/instructions.md') || '');
142
+ parts.push(ctx.fileContent('docs/AIDER.md') || '');
143
+ parts.push(ctx.fileContent('ARCHITECTURE.md') || '');
144
+ }
145
+ return parts.filter(Boolean).join('\n\n');
146
+ }
147
+
148
+ function hasAnyAiderSurface(ctx) {
149
+ if (hasAiderConfig(ctx)) return true;
150
+ if (hasAiderModelSettings(ctx)) return true;
151
+ if (hasAiderignore(ctx)) return true;
152
+ if (conventionFiles(ctx).length > 0) return true;
153
+ // README/CONTRIBUTING/AGENTS/CLAUDE explicitly mentioning aider counts
154
+ const docs = `${readmeContent(ctx)}\n${contributingContent(ctx)}\n${aidermdContent(ctx)}`;
155
+ return /\baider\b/i.test(docs);
156
+ }
157
+
158
+ function isPythonProject(ctx) {
159
+ if (!ctx.fileContent) return false;
160
+ return Boolean(
161
+ ctx.fileContent('requirements.txt') ||
162
+ ctx.fileContent('Pipfile') ||
163
+ ctx.fileContent('pyproject.toml') ||
164
+ ctx.fileContent('setup.py') ||
165
+ ctx.fileContent('setup.cfg')
166
+ );
167
+ }
168
+
169
+ function hasArchitectMode(ctx) {
170
+ const config = configContent(ctx);
171
+ if (!config) return false;
172
+ return /\barchitect\s*:\s*true\b/i.test(config) || /\barchitect-mode\s*:\s*true\b/i.test(config);
173
+ }
174
+
175
+ function hasEnvExample(ctx) {
176
+ if (!ctx.fileContent) return false;
177
+ return Boolean(
178
+ ctx.fileContent('.env.example') ||
179
+ ctx.fileContent('.env.sample') ||
180
+ ctx.fileContent('.env.template') ||
181
+ ctx.fileContent('.env.dist') ||
182
+ ctx.fileContent('env.example')
183
+ );
184
+ }
185
+
94
186
  function repoLooksRegulated(ctx) {
95
187
  const filenames = ctx.files.join('\n');
96
188
  const packageJson = ctx.fileContent('package.json') || '';
@@ -112,7 +204,14 @@ const AIDER_TECHNIQUES = {
112
204
  aiderConfYmlExists: {
113
205
  id: 'AD-A01',
114
206
  name: '.aider.conf.yml config file exists',
115
- check: (ctx) => Boolean(ctx.fileContent('.aider.conf.yml')),
207
+ // PP-04: Both .yml and .yaml are accepted by Aider. The config file is
208
+ // recommended but optional — many real Aider repos drive Aider entirely
209
+ // via CONVENTIONS.md + CLI flags. N/A when no Aider surface at all so
210
+ // arbitrary repos do not surface this as a top finding.
211
+ check: (ctx) => {
212
+ if (!hasAnyAiderSurface(ctx)) return null;
213
+ return hasAiderConfig(ctx);
214
+ },
116
215
  impact: 'critical',
117
216
  rating: 5,
118
217
  category: 'config',
@@ -177,10 +276,14 @@ const AIDER_TECHNIQUES = {
177
276
  aiderMapTokensConfigured: {
178
277
  id: 'AD-A05',
179
278
  name: 'Map tokens setting is configured',
279
+ // PP-04: Repo-map sizing is opt-in tuning — the default works for most
280
+ // repos. N/A when not explicitly set; only fail if explicitly set to a
281
+ // non-numeric or out-of-range value (caught by other checks).
180
282
  check: (ctx) => {
181
283
  const config = configContent(ctx);
182
284
  if (!config) return null;
183
- return /\bmap-tokens\s*:/i.test(config);
285
+ if (/\bmap-tokens\s*:/i.test(config)) return true;
286
+ return null;
184
287
  },
185
288
  impact: 'medium',
186
289
  rating: 3,
@@ -228,10 +331,14 @@ const AIDER_TECHNIQUES = {
228
331
  aiderEditFormatConfigured: {
229
332
  id: 'AD-A08',
230
333
  name: 'Edit format explicitly set',
334
+ // PP-04: Aider auto-selects edit-format per model; explicit override is
335
+ // only needed for unusual setups. Pass when set, N/A when relying on the
336
+ // sensible default.
231
337
  check: (ctx) => {
232
338
  const config = configContent(ctx);
233
339
  if (!config) return null;
234
- return /\bedit-format\s*:/i.test(config);
340
+ if (/\bedit-format\s*:/i.test(config)) return true;
341
+ return null;
235
342
  },
236
343
  impact: 'medium',
237
344
  rating: 3,
@@ -350,10 +457,14 @@ const AIDER_TECHNIQUES = {
350
457
  aiderCommitPrefixConfigured: {
351
458
  id: 'AD-B07',
352
459
  name: 'Commit prefix set for AI-authored commits',
460
+ // PP-04: Optional traceability nicety. Pass when set, N/A when not —
461
+ // attribute-author/committer (AD-B06) covers the same intent at higher
462
+ // confidence.
353
463
  check: (ctx) => {
354
464
  const config = configContent(ctx);
355
465
  if (!config) return null;
356
- return /\baider-commit-prefix\s*:/i.test(config) || /\bcommit-prefix\s*:/i.test(config);
466
+ if (/\baider-commit-prefix\s*:/i.test(config) || /\bcommit-prefix\s*:/i.test(config)) return true;
467
+ return null;
357
468
  },
358
469
  impact: 'low',
359
470
  rating: 2,
@@ -368,9 +479,14 @@ const AIDER_TECHNIQUES = {
368
479
  id: 'AD-B08',
369
480
  name: '/undo command awareness documented',
370
481
  check: (ctx) => {
371
- const conventions = conventionContent(ctx);
482
+ // PP-04: Awareness check. N/A unless the repo has an .aider.conf.yml —
483
+ // /undo is an Aider-specific concept and only meaningful for users who
484
+ // have actually configured Aider.
485
+ if (!hasAiderConfig(ctx)) return null;
486
+ const docs = docsBundle(ctx);
372
487
  const config = configContent(ctx);
373
- return /\bundo\b/i.test(conventions) || /\bundo\b/i.test(config);
488
+ if (!docs && !config) return null;
489
+ return /\bundo\b/i.test(docs) || /\bundo\b/i.test(config);
374
490
  },
375
491
  impact: 'low',
376
492
  rating: 2,
@@ -389,6 +505,9 @@ const AIDER_TECHNIQUES = {
389
505
  id: 'AD-C01',
390
506
  name: 'Editor model explicitly configured',
391
507
  check: (ctx) => {
508
+ // PP-04: editor-model is an architect-mode optimisation. N/A when not opted in.
509
+ if (!hasAiderConfig(ctx)) return null;
510
+ if (!hasArchitectMode(ctx)) return null;
392
511
  const roles = modelRoles(ctx);
393
512
  return roles.editor !== null;
394
513
  },
@@ -405,6 +524,8 @@ const AIDER_TECHNIQUES = {
405
524
  id: 'AD-C02',
406
525
  name: 'Weak model configured for commit messages',
407
526
  check: (ctx) => {
527
+ // PP-04: weak-model is a cost optimisation. N/A when no .aider.conf.yml.
528
+ if (!hasAiderConfig(ctx)) return null;
408
529
  const roles = modelRoles(ctx);
409
530
  return roles.weak !== null;
410
531
  },
@@ -420,10 +541,13 @@ const AIDER_TECHNIQUES = {
420
541
  aiderArchitectModeAvailable: {
421
542
  id: 'AD-C03',
422
543
  name: 'Architect mode configured (2-model workflow)',
544
+ // PP-04: Architect mode is opt-in (~1.73x cost). Pass when on, N/A when
545
+ // not set — most teams correctly stick with the cheaper standard mode.
423
546
  check: (ctx) => {
424
547
  const config = configContent(ctx);
425
548
  if (!config) return null;
426
- return /\barchitect\s*:\s*true\b/i.test(config);
549
+ if (/\barchitect\s*:\s*true\b/i.test(config)) return true;
550
+ return null;
427
551
  },
428
552
  impact: 'high',
429
553
  rating: 4,
@@ -437,7 +561,12 @@ const AIDER_TECHNIQUES = {
437
561
  aiderModelSettingsFileExists: {
438
562
  id: 'AD-C04',
439
563
  name: '.aider.model.settings.yml exists for model customization',
440
- check: (ctx) => Boolean(ctx.fileContent('.aider.model.settings.yml')),
564
+ // PP-04: model settings file is opt-in advanced customization. N/A when no
565
+ // .aider.conf.yml — there's no signal the team is using Aider intentionally.
566
+ check: (ctx) => {
567
+ if (!hasAiderConfig(ctx)) return null;
568
+ return hasAiderModelSettings(ctx);
569
+ },
441
570
  impact: 'medium',
442
571
  rating: 3,
443
572
  category: 'model-config',
@@ -468,10 +597,13 @@ const AIDER_TECHNIQUES = {
468
597
  aiderCachePromptsEnabled: {
469
598
  id: 'AD-C06',
470
599
  name: 'Prompt caching enabled for cost savings',
600
+ // PP-04: Cost optimisation. Pass when explicitly on, N/A when not — only
601
+ // some providers/models support prompt caching, so absence is not a defect.
471
602
  check: (ctx) => {
472
603
  const config = configContent(ctx);
473
604
  if (!config) return null;
474
- return /\bcache-prompts\s*:\s*true\b/i.test(config);
605
+ if (/\bcache-prompts\s*:\s*true\b/i.test(config)) return true;
606
+ return null;
475
607
  },
476
608
  impact: 'medium',
477
609
  rating: 3,
@@ -526,7 +658,16 @@ const AIDER_TECHNIQUES = {
526
658
  aiderConventionFileExists: {
527
659
  id: 'AD-D01',
528
660
  name: 'Convention file exists for Aider context',
529
- check: (ctx) => conventionFiles(ctx).length > 0,
661
+ check: (ctx) => {
662
+ // PP-04: AGENTS.md / CLAUDE.md / .ai/instructions.md / AIDER.md count as
663
+ // effective convention surfaces in real Aider repos — Aider users
664
+ // routinely use these files as their context bundle even though Aider
665
+ // itself does not auto-discover them. N/A when no Aider surface at all.
666
+ if (!hasAnyAiderSurface(ctx)) return null;
667
+ if (conventionFiles(ctx).length > 0) return true;
668
+ if (ctx.fileContent && (ctx.fileContent('AGENTS.md') || ctx.fileContent('CLAUDE.md') || ctx.fileContent('.claude/CLAUDE.md') || ctx.fileContent('AIDER.md') || ctx.fileContent('.ai/instructions.md'))) return true;
669
+ return false;
670
+ },
530
671
  impact: 'high',
531
672
  rating: 4,
532
673
  category: 'conventions',
@@ -556,11 +697,15 @@ const AIDER_TECHNIQUES = {
556
697
  aiderConventionHasArchitecture: {
557
698
  id: 'AD-D03',
558
699
  name: 'Convention file includes architecture/structure section',
700
+ // PP-04: Architecture content commonly lives in ARCHITECTURE.md, README,
701
+ // AGENTS.md, or CLAUDE.md — not just CONVENTIONS.md. Widen source.
559
702
  check: (ctx) => {
560
- const content = conventionContent(ctx);
703
+ if (!hasAnyAiderSurface(ctx)) return null;
704
+ const content = docsBundle(ctx);
561
705
  if (!content) return null;
562
- return /##\s+(?:Architecture|Structure|Project Map|Directory)/i.test(content) ||
563
- /```mermaid/i.test(content);
706
+ return /##\s+(?:Architecture|Structure|Project Map|Project Snapshot|Project Layout|Directory|Layout|Modules|Module Tiers|Components|Stack|Tech Stack|Overview|Tour)/i.test(content) ||
707
+ /```mermaid/i.test(content) ||
708
+ /\bproject\s+(?:layout|structure|snapshot)\b/i.test(content);
564
709
  },
565
710
  impact: 'high',
566
711
  rating: 4,
@@ -574,10 +719,13 @@ const AIDER_TECHNIQUES = {
574
719
  aiderConventionHasVerification: {
575
720
  id: 'AD-D04',
576
721
  name: 'Convention file includes verification commands',
722
+ // PP-04: Test/lint commands frequently live in README, CONTRIBUTING.md,
723
+ // AGENTS.md, or CLAUDE.md, not just CONVENTIONS.md. Widen source.
577
724
  check: (ctx) => {
578
- const content = conventionContent(ctx);
725
+ if (!hasAnyAiderSurface(ctx)) return null;
726
+ const content = docsBundle(ctx);
579
727
  if (!content) return null;
580
- return /\bnpm test\b|\bpnpm test\b|\byarn test\b|\bpytest\b|\bgo test\b|\bcargo test\b|\bmake test\b/i.test(content);
728
+ return /\bnpm (?:run )?test\b|\bpnpm test\b|\byarn test\b|\bpytest\b|\bgo test\b|\bcargo test\b|\bmake test\b|\bmvn test\b|\bgradle (?:test|check)\b|\brake test\b|\bdotnet test\b|\bswift test\b|\btox\b/i.test(content);
581
729
  },
582
730
  impact: 'high',
583
731
  rating: 4,
@@ -666,7 +814,14 @@ const AIDER_TECHNIQUES = {
666
814
  aiderAiderignoreExists: {
667
815
  id: 'AD-E03',
668
816
  name: '.aiderignore file exists for file filtering',
669
- check: (ctx) => Boolean(ctx.fileContent('.aiderignore')),
817
+ // PP-04: .aiderignore is a fully optional advanced filter. N/A unless the
818
+ // repo has an .aider.conf.yml (the strongest "we use Aider intentionally"
819
+ // signal). Most real Aider repos rely on .gitignore + repo-map and do not
820
+ // ship an .aiderignore.
821
+ check: (ctx) => {
822
+ if (!hasAiderConfig(ctx)) return null;
823
+ return hasAiderignore(ctx);
824
+ },
670
825
  impact: 'medium',
671
826
  rating: 3,
672
827
  category: 'architecture',
@@ -700,10 +855,13 @@ const AIDER_TECHNIQUES = {
700
855
  aiderEnvInGitignore: {
701
856
  id: 'AD-F01',
702
857
  name: '.env file excluded from git',
858
+ // PP-04: Only meaningful when the team uses Aider (or any tool that loads
859
+ // .env). N/A when no Aider surface and no .env in the repo at all.
703
860
  check: (ctx) => {
704
861
  const gi = gitignoreContent(ctx);
862
+ if (!hasAnyAiderSurface(ctx) && !(ctx.fileContent && ctx.fileContent('.env'))) return null;
705
863
  if (!gi) return false;
706
- return /^\.env$/m.test(gi) || /^\.env\b/m.test(gi);
864
+ return /^\.env$/m.test(gi) || /^\.env\b/m.test(gi) || /^\*\.env$/m.test(gi);
707
865
  },
708
866
  impact: 'critical',
709
867
  rating: 5,
@@ -868,11 +1026,25 @@ const AIDER_TECHNIQUES = {
868
1026
  aiderGitHooksForPreCommit: {
869
1027
  id: 'AD-G04',
870
1028
  name: 'Git pre-commit hooks or CI gates for quality',
871
- check: (ctx) => {
872
- // Check for pre-commit config or husky
873
- return Boolean(ctx.fileContent('.pre-commit-config.yaml')) ||
1029
+ // PP-04: pre-commit hooks are opt-in. N/A when no Aider surface — and accept
1030
+ // CI-as-quality-gate (a workflow that runs lint/test on PR) as a valid
1031
+ // alternative to local pre-commit hooks.
1032
+ check: (ctx) => {
1033
+ if (!hasAnyAiderSurface(ctx)) return null;
1034
+ if (
1035
+ Boolean(ctx.fileContent('.pre-commit-config.yaml')) ||
1036
+ Boolean(ctx.fileContent('.pre-commit-config.yml')) ||
874
1037
  Boolean(ctx.fileContent('.husky/pre-commit')) ||
875
- Boolean(ctx.fileContent('.lefthook.yml'));
1038
+ Boolean(ctx.fileContent('.lefthook.yml')) ||
1039
+ Boolean(ctx.fileContent('lefthook.yml'))
1040
+ ) return true;
1041
+ // Accept CI-side quality gate as equivalent
1042
+ const workflows = ctx.workflowFiles ? ctx.workflowFiles() : [];
1043
+ for (const wf of workflows) {
1044
+ const content = ctx.fileContent(wf) || '';
1045
+ if (/\b(lint|test|check|format)\b/i.test(content) && /\bpull_request\b|\bon:\s*\[/i.test(content)) return true;
1046
+ }
1047
+ return false;
876
1048
  },
877
1049
  impact: 'high',
878
1050
  rating: 4,
@@ -939,10 +1111,14 @@ const AIDER_TECHNIQUES = {
939
1111
  aiderConventionHasCodingStandards: {
940
1112
  id: 'AD-H01',
941
1113
  name: 'Convention file has coding standards section',
1114
+ // PP-04: Widen source to docsBundle (AGENTS.md / CLAUDE.md / CONTRIBUTING
1115
+ // commonly host the coding-standards section).
942
1116
  check: (ctx) => {
943
- const content = conventionContent(ctx);
1117
+ if (!hasAnyAiderSurface(ctx)) return null;
1118
+ const content = docsBundle(ctx);
944
1119
  if (!content) return null;
945
- return /##\s+(?:Coding|Style|Standards|Formatting|Conventions)/i.test(content);
1120
+ return /##\s+(?:Coding|Style|Standards|Formatting|Conventions|Guidelines|Code\s+Style|Hard constraints|Constraints|Platform conventions|Rules|Quick commands)/i.test(content) ||
1121
+ /\b(?:swift|rust|python|java|kotlin|go|typescript)\s+(?:and|\&)?\s*(?:platform\s+)?conventions?\b/i.test(content);
946
1122
  },
947
1123
  impact: 'high',
948
1124
  rating: 4,
@@ -957,9 +1133,10 @@ const AIDER_TECHNIQUES = {
957
1133
  id: 'AD-H02',
958
1134
  name: 'Convention file covers error handling',
959
1135
  check: (ctx) => {
960
- const content = conventionContent(ctx);
1136
+ if (!hasAnyAiderSurface(ctx)) return null;
1137
+ const content = docsBundle(ctx);
961
1138
  if (!content) return null;
962
- return /\berror\s+handling\b|\bexception\b|\btry[- ]catch\b|\bResult\s*<\b/i.test(content);
1139
+ return /\berror\s+handling\b|\bexception\b|\btry[- ]catch\b|\bResult\s*<\b|\bpanic\b|\b\?\?\s+/i.test(content);
963
1140
  },
964
1141
  impact: 'medium',
965
1142
  rating: 3,
@@ -974,9 +1151,11 @@ const AIDER_TECHNIQUES = {
974
1151
  id: 'AD-H03',
975
1152
  name: 'Convention file covers testing guidelines',
976
1153
  check: (ctx) => {
977
- const content = conventionContent(ctx);
1154
+ if (!hasAnyAiderSurface(ctx)) return null;
1155
+ const content = docsBundle(ctx);
978
1156
  if (!content) return null;
979
- return /##\s+(?:Test|Testing)/i.test(content) || /\bunit test\b|\bintegration test\b|\btest coverage\b/i.test(content);
1157
+ return /##\s+(?:Test|Testing|Tests)/i.test(content) ||
1158
+ /\bunit test\b|\bintegration test\b|\btest coverage\b|\bend[- ]to[- ]end\b/i.test(content);
980
1159
  },
981
1160
  impact: 'high',
982
1161
  rating: 4,
@@ -1047,7 +1226,16 @@ const AIDER_TECHNIQUES = {
1047
1226
  aiderEnvFileExists: {
1048
1227
  id: 'AD-M01',
1049
1228
  name: '.env file exists with API configuration',
1050
- check: (ctx) => Boolean(ctx.fileContent('.env')),
1229
+ // PP-04: .env is conventionally gitignored — its absence in a public repo
1230
+ // is the secure default, not a finding. Accept .env.example/.sample/.template
1231
+ // as valid evidence the team documents their env-var contract. N/A unless
1232
+ // there is an actual .aider.conf.yml — without one we have no signal the
1233
+ // team is on Aider rather than just mentioning it in docs.
1234
+ check: (ctx) => {
1235
+ if (!hasAiderConfig(ctx)) return null;
1236
+ if (ctx.fileContent && ctx.fileContent('.env')) return true;
1237
+ return hasEnvExample(ctx);
1238
+ },
1051
1239
  impact: 'high',
1052
1240
  rating: 4,
1053
1241
  category: 'advanced-config',
@@ -1060,10 +1248,20 @@ const AIDER_TECHNIQUES = {
1060
1248
  aiderEnvHasApiKey: {
1061
1249
  id: 'AD-M02',
1062
1250
  name: '.env contains at least one API key',
1251
+ // PP-04: Already correctly N/A when no .env (committed). Also accept
1252
+ // .env.example/sample with placeholder keys as evidence the env contract
1253
+ // is documented (real .env is gitignored and won't be in the audit tree).
1063
1254
  check: (ctx) => {
1064
1255
  const env = envContent(ctx);
1065
- if (!env) return null;
1066
- return /\b(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|DEEPSEEK_API_KEY)\s*=/i.test(env);
1256
+ if (env) {
1257
+ return /\b(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|DEEPSEEK_API_KEY|GEMINI_API_KEY|GROQ_API_KEY)\s*=/i.test(env);
1258
+ }
1259
+ // Fall back to an example file if no committed .env
1260
+ const example = (ctx.fileContent && (
1261
+ ctx.fileContent('.env.example') || ctx.fileContent('.env.sample') || ctx.fileContent('.env.template')
1262
+ )) || '';
1263
+ if (!example) return null;
1264
+ return /\b(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|OPENROUTER_API_KEY|DEEPSEEK_API_KEY|GEMINI_API_KEY|GROQ_API_KEY)\s*=/i.test(example);
1067
1265
  },
1068
1266
  impact: 'high',
1069
1267
  rating: 4,
@@ -1134,9 +1332,13 @@ const AIDER_TECHNIQUES = {
1134
1332
  aiderBrowserModeForDocs: {
1135
1333
  id: 'AD-N02',
1136
1334
  name: 'Browser integration known (/web command)',
1335
+ // PP-04: low-impact awareness. N/A unless an .aider.conf.yml exists —
1336
+ // /web is an Aider-specific in-chat command, not a generic concern.
1137
1337
  check: (ctx) => {
1138
- const content = conventionContent(ctx);
1139
- return /\b\/web\b|\bbrowser\b/i.test(content);
1338
+ if (!hasAiderConfig(ctx)) return null;
1339
+ const docs = docsBundle(ctx);
1340
+ if (!docs) return null;
1341
+ return /\b\/web\b|\bbrowser\s+(?:mode|docs)\b|\bbrowser\b.*\b\/web\b/i.test(docs);
1140
1342
  },
1141
1343
  impact: 'low',
1142
1344
  rating: 2,
@@ -1150,11 +1352,13 @@ const AIDER_TECHNIQUES = {
1150
1352
  aiderInChatCommandsDocumented: {
1151
1353
  id: 'AD-N03',
1152
1354
  name: 'Key in-chat commands documented in conventions',
1355
+ // PP-04: Aider-specific in-chat commands. N/A unless an .aider.conf.yml
1356
+ // exists. Widen content source to docsBundle.
1153
1357
  check: (ctx) => {
1154
- const content = conventionContent(ctx);
1358
+ if (!hasAiderConfig(ctx)) return null;
1359
+ const content = docsBundle(ctx);
1155
1360
  if (!content) return null;
1156
- // Check for documentation of key commands
1157
- const commands = ['/add', '/drop', '/run', '/test', '/undo'];
1361
+ const commands = ['/add', '/drop', '/run', '/test', '/undo', '/web', '/ask', '/code'];
1158
1362
  const found = commands.filter(cmd => content.includes(cmd));
1159
1363
  return found.length >= 2;
1160
1364
  },
@@ -1170,10 +1374,14 @@ const AIDER_TECHNIQUES = {
1170
1374
  aiderVoiceModeAware: {
1171
1375
  id: 'AD-N04',
1172
1376
  name: 'Voice mode configuration known',
1377
+ // PP-04: Voice coding is a niche developer preference, not a project
1378
+ // requirement. Pass when documented, N/A otherwise (no team should be
1379
+ // penalised for not using voice coding).
1173
1380
  check: (ctx) => {
1174
1381
  const config = configContent(ctx);
1175
1382
  if (!config) return null;
1176
- return /\bvoice-language\s*:/i.test(config) || /\bvoice\b/i.test(conventionContent(ctx));
1383
+ if (/\bvoice-language\s*:/i.test(config) || /\bvoice\b/i.test(docsBundle(ctx))) return true;
1384
+ return null;
1177
1385
  },
1178
1386
  impact: 'low',
1179
1387
  rating: 2,
@@ -1187,11 +1395,13 @@ const AIDER_TECHNIQUES = {
1187
1395
  aiderPlaywrightUrlScraping: {
1188
1396
  id: 'AD-N05',
1189
1397
  name: 'Playwright URL auto-scraping side effect is expected',
1398
+ // PP-04: Niche awareness advisory. N/A unless an .aider.conf.yml exists.
1190
1399
  check: (ctx) => {
1191
- const conventions = conventionContent(ctx);
1400
+ if (!hasAiderConfig(ctx)) return null;
1401
+ const docs = docsBundle(ctx);
1192
1402
  const config = configContent(ctx);
1193
- // Check if team is aware of the Playwright auto-scraping behavior
1194
- return /playwright|url.*scrap|scrape.*url|auto.*fetch|web.*fetch/i.test(conventions) ||
1403
+ if (!docs && !config) return null;
1404
+ return /playwright|url.*scrap|scrape.*url|auto.*fetch|web.*fetch/i.test(docs) ||
1195
1405
  /playwright|url.*scrap/i.test(config);
1196
1406
  },
1197
1407
  impact: 'medium',
@@ -1210,10 +1420,15 @@ const AIDER_TECHNIQUES = {
1210
1420
  aiderEditorIntegrationDocumented: {
1211
1421
  id: 'AD-O01',
1212
1422
  name: 'Editor integration documented (VS Code, NeoVim, etc.)',
1423
+ // PP-04: Editor integration is a developer-local concern, not a project
1424
+ // requirement — it tells team members which editor plugins exist for
1425
+ // Aider, but absence is not a real defect. N/A unless the repo has a
1426
+ // .aider.conf.yml AND the docs already discuss tooling/setup.
1213
1427
  check: (ctx) => {
1214
- const content = conventionContent(ctx);
1428
+ if (!hasAiderConfig(ctx)) return null;
1429
+ const content = docsBundle(ctx);
1215
1430
  if (!content) return null;
1216
- return /\bvs\s*code\b|\bneovim\b|\bvim\b|\beditor\b/i.test(content);
1431
+ return /\bvs\s*code\b|\bneovim\b|\bvim\b|\bemacs\b|\bjetbrains\b|\bintellij\b|\bsublime\b|\beditor[- ]integration\b/i.test(content);
1217
1432
  },
1218
1433
  impact: 'low',
1219
1434
  rating: 2,
@@ -1227,10 +1442,14 @@ const AIDER_TECHNIQUES = {
1227
1442
  aiderWatchModeKnown: {
1228
1443
  id: 'AD-O02',
1229
1444
  name: 'Watch mode (--watch-files) documented or configured',
1445
+ // PP-04: Niche feature awareness. N/A when no Aider config — without
1446
+ // .aider.conf.yml there's no place watch-files would meaningfully live.
1230
1447
  check: (ctx) => {
1448
+ if (!hasAiderConfig(ctx)) return null;
1231
1449
  const config = configContent(ctx);
1232
- if (!config) return null;
1233
- return /\bwatch-files\s*:/i.test(config) || /\bwatch\b/i.test(conventionContent(ctx));
1450
+ if (/\bwatch-files\s*:/i.test(config)) return true;
1451
+ const docs = docsBundle(ctx);
1452
+ return /\bwatch[- ]files\b|\b--watch\b/i.test(docs);
1234
1453
  },
1235
1454
  impact: 'medium',
1236
1455
  rating: 3,
@@ -1244,11 +1463,11 @@ const AIDER_TECHNIQUES = {
1244
1463
  aiderDarkModeConfigured: {
1245
1464
  id: 'AD-O03',
1246
1465
  name: 'Theme/dark mode configured for terminal',
1247
- check: (ctx) => {
1248
- const config = configContent(ctx);
1249
- if (!config) return null;
1250
- return /\bdark-mode\s*:/i.test(config) || /\blight-mode\s*:/i.test(config);
1251
- },
1466
+ // PP-04: Cosmetic preference; not meaningful as a project-level requirement.
1467
+ // Downgrade to N/A across the board (kept as a check so the catalog still
1468
+ // surfaces it, but it should not be a "fail" advisory on real repos —
1469
+ // theme is a developer-local preference, not a project artifact).
1470
+ check: () => null,
1252
1471
  impact: 'low',
1253
1472
  rating: 1,
1254
1473
  category: 'editor-integration',
@@ -1282,11 +1501,18 @@ const AIDER_TECHNIQUES = {
1282
1501
  aiderVersionPinned: {
1283
1502
  id: 'AD-P01',
1284
1503
  name: 'Aider version pinned in requirements or package manager',
1504
+ // PP-04: Aider is a Python package — pinning is only meaningful for Python
1505
+ // projects. Non-Python repos use Aider via a separate venv, not via their
1506
+ // own dependency manifest. N/A when the project isn't Python.
1285
1507
  check: (ctx) => {
1508
+ if (!isPythonProject(ctx)) return null;
1509
+ if (!hasAnyAiderSurface(ctx)) return null;
1286
1510
  const req = ctx.fileContent('requirements.txt') || '';
1287
1511
  const pipfile = ctx.fileContent('Pipfile') || '';
1288
1512
  const pyproject = ctx.fileContent('pyproject.toml') || '';
1289
- return /\baider-chat\b/i.test(req) || /\baider-chat\b/i.test(pipfile) || /\baider-chat\b/i.test(pyproject);
1513
+ const setupPy = ctx.fileContent('setup.py') || '';
1514
+ return /\baider-chat\b/i.test(req) || /\baider-chat\b/i.test(pipfile) ||
1515
+ /\baider-chat\b/i.test(pyproject) || /\baider-chat\b/i.test(setupPy);
1290
1516
  },
1291
1517
  impact: 'medium',
1292
1518
  rating: 3,
@@ -1300,11 +1526,16 @@ const AIDER_TECHNIQUES = {
1300
1526
  aiderAllConfigSurfacesPresent: {
1301
1527
  id: 'AD-P02',
1302
1528
  name: 'All essential Aider config surfaces present',
1529
+ // PP-04: .env is gitignored by convention; accept .env.example/sample as
1530
+ // evidence the env contract is documented. N/A unless an .aider.conf.yml
1531
+ // exists — otherwise this fails on every repo that mentions Aider in docs
1532
+ // but doesn't ship the full config triple.
1303
1533
  check: (ctx) => {
1304
- const hasConf = Boolean(ctx.fileContent('.aider.conf.yml'));
1305
- const hasEnv = Boolean(ctx.fileContent('.env'));
1306
- const hasGitignore = Boolean(ctx.fileContent('.gitignore'));
1307
- return hasConf && hasEnv && hasGitignore;
1534
+ if (!hasAiderConfig(ctx)) return null;
1535
+ const envFile = ctx.fileContent && ctx.fileContent('.env');
1536
+ const hasEnvSurface = Boolean(envFile) || hasEnvExample(ctx);
1537
+ const hasGitignore = Boolean(ctx.fileContent && ctx.fileContent('.gitignore'));
1538
+ return hasEnvSurface && hasGitignore;
1308
1539
  },
1309
1540
  impact: 'high',
1310
1541
  rating: 4,
@@ -1318,10 +1549,13 @@ const AIDER_TECHNIQUES = {
1318
1549
  aiderDocumentedWorkflow: {
1319
1550
  id: 'AD-P03',
1320
1551
  name: 'Aider workflow documented in README or conventions',
1552
+ // PP-04: Widen to the full docsBundle (README/CONTRIBUTING/CONVENTIONS/
1553
+ // AGENTS/CLAUDE/.ai/instructions). N/A when no Aider surface at all.
1321
1554
  check: (ctx) => {
1322
- const readme = ctx.fileContent('README.md') || '';
1323
- const content = conventionContent(ctx);
1324
- return /\baider\b/i.test(readme) || /\bworkflow\b/i.test(content);
1555
+ if (!hasAnyAiderSurface(ctx)) return null;
1556
+ const docs = docsBundle(ctx);
1557
+ if (!docs) return null;
1558
+ return /\baider\b/i.test(docs) || /\baider[- ]chat\b/i.test(docs);
1325
1559
  },
1326
1560
  impact: 'medium',
1327
1561
  rating: 3,
@@ -1336,16 +1570,16 @@ const AIDER_TECHNIQUES = {
1336
1570
  id: 'AD-P04',
1337
1571
  name: 'No conflicting platform configs (CLAUDE.md, AGENTS.md) without awareness',
1338
1572
  check: (ctx) => {
1339
- const hasAider = Boolean(ctx.fileContent('.aider.conf.yml'));
1573
+ // PP-04: Multi-platform is the norm in 2026, not a defect. Widen the
1574
+ // awareness source to the full docsBundle (CLAUDE.md often mentions
1575
+ // Aider, AGENTS.md often mentions Claude, README often does both).
1576
+ const hasAider = hasAiderConfig(ctx);
1340
1577
  const hasClaude = Boolean(ctx.fileContent('CLAUDE.md')) || Boolean(ctx.fileContent('.claude/CLAUDE.md'));
1341
1578
  const hasCodex = Boolean(ctx.fileContent('AGENTS.md'));
1342
1579
  if (!hasAider) return null;
1343
- // Multi-platform is fine just check conventions mention it
1344
- if (hasClaude || hasCodex) {
1345
- const content = conventionContent(ctx);
1346
- return /\bmulti[- ]?platform\b|\bclaude\b|\bcodex\b/i.test(content);
1347
- }
1348
- return true;
1580
+ if (!hasClaude && !hasCodex) return true;
1581
+ const content = docsBundle(ctx);
1582
+ return /\bmulti[- ]?platform\b|\bclaude\b|\bcodex\b|\bagents?\.md\b|\baider\b/i.test(content);
1349
1583
  },
1350
1584
  impact: 'medium',
1351
1585
  rating: 3,
@@ -1359,12 +1593,16 @@ const AIDER_TECHNIQUES = {
1359
1593
  aiderModelCostAwareness: {
1360
1594
  id: 'AD-P05',
1361
1595
  name: 'Model cost awareness configured (cache-prompts or explicit model selection)',
1596
+ // PP-04: Cost optimisation. Pass when any cost-aware knob is set; N/A
1597
+ // when none are — the default is sensible for most teams.
1362
1598
  check: (ctx) => {
1363
1599
  const config = configContent(ctx);
1364
1600
  if (!config) return null;
1365
- return /\bcache-prompts\s*:\s*true\b/i.test(config) ||
1601
+ if (/\bcache-prompts\s*:\s*true\b/i.test(config) ||
1366
1602
  /\bweak-model\s*:/i.test(config) ||
1367
- /\beditor-model\s*:/i.test(config);
1603
+ /\beditor-model\s*:/i.test(config) ||
1604
+ /\bmodel\s*:/i.test(config)) return true;
1605
+ return null;
1368
1606
  },
1369
1607
  impact: 'medium',
1370
1608
  rating: 3,
@@ -1378,10 +1616,17 @@ const AIDER_TECHNIQUES = {
1378
1616
  aiderGitBranchStrategy: {
1379
1617
  id: 'AD-P06',
1380
1618
  name: 'Git branch strategy for Aider work',
1619
+ // PP-04: Branch strategy lives in CONTRIBUTING / README more often than
1620
+ // CONVENTIONS. Widen source. N/A unless any Aider surface exists — we
1621
+ // don't expect arbitrary repos to document Aider-specific branching.
1381
1622
  check: (ctx) => {
1382
- const content = conventionContent(ctx);
1623
+ if (!hasAnyAiderSurface(ctx)) return null;
1624
+ const content = docsBundle(ctx);
1383
1625
  if (!content) return null;
1384
- return /\bbranch\b/i.test(content) && /\baider\b/i.test(content);
1626
+ // Either explicit aider+branch combo, or a documented branching workflow
1627
+ // (feature-branch / git-flow / trunk-based) is enough.
1628
+ if (/\baider\b/i.test(content) && /\bbranch\b/i.test(content)) return true;
1629
+ return /\bfeature[- ]branch\b|\bgit[- ]flow\b|\btrunk[- ]based\b|\bpull request\b.*\bbranch\b/i.test(content);
1385
1630
  },
1386
1631
  impact: 'medium',
1387
1632
  rating: 3,