@nerviq/cli 1.17.3 → 1.18.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.
@@ -45,7 +45,17 @@ const FILLER_PATTERNS = [
45
45
  ];
46
46
 
47
47
  function countSections(markdown) {
48
- return (markdown.match(/^##\s+/gm) || []).length;
48
+ // Count H1 (#) and H2 (##) as sections. Many real repos mix level-1 and
49
+ // level-2 headings (e.g. home-assistant/core) and penalising them for using
50
+ // `#` is a false positive.
51
+ const headingSections = (markdown.match(/^#{1,2}\s+/gm) || []).length;
52
+ if (headingSections >= 2) return headingSections;
53
+ // Fallback: some repos structure their instructions as a dense bullet list
54
+ // rather than a nested document (e.g. astral-sh/uv AGENTS.md is 20 bullets
55
+ // with no headings). Treat a substantial bullet list as "sectioned".
56
+ const bullets = (markdown.match(/^\s*[-*]\s+/gm) || []).length;
57
+ if (bullets >= 6) return Math.max(headingSections, 2);
58
+ return headingSections;
49
59
  }
50
60
 
51
61
  function firstLineMatching(text, matcher) {
@@ -111,6 +121,30 @@ function docsBundle(ctx) {
111
121
  return `${instr}\n${readme}`;
112
122
  }
113
123
 
124
+ /**
125
+ * Bundle of docs that stack-specific checks (CP-PY*, CP-RS*, CP-JV*, etc.)
126
+ * consult when deciding whether a convention is "documented". Real projects
127
+ * spread guidance across README / CONTRIBUTING / AGENTS.md / CLAUDE.md /
128
+ * copilot-instructions / docs/ — limiting the search to CLAUDE.md + README.md
129
+ * alone produces systematic false positives on mature codebases.
130
+ */
131
+ function stackDocsBundle(ctx) {
132
+ const parts = [
133
+ ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md'),
134
+ ctx.fileContent('README.md'),
135
+ ctx.fileContent('README.rst'),
136
+ ctx.fileContent('README.MD'),
137
+ ctx.fileContent('CONTRIBUTING.md'),
138
+ ctx.fileContent('.github/CONTRIBUTING.md'),
139
+ ctx.fileContent('.github/copilot-instructions.md'),
140
+ ctx.fileContent('AGENTS.md'),
141
+ ctx.fileContent('DEVELOPMENT.md'),
142
+ ctx.fileContent('docs/development.md'),
143
+ ctx.fileContent('STYLE.md'),
144
+ ].filter(Boolean);
145
+ return parts.join('\n');
146
+ }
147
+
114
148
  function expectedVerificationCategories(ctx) {
115
149
  const categories = new Set();
116
150
  const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
@@ -126,14 +160,17 @@ function expectedVerificationCategories(ctx) {
126
160
  }
127
161
 
128
162
  function hasCommandMention(content, category) {
163
+ // Broader patterns: real-world instruction files often just mention the tool
164
+ // ("run tests with pytest", "use cargo check", "mypy is enforced") rather
165
+ // than the full command form. Missing these is the #1 CP-A03 FP source.
129
166
  if (category === 'test') {
130
- return /\bnpm test\b|\bnpm run test\b|\bpnpm test\b|\byarn test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b|\bcargo test\b|\bmake test\b/i.test(content);
167
+ return /\bnpm test\b|\bnpm run test\b|\bpnpm test\b|\byarn test\b|\bvitest\b|\bjest\b|\bpytest\b|\bgo test\b|\bcargo test\b|\bcargo nextest\b|\bmake test\b|\bdotnet test\b|\bmvn test\b|\bgradle test\b|\btox\b|\bnox\b|\bphpunit\b|\btest:\s|tests? (?:can be )?run|run (?:the )?tests?|^#{1,3}\s*Testing\b|^#{1,3}\s*Tests\b|writing.*tests?|modifying.*tests?/im.test(content);
131
168
  }
132
169
  if (category === 'lint') {
133
- return /\bnpm run lint\b|\bpnpm lint\b|\byarn lint\b|\beslint\b|\bprettier\b|\bruff\b|\bclippy\b|\bgolangci-lint\b|\bmake lint\b/i.test(content);
170
+ return /\bnpm run lint\b|\bpnpm lint\b|\byarn lint\b|\beslint\b|\bprettier\b|\bruff\b|\bclippy\b|\bgolangci-lint\b|\bmake lint\b|\bmypy\b|\bpyright\b|\bblack\b|\bisort\b|\bflake8\b|\bpylint\b|\brubocop\b|\bbiome\b|\bpre-commit\b|\blint\b/i.test(content);
134
171
  }
135
172
  if (category === 'build') {
136
- return /\bnpm run build\b|\bpnpm build\b|\byarn build\b|\btsc\b|\bvite build\b|\bnext build\b|\bcargo build\b|\bgo build\b|\bmake\b/i.test(content);
173
+ return /\bnpm run build\b|\bpnpm build\b|\byarn build\b|\btsc\b|\bvite build\b|\bnext build\b|\bcargo build\b|\bcargo check\b|\bgo build\b|\bmake\b|\bdotnet build\b|\bmvn (?:package|install|compile)\b|\bgradle build\b|\buv build\b|\bpython -m build\b|\bbuild:\s|\bbuild\b/i.test(content);
137
174
  }
138
175
  return false;
139
176
  }
@@ -183,7 +220,9 @@ const COPILOT_TECHNIQUES = {
183
220
  const content = copilotInstructions(ctx);
184
221
  if (!content) return null;
185
222
  const nonEmpty = content.split(/\r?\n/).filter(l => l.trim()).length;
186
- return nonEmpty >= 20 && countSections(content) >= 2;
223
+ // Lowered from 20 15 to align with CP-N01 advisory tier. Pinning at 20
224
+ // flagged borderline but substantive files (19 non-empty lines) as FPs.
225
+ return nonEmpty >= 15 && countSections(content) >= 2;
187
226
  },
188
227
  impact: 'high',
189
228
  rating: 5,
@@ -201,8 +240,13 @@ const COPILOT_TECHNIQUES = {
201
240
  const content = copilotInstructions(ctx);
202
241
  if (!content) return null;
203
242
  const expected = expectedVerificationCategories(ctx);
204
- if (expected.length === 0) return /\bverify\b|\btest\b|\blint\b|\bbuild\b/i.test(content);
205
- return expected.every(cat => hasCommandMention(content, cat));
243
+ // Consider CONTRIBUTING.md / README / AGENTS.md alongside copilot-
244
+ // instructions. Large projects routinely document verification commands
245
+ // in CONTRIBUTING or a dev guide and cross-reference from copilot-
246
+ // instructions — the Copilot agent sees all of it.
247
+ const combined = `${content}\n${stackDocsBundle(ctx)}`;
248
+ if (expected.length === 0) return /\bverify\b|\btest\b|\blint\b|\bbuild\b/i.test(combined);
249
+ return expected.every(cat => hasCommandMention(combined, cat));
206
250
  },
207
251
  impact: 'high',
208
252
  rating: 5,
@@ -331,12 +375,23 @@ const COPILOT_TECHNIQUES = {
331
375
  id: 'CP-B01',
332
376
  name: '.vscode/settings.json has Copilot agent settings (VS Code-only)',
333
377
  check: (ctx) => {
378
+ const raw = vscodeSettingsRaw(ctx);
379
+ // No .vscode/settings.json at all → not applicable (repo isn't VS Code-configured).
380
+ if (!raw) return null;
334
381
  const data = vscodeSettingsData(ctx);
335
382
  if (!data) return false;
336
- // Check for any Copilot or chat-related key
337
- // NOTE: These settings affect VS Code only. Copilot CLI ignores them.
338
- const raw = vscodeSettingsRaw(ctx);
339
- return /github\.copilot|chat\./.test(raw);
383
+ if (/github\.copilot|chat\./.test(raw)) return true;
384
+ // Copilot settings in .vscode/settings.json are optional for repos that
385
+ // deliver Copilot configuration through copilot-instructions.md, prompt
386
+ // files, or MCP config. Don't flag the absence if any of these exist.
387
+ const hasOtherSurface = Boolean(
388
+ ctx.fileContent('.github/copilot-instructions.md') ||
389
+ ctx.hasDir('.github/prompts') ||
390
+ ctx.hasDir('.github/instructions') ||
391
+ ctx.fileContent('.vscode/mcp.json')
392
+ );
393
+ if (hasOtherSurface) return null;
394
+ return false;
340
395
  },
341
396
  impact: 'medium',
342
397
  rating: 4,
@@ -376,8 +431,13 @@ const COPILOT_TECHNIQUES = {
376
431
  const hasModelInPrompt = prompts.some(p => p.frontmatter && p.frontmatter.model);
377
432
  // Check instructions for model guidance
378
433
  const instr = copilotInstructions(ctx) || '';
434
+ // Only evaluate if the repo uses prompt files (where model is a
435
+ // meaningful frontmatter field) or already references models —
436
+ // otherwise defaulting to the account-default model is fine.
437
+ if (prompts.length === 0 && !/\bmodel\b|\bgpt\b|\bclaude\b|\bsonnet\b|\bopus\b/i.test(instr)) {
438
+ return null;
439
+ }
379
440
  const hasModelMention = /\bmodel\b.*\b(gpt|claude|o[134]|sonnet|opus)\b/i.test(instr);
380
- if (!prompts.length && !instr) return null;
381
441
  return hasModelInPrompt || hasModelMention;
382
442
  },
383
443
  impact: 'medium',
@@ -510,9 +570,17 @@ const COPILOT_TECHNIQUES = {
510
570
  id: 'CP-C03',
511
571
  name: 'Terminal sandbox enabled (VS Code-only — does NOT affect CLI)',
512
572
  check: (ctx) => {
573
+ const raw = vscodeSettingsRaw(ctx);
574
+ // N/A when the repo has no VS Code settings at all — sandbox setting is
575
+ // VS Code-only and simply doesn't apply.
576
+ if (!raw) return null;
577
+ // N/A when the repo's .vscode/settings.json has no Copilot / chat keys
578
+ // (e.g. it's using settings only for editor preferences). Sandbox is a
579
+ // Copilot-specific setting; absence in a non-Copilot settings file is
580
+ // not a finding.
581
+ if (!/github\.copilot|chat\./.test(raw)) return null;
513
582
  const data = vscodeSettingsData(ctx);
514
583
  if (!data) return false;
515
- const raw = vscodeSettingsRaw(ctx);
516
584
  // Check for chat.tools.terminal.sandbox.enabled = true
517
585
  // NOTE: This setting is VS Code-specific. Copilot CLI ignores it entirely.
518
586
  if (raw.includes('terminal.sandbox') && raw.includes('true')) return true;
@@ -535,6 +603,9 @@ const COPILOT_TECHNIQUES = {
535
603
  name: 'No terminal sandbox on Windows — documented',
536
604
  check: (ctx) => {
537
605
  if (os.platform() !== 'win32') return null; // N/A on non-Windows
606
+ // Only relevant if the repo actually configures VS Code — otherwise the
607
+ // Windows sandbox gap does not apply to this repo's Copilot surface.
608
+ if (!vscodeSettingsRaw(ctx)) return null;
538
609
  const instr = copilotInstructions(ctx) || '';
539
610
  const readme = ctx.fileContent('README.md') || '';
540
611
  const combined = `${instr}\n${readme}`;
@@ -672,6 +743,12 @@ const COPILOT_TECHNIQUES = {
672
743
  id: 'CP-D01',
673
744
  name: 'MCP servers configured per surface (.vscode/mcp.json)',
674
745
  check: (ctx) => {
746
+ // MCP is an opt-in capability. If the repo doesn't have a .vscode/mcp.json,
747
+ // and the instructions don't mention MCP, treat as N/A rather than fail —
748
+ // many repos don't use MCP at all and shouldn't be penalised.
749
+ const raw = mcpJsonRaw(ctx);
750
+ const instr = copilotInstructions(ctx) || '';
751
+ if (!raw && !/\bmcp\b|model context protocol/i.test(instr)) return null;
675
752
  const servers = ctx.mcpServers ? ctx.mcpServers() : {};
676
753
  return Object.keys(servers).length > 0;
677
754
  },
@@ -883,6 +960,9 @@ const COPILOT_TECHNIQUES = {
883
960
  check: (ctx) => {
884
961
  const instr = copilotInstructions(ctx) || '';
885
962
  if (!instr) return null;
963
+ // Plan mode is an advisory feature. Only evaluate if the repo has a cloud
964
+ // agent surface (where plan mode actually kicks in), otherwise N/A.
965
+ if (!cloudAgentContent(ctx)) return null;
886
966
  return /implementation plan|plan mode|step.?by.?step plan|break.*into.*steps/i.test(instr);
887
967
  },
888
968
  impact: 'medium',
@@ -927,7 +1007,15 @@ const COPILOT_TECHNIQUES = {
927
1007
  check: (ctx) => {
928
1008
  const instr = copilotInstructions(ctx) || '';
929
1009
  const readme = ctx.fileContent('README.md') || '';
930
- return /third.?party.*agent|agent.*policy|claude.*copilot|codex.*copilot/i.test(`${instr}\n${readme}`);
1010
+ const combined = `${instr}\n${readme}`;
1011
+ // Only evaluate if the repo already surfaces third-party agent usage
1012
+ // alongside Copilot (i.e. two or more distinct agents). The presence of
1013
+ // the word "agent" alone (which every Copilot instruction file uses)
1014
+ // isn't enough to demand a policy statement.
1015
+ const agents = ['claude', 'codex', 'cursor', 'aider', 'continue', 'windsurf', 'cline'];
1016
+ const hits = agents.filter(a => new RegExp('\\b' + a + '\\b', 'i').test(combined));
1017
+ if (hits.length === 0 && !/\bthird.?party\s+agent/i.test(combined)) return null;
1018
+ return /third.?party.*agent|agent.*policy|agent.*allowed|agent.*governed|claude.*copilot|codex.*copilot|multi[- ]agent|agents?[^.]*policy/i.test(combined);
931
1019
  },
932
1020
  impact: 'medium',
933
1021
  rating: 3,
@@ -1001,7 +1089,11 @@ const COPILOT_TECHNIQUES = {
1001
1089
  id: 'CP-G01',
1002
1090
  name: '.github/prompts/ directory exists with reusable templates',
1003
1091
  check: (ctx) => {
1004
- return ctx.hasDir('.github/prompts');
1092
+ // Prompt files are an opt-in feature. If the repo has a
1093
+ // .github/prompts/ directory, verify it's populated; otherwise N/A —
1094
+ // most repos don't (and shouldn't have to) maintain prompt templates.
1095
+ if (ctx.hasDir('.github/prompts')) return true;
1096
+ return null;
1005
1097
  },
1006
1098
  impact: 'medium',
1007
1099
  rating: 4,
@@ -1075,12 +1167,14 @@ const COPILOT_TECHNIQUES = {
1075
1167
  check: (ctx) => {
1076
1168
  const agentsMd = ctx.fileContent('AGENTS.md');
1077
1169
  if (!agentsMd) return null; // N/A
1078
- // AGENTS.md support needs explicit enabling in VS Code
1079
- // WARNING: Copilot CLI reads AGENTS.md (and CLAUDE.md) automatically without any setting!
1080
- // Use --no-custom-instructions in CLI to prevent this
1170
+ // AGENTS.md support needs explicit enabling in VS Code.
1171
+ // N/A if the repo doesn't configure VS Code for Copilot — then there's
1172
+ // no setting to toggle. (Copilot CLI reads AGENTS.md automatically.)
1173
+ const raw = vscodeSettingsRaw(ctx);
1174
+ if (!raw) return null;
1175
+ if (!/github\.copilot|chat\./.test(raw)) return null;
1081
1176
  const data = vscodeSettingsData(ctx);
1082
1177
  if (!data) return false;
1083
- const raw = vscodeSettingsRaw(ctx);
1084
1178
  return /chat\.agent\.enabled.*true|agent\.enabled.*true/i.test(raw);
1085
1179
  },
1086
1180
  impact: 'critical',
@@ -1118,8 +1212,10 @@ const COPILOT_TECHNIQUES = {
1118
1212
  name: 'Spaces/knowledge bases are indexed for relevant repos',
1119
1213
  check: (ctx) => {
1120
1214
  const instr = copilotInstructions(ctx) || '';
1121
- if (!/space|knowledge base/i.test(instr)) return null;
1122
- return /space.*index|index.*space|knowledge.*base.*configured/i.test(instr);
1215
+ // "space" matches "namespace", "workspace", "whitespace", etc. Tighten
1216
+ // to the Copilot-specific usage of "Copilot Spaces" or "knowledge base".
1217
+ if (!/copilot spaces?|\bspace\s+indexed|knowledge base/i.test(instr)) return null;
1218
+ return /space.*index|index.*space|knowledge.*base.*configured|knowledge.*base.*indexed/i.test(instr);
1123
1219
  },
1124
1220
  impact: 'medium',
1125
1221
  rating: 3,
@@ -1134,8 +1230,11 @@ const COPILOT_TECHNIQUES = {
1134
1230
  id: 'CP-H04',
1135
1231
  name: 'VS Code agent working set is appropriate for project size',
1136
1232
  check: (ctx) => {
1233
+ // Advisory — only evaluate if the repo uses VS Code and mentions it.
1234
+ if (!vscodeSettingsRaw(ctx)) return null;
1137
1235
  const instr = copilotInstructions(ctx) || '';
1138
- return /working set|context.*window|file.*limit|token.*limit/i.test(instr);
1236
+ if (!/working set|context.*window|file.*limit|token.*limit/i.test(instr)) return null;
1237
+ return true;
1139
1238
  },
1140
1239
  impact: 'low',
1141
1240
  rating: 2,
@@ -1175,8 +1274,12 @@ const COPILOT_TECHNIQUES = {
1175
1274
  id: 'CP-I02',
1176
1275
  name: 'Chat participants (@workspace, @terminal) configured',
1177
1276
  check: (ctx) => {
1277
+ // Advisory — only flag if the repo uses VS Code and already mentions
1278
+ // chat participants; otherwise N/A.
1279
+ if (!vscodeSettingsRaw(ctx)) return null;
1178
1280
  const instr = copilotInstructions(ctx) || '';
1179
- return /@workspace|@terminal|@vscode|chat participant/i.test(instr);
1281
+ if (!/@workspace|@terminal|@vscode|chat participant/i.test(instr)) return null;
1282
+ return true;
1180
1283
  },
1181
1284
  impact: 'low',
1182
1285
  rating: 2,
@@ -1234,10 +1337,13 @@ const COPILOT_TECHNIQUES = {
1234
1337
  id: 'CP-J01',
1235
1338
  name: 'gh copilot installed and authenticated',
1236
1339
  check: (ctx) => {
1237
- // Can't detect CLI from files; check for CLI documentation
1340
+ // N/A unless the repo actually references gh copilot or Copilot CLI
1341
+ // most repos aren't CLI-centric and shouldn't be penalised.
1238
1342
  const instr = copilotInstructions(ctx) || '';
1239
1343
  const readme = ctx.fileContent('README.md') || '';
1240
- return /gh copilot|github copilot cli/i.test(`${instr}\n${readme}`);
1344
+ const combined = `${instr}\n${readme}`;
1345
+ if (!/gh copilot|github copilot cli|copilot cli/i.test(combined)) return null;
1346
+ return true;
1241
1347
  },
1242
1348
  impact: 'medium',
1243
1349
  rating: 3,
@@ -1271,9 +1377,12 @@ const COPILOT_TECHNIQUES = {
1271
1377
  id: 'CP-J03',
1272
1378
  name: 'CLI aliases (ghcs/ghce) set up',
1273
1379
  check: (ctx) => {
1380
+ // Advisory — only relevant if the repo documents Copilot CLI usage.
1274
1381
  const instr = copilotInstructions(ctx) || '';
1275
1382
  const readme = ctx.fileContent('README.md') || '';
1276
- return /ghcs|ghce|copilot suggest|copilot explain/i.test(`${instr}\n${readme}`);
1383
+ const combined = `${instr}\n${readme}`;
1384
+ if (!/gh copilot|copilot cli/i.test(combined)) return null;
1385
+ return /ghcs|ghce|copilot suggest|copilot explain/i.test(combined);
1277
1386
  },
1278
1387
  impact: 'low',
1279
1388
  rating: 2,
@@ -1489,7 +1598,10 @@ const COPILOT_TECHNIQUES = {
1489
1598
  name: 'Third-party agent usage is explicitly governed',
1490
1599
  check: (ctx) => {
1491
1600
  const instr = copilotInstructions(ctx) || '';
1492
- return /third.?party.*agent.*governed|agent.*governance|governed.*agent/i.test(instr);
1601
+ // Only evaluate if the instructions already mention third-party agents
1602
+ // or an enterprise/governance context; otherwise N/A.
1603
+ if (!/third.?party|agent|enterprise|governance|policy/i.test(instr)) return null;
1604
+ return /third.?party.*agent.*governed|agent.*governance|governed.*agent|agent.*policy|agent.*allowed|agent.*restrict/i.test(instr);
1493
1605
  },
1494
1606
  impact: 'medium',
1495
1607
  rating: 3,
@@ -1510,7 +1622,11 @@ const COPILOT_TECHNIQUES = {
1510
1622
  check: (ctx) => {
1511
1623
  const instr = copilotInstructions(ctx) || '';
1512
1624
  if (!instr) return null;
1513
- return /\bprompt file|\bspace|\bagent mode|\b\.prompt\.md/i.test(instr);
1625
+ // Only evaluate if the instructions already reference Copilot features
1626
+ // or modes; mandating these mentions globally produces FPs on repos
1627
+ // whose instructions focus on project-specific guidance.
1628
+ if (!/\bcopilot\b|\bagent\b|\bprompt\b|\bchat\b/i.test(instr)) return null;
1629
+ return /\bprompt file|\bspace|\bagent mode|\b\.prompt\.md|\bcopilot\b/i.test(instr);
1514
1630
  },
1515
1631
  impact: 'medium',
1516
1632
  rating: 3,
@@ -1565,7 +1681,12 @@ const COPILOT_TECHNIQUES = {
1565
1681
  check: (ctx) => {
1566
1682
  const instr = copilotInstructions(ctx) || '';
1567
1683
  if (!instr) return null;
1568
- return /rate limit|billing|premium|usage limit|token limit/i.test(instr);
1684
+ // Billing awareness is only relevant for repos whose instructions
1685
+ // actually reference plans, billing, premium usage, or rate limits. The
1686
+ // predicate has to be specific; generic words like "limit" or "usage"
1687
+ // fire on unrelated content (e.g. "limit noise in comments").
1688
+ if (!/rate limit|\bbilling\b|\bpremium\b|\bquota\b|\bsubscription\b|token limit|usage limit/i.test(instr)) return null;
1689
+ return /rate limit|billing|premium|usage limit|token limit|quota/i.test(instr);
1569
1690
  },
1570
1691
  impact: 'medium',
1571
1692
  rating: 3,
@@ -1668,6 +1789,11 @@ const COPILOT_TECHNIQUES = {
1668
1789
  if (!autoApproval || (Array.isArray(autoApproval) && !autoApproval.includes('*'))) score++;
1669
1790
  const instr = copilotInstructions(ctx) || '';
1670
1791
  if (/security|secret|credential/i.test(instr)) score++;
1792
+ // Credit repos that ship a SECURITY.md or equivalent policy file.
1793
+ if (ctx.fileContent('SECURITY.md') || ctx.fileContent('.github/SECURITY.md')) score++;
1794
+ // Credit gitignore awareness of sensitive patterns (checked by CP-C01).
1795
+ const gitignore = ctx.fileContent('.gitignore') || '';
1796
+ if (/\.env\b|secrets\/|credentials|\.pem\b|\.key\b/i.test(gitignore)) score++;
1671
1797
  return score >= 2;
1672
1798
  },
1673
1799
  impact: 'high',
@@ -1687,7 +1813,13 @@ const COPILOT_TECHNIQUES = {
1687
1813
  let configured = 0;
1688
1814
  if (surfaces.vscode) configured++;
1689
1815
  if (surfaces.cloudAgent) configured++;
1690
- // At least VS Code surface should be configured
1816
+ // CLI surface: Copilot CLI reads copilot-instructions.md / AGENTS.md /
1817
+ // CLAUDE.md automatically. Count any of these as the CLI surface.
1818
+ if (ctx.fileContent('.github/copilot-instructions.md') ||
1819
+ ctx.fileContent('AGENTS.md') ||
1820
+ ctx.fileContent('CLAUDE.md')) {
1821
+ configured++;
1822
+ }
1691
1823
  return configured >= 1;
1692
1824
  },
1693
1825
  impact: 'medium',
@@ -1744,6 +1876,10 @@ const COPILOT_TECHNIQUES = {
1744
1876
  id: 'CP-O02',
1745
1877
  name: 'MCP packs recommended based on project signals',
1746
1878
  check: (ctx) => {
1879
+ // Only relevant if the repo uses MCP at all.
1880
+ const raw = mcpJsonRaw(ctx);
1881
+ const instr = copilotInstructions(ctx) || '';
1882
+ if (!raw && !/\bmcp\b|model context protocol/i.test(instr)) return null;
1747
1883
  const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1748
1884
  return Object.keys(servers).length > 0;
1749
1885
  },
@@ -1850,9 +1986,14 @@ const COPILOT_TECHNIQUES = {
1850
1986
  const agentsMd = ctx.fileContent('AGENTS.md');
1851
1987
  const claudeMd = ctx.fileContent('CLAUDE.md');
1852
1988
  if (!agentsMd && !claudeMd) return null; // No cross-platform files
1853
- const instr = copilotInstructions(ctx) || '';
1854
- // If non-Copilot instruction files exist, check that instructions acknowledge this
1855
- return /copilot cli|--no-custom-instructions|cross.platform|AGENTS\.md|CLAUDE\.md/i.test(instr);
1989
+ // If the repo has AGENTS.md or CLAUDE.md AND a dedicated copilot-
1990
+ // instructions.md, the cross-platform awareness needs to be explicit
1991
+ // (the files diverge in practice). If the repo has ONLY AGENTS.md /
1992
+ // CLAUDE.md (no dedicated Copilot file), the convergence is de facto
1993
+ // by design — Copilot CLI reads these automatically. Pass.
1994
+ const dedicatedCopilot = ctx.fileContent('.github/copilot-instructions.md');
1995
+ if (!dedicatedCopilot) return true;
1996
+ return /copilot cli|--no-custom-instructions|cross.platform|AGENTS\.md|CLAUDE\.md|claude code|codex|cursor/i.test(dedicatedCopilot);
1856
1997
  },
1857
1998
  impact: 'high',
1858
1999
  rating: 4,
@@ -1971,7 +2112,7 @@ const COPILOT_TECHNIQUES = {
1971
2112
  copilotPythonVenvMentioned: {
1972
2113
  id: 'CP-PY03',
1973
2114
  name: 'Virtual environment mentioned in instructions',
1974
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /venv|virtualenv|conda|poetry shell|uv venv/i.test(docs); },
2115
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = stackDocsBundle(ctx); return /venv|virtualenv|conda|poetry shell|uv venv/i.test(docs); },
1975
2116
  impact: 'medium',
1976
2117
  category: 'python',
1977
2118
  fix: 'Document virtual environment setup in project instructions.',
@@ -2037,7 +2178,7 @@ const COPILOT_TECHNIQUES = {
2037
2178
  copilotPythonDjangoSettingsDocumented: {
2038
2179
  id: 'CP-PY09',
2039
2180
  name: 'Django settings documented if Django project',
2040
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; if (!ctx.files.some(f => /manage\.py$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /django|settings\.py|DJANGO_SETTINGS_MODULE/i.test(docs); },
2181
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; if (!ctx.files.some(f => /manage\.py$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /django|settings\.py|DJANGO_SETTINGS_MODULE/i.test(docs); },
2041
2182
  impact: 'high',
2042
2183
  category: 'python',
2043
2184
  fix: 'Document Django settings module and configuration in project instructions.',
@@ -2048,7 +2189,7 @@ const COPILOT_TECHNIQUES = {
2048
2189
  copilotPythonFastapiEntryDocumented: {
2049
2190
  id: 'CP-PY10',
2050
2191
  name: 'FastAPI entry point documented if FastAPI project',
2051
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/fastapi/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /fastapi|uvicorn|app\.py|main\.py/i.test(docs); },
2192
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/fastapi/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /fastapi|uvicorn|app\.py|main\.py/i.test(docs); },
2052
2193
  impact: 'high',
2053
2194
  category: 'python',
2054
2195
  fix: 'Document FastAPI entry point and how to run the development server.',
@@ -2059,7 +2200,7 @@ const COPILOT_TECHNIQUES = {
2059
2200
  copilotPythonMigrationsDocumented: {
2060
2201
  id: 'CP-PY11',
2061
2202
  name: 'Database migrations mentioned (alembic / Django migrations)',
2062
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /alembic|migrate|makemigrations|django.{0,10}migration/i.test(docs) || ctx.files.some(f => /alembic[.]ini$|alembic[/]/.test(f)); },
2203
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = stackDocsBundle(ctx); return /alembic|migrate|makemigrations|django.{0,10}migration/i.test(docs) || ctx.files.some(f => /alembic[.]ini$|alembic[/]/.test(f)); },
2063
2204
  impact: 'medium',
2064
2205
  category: 'python',
2065
2206
  fix: 'Document database migration workflow (alembic or Django migrations).',
@@ -2070,7 +2211,7 @@ const COPILOT_TECHNIQUES = {
2070
2211
  copilotPythonEnvHandlingDocumented: {
2071
2212
  id: 'CP-PY12',
2072
2213
  name: '.env handling documented (python-dotenv)',
2073
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/dotenv|python-dotenv|environs/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /\.env|dotenv|environment.{0,10}variable/i.test(docs); },
2214
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/dotenv|python-dotenv|environs/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /\.env|dotenv|environment.{0,10}variable/i.test(docs); },
2074
2215
  impact: 'medium',
2075
2216
  category: 'python',
2076
2217
  fix: 'Document .env file usage and python-dotenv configuration.',
@@ -2125,7 +2266,7 @@ const COPILOT_TECHNIQUES = {
2125
2266
  copilotPythonAsyncDocumented: {
2126
2267
  id: 'CP-PY17',
2127
2268
  name: 'Async patterns documented if async project',
2128
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/asyncio|aiohttp|fastapi|starlette|httpx/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /async|await|asyncio|event.{0,5}loop/i.test(docs); },
2269
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/asyncio|aiohttp|fastapi|starlette|httpx/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /async|await|asyncio|event.{0,5}loop/i.test(docs); },
2129
2270
  impact: 'medium',
2130
2271
  category: 'python',
2131
2272
  fix: 'Document async patterns and conventions used in the project.',
@@ -2191,7 +2332,7 @@ const COPILOT_TECHNIQUES = {
2191
2332
  copilotPythonWsgiAsgiDocumented: {
2192
2333
  id: 'CP-PY23',
2193
2334
  name: 'WSGI/ASGI server documented (gunicorn / uvicorn)',
2194
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/gunicorn|uvicorn|daphne|hypercorn/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /gunicorn|uvicorn|daphne|hypercorn|wsgi|asgi/i.test(docs); },
2335
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/gunicorn|uvicorn|daphne|hypercorn/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /gunicorn|uvicorn|daphne|hypercorn|wsgi|asgi/i.test(docs); },
2195
2336
  impact: 'medium',
2196
2337
  category: 'python',
2197
2338
  fix: 'Document WSGI/ASGI server configuration (gunicorn, uvicorn).',
@@ -2202,7 +2343,7 @@ const COPILOT_TECHNIQUES = {
2202
2343
  copilotPythonTaskQueueDocumented: {
2203
2344
  id: 'CP-PY24',
2204
2345
  name: 'Task queue documented if used (celery / rq)',
2205
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/celery|rq|dramatiq|huey/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /celery|rq|dramatiq|huey|task.{0,10}queue|worker/i.test(docs); },
2346
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/celery|rq|dramatiq|huey/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /celery|rq|dramatiq|huey|task.{0,10}queue|worker/i.test(docs); },
2206
2347
  impact: 'medium',
2207
2348
  category: 'python',
2208
2349
  fix: 'Document task queue configuration and worker setup.',
@@ -2261,7 +2402,7 @@ const COPILOT_TECHNIQUES = {
2261
2402
  copilotGoTestDocumented: {
2262
2403
  id: 'CP-GO04',
2263
2404
  name: 'go test documented in instructions',
2264
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test/i.test(docs); },
2405
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /go test/i.test(docs); },
2265
2406
  impact: 'high',
2266
2407
  category: 'go',
2267
2408
  fix: 'Document go test command in project instructions.',
@@ -2272,7 +2413,7 @@ const COPILOT_TECHNIQUES = {
2272
2413
  copilotGoBuildDocumented: {
2273
2414
  id: 'CP-GO05',
2274
2415
  name: 'go build documented in instructions',
2275
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go build|go install/i.test(docs); },
2416
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /go build|go install/i.test(docs); },
2276
2417
  impact: 'high',
2277
2418
  category: 'go',
2278
2419
  fix: 'Document go build command in project instructions.',
@@ -2294,7 +2435,7 @@ const COPILOT_TECHNIQUES = {
2294
2435
  copilotGoErrorHandlingDocumented: {
2295
2436
  id: 'CP-GO07',
2296
2437
  name: 'Error handling patterns documented',
2297
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /error handling|errors?\.(?:New|Wrap|Is|As)|fmt\.Errorf/i.test(docs); },
2438
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /error handling|errors?\.(?:New|Wrap|Is|As)|fmt\.Errorf/i.test(docs); },
2298
2439
  impact: 'medium',
2299
2440
  category: 'go',
2300
2441
  fix: 'Document error handling conventions (error wrapping, sentinel errors, etc.).',
@@ -2305,7 +2446,7 @@ const COPILOT_TECHNIQUES = {
2305
2446
  copilotGoContextUsageDocumented: {
2306
2447
  id: 'CP-GO08',
2307
2448
  name: 'Context usage documented',
2308
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /context\.Context|ctx\.Done|context\.WithCancel|context\.WithTimeout/i.test(docs); },
2449
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /context\.Context|ctx\.Done|context\.WithCancel|context\.WithTimeout/i.test(docs); },
2309
2450
  impact: 'medium',
2310
2451
  category: 'go',
2311
2452
  fix: 'Document context.Context usage patterns for cancellation and timeouts.',
@@ -2316,7 +2457,7 @@ const COPILOT_TECHNIQUES = {
2316
2457
  copilotGoroutineSafetyDocumented: {
2317
2458
  id: 'CP-GO09',
2318
2459
  name: 'Goroutine safety documented',
2319
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /goroutine|sync\.Mutex|sync\.WaitGroup|channel|concurren/i.test(docs); },
2460
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /goroutine|sync\.Mutex|sync\.WaitGroup|channel|concurren/i.test(docs); },
2320
2461
  impact: 'medium',
2321
2462
  category: 'go',
2322
2463
  fix: 'Document goroutine safety patterns, mutex usage, and channel conventions.',
@@ -2327,7 +2468,7 @@ const COPILOT_TECHNIQUES = {
2327
2468
  copilotGoModTidyMentioned: {
2328
2469
  id: 'CP-GO10',
2329
2470
  name: 'go mod tidy mentioned in instructions',
2330
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go mod tidy/i.test(docs); },
2471
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /go mod tidy/i.test(docs); },
2331
2472
  impact: 'low',
2332
2473
  category: 'go',
2333
2474
  fix: 'Document go mod tidy in project workflow instructions.',
@@ -2338,7 +2479,7 @@ const COPILOT_TECHNIQUES = {
2338
2479
  copilotGoVetConfigured: {
2339
2480
  id: 'CP-GO11',
2340
2481
  name: 'go vet or staticcheck configured',
2341
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /go vet|staticcheck/i.test(docs + ci); },
2482
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /go vet|staticcheck/i.test(docs + ci); },
2342
2483
  impact: 'medium',
2343
2484
  category: 'go',
2344
2485
  fix: 'Configure go vet and/or staticcheck in CI or project instructions.',
@@ -2371,7 +2512,7 @@ const COPILOT_TECHNIQUES = {
2371
2512
  copilotGoCgoDocumented: {
2372
2513
  id: 'CP-GO14',
2373
2514
  name: 'CGO documented if used',
2374
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const goMod = ctx.fileContent('go.mod') || ''; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; if (!/CGO_ENABLED|import "C"/i.test(goMod + docs)) return null; return /CGO|cgo/i.test(docs); },
2515
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const goMod = ctx.fileContent('go.mod') || ''; const docs = stackDocsBundle(ctx); if (!/CGO_ENABLED|import "C"/i.test(goMod + docs)) return null; return /CGO|cgo/i.test(docs); },
2375
2516
  impact: 'low',
2376
2517
  category: 'go',
2377
2518
  fix: 'Document CGO usage, dependencies, and build requirements.',
@@ -2393,7 +2534,7 @@ const COPILOT_TECHNIQUES = {
2393
2534
  copilotGoBenchmarkTests: {
2394
2535
  id: 'CP-GO16',
2395
2536
  name: 'Benchmark tests mentioned',
2396
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test.*-bench|Benchmark/i.test(docs); },
2537
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /go test.*-bench|Benchmark/i.test(docs); },
2397
2538
  impact: 'low',
2398
2539
  category: 'go',
2399
2540
  fix: 'Document benchmark testing with go test -bench.',
@@ -2404,7 +2545,7 @@ const COPILOT_TECHNIQUES = {
2404
2545
  copilotGoRaceDetector: {
2405
2546
  id: 'CP-GO17',
2406
2547
  name: 'Race detector (-race) documented',
2407
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /-race/i.test(docs + ci); },
2548
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /-race/i.test(docs + ci); },
2408
2549
  impact: 'medium',
2409
2550
  category: 'go',
2410
2551
  fix: 'Document and enable race detector with go test -race.',
@@ -2415,7 +2556,7 @@ const COPILOT_TECHNIQUES = {
2415
2556
  copilotGoGenerateDocumented: {
2416
2557
  id: 'CP-GO18',
2417
2558
  name: 'go generate documented',
2418
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go generate/i.test(docs) || ctx.files.some(f => /generate\.go$/.test(f)); },
2559
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /go generate/i.test(docs) || ctx.files.some(f => /generate\.go$/.test(f)); },
2419
2560
  impact: 'low',
2420
2561
  category: 'go',
2421
2562
  fix: 'Document go generate usage and generated files.',
@@ -2426,7 +2567,7 @@ const COPILOT_TECHNIQUES = {
2426
2567
  copilotGoInterfaceDesignDocumented: {
2427
2568
  id: 'CP-GO19',
2428
2569
  name: 'Interface-based design documented',
2429
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /interface|mock|stub|dependency injection/i.test(docs); },
2570
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /interface|mock|stub|dependency injection/i.test(docs); },
2430
2571
  impact: 'low',
2431
2572
  category: 'go',
2432
2573
  fix: 'Document interface-based design patterns for testability and dependency injection.',
@@ -2495,7 +2636,7 @@ const COPILOT_TECHNIQUES = {
2495
2636
  copilotRustCargoTestDocumented: {
2496
2637
  id: 'CP-RS05',
2497
2638
  name: 'cargo test documented in instructions',
2498
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo test/i.test(docs); },
2639
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /cargo test/i.test(docs); },
2499
2640
  impact: 'high',
2500
2641
  category: 'rust',
2501
2642
  fix: 'Document cargo test command in project instructions.',
@@ -2506,7 +2647,7 @@ const COPILOT_TECHNIQUES = {
2506
2647
  copilotRustCargoBuildDocumented: {
2507
2648
  id: 'CP-RS06',
2508
2649
  name: 'cargo build/check documented in instructions',
2509
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo (?:build|check)/i.test(docs); },
2650
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /cargo (?:build|check)/i.test(docs); },
2510
2651
  impact: 'high',
2511
2652
  category: 'rust',
2512
2653
  fix: 'Document cargo build or cargo check command in project instructions.',
@@ -2517,7 +2658,7 @@ const COPILOT_TECHNIQUES = {
2517
2658
  copilotRustUnsafePolicyDocumented: {
2518
2659
  id: 'CP-RS07',
2519
2660
  name: 'Unsafe code policy documented',
2520
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /unsafe|#!?\[forbid\(unsafe|#!?\[deny\(unsafe/i.test(docs); },
2661
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /unsafe|#!?\[forbid\(unsafe|#!?\[deny\(unsafe/i.test(docs); },
2521
2662
  impact: 'high',
2522
2663
  category: 'rust',
2523
2664
  fix: 'Document unsafe code policy (forbidden, minimized, or where allowed).',
@@ -2539,7 +2680,7 @@ const COPILOT_TECHNIQUES = {
2539
2680
  copilotRustFeatureFlagsDocumented: {
2540
2681
  id: 'CP-RS09',
2541
2682
  name: 'Feature flags documented (Cargo.toml [features])',
2542
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/\[features\]/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /feature|--features|--all-features/i.test(docs); },
2683
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/\[features\]/i.test(cargo)) return null; const docs = stackDocsBundle(ctx); return /feature|--features|--all-features/i.test(docs); },
2543
2684
  impact: 'medium',
2544
2685
  category: 'rust',
2545
2686
  fix: 'Document feature flags and their purpose in project instructions.',
@@ -2572,7 +2713,7 @@ const COPILOT_TECHNIQUES = {
2572
2713
  copilotRustDocCommentsEncouraged: {
2573
2714
  id: 'CP-RS12',
2574
2715
  name: 'Doc comments (///) encouraged in instructions',
2575
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /doc comment|\/{3}|rustdoc|cargo doc/i.test(docs); },
2716
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /doc comment|\/{3}|rustdoc|cargo doc/i.test(docs); },
2576
2717
  impact: 'low',
2577
2718
  category: 'rust',
2578
2719
  fix: 'Encourage /// doc comments and cargo doc in project instructions.',
@@ -2594,7 +2735,7 @@ const COPILOT_TECHNIQUES = {
2594
2735
  copilotRustCrossCompilationDocumented: {
2595
2736
  id: 'CP-RS14',
2596
2737
  name: 'Cross-compilation documented',
2597
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cross.?compil|--target|rustup target|cargo build.*--target/i.test(docs); },
2738
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /cross.?compil|--target|rustup target|cargo build.*--target/i.test(docs); },
2598
2739
  impact: 'low',
2599
2740
  category: 'rust',
2600
2741
  fix: 'Document cross-compilation targets and setup instructions.',
@@ -2605,7 +2746,7 @@ const COPILOT_TECHNIQUES = {
2605
2746
  copilotRustMemorySafetyDocumented: {
2606
2747
  id: 'CP-RS15',
2607
2748
  name: 'Memory safety patterns documented',
2608
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /ownership|borrow|lifetime|memory.?safe|Arc|Rc|RefCell/i.test(docs); },
2749
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /ownership|borrow|lifetime|memory.?safe|Arc|Rc|RefCell/i.test(docs); },
2609
2750
  impact: 'medium',
2610
2751
  category: 'rust',
2611
2752
  fix: 'Document memory safety patterns (ownership, borrowing, lifetime conventions).',
@@ -2616,7 +2757,7 @@ const COPILOT_TECHNIQUES = {
2616
2757
  copilotRustAsyncRuntimeDocumented: {
2617
2758
  id: 'CP-RS16',
2618
2759
  name: 'Async runtime documented (tokio/async-std in deps)',
2619
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/tokio|async-std|smol/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /tokio|async-std|async|await|runtime/i.test(docs); },
2760
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/tokio|async-std|smol/i.test(cargo)) return null; const docs = stackDocsBundle(ctx); return /tokio|async-std|async|await|runtime/i.test(docs); },
2620
2761
  impact: 'medium',
2621
2762
  category: 'rust',
2622
2763
  fix: 'Document async runtime choice and patterns (tokio, async-std).',
@@ -2627,7 +2768,7 @@ const COPILOT_TECHNIQUES = {
2627
2768
  copilotRustSerdeDocumented: {
2628
2769
  id: 'CP-RS17',
2629
2770
  name: 'Serde patterns documented',
2630
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/serde/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /serde|Serialize|Deserialize|serde_json|serde_yaml/i.test(docs); },
2771
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/serde/i.test(cargo)) return null; const docs = stackDocsBundle(ctx); return /serde|Serialize|Deserialize|serde_json|serde_yaml/i.test(docs); },
2631
2772
  impact: 'medium',
2632
2773
  category: 'rust',
2633
2774
  fix: 'Document serde serialization/deserialization patterns and conventions.',
@@ -2649,7 +2790,7 @@ const COPILOT_TECHNIQUES = {
2649
2790
  copilotRustWasmTargetDocumented: {
2650
2791
  id: 'CP-RS19',
2651
2792
  name: 'WASM target documented if applicable',
2652
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/wasm|wasm-bindgen|wasm-pack/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /wasm|WebAssembly|wasm-pack|wasm-bindgen/i.test(docs); },
2793
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/wasm|wasm-bindgen|wasm-pack/i.test(cargo)) return null; const docs = stackDocsBundle(ctx); return /wasm|WebAssembly|wasm-pack|wasm-bindgen/i.test(docs); },
2653
2794
  impact: 'low',
2654
2795
  category: 'rust',
2655
2796
  fix: 'Document WASM target configuration and build process.',
@@ -2752,7 +2893,7 @@ const COPILOT_TECHNIQUES = {
2752
2893
  copilotJavaSpringProfilesDocumented: {
2753
2894
  id: 'CP-JV08',
2754
2895
  name: 'Spring profiles documented',
2755
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /spring[.]profiles|@Profile|SPRING_PROFILES_ACTIVE/i.test(docs); },
2896
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /spring[.]profiles|@Profile|SPRING_PROFILES_ACTIVE/i.test(docs); },
2756
2897
  impact: 'medium',
2757
2898
  category: 'java',
2758
2899
  fix: 'Document Spring profiles and their configuration in project instructions.',
@@ -2774,7 +2915,7 @@ const COPILOT_TECHNIQUES = {
2774
2915
  copilotJavaLombokDocumented: {
2775
2916
  id: 'CP-JV10',
2776
2917
  name: 'Lombok/MapStruct documented if used',
2777
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/lombok|mapstruct/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /lombok|mapstruct/i.test(docs); },
2918
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/lombok|mapstruct/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /lombok|mapstruct/i.test(docs); },
2778
2919
  impact: 'low',
2779
2920
  category: 'java',
2780
2921
  fix: 'Document Lombok/MapStruct usage and IDE setup requirements.',
@@ -2796,7 +2937,7 @@ const COPILOT_TECHNIQUES = {
2796
2937
  copilotJavaSecurityConfigured: {
2797
2938
  id: 'CP-JV12',
2798
2939
  name: 'Security configuration documented',
2799
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring-security|spring-boot-starter-security/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /security|authentication|authorization|SecurityConfig|@EnableWebSecurity/i.test(docs); },
2940
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring-security|spring-boot-starter-security/i.test(deps)) return null; const docs = stackDocsBundle(ctx); return /security|authentication|authorization|SecurityConfig|@EnableWebSecurity/i.test(docs); },
2800
2941
  impact: 'high',
2801
2942
  category: 'java',
2802
2943
  fix: 'Document Spring Security configuration and authentication setup.',
@@ -2884,7 +3025,7 @@ const COPILOT_TECHNIQUES = {
2884
3025
  copilotJavaBuildCommandDocumented: {
2885
3026
  id: 'CP-JV20',
2886
3027
  name: 'Build command documented in instructions',
2887
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /mvn|gradle|mvnw|gradlew|maven|./i.test(docs) && /build|compile|package|install/i.test(docs); },
3028
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /mvn|gradle|mvnw|gradlew|maven|./i.test(docs) && /build|compile|package|install/i.test(docs); },
2888
3029
  impact: 'high',
2889
3030
  category: 'java',
2890
3031
  fix: 'Document build command (mvnw package, gradlew build) in project instructions.',
@@ -2954,7 +3095,7 @@ const COPILOT_TECHNIQUES = {
2954
3095
  copilotrubyRailsCredentialsDocumented: {
2955
3096
  id: 'CP-RB06',
2956
3097
  name: 'Rails credentials documented in instructions',
2957
- check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/credentials/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /credentials|encrypted|master\.key|secret_key_base/i.test(docs); },
3098
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/credentials/.test(f))) return null; const docs = stackDocsBundle(ctx); return /credentials|encrypted|master\.key|secret_key_base/i.test(docs); },
2958
3099
  impact: 'high',
2959
3100
  category: 'ruby',
2960
3101
  fix: 'Document Rails credentials management (rails credentials:edit) in project instructions.',
@@ -2965,7 +3106,7 @@ const COPILOT_TECHNIQUES = {
2965
3106
  copilotrubyMigrationsDocumented: {
2966
3107
  id: 'CP-RB07',
2967
3108
  name: 'Database migrations documented (db/migrate/)',
2968
- check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /db\/migrate\//.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /migration|migrate|db:migrate|rails db/i.test(docs); },
3109
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /db\/migrate\//.test(f))) return null; const docs = stackDocsBundle(ctx); return /migration|migrate|db:migrate|rails db/i.test(docs); },
2969
3110
  impact: 'medium',
2970
3111
  category: 'ruby',
2971
3112
  fix: 'Document database migration workflow (rails db:migrate) in project instructions.',
@@ -2998,7 +3139,7 @@ const COPILOT_TECHNIQUES = {
2998
3139
  copilotrubyRailsRoutesDocumented: {
2999
3140
  id: 'CP-RB10',
3000
3141
  name: 'Rails routes documented',
3001
- check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/routes\.rb$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /routes|endpoints|api.*path|REST/i.test(docs); },
3142
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/routes\.rb$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /routes|endpoints|api.*path|REST/i.test(docs); },
3002
3143
  impact: 'medium',
3003
3144
  category: 'ruby',
3004
3145
  fix: 'Document key routes and API endpoints in project instructions.',
@@ -3009,7 +3150,7 @@ const COPILOT_TECHNIQUES = {
3009
3150
  copilotrubyBackgroundJobsDocumented: {
3010
3151
  id: 'CP-RB11',
3011
3152
  name: 'Background jobs documented (Sidekiq/GoodJob)',
3012
- check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sidekiq|good_job|delayed_job|resque/i.test(gf)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /sidekiq|good_job|delayed_job|resque|background.*job|worker|queue/i.test(docs); },
3153
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sidekiq|good_job|delayed_job|resque/i.test(gf)) return null; const docs = stackDocsBundle(ctx); return /sidekiq|good_job|delayed_job|resque|background.*job|worker|queue/i.test(docs); },
3013
3154
  impact: 'medium',
3014
3155
  category: 'ruby',
3015
3156
  fix: 'Document background job framework and worker configuration.',
@@ -3031,7 +3172,7 @@ const COPILOT_TECHNIQUES = {
3031
3172
  copilotrubyAssetPipelineDocumented: {
3032
3173
  id: 'CP-RB13',
3033
3174
  name: 'Asset pipeline documented',
3034
- check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sprockets|propshaft|webpacker|jsbundling|cssbundling/i.test(gf)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /asset|sprockets|propshaft|webpacker|jsbundling|cssbundling|esbuild|vite/i.test(docs); },
3175
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sprockets|propshaft|webpacker|jsbundling|cssbundling/i.test(gf)) return null; const docs = stackDocsBundle(ctx); return /asset|sprockets|propshaft|webpacker|jsbundling|cssbundling|esbuild|vite/i.test(docs); },
3035
3176
  impact: 'low',
3036
3177
  category: 'ruby',
3037
3178
  fix: 'Document asset pipeline configuration (Sprockets, Propshaft, or JS/CSS bundling).',
@@ -3101,7 +3242,7 @@ const COPILOT_TECHNIQUES = {
3101
3242
  copilotdotnetTestDocumented: {
3102
3243
  id: 'CP-DN04',
3103
3244
  name: 'dotnet test documented',
3104
- check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /dotnet test|xunit|nunit|mstest/i.test(docs); },
3245
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /dotnet test|xunit|nunit|mstest/i.test(docs); },
3105
3246
  impact: 'high',
3106
3247
  category: 'dotnet',
3107
3248
  fix: 'Document how to run tests with dotnet test in project instructions.',
@@ -3145,7 +3286,7 @@ const COPILOT_TECHNIQUES = {
3145
3286
  copilotdotnetUserSecretsDocumented: {
3146
3287
  id: 'CP-DN08',
3147
3288
  name: 'User secrets configured in instructions',
3148
- check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /user.?secrets|dotnet secrets|Secret Manager/i.test(docs); },
3289
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /user.?secrets|dotnet secrets|Secret Manager/i.test(docs); },
3149
3290
  impact: 'high',
3150
3291
  category: 'dotnet',
3151
3292
  fix: 'Document user secrets management (dotnet user-secrets) in project instructions.',
@@ -3336,7 +3477,7 @@ const COPILOT_TECHNIQUES = {
3336
3477
  copilotphpArtisanCommandsDocumented: {
3337
3478
  id: 'CP-PHP10',
3338
3479
  name: 'Artisan commands documented',
3339
- check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /artisan|php artisan|make:model|make:controller|migrate/i.test(docs); },
3480
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /artisan|php artisan|make:model|make:controller|migrate/i.test(docs); },
3340
3481
  impact: 'medium',
3341
3482
  category: 'php',
3342
3483
  fix: 'Document key Artisan commands (migrate, seed, make:*) in project instructions.',
@@ -3347,7 +3488,7 @@ const COPILOT_TECHNIQUES = {
3347
3488
  copilotphpQueueWorkerDocumented: {
3348
3489
  id: 'CP-PHP11',
3349
3490
  name: 'Queue worker documented',
3350
- check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; const cj = ctx.fileContent('composer.json') || ''; if (!/horizon|queue/i.test(cj) && !ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /queue|horizon|worker|job|dispatch/i.test(docs); },
3491
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; const cj = ctx.fileContent('composer.json') || ''; if (!/horizon|queue/i.test(cj) && !ctx.files.some(f => /artisan$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /queue|horizon|worker|job|dispatch/i.test(docs); },
3351
3492
  impact: 'medium',
3352
3493
  category: 'php',
3353
3494
  fix: 'Document queue worker setup (php artisan queue:work, Horizon).',
@@ -3369,7 +3510,7 @@ const COPILOT_TECHNIQUES = {
3369
3510
  copilotphpAssetBundlingDocumented: {
3370
3511
  id: 'CP-PHP13',
3371
3512
  name: 'Vite/Mix asset bundling documented',
3372
- check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /vite\.config\.|webpack\.mix\.js$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /vite|mix|asset|npm run dev|npm run build/i.test(docs); },
3513
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /vite\.config\.|webpack\.mix\.js$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /vite|mix|asset|npm run dev|npm run build/i.test(docs); },
3373
3514
  impact: 'low',
3374
3515
  category: 'php',
3375
3516
  fix: 'Document asset bundling setup (Vite or Mix) in project instructions.',
@@ -3380,7 +3521,7 @@ const COPILOT_TECHNIQUES = {
3380
3521
  copilotphpConfigCachingDocumented: {
3381
3522
  id: 'CP-PHP14',
3382
3523
  name: 'Laravel config caching documented',
3383
- check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /config:cache|config:clear|route:cache|optimize/i.test(docs); },
3524
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = stackDocsBundle(ctx); return /config:cache|config:clear|route:cache|optimize/i.test(docs); },
3384
3525
  impact: 'low',
3385
3526
  category: 'php',
3386
3527
  fix: 'Document config/route caching strategy (php artisan config:cache) for production.',