@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,614 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex Premium Operator UX — CP-15
|
|
3
|
+
*
|
|
4
|
+
* Three subsystems:
|
|
5
|
+
* 1. Multi-Pack Composition Engine — merge domain + MCP packs with dedup, conflict resolution, dependency ordering
|
|
6
|
+
* 2. CI Template Library — 5 GitHub Actions workflow templates for Codex automation
|
|
7
|
+
* 3. Adoption Signal Gate — activate features based on local usage telemetry
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { CODEX_DOMAIN_PACKS } = require('./domain-packs');
|
|
12
|
+
const { CODEX_MCP_PACKS } = require('./mcp-packs');
|
|
13
|
+
const { getCodexHistory } = require('./activity');
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// 1. Multi-Pack Composition Engine
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/** Pack dependency order — earlier = more foundational */
|
|
20
|
+
const PACK_DEPENDENCY_ORDER = [
|
|
21
|
+
'baseline-general',
|
|
22
|
+
'backend-api',
|
|
23
|
+
'frontend-ui',
|
|
24
|
+
'infra-platform',
|
|
25
|
+
'monorepo',
|
|
26
|
+
'enterprise-governed',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/** Specificity rank: higher = more specific, wins conflicts */
|
|
30
|
+
const PACK_SPECIFICITY = {
|
|
31
|
+
'baseline-general': 0,
|
|
32
|
+
'backend-api': 2,
|
|
33
|
+
'frontend-ui': 2,
|
|
34
|
+
'infra-platform': 3,
|
|
35
|
+
'monorepo': 3,
|
|
36
|
+
'enterprise-governed': 4,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const DEFAULT_SIZE_BUDGET = 16000; // characters for combined instruction content
|
|
40
|
+
|
|
41
|
+
function lookupDomainPack(key) {
|
|
42
|
+
return CODEX_DOMAIN_PACKS.find(p => p.key === key) || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function lookupMcpPack(key) {
|
|
46
|
+
return CODEX_MCP_PACKS.find(p => p.key === key) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compose domain packs and MCP packs into a unified, deduplicated, ordered result.
|
|
51
|
+
*
|
|
52
|
+
* @param {string[]} domainPackKeys - Array of domain pack keys to compose
|
|
53
|
+
* @param {string[]} mcpPackKeys - Array of MCP pack keys to compose
|
|
54
|
+
* @param {object} [options]
|
|
55
|
+
* @param {number} [options.sizeBudget] - Max characters for combined instructions (default 16000)
|
|
56
|
+
* @returns {object} Composition report
|
|
57
|
+
*/
|
|
58
|
+
function composePacks(domainPackKeys = [], mcpPackKeys = [], options = {}) {
|
|
59
|
+
const sizeBudget = options.sizeBudget || DEFAULT_SIZE_BUDGET;
|
|
60
|
+
const warnings = [];
|
|
61
|
+
|
|
62
|
+
// --- Resolve domain packs ---
|
|
63
|
+
const seenDomainKeys = new Set();
|
|
64
|
+
const rawDomainPacks = [];
|
|
65
|
+
for (const key of domainPackKeys) {
|
|
66
|
+
if (seenDomainKeys.has(key)) continue;
|
|
67
|
+
seenDomainKeys.add(key);
|
|
68
|
+
const pack = lookupDomainPack(key);
|
|
69
|
+
if (!pack) {
|
|
70
|
+
warnings.push(`Domain pack "${key}" not found, skipped.`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
rawDomainPacks.push(pack);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Order by dependency (base → framework → tool-specific)
|
|
77
|
+
rawDomainPacks.sort((a, b) => {
|
|
78
|
+
const orderA = PACK_DEPENDENCY_ORDER.indexOf(a.key);
|
|
79
|
+
const orderB = PACK_DEPENDENCY_ORDER.indexOf(b.key);
|
|
80
|
+
return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Deduplicate overlapping recommendedModules; more specific pack wins
|
|
84
|
+
const moduleOwner = new Map(); // module → { key, specificity }
|
|
85
|
+
for (const pack of rawDomainPacks) {
|
|
86
|
+
const specificity = PACK_SPECIFICITY[pack.key] ?? 1;
|
|
87
|
+
for (const mod of pack.recommendedModules || []) {
|
|
88
|
+
const existing = moduleOwner.get(mod);
|
|
89
|
+
if (!existing || specificity > existing.specificity) {
|
|
90
|
+
moduleOwner.set(mod, { key: pack.key, specificity });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Deduplicate surfaces
|
|
96
|
+
const allSurfaces = new Set();
|
|
97
|
+
for (const pack of rawDomainPacks) {
|
|
98
|
+
for (const surface of pack.recommendedSurfaces || []) {
|
|
99
|
+
allSurfaces.add(surface);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Deduplicate proposal families
|
|
104
|
+
const allProposalFamilies = new Set();
|
|
105
|
+
for (const pack of rawDomainPacks) {
|
|
106
|
+
for (const family of pack.recommendedProposalFamilies || []) {
|
|
107
|
+
allProposalFamilies.add(family);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Resolve MCP packs ---
|
|
112
|
+
const seenMcpKeys = new Set();
|
|
113
|
+
const resolvedMcpPacks = [];
|
|
114
|
+
for (const key of mcpPackKeys) {
|
|
115
|
+
if (seenMcpKeys.has(key)) continue;
|
|
116
|
+
seenMcpKeys.add(key);
|
|
117
|
+
const pack = lookupMcpPack(key);
|
|
118
|
+
if (!pack) {
|
|
119
|
+
warnings.push(`MCP pack "${key}" not found, skipped.`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
resolvedMcpPacks.push(pack);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Merge enabled_tools across all MCP packs (union, not replace)
|
|
126
|
+
const mergedEnabledTools = new Set();
|
|
127
|
+
for (const pack of resolvedMcpPacks) {
|
|
128
|
+
for (const tool of pack.enabledTools || []) {
|
|
129
|
+
mergedEnabledTools.add(tool);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Collect required auth (union)
|
|
134
|
+
const mergedRequiredAuth = new Set();
|
|
135
|
+
for (const pack of resolvedMcpPacks) {
|
|
136
|
+
for (const auth of pack.requiredAuth || []) {
|
|
137
|
+
mergedRequiredAuth.add(auth);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Size budget tracking ---
|
|
142
|
+
const estimatedSize = estimateCompositionSize(rawDomainPacks, resolvedMcpPacks);
|
|
143
|
+
const overBudget = estimatedSize > sizeBudget;
|
|
144
|
+
if (overBudget) {
|
|
145
|
+
warnings.push(
|
|
146
|
+
`Combined instruction size (~${estimatedSize} chars) exceeds budget (${sizeBudget}). ` +
|
|
147
|
+
`Consider removing lower-priority packs.`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
domainPacks: rawDomainPacks.map(p => ({
|
|
153
|
+
key: p.key,
|
|
154
|
+
label: p.label,
|
|
155
|
+
modulesOwned: [...moduleOwner.entries()]
|
|
156
|
+
.filter(([, owner]) => owner.key === p.key)
|
|
157
|
+
.map(([mod]) => mod),
|
|
158
|
+
})),
|
|
159
|
+
mcpPacks: resolvedMcpPacks.map(p => ({
|
|
160
|
+
key: p.key,
|
|
161
|
+
label: p.label,
|
|
162
|
+
serverName: p.serverName,
|
|
163
|
+
trustLevel: p.trustLevel,
|
|
164
|
+
})),
|
|
165
|
+
merged: {
|
|
166
|
+
surfaces: [...allSurfaces],
|
|
167
|
+
proposalFamilies: [...allProposalFamilies],
|
|
168
|
+
modules: [...moduleOwner.entries()].map(([mod, owner]) => ({ module: mod, owner: owner.key })),
|
|
169
|
+
enabledTools: [...mergedEnabledTools].sort(),
|
|
170
|
+
requiredAuth: [...mergedRequiredAuth].sort(),
|
|
171
|
+
},
|
|
172
|
+
budget: {
|
|
173
|
+
estimatedSize,
|
|
174
|
+
limit: sizeBudget,
|
|
175
|
+
overBudget,
|
|
176
|
+
utilization: Math.round((estimatedSize / sizeBudget) * 100),
|
|
177
|
+
},
|
|
178
|
+
warnings,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function estimateCompositionSize(domainPacks, mcpPacks) {
|
|
183
|
+
let size = 0;
|
|
184
|
+
for (const pack of domainPacks) {
|
|
185
|
+
size += (pack.label || '').length + (pack.useWhen || '').length + (pack.adoption || '').length;
|
|
186
|
+
size += JSON.stringify(pack.recommendedModules || []).length;
|
|
187
|
+
size += JSON.stringify(pack.benchmarkFocus || []).length;
|
|
188
|
+
}
|
|
189
|
+
for (const pack of mcpPacks) {
|
|
190
|
+
size += (pack.label || '').length + (pack.description || '').length;
|
|
191
|
+
size += JSON.stringify(pack.tomlProjection || {}).length;
|
|
192
|
+
size += JSON.stringify(pack.enabledTools || []).length;
|
|
193
|
+
}
|
|
194
|
+
return size;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// 2. CI Template Library
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
const CI_TEMPLATES = [
|
|
202
|
+
{
|
|
203
|
+
key: 'codex-pr-review',
|
|
204
|
+
label: 'Codex PR Review',
|
|
205
|
+
filename: 'codex-pr-review.yml',
|
|
206
|
+
description: 'Runs Codex review on pull request diffs.',
|
|
207
|
+
trigger: 'pull_request',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: 'codex-issue-triage',
|
|
211
|
+
label: 'Codex Issue Triage',
|
|
212
|
+
filename: 'codex-issue-triage.yml',
|
|
213
|
+
description: 'Classifies and labels new issues using Codex.',
|
|
214
|
+
trigger: 'issues.opened',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
key: 'codex-scheduled-audit',
|
|
218
|
+
label: 'Codex Scheduled Audit',
|
|
219
|
+
filename: 'codex-scheduled-audit.yml',
|
|
220
|
+
description: 'Weekly deep review of the codebase with Codex.',
|
|
221
|
+
trigger: 'schedule (cron)',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
key: 'codex-test-gen',
|
|
225
|
+
label: 'Codex Test Generation',
|
|
226
|
+
filename: 'codex-test-gen.yml',
|
|
227
|
+
description: 'Generates test stubs for newly added files.',
|
|
228
|
+
trigger: 'pull_request',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: 'codex-docs-sync',
|
|
232
|
+
label: 'Codex Docs Sync',
|
|
233
|
+
filename: 'codex-docs-sync.yml',
|
|
234
|
+
description: 'Checks for documentation staleness.',
|
|
235
|
+
trigger: 'schedule (cron)',
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
const TEMPLATE_CONTENT = {
|
|
240
|
+
'codex-pr-review': `# Codex PR Review — generated by nerviq (CP-15)
|
|
241
|
+
# Runs Codex to review pull request diffs and post suggestions.
|
|
242
|
+
#
|
|
243
|
+
# CUSTOMIZE: Adjust the model, approval_policy, and timeout to match your team.
|
|
244
|
+
|
|
245
|
+
name: Codex PR Review
|
|
246
|
+
|
|
247
|
+
on:
|
|
248
|
+
pull_request:
|
|
249
|
+
types: [opened, synchronize]
|
|
250
|
+
|
|
251
|
+
# CUSTOMIZE: Add path filters if you only want reviews on certain directories
|
|
252
|
+
# paths: ['src/**', 'lib/**']
|
|
253
|
+
|
|
254
|
+
permissions:
|
|
255
|
+
contents: read
|
|
256
|
+
pull-requests: write
|
|
257
|
+
|
|
258
|
+
jobs:
|
|
259
|
+
codex-review:
|
|
260
|
+
runs-on: ubuntu-latest
|
|
261
|
+
timeout-minutes: 15 # CUSTOMIZE: Adjust timeout for your repo size
|
|
262
|
+
env:
|
|
263
|
+
CODEX_API_KEY: \${{ secrets.CODEX_API_KEY }}
|
|
264
|
+
# CUSTOMIZE: Set DRY_RUN=true to preview without posting comments
|
|
265
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
266
|
+
steps:
|
|
267
|
+
- uses: actions/checkout@v4
|
|
268
|
+
with:
|
|
269
|
+
fetch-depth: 0
|
|
270
|
+
|
|
271
|
+
- name: Install Codex CLI
|
|
272
|
+
run: npm install -g @openai/codex
|
|
273
|
+
|
|
274
|
+
- name: Run Codex Review
|
|
275
|
+
run: |
|
|
276
|
+
DIFF=$(git diff \${{ github.event.pull_request.base.sha }}..\${{ github.sha }})
|
|
277
|
+
if [ "\$DRY_RUN" = "true" ]; then
|
|
278
|
+
echo "DRY RUN — would review the following diff:"
|
|
279
|
+
echo "\$DIFF" | head -100
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
echo "\$DIFF" | codex review \\
|
|
283
|
+
--approval-policy suggest \\
|
|
284
|
+
--format github-pr-comment
|
|
285
|
+
`,
|
|
286
|
+
|
|
287
|
+
'codex-issue-triage': `# Codex Issue Triage — generated by nerviq (CP-15)
|
|
288
|
+
# Classifies newly opened issues and applies labels.
|
|
289
|
+
#
|
|
290
|
+
# CUSTOMIZE: Edit the label mapping and classification prompt below.
|
|
291
|
+
|
|
292
|
+
name: Codex Issue Triage
|
|
293
|
+
|
|
294
|
+
on:
|
|
295
|
+
issues:
|
|
296
|
+
types: [opened]
|
|
297
|
+
|
|
298
|
+
permissions:
|
|
299
|
+
issues: write
|
|
300
|
+
|
|
301
|
+
jobs:
|
|
302
|
+
triage:
|
|
303
|
+
runs-on: ubuntu-latest
|
|
304
|
+
timeout-minutes: 5 # CUSTOMIZE: Issues are small; 5 min is usually enough
|
|
305
|
+
env:
|
|
306
|
+
CODEX_API_KEY: \${{ secrets.CODEX_API_KEY }}
|
|
307
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
308
|
+
steps:
|
|
309
|
+
- uses: actions/checkout@v4
|
|
310
|
+
|
|
311
|
+
- name: Install Codex CLI
|
|
312
|
+
run: npm install -g @openai/codex
|
|
313
|
+
|
|
314
|
+
- name: Classify Issue
|
|
315
|
+
run: |
|
|
316
|
+
TITLE="\${{ github.event.issue.title }}"
|
|
317
|
+
BODY="\${{ github.event.issue.body }}"
|
|
318
|
+
if [ "\$DRY_RUN" = "true" ]; then
|
|
319
|
+
echo "DRY RUN — would classify: \$TITLE"
|
|
320
|
+
exit 0
|
|
321
|
+
fi
|
|
322
|
+
# CUSTOMIZE: Adjust the classification prompt and label set
|
|
323
|
+
codex classify \\
|
|
324
|
+
--approval-policy suggest \\
|
|
325
|
+
--labels "bug,feature,question,docs,security" \\
|
|
326
|
+
--title "\$TITLE" \\
|
|
327
|
+
--body "\$BODY"
|
|
328
|
+
`,
|
|
329
|
+
|
|
330
|
+
'codex-scheduled-audit': `# Codex Scheduled Audit — generated by nerviq (CP-15)
|
|
331
|
+
# Weekly deep review of the codebase.
|
|
332
|
+
#
|
|
333
|
+
# CUSTOMIZE: Adjust the cron schedule and audit scope.
|
|
334
|
+
|
|
335
|
+
name: Codex Scheduled Audit
|
|
336
|
+
|
|
337
|
+
on:
|
|
338
|
+
schedule:
|
|
339
|
+
- cron: '0 9 * * 1' # CUSTOMIZE: Every Monday at 09:00 UTC
|
|
340
|
+
workflow_dispatch:
|
|
341
|
+
inputs:
|
|
342
|
+
dry_run:
|
|
343
|
+
description: 'Run in dry-run mode (no changes)'
|
|
344
|
+
required: false
|
|
345
|
+
default: 'false'
|
|
346
|
+
|
|
347
|
+
permissions:
|
|
348
|
+
contents: read
|
|
349
|
+
issues: write
|
|
350
|
+
|
|
351
|
+
jobs:
|
|
352
|
+
audit:
|
|
353
|
+
runs-on: ubuntu-latest
|
|
354
|
+
timeout-minutes: 30 # CUSTOMIZE: Deep audits may need more time
|
|
355
|
+
env:
|
|
356
|
+
CODEX_API_KEY: \${{ secrets.CODEX_API_KEY }}
|
|
357
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
358
|
+
steps:
|
|
359
|
+
- uses: actions/checkout@v4
|
|
360
|
+
with:
|
|
361
|
+
fetch-depth: 0
|
|
362
|
+
|
|
363
|
+
- name: Install Codex CLI
|
|
364
|
+
run: npm install -g @openai/codex
|
|
365
|
+
|
|
366
|
+
- name: Run Deep Audit
|
|
367
|
+
run: |
|
|
368
|
+
if [ "\$DRY_RUN" = "true" ]; then
|
|
369
|
+
echo "DRY RUN — would run full audit"
|
|
370
|
+
exit 0
|
|
371
|
+
fi
|
|
372
|
+
# CUSTOMIZE: Add --path filters for focused audits
|
|
373
|
+
codex audit \\
|
|
374
|
+
--approval-policy suggest \\
|
|
375
|
+
--depth deep \\
|
|
376
|
+
--format markdown > audit-report.md
|
|
377
|
+
|
|
378
|
+
- name: Upload Report
|
|
379
|
+
if: env.DRY_RUN != 'true'
|
|
380
|
+
uses: actions/upload-artifact@v4
|
|
381
|
+
with:
|
|
382
|
+
name: codex-audit-report
|
|
383
|
+
path: audit-report.md
|
|
384
|
+
retention-days: 30
|
|
385
|
+
`,
|
|
386
|
+
|
|
387
|
+
'codex-test-gen': `# Codex Test Generation — generated by nerviq (CP-15)
|
|
388
|
+
# Generates test stubs for new files in a pull request.
|
|
389
|
+
#
|
|
390
|
+
# CUSTOMIZE: Adjust file patterns, test framework, and output directory.
|
|
391
|
+
|
|
392
|
+
name: Codex Test Generation
|
|
393
|
+
|
|
394
|
+
on:
|
|
395
|
+
pull_request:
|
|
396
|
+
types: [opened, synchronize]
|
|
397
|
+
|
|
398
|
+
permissions:
|
|
399
|
+
contents: read
|
|
400
|
+
pull-requests: write
|
|
401
|
+
|
|
402
|
+
jobs:
|
|
403
|
+
test-gen:
|
|
404
|
+
runs-on: ubuntu-latest
|
|
405
|
+
timeout-minutes: 10 # CUSTOMIZE: Adjust for repo size
|
|
406
|
+
env:
|
|
407
|
+
CODEX_API_KEY: \${{ secrets.CODEX_API_KEY }}
|
|
408
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
409
|
+
steps:
|
|
410
|
+
- uses: actions/checkout@v4
|
|
411
|
+
with:
|
|
412
|
+
fetch-depth: 0
|
|
413
|
+
|
|
414
|
+
- name: Install Codex CLI
|
|
415
|
+
run: npm install -g @openai/codex
|
|
416
|
+
|
|
417
|
+
- name: Detect New Files
|
|
418
|
+
id: new-files
|
|
419
|
+
run: |
|
|
420
|
+
FILES=$(git diff --name-only --diff-filter=A \${{ github.event.pull_request.base.sha }}..\${{ github.sha }})
|
|
421
|
+
# CUSTOMIZE: Filter to source files only
|
|
422
|
+
SRC_FILES=$(echo "\$FILES" | grep -E '\\.(js|ts|py|go|rs|java)$' || true)
|
|
423
|
+
echo "files=\$SRC_FILES" >> \$GITHUB_OUTPUT
|
|
424
|
+
|
|
425
|
+
- name: Generate Test Stubs
|
|
426
|
+
if: steps.new-files.outputs.files != ''
|
|
427
|
+
run: |
|
|
428
|
+
if [ "\$DRY_RUN" = "true" ]; then
|
|
429
|
+
echo "DRY RUN — would generate tests for:"
|
|
430
|
+
echo "\${{ steps.new-files.outputs.files }}"
|
|
431
|
+
exit 0
|
|
432
|
+
fi
|
|
433
|
+
echo "\${{ steps.new-files.outputs.files }}" | while read -r file; do
|
|
434
|
+
[ -z "\$file" ] && continue
|
|
435
|
+
codex generate-test \\
|
|
436
|
+
--approval-policy suggest \\
|
|
437
|
+
--file "\$file"
|
|
438
|
+
done
|
|
439
|
+
`,
|
|
440
|
+
|
|
441
|
+
'codex-docs-sync': `# Codex Docs Sync — generated by nerviq (CP-15)
|
|
442
|
+
# Checks for documentation staleness and opens issues for stale docs.
|
|
443
|
+
#
|
|
444
|
+
# CUSTOMIZE: Adjust the staleness threshold and doc paths.
|
|
445
|
+
|
|
446
|
+
name: Codex Docs Sync
|
|
447
|
+
|
|
448
|
+
on:
|
|
449
|
+
schedule:
|
|
450
|
+
- cron: '0 10 * * 3' # CUSTOMIZE: Every Wednesday at 10:00 UTC
|
|
451
|
+
workflow_dispatch:
|
|
452
|
+
inputs:
|
|
453
|
+
dry_run:
|
|
454
|
+
description: 'Run in dry-run mode (no changes)'
|
|
455
|
+
required: false
|
|
456
|
+
default: 'false'
|
|
457
|
+
|
|
458
|
+
permissions:
|
|
459
|
+
contents: read
|
|
460
|
+
issues: write
|
|
461
|
+
|
|
462
|
+
jobs:
|
|
463
|
+
docs-check:
|
|
464
|
+
runs-on: ubuntu-latest
|
|
465
|
+
timeout-minutes: 10 # CUSTOMIZE: Usually fast
|
|
466
|
+
env:
|
|
467
|
+
CODEX_API_KEY: \${{ secrets.CODEX_API_KEY }}
|
|
468
|
+
DRY_RUN: \${{ inputs.dry_run || 'false' }}
|
|
469
|
+
steps:
|
|
470
|
+
- uses: actions/checkout@v4
|
|
471
|
+
with:
|
|
472
|
+
fetch-depth: 0
|
|
473
|
+
|
|
474
|
+
- name: Install Codex CLI
|
|
475
|
+
run: npm install -g @openai/codex
|
|
476
|
+
|
|
477
|
+
- name: Check Doc Staleness
|
|
478
|
+
run: |
|
|
479
|
+
# CUSTOMIZE: Adjust paths and staleness threshold (days)
|
|
480
|
+
STALE_THRESHOLD=30
|
|
481
|
+
DOC_PATHS="README.md docs/ CONTRIBUTING.md"
|
|
482
|
+
|
|
483
|
+
if [ "\$DRY_RUN" = "true" ]; then
|
|
484
|
+
echo "DRY RUN — would check staleness for: \$DOC_PATHS"
|
|
485
|
+
exit 0
|
|
486
|
+
fi
|
|
487
|
+
|
|
488
|
+
for doc_path in \$DOC_PATHS; do
|
|
489
|
+
[ ! -e "\$doc_path" ] && continue
|
|
490
|
+
LAST_MODIFIED=$(git log -1 --format="%ct" -- "\$doc_path" 2>/dev/null || echo 0)
|
|
491
|
+
NOW=$(date +%s)
|
|
492
|
+
DAYS_OLD=$(( (NOW - LAST_MODIFIED) / 86400 ))
|
|
493
|
+
if [ "\$DAYS_OLD" -gt "\$STALE_THRESHOLD" ]; then
|
|
494
|
+
echo "STALE: \$doc_path (\$DAYS_OLD days old)"
|
|
495
|
+
codex check-docs \\
|
|
496
|
+
--approval-policy suggest \\
|
|
497
|
+
--file "\$doc_path" \\
|
|
498
|
+
--staleness-days \$DAYS_OLD
|
|
499
|
+
fi
|
|
500
|
+
done
|
|
501
|
+
`,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get a CI template by key.
|
|
506
|
+
*
|
|
507
|
+
* @param {string} templateKey - One of the CI_TEMPLATES keys
|
|
508
|
+
* @returns {string|null} Template content string or null if not found
|
|
509
|
+
*/
|
|
510
|
+
function getCiTemplate(templateKey) {
|
|
511
|
+
const meta = CI_TEMPLATES.find(t => t.key === templateKey);
|
|
512
|
+
if (!meta) return null;
|
|
513
|
+
return TEMPLATE_CONTENT[templateKey] || null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
// 3. Adoption Signal Gate
|
|
518
|
+
// ---------------------------------------------------------------------------
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Gate thresholds: each gate defines a minimum usage signal before activation.
|
|
522
|
+
* Values represent counts of relevant events in snapshot history.
|
|
523
|
+
*/
|
|
524
|
+
const GATE_THRESHOLDS = {
|
|
525
|
+
'ci-templates': {
|
|
526
|
+
metric: 'auditCount',
|
|
527
|
+
threshold: 3,
|
|
528
|
+
description: 'Activate CI templates after 3+ successful audits.',
|
|
529
|
+
},
|
|
530
|
+
'mcp-advanced': {
|
|
531
|
+
metric: 'auditCount',
|
|
532
|
+
threshold: 5,
|
|
533
|
+
description: 'Activate advanced MCP packs after 5+ audits.',
|
|
534
|
+
},
|
|
535
|
+
'multi-pack': {
|
|
536
|
+
metric: 'auditCount',
|
|
537
|
+
threshold: 2,
|
|
538
|
+
description: 'Activate multi-pack composition after 2+ audits.',
|
|
539
|
+
},
|
|
540
|
+
'trend-reports': {
|
|
541
|
+
metric: 'auditCount',
|
|
542
|
+
threshold: 3,
|
|
543
|
+
description: 'Activate trend reports after 3+ snapshots.',
|
|
544
|
+
},
|
|
545
|
+
'governance-upgrade': {
|
|
546
|
+
metric: 'averageScore',
|
|
547
|
+
threshold: 70,
|
|
548
|
+
description: 'Suggest governance upgrade when average score exceeds 70.',
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Check if an adoption gate should be activated based on local telemetry.
|
|
554
|
+
*
|
|
555
|
+
* @param {string} gateKey - Key from GATE_THRESHOLDS
|
|
556
|
+
* @param {string} dir - Project directory to read snapshots from
|
|
557
|
+
* @returns {{ activated: boolean, current: number, threshold: number, gate: string, description: string }}
|
|
558
|
+
*/
|
|
559
|
+
function checkAdoptionGate(gateKey, dir) {
|
|
560
|
+
const gate = GATE_THRESHOLDS[gateKey];
|
|
561
|
+
if (!gate) {
|
|
562
|
+
return {
|
|
563
|
+
activated: false,
|
|
564
|
+
current: 0,
|
|
565
|
+
threshold: 0,
|
|
566
|
+
gate: gateKey,
|
|
567
|
+
description: `Unknown gate "${gateKey}".`,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const history = getCodexHistory(dir, 100);
|
|
572
|
+
let current = 0;
|
|
573
|
+
|
|
574
|
+
switch (gate.metric) {
|
|
575
|
+
case 'auditCount':
|
|
576
|
+
current = history.length;
|
|
577
|
+
break;
|
|
578
|
+
case 'averageScore': {
|
|
579
|
+
const scores = history
|
|
580
|
+
.map(e => e.summary?.score)
|
|
581
|
+
.filter(s => typeof s === 'number');
|
|
582
|
+
current = scores.length > 0
|
|
583
|
+
? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length)
|
|
584
|
+
: 0;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
default:
|
|
588
|
+
current = 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
activated: current >= gate.threshold,
|
|
593
|
+
current,
|
|
594
|
+
threshold: gate.threshold,
|
|
595
|
+
gate: gateKey,
|
|
596
|
+
description: gate.description,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
// Exports
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
module.exports = {
|
|
605
|
+
composePacks,
|
|
606
|
+
getCiTemplate,
|
|
607
|
+
CI_TEMPLATES,
|
|
608
|
+
checkAdoptionGate,
|
|
609
|
+
// Internals exposed for testing
|
|
610
|
+
GATE_THRESHOLDS,
|
|
611
|
+
PACK_DEPENDENCY_ORDER,
|
|
612
|
+
PACK_SPECIFICITY,
|
|
613
|
+
DEFAULT_SIZE_BUDGET,
|
|
614
|
+
};
|