@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,261 @@
1
+ /**
2
+ * S8. Adaptive Project Configuration
3
+ *
4
+ * Detects project changes and propagates configuration updates
5
+ * across all active platforms automatically.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const PLATFORM_CONFIG_FILES = {
12
+ claude: ['CLAUDE.md', '.claude/settings.json', '.claude/commands', '.claude/rules'],
13
+ codex: ['AGENTS.md', 'codex.json', '.codex'],
14
+ gemini: ['GEMINI.md', '.gemini/settings.json'],
15
+ copilot: ['.github/copilot-instructions.md', '.github/copilot'],
16
+ cursor: ['.cursor/rules', '.cursorrules'],
17
+ };
18
+
19
+ const CHANGE_DETECTORS = [
20
+ {
21
+ type: 'new-api-route',
22
+ detect: (current, previous) => {
23
+ const currentRoutes = (current.routes || []);
24
+ const previousRoutes = (previous.routes || []);
25
+ const newRoutes = currentRoutes.filter(r => !previousRoutes.includes(r));
26
+ return newRoutes.length > 0 ? { newRoutes } : null;
27
+ },
28
+ impact: (data) => ({
29
+ description: `New API route(s): ${data.newRoutes.join(', ')}`,
30
+ affectedPlatforms: ['claude', 'codex', 'gemini', 'copilot', 'cursor'],
31
+ recommendedAction: 'Update architecture documentation in all instruction files',
32
+ priority: 'medium',
33
+ }),
34
+ },
35
+ {
36
+ type: 'new-dependency',
37
+ detect: (current, previous) => {
38
+ const currentDeps = Object.keys(current.dependencies || {});
39
+ const previousDeps = Object.keys(previous.dependencies || {});
40
+ const newDeps = currentDeps.filter(d => !previousDeps.includes(d));
41
+ return newDeps.length > 0 ? { newDeps } : null;
42
+ },
43
+ impact: (data) => ({
44
+ description: `New dependencies: ${data.newDeps.join(', ')}`,
45
+ affectedPlatforms: ['claude', 'codex', 'gemini', 'copilot', 'cursor'],
46
+ recommendedAction: 'Add dependency context to instruction files; check for MCP pack needs',
47
+ priority: 'low',
48
+ }),
49
+ },
50
+ {
51
+ type: 'new-ci-workflow',
52
+ detect: (current, previous) => {
53
+ const currentCI = (current.ciWorkflows || []);
54
+ const previousCI = (previous.ciWorkflows || []);
55
+ const newCI = currentCI.filter(w => !previousCI.includes(w));
56
+ return newCI.length > 0 ? { newCI } : null;
57
+ },
58
+ impact: (data) => ({
59
+ description: `New CI workflow(s): ${data.newCI.join(', ')}`,
60
+ affectedPlatforms: ['claude', 'codex', 'copilot'],
61
+ recommendedAction: 'Align CI review commands across platforms',
62
+ priority: 'medium',
63
+ }),
64
+ },
65
+ {
66
+ type: 'new-database',
67
+ detect: (current, previous) => {
68
+ const currentDBs = (current.databases || []);
69
+ const previousDBs = (previous.databases || []);
70
+ const newDBs = currentDBs.filter(db => !previousDBs.includes(db));
71
+ return newDBs.length > 0 ? { newDBs } : null;
72
+ },
73
+ impact: (data) => ({
74
+ description: `New database(s): ${data.newDBs.join(', ')}`,
75
+ affectedPlatforms: ['claude', 'codex', 'gemini', 'copilot', 'cursor'],
76
+ recommendedAction: 'Add database MCP on all platforms; update architecture docs',
77
+ priority: 'high',
78
+ }),
79
+ },
80
+ {
81
+ type: 'stack-change',
82
+ detect: (current, previous) => {
83
+ const currentStacks = (current.stacks || []);
84
+ const previousStacks = (previous.stacks || []);
85
+ const added = currentStacks.filter(s => !previousStacks.includes(s));
86
+ const removed = previousStacks.filter(s => !currentStacks.includes(s));
87
+ return (added.length > 0 || removed.length > 0) ? { added, removed } : null;
88
+ },
89
+ impact: (data) => ({
90
+ description: `Stack changes — added: ${data.added.join(', ') || 'none'}, removed: ${data.removed.join(', ') || 'none'}`,
91
+ affectedPlatforms: ['claude', 'codex', 'gemini', 'copilot', 'cursor'],
92
+ recommendedAction: 'Reconfigure domain packs and technique checks for new stack',
93
+ priority: 'high',
94
+ }),
95
+ },
96
+ {
97
+ type: 'config-file-change',
98
+ detect: (current, previous) => {
99
+ const currentConfigs = (current.configFiles || []);
100
+ const previousConfigs = (previous.configFiles || []);
101
+ const changed = currentConfigs.filter(c => !previousConfigs.includes(c));
102
+ return changed.length > 0 ? { changed } : null;
103
+ },
104
+ impact: (data) => ({
105
+ description: `Config file changes: ${data.changed.join(', ')}`,
106
+ affectedPlatforms: ['claude', 'codex', 'gemini', 'copilot', 'cursor'],
107
+ recommendedAction: 'Review and sync configuration changes across platforms',
108
+ priority: 'low',
109
+ }),
110
+ },
111
+ ];
112
+
113
+ /**
114
+ * Build a simple canonical model of the project state for comparison.
115
+ */
116
+ function buildSimpleCanon(dir) {
117
+ const canon = {
118
+ dependencies: {},
119
+ routes: [],
120
+ ciWorkflows: [],
121
+ databases: [],
122
+ stacks: [],
123
+ configFiles: [],
124
+ };
125
+
126
+ // Read package.json dependencies
127
+ try {
128
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
129
+ canon.dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
130
+
131
+ // Detect databases from dependencies
132
+ const dbIndicators = {
133
+ pg: 'postgres', mysql2: 'mysql', mongodb: 'mongodb', mongoose: 'mongodb',
134
+ redis: 'redis', ioredis: 'redis', sqlite3: 'sqlite', prisma: 'prisma',
135
+ typeorm: 'typeorm', sequelize: 'sequelize',
136
+ };
137
+ for (const dep of Object.keys(canon.dependencies)) {
138
+ if (dbIndicators[dep]) canon.databases.push(dbIndicators[dep]);
139
+ }
140
+ } catch { /* no package.json */ }
141
+
142
+ // Detect CI workflows
143
+ try {
144
+ const ghDir = path.join(dir, '.github', 'workflows');
145
+ if (fs.existsSync(ghDir)) {
146
+ canon.ciWorkflows = fs.readdirSync(ghDir).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
147
+ }
148
+ } catch { /* no workflows */ }
149
+
150
+ // Detect config files
151
+ for (const configs of Object.values(PLATFORM_CONFIG_FILES)) {
152
+ for (const configPath of configs) {
153
+ try {
154
+ if (fs.existsSync(path.join(dir, configPath))) {
155
+ canon.configFiles.push(configPath);
156
+ }
157
+ } catch { /* skip */ }
158
+ }
159
+ }
160
+
161
+ return canon;
162
+ }
163
+
164
+ /**
165
+ * Detect project changes by comparing current state against previous canon.
166
+ *
167
+ * @param {string} dir - Project directory
168
+ * @param {Object} previousCanon - Previous canonical model (from last run)
169
+ * @returns {Object} Detected changes with cross-platform impact
170
+ */
171
+ function detectProjectChanges(dir, previousCanon) {
172
+ const currentCanon = buildSimpleCanon(dir);
173
+ const prev = previousCanon || {};
174
+ const changes = [];
175
+
176
+ for (const detector of CHANGE_DETECTORS) {
177
+ const data = detector.detect(currentCanon, prev);
178
+ if (data) {
179
+ const impact = detector.impact(data);
180
+ changes.push({
181
+ type: detector.type,
182
+ ...impact,
183
+ data,
184
+ });
185
+ }
186
+ }
187
+
188
+ return {
189
+ changes,
190
+ currentCanon,
191
+ summary: changes.length > 0
192
+ ? `${changes.length} change(s) detected affecting ${new Set(changes.flatMap(c => c.affectedPlatforms)).size} platform(s)`
193
+ : 'No changes detected',
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Generate platform-native configuration updates for detected changes.
199
+ *
200
+ * @param {Object[]} changes - Changes from detectProjectChanges
201
+ * @returns {Object} Platform-specific update instructions
202
+ */
203
+ function generateAdaptiveUpdates(changes) {
204
+ if (!Array.isArray(changes) || changes.length === 0) {
205
+ return { updates: [], summary: 'No changes to propagate' };
206
+ }
207
+
208
+ const updates = [];
209
+
210
+ for (const change of changes) {
211
+ const platforms = change.affectedPlatforms || [];
212
+
213
+ for (const platform of platforms) {
214
+ const configFiles = PLATFORM_CONFIG_FILES[platform] || [];
215
+ const primaryConfig = configFiles[0];
216
+ if (!primaryConfig) continue;
217
+
218
+ let content = '';
219
+ let action = 'append';
220
+
221
+ switch (change.type) {
222
+ case 'new-api-route':
223
+ content = `\n## API Routes (auto-detected)\n${(change.data.newRoutes || []).map(r => `- ${r}`).join('\n')}\n`;
224
+ break;
225
+ case 'new-dependency':
226
+ content = `\n<!-- New dependencies: ${(change.data.newDeps || []).join(', ')} -->\n`;
227
+ action = 'append';
228
+ break;
229
+ case 'new-database':
230
+ content = `\n## Database\nThis project uses: ${(change.data.newDBs || []).join(', ')}\n`;
231
+ break;
232
+ case 'new-ci-workflow':
233
+ content = `\n## CI/CD\nWorkflows: ${(change.data.newCI || []).join(', ')}\n`;
234
+ break;
235
+ case 'stack-change':
236
+ content = `\n## Stack Update\nAdded: ${(change.data.added || []).join(', ') || 'none'}\nRemoved: ${(change.data.removed || []).join(', ') || 'none'}\n`;
237
+ break;
238
+ default:
239
+ content = `\n<!-- Change detected: ${change.description} -->\n`;
240
+ }
241
+
242
+ updates.push({
243
+ platform,
244
+ file: primaryConfig,
245
+ action,
246
+ content,
247
+ reason: change.description,
248
+ priority: change.priority || 'low',
249
+ });
250
+ }
251
+ }
252
+
253
+ const affectedPlatforms = new Set(updates.map(u => u.platform));
254
+
255
+ return {
256
+ updates,
257
+ summary: `${changes.length} change(s) detected, ${affectedPlatforms.size} platform(s) affected, ${updates.length} update(s) generated`,
258
+ };
259
+ }
260
+
261
+ module.exports = { detectProjectChanges, generateAdaptiveUpdates };
@@ -0,0 +1,156 @@
1
+ /**
2
+ * S4. Strength/Weakness Compensation
3
+ *
4
+ * Identifies where platforms complement each other and recommends
5
+ * platform additions to fill coverage gaps.
6
+ */
7
+
8
+ const { PLATFORM_CAPABILITIES } = require('./routing');
9
+
10
+ const AREA_LABELS = {
11
+ reasoning: 'Complex reasoning & analysis',
12
+ refactoring: 'Code refactoring',
13
+ debugging: 'Bug diagnosis & debugging',
14
+ CI: 'CI/CD pipeline integration',
15
+ IDE: 'IDE integration',
16
+ UI: 'UI/frontend work',
17
+ async: 'Async/background tasks',
18
+ review: 'Code review',
19
+ architecture: 'Architecture design',
20
+ inline: 'Inline code completion',
21
+ cloudTasks: 'Cloud-based tasks',
22
+ context: 'Large context handling',
23
+ sandbox: 'Sandboxed execution',
24
+ cloudAgent: 'Cloud agent mode',
25
+ background: 'Background processing',
26
+ automation: 'Workflow automation',
27
+ };
28
+
29
+ const WEAKNESS_THRESHOLD = 3;
30
+ const STRENGTH_THRESHOLD = 4;
31
+
32
+ /**
33
+ * Analyze where platforms compensate for each other's weaknesses.
34
+ *
35
+ * @param {string[]} activePlatforms - Currently active platforms
36
+ * @param {Object} [platformAudits] - Optional audit results per platform
37
+ * @returns {Object} Compensation analysis
38
+ */
39
+ function analyzeCompensation(activePlatforms, platformAudits) {
40
+ const platforms = (activePlatforms || []).filter(p => PLATFORM_CAPABILITIES[p]);
41
+ const compensations = [];
42
+ const uncoveredGaps = [];
43
+
44
+ // Gather all capability areas across active platforms
45
+ const allAreas = new Set();
46
+ for (const platform of platforms) {
47
+ for (const area of Object.keys(PLATFORM_CAPABILITIES[platform])) {
48
+ allAreas.add(area);
49
+ }
50
+ }
51
+
52
+ // For each platform, find weaknesses and check if another compensates
53
+ for (const platform of platforms) {
54
+ const caps = PLATFORM_CAPABILITIES[platform];
55
+
56
+ for (const area of allAreas) {
57
+ const score = caps[area] || 0;
58
+ if (score >= WEAKNESS_THRESHOLD) continue;
59
+
60
+ // This is a weakness — look for compensation
61
+ let bestCompensator = null;
62
+
63
+ for (const other of platforms) {
64
+ if (other === platform) continue;
65
+ const otherScore = (PLATFORM_CAPABILITIES[other] || {})[area] || 0;
66
+ if (otherScore >= STRENGTH_THRESHOLD) {
67
+ if (!bestCompensator || otherScore > bestCompensator.score) {
68
+ bestCompensator = { platform: other, area, score: otherScore };
69
+ }
70
+ }
71
+ }
72
+
73
+ if (bestCompensator) {
74
+ compensations.push({
75
+ weakness: { platform, area, score, label: AREA_LABELS[area] || area },
76
+ compensatedBy: {
77
+ platform: bestCompensator.platform,
78
+ area: bestCompensator.area,
79
+ score: bestCompensator.score,
80
+ label: AREA_LABELS[area] || area,
81
+ },
82
+ netBenefit: bestCompensator.score - score,
83
+ });
84
+ } else {
85
+ uncoveredGaps.push({
86
+ area,
87
+ label: AREA_LABELS[area] || area,
88
+ weakPlatforms: [{ platform, score }],
89
+ });
90
+ }
91
+ }
92
+ }
93
+
94
+ // Deduplicate uncovered gaps by area
95
+ const gapsByArea = {};
96
+ for (const gap of uncoveredGaps) {
97
+ if (!gapsByArea[gap.area]) {
98
+ gapsByArea[gap.area] = { area: gap.area, label: gap.label, weakPlatforms: [] };
99
+ }
100
+ gapsByArea[gap.area].weakPlatforms.push(...gap.weakPlatforms);
101
+ }
102
+ const dedupedGaps = Object.values(gapsByArea);
103
+
104
+ // Recommend platform additions that would fill gaps
105
+ const inactivePlatforms = Object.keys(PLATFORM_CAPABILITIES).filter(
106
+ p => !platforms.includes(p)
107
+ );
108
+ const recommendedAdditions = [];
109
+
110
+ for (const candidate of inactivePlatforms) {
111
+ const candidateCaps = PLATFORM_CAPABILITIES[candidate];
112
+ const wouldCover = [];
113
+ let estimatedBenefit = 0;
114
+
115
+ for (const gap of dedupedGaps) {
116
+ const candidateScore = candidateCaps[gap.area] || 0;
117
+ if (candidateScore >= STRENGTH_THRESHOLD) {
118
+ wouldCover.push({
119
+ area: gap.area,
120
+ label: gap.label,
121
+ score: candidateScore,
122
+ });
123
+ estimatedBenefit += candidateScore;
124
+ }
125
+ }
126
+
127
+ if (wouldCover.length > 0) {
128
+ recommendedAdditions.push({
129
+ platform: candidate,
130
+ wouldCover,
131
+ estimatedBenefit,
132
+ });
133
+ }
134
+ }
135
+
136
+ recommendedAdditions.sort((a, b) => b.estimatedBenefit - a.estimatedBenefit);
137
+
138
+ return {
139
+ compensations,
140
+ uncoveredGaps: dedupedGaps,
141
+ recommendedAdditions,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Get areas not well covered by any active platform.
147
+ *
148
+ * @param {string[]} activePlatforms - Currently active platforms
149
+ * @returns {Object[]} Uncovered gap areas
150
+ */
151
+ function getUncoveredGaps(activePlatforms) {
152
+ const result = analyzeCompensation(activePlatforms);
153
+ return result.uncoveredGaps;
154
+ }
155
+
156
+ module.exports = { analyzeCompensation, getUncoveredGaps };
@@ -0,0 +1,193 @@
1
+ /**
2
+ * S3. Compound Evidence System
3
+ *
4
+ * Combines audit findings from multiple platforms for stronger insights.
5
+ * Cross-validates findings and calculates compound amplification scores.
6
+ */
7
+
8
+ /**
9
+ * Normalize a finding key across platforms.
10
+ * Different platforms may name the same concept differently.
11
+ */
12
+ function normalizeFindingKey(finding) {
13
+ // Use the technique key or a composite of category+name
14
+ return finding.key || `${finding.category || 'unknown'}:${finding.name || 'unnamed'}`;
15
+ }
16
+
17
+ /**
18
+ * Group findings by their normalized concept.
19
+ */
20
+ function groupByConcept(platformAudits) {
21
+ const concepts = {};
22
+
23
+ for (const [platform, auditResult] of Object.entries(platformAudits)) {
24
+ if (!auditResult || !auditResult.results) continue;
25
+
26
+ for (const finding of auditResult.results) {
27
+ const key = normalizeFindingKey(finding);
28
+ if (!concepts[key]) {
29
+ concepts[key] = {
30
+ key,
31
+ name: finding.name,
32
+ category: finding.category,
33
+ impact: finding.impact,
34
+ platforms: {},
35
+ };
36
+ }
37
+ concepts[key].platforms[platform] = {
38
+ passed: finding.passed,
39
+ fix: finding.fix,
40
+ };
41
+ }
42
+ }
43
+
44
+ return concepts;
45
+ }
46
+
47
+ /**
48
+ * Combine audit findings from multiple platforms for stronger insights.
49
+ *
50
+ * @param {Object} platformAudits - { claude: auditResult, codex: auditResult, ... }
51
+ * @returns {Object} Compound audit result with cross-validation and amplification
52
+ */
53
+ function compoundAudit(platformAudits) {
54
+ const platforms = Object.keys(platformAudits).filter(
55
+ p => platformAudits[p] && platformAudits[p].results
56
+ );
57
+
58
+ if (platforms.length === 0) {
59
+ return {
60
+ compoundScore: 0,
61
+ bestSingleScore: 0,
62
+ amplification: 0,
63
+ totalFindings: 0,
64
+ crossValidated: [],
65
+ platformUnique: {},
66
+ coverageMap: {},
67
+ };
68
+ }
69
+
70
+ const concepts = groupByConcept(platformAudits);
71
+ const crossValidated = [];
72
+ const platformUnique = {};
73
+ const coverageMap = {};
74
+
75
+ for (const platform of platforms) {
76
+ platformUnique[platform] = [];
77
+ coverageMap[platform] = { found: 0, unique: 0, shared: 0 };
78
+ }
79
+
80
+ // Analyze each concept across platforms
81
+ for (const [key, concept] of Object.entries(concepts)) {
82
+ const involvedPlatforms = Object.keys(concept.platforms);
83
+ const failedOn = involvedPlatforms.filter(p => concept.platforms[p].passed === false);
84
+ const passedOn = involvedPlatforms.filter(p => concept.platforms[p].passed === true);
85
+
86
+ // Track coverage
87
+ for (const p of involvedPlatforms) {
88
+ coverageMap[p].found++;
89
+ }
90
+
91
+ if (involvedPlatforms.length >= 2) {
92
+ // Cross-validated finding
93
+ for (const p of involvedPlatforms) {
94
+ coverageMap[p].shared++;
95
+ }
96
+
97
+ if (failedOn.length >= 2) {
98
+ crossValidated.push({
99
+ key,
100
+ name: concept.name,
101
+ category: concept.category,
102
+ impact: concept.impact,
103
+ failedOn,
104
+ passedOn,
105
+ confidence: Math.min(1, 0.5 + (failedOn.length * 0.15)),
106
+ verdict: failedOn.length === involvedPlatforms.length
107
+ ? 'universal-gap'
108
+ : 'partial-gap',
109
+ });
110
+ } else if (passedOn.length >= 2) {
111
+ crossValidated.push({
112
+ key,
113
+ name: concept.name,
114
+ category: concept.category,
115
+ impact: concept.impact,
116
+ failedOn,
117
+ passedOn,
118
+ confidence: Math.min(1, 0.5 + (passedOn.length * 0.15)),
119
+ verdict: 'cross-validated-pass',
120
+ });
121
+ }
122
+ } else {
123
+ // Platform-unique finding
124
+ const platform = involvedPlatforms[0];
125
+ coverageMap[platform].unique++;
126
+ platformUnique[platform].push({
127
+ key,
128
+ name: concept.name,
129
+ category: concept.category,
130
+ impact: concept.impact,
131
+ passed: concept.platforms[platform].passed,
132
+ fix: concept.platforms[platform].fix,
133
+ });
134
+ }
135
+ }
136
+
137
+ // Calculate scores
138
+ const singleScores = platforms.map(p => platformAudits[p].score || 0);
139
+ const bestSingleScore = Math.max(...singleScores);
140
+
141
+ // Compound score = best single + bonus from cross-validation + bonus from unique coverage
142
+ const crossValidationBonus = crossValidated
143
+ .filter(cv => cv.verdict === 'cross-validated-pass')
144
+ .length * 0.5;
145
+ const uniqueCoverageBonus = Object.values(platformUnique)
146
+ .reduce((sum, findings) => sum + findings.filter(f => f.passed).length, 0) * 0.3;
147
+ const totalUniqueFindings = Object.keys(concepts).length;
148
+
149
+ const compoundScore = Math.round(
150
+ Math.min(150, bestSingleScore + crossValidationBonus + uniqueCoverageBonus)
151
+ );
152
+
153
+ return {
154
+ compoundScore,
155
+ bestSingleScore,
156
+ amplification: compoundScore - bestSingleScore,
157
+ totalFindings: totalUniqueFindings,
158
+ crossValidated,
159
+ platformUnique,
160
+ coverageMap,
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Calculate the amplification factor from combining multiple platforms.
166
+ *
167
+ * @param {Object} platformAudits - Per-platform audit results
168
+ * @returns {Object} Amplification metrics
169
+ */
170
+ function calculateAmplification(platformAudits) {
171
+ const result = compoundAudit(platformAudits);
172
+ const platformCount = Object.keys(platformAudits).filter(
173
+ p => platformAudits[p] && platformAudits[p].results
174
+ ).length;
175
+
176
+ return {
177
+ amplification: result.amplification,
178
+ amplificationPercent: result.bestSingleScore > 0
179
+ ? Math.round((result.amplification / result.bestSingleScore) * 100)
180
+ : 0,
181
+ platformCount,
182
+ crossValidatedCount: result.crossValidated.length,
183
+ uniqueFindingsPerPlatform: Object.fromEntries(
184
+ Object.entries(result.platformUnique).map(([p, findings]) => [p, findings.length])
185
+ ),
186
+ verdict: result.amplification > 10 ? 'strong-synergy'
187
+ : result.amplification > 5 ? 'moderate-synergy'
188
+ : result.amplification > 0 ? 'mild-synergy'
189
+ : 'no-synergy',
190
+ };
191
+ }
192
+
193
+ module.exports = { compoundAudit, calculateAmplification };