@nerviq/cli 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +1 -0
- package/package.json +2 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +6 -2
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +312 -67
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +10 -4
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +120 -10
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +155 -33
- package/src/workspace.js +375 -375
package/src/copilot/activity.js
CHANGED
|
@@ -1,309 +1,309 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copilot Repeat-Usage Surfaces
|
|
3
|
-
*
|
|
4
|
-
* Adapts the shared activity/snapshot backend for Copilot platform.
|
|
5
|
-
* Provides: history, compare, trend, feedback, insights.
|
|
6
|
-
*
|
|
7
|
-
* 6 repeat-usage surfaces filtered by platform='copilot':
|
|
8
|
-
* 1. History — audit snapshot history
|
|
9
|
-
* 2. Compare — latest vs previous snapshot
|
|
10
|
-
* 3. Trend — score trend over time
|
|
11
|
-
* 4. Feedback — recommendation outcome tracking
|
|
12
|
-
* 5. Insights — pattern detection from history
|
|
13
|
-
* 6. Surface tracking — per-surface (VS Code/cloud/CLI) progress
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const {
|
|
18
|
-
readSnapshotIndex,
|
|
19
|
-
recordRecommendationOutcome,
|
|
20
|
-
readOutcomeIndex,
|
|
21
|
-
summarizeOutcomeEntries,
|
|
22
|
-
} = require('../activity');
|
|
23
|
-
const { version } = require('../../package.json');
|
|
24
|
-
|
|
25
|
-
// --- History ---
|
|
26
|
-
|
|
27
|
-
function getCopilotHistory(dir, limit = 20) {
|
|
28
|
-
const entries = readSnapshotIndex(dir);
|
|
29
|
-
return entries
|
|
30
|
-
.filter(e => e.snapshotKind === 'audit' && (e.platform === 'copilot' || e.summary?.platform === 'copilot'))
|
|
31
|
-
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
32
|
-
.slice(0, limit);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatCopilotHistory(dir) {
|
|
36
|
-
const history = getCopilotHistory(dir, 10);
|
|
37
|
-
if (history.length === 0) {
|
|
38
|
-
return 'No Copilot snapshots found. Run `npx nerviq --platform copilot --snapshot` to save one.';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const lines = ['Copilot Score History (most recent first):', ''];
|
|
42
|
-
for (const entry of history) {
|
|
43
|
-
const date = entry.createdAt?.split('T')[0] || 'unknown';
|
|
44
|
-
const score = entry.summary?.score ?? '?';
|
|
45
|
-
const passed = entry.summary?.passed ?? '?';
|
|
46
|
-
const total = entry.summary?.checkCount ?? '?';
|
|
47
|
-
const surfaces = entry.summary?.surfaces || {};
|
|
48
|
-
const surfaceStr = [
|
|
49
|
-
surfaces.vscode ? 'VS Code' : null,
|
|
50
|
-
surfaces.cloudAgent ? 'Cloud' : null,
|
|
51
|
-
surfaces.cli ? 'CLI' : null,
|
|
52
|
-
].filter(Boolean).join('+') || 'unknown';
|
|
53
|
-
lines.push(` ${date} ${score}/100 (${passed}/${total} passing) [${surfaceStr}]`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const comparison = compareCopilotLatest(dir);
|
|
57
|
-
if (comparison) {
|
|
58
|
-
lines.push('');
|
|
59
|
-
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
60
|
-
lines.push(` Trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous)`);
|
|
61
|
-
if (comparison.improvements.length > 0) lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
62
|
-
if (comparison.regressions.length > 0) lines.push(` New gaps: ${comparison.regressions.join(', ')}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return lines.join('\n');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// --- Compare ---
|
|
69
|
-
|
|
70
|
-
function compareCopilotLatest(dir) {
|
|
71
|
-
const audits = getCopilotHistory(dir, 2);
|
|
72
|
-
if (audits.length < 2) return null;
|
|
73
|
-
|
|
74
|
-
const current = audits[0];
|
|
75
|
-
const previous = audits[1];
|
|
76
|
-
|
|
77
|
-
const delta = {
|
|
78
|
-
score: (current.summary?.score || 0) - (previous.summary?.score || 0),
|
|
79
|
-
organic: (current.summary?.organicScore || 0) - (previous.summary?.organicScore || 0),
|
|
80
|
-
passed: (current.summary?.passed || 0) - (previous.summary?.passed || 0),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const regressions = [];
|
|
84
|
-
const improvements = [];
|
|
85
|
-
const prevKeys = new Set(previous.summary?.topActionKeys || []);
|
|
86
|
-
const currKeys = new Set(current.summary?.topActionKeys || []);
|
|
87
|
-
|
|
88
|
-
for (const key of currKeys) { if (!prevKeys.has(key)) regressions.push(key); }
|
|
89
|
-
for (const key of prevKeys) { if (!currKeys.has(key)) improvements.push(key); }
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
platform: 'copilot',
|
|
93
|
-
current: { date: current.createdAt, score: current.summary?.score, passed: current.summary?.passed },
|
|
94
|
-
previous: { date: previous.createdAt, score: previous.summary?.score, passed: previous.summary?.passed },
|
|
95
|
-
delta,
|
|
96
|
-
regressions,
|
|
97
|
-
improvements,
|
|
98
|
-
trend: delta.score > 0 ? 'improving' : delta.score < 0 ? 'regressing' : 'stable',
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// --- Trend ---
|
|
103
|
-
|
|
104
|
-
function exportCopilotTrendReport(dir) {
|
|
105
|
-
const history = getCopilotHistory(dir, 50);
|
|
106
|
-
if (history.length === 0) return null;
|
|
107
|
-
|
|
108
|
-
const comparison = compareCopilotLatest(dir);
|
|
109
|
-
const lines = [
|
|
110
|
-
'# GitHub Copilot Setup Trend Report',
|
|
111
|
-
'',
|
|
112
|
-
`**Project:** ${path.basename(dir)}`,
|
|
113
|
-
`**Platform:** GitHub Copilot`,
|
|
114
|
-
`**Generated:** ${new Date().toISOString().split('T')[0]}`,
|
|
115
|
-
`**Snapshots:** ${history.length}`,
|
|
116
|
-
'',
|
|
117
|
-
'## Score History',
|
|
118
|
-
'',
|
|
119
|
-
'| Date | Score | Passed | Checks | Surfaces |',
|
|
120
|
-
'|------|-------|--------|--------|----------|',
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
for (const entry of history) {
|
|
124
|
-
const date = entry.createdAt?.split('T')[0] || '?';
|
|
125
|
-
const surfaces = entry.summary?.surfaces || {};
|
|
126
|
-
const surfaceStr = [surfaces.vscode ? 'VS' : null, surfaces.cloudAgent ? 'Cloud' : null, surfaces.cli ? 'CLI' : null].filter(Boolean).join('+') || '?';
|
|
127
|
-
lines.push(`| ${date} | ${entry.summary?.score ?? '?'}/100 | ${entry.summary?.passed ?? '?'} | ${entry.summary?.checkCount ?? '?'} | ${surfaceStr} |`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (comparison) {
|
|
131
|
-
lines.push('', '## Latest Comparison', '');
|
|
132
|
-
lines.push(`- **Previous:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
133
|
-
lines.push(`- **Current:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
134
|
-
lines.push(`- **Delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
135
|
-
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
136
|
-
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
137
|
-
if (comparison.regressions.length > 0) lines.push(`- **New gaps:** ${comparison.regressions.join(', ')}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (history.length >= 3) {
|
|
141
|
-
lines.push('', '## Trend Chart', '', '```');
|
|
142
|
-
const scores = history.slice().reverse().map(e => e.summary?.score ?? 0);
|
|
143
|
-
const max = Math.max(...scores, 100);
|
|
144
|
-
const chartHeight = 10;
|
|
145
|
-
for (let row = chartHeight; row >= 0; row--) {
|
|
146
|
-
const threshold = (row / chartHeight) * max;
|
|
147
|
-
const rowLabel = String(Math.round(threshold)).padStart(3);
|
|
148
|
-
const bar = scores.map(s => s >= threshold ? '#' : ' ').join('');
|
|
149
|
-
lines.push(`${rowLabel} |${bar}`);
|
|
150
|
-
}
|
|
151
|
-
lines.push(` +${'─'.repeat(scores.length)}`);
|
|
152
|
-
lines.push('```');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
lines.push('', '---', `*Generated by nerviq v${version} for GitHub Copilot*`);
|
|
156
|
-
return lines.join('\n');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// --- Feedback ---
|
|
160
|
-
|
|
161
|
-
function recordCopilotFeedback(dir, payload) {
|
|
162
|
-
return recordRecommendationOutcome(dir, {
|
|
163
|
-
...payload,
|
|
164
|
-
source: payload.source || 'copilot',
|
|
165
|
-
platform: 'copilot',
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function getCopilotFeedbackSummary(dir) {
|
|
170
|
-
const entries = readOutcomeIndex(dir)
|
|
171
|
-
.filter(e => e.source === 'copilot' || e.platform === 'copilot');
|
|
172
|
-
return summarizeOutcomeEntries(entries);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function formatCopilotFeedback(dir) {
|
|
176
|
-
const summary = getCopilotFeedbackSummary(dir);
|
|
177
|
-
if (!summary || Object.keys(summary).length === 0) {
|
|
178
|
-
return 'No Copilot feedback recorded yet. Use `npx nerviq --platform copilot feedback` to rate recommendations.';
|
|
179
|
-
}
|
|
180
|
-
const lines = ['Copilot Recommendation Feedback:', ''];
|
|
181
|
-
const entries = Array.isArray(summary) ? summary : Object.values(summary);
|
|
182
|
-
for (const entry of entries) {
|
|
183
|
-
lines.push(` ${entry.key || 'unknown'}: ${entry.accepted || 0} accepted, ${entry.rejected || 0} rejected (${entry.total || 0} total)`);
|
|
184
|
-
}
|
|
185
|
-
return lines.join('\n');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// --- Insights ---
|
|
189
|
-
|
|
190
|
-
function generateCopilotInsights(dir) {
|
|
191
|
-
const history = getCopilotHistory(dir, 50);
|
|
192
|
-
const feedback = getCopilotFeedbackSummary(dir);
|
|
193
|
-
const insights = [];
|
|
194
|
-
|
|
195
|
-
// Pattern 1: Persistent failures
|
|
196
|
-
if (history.length >= 3) {
|
|
197
|
-
const recentFailKeys = new Map();
|
|
198
|
-
for (const entry of history.slice(0, 5)) {
|
|
199
|
-
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
200
|
-
recentFailKeys.set(key, (recentFailKeys.get(key) || 0) + 1);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
for (const [key, count] of recentFailKeys) {
|
|
204
|
-
if (count >= 3) {
|
|
205
|
-
insights.push({ type: 'persistent-failure', severity: 'high', key, message: `Check ${key} has failed in ${count} of the last ${Math.min(history.length, 5)} audits.` });
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Pattern 2: Score regression
|
|
211
|
-
if (history.length >= 2) {
|
|
212
|
-
const scores = history.map(e => e.summary?.score ?? 0);
|
|
213
|
-
if (scores[0] < scores[1]) {
|
|
214
|
-
insights.push({ type: 'regression-pattern', severity: 'medium', message: `Score dropped from ${scores[1]} to ${scores[0]} in the most recent audit.`, delta: scores[0] - scores[1] });
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Pattern 3: Velocity stall
|
|
219
|
-
if (history.length >= 5) {
|
|
220
|
-
const recentScores = history.slice(0, 5).map(e => e.summary?.score ?? 0);
|
|
221
|
-
const range = Math.max(...recentScores) - Math.min(...recentScores);
|
|
222
|
-
if (range <= 2) {
|
|
223
|
-
insights.push({ type: 'velocity-stall', severity: 'low', message: `Score flat (range: ${range}) over last 5 audits.` });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Copilot-specific Pattern 4: Surface coverage stagnation
|
|
228
|
-
if (history.length >= 3) {
|
|
229
|
-
const surfaceKeys = [];
|
|
230
|
-
for (const entry of history.slice(0, 5)) {
|
|
231
|
-
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
232
|
-
if (key.includes('cloud') || key.includes('surface') || key.includes('CLI')) {
|
|
233
|
-
surfaceKeys.push(key);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (surfaceKeys.length >= 2) {
|
|
238
|
-
insights.push({ type: 'surface-coverage-gap', severity: 'medium', message: `Surface coverage checks have appeared in ${surfaceKeys.length} recent audits. Consider configuring additional Copilot surfaces.`, keys: [...new Set(surfaceKeys)] });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Copilot-specific Pattern 5: Content exclusion gap persistence
|
|
243
|
-
if (history.length >= 3) {
|
|
244
|
-
const exclusionKeys = new Map();
|
|
245
|
-
for (const entry of history.slice(0, 5)) {
|
|
246
|
-
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
247
|
-
if (key.includes('exclusion') || key.includes('content')) {
|
|
248
|
-
exclusionKeys.set(key, (exclusionKeys.get(key) || 0) + 1);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
for (const [key, count] of exclusionKeys) {
|
|
253
|
-
if (count >= 3) {
|
|
254
|
-
insights.push({ type: 'content-exclusion-gap', severity: 'high', key, message: `Content exclusion check ${key} has persisted across ${count} audits. The cloud agent content exclusion gap may need team-level documentation.` });
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Copilot-specific Pattern 6: Windows sandbox awareness
|
|
260
|
-
if (history.length >= 2) {
|
|
261
|
-
const latest = history[0];
|
|
262
|
-
const windowsKeys = (latest.summary?.topActionKeys || []).filter(k => k.includes('windows') || k.includes('sandbox'));
|
|
263
|
-
if (windowsKeys.length >= 1) {
|
|
264
|
-
insights.push({ type: 'windows-sandbox-gap', severity: 'medium', message: `Windows sandbox-related checks failing. Terminal sandbox is unavailable on native Windows.`, keys: windowsKeys });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Feedback signals
|
|
269
|
-
const feedbackEntries = Array.isArray(feedback) ? feedback : Object.values(feedback || {});
|
|
270
|
-
for (const entry of feedbackEntries) {
|
|
271
|
-
if (entry.rejected > entry.accepted && entry.total >= 2) {
|
|
272
|
-
insights.push({ type: 'feedback-signal', severity: 'medium', key: entry.key, message: `Recommendation ${entry.key} has been rejected more than accepted (${entry.rejected}/${entry.total}).` });
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
platform: 'copilot',
|
|
278
|
-
generatedAt: new Date().toISOString(),
|
|
279
|
-
snapshotCount: history.length,
|
|
280
|
-
feedbackCount: feedbackEntries.length,
|
|
281
|
-
insights,
|
|
282
|
-
summary: insights.length === 0
|
|
283
|
-
? 'No actionable insights detected. Keep running audits to build pattern data.'
|
|
284
|
-
: `${insights.length} insight(s) detected across ${history.length} snapshots.`,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function formatCopilotInsights(dir) {
|
|
289
|
-
const result = generateCopilotInsights(dir);
|
|
290
|
-
if (result.insights.length === 0) return result.summary;
|
|
291
|
-
const lines = ['Copilot Insights:', ''];
|
|
292
|
-
for (const insight of result.insights) {
|
|
293
|
-
lines.push(` [${insight.severity.toUpperCase()}] ${insight.message}`);
|
|
294
|
-
}
|
|
295
|
-
lines.push('', result.summary);
|
|
296
|
-
return lines.join('\n');
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
module.exports = {
|
|
300
|
-
getCopilotHistory,
|
|
301
|
-
formatCopilotHistory,
|
|
302
|
-
compareCopilotLatest,
|
|
303
|
-
exportCopilotTrendReport,
|
|
304
|
-
recordCopilotFeedback,
|
|
305
|
-
getCopilotFeedbackSummary,
|
|
306
|
-
formatCopilotFeedback,
|
|
307
|
-
generateCopilotInsights,
|
|
308
|
-
formatCopilotInsights,
|
|
309
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Copilot Repeat-Usage Surfaces
|
|
3
|
+
*
|
|
4
|
+
* Adapts the shared activity/snapshot backend for Copilot platform.
|
|
5
|
+
* Provides: history, compare, trend, feedback, insights.
|
|
6
|
+
*
|
|
7
|
+
* 6 repeat-usage surfaces filtered by platform='copilot':
|
|
8
|
+
* 1. History — audit snapshot history
|
|
9
|
+
* 2. Compare — latest vs previous snapshot
|
|
10
|
+
* 3. Trend — score trend over time
|
|
11
|
+
* 4. Feedback — recommendation outcome tracking
|
|
12
|
+
* 5. Insights — pattern detection from history
|
|
13
|
+
* 6. Surface tracking — per-surface (VS Code/cloud/CLI) progress
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const {
|
|
18
|
+
readSnapshotIndex,
|
|
19
|
+
recordRecommendationOutcome,
|
|
20
|
+
readOutcomeIndex,
|
|
21
|
+
summarizeOutcomeEntries,
|
|
22
|
+
} = require('../activity');
|
|
23
|
+
const { version } = require('../../package.json');
|
|
24
|
+
|
|
25
|
+
// --- History ---
|
|
26
|
+
|
|
27
|
+
function getCopilotHistory(dir, limit = 20) {
|
|
28
|
+
const entries = readSnapshotIndex(dir);
|
|
29
|
+
return entries
|
|
30
|
+
.filter(e => e.snapshotKind === 'audit' && (e.platform === 'copilot' || e.summary?.platform === 'copilot'))
|
|
31
|
+
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
|
32
|
+
.slice(0, limit);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatCopilotHistory(dir) {
|
|
36
|
+
const history = getCopilotHistory(dir, 10);
|
|
37
|
+
if (history.length === 0) {
|
|
38
|
+
return 'No Copilot snapshots found. Run `npx nerviq --platform copilot --snapshot` to save one.';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const lines = ['Copilot Score History (most recent first):', ''];
|
|
42
|
+
for (const entry of history) {
|
|
43
|
+
const date = entry.createdAt?.split('T')[0] || 'unknown';
|
|
44
|
+
const score = entry.summary?.score ?? '?';
|
|
45
|
+
const passed = entry.summary?.passed ?? '?';
|
|
46
|
+
const total = entry.summary?.checkCount ?? '?';
|
|
47
|
+
const surfaces = entry.summary?.surfaces || {};
|
|
48
|
+
const surfaceStr = [
|
|
49
|
+
surfaces.vscode ? 'VS Code' : null,
|
|
50
|
+
surfaces.cloudAgent ? 'Cloud' : null,
|
|
51
|
+
surfaces.cli ? 'CLI' : null,
|
|
52
|
+
].filter(Boolean).join('+') || 'unknown';
|
|
53
|
+
lines.push(` ${date} ${score}/100 (${passed}/${total} passing) [${surfaceStr}]`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const comparison = compareCopilotLatest(dir);
|
|
57
|
+
if (comparison) {
|
|
58
|
+
lines.push('');
|
|
59
|
+
const sign = comparison.delta.score >= 0 ? '+' : '';
|
|
60
|
+
lines.push(` Trend: ${comparison.trend} (${sign}${comparison.delta.score} since previous)`);
|
|
61
|
+
if (comparison.improvements.length > 0) lines.push(` Fixed: ${comparison.improvements.join(', ')}`);
|
|
62
|
+
if (comparison.regressions.length > 0) lines.push(` New gaps: ${comparison.regressions.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- Compare ---
|
|
69
|
+
|
|
70
|
+
function compareCopilotLatest(dir) {
|
|
71
|
+
const audits = getCopilotHistory(dir, 2);
|
|
72
|
+
if (audits.length < 2) return null;
|
|
73
|
+
|
|
74
|
+
const current = audits[0];
|
|
75
|
+
const previous = audits[1];
|
|
76
|
+
|
|
77
|
+
const delta = {
|
|
78
|
+
score: (current.summary?.score || 0) - (previous.summary?.score || 0),
|
|
79
|
+
organic: (current.summary?.organicScore || 0) - (previous.summary?.organicScore || 0),
|
|
80
|
+
passed: (current.summary?.passed || 0) - (previous.summary?.passed || 0),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const regressions = [];
|
|
84
|
+
const improvements = [];
|
|
85
|
+
const prevKeys = new Set(previous.summary?.topActionKeys || []);
|
|
86
|
+
const currKeys = new Set(current.summary?.topActionKeys || []);
|
|
87
|
+
|
|
88
|
+
for (const key of currKeys) { if (!prevKeys.has(key)) regressions.push(key); }
|
|
89
|
+
for (const key of prevKeys) { if (!currKeys.has(key)) improvements.push(key); }
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
platform: 'copilot',
|
|
93
|
+
current: { date: current.createdAt, score: current.summary?.score, passed: current.summary?.passed },
|
|
94
|
+
previous: { date: previous.createdAt, score: previous.summary?.score, passed: previous.summary?.passed },
|
|
95
|
+
delta,
|
|
96
|
+
regressions,
|
|
97
|
+
improvements,
|
|
98
|
+
trend: delta.score > 0 ? 'improving' : delta.score < 0 ? 'regressing' : 'stable',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Trend ---
|
|
103
|
+
|
|
104
|
+
function exportCopilotTrendReport(dir) {
|
|
105
|
+
const history = getCopilotHistory(dir, 50);
|
|
106
|
+
if (history.length === 0) return null;
|
|
107
|
+
|
|
108
|
+
const comparison = compareCopilotLatest(dir);
|
|
109
|
+
const lines = [
|
|
110
|
+
'# GitHub Copilot Setup Trend Report',
|
|
111
|
+
'',
|
|
112
|
+
`**Project:** ${path.basename(dir)}`,
|
|
113
|
+
`**Platform:** GitHub Copilot`,
|
|
114
|
+
`**Generated:** ${new Date().toISOString().split('T')[0]}`,
|
|
115
|
+
`**Snapshots:** ${history.length}`,
|
|
116
|
+
'',
|
|
117
|
+
'## Score History',
|
|
118
|
+
'',
|
|
119
|
+
'| Date | Score | Passed | Checks | Surfaces |',
|
|
120
|
+
'|------|-------|--------|--------|----------|',
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
for (const entry of history) {
|
|
124
|
+
const date = entry.createdAt?.split('T')[0] || '?';
|
|
125
|
+
const surfaces = entry.summary?.surfaces || {};
|
|
126
|
+
const surfaceStr = [surfaces.vscode ? 'VS' : null, surfaces.cloudAgent ? 'Cloud' : null, surfaces.cli ? 'CLI' : null].filter(Boolean).join('+') || '?';
|
|
127
|
+
lines.push(`| ${date} | ${entry.summary?.score ?? '?'}/100 | ${entry.summary?.passed ?? '?'} | ${entry.summary?.checkCount ?? '?'} | ${surfaceStr} |`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (comparison) {
|
|
131
|
+
lines.push('', '## Latest Comparison', '');
|
|
132
|
+
lines.push(`- **Previous:** ${comparison.previous.score}/100 (${comparison.previous.date?.split('T')[0]})`);
|
|
133
|
+
lines.push(`- **Current:** ${comparison.current.score}/100 (${comparison.current.date?.split('T')[0]})`);
|
|
134
|
+
lines.push(`- **Delta:** ${comparison.delta.score >= 0 ? '+' : ''}${comparison.delta.score} points`);
|
|
135
|
+
lines.push(`- **Trend:** ${comparison.trend}`);
|
|
136
|
+
if (comparison.improvements.length > 0) lines.push(`- **Fixed:** ${comparison.improvements.join(', ')}`);
|
|
137
|
+
if (comparison.regressions.length > 0) lines.push(`- **New gaps:** ${comparison.regressions.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (history.length >= 3) {
|
|
141
|
+
lines.push('', '## Trend Chart', '', '```');
|
|
142
|
+
const scores = history.slice().reverse().map(e => e.summary?.score ?? 0);
|
|
143
|
+
const max = Math.max(...scores, 100);
|
|
144
|
+
const chartHeight = 10;
|
|
145
|
+
for (let row = chartHeight; row >= 0; row--) {
|
|
146
|
+
const threshold = (row / chartHeight) * max;
|
|
147
|
+
const rowLabel = String(Math.round(threshold)).padStart(3);
|
|
148
|
+
const bar = scores.map(s => s >= threshold ? '#' : ' ').join('');
|
|
149
|
+
lines.push(`${rowLabel} |${bar}`);
|
|
150
|
+
}
|
|
151
|
+
lines.push(` +${'─'.repeat(scores.length)}`);
|
|
152
|
+
lines.push('```');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
lines.push('', '---', `*Generated by nerviq v${version} for GitHub Copilot*`);
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Feedback ---
|
|
160
|
+
|
|
161
|
+
function recordCopilotFeedback(dir, payload) {
|
|
162
|
+
return recordRecommendationOutcome(dir, {
|
|
163
|
+
...payload,
|
|
164
|
+
source: payload.source || 'copilot',
|
|
165
|
+
platform: 'copilot',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getCopilotFeedbackSummary(dir) {
|
|
170
|
+
const entries = readOutcomeIndex(dir)
|
|
171
|
+
.filter(e => e.source === 'copilot' || e.platform === 'copilot');
|
|
172
|
+
return summarizeOutcomeEntries(entries);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function formatCopilotFeedback(dir) {
|
|
176
|
+
const summary = getCopilotFeedbackSummary(dir);
|
|
177
|
+
if (!summary || Object.keys(summary).length === 0) {
|
|
178
|
+
return 'No Copilot feedback recorded yet. Use `npx nerviq --platform copilot feedback` to rate recommendations.';
|
|
179
|
+
}
|
|
180
|
+
const lines = ['Copilot Recommendation Feedback:', ''];
|
|
181
|
+
const entries = Array.isArray(summary) ? summary : Object.values(summary);
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
lines.push(` ${entry.key || 'unknown'}: ${entry.accepted || 0} accepted, ${entry.rejected || 0} rejected (${entry.total || 0} total)`);
|
|
184
|
+
}
|
|
185
|
+
return lines.join('\n');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// --- Insights ---
|
|
189
|
+
|
|
190
|
+
function generateCopilotInsights(dir) {
|
|
191
|
+
const history = getCopilotHistory(dir, 50);
|
|
192
|
+
const feedback = getCopilotFeedbackSummary(dir);
|
|
193
|
+
const insights = [];
|
|
194
|
+
|
|
195
|
+
// Pattern 1: Persistent failures
|
|
196
|
+
if (history.length >= 3) {
|
|
197
|
+
const recentFailKeys = new Map();
|
|
198
|
+
for (const entry of history.slice(0, 5)) {
|
|
199
|
+
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
200
|
+
recentFailKeys.set(key, (recentFailKeys.get(key) || 0) + 1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const [key, count] of recentFailKeys) {
|
|
204
|
+
if (count >= 3) {
|
|
205
|
+
insights.push({ type: 'persistent-failure', severity: 'high', key, message: `Check ${key} has failed in ${count} of the last ${Math.min(history.length, 5)} audits.` });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Pattern 2: Score regression
|
|
211
|
+
if (history.length >= 2) {
|
|
212
|
+
const scores = history.map(e => e.summary?.score ?? 0);
|
|
213
|
+
if (scores[0] < scores[1]) {
|
|
214
|
+
insights.push({ type: 'regression-pattern', severity: 'medium', message: `Score dropped from ${scores[1]} to ${scores[0]} in the most recent audit.`, delta: scores[0] - scores[1] });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Pattern 3: Velocity stall
|
|
219
|
+
if (history.length >= 5) {
|
|
220
|
+
const recentScores = history.slice(0, 5).map(e => e.summary?.score ?? 0);
|
|
221
|
+
const range = Math.max(...recentScores) - Math.min(...recentScores);
|
|
222
|
+
if (range <= 2) {
|
|
223
|
+
insights.push({ type: 'velocity-stall', severity: 'low', message: `Score flat (range: ${range}) over last 5 audits.` });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Copilot-specific Pattern 4: Surface coverage stagnation
|
|
228
|
+
if (history.length >= 3) {
|
|
229
|
+
const surfaceKeys = [];
|
|
230
|
+
for (const entry of history.slice(0, 5)) {
|
|
231
|
+
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
232
|
+
if (key.includes('cloud') || key.includes('surface') || key.includes('CLI')) {
|
|
233
|
+
surfaceKeys.push(key);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (surfaceKeys.length >= 2) {
|
|
238
|
+
insights.push({ type: 'surface-coverage-gap', severity: 'medium', message: `Surface coverage checks have appeared in ${surfaceKeys.length} recent audits. Consider configuring additional Copilot surfaces.`, keys: [...new Set(surfaceKeys)] });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Copilot-specific Pattern 5: Content exclusion gap persistence
|
|
243
|
+
if (history.length >= 3) {
|
|
244
|
+
const exclusionKeys = new Map();
|
|
245
|
+
for (const entry of history.slice(0, 5)) {
|
|
246
|
+
for (const key of (entry.summary?.topActionKeys || [])) {
|
|
247
|
+
if (key.includes('exclusion') || key.includes('content')) {
|
|
248
|
+
exclusionKeys.set(key, (exclusionKeys.get(key) || 0) + 1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const [key, count] of exclusionKeys) {
|
|
253
|
+
if (count >= 3) {
|
|
254
|
+
insights.push({ type: 'content-exclusion-gap', severity: 'high', key, message: `Content exclusion check ${key} has persisted across ${count} audits. The cloud agent content exclusion gap may need team-level documentation.` });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Copilot-specific Pattern 6: Windows sandbox awareness
|
|
260
|
+
if (history.length >= 2) {
|
|
261
|
+
const latest = history[0];
|
|
262
|
+
const windowsKeys = (latest.summary?.topActionKeys || []).filter(k => k.includes('windows') || k.includes('sandbox'));
|
|
263
|
+
if (windowsKeys.length >= 1) {
|
|
264
|
+
insights.push({ type: 'windows-sandbox-gap', severity: 'medium', message: `Windows sandbox-related checks failing. Terminal sandbox is unavailable on native Windows.`, keys: windowsKeys });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Feedback signals
|
|
269
|
+
const feedbackEntries = Array.isArray(feedback) ? feedback : Object.values(feedback || {});
|
|
270
|
+
for (const entry of feedbackEntries) {
|
|
271
|
+
if (entry.rejected > entry.accepted && entry.total >= 2) {
|
|
272
|
+
insights.push({ type: 'feedback-signal', severity: 'medium', key: entry.key, message: `Recommendation ${entry.key} has been rejected more than accepted (${entry.rejected}/${entry.total}).` });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
platform: 'copilot',
|
|
278
|
+
generatedAt: new Date().toISOString(),
|
|
279
|
+
snapshotCount: history.length,
|
|
280
|
+
feedbackCount: feedbackEntries.length,
|
|
281
|
+
insights,
|
|
282
|
+
summary: insights.length === 0
|
|
283
|
+
? 'No actionable insights detected. Keep running audits to build pattern data.'
|
|
284
|
+
: `${insights.length} insight(s) detected across ${history.length} snapshots.`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function formatCopilotInsights(dir) {
|
|
289
|
+
const result = generateCopilotInsights(dir);
|
|
290
|
+
if (result.insights.length === 0) return result.summary;
|
|
291
|
+
const lines = ['Copilot Insights:', ''];
|
|
292
|
+
for (const insight of result.insights) {
|
|
293
|
+
lines.push(` [${insight.severity.toUpperCase()}] ${insight.message}`);
|
|
294
|
+
}
|
|
295
|
+
lines.push('', result.summary);
|
|
296
|
+
return lines.join('\n');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = {
|
|
300
|
+
getCopilotHistory,
|
|
301
|
+
formatCopilotHistory,
|
|
302
|
+
compareCopilotLatest,
|
|
303
|
+
exportCopilotTrendReport,
|
|
304
|
+
recordCopilotFeedback,
|
|
305
|
+
getCopilotFeedbackSummary,
|
|
306
|
+
formatCopilotFeedback,
|
|
307
|
+
generateCopilotInsights,
|
|
308
|
+
formatCopilotInsights,
|
|
309
|
+
};
|