@nerviq/cli 0.0.1 → 0.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,1822 @@
1
+ /**
2
+ * Copilot techniques module — CHECK CATALOG
3
+ *
4
+ * 82 checks across 16 categories:
5
+ * v0.1 (38): A. Instructions(8), B. Config(6), C. Trust & Safety(9), D. MCP(5), E. Cloud Agent(5), F. Organization(5)
6
+ * v0.5 (54): G. Prompt Files(4), H. Agents & Skills(4), I. VS Code IDE(4), J. CLI(4)
7
+ * v1.0 (70): K. Cross-Surface(5), L. Enterprise(5), M. Quality Deep(6)
8
+ * CP-08 (82): N. Advisory(4), O. Pack(4), P. Repeat(3), Q. Freshness(3)
9
+ *
10
+ * Each check: { id, name, check(ctx), impact, rating, category, fix, template, file(), line() }
11
+ */
12
+
13
+ const os = require('os');
14
+ const path = require('path');
15
+ const { CopilotProjectContext } = require('./context');
16
+ const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
17
+ const { extractFrontmatter, validateInstructionFrontmatter, validatePromptFrontmatter } = require('./config-parser');
18
+
19
+ // ─── Shared helpers ─────────────────────────────────────────────────────────
20
+
21
+ const FILLER_PATTERNS = [
22
+ /\bbe helpful\b/i,
23
+ /\bbe accurate\b/i,
24
+ /\bbe concise\b/i,
25
+ /\balways do your best\b/i,
26
+ /\bmaintain high quality\b/i,
27
+ /\bwrite clean code\b/i,
28
+ /\bfollow best practices\b/i,
29
+ ];
30
+
31
+ function countSections(markdown) {
32
+ return (markdown.match(/^##\s+/gm) || []).length;
33
+ }
34
+
35
+ function firstLineMatching(text, matcher) {
36
+ if (!text) return null;
37
+ const lines = text.split(/\r?\n/);
38
+ for (let index = 0; index < lines.length; index++) {
39
+ const line = lines[index];
40
+ if (typeof matcher === 'string' && line.includes(matcher)) return index + 1;
41
+ if (matcher instanceof RegExp && matcher.test(line)) {
42
+ matcher.lastIndex = 0;
43
+ return index + 1;
44
+ }
45
+ if (typeof matcher === 'function' && matcher(line, index + 1)) return index + 1;
46
+ }
47
+ return null;
48
+ }
49
+
50
+ function findFillerLine(content) {
51
+ return firstLineMatching(content, (line) => FILLER_PATTERNS.some((p) => p.test(line)));
52
+ }
53
+
54
+ function findSecretLine(content) {
55
+ const lines = content.split(/\r?\n/);
56
+ for (let index = 0; index < lines.length; index++) {
57
+ const matched = EMBEDDED_SECRET_PATTERNS.some((pattern) => {
58
+ pattern.lastIndex = 0;
59
+ return pattern.test(lines[index]);
60
+ });
61
+ if (matched) return index + 1;
62
+ }
63
+ return null;
64
+ }
65
+
66
+ function copilotInstructions(ctx) {
67
+ return ctx.copilotInstructionsContent ? ctx.copilotInstructionsContent() : (ctx.fileContent('.github/copilot-instructions.md') || null);
68
+ }
69
+
70
+ function vscodeSettingsRaw(ctx) {
71
+ return ctx.fileContent('.vscode/settings.json') || '';
72
+ }
73
+
74
+ function vscodeSettingsData(ctx) {
75
+ const result = ctx.vscodeSettings();
76
+ return result && result.ok ? result.data : null;
77
+ }
78
+
79
+ function mcpJsonRaw(ctx) {
80
+ return ctx.fileContent('.vscode/mcp.json') || '';
81
+ }
82
+
83
+ function mcpJsonData(ctx) {
84
+ const result = ctx.mcpConfig();
85
+ return result && result.ok ? result.data : null;
86
+ }
87
+
88
+ function cloudAgentContent(ctx) {
89
+ return ctx.cloudAgentConfig ? ctx.cloudAgentConfig() : null;
90
+ }
91
+
92
+ function docsBundle(ctx) {
93
+ const instr = copilotInstructions(ctx) || '';
94
+ const readme = ctx.fileContent('README.md') || '';
95
+ return `${instr}\n${readme}`;
96
+ }
97
+
98
+ function expectedVerificationCategories(ctx) {
99
+ const categories = new Set();
100
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
101
+ const scripts = pkg && pkg.scripts ? pkg.scripts : {};
102
+ if (scripts.test) categories.add('test');
103
+ if (scripts.lint) categories.add('lint');
104
+ if (scripts.build) categories.add('build');
105
+ if (ctx.fileContent('Cargo.toml')) { categories.add('test'); categories.add('build'); }
106
+ if (ctx.fileContent('go.mod')) { categories.add('test'); categories.add('build'); }
107
+ if (ctx.fileContent('pyproject.toml') || ctx.fileContent('requirements.txt')) categories.add('test');
108
+ if (ctx.fileContent('Makefile') || ctx.fileContent('justfile')) categories.add('build');
109
+ return [...categories];
110
+ }
111
+
112
+ function hasCommandMention(content, category) {
113
+ if (category === 'test') {
114
+ 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);
115
+ }
116
+ if (category === 'lint') {
117
+ 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);
118
+ }
119
+ if (category === 'build') {
120
+ 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);
121
+ }
122
+ return false;
123
+ }
124
+
125
+ function hasArchitecture(content) {
126
+ return /```mermaid|flowchart\b|graph\s+(TD|LR|RL|BT)\b|##\s+Architecture\b|##\s+Project Map\b|##\s+Structure\b/i.test(content);
127
+ }
128
+
129
+ function getCopilotSetting(ctx, key) {
130
+ const data = vscodeSettingsData(ctx);
131
+ if (!data) return undefined;
132
+ // Support dotted key navigation through nested objects
133
+ const parts = key.split('.');
134
+ let cursor = data;
135
+ for (const part of parts) {
136
+ if (cursor == null || typeof cursor !== 'object') return undefined;
137
+ cursor = cursor[part];
138
+ }
139
+ return cursor;
140
+ }
141
+
142
+ // ─── COPILOT_TECHNIQUES ──────────────────────────────────────────────────────
143
+
144
+ const COPILOT_TECHNIQUES = {
145
+
146
+ // =============================================
147
+ // A. Instructions (8 checks) — CP-A01..CP-A08
148
+ // =============================================
149
+
150
+ copilotInstructionsExists: {
151
+ id: 'CP-A01',
152
+ name: '.github/copilot-instructions.md exists',
153
+ check: (ctx) => Boolean(copilotInstructions(ctx)),
154
+ impact: 'critical',
155
+ rating: 5,
156
+ category: 'instructions',
157
+ fix: 'Create .github/copilot-instructions.md with repo-specific instructions for Copilot.',
158
+ template: 'copilot-instructions',
159
+ file: () => '.github/copilot-instructions.md',
160
+ line: (ctx) => (copilotInstructions(ctx) ? 1 : null),
161
+ },
162
+
163
+ copilotInstructionsSubstantive: {
164
+ id: 'CP-A02',
165
+ name: 'Instructions have substantive content (>20 lines, 2+ sections)',
166
+ check: (ctx) => {
167
+ const content = copilotInstructions(ctx);
168
+ if (!content) return null;
169
+ const nonEmpty = content.split(/\r?\n/).filter(l => l.trim()).length;
170
+ return nonEmpty >= 20 && countSections(content) >= 2;
171
+ },
172
+ impact: 'high',
173
+ rating: 5,
174
+ category: 'instructions',
175
+ fix: 'Expand copilot-instructions.md to at least 20 substantive lines and 2+ sections.',
176
+ template: 'copilot-instructions',
177
+ file: () => '.github/copilot-instructions.md',
178
+ line: () => 1,
179
+ },
180
+
181
+ copilotInstructionsCommands: {
182
+ id: 'CP-A03',
183
+ name: 'Instructions include build/test/lint commands',
184
+ check: (ctx) => {
185
+ const content = copilotInstructions(ctx);
186
+ if (!content) return null;
187
+ const expected = expectedVerificationCategories(ctx);
188
+ if (expected.length === 0) return /\bverify\b|\btest\b|\blint\b|\bbuild\b/i.test(content);
189
+ return expected.every(cat => hasCommandMention(content, cat));
190
+ },
191
+ impact: 'high',
192
+ rating: 5,
193
+ category: 'instructions',
194
+ fix: 'Document the actual test/lint/build commands so Copilot agent can verify its changes.',
195
+ template: 'copilot-instructions',
196
+ file: () => '.github/copilot-instructions.md',
197
+ line: (ctx) => {
198
+ const content = copilotInstructions(ctx);
199
+ return content ? (firstLineMatching(content, /\bVerification\b|\btest\b|\blint\b|\bbuild\b/i) || 1) : null;
200
+ },
201
+ },
202
+
203
+ copilotInstructionsNoFiller: {
204
+ id: 'CP-A04',
205
+ name: 'No generic filler instructions',
206
+ check: (ctx) => {
207
+ const content = copilotInstructions(ctx);
208
+ if (!content) return null;
209
+ return !FILLER_PATTERNS.some(p => p.test(content));
210
+ },
211
+ impact: 'low',
212
+ rating: 3,
213
+ category: 'instructions',
214
+ fix: 'Replace generic filler like "be helpful" with concrete repo-specific guidance.',
215
+ template: null,
216
+ file: () => '.github/copilot-instructions.md',
217
+ line: (ctx) => {
218
+ const content = copilotInstructions(ctx);
219
+ return content ? findFillerLine(content) : null;
220
+ },
221
+ },
222
+
223
+ copilotInstructionsNoSecrets: {
224
+ id: 'CP-A05',
225
+ name: 'No secrets/API keys in instruction files',
226
+ check: (ctx) => {
227
+ const content = copilotInstructions(ctx) || '';
228
+ const scoped = (ctx.scopedInstructions ? ctx.scopedInstructions() : []).map(s => s.body || '').join('\n');
229
+ const prompts = (ctx.promptFiles ? ctx.promptFiles() : []).map(p => p.body || '').join('\n');
230
+ const combined = `${content}\n${scoped}\n${prompts}`;
231
+ if (!combined.trim()) return null;
232
+ return !containsEmbeddedSecret(combined);
233
+ },
234
+ impact: 'critical',
235
+ rating: 5,
236
+ category: 'instructions',
237
+ fix: 'Remove API keys and secrets from instruction and prompt files. Use environment variables instead.',
238
+ template: null,
239
+ file: () => '.github/copilot-instructions.md',
240
+ line: (ctx) => {
241
+ const content = copilotInstructions(ctx);
242
+ return content ? findSecretLine(content) : null;
243
+ },
244
+ },
245
+
246
+ copilotScopedInstructionsFrontmatter: {
247
+ id: 'CP-A06',
248
+ name: 'Scoped instruction files have valid applyTo glob in frontmatter',
249
+ check: (ctx) => {
250
+ const scoped = ctx.scopedInstructions ? ctx.scopedInstructions() : [];
251
+ if (scoped.length === 0) return null; // No scoped instructions = N/A
252
+ return scoped.every(s => {
253
+ if (!s.frontmatter) return false;
254
+ const validation = validateInstructionFrontmatter(s.frontmatter);
255
+ return validation.valid;
256
+ });
257
+ },
258
+ impact: 'high',
259
+ rating: 4,
260
+ category: 'instructions',
261
+ fix: 'Add valid applyTo glob pattern in YAML frontmatter of each .github/instructions/*.instructions.md file.',
262
+ template: null,
263
+ file: () => '.github/instructions/',
264
+ line: () => 1,
265
+ },
266
+
267
+ copilotNoOrgContradiction: {
268
+ id: 'CP-A07',
269
+ name: 'No contradictions between repo and org instructions',
270
+ check: (ctx) => {
271
+ // Can't detect org instructions from files alone; check for explicit org markers
272
+ const content = copilotInstructions(ctx);
273
+ if (!content) return null;
274
+ // Check if instructions reference overriding org-level rules
275
+ const hasOrgOverride = /\boverride org\b|\bignore org\b|\bdisable org\b/i.test(content);
276
+ return !hasOrgOverride;
277
+ },
278
+ impact: 'medium',
279
+ rating: 3,
280
+ category: 'instructions',
281
+ fix: 'Ensure repo instructions complement (not contradict) org-level instructions.',
282
+ template: null,
283
+ file: () => '.github/copilot-instructions.md',
284
+ line: (ctx) => {
285
+ const content = copilotInstructions(ctx);
286
+ return content ? firstLineMatching(content, /\boverride org\b|\bignore org\b|\bdisable org\b/i) : null;
287
+ },
288
+ },
289
+
290
+ copilotNoDeprecatedCodeGenInstructions: {
291
+ id: 'CP-A08',
292
+ name: 'Deprecated codeGeneration.instructions not used in VS Code settings',
293
+ check: (ctx) => {
294
+ const raw = vscodeSettingsRaw(ctx);
295
+ if (!raw) return null;
296
+ return !raw.includes('codeGeneration.instructions');
297
+ },
298
+ impact: 'medium',
299
+ rating: 4,
300
+ category: 'instructions',
301
+ fix: 'Remove github.copilot.chat.codeGeneration.instructions from settings.json (deprecated since VS Code 1.102). Use .github/instructions/*.instructions.md instead.',
302
+ template: null,
303
+ file: () => '.vscode/settings.json',
304
+ line: (ctx) => {
305
+ const raw = vscodeSettingsRaw(ctx);
306
+ return raw ? firstLineMatching(raw, /codeGeneration\.instructions/) : null;
307
+ },
308
+ },
309
+
310
+ // =============================================
311
+ // B. Config (6 checks) — CP-B01..CP-B06
312
+ // =============================================
313
+
314
+ copilotVscodeSettingsExists: {
315
+ id: 'CP-B01',
316
+ name: '.vscode/settings.json has Copilot agent settings',
317
+ check: (ctx) => {
318
+ const data = vscodeSettingsData(ctx);
319
+ if (!data) return false;
320
+ // Check for any Copilot or chat-related key
321
+ const raw = vscodeSettingsRaw(ctx);
322
+ return /github\.copilot|chat\./.test(raw);
323
+ },
324
+ impact: 'medium',
325
+ rating: 4,
326
+ category: 'config',
327
+ fix: 'Add Copilot agent settings to .vscode/settings.json.',
328
+ template: 'copilot-vscode-settings',
329
+ file: () => '.vscode/settings.json',
330
+ line: () => 1,
331
+ },
332
+
333
+ copilotCloudAgentSetup: {
334
+ id: 'CP-B02',
335
+ name: 'Cloud agent setup workflow exists if cloud agent is used',
336
+ check: (ctx) => {
337
+ const content = cloudAgentContent(ctx);
338
+ // If no cloud agent signals, N/A
339
+ const hasCloudSignals = ctx.fileContent('.github/copilot-instructions.md') &&
340
+ (ctx.workflowFiles ? ctx.workflowFiles() : []).some(f => f.includes('copilot'));
341
+ if (!hasCloudSignals && !content) return null;
342
+ return Boolean(content);
343
+ },
344
+ impact: 'high',
345
+ rating: 5,
346
+ category: 'config',
347
+ fix: 'Create .github/workflows/copilot-setup-steps.yml to configure the cloud agent environment.',
348
+ template: 'copilot-cloud-setup',
349
+ file: () => '.github/workflows/copilot-setup-steps.yml',
350
+ line: () => 1,
351
+ },
352
+
353
+ copilotModelExplicit: {
354
+ id: 'CP-B03',
355
+ name: 'Model preference is explicit (not silently defaulting)',
356
+ check: (ctx) => {
357
+ // Check prompt files for explicit model setting
358
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
359
+ const hasModelInPrompt = prompts.some(p => p.frontmatter && p.frontmatter.model);
360
+ // Check instructions for model guidance
361
+ const instr = copilotInstructions(ctx) || '';
362
+ const hasModelMention = /\bmodel\b.*\b(gpt|claude|o[134]|sonnet|opus)\b/i.test(instr);
363
+ if (!prompts.length && !instr) return null;
364
+ return hasModelInPrompt || hasModelMention;
365
+ },
366
+ impact: 'medium',
367
+ rating: 3,
368
+ category: 'config',
369
+ fix: 'Set model preference explicitly in prompt files or instructions to avoid silent downgrades.',
370
+ template: null,
371
+ file: () => '.github/prompts/',
372
+ line: () => 1,
373
+ },
374
+
375
+ copilotNoDeprecatedSettings: {
376
+ id: 'CP-B04',
377
+ name: 'No deprecated VS Code Copilot settings',
378
+ check: (ctx) => {
379
+ const raw = vscodeSettingsRaw(ctx);
380
+ if (!raw) return null;
381
+ const deprecatedPatterns = [
382
+ /github\.copilot\.chat\.codeGeneration\.instructions/,
383
+ /github\.copilot\.inlineSuggest\.enable/,
384
+ ];
385
+ return !deprecatedPatterns.some(p => p.test(raw));
386
+ },
387
+ impact: 'medium',
388
+ rating: 4,
389
+ category: 'config',
390
+ fix: 'Replace deprecated Copilot settings with current equivalents.',
391
+ template: null,
392
+ file: () => '.vscode/settings.json',
393
+ line: (ctx) => {
394
+ const raw = vscodeSettingsRaw(ctx);
395
+ return raw ? firstLineMatching(raw, /codeGeneration\.instructions|inlineSuggest\.enable/) : null;
396
+ },
397
+ },
398
+
399
+ copilotPromptFilesValid: {
400
+ id: 'CP-B05',
401
+ name: 'Prompt files (.github/prompts/) use valid frontmatter',
402
+ check: (ctx) => {
403
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
404
+ if (prompts.length === 0) return null;
405
+ return prompts.every(p => {
406
+ if (!p.frontmatter) return false;
407
+ const validation = validatePromptFrontmatter(p.frontmatter);
408
+ return validation.valid;
409
+ });
410
+ },
411
+ impact: 'medium',
412
+ rating: 4,
413
+ category: 'config',
414
+ fix: 'Ensure all .github/prompts/*.prompt.md files have valid YAML frontmatter with description, agent, model, or tools fields.',
415
+ template: null,
416
+ file: () => '.github/prompts/',
417
+ line: () => 1,
418
+ },
419
+
420
+ copilotVscodeSettingsValidJson: {
421
+ id: 'CP-B06',
422
+ name: 'VS Code settings.json is valid JSON',
423
+ check: (ctx) => {
424
+ const raw = vscodeSettingsRaw(ctx);
425
+ if (!raw) return null;
426
+ const result = ctx.vscodeSettings();
427
+ return result && result.ok;
428
+ },
429
+ impact: 'critical',
430
+ rating: 5,
431
+ category: 'config',
432
+ fix: 'Fix malformed JSON in .vscode/settings.json.',
433
+ template: null,
434
+ file: () => '.vscode/settings.json',
435
+ line: (ctx) => {
436
+ const result = ctx.vscodeSettings();
437
+ if (result && result.ok) return null;
438
+ if (result && result.error) {
439
+ const match = result.error.match(/position (\d+)/i);
440
+ if (match) {
441
+ const raw = vscodeSettingsRaw(ctx);
442
+ return raw ? raw.slice(0, Number(match[1])).split('\n').length : 1;
443
+ }
444
+ }
445
+ return 1;
446
+ },
447
+ },
448
+
449
+ // =============================================
450
+ // C. Trust & Safety (9 checks) — CP-C01..CP-C09
451
+ // =============================================
452
+
453
+ copilotContentExclusions: {
454
+ id: 'CP-C01',
455
+ name: 'Content exclusions configured for sensitive files',
456
+ check: (ctx) => {
457
+ const exclusions = ctx.contentExclusions ? ctx.contentExclusions() : null;
458
+ if (exclusions) return true;
459
+ // Also check for .gitignore patterns that suggest awareness
460
+ const gitignore = ctx.fileContent('.gitignore') || '';
461
+ return /\.env\b|secrets\/|credentials|\.pem\b|\.key\b/i.test(gitignore);
462
+ },
463
+ impact: 'high',
464
+ rating: 5,
465
+ category: 'trust',
466
+ fix: 'Configure content exclusions for .env, secrets/, credentials, and *.pem files in org settings or repo config.',
467
+ template: null,
468
+ file: () => '.vscode/settings.json',
469
+ line: () => null,
470
+ },
471
+
472
+ copilotCloudContentExclusionGap: {
473
+ id: 'CP-C02',
474
+ name: 'Cloud agent content exclusion gap documented',
475
+ check: (ctx) => {
476
+ const cloud = cloudAgentContent(ctx);
477
+ if (!cloud) return null; // N/A if no cloud agent
478
+ // Check if the gap is documented
479
+ const instr = copilotInstructions(ctx) || '';
480
+ return /content exclu.*cloud|cloud.*content exclu|cloud agent.*sensitive|exclusion.*not enforced/i.test(instr) ||
481
+ /content exclu.*cloud|cloud.*content exclu/i.test(cloud);
482
+ },
483
+ impact: 'critical',
484
+ rating: 5,
485
+ category: 'trust',
486
+ fix: 'Document that content exclusions are NOT enforced on the cloud agent. Review cloud agent PRs carefully for sensitive file access.',
487
+ template: null,
488
+ file: () => '.github/copilot-instructions.md',
489
+ line: () => null,
490
+ },
491
+
492
+ copilotTerminalSandboxEnabled: {
493
+ id: 'CP-C03',
494
+ name: 'Terminal sandbox enabled (VS Code agent)',
495
+ check: (ctx) => {
496
+ const data = vscodeSettingsData(ctx);
497
+ if (!data) return false;
498
+ const raw = vscodeSettingsRaw(ctx);
499
+ // Check for chat.tools.terminal.sandbox.enabled = true
500
+ if (raw.includes('terminal.sandbox') && raw.includes('true')) return true;
501
+ return getCopilotSetting(ctx, 'chat.tools.terminal.sandbox.enabled') === true;
502
+ },
503
+ impact: 'high',
504
+ rating: 5,
505
+ category: 'trust',
506
+ fix: 'Add "chat.tools.terminal.sandbox.enabled": true to .vscode/settings.json.',
507
+ template: 'copilot-vscode-settings',
508
+ file: () => '.vscode/settings.json',
509
+ line: (ctx) => {
510
+ const raw = vscodeSettingsRaw(ctx);
511
+ return raw ? firstLineMatching(raw, /terminal\.sandbox/) : null;
512
+ },
513
+ },
514
+
515
+ copilotNoWindowsSandbox: {
516
+ id: 'CP-C04',
517
+ name: 'No terminal sandbox on Windows — documented',
518
+ check: (ctx) => {
519
+ if (os.platform() !== 'win32') return null; // N/A on non-Windows
520
+ const instr = copilotInstructions(ctx) || '';
521
+ const readme = ctx.fileContent('README.md') || '';
522
+ const combined = `${instr}\n${readme}`;
523
+ return /\bwindows\b.*sandbox|sandbox.*\bwindows\b|terminal sandbox.*unavailable|no sandbox.*windows/i.test(combined);
524
+ },
525
+ impact: 'critical',
526
+ rating: 5,
527
+ category: 'trust',
528
+ fix: 'Document that terminal sandbox is unavailable on native Windows. Use WSL2 or Docker for sandboxed execution.',
529
+ template: null,
530
+ file: () => '.github/copilot-instructions.md',
531
+ line: () => null,
532
+ },
533
+
534
+ copilotAutoApprovalSpecific: {
535
+ id: 'CP-C05',
536
+ name: 'Auto-approval rules are specific (not wildcard)',
537
+ check: (ctx) => {
538
+ const data = vscodeSettingsData(ctx);
539
+ if (!data) return null;
540
+ const raw = vscodeSettingsRaw(ctx);
541
+ // Check for auto-approval patterns
542
+ const autoApproval = getCopilotSetting(ctx, 'chat.agent.autoApproval.terminalCommands');
543
+ if (!autoApproval || !Array.isArray(autoApproval)) return null;
544
+ // Fail if any wildcard patterns
545
+ return !autoApproval.some(pattern => pattern === '*' || pattern === '**' || pattern === '.*');
546
+ },
547
+ impact: 'high',
548
+ rating: 5,
549
+ category: 'trust',
550
+ fix: 'Replace wildcard auto-approval patterns with specific command patterns (e.g., "npm test", "npm run lint").',
551
+ template: null,
552
+ file: () => '.vscode/settings.json',
553
+ line: (ctx) => {
554
+ const raw = vscodeSettingsRaw(ctx);
555
+ return raw ? firstLineMatching(raw, /autoApproval/) : null;
556
+ },
557
+ },
558
+
559
+ copilotCloudAgentPRReview: {
560
+ id: 'CP-C06',
561
+ name: 'Cloud agent PRs require review before CI runs',
562
+ check: (ctx) => {
563
+ const cloud = cloudAgentContent(ctx);
564
+ if (!cloud) return null;
565
+ // Check workflows for branch protection or review requirements
566
+ const workflows = ctx.workflowFiles ? ctx.workflowFiles() : [];
567
+ const hasReviewGate = workflows.some(f => {
568
+ const content = ctx.fileContent(f) || '';
569
+ return /pull_request_review|required_status_checks|require.*approval/i.test(content);
570
+ });
571
+ // Also check instructions for review guidance
572
+ const instr = copilotInstructions(ctx) || '';
573
+ return hasReviewGate || /\breview before merge\b|\breview required\b|\bPR review\b/i.test(instr);
574
+ },
575
+ impact: 'high',
576
+ rating: 4,
577
+ category: 'trust',
578
+ fix: 'Ensure cloud agent PRs require human review before CI/CD runs.',
579
+ template: null,
580
+ file: () => '.github/workflows/',
581
+ line: () => null,
582
+ },
583
+
584
+ copilotDataUsageOptOut: {
585
+ id: 'CP-C07',
586
+ name: 'Data usage opt-out configured for training-sensitive repos',
587
+ check: (ctx) => {
588
+ const instr = copilotInstructions(ctx) || '';
589
+ const readme = ctx.fileContent('README.md') || '';
590
+ const combined = `${instr}\n${readme}`;
591
+ // Check if there's awareness of data training policy
592
+ if (/\bopt.?out\b.*training|\bdata.*training.*opt|\binteraction data\b/i.test(combined)) return true;
593
+ // If repo looks regulated, it should document this
594
+ const filenames = (ctx.files || []).join('\n');
595
+ const isRegulated = /\bhipaa\b|\bpci\b|\bsoc2\b|\bgdpr\b|\bcompliance\b/i.test(`${filenames}\n${combined}`);
596
+ if (isRegulated) return false;
597
+ return null; // N/A for non-regulated repos
598
+ },
599
+ impact: 'medium',
600
+ rating: 3,
601
+ category: 'trust',
602
+ fix: 'Document data usage training opt-out if required. Since April 24, 2026, interaction data may be used for training on Free/Pro/Pro+ plans.',
603
+ template: null,
604
+ file: () => '.github/copilot-instructions.md',
605
+ line: () => null,
606
+ },
607
+
608
+ copilotNoSecretsInCloudSetup: {
609
+ id: 'CP-C08',
610
+ name: 'No secrets in copilot-setup-steps.yml',
611
+ check: (ctx) => {
612
+ const cloud = cloudAgentContent(ctx);
613
+ if (!cloud) return null;
614
+ return !containsEmbeddedSecret(cloud);
615
+ },
616
+ impact: 'critical',
617
+ rating: 5,
618
+ category: 'trust',
619
+ fix: 'Remove hardcoded secrets from copilot-setup-steps.yml. Use GitHub Actions secrets instead (${{ secrets.* }}).',
620
+ template: null,
621
+ file: () => '.github/workflows/copilot-setup-steps.yml',
622
+ line: (ctx) => {
623
+ const cloud = cloudAgentContent(ctx);
624
+ return cloud ? findSecretLine(cloud) : null;
625
+ },
626
+ },
627
+
628
+ copilotMcpOrgAllowlist: {
629
+ id: 'CP-C09',
630
+ name: 'MCP servers restricted by org allowlist (if Enterprise/Business)',
631
+ check: (ctx) => {
632
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
633
+ if (Object.keys(servers).length === 0) return null;
634
+ // Check instructions for MCP governance mention
635
+ const instr = copilotInstructions(ctx) || '';
636
+ return /\bmcp.*allowlist\b|\bmcp.*registry\b|\bmcp.*approved\b|\borg.*mcp/i.test(instr);
637
+ },
638
+ impact: 'medium',
639
+ rating: 3,
640
+ category: 'trust',
641
+ fix: 'If using Business/Enterprise plan, configure MCP server allowlist in org admin settings.',
642
+ template: null,
643
+ file: () => '.vscode/mcp.json',
644
+ line: () => null,
645
+ },
646
+
647
+ // =============================================
648
+ // D. MCP (5 checks) — CP-D01..CP-D05
649
+ // =============================================
650
+
651
+ copilotMcpConfigured: {
652
+ id: 'CP-D01',
653
+ name: 'MCP servers configured per surface (.vscode/mcp.json)',
654
+ check: (ctx) => {
655
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
656
+ return Object.keys(servers).length > 0;
657
+ },
658
+ impact: 'medium',
659
+ rating: 4,
660
+ category: 'mcp',
661
+ fix: 'Configure MCP servers in .vscode/mcp.json for VS Code agent mode.',
662
+ template: 'copilot-mcp',
663
+ file: () => '.vscode/mcp.json',
664
+ line: () => 1,
665
+ },
666
+
667
+ copilotMcpCloudNoOAuth: {
668
+ id: 'CP-D02',
669
+ name: 'Cloud agent MCP avoids OAuth-required servers (known gap)',
670
+ check: (ctx) => {
671
+ const cloud = cloudAgentContent(ctx);
672
+ if (!cloud) return null;
673
+ const mcpData = mcpJsonData(ctx);
674
+ if (!mcpData) return null;
675
+ const servers = mcpData.servers || mcpData.mcpServers || {};
676
+ // Check if any server has OAuth requirements and cloud references them
677
+ for (const [name, config] of Object.entries(servers)) {
678
+ const configStr = JSON.stringify(config);
679
+ if (/oauth|auth_url|authorization_url/i.test(configStr) && cloud.includes(name)) {
680
+ return false;
681
+ }
682
+ }
683
+ return true;
684
+ },
685
+ impact: 'high',
686
+ rating: 4,
687
+ category: 'mcp',
688
+ fix: 'Remove OAuth-dependent MCP servers from cloud agent config. OAuth is not supported on cloud agent.',
689
+ template: null,
690
+ file: () => '.vscode/mcp.json',
691
+ line: () => null,
692
+ },
693
+
694
+ copilotMcpToolRestrictions: {
695
+ id: 'CP-D03',
696
+ name: 'MCP tool restrictions configured',
697
+ check: (ctx) => {
698
+ const mcpData = mcpJsonData(ctx);
699
+ if (!mcpData) return null;
700
+ const servers = mcpData.servers || mcpData.mcpServers || {};
701
+ if (Object.keys(servers).length === 0) return null;
702
+ // Check if any server has tool restrictions
703
+ return Object.values(servers).some(config =>
704
+ config.tools || config.excludeTools || config.allowedTools
705
+ );
706
+ },
707
+ impact: 'medium',
708
+ rating: 3,
709
+ category: 'mcp',
710
+ fix: 'Add tool restrictions to MCP server configs to limit which tools can be invoked.',
711
+ template: null,
712
+ file: () => '.vscode/mcp.json',
713
+ line: () => null,
714
+ },
715
+
716
+ copilotMcpConsistentAcrossSurfaces: {
717
+ id: 'CP-D04',
718
+ name: 'MCP config consistent across surfaces',
719
+ check: (ctx) => {
720
+ const mcpData = mcpJsonData(ctx);
721
+ if (!mcpData) return null;
722
+ const cloud = cloudAgentContent(ctx);
723
+ if (!cloud) return null;
724
+ // If both VS Code MCP and cloud config exist, check for alignment
725
+ const vsCodeServers = Object.keys(mcpData.servers || mcpData.mcpServers || {});
726
+ if (vsCodeServers.length === 0) return null;
727
+ // Check if cloud setup mentions MCP or server names
728
+ const cloudMentionsMcp = /mcp|server/i.test(cloud);
729
+ return cloudMentionsMcp;
730
+ },
731
+ impact: 'medium',
732
+ rating: 3,
733
+ category: 'mcp',
734
+ fix: 'Ensure MCP server configuration is consistent across VS Code and cloud agent surfaces.',
735
+ template: null,
736
+ file: () => '.vscode/mcp.json',
737
+ line: () => null,
738
+ },
739
+
740
+ copilotMcpAuthDocumented: {
741
+ id: 'CP-D05',
742
+ name: 'MCP auth requirements documented',
743
+ check: (ctx) => {
744
+ const mcpData = mcpJsonData(ctx);
745
+ if (!mcpData) return null;
746
+ const servers = mcpData.servers || mcpData.mcpServers || {};
747
+ if (Object.keys(servers).length === 0) return null;
748
+ // Check if any server has env vars that need to be set
749
+ const hasEnvVars = Object.values(servers).some(config => {
750
+ const configStr = JSON.stringify(config);
751
+ return /\$\{|\benv\b|API_KEY|TOKEN|SECRET/i.test(configStr);
752
+ });
753
+ if (!hasEnvVars) return true;
754
+ // Check if auth is documented
755
+ const instr = copilotInstructions(ctx) || '';
756
+ const readme = ctx.fileContent('README.md') || '';
757
+ return /mcp.*auth|mcp.*key|mcp.*token|mcp.*secret/i.test(`${instr}\n${readme}`);
758
+ },
759
+ impact: 'medium',
760
+ rating: 3,
761
+ category: 'mcp',
762
+ fix: 'Document MCP server authentication requirements (API keys, tokens) in instructions or README.',
763
+ template: null,
764
+ file: () => '.github/copilot-instructions.md',
765
+ line: () => null,
766
+ },
767
+
768
+ // =============================================
769
+ // E. Cloud Agent (5 checks) — CP-E01..CP-E05
770
+ // =============================================
771
+
772
+ copilotCloudDependencyInstall: {
773
+ id: 'CP-E01',
774
+ name: 'copilot-setup-steps.yml has dependency installation',
775
+ check: (ctx) => {
776
+ const cloud = cloudAgentContent(ctx);
777
+ if (!cloud) return null;
778
+ return /npm install|yarn install|pnpm install|pip install|apt-get|brew install|go mod download|cargo build/i.test(cloud);
779
+ },
780
+ impact: 'high',
781
+ rating: 5,
782
+ category: 'cloud-agent',
783
+ fix: 'Add dependency installation steps to copilot-setup-steps.yml.',
784
+ template: 'copilot-cloud-setup',
785
+ file: () => '.github/workflows/copilot-setup-steps.yml',
786
+ line: (ctx) => {
787
+ const cloud = cloudAgentContent(ctx);
788
+ return cloud ? firstLineMatching(cloud, /install/) : null;
789
+ },
790
+ },
791
+
792
+ copilotCloudTestConfigured: {
793
+ id: 'CP-E02',
794
+ name: 'copilot-setup-steps.yml has test command configured',
795
+ check: (ctx) => {
796
+ const cloud = cloudAgentContent(ctx);
797
+ if (!cloud) return null;
798
+ return /npm test|yarn test|pnpm test|pytest|go test|cargo test|make test/i.test(cloud);
799
+ },
800
+ impact: 'high',
801
+ rating: 4,
802
+ category: 'cloud-agent',
803
+ fix: 'Add test command to copilot-setup-steps.yml so cloud agent can verify changes.',
804
+ template: 'copilot-cloud-setup',
805
+ file: () => '.github/workflows/copilot-setup-steps.yml',
806
+ line: (ctx) => {
807
+ const cloud = cloudAgentContent(ctx);
808
+ return cloud ? firstLineMatching(cloud, /test/) : null;
809
+ },
810
+ },
811
+
812
+ copilotCloudSignedCommits: {
813
+ id: 'CP-E03',
814
+ name: 'Cloud agent commits are signed (verified GA April 3, 2026)',
815
+ check: (ctx) => {
816
+ const cloud = cloudAgentContent(ctx);
817
+ if (!cloud) return null;
818
+ // Signed commits are now GA — check if documented
819
+ const instr = copilotInstructions(ctx) || '';
820
+ return /signed commit|commit.*sign|gpg.*sign|verified.*commit/i.test(`${instr}\n${cloud}`);
821
+ },
822
+ impact: 'medium',
823
+ rating: 3,
824
+ category: 'cloud-agent',
825
+ fix: 'Document that cloud agent commits are signed (GA since April 3, 2026).',
826
+ template: null,
827
+ file: () => '.github/workflows/copilot-setup-steps.yml',
828
+ line: () => null,
829
+ },
830
+
831
+ copilotCloudNoUnsafeEnvVars: {
832
+ id: 'CP-E04',
833
+ name: 'No unsafe env vars exposed in setup workflow',
834
+ check: (ctx) => {
835
+ const cloud = cloudAgentContent(ctx);
836
+ if (!cloud) return null;
837
+ // Check for hardcoded secrets or dangerous env patterns
838
+ if (containsEmbeddedSecret(cloud)) return false;
839
+ // Check for env vars that expose secrets without using GitHub secrets syntax
840
+ const lines = cloud.split(/\r?\n/);
841
+ for (const line of lines) {
842
+ if (/^\s*(export\s+)?[A-Z_]+=\S/.test(line) && !/\$\{\{/.test(line)) {
843
+ if (/KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL/i.test(line)) return false;
844
+ }
845
+ }
846
+ return true;
847
+ },
848
+ impact: 'critical',
849
+ rating: 5,
850
+ category: 'cloud-agent',
851
+ fix: 'Use ${{ secrets.* }} for all sensitive env vars in copilot-setup-steps.yml instead of hardcoded values.',
852
+ template: null,
853
+ file: () => '.github/workflows/copilot-setup-steps.yml',
854
+ line: (ctx) => {
855
+ const cloud = cloudAgentContent(ctx);
856
+ return cloud ? findSecretLine(cloud) : null;
857
+ },
858
+ },
859
+
860
+ copilotCloudImplementationPlan: {
861
+ id: 'CP-E05',
862
+ name: 'Implementation plan mode enabled for complex tasks',
863
+ check: (ctx) => {
864
+ const instr = copilotInstructions(ctx) || '';
865
+ if (!instr) return null;
866
+ return /implementation plan|plan mode|step.?by.?step plan|break.*into.*steps/i.test(instr);
867
+ },
868
+ impact: 'medium',
869
+ rating: 3,
870
+ category: 'cloud-agent',
871
+ fix: 'Document implementation plan mode in instructions for complex cloud agent tasks.',
872
+ template: null,
873
+ file: () => '.github/copilot-instructions.md',
874
+ line: (ctx) => {
875
+ const instr = copilotInstructions(ctx);
876
+ return instr ? firstLineMatching(instr, /implementation plan|plan mode/i) : null;
877
+ },
878
+ },
879
+
880
+ // =============================================
881
+ // F. Organization (5 checks) — CP-F01..CP-F05
882
+ // =============================================
883
+
884
+ copilotOrgPoliciesConfigured: {
885
+ id: 'CP-F01',
886
+ name: 'Org policies are configured (if Business/Enterprise)',
887
+ check: (ctx) => {
888
+ // Can't detect org policies from files; check for awareness
889
+ const instr = copilotInstructions(ctx) || '';
890
+ const readme = ctx.fileContent('README.md') || '';
891
+ const hasOrgMention = /\borg.*polic|\borg.*admin|\bbusiness plan|\benterprise/i.test(`${instr}\n${readme}`);
892
+ if (hasOrgMention) return true;
893
+ return null; // N/A if no org signals
894
+ },
895
+ impact: 'medium',
896
+ rating: 3,
897
+ category: 'organization',
898
+ fix: 'If using Business/Enterprise plan, document org-level policies that affect Copilot behavior.',
899
+ template: null,
900
+ file: () => '.github/copilot-instructions.md',
901
+ line: () => null,
902
+ },
903
+
904
+ copilotThirdPartyAgentPolicy: {
905
+ id: 'CP-F02',
906
+ name: 'Third-party agent policy is explicit',
907
+ check: (ctx) => {
908
+ const instr = copilotInstructions(ctx) || '';
909
+ const readme = ctx.fileContent('README.md') || '';
910
+ return /third.?party.*agent|agent.*policy|claude.*copilot|codex.*copilot/i.test(`${instr}\n${readme}`);
911
+ },
912
+ impact: 'medium',
913
+ rating: 3,
914
+ category: 'organization',
915
+ fix: 'Document third-party agent policy (whether Claude, Codex, etc. are allowed within Copilot).',
916
+ template: null,
917
+ file: () => '.github/copilot-instructions.md',
918
+ line: () => null,
919
+ },
920
+
921
+ copilotAuditLogsEnabled: {
922
+ id: 'CP-F03',
923
+ name: 'Audit logs enabled (Enterprise)',
924
+ check: (ctx) => {
925
+ const instr = copilotInstructions(ctx) || '';
926
+ const readme = ctx.fileContent('README.md') || '';
927
+ const combined = `${instr}\n${readme}`;
928
+ if (!/enterprise/i.test(combined)) return null;
929
+ return /\baudit log|\baudit trail/i.test(combined);
930
+ },
931
+ impact: 'medium',
932
+ rating: 3,
933
+ category: 'organization',
934
+ fix: 'Enable and document audit log configuration for Enterprise Copilot usage.',
935
+ template: null,
936
+ file: () => '.github/copilot-instructions.md',
937
+ line: () => null,
938
+ },
939
+
940
+ copilotModelAccessPolicy: {
941
+ id: 'CP-F04',
942
+ name: 'Model access policy matches team needs',
943
+ check: (ctx) => {
944
+ // N/A unless we detect team/org signals
945
+ const instr = copilotInstructions(ctx) || '';
946
+ if (!/\bteam\b|\borg\b|\benterprise\b/i.test(instr)) return null;
947
+ return /model.*access|model.*policy|allowed model/i.test(instr);
948
+ },
949
+ impact: 'low',
950
+ rating: 2,
951
+ category: 'organization',
952
+ fix: 'Document model access policy if specific models need to be enabled or restricted for the team.',
953
+ template: null,
954
+ file: () => '.github/copilot-instructions.md',
955
+ line: () => null,
956
+ },
957
+
958
+ copilotContentExclusionPropagation: {
959
+ id: 'CP-F05',
960
+ name: 'Content exclusion propagation delay documented',
961
+ check: (ctx) => {
962
+ const exclusions = ctx.contentExclusions ? ctx.contentExclusions() : null;
963
+ if (!exclusions) return null;
964
+ const instr = copilotInstructions(ctx) || '';
965
+ return /propagation.*delay|30 minute|exclusion.*delay/i.test(instr);
966
+ },
967
+ impact: 'low',
968
+ rating: 2,
969
+ category: 'organization',
970
+ fix: 'Document that content exclusion changes have up to 30-minute propagation delay.',
971
+ template: null,
972
+ file: () => '.github/copilot-instructions.md',
973
+ line: () => null,
974
+ },
975
+
976
+ // =============================================
977
+ // G. Prompt Files & Templates (4 checks) — CP-G01..CP-G04
978
+ // =============================================
979
+
980
+ copilotPromptDirExists: {
981
+ id: 'CP-G01',
982
+ name: '.github/prompts/ directory exists with reusable templates',
983
+ check: (ctx) => {
984
+ return ctx.hasDir('.github/prompts');
985
+ },
986
+ impact: 'medium',
987
+ rating: 4,
988
+ category: 'prompt-files',
989
+ fix: 'Create .github/prompts/ directory with reusable prompt templates.',
990
+ template: 'copilot-prompts',
991
+ file: () => '.github/prompts/',
992
+ line: () => null,
993
+ },
994
+
995
+ copilotPromptFilesValidFrontmatter: {
996
+ id: 'CP-G02',
997
+ name: 'Prompt files have valid frontmatter (agent, model, tools)',
998
+ check: (ctx) => {
999
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
1000
+ if (prompts.length === 0) return null;
1001
+ return prompts.every(p => p.frontmatter !== null);
1002
+ },
1003
+ impact: 'high',
1004
+ rating: 4,
1005
+ category: 'prompt-files',
1006
+ fix: 'Add YAML frontmatter to all .prompt.md files with at least a description field.',
1007
+ template: null,
1008
+ file: () => '.github/prompts/',
1009
+ line: () => 1,
1010
+ },
1011
+
1012
+ copilotPromptParameterization: {
1013
+ id: 'CP-G03',
1014
+ name: 'Prompt files use ${input:var} for parameterization',
1015
+ check: (ctx) => {
1016
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
1017
+ if (prompts.length === 0) return null;
1018
+ // Not all prompts need params, but check if any use them
1019
+ return prompts.some(p => /\$\{input:/.test(p.body || ''));
1020
+ },
1021
+ impact: 'low',
1022
+ rating: 2,
1023
+ category: 'prompt-files',
1024
+ fix: 'Consider using ${input:variable} in prompt files for dynamic parameterization.',
1025
+ template: null,
1026
+ file: () => '.github/prompts/',
1027
+ line: () => null,
1028
+ },
1029
+
1030
+ copilotNoDuplicatePromptNames: {
1031
+ id: 'CP-G04',
1032
+ name: 'No duplicate prompt names (avoid /name conflicts)',
1033
+ check: (ctx) => {
1034
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
1035
+ if (prompts.length <= 1) return null;
1036
+ const names = prompts.map(p => p.name);
1037
+ return new Set(names).size === names.length;
1038
+ },
1039
+ impact: 'medium',
1040
+ rating: 3,
1041
+ category: 'prompt-files',
1042
+ fix: 'Rename duplicate prompt files to avoid /name conflicts in Copilot Chat.',
1043
+ template: null,
1044
+ file: () => '.github/prompts/',
1045
+ line: () => null,
1046
+ },
1047
+
1048
+ // =============================================
1049
+ // H. Agents & Skills (4 checks) — CP-H01..CP-H04
1050
+ // =============================================
1051
+
1052
+ copilotAgentsMdEnabled: {
1053
+ id: 'CP-H01',
1054
+ name: 'If AGENTS.md exists, verify it is enabled in VS Code settings',
1055
+ check: (ctx) => {
1056
+ const agentsMd = ctx.fileContent('AGENTS.md');
1057
+ if (!agentsMd) return null; // N/A
1058
+ // AGENTS.md support needs explicit enabling
1059
+ const data = vscodeSettingsData(ctx);
1060
+ if (!data) return false;
1061
+ const raw = vscodeSettingsRaw(ctx);
1062
+ return /chat\.agent\.enabled.*true|agent\.enabled.*true/i.test(raw);
1063
+ },
1064
+ impact: 'critical',
1065
+ rating: 5,
1066
+ category: 'skills-agents',
1067
+ fix: 'Enable AGENTS.md support in VS Code settings. It is off by default and silently ignored.',
1068
+ template: 'copilot-vscode-settings',
1069
+ file: () => '.vscode/settings.json',
1070
+ line: (ctx) => {
1071
+ const raw = vscodeSettingsRaw(ctx);
1072
+ return raw ? firstLineMatching(raw, /agent\.enabled/) : null;
1073
+ },
1074
+ },
1075
+
1076
+ copilotExtensionsMode: {
1077
+ id: 'CP-H02',
1078
+ name: 'Extensions are compatible with intended mode (Ask vs Agent)',
1079
+ check: (ctx) => {
1080
+ // Check if instructions mention extensions and clarify mode compatibility
1081
+ const instr = copilotInstructions(ctx) || '';
1082
+ if (!/extension/i.test(instr)) return null;
1083
+ return /extension.*ask mode|extension.*agent mode|ask mode.*extension|agent mode.*extension/i.test(instr);
1084
+ },
1085
+ impact: 'medium',
1086
+ rating: 3,
1087
+ category: 'skills-agents',
1088
+ fix: 'Document that Copilot Extensions only work in Ask mode, not Agent mode.',
1089
+ template: null,
1090
+ file: () => '.github/copilot-instructions.md',
1091
+ line: () => null,
1092
+ },
1093
+
1094
+ copilotSpacesIndexed: {
1095
+ id: 'CP-H03',
1096
+ name: 'Spaces/knowledge bases are indexed for relevant repos',
1097
+ check: (ctx) => {
1098
+ const instr = copilotInstructions(ctx) || '';
1099
+ if (!/space|knowledge base/i.test(instr)) return null;
1100
+ return /space.*index|index.*space|knowledge.*base.*configured/i.test(instr);
1101
+ },
1102
+ impact: 'medium',
1103
+ rating: 3,
1104
+ category: 'skills-agents',
1105
+ fix: 'Configure Copilot Spaces/knowledge bases for relevant repos.',
1106
+ template: null,
1107
+ file: () => '.github/copilot-instructions.md',
1108
+ line: () => null,
1109
+ },
1110
+
1111
+ copilotWorkingSetAppropriate: {
1112
+ id: 'CP-H04',
1113
+ name: 'VS Code agent working set is appropriate for project size',
1114
+ check: (ctx) => {
1115
+ const instr = copilotInstructions(ctx) || '';
1116
+ return /working set|context.*window|file.*limit|token.*limit/i.test(instr);
1117
+ },
1118
+ impact: 'low',
1119
+ rating: 2,
1120
+ category: 'skills-agents',
1121
+ fix: 'Document working set and context management guidance for large projects.',
1122
+ template: null,
1123
+ file: () => '.github/copilot-instructions.md',
1124
+ line: () => null,
1125
+ },
1126
+
1127
+ // =============================================
1128
+ // I. VS Code IDE (4 checks) — CP-I01..CP-I04
1129
+ // =============================================
1130
+
1131
+ copilotAgentModeEnabled: {
1132
+ id: 'CP-I01',
1133
+ name: 'Agent mode enabled in VS Code',
1134
+ check: (ctx) => {
1135
+ const data = vscodeSettingsData(ctx);
1136
+ if (!data) return null;
1137
+ const raw = vscodeSettingsRaw(ctx);
1138
+ return /agent\.enabled.*true|github\.copilot\.chat\.agent\.enabled.*true/i.test(raw);
1139
+ },
1140
+ impact: 'medium',
1141
+ rating: 4,
1142
+ category: 'extensions',
1143
+ fix: 'Enable agent mode in .vscode/settings.json: "github.copilot.chat.agent.enabled": true',
1144
+ template: 'copilot-vscode-settings',
1145
+ file: () => '.vscode/settings.json',
1146
+ line: (ctx) => {
1147
+ const raw = vscodeSettingsRaw(ctx);
1148
+ return raw ? firstLineMatching(raw, /agent\.enabled/) : null;
1149
+ },
1150
+ },
1151
+
1152
+ copilotChatParticipants: {
1153
+ id: 'CP-I02',
1154
+ name: 'Chat participants (@workspace, @terminal) configured',
1155
+ check: (ctx) => {
1156
+ const instr = copilotInstructions(ctx) || '';
1157
+ return /@workspace|@terminal|@vscode|chat participant/i.test(instr);
1158
+ },
1159
+ impact: 'low',
1160
+ rating: 2,
1161
+ category: 'extensions',
1162
+ fix: 'Document available chat participants (@workspace, @terminal) in instructions.',
1163
+ template: null,
1164
+ file: () => '.github/copilot-instructions.md',
1165
+ line: () => null,
1166
+ },
1167
+
1168
+ copilotActiveInstructions: {
1169
+ id: 'CP-I03',
1170
+ name: 'Review, commit, PR instructions are active (not deprecated)',
1171
+ check: (ctx) => {
1172
+ const raw = vscodeSettingsRaw(ctx);
1173
+ if (!raw) return null;
1174
+ // Check if any active instruction keys are used
1175
+ return /reviewSelection\.instructions|commitMessageGeneration\.instructions|pullRequestDescriptionGeneration\.instructions/i.test(raw);
1176
+ },
1177
+ impact: 'medium',
1178
+ rating: 3,
1179
+ category: 'extensions',
1180
+ fix: 'Use active instruction keys (reviewSelection, commitMessageGeneration, pullRequestDescriptionGeneration) instead of deprecated codeGeneration.',
1181
+ template: null,
1182
+ file: () => '.vscode/settings.json',
1183
+ line: (ctx) => {
1184
+ const raw = vscodeSettingsRaw(ctx);
1185
+ return raw ? firstLineMatching(raw, /reviewSelection|commitMessage|pullRequestDescription/) : null;
1186
+ },
1187
+ },
1188
+
1189
+ copilotDevContainerSupport: {
1190
+ id: 'CP-I04',
1191
+ name: 'DevContainer support documented if used',
1192
+ check: (ctx) => {
1193
+ const hasDevContainer = ctx.fileContent('.devcontainer/devcontainer.json') || ctx.hasDir('.devcontainer');
1194
+ if (!hasDevContainer) return null;
1195
+ const instr = copilotInstructions(ctx) || '';
1196
+ return /devcontainer|dev container|codespace/i.test(instr);
1197
+ },
1198
+ impact: 'medium',
1199
+ rating: 3,
1200
+ category: 'extensions',
1201
+ fix: 'Document DevContainer / Codespaces configuration for Copilot usage.',
1202
+ template: null,
1203
+ file: () => '.github/copilot-instructions.md',
1204
+ line: () => null,
1205
+ },
1206
+
1207
+ // =============================================
1208
+ // J. CLI (4 checks) — CP-J01..CP-J04
1209
+ // =============================================
1210
+
1211
+ copilotCliInstalled: {
1212
+ id: 'CP-J01',
1213
+ name: 'gh copilot installed and authenticated',
1214
+ check: (ctx) => {
1215
+ // Can't detect CLI from files; check for CLI documentation
1216
+ const instr = copilotInstructions(ctx) || '';
1217
+ const readme = ctx.fileContent('README.md') || '';
1218
+ return /gh copilot|github copilot cli/i.test(`${instr}\n${readme}`);
1219
+ },
1220
+ impact: 'medium',
1221
+ rating: 3,
1222
+ category: 'ci-automation',
1223
+ fix: 'Document gh copilot CLI setup instructions.',
1224
+ template: null,
1225
+ file: () => '.github/copilot-instructions.md',
1226
+ line: () => null,
1227
+ },
1228
+
1229
+ copilotCliMcp: {
1230
+ id: 'CP-J02',
1231
+ name: 'CLI MCP servers configured',
1232
+ check: (ctx) => {
1233
+ // CLI MCP is local-only; check for documentation
1234
+ const instr = copilotInstructions(ctx) || '';
1235
+ const readme = ctx.fileContent('README.md') || '';
1236
+ if (!/cli.*mcp|mcp.*cli/i.test(`${instr}\n${readme}`)) return null;
1237
+ return true;
1238
+ },
1239
+ impact: 'medium',
1240
+ rating: 3,
1241
+ category: 'ci-automation',
1242
+ fix: 'Document CLI MCP server configuration.',
1243
+ template: null,
1244
+ file: () => '.github/copilot-instructions.md',
1245
+ line: () => null,
1246
+ },
1247
+
1248
+ copilotCliAliases: {
1249
+ id: 'CP-J03',
1250
+ name: 'CLI aliases (ghcs/ghce) set up',
1251
+ check: (ctx) => {
1252
+ const instr = copilotInstructions(ctx) || '';
1253
+ const readme = ctx.fileContent('README.md') || '';
1254
+ return /ghcs|ghce|copilot suggest|copilot explain/i.test(`${instr}\n${readme}`);
1255
+ },
1256
+ impact: 'low',
1257
+ rating: 2,
1258
+ category: 'ci-automation',
1259
+ fix: 'Document CLI aliases (ghcs for suggest, ghce for explain).',
1260
+ template: null,
1261
+ file: () => '.github/copilot-instructions.md',
1262
+ line: () => null,
1263
+ },
1264
+
1265
+ copilotCliAuthToken: {
1266
+ id: 'CP-J04',
1267
+ name: 'CLI auth uses token, not hardcoded credentials',
1268
+ check: (ctx) => {
1269
+ // Check if any file has hardcoded gh auth tokens
1270
+ const files = ['.env', '.env.example', 'copilot-setup-steps.yml'];
1271
+ for (const f of files) {
1272
+ const content = ctx.fileContent(f) || ctx.fileContent(`.github/workflows/${f}`) || '';
1273
+ if (/gh[ps]_[A-Za-z0-9_]{36,}/.test(content)) return false;
1274
+ }
1275
+ return true;
1276
+ },
1277
+ impact: 'high',
1278
+ rating: 5,
1279
+ category: 'ci-automation',
1280
+ fix: 'Use gh auth login or token-based auth instead of hardcoded credentials.',
1281
+ template: null,
1282
+ file: () => '.env',
1283
+ line: () => null,
1284
+ },
1285
+
1286
+ // =============================================
1287
+ // K. Cross-Surface Consistency (5 checks) — CP-K01..CP-K05
1288
+ // =============================================
1289
+
1290
+ copilotCrossSurfaceInstructions: {
1291
+ id: 'CP-K01',
1292
+ name: 'Instructions are consistent across VS Code, cloud, and CLI surfaces',
1293
+ check: (ctx) => {
1294
+ const instr = copilotInstructions(ctx);
1295
+ const cloud = cloudAgentContent(ctx);
1296
+ if (!instr) return null;
1297
+ if (!cloud) return null;
1298
+ // Check that cloud setup references the instructions
1299
+ return /copilot-instructions|instructions/i.test(cloud);
1300
+ },
1301
+ impact: 'high',
1302
+ rating: 4,
1303
+ category: 'quality-deep',
1304
+ fix: 'Ensure instructions are referenced consistently across all Copilot surfaces.',
1305
+ template: null,
1306
+ file: () => '.github/copilot-instructions.md',
1307
+ line: () => null,
1308
+ },
1309
+
1310
+ copilotCrossSurfaceMcp: {
1311
+ id: 'CP-K02',
1312
+ name: 'MCP config is consistent across surfaces',
1313
+ check: (ctx) => {
1314
+ const mcpData = mcpJsonData(ctx);
1315
+ const cloud = cloudAgentContent(ctx);
1316
+ if (!mcpData || !cloud) return null;
1317
+ return /mcp/i.test(cloud);
1318
+ },
1319
+ impact: 'medium',
1320
+ rating: 3,
1321
+ category: 'quality-deep',
1322
+ fix: 'Align MCP server configuration across VS Code and cloud agent surfaces.',
1323
+ template: null,
1324
+ file: () => '.vscode/mcp.json',
1325
+ line: () => null,
1326
+ },
1327
+
1328
+ copilotCrossSurfaceModel: {
1329
+ id: 'CP-K03',
1330
+ name: 'Model preferences are aligned across surfaces',
1331
+ check: (ctx) => {
1332
+ const prompts = ctx.promptFiles ? ctx.promptFiles() : [];
1333
+ const models = new Set();
1334
+ for (const p of prompts) {
1335
+ if (p.frontmatter && p.frontmatter.model) {
1336
+ models.add(p.frontmatter.model);
1337
+ }
1338
+ }
1339
+ if (models.size <= 1) return null;
1340
+ // Multiple different models in prompt files — flag for review
1341
+ return models.size <= 2; // Allow up to 2 different models
1342
+ },
1343
+ impact: 'medium',
1344
+ rating: 3,
1345
+ category: 'quality-deep',
1346
+ fix: 'Align model preferences across prompt files and surfaces for consistent behavior.',
1347
+ template: null,
1348
+ file: () => '.github/prompts/',
1349
+ line: () => null,
1350
+ },
1351
+
1352
+ copilotCrossSurfaceSecurity: {
1353
+ id: 'CP-K04',
1354
+ name: 'Security posture is consistent (no surface has weaker controls)',
1355
+ check: (ctx) => {
1356
+ const hasSandbox = getCopilotSetting(ctx, 'chat.tools.terminal.sandbox.enabled') === true;
1357
+ const cloud = cloudAgentContent(ctx);
1358
+ const instr = copilotInstructions(ctx) || '';
1359
+ // If VS Code is sandboxed, check cloud and CLI awareness
1360
+ if (hasSandbox && cloud) {
1361
+ return /security|review.*required|PR.*gate/i.test(cloud);
1362
+ }
1363
+ return null;
1364
+ },
1365
+ impact: 'high',
1366
+ rating: 4,
1367
+ category: 'quality-deep',
1368
+ fix: 'Ensure no Copilot surface has weaker security controls than others.',
1369
+ template: null,
1370
+ file: () => '.github/copilot-instructions.md',
1371
+ line: () => null,
1372
+ },
1373
+
1374
+ copilotCrossSurfaceExclusions: {
1375
+ id: 'CP-K05',
1376
+ name: 'Content exclusions applied at org level (not just repo)',
1377
+ check: (ctx) => {
1378
+ const exclusions = ctx.contentExclusions ? ctx.contentExclusions() : null;
1379
+ if (!exclusions) return null;
1380
+ const instr = copilotInstructions(ctx) || '';
1381
+ return /org.*exclu|exclu.*org|organization.*content/i.test(instr);
1382
+ },
1383
+ impact: 'medium',
1384
+ rating: 3,
1385
+ category: 'quality-deep',
1386
+ fix: 'Apply content exclusions at org level for consistent enforcement across repos.',
1387
+ template: null,
1388
+ file: () => '.github/copilot-instructions.md',
1389
+ line: () => null,
1390
+ },
1391
+
1392
+ // =============================================
1393
+ // L. Enterprise & Governance (5 checks) — CP-L01..CP-L05
1394
+ // =============================================
1395
+
1396
+ copilotBYOKConfigured: {
1397
+ id: 'CP-L01',
1398
+ name: 'BYOK (custom model provider) is configured correctly',
1399
+ check: (ctx) => {
1400
+ const instr = copilotInstructions(ctx) || '';
1401
+ if (!/byok|bring your own|custom model|custom provider/i.test(instr)) return null;
1402
+ return /byok.*configured|custom.*model.*set/i.test(instr);
1403
+ },
1404
+ impact: 'medium',
1405
+ rating: 3,
1406
+ category: 'enterprise',
1407
+ fix: 'Document BYOK (custom model provider) configuration if used.',
1408
+ template: null,
1409
+ file: () => '.github/copilot-instructions.md',
1410
+ line: () => null,
1411
+ },
1412
+
1413
+ copilotFineTunedModelScoped: {
1414
+ id: 'CP-L02',
1415
+ name: 'Fine-tuned model access is scoped to appropriate repos',
1416
+ check: (ctx) => {
1417
+ const instr = copilotInstructions(ctx) || '';
1418
+ if (!/fine.?tune|custom.*model/i.test(instr)) return null;
1419
+ return /scope|restrict|appropriate.*repo/i.test(instr);
1420
+ },
1421
+ impact: 'high',
1422
+ rating: 4,
1423
+ category: 'enterprise',
1424
+ fix: 'Scope fine-tuned model access to appropriate repos only.',
1425
+ template: null,
1426
+ file: () => '.github/copilot-instructions.md',
1427
+ line: () => null,
1428
+ },
1429
+
1430
+ copilotAuditRetention: {
1431
+ id: 'CP-L03',
1432
+ name: 'Audit log retention meets compliance requirements',
1433
+ check: (ctx) => {
1434
+ const instr = copilotInstructions(ctx) || '';
1435
+ if (!/audit.*log|audit.*trail/i.test(instr)) return null;
1436
+ return /retention|compliance|retention.*day|retention.*month/i.test(instr);
1437
+ },
1438
+ impact: 'medium',
1439
+ rating: 3,
1440
+ category: 'enterprise',
1441
+ fix: 'Document audit log retention policy for compliance.',
1442
+ template: null,
1443
+ file: () => '.github/copilot-instructions.md',
1444
+ line: () => null,
1445
+ },
1446
+
1447
+ copilotMcpRegistryAllowlist: {
1448
+ id: 'CP-L04',
1449
+ name: 'MCP registry allowlist is maintained',
1450
+ check: (ctx) => {
1451
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1452
+ if (Object.keys(servers).length === 0) return null;
1453
+ const instr = copilotInstructions(ctx) || '';
1454
+ return /mcp.*allowlist|mcp.*registry|approved.*mcp/i.test(instr);
1455
+ },
1456
+ impact: 'high',
1457
+ rating: 4,
1458
+ category: 'enterprise',
1459
+ fix: 'Maintain an MCP registry allowlist for governance.',
1460
+ template: null,
1461
+ file: () => '.github/copilot-instructions.md',
1462
+ line: () => null,
1463
+ },
1464
+
1465
+ copilotThirdPartyAgentGoverned: {
1466
+ id: 'CP-L05',
1467
+ name: 'Third-party agent usage is explicitly governed',
1468
+ check: (ctx) => {
1469
+ const instr = copilotInstructions(ctx) || '';
1470
+ return /third.?party.*agent.*governed|agent.*governance|governed.*agent/i.test(instr);
1471
+ },
1472
+ impact: 'medium',
1473
+ rating: 3,
1474
+ category: 'enterprise',
1475
+ fix: 'Document governance rules for third-party agent usage within Copilot.',
1476
+ template: null,
1477
+ file: () => '.github/copilot-instructions.md',
1478
+ line: () => null,
1479
+ },
1480
+
1481
+ // =============================================
1482
+ // M. Quality Deep (6 checks) — CP-M01..CP-M06
1483
+ // =============================================
1484
+
1485
+ copilotModernFeatures: {
1486
+ id: 'CP-M01',
1487
+ name: 'Instructions mention modern Copilot features (prompt files, Spaces, agent mode)',
1488
+ check: (ctx) => {
1489
+ const instr = copilotInstructions(ctx) || '';
1490
+ if (!instr) return null;
1491
+ return /\bprompt file|\bspace|\bagent mode|\b\.prompt\.md/i.test(instr);
1492
+ },
1493
+ impact: 'medium',
1494
+ rating: 3,
1495
+ category: 'quality-deep',
1496
+ fix: 'Reference modern Copilot features (prompt files, Spaces, agent mode) in instructions.',
1497
+ template: null,
1498
+ file: () => '.github/copilot-instructions.md',
1499
+ line: () => null,
1500
+ },
1501
+
1502
+ copilotNoDeprecatedReferences: {
1503
+ id: 'CP-M02',
1504
+ name: 'No references to deprecated features (knowledge bases, codeGeneration.instructions)',
1505
+ check: (ctx) => {
1506
+ const instr = copilotInstructions(ctx) || '';
1507
+ if (!instr) return null;
1508
+ return !/codeGeneration\.instructions|knowledge base.*deprecated/i.test(instr);
1509
+ },
1510
+ impact: 'medium',
1511
+ rating: 3,
1512
+ category: 'quality-deep',
1513
+ fix: 'Remove references to deprecated features from instructions.',
1514
+ template: null,
1515
+ file: () => '.github/copilot-instructions.md',
1516
+ line: (ctx) => {
1517
+ const instr = copilotInstructions(ctx);
1518
+ return instr ? firstLineMatching(instr, /codeGeneration\.instructions/) : null;
1519
+ },
1520
+ },
1521
+
1522
+ copilotColdBootAwareness: {
1523
+ id: 'CP-M03',
1524
+ name: 'Cloud agent cold-boot awareness documented',
1525
+ check: (ctx) => {
1526
+ const cloud = cloudAgentContent(ctx);
1527
+ if (!cloud) return null;
1528
+ const instr = copilotInstructions(ctx) || '';
1529
+ return /cold.?boot|90 second|startup.*delay|initialization.*time/i.test(`${instr}\n${cloud}`);
1530
+ },
1531
+ impact: 'low',
1532
+ rating: 2,
1533
+ category: 'quality-deep',
1534
+ fix: 'Document cloud agent cold-boot time (~90 seconds) and mitigation strategies.',
1535
+ template: null,
1536
+ file: () => '.github/copilot-instructions.md',
1537
+ line: () => null,
1538
+ },
1539
+
1540
+ copilotBillingAwareness: {
1541
+ id: 'CP-M04',
1542
+ name: 'Rate limit / premium billing awareness documented',
1543
+ check: (ctx) => {
1544
+ const instr = copilotInstructions(ctx) || '';
1545
+ if (!instr) return null;
1546
+ return /rate limit|billing|premium|usage limit|token limit/i.test(instr);
1547
+ },
1548
+ impact: 'medium',
1549
+ rating: 3,
1550
+ category: 'quality-deep',
1551
+ fix: 'Document rate limits, premium billing, and usage awareness.',
1552
+ template: null,
1553
+ file: () => '.github/copilot-instructions.md',
1554
+ line: () => null,
1555
+ },
1556
+
1557
+ copilotReviewCharLimit: {
1558
+ id: 'CP-M05',
1559
+ name: 'Instructions tailored for code review (within 4,000 char limit)',
1560
+ check: (ctx) => {
1561
+ const raw = vscodeSettingsRaw(ctx);
1562
+ if (!raw) return null;
1563
+ if (!raw.includes('reviewSelection')) return null;
1564
+ // Check if review instructions exist and are within char limit
1565
+ const reviewInstr = getCopilotSetting(ctx, 'github.copilot.chat.reviewSelection.instructions');
1566
+ if (!reviewInstr) return null;
1567
+ const content = Array.isArray(reviewInstr)
1568
+ ? reviewInstr.map(i => typeof i === 'string' ? i : (i.text || '')).join('\n')
1569
+ : String(reviewInstr);
1570
+ return content.length <= 4000;
1571
+ },
1572
+ impact: 'medium',
1573
+ rating: 3,
1574
+ category: 'quality-deep',
1575
+ fix: 'Keep code review instructions within the 4,000 character limit.',
1576
+ template: null,
1577
+ file: () => '.vscode/settings.json',
1578
+ line: (ctx) => {
1579
+ const raw = vscodeSettingsRaw(ctx);
1580
+ return raw ? firstLineMatching(raw, /reviewSelection/) : null;
1581
+ },
1582
+ },
1583
+
1584
+ copilotInstructionDuplication: {
1585
+ id: 'CP-M06',
1586
+ name: 'Cross-surface instruction duplication minimized',
1587
+ check: (ctx) => {
1588
+ const instr = copilotInstructions(ctx) || '';
1589
+ const cloud = cloudAgentContent(ctx) || '';
1590
+ if (!instr || !cloud) return null;
1591
+ // Simple check: count common substantial lines
1592
+ const instrLines = instr.split(/\r?\n/).filter(l => l.trim().length > 30);
1593
+ const cloudLines = new Set(cloud.split(/\r?\n/).filter(l => l.trim().length > 30));
1594
+ let dupes = 0;
1595
+ for (const line of instrLines) {
1596
+ if (cloudLines.has(line.trim())) dupes++;
1597
+ }
1598
+ return dupes < 5;
1599
+ },
1600
+ impact: 'low',
1601
+ rating: 2,
1602
+ category: 'quality-deep',
1603
+ fix: 'Minimize instruction duplication across surfaces. Use copilot-instructions.md as single source of truth.',
1604
+ template: null,
1605
+ file: () => '.github/copilot-instructions.md',
1606
+ line: () => null,
1607
+ },
1608
+
1609
+ // =============================================
1610
+ // N. Advisory (4 checks) — CP-N01..CP-N04
1611
+ // =============================================
1612
+
1613
+ copilotAdvisoryInstructionQuality: {
1614
+ id: 'CP-N01',
1615
+ name: 'Instruction quality score meets advisory threshold',
1616
+ check: (ctx) => {
1617
+ const content = copilotInstructions(ctx);
1618
+ if (!content) return null;
1619
+ const lines = content.split(/\r?\n/).filter(l => l.trim()).length;
1620
+ const sections = countSections(content);
1621
+ const hasArch = hasArchitecture(content);
1622
+ const hasVerify = /\bverif|\btest|\blint|\bbuild/i.test(content);
1623
+ const score = (lines >= 30 ? 2 : lines >= 15 ? 1 : 0) +
1624
+ (sections >= 4 ? 2 : sections >= 2 ? 1 : 0) +
1625
+ (hasArch ? 1 : 0) +
1626
+ (hasVerify ? 1 : 0);
1627
+ return score >= 4;
1628
+ },
1629
+ impact: 'medium',
1630
+ rating: 4,
1631
+ category: 'advisory',
1632
+ fix: 'Improve instruction quality: add more sections, architecture diagram, and verification commands.',
1633
+ template: 'copilot-instructions',
1634
+ file: () => '.github/copilot-instructions.md',
1635
+ line: () => 1,
1636
+ },
1637
+
1638
+ copilotAdvisorySecurityPosture: {
1639
+ id: 'CP-N02',
1640
+ name: 'Security posture meets advisory threshold',
1641
+ check: (ctx) => {
1642
+ let score = 0;
1643
+ if (getCopilotSetting(ctx, 'chat.tools.terminal.sandbox.enabled') === true) score++;
1644
+ if (ctx.contentExclusions && ctx.contentExclusions()) score++;
1645
+ const autoApproval = getCopilotSetting(ctx, 'chat.agent.autoApproval.terminalCommands');
1646
+ if (!autoApproval || (Array.isArray(autoApproval) && !autoApproval.includes('*'))) score++;
1647
+ const instr = copilotInstructions(ctx) || '';
1648
+ if (/security|secret|credential/i.test(instr)) score++;
1649
+ return score >= 2;
1650
+ },
1651
+ impact: 'high',
1652
+ rating: 5,
1653
+ category: 'advisory',
1654
+ fix: 'Improve security posture: enable sandbox, configure exclusions, restrict auto-approval.',
1655
+ template: null,
1656
+ file: () => '.vscode/settings.json',
1657
+ line: () => null,
1658
+ },
1659
+
1660
+ copilotAdvisorySurfaceCoverage: {
1661
+ id: 'CP-N03',
1662
+ name: 'Multi-surface coverage meets advisory threshold',
1663
+ check: (ctx) => {
1664
+ const surfaces = ctx.detectSurfaces ? ctx.detectSurfaces() : {};
1665
+ let configured = 0;
1666
+ if (surfaces.vscode) configured++;
1667
+ if (surfaces.cloudAgent) configured++;
1668
+ // At least VS Code surface should be configured
1669
+ return configured >= 1;
1670
+ },
1671
+ impact: 'medium',
1672
+ rating: 4,
1673
+ category: 'advisory',
1674
+ fix: 'Configure at least VS Code surface. Add cloud agent setup for full coverage.',
1675
+ template: null,
1676
+ file: () => '.vscode/settings.json',
1677
+ line: () => null,
1678
+ },
1679
+
1680
+ copilotAdvisoryMcpHealth: {
1681
+ id: 'CP-N04',
1682
+ name: 'MCP configuration health meets advisory threshold',
1683
+ check: (ctx) => {
1684
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1685
+ const count = Object.keys(servers).length;
1686
+ if (count === 0) return null;
1687
+ // Check that MCP config is valid JSON and servers have required fields
1688
+ const mcpResult = ctx.mcpConfig();
1689
+ return mcpResult && mcpResult.ok;
1690
+ },
1691
+ impact: 'medium',
1692
+ rating: 3,
1693
+ category: 'advisory',
1694
+ fix: 'Ensure MCP configuration is valid and servers are properly configured.',
1695
+ template: null,
1696
+ file: () => '.vscode/mcp.json',
1697
+ line: () => null,
1698
+ },
1699
+
1700
+ // =============================================
1701
+ // O. Pack (4 checks) — CP-O01..CP-O04
1702
+ // =============================================
1703
+
1704
+ copilotPackDomainDetected: {
1705
+ id: 'CP-O01',
1706
+ name: 'Domain pack detection returns relevant results',
1707
+ check: (ctx) => {
1708
+ // Always passes if we can detect stacks
1709
+ const pkg = ctx.jsonFile ? ctx.jsonFile('package.json') : null;
1710
+ return Boolean(pkg || ctx.fileContent('go.mod') || ctx.fileContent('Cargo.toml') || ctx.fileContent('pyproject.toml'));
1711
+ },
1712
+ impact: 'low',
1713
+ rating: 2,
1714
+ category: 'advisory',
1715
+ fix: 'Ensure project has identifiable stack markers for domain pack detection.',
1716
+ template: null,
1717
+ file: () => 'package.json',
1718
+ line: () => null,
1719
+ },
1720
+
1721
+ copilotPackMcpRecommended: {
1722
+ id: 'CP-O02',
1723
+ name: 'MCP packs recommended based on project signals',
1724
+ check: (ctx) => {
1725
+ const servers = ctx.mcpServers ? ctx.mcpServers() : {};
1726
+ return Object.keys(servers).length > 0;
1727
+ },
1728
+ impact: 'low',
1729
+ rating: 2,
1730
+ category: 'advisory',
1731
+ fix: 'Add recommended MCP packs to .vscode/mcp.json based on project domain.',
1732
+ template: 'copilot-mcp',
1733
+ file: () => '.vscode/mcp.json',
1734
+ line: () => null,
1735
+ },
1736
+
1737
+ copilotPackGovernanceApplied: {
1738
+ id: 'CP-O03',
1739
+ name: 'Governance pack applied if enterprise signals detected',
1740
+ check: (ctx) => {
1741
+ const instr = copilotInstructions(ctx) || '';
1742
+ if (!/enterprise|business/i.test(instr)) return null;
1743
+ return /governance|policy|audit/i.test(instr);
1744
+ },
1745
+ impact: 'medium',
1746
+ rating: 3,
1747
+ category: 'advisory',
1748
+ fix: 'Apply governance pack for enterprise repos.',
1749
+ template: null,
1750
+ file: () => '.github/copilot-instructions.md',
1751
+ line: () => null,
1752
+ },
1753
+
1754
+ copilotPackConsistency: {
1755
+ id: 'CP-O04',
1756
+ name: 'All applied packs are consistent with each other',
1757
+ check: (ctx) => {
1758
+ // Check that instructions and settings don't contradict
1759
+ const instr = copilotInstructions(ctx) || '';
1760
+ const raw = vscodeSettingsRaw(ctx);
1761
+ if (!instr || !raw) return null;
1762
+ // No contradiction: if instructions say "strict" and settings say "yolo"
1763
+ const instrStrict = /\bstrict\b|\blocked.?down\b|\bno auto/i.test(instr);
1764
+ const settingsPermissive = /autoApproval.*\*|yolo/i.test(raw);
1765
+ return !(instrStrict && settingsPermissive);
1766
+ },
1767
+ impact: 'medium',
1768
+ rating: 3,
1769
+ category: 'advisory',
1770
+ fix: 'Resolve contradictions between instruction guidance and settings configuration.',
1771
+ template: null,
1772
+ file: () => '.vscode/settings.json',
1773
+ line: () => null,
1774
+ },
1775
+
1776
+ // =============================================
1777
+ // P. Repeat (3 checks) — CP-P01..CP-P03
1778
+ // =============================================
1779
+
1780
+ copilotRepeatScoreImproved: {
1781
+ id: 'CP-P01',
1782
+ name: 'Audit score improved since last run',
1783
+ check: () => null, // Requires snapshot history — always N/A in static check
1784
+ impact: 'low',
1785
+ rating: 2,
1786
+ category: 'freshness',
1787
+ fix: 'Run audits regularly and track score improvement over time.',
1788
+ template: null,
1789
+ file: () => null,
1790
+ line: () => null,
1791
+ },
1792
+
1793
+ copilotRepeatNoRegressions: {
1794
+ id: 'CP-P02',
1795
+ name: 'No regressions since last audit',
1796
+ check: () => null, // Requires snapshot history
1797
+ impact: 'medium',
1798
+ rating: 3,
1799
+ category: 'freshness',
1800
+ fix: 'Review and fix any regressions detected since the last audit.',
1801
+ template: null,
1802
+ file: () => null,
1803
+ line: () => null,
1804
+ },
1805
+
1806
+ copilotRepeatFeedbackLoop: {
1807
+ id: 'CP-P03',
1808
+ name: 'Feedback loop active for recommendations',
1809
+ check: () => null, // Requires feedback data
1810
+ impact: 'low',
1811
+ rating: 2,
1812
+ category: 'freshness',
1813
+ fix: 'Use `npx nerviq --platform copilot feedback` to rate recommendations.',
1814
+ template: null,
1815
+ file: () => null,
1816
+ line: () => null,
1817
+ },
1818
+ };
1819
+
1820
+ module.exports = {
1821
+ COPILOT_TECHNIQUES,
1822
+ };