@nerviq/cli 0.0.1 → 0.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +181 -0
  2. package/LICENSE +21 -0
  3. package/README.md +447 -0
  4. package/bin/cli.js +749 -0
  5. package/content/case-study-template.md +91 -0
  6. package/content/claims-governance.md +37 -0
  7. package/content/claude-code/audit-repo/SKILL.md +20 -0
  8. package/content/claude-native-integration.md +60 -0
  9. package/content/devto-article.json +9 -0
  10. package/content/launch-posts.md +226 -0
  11. package/content/pilot-rollout-kit.md +30 -0
  12. package/content/release-checklist.md +31 -0
  13. package/package.json +53 -4
  14. package/src/activity.js +529 -0
  15. package/src/aider/activity.js +226 -0
  16. package/src/aider/config-parser.js +166 -0
  17. package/src/aider/context.js +158 -0
  18. package/src/aider/deep-review.js +316 -0
  19. package/src/aider/domain-packs.js +278 -0
  20. package/src/aider/freshness.js +168 -0
  21. package/src/aider/governance.js +253 -0
  22. package/src/aider/interactive.js +334 -0
  23. package/src/aider/mcp-packs.js +98 -0
  24. package/src/aider/patch.js +214 -0
  25. package/src/aider/plans.js +186 -0
  26. package/src/aider/premium.js +360 -0
  27. package/src/aider/setup.js +404 -0
  28. package/src/aider/techniques.js +1323 -0
  29. package/src/analyze.js +821 -0
  30. package/src/audit.js +1003 -0
  31. package/src/badge.js +13 -0
  32. package/src/benchmark.js +339 -0
  33. package/src/claudex-sync.json +7 -0
  34. package/src/codex/activity.js +324 -0
  35. package/src/codex/config-parser.js +183 -0
  36. package/src/codex/context.js +221 -0
  37. package/src/codex/deep-review.js +493 -0
  38. package/src/codex/domain-packs.js +372 -0
  39. package/src/codex/freshness.js +167 -0
  40. package/src/codex/governance.js +192 -0
  41. package/src/codex/interactive.js +618 -0
  42. package/src/codex/mcp-packs.js +660 -0
  43. package/src/codex/patch.js +209 -0
  44. package/src/codex/plans.js +251 -0
  45. package/src/codex/premium.js +614 -0
  46. package/src/codex/setup.js +603 -0
  47. package/src/codex/techniques.js +2649 -0
  48. package/src/context.js +272 -0
  49. package/src/copilot/activity.js +309 -0
  50. package/src/copilot/config-parser.js +226 -0
  51. package/src/copilot/context.js +197 -0
  52. package/src/copilot/deep-review.js +346 -0
  53. package/src/copilot/domain-packs.js +350 -0
  54. package/src/copilot/freshness.js +197 -0
  55. package/src/copilot/governance.js +222 -0
  56. package/src/copilot/interactive.js +406 -0
  57. package/src/copilot/mcp-packs.js +572 -0
  58. package/src/copilot/patch.js +238 -0
  59. package/src/copilot/plans.js +253 -0
  60. package/src/copilot/premium.js +450 -0
  61. package/src/copilot/setup.js +488 -0
  62. package/src/copilot/techniques.js +1822 -0
  63. package/src/cursor/activity.js +301 -0
  64. package/src/cursor/config-parser.js +265 -0
  65. package/src/cursor/context.js +236 -0
  66. package/src/cursor/deep-review.js +334 -0
  67. package/src/cursor/domain-packs.js +346 -0
  68. package/src/cursor/freshness.js +214 -0
  69. package/src/cursor/governance.js +229 -0
  70. package/src/cursor/interactive.js +391 -0
  71. package/src/cursor/mcp-packs.js +571 -0
  72. package/src/cursor/patch.js +243 -0
  73. package/src/cursor/plans.js +254 -0
  74. package/src/cursor/premium.js +468 -0
  75. package/src/cursor/setup.js +488 -0
  76. package/src/cursor/techniques.js +1786 -0
  77. package/src/deep-review.js +345 -0
  78. package/src/domain-packs.js +364 -0
  79. package/src/formatters/sarif.js +115 -0
  80. package/src/gemini/activity.js +402 -0
  81. package/src/gemini/config-parser.js +275 -0
  82. package/src/gemini/context.js +221 -0
  83. package/src/gemini/deep-review.js +559 -0
  84. package/src/gemini/domain-packs.js +371 -0
  85. package/src/gemini/freshness.js +204 -0
  86. package/src/gemini/governance.js +201 -0
  87. package/src/gemini/interactive.js +860 -0
  88. package/src/gemini/mcp-packs.js +658 -0
  89. package/src/gemini/patch.js +229 -0
  90. package/src/gemini/plans.js +269 -0
  91. package/src/gemini/premium.js +759 -0
  92. package/src/gemini/setup.js +692 -0
  93. package/src/gemini/techniques.js +2084 -0
  94. package/src/governance.js +523 -0
  95. package/src/harmony/advisor.js +383 -0
  96. package/src/harmony/audit.js +303 -0
  97. package/src/harmony/canon.js +444 -0
  98. package/src/harmony/cli.js +331 -0
  99. package/src/harmony/drift.js +401 -0
  100. package/src/harmony/governance.js +313 -0
  101. package/src/harmony/memory.js +238 -0
  102. package/src/harmony/sync.js +458 -0
  103. package/src/harmony/watch.js +336 -0
  104. package/src/index.js +256 -0
  105. package/src/insights.js +119 -0
  106. package/src/interactive.js +118 -0
  107. package/src/mcp-packs.js +597 -0
  108. package/src/opencode/activity.js +286 -0
  109. package/src/opencode/config-parser.js +109 -0
  110. package/src/opencode/context.js +247 -0
  111. package/src/opencode/deep-review.js +313 -0
  112. package/src/opencode/domain-packs.js +240 -0
  113. package/src/opencode/freshness.js +158 -0
  114. package/src/opencode/governance.js +159 -0
  115. package/src/opencode/interactive.js +392 -0
  116. package/src/opencode/mcp-packs.js +474 -0
  117. package/src/opencode/patch.js +184 -0
  118. package/src/opencode/plans.js +231 -0
  119. package/src/opencode/premium.js +413 -0
  120. package/src/opencode/setup.js +449 -0
  121. package/src/opencode/techniques.js +1713 -0
  122. package/src/plans.js +655 -0
  123. package/src/secret-patterns.js +30 -0
  124. package/src/setup.js +1274 -0
  125. package/src/synergy/adaptive.js +261 -0
  126. package/src/synergy/compensation.js +156 -0
  127. package/src/synergy/evidence.js +193 -0
  128. package/src/synergy/learning.js +184 -0
  129. package/src/synergy/patterns.js +227 -0
  130. package/src/synergy/ranking.js +83 -0
  131. package/src/synergy/report.js +163 -0
  132. package/src/synergy/routing.js +152 -0
  133. package/src/techniques.js +1354 -0
  134. package/src/watch.js +229 -0
  135. package/src/windsurf/activity.js +302 -0
  136. package/src/windsurf/config-parser.js +267 -0
  137. package/src/windsurf/context.js +249 -0
  138. package/src/windsurf/deep-review.js +337 -0
  139. package/src/windsurf/domain-packs.js +348 -0
  140. package/src/windsurf/freshness.js +215 -0
  141. package/src/windsurf/governance.js +231 -0
  142. package/src/windsurf/interactive.js +388 -0
  143. package/src/windsurf/mcp-packs.js +535 -0
  144. package/src/windsurf/patch.js +231 -0
  145. package/src/windsurf/plans.js +247 -0
  146. package/src/windsurf/premium.js +467 -0
  147. package/src/windsurf/setup.js +471 -0
  148. package/src/windsurf/techniques.js +1758 -0
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Codex Patch Intelligence — CP-09
3
+ *
4
+ * Safe patching of existing Codex files using managed blocks.
5
+ * Supports AGENTS.md (HTML comment blocks) and config.toml (TOML comment blocks).
6
+ *
7
+ * Managed blocks are sections that nerviq controls.
8
+ * Hand-authored content outside managed blocks is preserved.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { writeRollbackArtifact, writeActivityArtifact } = require('../activity');
14
+
15
+ // Managed block markers
16
+ const MANAGED_START_MD = '<!-- nerviq:managed:start -->';
17
+ const MANAGED_END_MD = '<!-- nerviq:managed:end -->';
18
+ const MANAGED_START_TOML = '# <!-- nerviq:managed:start -->';
19
+ const MANAGED_END_TOML = '# <!-- nerviq:managed:end -->';
20
+
21
+ /**
22
+ * Extract managed blocks from a file.
23
+ * Returns { before, managed, after } where managed is the content between markers.
24
+ */
25
+ function extractManagedBlock(content, startMarker, endMarker) {
26
+ const startIdx = content.indexOf(startMarker);
27
+ const endIdx = content.indexOf(endMarker);
28
+
29
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
30
+ return { before: content, managed: null, after: '' };
31
+ }
32
+
33
+ return {
34
+ before: content.substring(0, startIdx),
35
+ managed: content.substring(startIdx + startMarker.length, endIdx).trim(),
36
+ after: content.substring(endIdx + endMarker.length),
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Replace or insert a managed block in a file.
42
+ * If the file already has managed markers, replace the content between them.
43
+ * If not, append the managed block at the end.
44
+ */
45
+ function upsertManagedBlock(content, newManaged, startMarker, endMarker) {
46
+ const { before, managed, after } = extractManagedBlock(content, startMarker, endMarker);
47
+
48
+ if (managed !== null) {
49
+ // Replace existing managed block
50
+ return `${before}${startMarker}\n${newManaged}\n${endMarker}${after}`;
51
+ }
52
+
53
+ // Append new managed block
54
+ const separator = content.endsWith('\n') ? '\n' : '\n\n';
55
+ return `${content}${separator}${startMarker}\n${newManaged}\n${endMarker}\n`;
56
+ }
57
+
58
+ /**
59
+ * Patch AGENTS.md with managed sections.
60
+ * Preserves all hand-authored content.
61
+ */
62
+ function patchAgentsMd(existingContent, managedSections) {
63
+ const newManaged = Object.entries(managedSections)
64
+ .map(([section, content]) => `## ${section}\n${content}`)
65
+ .join('\n\n');
66
+
67
+ return upsertManagedBlock(existingContent, newManaged, MANAGED_START_MD, MANAGED_END_MD);
68
+ }
69
+
70
+ /**
71
+ * Patch config.toml by safely adding new sections.
72
+ * Never weakens existing sandbox or approval posture.
73
+ * Only adds new [section] blocks that don't already exist.
74
+ */
75
+ function patchConfigToml(existingContent, newSections) {
76
+ const existingSections = new Set();
77
+ const sectionPattern = /^\[([^\]]+)\]/gm;
78
+ let match;
79
+ while ((match = sectionPattern.exec(existingContent)) !== null) {
80
+ existingSections.add(match[1]);
81
+ }
82
+
83
+ const additions = [];
84
+ for (const [section, content] of Object.entries(newSections)) {
85
+ if (!existingSections.has(section)) {
86
+ additions.push(`[${section}]\n${content}`);
87
+ }
88
+ }
89
+
90
+ if (additions.length === 0) return existingContent;
91
+
92
+ const newManaged = additions.join('\n\n');
93
+ return upsertManagedBlock(existingContent, newManaged, MANAGED_START_TOML, MANAGED_END_TOML);
94
+ }
95
+
96
+ /**
97
+ * Detect if a repo has both Claude and Codex surfaces (mixed-agent repo).
98
+ */
99
+ function detectMixedAgentRepo(dir) {
100
+ const hasClaude = fs.existsSync(path.join(dir, 'CLAUDE.md')) ||
101
+ fs.existsSync(path.join(dir, '.claude'));
102
+ const hasCodex = fs.existsSync(path.join(dir, 'AGENTS.md')) ||
103
+ fs.existsSync(path.join(dir, '.codex'));
104
+
105
+ return {
106
+ isMixed: hasClaude && hasCodex,
107
+ hasClaude,
108
+ hasCodex,
109
+ guidance: hasClaude && hasCodex
110
+ ? 'This is a mixed-agent repo. Keep Claude instructions in CLAUDE.md and Codex instructions in AGENTS.md. Do not merge them.'
111
+ : null,
112
+ };
113
+ }
114
+
115
+ /**
116
+ * Generate a diff preview for a patch operation.
117
+ */
118
+ function generatePatchPreview(originalContent, patchedContent, filePath) {
119
+ const origLines = originalContent.split('\n');
120
+ const patchLines = patchedContent.split('\n');
121
+
122
+ const lines = [`--- ${filePath} (original)`, `+++ ${filePath} (patched)`];
123
+
124
+ // Simple line-by-line diff showing only changed sections
125
+ let inChange = false;
126
+ for (let i = 0; i < Math.max(origLines.length, patchLines.length); i++) {
127
+ const orig = origLines[i] || '';
128
+ const patched = patchLines[i] || '';
129
+ if (orig !== patched) {
130
+ if (!inChange) {
131
+ lines.push(`@@ line ${i + 1} @@`);
132
+ inChange = true;
133
+ }
134
+ if (i < origLines.length) lines.push(`-${orig}`);
135
+ if (i < patchLines.length) lines.push(`+${patched}`);
136
+ } else {
137
+ inChange = false;
138
+ }
139
+ }
140
+
141
+ return lines.join('\n');
142
+ }
143
+
144
+ /**
145
+ * Apply a patch to a file with backup and rollback support.
146
+ */
147
+ function applyPatch(dir, filePath, patchFn, options = {}) {
148
+ const fullPath = path.join(dir, filePath);
149
+ const dryRun = options.dryRun === true;
150
+
151
+ if (!fs.existsSync(fullPath)) {
152
+ return { success: false, reason: `${filePath} does not exist`, preview: null };
153
+ }
154
+
155
+ const original = fs.readFileSync(fullPath, 'utf8');
156
+ const patched = patchFn(original);
157
+
158
+ if (patched === original) {
159
+ return { success: true, reason: 'no changes needed', preview: null, unchanged: true };
160
+ }
161
+
162
+ const preview = generatePatchPreview(original, patched, filePath);
163
+
164
+ if (dryRun) {
165
+ return { success: true, reason: 'dry run', preview, unchanged: false };
166
+ }
167
+
168
+ // Backup + write
169
+ const backupPath = fullPath + '.claudex-backup';
170
+ fs.writeFileSync(backupPath, original, 'utf8');
171
+ fs.writeFileSync(fullPath, patched, 'utf8');
172
+
173
+ // Rollback artifact
174
+ const rollback = writeRollbackArtifact(dir, {
175
+ sourcePlan: 'codex-patch',
176
+ patchedFiles: [filePath],
177
+ backupFiles: [{ original: filePath, backup: path.relative(dir, backupPath) }],
178
+ rollbackInstructions: [`Restore ${filePath} from ${path.relative(dir, backupPath)}`],
179
+ });
180
+
181
+ const activity = writeActivityArtifact(dir, 'codex-patch', {
182
+ platform: 'codex',
183
+ patchedFiles: [filePath],
184
+ rollbackArtifact: rollback.relativePath,
185
+ });
186
+
187
+ return {
188
+ success: true,
189
+ reason: 'patched',
190
+ preview,
191
+ unchanged: false,
192
+ rollbackArtifact: rollback.relativePath,
193
+ activityArtifact: activity.relativePath,
194
+ };
195
+ }
196
+
197
+ module.exports = {
198
+ MANAGED_START_MD,
199
+ MANAGED_END_MD,
200
+ MANAGED_START_TOML,
201
+ MANAGED_END_TOML,
202
+ extractManagedBlock,
203
+ upsertManagedBlock,
204
+ patchAgentsMd,
205
+ patchConfigToml,
206
+ detectMixedAgentRepo,
207
+ generatePatchPreview,
208
+ applyPatch,
209
+ };
@@ -0,0 +1,251 @@
1
+ const path = require('path');
2
+ const { version } = require('../../package.json');
3
+ const { audit } = require('../audit');
4
+ const { analyzeProject } = require('../analyze');
5
+ const { buildCodexSetupFiles } = require('./setup');
6
+ const { getCodexMcpPreflight } = require('./mcp-packs');
7
+
8
+ function maturityFromScore(score) {
9
+ if (score >= 81) return 'mature';
10
+ if (score >= 61) return 'solid';
11
+ if (score >= 41) return 'developing';
12
+ if (score >= 21) return 'weak';
13
+ return 'raw';
14
+ }
15
+
16
+ function triggerMatchesFile(result, filePath) {
17
+ if (filePath === 'AGENTS.md') {
18
+ return result.file === 'AGENTS.md' || result.category === 'instructions' || result.category === 'review' || result.category === 'quality-deep';
19
+ }
20
+ if (filePath === '.codex/config.toml') {
21
+ return result.file === '.codex/config.toml' || ['config', 'trust', 'hooks', 'mcp', 'agents', 'automation', 'local'].includes(result.category);
22
+ }
23
+ if (filePath === '.codex/rules/README.md') {
24
+ return result.category === 'rules';
25
+ }
26
+ if (filePath === '.codex/hooks.json') {
27
+ return result.category === 'hooks';
28
+ }
29
+ if (filePath === '.agents/skills/README.md') {
30
+ return result.category === 'skills';
31
+ }
32
+ if (filePath === '.codex/agents/README.md') {
33
+ return result.category === 'agents';
34
+ }
35
+ if (filePath === '.codex/config.toml (MCP append)') {
36
+ return result.category === 'mcp';
37
+ }
38
+ if (filePath === '.github/workflows/codex-review.yml') {
39
+ return result.category === 'automation' || result.category === 'review';
40
+ }
41
+ return result.file === filePath;
42
+ }
43
+
44
+ function uniqueValues(items = []) {
45
+ return [...new Set(items.filter(Boolean))];
46
+ }
47
+
48
+ function buildDomainPackGuidance(report) {
49
+ return (report.recommendedDomainPacks || []).map((pack) => ({
50
+ key: pack.key,
51
+ label: pack.label,
52
+ useWhen: pack.useWhen,
53
+ matchReasons: pack.matchReasons || [],
54
+ recommendedModules: pack.recommendedModules || [],
55
+ recommendedProposalFamilies: pack.recommendedProposalFamilies || [],
56
+ recommendedSurfaces: pack.recommendedSurfaces || [],
57
+ benchmarkFocus: pack.benchmarkFocus || [],
58
+ }));
59
+ }
60
+
61
+ function selectPackContext(filePath, domainPackGuidance = []) {
62
+ return domainPackGuidance
63
+ .filter((pack) => {
64
+ if (!Array.isArray(pack.recommendedSurfaces) || pack.recommendedSurfaces.length === 0) {
65
+ return true;
66
+ }
67
+ return pack.recommendedSurfaces.some((surface) => filePath === surface || filePath.startsWith(surface));
68
+ })
69
+ .map((pack) => ({
70
+ key: pack.key,
71
+ label: pack.label,
72
+ why: pack.matchReasons[0] || pack.useWhen,
73
+ recommendedModules: (pack.recommendedModules || []).slice(0, 3),
74
+ recommendedProposalFamilies: (pack.recommendedProposalFamilies || []).slice(0, 3),
75
+ benchmarkFocus: (pack.benchmarkFocus || []).slice(0, 2),
76
+ }));
77
+ }
78
+
79
+ const PROPOSAL_FAMILIES = {
80
+ 'AGENTS.md': {
81
+ id: 'codex-agents-md',
82
+ title: 'Create Codex AGENTS.md baseline',
83
+ module: 'instructions',
84
+ risk: 'low',
85
+ confidence: 'high',
86
+ },
87
+ '.codex/config.toml': {
88
+ id: 'codex-config',
89
+ title: 'Create Codex config baseline',
90
+ module: 'config',
91
+ risk: 'medium',
92
+ confidence: 'high',
93
+ },
94
+ '.codex/rules/README.md': {
95
+ id: 'codex-rules',
96
+ title: 'Create Codex rules starter',
97
+ module: 'rules',
98
+ risk: 'low',
99
+ confidence: 'high',
100
+ },
101
+ '.codex/hooks.json': {
102
+ id: 'codex-hooks',
103
+ title: 'Create Codex hooks scaffold',
104
+ module: 'hooks',
105
+ risk: 'medium',
106
+ confidence: 'medium',
107
+ },
108
+ '.agents/skills/README.md': {
109
+ id: 'codex-skills',
110
+ title: 'Create Codex skills starter',
111
+ module: 'skills',
112
+ risk: 'low',
113
+ confidence: 'high',
114
+ },
115
+ '.codex/agents/README.md': {
116
+ id: 'codex-subagents',
117
+ title: 'Create Codex custom agents starter',
118
+ module: 'agents',
119
+ risk: 'low',
120
+ confidence: 'medium',
121
+ },
122
+ '.codex/config.toml (MCP append)': {
123
+ id: 'codex-mcp',
124
+ title: 'Add recommended MCP packs to Codex config',
125
+ module: 'mcp',
126
+ risk: 'medium',
127
+ confidence: 'high',
128
+ },
129
+ '.github/workflows/codex-review.yml': {
130
+ id: 'codex-ci-review',
131
+ title: 'Create Codex CI review workflow',
132
+ module: 'ci',
133
+ risk: 'medium',
134
+ confidence: 'medium',
135
+ },
136
+ };
137
+
138
+ function resolveProposalFamily(file) {
139
+ const familyFromFile = file.family
140
+ ? Object.values(PROPOSAL_FAMILIES).find(f => f.id === file.family)
141
+ : null;
142
+ return familyFromFile || PROPOSAL_FAMILIES[file.path] || {
143
+ id: 'codex-unknown',
144
+ title: `Create ${file.path}`,
145
+ module: 'unknown',
146
+ risk: 'medium',
147
+ confidence: 'low',
148
+ };
149
+ }
150
+
151
+ function proposalForFile(file, auditResult, domainPackGuidance = []) {
152
+ const triggers = auditResult.results
153
+ .filter((result) => result.passed === false && triggerMatchesFile(result, file.path))
154
+ .sort((a, b) => {
155
+ const weight = { critical: 3, high: 2, medium: 1, low: 0 };
156
+ return (weight[b.impact] || 0) - (weight[a.impact] || 0);
157
+ })
158
+ .slice(0, 6)
159
+ .map((result) => ({
160
+ key: result.key,
161
+ name: result.name,
162
+ impact: result.impact,
163
+ fix: result.fix,
164
+ }));
165
+ const packContext = selectPackContext(file.path, domainPackGuidance);
166
+
167
+ const familyMeta = resolveProposalFamily(file);
168
+
169
+ return {
170
+ id: familyMeta.id,
171
+ title: familyMeta.title,
172
+ module: familyMeta.module,
173
+ risk: familyMeta.risk,
174
+ confidence: familyMeta.confidence,
175
+ triggers,
176
+ rationale: uniqueValues([
177
+ ...triggers.map((item) => item.fix),
178
+ ...packContext.map((item) => `Supports ${item.label} rollout guidance: ${item.why}`),
179
+ ]),
180
+ packContext,
181
+ files: [{
182
+ path: file.path,
183
+ action: file.action,
184
+ currentState: file.currentState,
185
+ proposedState: file.proposedState,
186
+ content: file.content,
187
+ preview: file.content.split('\n').slice(0, 12).join('\n'),
188
+ diffPreview: [
189
+ `--- missing`,
190
+ `+++ ${file.path}`,
191
+ ...file.content.split('\n').slice(0, 12).map((line) => `+${line}`),
192
+ ].join('\n'),
193
+ }],
194
+ readyToApply: true,
195
+ };
196
+ }
197
+
198
+ async function buildCodexProposalBundle(options) {
199
+ const auditResult = await audit({ ...options, platform: 'codex', silent: true });
200
+ const analysisReport = await analyzeProject({ ...options, platform: 'codex', mode: 'suggest-only' });
201
+ const domainPackGuidance = buildDomainPackGuidance(analysisReport);
202
+ const { files } = buildCodexSetupFiles(options);
203
+ const proposals = files.map((file) => proposalForFile(file, auditResult, domainPackGuidance));
204
+
205
+ // MCP preflight warnings for any MCP proposals
206
+ const mcpProposal = proposals.find(p => p.id === 'codex-mcp');
207
+ let mcpPreflightWarnings = [];
208
+ if (mcpProposal) {
209
+ const mcpFile = files.find(f => f.family === 'codex-mcp');
210
+ if (mcpFile && mcpFile.content) {
211
+ const packKeyMatches = mcpFile.content.match(/\[mcp_servers\.([^\]]+)\]/g) || [];
212
+ const detectedKeys = packKeyMatches.map(m => m.replace('[mcp_servers.', '').replace(']', ''));
213
+ mcpPreflightWarnings = getCodexMcpPreflight(detectedKeys)
214
+ .filter(p => !p.safe)
215
+ .map(p => ({ key: p.key, label: p.label, warning: p.warning }));
216
+ }
217
+ }
218
+
219
+ return {
220
+ schemaVersion: 2,
221
+ generatedBy: `nerviq@${version}`,
222
+ createdAt: new Date().toISOString(),
223
+ platform: 'codex',
224
+ directory: options.dir,
225
+ projectSummary: {
226
+ name: path.basename(options.dir),
227
+ score: auditResult.score,
228
+ organicScore: auditResult.organicScore,
229
+ maturity: maturityFromScore(auditResult.score),
230
+ domains: analysisReport.projectSummary.domains || [],
231
+ },
232
+ strengthsPreserved: auditResult.results
233
+ .filter((item) => item.passed === true)
234
+ .slice(0, 5)
235
+ .map((item) => item.name),
236
+ topNextActions: auditResult.topNextActions,
237
+ recommendedDomainPacks: domainPackGuidance,
238
+ proposalFamilies: [...new Set(proposals.map(p => p.id))],
239
+ optionalModules: analysisReport.optionalModules || [],
240
+ riskNotes: uniqueValues([
241
+ ...(analysisReport.riskNotes || []),
242
+ ...((auditResult.platformCaveats || []).map((item) => item.message)),
243
+ ]),
244
+ mcpPreflightWarnings,
245
+ proposals,
246
+ };
247
+ }
248
+
249
+ module.exports = {
250
+ buildCodexProposalBundle,
251
+ };