@nerviq/cli 1.17.2 → 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.
- package/README.md +3 -3
- package/bin/cli.js +140 -344
- package/package.json +60 -60
- package/src/codex/techniques.js +26 -7
- package/src/copilot/config-parser.js +280 -226
- package/src/copilot/context.js +218 -197
- package/src/copilot/techniques.js +219 -78
- package/src/fix-engine.js +783 -0
|
@@ -45,7 +45,17 @@ const FILLER_PATTERNS = [
|
|
|
45
45
|
];
|
|
46
46
|
|
|
47
47
|
function countSections(markdown) {
|
|
48
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1080
|
-
//
|
|
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
|
-
|
|
1122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1854
|
-
//
|
|
1855
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.',
|