@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,458 @@
1
+ /**
2
+ * Harmony Sync — Unified Setup / Sync Engine
3
+ *
4
+ * Generates aligned configs for ALL active platforms from a shared canonical
5
+ * understanding. Ensures instructions, MCP servers, and trust posture are
6
+ * consistent across Claude, Codex, Gemini, Copilot, and Cursor.
7
+ *
8
+ * Uses managed blocks from each platform's patch module so hand-authored
9
+ * content is always preserved.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { buildCanonicalModel, PLATFORM_SIGNATURES } = require('./canon');
15
+
16
+ // ─── Managed block markers (imported from platform patch modules) ───────────
17
+
18
+ const MANAGED_MARKERS = {
19
+ claude: {
20
+ start: '<!-- nerviq:managed:start -->',
21
+ end: '<!-- nerviq:managed:end -->',
22
+ },
23
+ codex: {
24
+ start: '<!-- nerviq:managed:start -->',
25
+ end: '<!-- nerviq:managed:end -->',
26
+ },
27
+ gemini: {
28
+ start: '<!-- nerviq:managed:start -->',
29
+ end: '<!-- nerviq:managed:end -->',
30
+ },
31
+ copilot: {
32
+ start: '<!-- nerviq:managed:start -->',
33
+ end: '<!-- nerviq:managed:end -->',
34
+ },
35
+ cursor: {
36
+ // Cursor uses MDC format but managed blocks are still HTML-comment-based for .mdc
37
+ start: '<!-- nerviq:managed:start -->',
38
+ end: '<!-- nerviq:managed:end -->',
39
+ },
40
+ };
41
+
42
+ // ─── Helpers ────────────────────────────────────────────────────────────────
43
+
44
+ function safeReadFile(filePath) {
45
+ try {
46
+ return fs.readFileSync(filePath, 'utf8');
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function ensureDir(dirPath) {
53
+ try {
54
+ fs.mkdirSync(dirPath, { recursive: true });
55
+ } catch {
56
+ // Already exists or not writable
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Extract managed block from content.
62
+ */
63
+ function extractManagedBlock(content, startMarker, endMarker) {
64
+ const startIdx = content.indexOf(startMarker);
65
+ const endIdx = content.indexOf(endMarker);
66
+
67
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
68
+ return { before: content, managed: null, after: '' };
69
+ }
70
+
71
+ return {
72
+ before: content.substring(0, startIdx),
73
+ managed: content.substring(startIdx + startMarker.length, endIdx).trim(),
74
+ after: content.substring(endIdx + endMarker.length),
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Upsert a managed block within content.
80
+ */
81
+ function upsertManagedBlock(content, newManaged, startMarker, endMarker) {
82
+ const { before, managed, after } = extractManagedBlock(content, startMarker, endMarker);
83
+
84
+ if (managed !== null) {
85
+ return `${before}${startMarker}\n${newManaged}\n${endMarker}${after}`;
86
+ }
87
+
88
+ const separator = content.endsWith('\n') ? '\n' : '\n\n';
89
+ return `${content}${separator}${startMarker}\n${newManaged}\n${endMarker}\n`;
90
+ }
91
+
92
+ // ─── Instruction content builders ───────────────────────────────────────────
93
+
94
+ /**
95
+ * Build a shared instruction block that should appear on every platform.
96
+ * Derived from the canonical model's shared understanding.
97
+ */
98
+ function buildSharedInstructionBlock(model) {
99
+ const lines = [];
100
+
101
+ lines.push(`## Harmony-Managed Instructions`);
102
+ lines.push(`<!-- Synced by nerviq harmony. Do not edit this block manually. -->`);
103
+ lines.push('');
104
+
105
+ // Project identity
106
+ lines.push(`Project: ${model.projectName}`);
107
+ if (model.stacks.length > 0) {
108
+ lines.push(`Stacks: ${model.stacks.join(', ')}`);
109
+ }
110
+ lines.push('');
111
+
112
+ // Shared instructions (if any were found across platforms)
113
+ if (model.sharedInstructions.length > 0) {
114
+ lines.push('### Shared Guidelines');
115
+ for (const instruction of model.sharedInstructions.slice(0, 20)) {
116
+ lines.push(instruction);
117
+ }
118
+ lines.push('');
119
+ }
120
+
121
+ // MCP servers (unified list)
122
+ const mcpNames = Object.keys(model.mcpServers);
123
+ if (mcpNames.length > 0) {
124
+ lines.push('### Available MCP Servers');
125
+ for (const name of mcpNames) {
126
+ const server = model.mcpServers[name];
127
+ lines.push(`- ${name} (on: ${server.platforms.join(', ')})`);
128
+ }
129
+ lines.push('');
130
+ }
131
+
132
+ return lines.join('\n');
133
+ }
134
+
135
+ // ─── Platform-specific file generators ──────────────────────────────────────
136
+
137
+ /**
138
+ * Generate the instruction file path for a platform.
139
+ */
140
+ function getInstructionPath(platform) {
141
+ switch (platform) {
142
+ case 'claude': return 'CLAUDE.md';
143
+ case 'codex': return 'AGENTS.md';
144
+ case 'gemini': return 'GEMINI.md';
145
+ case 'copilot': return '.github/copilot-instructions.md';
146
+ case 'cursor': return '.cursorrules';
147
+ default: return null;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Generate MCP config content for a platform based on the unified MCP server list.
153
+ */
154
+ function buildMcpConfig(platform, mcpServers) {
155
+ // Filter to servers that should be on this platform
156
+ const servers = {};
157
+ for (const [name, server] of Object.entries(mcpServers)) {
158
+ servers[name] = {
159
+ command: server.command || 'npx',
160
+ args: server.args || [],
161
+ };
162
+ }
163
+
164
+ if (Object.keys(servers).length === 0) return null;
165
+
166
+ if (platform === 'claude') {
167
+ // Claude settings.json format
168
+ return { mcpServers: servers };
169
+ }
170
+
171
+ if (platform === 'cursor' || platform === 'copilot') {
172
+ // mcp.json format
173
+ return { mcpServers: servers };
174
+ }
175
+
176
+ if (platform === 'gemini') {
177
+ // Gemini settings.json format
178
+ return { mcpServers: servers };
179
+ }
180
+
181
+ return null;
182
+ }
183
+
184
+ /**
185
+ * Get the MCP config file path for a platform.
186
+ */
187
+ function getMcpConfigPath(platform) {
188
+ switch (platform) {
189
+ case 'claude': return '.claude/settings.json';
190
+ case 'gemini': return '.gemini/settings.json';
191
+ case 'copilot': return '.vscode/mcp.json';
192
+ case 'cursor': return '.cursor/mcp.json';
193
+ default: return null;
194
+ }
195
+ }
196
+
197
+ // ─── Trust alignment recommendations ────────────────────────────────────────
198
+
199
+ /**
200
+ * Recommend a unified trust posture across all platforms.
201
+ * Strategy: use the most restrictive trust level present.
202
+ */
203
+ function recommendTrustPosture(model) {
204
+ const TRUST_LEVELS = {
205
+ 'locked-down': 0,
206
+ 'default': 1,
207
+ 'unknown': 1,
208
+ 'safe-write': 2,
209
+ 'standard': 2,
210
+ 'no-sandbox': 3,
211
+ 'full-auto': 4,
212
+ 'bypass': 4,
213
+ 'unrestricted': 5,
214
+ };
215
+
216
+ const postures = Object.values(model.trustPosture);
217
+ if (postures.length === 0) return 'safe-write';
218
+
219
+ // Find the most restrictive (lowest) trust level
220
+ let minLevel = Infinity;
221
+ let minPosture = 'default';
222
+ for (const posture of postures) {
223
+ const level = TRUST_LEVELS[posture] ?? 1;
224
+ if (level < minLevel) {
225
+ minLevel = level;
226
+ minPosture = posture;
227
+ }
228
+ }
229
+
230
+ return minPosture;
231
+ }
232
+
233
+ // ─── Main sync functions ────────────────────────────────────────────────────
234
+
235
+ /**
236
+ * Generate harmony sync operations from a canonical model.
237
+ *
238
+ * For each active platform:
239
+ * - Generate/update instruction file with shared managed block
240
+ * - Ensure shared MCP servers exist on all platforms that support MCP
241
+ * - Report trust posture alignment recommendations
242
+ *
243
+ * @param {object} canonicalModel - Output of buildCanonicalModel()
244
+ * @param {object} [options] - { syncMcp: true, syncInstructions: true, dryRun: false }
245
+ * @returns {object} { files, summary, warnings }
246
+ */
247
+ function generateHarmonySync(canonicalModel, options = {}) {
248
+ const {
249
+ syncMcp = true,
250
+ syncInstructions = true,
251
+ } = options;
252
+
253
+ const model = canonicalModel;
254
+ const files = [];
255
+ const warnings = [];
256
+
257
+ const sharedBlock = buildSharedInstructionBlock(model);
258
+ const recommendedTrust = recommendTrustPosture(model);
259
+
260
+ for (const ap of model.activePlatforms) {
261
+ const platform = ap.platform;
262
+
263
+ // ── Instruction file sync ──
264
+ if (syncInstructions) {
265
+ const instrPath = getInstructionPath(platform);
266
+ if (instrPath) {
267
+ const fullPath = path.join(model.dir, instrPath);
268
+ const existingContent = safeReadFile(fullPath);
269
+ const markers = MANAGED_MARKERS[platform];
270
+
271
+ if (existingContent) {
272
+ // Patch existing file with managed block
273
+ const updated = upsertManagedBlock(
274
+ existingContent,
275
+ sharedBlock,
276
+ markers.start,
277
+ markers.end,
278
+ );
279
+
280
+ if (updated !== existingContent) {
281
+ files.push({
282
+ platform,
283
+ path: instrPath,
284
+ action: 'patch',
285
+ content: updated,
286
+ preview: `Update managed block in ${instrPath}`,
287
+ });
288
+ }
289
+ } else {
290
+ // Create new instruction file with managed block
291
+ const newContent = `# ${platform === 'codex' ? 'AGENTS' : platform.charAt(0).toUpperCase() + platform.slice(1)} Instructions\n\n` +
292
+ `${markers.start}\n${sharedBlock}\n${markers.end}\n`;
293
+
294
+ files.push({
295
+ platform,
296
+ path: instrPath,
297
+ action: 'create',
298
+ content: newContent,
299
+ preview: `Create ${instrPath} with harmony-managed content`,
300
+ });
301
+ }
302
+ }
303
+ }
304
+
305
+ // ── MCP server sync ──
306
+ if (syncMcp && Object.keys(model.mcpServers).length > 0) {
307
+ const mcpPath = getMcpConfigPath(platform);
308
+ if (!mcpPath) continue; // Codex doesn't support MCP config
309
+
310
+ const fullMcpPath = path.join(model.dir, mcpPath);
311
+ const existingMcp = safeReadFile(fullMcpPath);
312
+ const mcpConfig = buildMcpConfig(platform, model.mcpServers);
313
+
314
+ if (!mcpConfig) continue;
315
+
316
+ if (existingMcp) {
317
+ // Merge: add missing servers, don't overwrite existing
318
+ let existingJson;
319
+ try {
320
+ existingJson = JSON.parse(existingMcp);
321
+ } catch {
322
+ warnings.push(`Cannot parse ${mcpPath} — skipping MCP sync for ${platform}`);
323
+ continue;
324
+ }
325
+
326
+ const existingServers = existingJson.mcpServers || existingJson.servers || {};
327
+ const newServers = mcpConfig.mcpServers || {};
328
+ let added = 0;
329
+
330
+ for (const [name, config] of Object.entries(newServers)) {
331
+ if (!existingServers[name]) {
332
+ existingServers[name] = config;
333
+ added++;
334
+ }
335
+ }
336
+
337
+ if (added > 0) {
338
+ // Preserve the original key name (mcpServers or servers)
339
+ const serverKey = existingJson.servers ? 'servers' : 'mcpServers';
340
+ existingJson[serverKey] = existingServers;
341
+
342
+ files.push({
343
+ platform,
344
+ path: mcpPath,
345
+ action: 'patch',
346
+ content: JSON.stringify(existingJson, null, 2) + '\n',
347
+ preview: `Add ${added} MCP server(s) to ${mcpPath}`,
348
+ });
349
+ }
350
+ } else {
351
+ // Create new MCP config
352
+ files.push({
353
+ platform,
354
+ path: mcpPath,
355
+ action: 'create',
356
+ content: JSON.stringify(mcpConfig, null, 2) + '\n',
357
+ preview: `Create ${mcpPath} with ${Object.keys(mcpConfig.mcpServers || {}).length} MCP server(s)`,
358
+ });
359
+ }
360
+ }
361
+ }
362
+
363
+ // Trust posture warnings
364
+ const trustValues = Object.values(model.trustPosture);
365
+ const uniqueTrust = new Set(trustValues);
366
+ if (uniqueTrust.size > 1) {
367
+ warnings.push(
368
+ `Trust posture varies across platforms. Recommended baseline: "${recommendedTrust}". ` +
369
+ `Current: ${Object.entries(model.trustPosture).map(([p, t]) => `${p}=${t}`).join(', ')}. ` +
370
+ `Trust alignment must be applied manually per platform.`
371
+ );
372
+ }
373
+
374
+ // Summary
375
+ const creates = files.filter(f => f.action === 'create').length;
376
+ const patches = files.filter(f => f.action === 'patch').length;
377
+ const summary = {
378
+ totalFiles: files.length,
379
+ creates,
380
+ patches,
381
+ platforms: [...new Set(files.map(f => f.platform))],
382
+ recommendedTrust,
383
+ };
384
+
385
+ return { files, summary, warnings };
386
+ }
387
+
388
+ /**
389
+ * Preview sync operations without writing.
390
+ *
391
+ * @param {string} dir - Project root directory
392
+ * @param {object} [options] - Same as generateHarmonySync options
393
+ * @returns {object} Sync plan (files, summary, warnings)
394
+ */
395
+ function previewHarmonySync(dir, options = {}) {
396
+ const model = buildCanonicalModel(dir);
397
+ return generateHarmonySync(model, options);
398
+ }
399
+
400
+ /**
401
+ * Apply harmony sync — write all generated files to disk.
402
+ *
403
+ * @param {string} dir - Project root directory
404
+ * @param {object} [options] - { syncMcp, syncInstructions, dryRun }
405
+ * @returns {object} { applied, skipped, warnings }
406
+ */
407
+ function applyHarmonySync(dir, options = {}) {
408
+ const { dryRun = false } = options;
409
+ const model = buildCanonicalModel(dir);
410
+ const sync = generateHarmonySync(model, options);
411
+
412
+ if (dryRun) {
413
+ return {
414
+ applied: [],
415
+ skipped: sync.files.map(f => f.path),
416
+ warnings: sync.warnings,
417
+ plan: sync,
418
+ };
419
+ }
420
+
421
+ const applied = [];
422
+ const skipped = [];
423
+
424
+ for (const file of sync.files) {
425
+ const fullPath = path.join(dir, file.path);
426
+
427
+ try {
428
+ // Ensure parent directory exists
429
+ ensureDir(path.dirname(fullPath));
430
+
431
+ fs.writeFileSync(fullPath, file.content, 'utf8');
432
+ applied.push({
433
+ platform: file.platform,
434
+ path: file.path,
435
+ action: file.action,
436
+ });
437
+ } catch (err) {
438
+ skipped.push({
439
+ platform: file.platform,
440
+ path: file.path,
441
+ reason: err.message,
442
+ });
443
+ }
444
+ }
445
+
446
+ return {
447
+ applied,
448
+ skipped,
449
+ warnings: sync.warnings,
450
+ summary: sync.summary,
451
+ };
452
+ }
453
+
454
+ module.exports = {
455
+ generateHarmonySync,
456
+ applyHarmonySync,
457
+ previewHarmonySync,
458
+ };