@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.
- package/CHANGELOG.md +181 -0
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/bin/cli.js +749 -0
- package/content/case-study-template.md +91 -0
- package/content/claims-governance.md +37 -0
- package/content/claude-code/audit-repo/SKILL.md +20 -0
- package/content/claude-native-integration.md +60 -0
- package/content/devto-article.json +9 -0
- package/content/launch-posts.md +226 -0
- package/content/pilot-rollout-kit.md +30 -0
- package/content/release-checklist.md +31 -0
- package/package.json +53 -4
- package/src/activity.js +529 -0
- package/src/aider/activity.js +226 -0
- package/src/aider/config-parser.js +166 -0
- package/src/aider/context.js +158 -0
- package/src/aider/deep-review.js +316 -0
- package/src/aider/domain-packs.js +278 -0
- package/src/aider/freshness.js +168 -0
- package/src/aider/governance.js +253 -0
- package/src/aider/interactive.js +334 -0
- package/src/aider/mcp-packs.js +98 -0
- package/src/aider/patch.js +214 -0
- package/src/aider/plans.js +186 -0
- package/src/aider/premium.js +360 -0
- package/src/aider/setup.js +404 -0
- package/src/aider/techniques.js +1323 -0
- package/src/analyze.js +821 -0
- package/src/audit.js +1003 -0
- package/src/badge.js +13 -0
- package/src/benchmark.js +339 -0
- package/src/claudex-sync.json +7 -0
- package/src/codex/activity.js +324 -0
- package/src/codex/config-parser.js +183 -0
- package/src/codex/context.js +221 -0
- package/src/codex/deep-review.js +493 -0
- package/src/codex/domain-packs.js +372 -0
- package/src/codex/freshness.js +167 -0
- package/src/codex/governance.js +192 -0
- package/src/codex/interactive.js +618 -0
- package/src/codex/mcp-packs.js +660 -0
- package/src/codex/patch.js +209 -0
- package/src/codex/plans.js +251 -0
- package/src/codex/premium.js +614 -0
- package/src/codex/setup.js +603 -0
- package/src/codex/techniques.js +2649 -0
- package/src/context.js +272 -0
- package/src/copilot/activity.js +309 -0
- package/src/copilot/config-parser.js +226 -0
- package/src/copilot/context.js +197 -0
- package/src/copilot/deep-review.js +346 -0
- package/src/copilot/domain-packs.js +350 -0
- package/src/copilot/freshness.js +197 -0
- package/src/copilot/governance.js +222 -0
- package/src/copilot/interactive.js +406 -0
- package/src/copilot/mcp-packs.js +572 -0
- package/src/copilot/patch.js +238 -0
- package/src/copilot/plans.js +253 -0
- package/src/copilot/premium.js +450 -0
- package/src/copilot/setup.js +488 -0
- package/src/copilot/techniques.js +1822 -0
- package/src/cursor/activity.js +301 -0
- package/src/cursor/config-parser.js +265 -0
- package/src/cursor/context.js +236 -0
- package/src/cursor/deep-review.js +334 -0
- package/src/cursor/domain-packs.js +346 -0
- package/src/cursor/freshness.js +214 -0
- package/src/cursor/governance.js +229 -0
- package/src/cursor/interactive.js +391 -0
- package/src/cursor/mcp-packs.js +571 -0
- package/src/cursor/patch.js +243 -0
- package/src/cursor/plans.js +254 -0
- package/src/cursor/premium.js +468 -0
- package/src/cursor/setup.js +488 -0
- package/src/cursor/techniques.js +1786 -0
- package/src/deep-review.js +345 -0
- package/src/domain-packs.js +364 -0
- package/src/formatters/sarif.js +115 -0
- package/src/gemini/activity.js +402 -0
- package/src/gemini/config-parser.js +275 -0
- package/src/gemini/context.js +221 -0
- package/src/gemini/deep-review.js +559 -0
- package/src/gemini/domain-packs.js +371 -0
- package/src/gemini/freshness.js +204 -0
- package/src/gemini/governance.js +201 -0
- package/src/gemini/interactive.js +860 -0
- package/src/gemini/mcp-packs.js +658 -0
- package/src/gemini/patch.js +229 -0
- package/src/gemini/plans.js +269 -0
- package/src/gemini/premium.js +759 -0
- package/src/gemini/setup.js +692 -0
- package/src/gemini/techniques.js +2084 -0
- package/src/governance.js +523 -0
- package/src/harmony/advisor.js +383 -0
- package/src/harmony/audit.js +303 -0
- package/src/harmony/canon.js +444 -0
- package/src/harmony/cli.js +331 -0
- package/src/harmony/drift.js +401 -0
- package/src/harmony/governance.js +313 -0
- package/src/harmony/memory.js +238 -0
- package/src/harmony/sync.js +458 -0
- package/src/harmony/watch.js +336 -0
- package/src/index.js +256 -0
- package/src/insights.js +119 -0
- package/src/interactive.js +118 -0
- package/src/mcp-packs.js +597 -0
- package/src/opencode/activity.js +286 -0
- package/src/opencode/config-parser.js +109 -0
- package/src/opencode/context.js +247 -0
- package/src/opencode/deep-review.js +313 -0
- package/src/opencode/domain-packs.js +240 -0
- package/src/opencode/freshness.js +158 -0
- package/src/opencode/governance.js +159 -0
- package/src/opencode/interactive.js +392 -0
- package/src/opencode/mcp-packs.js +474 -0
- package/src/opencode/patch.js +184 -0
- package/src/opencode/plans.js +231 -0
- package/src/opencode/premium.js +413 -0
- package/src/opencode/setup.js +449 -0
- package/src/opencode/techniques.js +1713 -0
- package/src/plans.js +655 -0
- package/src/secret-patterns.js +30 -0
- package/src/setup.js +1274 -0
- package/src/synergy/adaptive.js +261 -0
- package/src/synergy/compensation.js +156 -0
- package/src/synergy/evidence.js +193 -0
- package/src/synergy/learning.js +184 -0
- package/src/synergy/patterns.js +227 -0
- package/src/synergy/ranking.js +83 -0
- package/src/synergy/report.js +163 -0
- package/src/synergy/routing.js +152 -0
- package/src/techniques.js +1354 -0
- package/src/watch.js +229 -0
- package/src/windsurf/activity.js +302 -0
- package/src/windsurf/config-parser.js +267 -0
- package/src/windsurf/context.js +249 -0
- package/src/windsurf/deep-review.js +337 -0
- package/src/windsurf/domain-packs.js +348 -0
- package/src/windsurf/freshness.js +215 -0
- package/src/windsurf/governance.js +231 -0
- package/src/windsurf/interactive.js +388 -0
- package/src/windsurf/mcp-packs.js +535 -0
- package/src/windsurf/patch.js +231 -0
- package/src/windsurf/plans.js +247 -0
- package/src/windsurf/premium.js +467 -0
- package/src/windsurf/setup.js +471 -0
- package/src/windsurf/techniques.js +1758 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Premium Operator UX
|
|
3
|
+
*
|
|
4
|
+
* Three subsystems:
|
|
5
|
+
* 1. Multi-Pack Composition Engine — merge domain + MCP + policy + extension packs with dedup, conflict resolution, dependency ordering
|
|
6
|
+
* 2. CI Template Library — 5 GitHub Actions workflow templates for Gemini CLI automation (using run-gemini-cli action)
|
|
7
|
+
* 3. Adoption Signal Gate — activate features based on local usage telemetry (5 gates)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { GEMINI_DOMAIN_PACKS } = require('./domain-packs');
|
|
12
|
+
const { GEMINI_MCP_PACKS } = require('./mcp-packs');
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// 1. Multi-Pack Composition Engine
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/** Pack dependency order — earlier = more foundational */
|
|
19
|
+
const PACK_DEPENDENCY_ORDER = [
|
|
20
|
+
'baseline-general',
|
|
21
|
+
'backend-api',
|
|
22
|
+
'frontend-ui',
|
|
23
|
+
'infra-platform',
|
|
24
|
+
'monorepo',
|
|
25
|
+
'enterprise-governed',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Specificity rank: higher = more specific, wins conflicts */
|
|
29
|
+
const PACK_SPECIFICITY = {
|
|
30
|
+
'baseline-general': 0,
|
|
31
|
+
'backend-api': 2,
|
|
32
|
+
'frontend-ui': 2,
|
|
33
|
+
'infra-platform': 3,
|
|
34
|
+
'monorepo': 3,
|
|
35
|
+
'enterprise-governed': 4,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Policy pack definitions — Gemini-unique */
|
|
39
|
+
const POLICY_PACKS = [
|
|
40
|
+
{
|
|
41
|
+
key: 'policy-file-restrictions',
|
|
42
|
+
label: 'File Restrictions',
|
|
43
|
+
description: 'Protect sensitive files from unreviewed edits.',
|
|
44
|
+
policyContent: {
|
|
45
|
+
'file-restrictions': {
|
|
46
|
+
deny_edit: ['.env', '.env.*', '*.pem', '*.key', 'credentials.*'],
|
|
47
|
+
deny_delete: ['.env', '.env.*', '*.pem', '*.key', '*.lock'],
|
|
48
|
+
read_only_dirs: ['.git', 'node_modules', '.gemini/policy'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: 'policy-tool-restrictions',
|
|
54
|
+
label: 'Tool Restrictions',
|
|
55
|
+
description: 'Limit which tools the agent can invoke without approval.',
|
|
56
|
+
policyContent: {
|
|
57
|
+
'tool-restrictions': {
|
|
58
|
+
deny_tools: ['shell_exec_unsafe', 'network_raw'],
|
|
59
|
+
require_approval: ['file_delete', 'git_push', 'deploy'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: 'policy-governance',
|
|
65
|
+
label: 'Governance',
|
|
66
|
+
description: 'Strict audit and escalation policies for enterprise use.',
|
|
67
|
+
policyContent: {
|
|
68
|
+
governance: {
|
|
69
|
+
audit_trail: true,
|
|
70
|
+
escalation_on_deny: true,
|
|
71
|
+
max_auto_edits_per_session: 50,
|
|
72
|
+
require_justification_for: ['security-override', 'policy-bypass'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** Extension pack definitions — Gemini-unique */
|
|
79
|
+
const EXTENSION_PACKS = [
|
|
80
|
+
{
|
|
81
|
+
key: 'ext-code-review',
|
|
82
|
+
label: 'Code Review Extension',
|
|
83
|
+
description: 'Structured code review with severity ratings and inline suggestions.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: 'ext-test-gen',
|
|
87
|
+
label: 'Test Generation Extension',
|
|
88
|
+
description: 'Auto-generate test stubs for new or modified files.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: 'ext-docs-sync',
|
|
92
|
+
label: 'Docs Sync Extension',
|
|
93
|
+
description: 'Detect stale documentation and suggest updates.',
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const DEFAULT_SIZE_BUDGET = 16000; // characters for combined instruction content
|
|
98
|
+
|
|
99
|
+
function lookupDomainPack(key) {
|
|
100
|
+
return GEMINI_DOMAIN_PACKS.find(p => p.key === key) || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lookupMcpPack(key) {
|
|
104
|
+
return GEMINI_MCP_PACKS.find(p => p.key === key) || null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function lookupPolicyPack(key) {
|
|
108
|
+
return POLICY_PACKS.find(p => p.key === key) || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function lookupExtensionPack(key) {
|
|
112
|
+
return EXTENSION_PACKS.find(p => p.key === key) || null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compose domain packs, MCP packs, policy packs, and extension packs into a unified,
|
|
117
|
+
* deduplicated, ordered result.
|
|
118
|
+
*
|
|
119
|
+
* @param {string[]} domainPackKeys - Array of domain pack keys to compose
|
|
120
|
+
* @param {string[]} mcpPackKeys - Array of MCP pack keys to compose
|
|
121
|
+
* @param {object} [options]
|
|
122
|
+
* @param {number} [options.sizeBudget] - Max characters for combined instructions (default 16000)
|
|
123
|
+
* @param {string[]} [options.policyPackKeys] - Array of policy pack keys (Gemini-unique)
|
|
124
|
+
* @param {string[]} [options.extensionPackKeys] - Array of extension pack keys (Gemini-unique)
|
|
125
|
+
* @returns {object} Composition report
|
|
126
|
+
*/
|
|
127
|
+
function composePacks(domainPackKeys = [], mcpPackKeys = [], options = {}) {
|
|
128
|
+
const sizeBudget = options.sizeBudget || DEFAULT_SIZE_BUDGET;
|
|
129
|
+
const policyPackKeys = options.policyPackKeys || [];
|
|
130
|
+
const extensionPackKeys = options.extensionPackKeys || [];
|
|
131
|
+
const warnings = [];
|
|
132
|
+
|
|
133
|
+
// --- Resolve domain packs ---
|
|
134
|
+
const seenDomainKeys = new Set();
|
|
135
|
+
const rawDomainPacks = [];
|
|
136
|
+
for (const key of domainPackKeys) {
|
|
137
|
+
if (seenDomainKeys.has(key)) continue;
|
|
138
|
+
seenDomainKeys.add(key);
|
|
139
|
+
const pack = lookupDomainPack(key);
|
|
140
|
+
if (!pack) {
|
|
141
|
+
warnings.push(`Domain pack "${key}" not found, skipped.`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
rawDomainPacks.push(pack);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Order by dependency (base -> framework -> tool-specific)
|
|
148
|
+
rawDomainPacks.sort((a, b) => {
|
|
149
|
+
const orderA = PACK_DEPENDENCY_ORDER.indexOf(a.key);
|
|
150
|
+
const orderB = PACK_DEPENDENCY_ORDER.indexOf(b.key);
|
|
151
|
+
return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Deduplicate overlapping recommendedModules; more specific pack wins
|
|
155
|
+
const moduleOwner = new Map(); // module -> { key, specificity }
|
|
156
|
+
for (const pack of rawDomainPacks) {
|
|
157
|
+
const specificity = PACK_SPECIFICITY[pack.key] ?? 1;
|
|
158
|
+
for (const mod of pack.recommendedModules || []) {
|
|
159
|
+
const existing = moduleOwner.get(mod);
|
|
160
|
+
if (!existing || specificity > existing.specificity) {
|
|
161
|
+
moduleOwner.set(mod, { key: pack.key, specificity });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Deduplicate surfaces
|
|
167
|
+
const allSurfaces = new Set();
|
|
168
|
+
for (const pack of rawDomainPacks) {
|
|
169
|
+
for (const surface of pack.recommendedSurfaces || []) {
|
|
170
|
+
allSurfaces.add(surface);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Deduplicate proposal families
|
|
175
|
+
const allProposalFamilies = new Set();
|
|
176
|
+
for (const pack of rawDomainPacks) {
|
|
177
|
+
for (const family of pack.recommendedProposalFamilies || []) {
|
|
178
|
+
allProposalFamilies.add(family);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- Resolve MCP packs ---
|
|
183
|
+
const seenMcpKeys = new Set();
|
|
184
|
+
const resolvedMcpPacks = [];
|
|
185
|
+
for (const key of mcpPackKeys) {
|
|
186
|
+
if (seenMcpKeys.has(key)) continue;
|
|
187
|
+
seenMcpKeys.add(key);
|
|
188
|
+
const pack = lookupMcpPack(key);
|
|
189
|
+
if (!pack) {
|
|
190
|
+
warnings.push(`MCP pack "${key}" not found, skipped.`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
resolvedMcpPacks.push(pack);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Merge excludeTools across all MCP packs (union — Gemini uses deny-list, not allow-list)
|
|
197
|
+
const mergedExcludeTools = new Set();
|
|
198
|
+
for (const pack of resolvedMcpPacks) {
|
|
199
|
+
for (const tool of pack.excludeTools || []) {
|
|
200
|
+
mergedExcludeTools.add(tool);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Collect required auth (union)
|
|
205
|
+
const mergedRequiredAuth = new Set();
|
|
206
|
+
for (const pack of resolvedMcpPacks) {
|
|
207
|
+
for (const auth of pack.requiredAuth || []) {
|
|
208
|
+
mergedRequiredAuth.add(auth);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- Resolve policy packs (Gemini-unique) ---
|
|
213
|
+
const seenPolicyKeys = new Set();
|
|
214
|
+
const resolvedPolicyPacks = [];
|
|
215
|
+
const mergedPolicyContent = {};
|
|
216
|
+
for (const key of policyPackKeys) {
|
|
217
|
+
if (seenPolicyKeys.has(key)) continue;
|
|
218
|
+
seenPolicyKeys.add(key);
|
|
219
|
+
const pack = lookupPolicyPack(key);
|
|
220
|
+
if (!pack) {
|
|
221
|
+
warnings.push(`Policy pack "${key}" not found, skipped.`);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
resolvedPolicyPacks.push(pack);
|
|
225
|
+
// Merge policy content sections
|
|
226
|
+
if (pack.policyContent) {
|
|
227
|
+
for (const [section, rules] of Object.entries(pack.policyContent)) {
|
|
228
|
+
if (!mergedPolicyContent[section]) {
|
|
229
|
+
mergedPolicyContent[section] = { ...rules };
|
|
230
|
+
} else {
|
|
231
|
+
// Merge arrays, overwrite scalars
|
|
232
|
+
for (const [ruleKey, ruleValue] of Object.entries(rules)) {
|
|
233
|
+
if (Array.isArray(ruleValue) && Array.isArray(mergedPolicyContent[section][ruleKey])) {
|
|
234
|
+
const merged = new Set([...mergedPolicyContent[section][ruleKey], ...ruleValue]);
|
|
235
|
+
mergedPolicyContent[section][ruleKey] = [...merged];
|
|
236
|
+
} else {
|
|
237
|
+
mergedPolicyContent[section][ruleKey] = ruleValue;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --- Resolve extension packs (Gemini-unique) ---
|
|
246
|
+
const seenExtKeys = new Set();
|
|
247
|
+
const resolvedExtensionPacks = [];
|
|
248
|
+
for (const key of extensionPackKeys) {
|
|
249
|
+
if (seenExtKeys.has(key)) continue;
|
|
250
|
+
seenExtKeys.add(key);
|
|
251
|
+
const pack = lookupExtensionPack(key);
|
|
252
|
+
if (!pack) {
|
|
253
|
+
warnings.push(`Extension pack "${key}" not found, skipped.`);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
resolvedExtensionPacks.push(pack);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- Size budget tracking ---
|
|
260
|
+
const estimatedSize = estimateCompositionSize(rawDomainPacks, resolvedMcpPacks, resolvedPolicyPacks, resolvedExtensionPacks);
|
|
261
|
+
const overBudget = estimatedSize > sizeBudget;
|
|
262
|
+
if (overBudget) {
|
|
263
|
+
warnings.push(
|
|
264
|
+
`Combined instruction size (~${estimatedSize} chars) exceeds budget (${sizeBudget}). ` +
|
|
265
|
+
`Consider removing lower-priority packs.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
domainPacks: rawDomainPacks.map(p => ({
|
|
271
|
+
key: p.key,
|
|
272
|
+
label: p.label,
|
|
273
|
+
modulesOwned: [...moduleOwner.entries()]
|
|
274
|
+
.filter(([, owner]) => owner.key === p.key)
|
|
275
|
+
.map(([mod]) => mod),
|
|
276
|
+
})),
|
|
277
|
+
mcpPacks: resolvedMcpPacks.map(p => ({
|
|
278
|
+
key: p.key,
|
|
279
|
+
label: p.label,
|
|
280
|
+
serverName: p.serverName,
|
|
281
|
+
trustLevel: p.trustLevel,
|
|
282
|
+
})),
|
|
283
|
+
policyPacks: resolvedPolicyPacks.map(p => ({
|
|
284
|
+
key: p.key,
|
|
285
|
+
label: p.label,
|
|
286
|
+
description: p.description,
|
|
287
|
+
})),
|
|
288
|
+
extensionPacks: resolvedExtensionPacks.map(p => ({
|
|
289
|
+
key: p.key,
|
|
290
|
+
label: p.label,
|
|
291
|
+
description: p.description,
|
|
292
|
+
})),
|
|
293
|
+
merged: {
|
|
294
|
+
surfaces: [...allSurfaces],
|
|
295
|
+
proposalFamilies: [...allProposalFamilies],
|
|
296
|
+
modules: [...moduleOwner.entries()].map(([mod, owner]) => ({ module: mod, owner: owner.key })),
|
|
297
|
+
excludeTools: [...mergedExcludeTools].sort(),
|
|
298
|
+
requiredAuth: [...mergedRequiredAuth].sort(),
|
|
299
|
+
policyContent: mergedPolicyContent,
|
|
300
|
+
},
|
|
301
|
+
budget: {
|
|
302
|
+
estimatedSize,
|
|
303
|
+
limit: sizeBudget,
|
|
304
|
+
overBudget,
|
|
305
|
+
utilization: Math.round((estimatedSize / sizeBudget) * 100),
|
|
306
|
+
},
|
|
307
|
+
warnings,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function estimateCompositionSize(domainPacks, mcpPacks, policyPacks = [], extensionPacks = []) {
|
|
312
|
+
let size = 0;
|
|
313
|
+
for (const pack of domainPacks) {
|
|
314
|
+
size += (pack.label || '').length + (pack.useWhen || '').length + (pack.adoption || '').length;
|
|
315
|
+
size += JSON.stringify(pack.recommendedModules || []).length;
|
|
316
|
+
size += JSON.stringify(pack.benchmarkFocus || []).length;
|
|
317
|
+
}
|
|
318
|
+
for (const pack of mcpPacks) {
|
|
319
|
+
size += (pack.label || '').length + (pack.description || '').length;
|
|
320
|
+
size += JSON.stringify(pack.jsonProjection || {}).length;
|
|
321
|
+
size += JSON.stringify(pack.excludeTools || []).length;
|
|
322
|
+
}
|
|
323
|
+
for (const pack of policyPacks) {
|
|
324
|
+
size += (pack.label || '').length + (pack.description || '').length;
|
|
325
|
+
size += JSON.stringify(pack.policyContent || {}).length;
|
|
326
|
+
}
|
|
327
|
+
for (const pack of extensionPacks) {
|
|
328
|
+
size += (pack.label || '').length + (pack.description || '').length;
|
|
329
|
+
}
|
|
330
|
+
return size;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// 2. CI Template Library (5 templates using run-gemini-cli action)
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
const CI_TEMPLATES = [
|
|
338
|
+
{
|
|
339
|
+
key: 'gemini-pr-review',
|
|
340
|
+
label: 'Gemini PR Review',
|
|
341
|
+
filename: 'gemini-pr-review.yml',
|
|
342
|
+
description: 'Runs Gemini CLI review on pull request diffs.',
|
|
343
|
+
trigger: 'pull_request',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
key: 'gemini-issue-triage',
|
|
347
|
+
label: 'Gemini Issue Triage',
|
|
348
|
+
filename: 'gemini-issue-triage.yml',
|
|
349
|
+
description: 'Classifies and labels new issues using Gemini CLI.',
|
|
350
|
+
trigger: 'issues.opened',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
key: 'gemini-scheduled-audit',
|
|
354
|
+
label: 'Gemini Scheduled Audit',
|
|
355
|
+
filename: 'gemini-scheduled-audit.yml',
|
|
356
|
+
description: 'Weekly deep review of the codebase with Gemini CLI.',
|
|
357
|
+
trigger: 'schedule (cron)',
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
key: 'gemini-test-gen',
|
|
361
|
+
label: 'Gemini Test Generation',
|
|
362
|
+
filename: 'gemini-test-gen.yml',
|
|
363
|
+
description: 'Generates test stubs for newly added files using Gemini CLI.',
|
|
364
|
+
trigger: 'pull_request',
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
key: 'gemini-docs-sync',
|
|
368
|
+
label: 'Gemini Docs Sync',
|
|
369
|
+
filename: 'gemini-docs-sync.yml',
|
|
370
|
+
description: 'Checks for documentation staleness using Gemini CLI.',
|
|
371
|
+
trigger: 'schedule (cron)',
|
|
372
|
+
},
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
const TEMPLATE_CONTENT = {
|
|
376
|
+
'gemini-pr-review': `# Gemini PR Review — generated by nerviq
|
|
377
|
+
# Runs Gemini CLI to review pull request diffs and post suggestions.
|
|
378
|
+
#
|
|
379
|
+
# CUSTOMIZE: Adjust the model, sandbox, and timeout to match your team.
|
|
380
|
+
|
|
381
|
+
name: Gemini PR Review
|
|
382
|
+
|
|
383
|
+
on:
|
|
384
|
+
pull_request:
|
|
385
|
+
types: [opened, synchronize]
|
|
386
|
+
|
|
387
|
+
# CUSTOMIZE: Add path filters if you only want reviews on certain directories
|
|
388
|
+
# paths: ['src/**', 'lib/**']
|
|
389
|
+
|
|
390
|
+
permissions:
|
|
391
|
+
contents: read
|
|
392
|
+
pull-requests: write
|
|
393
|
+
|
|
394
|
+
jobs:
|
|
395
|
+
gemini-review:
|
|
396
|
+
runs-on: ubuntu-latest
|
|
397
|
+
timeout-minutes: 15 # CUSTOMIZE: Adjust timeout for your repo size
|
|
398
|
+
env:
|
|
399
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
400
|
+
# CUSTOMIZE: Set DRY_RUN=true to preview without posting comments
|
|
401
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
402
|
+
steps:
|
|
403
|
+
- uses: actions/checkout@v4
|
|
404
|
+
with:
|
|
405
|
+
fetch-depth: 0
|
|
406
|
+
|
|
407
|
+
- name: Run Gemini CLI Review
|
|
408
|
+
uses: google/run-gemini-cli@v1
|
|
409
|
+
with:
|
|
410
|
+
prompt: |
|
|
411
|
+
Review the following pull request diff for issues, improvements, and best practices.
|
|
412
|
+
Focus on security, performance, and correctness.
|
|
413
|
+
sandbox: docker
|
|
414
|
+
env:
|
|
415
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
416
|
+
`,
|
|
417
|
+
|
|
418
|
+
'gemini-issue-triage': `# Gemini Issue Triage — generated by nerviq
|
|
419
|
+
# Classifies newly opened issues and applies labels using Gemini CLI.
|
|
420
|
+
#
|
|
421
|
+
# CUSTOMIZE: Edit the label mapping and classification prompt below.
|
|
422
|
+
|
|
423
|
+
name: Gemini Issue Triage
|
|
424
|
+
|
|
425
|
+
on:
|
|
426
|
+
issues:
|
|
427
|
+
types: [opened]
|
|
428
|
+
|
|
429
|
+
permissions:
|
|
430
|
+
issues: write
|
|
431
|
+
|
|
432
|
+
jobs:
|
|
433
|
+
triage:
|
|
434
|
+
runs-on: ubuntu-latest
|
|
435
|
+
timeout-minutes: 5 # CUSTOMIZE: Issues are small; 5 min is usually enough
|
|
436
|
+
env:
|
|
437
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
438
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
439
|
+
steps:
|
|
440
|
+
- uses: actions/checkout@v4
|
|
441
|
+
|
|
442
|
+
- name: Classify Issue with Gemini
|
|
443
|
+
uses: google/run-gemini-cli@v1
|
|
444
|
+
with:
|
|
445
|
+
prompt: |
|
|
446
|
+
Classify this issue and suggest labels from: bug, feature, question, docs, security.
|
|
447
|
+
Title: \${{ github.event.issue.title }}
|
|
448
|
+
Body: \${{ github.event.issue.body }}
|
|
449
|
+
sandbox: docker
|
|
450
|
+
env:
|
|
451
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
452
|
+
`,
|
|
453
|
+
|
|
454
|
+
'gemini-scheduled-audit': `# Gemini Scheduled Audit — generated by nerviq
|
|
455
|
+
# Weekly deep review of the codebase using Gemini CLI.
|
|
456
|
+
#
|
|
457
|
+
# CUSTOMIZE: Adjust the cron schedule and audit scope.
|
|
458
|
+
|
|
459
|
+
name: Gemini Scheduled Audit
|
|
460
|
+
|
|
461
|
+
on:
|
|
462
|
+
schedule:
|
|
463
|
+
- cron: '0 9 * * 1' # CUSTOMIZE: Every Monday at 09:00 UTC
|
|
464
|
+
workflow_dispatch:
|
|
465
|
+
inputs:
|
|
466
|
+
dry_run:
|
|
467
|
+
description: 'Run in dry-run mode (no changes)'
|
|
468
|
+
required: false
|
|
469
|
+
default: 'false'
|
|
470
|
+
|
|
471
|
+
permissions:
|
|
472
|
+
contents: read
|
|
473
|
+
issues: write
|
|
474
|
+
|
|
475
|
+
jobs:
|
|
476
|
+
audit:
|
|
477
|
+
runs-on: ubuntu-latest
|
|
478
|
+
timeout-minutes: 30 # CUSTOMIZE: Deep audits may need more time
|
|
479
|
+
env:
|
|
480
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
481
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
482
|
+
steps:
|
|
483
|
+
- uses: actions/checkout@v4
|
|
484
|
+
with:
|
|
485
|
+
fetch-depth: 0
|
|
486
|
+
|
|
487
|
+
- name: Run Deep Audit with Gemini
|
|
488
|
+
uses: google/run-gemini-cli@v1
|
|
489
|
+
with:
|
|
490
|
+
prompt: |
|
|
491
|
+
Perform a deep audit of this codebase. Check for:
|
|
492
|
+
- Security vulnerabilities
|
|
493
|
+
- Performance issues
|
|
494
|
+
- Code quality concerns
|
|
495
|
+
- Dependency health
|
|
496
|
+
Output a structured markdown report.
|
|
497
|
+
sandbox: docker
|
|
498
|
+
env:
|
|
499
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
500
|
+
|
|
501
|
+
- name: Upload Report
|
|
502
|
+
if: env.DRY_RUN != 'true'
|
|
503
|
+
uses: actions/upload-artifact@v4
|
|
504
|
+
with:
|
|
505
|
+
name: gemini-audit-report
|
|
506
|
+
path: audit-report.md
|
|
507
|
+
retention-days: 30
|
|
508
|
+
`,
|
|
509
|
+
|
|
510
|
+
'gemini-test-gen': `# Gemini Test Generation — generated by nerviq
|
|
511
|
+
# Generates test stubs for new files in a pull request using Gemini CLI.
|
|
512
|
+
#
|
|
513
|
+
# CUSTOMIZE: Adjust file patterns, test framework, and output directory.
|
|
514
|
+
|
|
515
|
+
name: Gemini Test Generation
|
|
516
|
+
|
|
517
|
+
on:
|
|
518
|
+
pull_request:
|
|
519
|
+
types: [opened, synchronize]
|
|
520
|
+
|
|
521
|
+
permissions:
|
|
522
|
+
contents: read
|
|
523
|
+
pull-requests: write
|
|
524
|
+
|
|
525
|
+
jobs:
|
|
526
|
+
test-gen:
|
|
527
|
+
runs-on: ubuntu-latest
|
|
528
|
+
timeout-minutes: 10 # CUSTOMIZE: Adjust for repo size
|
|
529
|
+
env:
|
|
530
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
531
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
532
|
+
steps:
|
|
533
|
+
- uses: actions/checkout@v4
|
|
534
|
+
with:
|
|
535
|
+
fetch-depth: 0
|
|
536
|
+
|
|
537
|
+
- name: Detect New Files
|
|
538
|
+
id: new-files
|
|
539
|
+
run: |
|
|
540
|
+
FILES=$(git diff --name-only --diff-filter=A \${{ github.event.pull_request.base.sha }}..\${{ github.sha }})
|
|
541
|
+
# CUSTOMIZE: Filter to source files only
|
|
542
|
+
SRC_FILES=$(echo "$FILES" | grep -E '\\.(js|ts|py|go|rs|java)$' || true)
|
|
543
|
+
echo "files=$SRC_FILES" >> $GITHUB_OUTPUT
|
|
544
|
+
|
|
545
|
+
- name: Generate Test Stubs with Gemini
|
|
546
|
+
if: steps.new-files.outputs.files != ''
|
|
547
|
+
uses: google/run-gemini-cli@v1
|
|
548
|
+
with:
|
|
549
|
+
prompt: |
|
|
550
|
+
Generate test stubs for the following new files:
|
|
551
|
+
\${{ steps.new-files.outputs.files }}
|
|
552
|
+
Use the project's existing test framework and conventions.
|
|
553
|
+
sandbox: docker
|
|
554
|
+
env:
|
|
555
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
556
|
+
`,
|
|
557
|
+
|
|
558
|
+
'gemini-docs-sync': `# Gemini Docs Sync — generated by nerviq
|
|
559
|
+
# Checks for documentation staleness and suggests updates using Gemini CLI.
|
|
560
|
+
#
|
|
561
|
+
# CUSTOMIZE: Adjust the staleness threshold and doc paths.
|
|
562
|
+
|
|
563
|
+
name: Gemini Docs Sync
|
|
564
|
+
|
|
565
|
+
on:
|
|
566
|
+
schedule:
|
|
567
|
+
- cron: '0 10 * * 3' # CUSTOMIZE: Every Wednesday at 10:00 UTC
|
|
568
|
+
workflow_dispatch:
|
|
569
|
+
inputs:
|
|
570
|
+
dry_run:
|
|
571
|
+
description: 'Run in dry-run mode (no changes)'
|
|
572
|
+
required: false
|
|
573
|
+
default: 'false'
|
|
574
|
+
|
|
575
|
+
permissions:
|
|
576
|
+
contents: read
|
|
577
|
+
issues: write
|
|
578
|
+
|
|
579
|
+
jobs:
|
|
580
|
+
docs-check:
|
|
581
|
+
runs-on: ubuntu-latest
|
|
582
|
+
timeout-minutes: 10 # CUSTOMIZE: Usually fast
|
|
583
|
+
env:
|
|
584
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
585
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
586
|
+
steps:
|
|
587
|
+
- uses: actions/checkout@v4
|
|
588
|
+
with:
|
|
589
|
+
fetch-depth: 0
|
|
590
|
+
|
|
591
|
+
- name: Check Doc Staleness
|
|
592
|
+
run: |
|
|
593
|
+
# CUSTOMIZE: Adjust paths and staleness threshold (days)
|
|
594
|
+
STALE_THRESHOLD=30
|
|
595
|
+
DOC_PATHS="README.md docs/ CONTRIBUTING.md"
|
|
596
|
+
|
|
597
|
+
for doc_path in $DOC_PATHS; do
|
|
598
|
+
[ ! -e "$doc_path" ] && continue
|
|
599
|
+
LAST_MODIFIED=$(git log -1 --format="%ct" -- "$doc_path" 2>/dev/null || echo 0)
|
|
600
|
+
NOW=$(date +%s)
|
|
601
|
+
DAYS_OLD=$(( (NOW - LAST_MODIFIED) / 86400 ))
|
|
602
|
+
if [ "$DAYS_OLD" -gt "$STALE_THRESHOLD" ]; then
|
|
603
|
+
echo "STALE: $doc_path ($DAYS_OLD days old)"
|
|
604
|
+
fi
|
|
605
|
+
done
|
|
606
|
+
|
|
607
|
+
- name: Suggest Doc Updates with Gemini
|
|
608
|
+
uses: google/run-gemini-cli@v1
|
|
609
|
+
with:
|
|
610
|
+
prompt: |
|
|
611
|
+
Review the project documentation for staleness and accuracy.
|
|
612
|
+
Compare docs against the current codebase and suggest updates.
|
|
613
|
+
Focus on README.md, CONTRIBUTING.md, and files in docs/.
|
|
614
|
+
sandbox: docker
|
|
615
|
+
env:
|
|
616
|
+
GEMINI_API_KEY: \${{ secrets.GEMINI_API_KEY }}
|
|
617
|
+
`,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Get a CI template by key.
|
|
622
|
+
*
|
|
623
|
+
* @param {string} templateKey - One of the CI_TEMPLATES keys
|
|
624
|
+
* @returns {string|null} Template content string or null if not found
|
|
625
|
+
*/
|
|
626
|
+
function getCiTemplate(templateKey) {
|
|
627
|
+
const meta = CI_TEMPLATES.find(t => t.key === templateKey);
|
|
628
|
+
if (!meta) return null;
|
|
629
|
+
return TEMPLATE_CONTENT[templateKey] || null;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
633
|
+
// 3. Adoption Signal Gate (5 gates)
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Gate thresholds: each gate defines a minimum usage signal before activation.
|
|
638
|
+
* Uses local snapshot history for privacy-preserving telemetry.
|
|
639
|
+
*/
|
|
640
|
+
const GATE_THRESHOLDS = {
|
|
641
|
+
'ci-templates': {
|
|
642
|
+
metric: 'auditCount',
|
|
643
|
+
threshold: 3,
|
|
644
|
+
description: 'Activate CI templates after 3+ successful audits.',
|
|
645
|
+
},
|
|
646
|
+
'policy-engine': {
|
|
647
|
+
metric: 'auditCount',
|
|
648
|
+
threshold: 2,
|
|
649
|
+
description: 'Activate policy engine packs after 2+ audits.',
|
|
650
|
+
},
|
|
651
|
+
'multi-pack': {
|
|
652
|
+
metric: 'auditCount',
|
|
653
|
+
threshold: 2,
|
|
654
|
+
description: 'Activate multi-pack composition after 2+ audits.',
|
|
655
|
+
},
|
|
656
|
+
'extension-marketplace': {
|
|
657
|
+
metric: 'auditCount',
|
|
658
|
+
threshold: 5,
|
|
659
|
+
description: 'Activate extension marketplace recommendations after 5+ audits.',
|
|
660
|
+
},
|
|
661
|
+
'governance-upgrade': {
|
|
662
|
+
metric: 'averageScore',
|
|
663
|
+
threshold: 70,
|
|
664
|
+
description: 'Suggest governance upgrade when average score exceeds 70.',
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Read Gemini audit snapshot history from the local .gemini/.claudex/ directory.
|
|
670
|
+
* @param {string} dir - Project directory
|
|
671
|
+
* @param {number} limit - Max snapshots to read
|
|
672
|
+
* @returns {object[]} Array of snapshot objects
|
|
673
|
+
*/
|
|
674
|
+
function getGeminiHistory(dir, limit = 20) {
|
|
675
|
+
const fs = require('fs');
|
|
676
|
+
const snapshotDir = path.join(dir, '.gemini', '.claudex', 'snapshots');
|
|
677
|
+
try {
|
|
678
|
+
const files = fs.readdirSync(snapshotDir)
|
|
679
|
+
.filter(f => f.endsWith('.json'))
|
|
680
|
+
.sort()
|
|
681
|
+
.slice(-limit);
|
|
682
|
+
|
|
683
|
+
return files.map(f => {
|
|
684
|
+
try {
|
|
685
|
+
return JSON.parse(fs.readFileSync(path.join(snapshotDir, f), 'utf8'));
|
|
686
|
+
} catch {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
}).filter(Boolean);
|
|
690
|
+
} catch {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Check if an adoption gate should be activated based on local telemetry.
|
|
697
|
+
*
|
|
698
|
+
* @param {string} gateKey - Key from GATE_THRESHOLDS
|
|
699
|
+
* @param {string} dir - Project directory to read snapshots from
|
|
700
|
+
* @returns {{ activated: boolean, current: number, threshold: number, gate: string, description: string }}
|
|
701
|
+
*/
|
|
702
|
+
function checkAdoptionGate(gateKey, dir) {
|
|
703
|
+
const gate = GATE_THRESHOLDS[gateKey];
|
|
704
|
+
if (!gate) {
|
|
705
|
+
return {
|
|
706
|
+
activated: false,
|
|
707
|
+
current: 0,
|
|
708
|
+
threshold: 0,
|
|
709
|
+
gate: gateKey,
|
|
710
|
+
description: `Unknown gate "${gateKey}".`,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const history = getGeminiHistory(dir, 100);
|
|
715
|
+
let current = 0;
|
|
716
|
+
|
|
717
|
+
switch (gate.metric) {
|
|
718
|
+
case 'auditCount':
|
|
719
|
+
current = history.length;
|
|
720
|
+
break;
|
|
721
|
+
case 'averageScore': {
|
|
722
|
+
const scores = history
|
|
723
|
+
.map(e => e.summary?.score)
|
|
724
|
+
.filter(s => typeof s === 'number');
|
|
725
|
+
current = scores.length > 0
|
|
726
|
+
? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)
|
|
727
|
+
: 0;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
default:
|
|
731
|
+
current = 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return {
|
|
735
|
+
activated: current >= gate.threshold,
|
|
736
|
+
current,
|
|
737
|
+
threshold: gate.threshold,
|
|
738
|
+
gate: gateKey,
|
|
739
|
+
description: gate.description,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// ---------------------------------------------------------------------------
|
|
744
|
+
// Exports
|
|
745
|
+
// ---------------------------------------------------------------------------
|
|
746
|
+
|
|
747
|
+
module.exports = {
|
|
748
|
+
composePacks,
|
|
749
|
+
getCiTemplate,
|
|
750
|
+
CI_TEMPLATES,
|
|
751
|
+
checkAdoptionGate,
|
|
752
|
+
// Internals exposed for testing
|
|
753
|
+
GATE_THRESHOLDS,
|
|
754
|
+
PACK_DEPENDENCY_ORDER,
|
|
755
|
+
PACK_SPECIFICITY,
|
|
756
|
+
DEFAULT_SIZE_BUDGET,
|
|
757
|
+
POLICY_PACKS,
|
|
758
|
+
EXTENSION_PACKS,
|
|
759
|
+
};
|