@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
package/bin/cli.js ADDED
@@ -0,0 +1,749 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { audit } = require('../src/audit');
4
+ const { setup } = require('../src/setup');
5
+ const { analyzeProject, printAnalysis, exportMarkdown } = require('../src/analyze');
6
+ const { buildProposalBundle, printProposalBundle, writePlanFile, applyProposalBundle, printApplyResult } = require('../src/plans');
7
+ const { getGovernanceSummary, printGovernanceSummary, ensureWritableProfile, renderGovernanceMarkdown } = require('../src/governance');
8
+ const { runBenchmark, printBenchmark, writeBenchmarkReport } = require('../src/benchmark');
9
+ const { writeSnapshotArtifact, recordRecommendationOutcome, formatRecommendationOutcomeSummary, getRecommendationOutcomeSummary } = require('../src/activity');
10
+ const { version } = require('../package.json');
11
+
12
+ const args = process.argv.slice(2);
13
+ const COMMAND_ALIASES = {
14
+ review: 'deep-review',
15
+ wizard: 'interactive',
16
+ learn: 'insights',
17
+ discover: 'audit',
18
+ starter: 'setup',
19
+ suggest: 'suggest-only',
20
+ gov: 'governance',
21
+ outcome: 'feedback',
22
+ };
23
+ const KNOWN_COMMANDS = ['audit', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'help', 'version'];
24
+
25
+ function levenshtein(a, b) {
26
+ const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
27
+ for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
28
+ for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
29
+ for (let i = 1; i <= a.length; i++) {
30
+ for (let j = 1; j <= b.length; j++) {
31
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
32
+ matrix[i][j] = Math.min(
33
+ matrix[i - 1][j] + 1,
34
+ matrix[i][j - 1] + 1,
35
+ matrix[i - 1][j - 1] + cost
36
+ );
37
+ }
38
+ }
39
+ return matrix[a.length][b.length];
40
+ }
41
+
42
+ function suggestCommand(input) {
43
+ const candidates = [...KNOWN_COMMANDS, ...Object.keys(COMMAND_ALIASES)];
44
+ let best = null;
45
+ let bestDistance = Infinity;
46
+ for (const candidate of candidates) {
47
+ const distance = levenshtein(input, candidate);
48
+ if (distance < bestDistance) {
49
+ best = candidate;
50
+ bestDistance = distance;
51
+ }
52
+ }
53
+ return bestDistance <= 3 ? best : null;
54
+ }
55
+
56
+ function parseArgs(rawArgs) {
57
+ const flags = [];
58
+ let command = 'audit';
59
+ let threshold = null;
60
+ let out = null;
61
+ let planFile = null;
62
+ let only = [];
63
+ let profile = 'safe-write';
64
+ let mcpPacks = [];
65
+ let requireChecks = [];
66
+ let feedbackKey = null;
67
+ let feedbackStatus = null;
68
+ let feedbackEffect = null;
69
+ let feedbackNotes = null;
70
+ let feedbackSource = null;
71
+ let feedbackScoreDelta = null;
72
+ let platform = 'claude';
73
+ let format = null;
74
+ let commandSet = false;
75
+ let extraArgs = [];
76
+
77
+ for (let i = 0; i < rawArgs.length; i++) {
78
+ const arg = rawArgs[i];
79
+
80
+ if (arg === '--threshold' || arg === '--out' || arg === '--plan' || arg === '--only' || arg === '--profile' || arg === '--mcp-pack' || arg === '--require' || arg === '--key' || arg === '--status' || arg === '--effect' || arg === '--notes' || arg === '--source' || arg === '--score-delta' || arg === '--platform' || arg === '--format') {
81
+ const value = rawArgs[i + 1];
82
+ if (!value || value.startsWith('--')) {
83
+ throw new Error(`${arg} requires a value`);
84
+ }
85
+ if (arg === '--threshold') threshold = value;
86
+ if (arg === '--out') out = value;
87
+ if (arg === '--plan') planFile = value;
88
+ if (arg === '--only') only = value.split(',').map(item => item.trim()).filter(Boolean);
89
+ if (arg === '--profile') profile = value.trim();
90
+ if (arg === '--mcp-pack') mcpPacks = value.split(',').map(item => item.trim()).filter(Boolean);
91
+ if (arg === '--require') requireChecks = value.split(',').map(item => item.trim()).filter(Boolean);
92
+ if (arg === '--key') feedbackKey = value.trim();
93
+ if (arg === '--status') feedbackStatus = value.trim();
94
+ if (arg === '--effect') feedbackEffect = value.trim();
95
+ if (arg === '--notes') feedbackNotes = value;
96
+ if (arg === '--source') feedbackSource = value.trim();
97
+ if (arg === '--score-delta') feedbackScoreDelta = value.trim();
98
+ if (arg === '--platform') platform = value.trim().toLowerCase();
99
+ if (arg === '--format') format = value.trim().toLowerCase();
100
+ i++;
101
+ continue;
102
+ }
103
+
104
+ if (arg.startsWith('--require=')) {
105
+ requireChecks = arg.split('=').slice(1).join('=').split(',').map(item => item.trim()).filter(Boolean);
106
+ continue;
107
+ }
108
+
109
+ if (arg.startsWith('--threshold=')) {
110
+ threshold = arg.split('=')[1];
111
+ continue;
112
+ }
113
+
114
+ if (arg.startsWith('--out=')) {
115
+ out = arg.split('=').slice(1).join('=');
116
+ continue;
117
+ }
118
+
119
+ if (arg.startsWith('--plan=')) {
120
+ planFile = arg.split('=').slice(1).join('=');
121
+ continue;
122
+ }
123
+
124
+ if (arg.startsWith('--only=')) {
125
+ only = arg.split('=').slice(1).join('=').split(',').map(item => item.trim()).filter(Boolean);
126
+ continue;
127
+ }
128
+
129
+ if (arg.startsWith('--profile=')) {
130
+ profile = arg.split('=').slice(1).join('=').trim();
131
+ continue;
132
+ }
133
+
134
+ if (arg.startsWith('--mcp-pack=')) {
135
+ mcpPacks = arg.split('=').slice(1).join('=').split(',').map(item => item.trim()).filter(Boolean);
136
+ continue;
137
+ }
138
+
139
+ if (arg.startsWith('--key=')) {
140
+ feedbackKey = arg.split('=').slice(1).join('=').trim();
141
+ continue;
142
+ }
143
+
144
+ if (arg.startsWith('--status=')) {
145
+ feedbackStatus = arg.split('=').slice(1).join('=').trim();
146
+ continue;
147
+ }
148
+
149
+ if (arg.startsWith('--effect=')) {
150
+ feedbackEffect = arg.split('=').slice(1).join('=').trim();
151
+ continue;
152
+ }
153
+
154
+ if (arg.startsWith('--notes=')) {
155
+ feedbackNotes = arg.split('=').slice(1).join('=');
156
+ continue;
157
+ }
158
+
159
+ if (arg.startsWith('--source=')) {
160
+ feedbackSource = arg.split('=').slice(1).join('=').trim();
161
+ continue;
162
+ }
163
+
164
+ if (arg.startsWith('--score-delta=')) {
165
+ feedbackScoreDelta = arg.split('=').slice(1).join('=').trim();
166
+ continue;
167
+ }
168
+
169
+ if (arg.startsWith('--platform=')) {
170
+ platform = arg.split('=').slice(1).join('=').trim().toLowerCase();
171
+ continue;
172
+ }
173
+
174
+ if (arg.startsWith('--format=')) {
175
+ format = arg.split('=').slice(1).join('=').trim().toLowerCase();
176
+ continue;
177
+ }
178
+
179
+ if (arg.startsWith('--')) {
180
+ flags.push(arg);
181
+ continue;
182
+ }
183
+
184
+ if (!commandSet) {
185
+ command = arg;
186
+ commandSet = true;
187
+ } else {
188
+ extraArgs.push(arg);
189
+ }
190
+ }
191
+
192
+ const normalizedCommand = COMMAND_ALIASES[command] || command;
193
+
194
+ return { flags, command, normalizedCommand, threshold, out, planFile, only, profile, mcpPacks, requireChecks, feedbackKey, feedbackStatus, feedbackEffect, feedbackNotes, feedbackSource, feedbackScoreDelta, platform, format, extraArgs };
195
+ }
196
+
197
+ const HELP = `
198
+ nerviq v${version}
199
+ Score your repo's Claude Code setup. Fix gaps safely. Benchmark the impact.
200
+
201
+ Start here (read-only, nothing changes):
202
+ npx nerviq Audit your project (10 seconds)
203
+ npx nerviq --lite Quick scan: top 3 gaps + next command
204
+ npx nerviq --platform codex Audit your Codex repo setup
205
+ npx nerviq --platform codex augment Codex-aware advisory pass, no writes
206
+ npx nerviq --platform codex suggest-only Structured Codex report, no writes
207
+ npx nerviq augment Repo-aware analysis, no writes
208
+ npx nerviq suggest-only Structured report, no writes
209
+
210
+ Plan and apply (when you're ready to change things):
211
+ npx nerviq plan Export proposal bundles with previews
212
+ npx nerviq apply Apply proposals selectively with rollback
213
+ npx nerviq setup Generate starter-safe baseline
214
+ npx nerviq setup --auto Apply all generated files without prompts
215
+
216
+ Track progress over time:
217
+ npx nerviq history Show score history from saved snapshots
218
+ npx nerviq compare Compare latest vs previous snapshot
219
+ npx nerviq trend --out r.md Export trend report as markdown
220
+
221
+ Multi-repo:
222
+ npx nerviq scan dir1 dir2 Compare multiple repos side-by-side
223
+
224
+ Advanced:
225
+ npx nerviq governance Permission profiles, hooks, policy packs
226
+ npx nerviq benchmark Before/after in isolated temp copy
227
+ npx nerviq deep-review AI-powered config review (opt-in, uses API)
228
+ npx nerviq interactive Step-by-step guided wizard
229
+ npx nerviq watch Live monitoring on config changes with cross-platform watch fallback
230
+ npx nerviq badge Generate shields.io badge markdown
231
+ npx nerviq feedback Record recommendation outcomes or show local outcome summary
232
+
233
+ Options:
234
+ --threshold N Exit with code 1 if score is below N (useful for CI)
235
+ --require A,B Exit with code 1 if named checks fail (e.g. --require secretsProtection,permissionDeny)
236
+ --out FILE Write JSON or markdown output to a file
237
+ --plan FILE Load a previously exported plan file
238
+ --only A,B Limit plan/apply to selected proposal ids or technique keys
239
+ --profile NAME Choose permission profile (read-only, suggest-only, safe-write, power-user, internal-research)
240
+ --mcp-pack A,B Merge named MCP packs into generated settings (e.g. context7-docs,next-devtools)
241
+ --key NAME Recommendation key for feedback logging (e.g. permissionDeny)
242
+ --status VALUE Feedback status: accepted, rejected, deferred
243
+ --effect VALUE Feedback effect: positive, neutral, negative
244
+ --notes TEXT Short notes to store with a feedback event
245
+ --source NAME Source label for feedback event (default: manual-cli)
246
+ --score-delta N Optional observed score delta tied to the outcome
247
+ --platform NAME Choose platform surface (claude default, codex advisory/build preview)
248
+ --format NAME Output format for audit results (json, sarif)
249
+ --snapshot Save a normalized snapshot artifact under .claude/nerviq/snapshots/
250
+ --lite Show a short top-3 quick scan with one clear next command
251
+ --dry-run Preview apply without writing files
252
+ --verbose Show all recommendations (not just critical/high)
253
+ --json Output as JSON (for CI pipelines)
254
+ --auto Apply all generated setup files without prompting
255
+ --insights Enable anonymous usage insights (off by default)
256
+ --help Show this help
257
+ --version Show version
258
+
259
+ Examples:
260
+ npx nerviq
261
+ npx nerviq --lite
262
+ npx nerviq --platform codex
263
+ npx nerviq --platform codex augment
264
+ npx nerviq --platform codex suggest-only --json
265
+ npx nerviq --platform codex setup
266
+ npx nerviq --platform codex plan --out codex-plan.json
267
+ npx nerviq --platform codex --format sarif
268
+ npx nerviq --snapshot
269
+ npx nerviq augment
270
+ npx nerviq augment --snapshot
271
+ npx nerviq suggest-only --json
272
+ npx nerviq governance --snapshot
273
+ npx nerviq plan --out claudex-plan.json
274
+ npx nerviq plan --profile safe-write
275
+ npx nerviq setup --mcp-pack context7-docs
276
+ npx nerviq apply --plan claudex-plan.json --only hooks,commands
277
+ npx nerviq apply --mcp-pack context7-docs,next-devtools --only hooks
278
+ npx nerviq apply --profile power-user --only claude-md,hooks
279
+ npx nerviq governance --json
280
+ npx nerviq benchmark --out benchmark.md
281
+ npx nerviq feedback
282
+ npx nerviq feedback --key permissionDeny --status accepted --effect positive --score-delta 12
283
+ npx nerviq --json --threshold 60
284
+ npx nerviq setup --auto
285
+ npx nerviq interactive
286
+
287
+ Exit codes:
288
+ 0 Success
289
+ 1 Error, unknown command, or score below --threshold
290
+ `;
291
+
292
+ async function main() {
293
+ let parsed;
294
+ try {
295
+ parsed = parseArgs(args);
296
+ } catch (err) {
297
+ console.error(`\n Error: ${err.message}\n`);
298
+ process.exit(1);
299
+ }
300
+
301
+ const { flags, command, normalizedCommand } = parsed;
302
+
303
+ if (flags.includes('--help') || command === 'help') {
304
+ console.log(HELP);
305
+ process.exit(0);
306
+ }
307
+
308
+ if (flags.includes('--version') || command === 'version') {
309
+ console.log(version);
310
+ process.exit(0);
311
+ }
312
+
313
+ const options = {
314
+ verbose: flags.includes('--verbose'),
315
+ json: flags.includes('--json'),
316
+ auto: flags.includes('--auto'),
317
+ lite: flags.includes('--lite'),
318
+ snapshot: flags.includes('--snapshot'),
319
+ dryRun: flags.includes('--dry-run'),
320
+ threshold: parsed.threshold !== null ? Number(parsed.threshold) : null,
321
+ out: parsed.out,
322
+ planFile: parsed.planFile,
323
+ only: parsed.only,
324
+ profile: parsed.profile,
325
+ mcpPacks: parsed.mcpPacks,
326
+ require: parsed.requireChecks,
327
+ platform: parsed.platform || 'claude',
328
+ format: parsed.format || null,
329
+ dir: process.cwd()
330
+ };
331
+
332
+ if (!['claude', 'codex'].includes(options.platform)) {
333
+ console.error(`\n Error: Unsupported platform '${options.platform}'. Use 'claude' or 'codex'.\n`);
334
+ process.exit(1);
335
+ }
336
+
337
+ if (options.format !== null && !['json', 'sarif'].includes(options.format)) {
338
+ console.error(`\n Error: Unsupported format '${options.format}'. Use 'json' or 'sarif'.\n`);
339
+ process.exit(1);
340
+ }
341
+
342
+ if (options.threshold !== null && (!Number.isFinite(options.threshold) || options.threshold < 0 || options.threshold > 100)) {
343
+ console.error('\n Error: --threshold must be a number between 0 and 100.\n');
344
+ process.exit(1);
345
+ }
346
+
347
+ if (options.require && options.require.length > 0 && normalizedCommand !== 'audit' && !['audit', 'discover'].includes(command)) {
348
+ console.error(`\n Warning: --require is only supported with the audit command. Ignoring for '${normalizedCommand}'.\n`);
349
+ }
350
+
351
+ if (!KNOWN_COMMANDS.includes(normalizedCommand)) {
352
+ const suggestion = suggestCommand(command);
353
+ console.error(`\n Error: Unknown command '${command}'.`);
354
+ if (suggestion) {
355
+ console.error(` Did you mean '${suggestion}'?`);
356
+ }
357
+ console.error(' Run nerviq --help for usage.\n');
358
+ process.exit(1);
359
+ }
360
+
361
+ if (!require('fs').existsSync(options.dir)) {
362
+ console.error(`\n Error: Directory not found: ${options.dir}`);
363
+ console.error(' Run nerviq from inside your project directory.\n');
364
+ process.exit(1);
365
+ }
366
+
367
+ if (['setup', 'apply', 'benchmark'].includes(normalizedCommand)) {
368
+ try {
369
+ ensureWritableProfile(options.profile, normalizedCommand, options.dryRun);
370
+ } catch (err) {
371
+ console.error(`\n Error: ${err.message}\n`);
372
+ process.exit(1);
373
+ }
374
+ }
375
+
376
+ try {
377
+ const FULL_COMMAND_SET = new Set([
378
+ 'audit', 'scan', 'badge', 'augment', 'suggest-only', 'setup', 'plan', 'apply',
379
+ 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'insights',
380
+ 'history', 'compare', 'trend', 'feedback', 'help', 'version',
381
+ // Harmony + Synergy (cross-platform)
382
+ 'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise',
383
+ 'harmony-watch', 'harmony-governance', 'synergy-report',
384
+ ]);
385
+
386
+ if (options.platform === 'codex') {
387
+ if (!FULL_COMMAND_SET.has(normalizedCommand)) {
388
+ console.error(`\n Error: '${normalizedCommand}' is not supported for --platform codex.`);
389
+ console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
390
+ process.exit(1);
391
+ }
392
+ }
393
+
394
+ if (options.platform === 'gemini') {
395
+ if (!FULL_COMMAND_SET.has(normalizedCommand)) {
396
+ console.error(`\n Error: '${normalizedCommand}' is not supported for --platform gemini.`);
397
+ console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
398
+ process.exit(1);
399
+ }
400
+ }
401
+
402
+ if (options.platform === 'copilot') {
403
+ if (!FULL_COMMAND_SET.has(normalizedCommand)) {
404
+ console.error(`\n Error: '${normalizedCommand}' is not supported for --platform copilot.`);
405
+ console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
406
+ process.exit(1);
407
+ }
408
+ }
409
+
410
+ if (options.platform === 'cursor') {
411
+ if (!FULL_COMMAND_SET.has(normalizedCommand)) {
412
+ console.error(`\n Error: '${normalizedCommand}' is not supported for --platform cursor.`);
413
+ console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ for (const plat of ['windsurf', 'aider', 'opencode']) {
419
+ if (options.platform === plat) {
420
+ if (!FULL_COMMAND_SET.has(normalizedCommand)) {
421
+ console.error(`\n Error: '${normalizedCommand}' is not supported for --platform ${plat}.`);
422
+ console.error(' Available: ' + [...FULL_COMMAND_SET].filter(c => c !== 'help' && c !== 'version').join(', ') + '.');
423
+ process.exit(1);
424
+ }
425
+ }
426
+ }
427
+
428
+ if (normalizedCommand === 'scan') {
429
+ const scanDirs = parsed.extraArgs;
430
+ if (scanDirs.length === 0) {
431
+ console.error('\n Error: scan requires at least one directory argument.');
432
+ console.error(' Usage: npx nerviq scan dir1 dir2 dir3\n');
433
+ process.exit(1);
434
+ }
435
+ const fs = require('fs');
436
+ const pathMod = require('path');
437
+ const rows = [];
438
+ for (const rawDir of scanDirs) {
439
+ const dir = pathMod.resolve(rawDir);
440
+ if (!fs.existsSync(dir)) {
441
+ rows.push({ name: pathMod.basename(rawDir), dir: rawDir, score: null, passed: '-', failed: '-', suggested: '-', error: 'directory not found' });
442
+ continue;
443
+ }
444
+ try {
445
+ const result = await audit({ dir, silent: true, platform: options.platform });
446
+ rows.push({
447
+ name: pathMod.basename(dir),
448
+ dir: rawDir,
449
+ score: result.score,
450
+ passed: result.passed,
451
+ failed: result.failed,
452
+ suggested: result.suggestedNextCommand || '-',
453
+ error: null,
454
+ });
455
+ } catch (err) {
456
+ rows.push({ name: pathMod.basename(dir), dir: rawDir, score: null, passed: '-', failed: '-', suggested: '-', error: err.message });
457
+ }
458
+ }
459
+
460
+ if (options.json) {
461
+ console.log(JSON.stringify(rows, null, 2));
462
+ } else {
463
+ // Find weakest
464
+ const validRows = rows.filter(r => r.score !== null);
465
+ const minScore = validRows.length > 0 ? Math.min(...validRows.map(r => r.score)) : null;
466
+ const weakest = validRows.length > 1 && validRows.filter(r => r.score > minScore).length > 0
467
+ ? validRows.find(r => r.score === minScore)
468
+ : null;
469
+
470
+ console.log('');
471
+ console.log('\x1b[1m nerviq multi-repo scan\x1b[0m');
472
+ console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
473
+ console.log('');
474
+
475
+ // Table header
476
+ const nameW = Math.max(8, ...rows.map(r => r.name.length)) + 2;
477
+ const header = ` ${'Project'.padEnd(nameW)} ${'Score'.padStart(5)} ${'Pass'.padStart(4)} ${'Fail'.padStart(4)} Suggested Command`;
478
+ console.log('\x1b[1m' + header + '\x1b[0m');
479
+ console.log(' ' + '─'.repeat(header.trim().length));
480
+
481
+ for (const row of rows) {
482
+ if (row.error) {
483
+ console.log(` ${row.name.padEnd(nameW)} \x1b[31m${('ERR').padStart(5)}\x1b[0m ${String(row.passed).padStart(4)} ${String(row.failed).padStart(4)} ${row.error}`);
484
+ continue;
485
+ }
486
+ const isWeak = weakest && row.name === weakest.name && row.dir === weakest.dir;
487
+ const scoreColor = row.score >= 70 ? '\x1b[32m' : row.score >= 40 ? '\x1b[33m' : '\x1b[31m';
488
+ const prefix = isWeak ? '\x1b[31m⚠ ' : ' ';
489
+ const suffix = isWeak ? ' ← weakest\x1b[0m' : '';
490
+ console.log(`${prefix}${row.name.padEnd(nameW)} ${scoreColor}${String(row.score).padStart(5)}\x1b[0m ${String(row.passed).padStart(4)} ${String(row.failed).padStart(4)} ${row.suggested}${suffix}`);
491
+ }
492
+ console.log('');
493
+ }
494
+ process.exit(0);
495
+ } else if (normalizedCommand === 'history') {
496
+ const { formatHistory } = require('../src/activity');
497
+ console.log('');
498
+ console.log(formatHistory(options.dir));
499
+ console.log('');
500
+ process.exit(0);
501
+ } else if (normalizedCommand === 'compare') {
502
+ const { compareLatest } = require('../src/activity');
503
+ const result = compareLatest(options.dir);
504
+ if (!result) {
505
+ console.log('\n Need at least 2 snapshots to compare. Run `npx nerviq --snapshot` twice.\n');
506
+ process.exit(0);
507
+ }
508
+ if (options.json) {
509
+ console.log(JSON.stringify(result, null, 2));
510
+ } else {
511
+ const sign = result.delta.score >= 0 ? '+' : '';
512
+ console.log('');
513
+ console.log(` Previous: ${result.previous.score}/100 (${result.previous.date?.split('T')[0]})`);
514
+ console.log(` Current: ${result.current.score}/100 (${result.current.date?.split('T')[0]})`);
515
+ console.log(` Delta: ${sign}${result.delta.score} points`);
516
+ console.log(` Trend: ${result.trend}`);
517
+ if (result.improvements.length > 0) console.log(` Fixed: ${result.improvements.join(', ')}`);
518
+ if (result.regressions.length > 0) console.log(` New gaps: ${result.regressions.join(', ')}`);
519
+ console.log('');
520
+ }
521
+ process.exit(0);
522
+ } else if (normalizedCommand === 'trend') {
523
+ const { exportTrendReport } = require('../src/activity');
524
+ const report = exportTrendReport(options.dir);
525
+ if (!report) {
526
+ console.log('\n No snapshots found. Run `npx nerviq --snapshot` to start tracking.\n');
527
+ process.exit(0);
528
+ }
529
+ if (options.out) {
530
+ require('fs').writeFileSync(options.out, report, 'utf8');
531
+ console.log(`\n Trend report exported to ${options.out}\n`);
532
+ } else {
533
+ console.log(report);
534
+ }
535
+ process.exit(0);
536
+ } else if (normalizedCommand === 'badge') {
537
+ const { getBadgeMarkdown } = require('../src/badge');
538
+ const result = await audit({ ...options, silent: true });
539
+ console.log(getBadgeMarkdown(result.score));
540
+ console.log('');
541
+ console.log('Add this to your README.md');
542
+ process.exit(0);
543
+ } else if (normalizedCommand === 'insights') {
544
+ const https = require('https');
545
+ const url = 'https://claudex-insights.claudex.workers.dev/v1/stats';
546
+ const req = https.get(url, (res) => {
547
+ let data = '';
548
+ res.on('data', chunk => data += chunk);
549
+ res.on('end', () => {
550
+ try {
551
+ const stats = JSON.parse(data);
552
+ console.log('');
553
+ console.log('\x1b[1m CLAUDEX Community Insights\x1b[0m');
554
+ console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
555
+ console.log(` Total audits run: \x1b[1m${stats.totalRuns}\x1b[0m`);
556
+ console.log(` Average score: \x1b[1m${stats.averageScore}/100\x1b[0m`);
557
+ console.log('');
558
+ if (stats.topFailedChecks && stats.topFailedChecks.length > 0) {
559
+ console.log('\x1b[33m Most common gaps:\x1b[0m');
560
+ for (const f of stats.topFailedChecks.slice(0, 5)) {
561
+ console.log(` ${f.pct}% miss: \x1b[1m${f.check}\x1b[0m`);
562
+ }
563
+ console.log('');
564
+ }
565
+ if (stats.topStacks && stats.topStacks.length > 0) {
566
+ console.log('\x1b[36m Popular stacks:\x1b[0m');
567
+ console.log(` ${stats.topStacks.map(s => s.stack).join(', ')}`);
568
+ }
569
+ console.log('');
570
+ } catch (e) {
571
+ console.log(' No community data available yet. Be the first to run: npx nerviq');
572
+ }
573
+ });
574
+ }).on('error', () => {
575
+ console.log(' Could not reach insights server. Run locally: npx nerviq');
576
+ });
577
+ req.setTimeout(10000, () => {
578
+ req.destroy();
579
+ console.log(' Insights request timed out. Run locally: npx nerviq');
580
+ });
581
+ return; // keep process alive for http
582
+ } else if (normalizedCommand === 'feedback') {
583
+ if (parsed.feedbackKey) {
584
+ if (!parsed.feedbackStatus) {
585
+ console.error('\n Error: feedback logging requires --status when --key is provided.\n');
586
+ process.exit(1);
587
+ }
588
+ const artifact = recordRecommendationOutcome(options.dir, {
589
+ key: parsed.feedbackKey,
590
+ status: parsed.feedbackStatus,
591
+ effect: parsed.feedbackEffect || 'neutral',
592
+ notes: parsed.feedbackNotes || '',
593
+ source: parsed.feedbackSource || 'manual-cli',
594
+ scoreDelta: parsed.feedbackScoreDelta !== null ? Number(parsed.feedbackScoreDelta) : null,
595
+ });
596
+ const summary = getRecommendationOutcomeSummary(options.dir);
597
+ if (options.json) {
598
+ console.log(JSON.stringify({ artifact, summary }, null, 2));
599
+ } else {
600
+ console.log('');
601
+ console.log(` Feedback recorded for ${parsed.feedbackKey}`);
602
+ console.log(` Artifact: ${artifact.relativePath}`);
603
+ console.log('');
604
+ console.log(formatRecommendationOutcomeSummary(options.dir));
605
+ console.log('');
606
+ }
607
+ } else {
608
+ if (options.json) {
609
+ console.log(JSON.stringify(getRecommendationOutcomeSummary(options.dir), null, 2));
610
+ } else {
611
+ console.log('');
612
+ console.log(formatRecommendationOutcomeSummary(options.dir));
613
+ console.log('');
614
+ }
615
+ }
616
+ process.exit(0);
617
+ } else if (normalizedCommand === 'augment' || normalizedCommand === 'suggest-only') {
618
+ const report = await analyzeProject({ ...options, mode: normalizedCommand });
619
+ const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, normalizedCommand, report, {
620
+ sourceCommand: normalizedCommand,
621
+ }) : null;
622
+ if (options.out && !options.json) {
623
+ const fs = require('fs');
624
+ const md = exportMarkdown(report);
625
+ fs.writeFileSync(options.out, md, 'utf8');
626
+ console.log(`\n Report exported to ${options.out}\n`);
627
+ }
628
+ printAnalysis(report, options);
629
+ if (snapshot && !options.json) {
630
+ console.log(` Snapshot saved: ${snapshot.relativePath}`);
631
+ console.log(` Snapshot index: ${snapshot.indexPath}`);
632
+ console.log('');
633
+ }
634
+ } else if (normalizedCommand === 'plan') {
635
+ const bundle = await buildProposalBundle(options);
636
+ let artifact = null;
637
+ if (options.out) {
638
+ artifact = writePlanFile(bundle, options.out);
639
+ }
640
+ printProposalBundle(bundle, options);
641
+ if (options.out && !options.json) {
642
+ console.log(` Plan written to ${options.out}`);
643
+ if (artifact) {
644
+ console.log(` Activity log: ${artifact.relativePath}`);
645
+ }
646
+ console.log('');
647
+ }
648
+ } else if (normalizedCommand === 'apply') {
649
+ const result = await applyProposalBundle(options);
650
+ printApplyResult(result, options);
651
+ } else if (normalizedCommand === 'governance') {
652
+ const fs = require('fs');
653
+ const path = require('path');
654
+ const summary = getGovernanceSummary(options.platform);
655
+ if (options.out) {
656
+ fs.mkdirSync(path.dirname(options.out), { recursive: true });
657
+ const content = path.extname(options.out).toLowerCase() === '.md'
658
+ ? renderGovernanceMarkdown(summary)
659
+ : JSON.stringify(summary, null, 2);
660
+ fs.writeFileSync(options.out, content, 'utf8');
661
+ }
662
+ printGovernanceSummary(summary, options);
663
+ const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'governance', summary, {
664
+ sourceCommand: normalizedCommand,
665
+ }) : null;
666
+ if (options.out && !options.json) {
667
+ console.log(` Governance report written to ${options.out}`);
668
+ console.log('');
669
+ }
670
+ if (snapshot && !options.json) {
671
+ console.log(` Snapshot saved: ${snapshot.relativePath}`);
672
+ console.log(` Snapshot index: ${snapshot.indexPath}`);
673
+ console.log('');
674
+ }
675
+ } else if (normalizedCommand === 'benchmark') {
676
+ const report = await runBenchmark(options);
677
+ const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'benchmark', report, {
678
+ sourceCommand: normalizedCommand,
679
+ }) : null;
680
+ if (options.out) {
681
+ writeBenchmarkReport(report, options.out);
682
+ }
683
+ printBenchmark(report, options);
684
+ if (options.out && !options.json) {
685
+ console.log(` Benchmark report written to ${options.out}`);
686
+ console.log('');
687
+ }
688
+ if (snapshot && !options.json) {
689
+ console.log(` Snapshot saved: ${snapshot.relativePath}`);
690
+ console.log(` Snapshot index: ${snapshot.indexPath}`);
691
+ console.log('');
692
+ }
693
+ } else if (normalizedCommand === 'deep-review') {
694
+ const { deepReview } = require('../src/deep-review');
695
+ await deepReview(options);
696
+ } else if (normalizedCommand === 'interactive') {
697
+ const { interactive } = require('../src/interactive');
698
+ await interactive(options);
699
+ } else if (normalizedCommand === 'watch') {
700
+ const { watch } = require('../src/watch');
701
+ await watch(options);
702
+ } else if (normalizedCommand === 'setup') {
703
+ await setup(options);
704
+ if (options.snapshot) {
705
+ const postSetupResult = await audit({ dir: options.dir, silent: true, platform: options.platform });
706
+ const snapshot = writeSnapshotArtifact(options.dir, 'audit', postSetupResult, {
707
+ sourceCommand: 'setup',
708
+ });
709
+ if (!options.json) {
710
+ console.log(` Snapshot saved: ${snapshot.relativePath}`);
711
+ }
712
+ }
713
+ } else {
714
+ const result = await audit(options);
715
+ const snapshot = options.snapshot ? writeSnapshotArtifact(options.dir, 'audit', result, {
716
+ sourceCommand: normalizedCommand,
717
+ }) : null;
718
+ if (snapshot && !options.json) {
719
+ console.log(` Snapshot saved: ${snapshot.relativePath}`);
720
+ console.log(` Snapshot index: ${snapshot.indexPath}`);
721
+ console.log('');
722
+ }
723
+ if (options.threshold !== null && result.score < options.threshold) {
724
+ if (!options.json) {
725
+ console.error(` Threshold failed: score ${result.score}/100 is below required ${options.threshold}/100.\n`);
726
+ }
727
+ process.exit(1);
728
+ }
729
+ if (options.require && options.require.length > 0) {
730
+ const failedRequired = options.require.filter(key => {
731
+ const check = result.results.find(r => r.key === key);
732
+ return !check || check.passed !== true;
733
+ });
734
+ if (failedRequired.length > 0) {
735
+ if (!options.json) {
736
+ console.error(`\n Required checks failed: ${failedRequired.join(', ')}`);
737
+ console.error(' These must pass for CI to succeed.\n');
738
+ }
739
+ process.exit(1);
740
+ }
741
+ }
742
+ }
743
+ } catch (err) {
744
+ console.error(`\n Error: ${err.message}`);
745
+ process.exit(1);
746
+ }
747
+ }
748
+
749
+ main();