infernoflow 0.37.0 → 0.37.3

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 (88) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/bin/infernoflow.mjs +29 -277
  3. package/dist/lib/adopters/angular.mjs +1 -128
  4. package/dist/lib/adopters/css.mjs +1 -111
  5. package/dist/lib/adopters/react.mjs +1 -104
  6. package/dist/lib/ai/ideDetection.mjs +1 -31
  7. package/dist/lib/ai/localProvider.mjs +1 -88
  8. package/dist/lib/ai/providerRouter.mjs +2 -295
  9. package/dist/lib/commands/adopt.mjs +20 -869
  10. package/dist/lib/commands/adoptWizard.mjs +9 -320
  11. package/dist/lib/commands/agent.mjs +5 -191
  12. package/dist/lib/commands/ai.mjs +2 -407
  13. package/dist/lib/commands/ask.mjs +4 -299
  14. package/dist/lib/commands/audit.mjs +13 -300
  15. package/dist/lib/commands/changelog.mjs +26 -594
  16. package/dist/lib/commands/check.mjs +3 -184
  17. package/dist/lib/commands/ci.mjs +3 -208
  18. package/dist/lib/commands/claudeMd.mjs +30 -135
  19. package/dist/lib/commands/cloud.mjs +10 -773
  20. package/dist/lib/commands/context.mjs +34 -346
  21. package/dist/lib/commands/coverage.mjs +2 -282
  22. package/dist/lib/commands/dashboard.mjs +123 -635
  23. package/dist/lib/commands/demo.mjs +8 -465
  24. package/dist/lib/commands/diff.mjs +5 -274
  25. package/dist/lib/commands/docGate.mjs +2 -81
  26. package/dist/lib/commands/doctor.mjs +3 -321
  27. package/dist/lib/commands/explain.mjs +8 -438
  28. package/dist/lib/commands/export.mjs +10 -239
  29. package/dist/lib/commands/feedback.mjs +12 -216
  30. package/dist/lib/commands/generateSkills.mjs +38 -163
  31. package/dist/lib/commands/graph.mjs +11 -378
  32. package/dist/lib/commands/health.mjs +2 -309
  33. package/dist/lib/commands/impact.mjs +2 -325
  34. package/dist/lib/commands/implement.mjs +7 -103
  35. package/dist/lib/commands/init.mjs +45 -631
  36. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  37. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  38. package/dist/lib/commands/link.mjs +2 -342
  39. package/dist/lib/commands/log.mjs +18 -248
  40. package/dist/lib/commands/monorepo.mjs +4 -428
  41. package/dist/lib/commands/notify.mjs +4 -258
  42. package/dist/lib/commands/onboard.mjs +4 -296
  43. package/dist/lib/commands/prComment.mjs +2 -361
  44. package/dist/lib/commands/prImpact.mjs +2 -157
  45. package/dist/lib/commands/publish.mjs +15 -316
  46. package/dist/lib/commands/recap.mjs +6 -380
  47. package/dist/lib/commands/report.mjs +28 -272
  48. package/dist/lib/commands/review.mjs +9 -223
  49. package/dist/lib/commands/run.mjs +8 -336
  50. package/dist/lib/commands/scaffold.mjs +54 -419
  51. package/dist/lib/commands/scan.mjs +11 -1118
  52. package/dist/lib/commands/scout.mjs +2 -291
  53. package/dist/lib/commands/setup.mjs +5 -310
  54. package/dist/lib/commands/share.mjs +13 -196
  55. package/dist/lib/commands/snapshot.mjs +3 -383
  56. package/dist/lib/commands/stability.mjs +2 -293
  57. package/dist/lib/commands/stats.mjs +5 -402
  58. package/dist/lib/commands/status.mjs +4 -172
  59. package/dist/lib/commands/suggest.mjs +21 -563
  60. package/dist/lib/commands/switch.mjs +13 -517
  61. package/dist/lib/commands/syncAuto.mjs +1 -96
  62. package/dist/lib/commands/synthesize.mjs +10 -228
  63. package/dist/lib/commands/teamSync.mjs +2 -388
  64. package/dist/lib/commands/test.mjs +6 -363
  65. package/dist/lib/commands/theme.mjs +18 -195
  66. package/dist/lib/commands/uninstall.mjs +13 -406
  67. package/dist/lib/commands/upgrade.mjs +20 -153
  68. package/dist/lib/commands/version.mjs +2 -282
  69. package/dist/lib/commands/vibe.mjs +7 -357
  70. package/dist/lib/commands/watch.mjs +4 -203
  71. package/dist/lib/commands/why.mjs +4 -358
  72. package/dist/lib/cursorHooksInstall.mjs +1 -60
  73. package/dist/lib/draftToolingInstall.mjs +7 -68
  74. package/dist/lib/git/detect-drift.mjs +4 -208
  75. package/dist/lib/learning/adapt.mjs +6 -101
  76. package/dist/lib/learning/observe.mjs +1 -119
  77. package/dist/lib/learning/patternDetector.mjs +1 -298
  78. package/dist/lib/learning/profile.mjs +2 -279
  79. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  80. package/dist/lib/telemetry.mjs +19 -269
  81. package/dist/lib/templates/index.mjs +1 -131
  82. package/dist/lib/theme/scanner.mjs +4 -343
  83. package/dist/lib/ui/errors.mjs +1 -142
  84. package/dist/lib/ui/output.mjs +6 -95
  85. package/dist/lib/ui/prompts.mjs +6 -147
  86. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  87. package/package.json +2 -4
  88. package/scripts/postinstall.js +2 -2
@@ -1,406 +1,13 @@
1
- /**
2
- * infernoflow uninstall
3
- *
4
- * Removes everything infernoflow installed from a project.
5
- * The inverse of `infernoflow setup`.
6
- *
7
- * What it removes:
8
- * - inferno/ — contract, capabilities, session memory, HANDOFF.md
9
- * - CLAUDE.md — auto-behavior instruction file
10
- * - .claude/settings.json — pre-approved tools (infernoflow entries only)
11
- * - .cursor/inferno-mcp-server.mjs — MCP server file
12
- * - .cursor/hooks.json — cursor hooks config (if infernoflow-only)
13
- * - .cursor/hooks/inferno-session-draft.mjs cursor hook script
14
- * - .cursor/mcp.json — infernoflow entry (other entries preserved)
15
- * - inferno-mcp-server.mjs — root-level MCP server copy
16
- * - ~/.claude.json — infernoflow mcpServers entry (other entries preserved)
17
- * - .git/hooks/post-commit / pre-push — infernoflow sections (other hooks preserved)
18
- *
19
- * Flags:
20
- * --dry-run Preview what would be removed without touching anything
21
- * --keep-memory Preserve inferno/sessions.jsonl (your session logs)
22
- * --keep-inferno Preserve the entire inferno/ folder
23
- * --yes / -y Skip confirmation prompt
24
- * --json Machine-readable output
25
- *
26
- * Usage:
27
- * infernoflow uninstall Interactive — shows plan, asks to confirm
28
- * infernoflow uninstall --dry-run Show what would be removed
29
- * infernoflow uninstall --yes Remove without prompting
30
- * infernoflow uninstall --keep-memory Remove setup but keep session logs
31
- */
32
-
33
- import * as fs from "node:fs";
34
- import * as path from "node:path";
35
- import * as os from "node:os";
36
- import * as readline from "node:readline";
37
- import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
38
-
39
- const INFERNO_DIR = "inferno";
40
- const CLAUDE_MD = "CLAUDE.md";
41
- const CLAUDE_DIR = ".claude";
42
- const CURSOR_DIR = ".cursor";
43
- const MCP_SERVER = path.join(CURSOR_DIR, "inferno-mcp-server.mjs");
44
- const MCP_SERVER_ROOT = "inferno-mcp-server.mjs"; // root-level copy from install-cursor-hooks
45
- const CURSOR_HOOKS_JSON= path.join(CURSOR_DIR, "hooks.json");
46
- const CURSOR_HOOK_FILE = path.join(CURSOR_DIR, "hooks", "inferno-session-draft.mjs");
47
- const CURSOR_MCP = path.join(CURSOR_DIR, "mcp.json");
48
- const CLAUDE_JSON = path.join(os.homedir(), ".claude.json");
49
- const GIT_HOOKS = [".git/hooks/post-commit", ".git/hooks/pre-push"];
50
- const INFERNO_MARKER = "# infernoflow";
51
-
52
- // ── helpers ──────────────────────────────────────────────────────────────────
53
-
54
- function exists(p) { return fs.existsSync(p); }
55
- function readJSON(f) { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } }
56
-
57
- function readFile(f) {
58
- try { return fs.readFileSync(f, "utf8"); } catch { return null; }
59
- }
60
-
61
- function prompt(question) {
62
- return new Promise(resolve => {
63
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
64
- rl.question(question, ans => { rl.close(); resolve(ans); });
65
- });
66
- }
67
-
68
- // ── planners — compute what would be removed ─────────────────────────────────
69
-
70
- function planInfernoDir(cwd, keepMemory, keepInferno) {
71
- const items = [];
72
- const dir = path.join(cwd, INFERNO_DIR);
73
- if (!exists(dir)) return items;
74
-
75
- if (keepInferno) {
76
- items.push({ type: "skip", path: INFERNO_DIR, reason: "--keep-inferno" });
77
- return items;
78
- }
79
-
80
- if (keepMemory) {
81
- // Remove everything except sessions.jsonl
82
- const files = fs.readdirSync(dir);
83
- for (const f of files) {
84
- if (f === "sessions.jsonl") {
85
- items.push({ type: "skip", path: path.join(INFERNO_DIR, f), reason: "--keep-memory" });
86
- } else {
87
- const full = path.join(dir, f);
88
- const stat = fs.statSync(full);
89
- if (stat.isDirectory()) {
90
- items.push({ type: "rmdir", path: path.join(INFERNO_DIR, f) });
91
- } else {
92
- items.push({ type: "rm", path: path.join(INFERNO_DIR, f) });
93
- }
94
- }
95
- }
96
- } else {
97
- items.push({ type: "rmdir", path: INFERNO_DIR });
98
- }
99
-
100
- return items;
101
- }
102
-
103
- function planClaudeMd(cwd) {
104
- const p = path.join(cwd, CLAUDE_MD);
105
- if (!exists(p)) return [];
106
- return [{ type: "rm", path: CLAUDE_MD }];
107
- }
108
-
109
- function planClaudeDir(cwd) {
110
- const items = [];
111
- const settingsFile = path.join(cwd, CLAUDE_DIR, "settings.json");
112
- if (!exists(settingsFile)) return items;
113
-
114
- const settings = readJSON(settingsFile);
115
- const hasInfernoTools = settings?.tools?.some?.(t => t.startsWith?.("mcp__infernoflow"));
116
- const hasOtherContent = settings && Object.keys(settings).some(k => {
117
- if (k === "tools") {
118
- return (settings.tools || []).some(t => !t.startsWith("mcp__infernoflow"));
119
- }
120
- return k !== "tools";
121
- });
122
-
123
- if (hasInfernoTools && !hasOtherContent) {
124
- items.push({ type: "rm", path: path.join(CLAUDE_DIR, "settings.json"), desc: "auto-approved tools" });
125
- } else if (hasInfernoTools) {
126
- items.push({ type: "edit", path: path.join(CLAUDE_DIR, "settings.json"), desc: "remove infernoflow tools (preserve other content)" });
127
- }
128
- return items;
129
- }
130
-
131
- function planCursorMcpServer(cwd) {
132
- const items = [];
133
- // .cursor/inferno-mcp-server.mjs
134
- if (exists(path.join(cwd, MCP_SERVER))) items.push({ type: "rm", path: MCP_SERVER });
135
- // root-level inferno-mcp-server.mjs (written by install-cursor-hooks)
136
- if (exists(path.join(cwd, MCP_SERVER_ROOT))) items.push({ type: "rm", path: MCP_SERVER_ROOT });
137
- // .cursor/hooks/inferno-session-draft.mjs
138
- if (exists(path.join(cwd, CURSOR_HOOK_FILE))) items.push({ type: "rm", path: CURSOR_HOOK_FILE });
139
- // .cursor/hooks.json — only remove if it only contains the infernoflow hook
140
- const hooksJsonPath = path.join(cwd, CURSOR_HOOKS_JSON);
141
- if (exists(hooksJsonPath)) {
142
- const cfg = readJSON(hooksJsonPath);
143
- const hooks = cfg?.hooks || [];
144
- const hasOnlyInferno = hooks.every(h => (h.name || h.command || "").includes("inferno"));
145
- if (hasOnlyInferno) {
146
- items.push({ type: "rm", path: CURSOR_HOOKS_JSON, desc: "infernoflow-only hooks config" });
147
- } else {
148
- items.push({ type: "edit", path: CURSOR_HOOKS_JSON, desc: "remove infernoflow hook entry (preserve others)" });
149
- }
150
- }
151
- return items;
152
- }
153
-
154
- function planCursorMcpJson(cwd) {
155
- const p = path.join(cwd, CURSOR_MCP);
156
- if (!exists(p)) return [];
157
- const cfg = readJSON(p);
158
- if (!cfg?.mcpServers?.infernoflow) return [];
159
- const otherKeys = Object.keys(cfg.mcpServers || {}).filter(k => k !== "infernoflow");
160
- if (otherKeys.length === 0 && Object.keys(cfg).length === 1) {
161
- return [{ type: "rm", path: CURSOR_MCP, desc: "infernoflow-only file" }];
162
- }
163
- return [{ type: "edit", path: CURSOR_MCP, desc: 'remove "infernoflow" key (preserve other servers)' }];
164
- }
165
-
166
- function planClaudeJson() {
167
- if (!exists(CLAUDE_JSON)) return [];
168
- const cfg = readJSON(CLAUDE_JSON);
169
- if (!cfg?.mcpServers?.infernoflow) return [];
170
- return [{ type: "edit", path: "~/.claude.json", desc: 'remove "infernoflow" MCP entry (preserve other entries)', _realPath: CLAUDE_JSON }];
171
- }
172
-
173
- function planGitHooks(cwd) {
174
- const items = [];
175
- for (const hookRel of GIT_HOOKS) {
176
- const hookPath = path.join(cwd, hookRel);
177
- if (!exists(hookPath)) continue;
178
- const content = readFile(hookPath);
179
- if (!content?.includes(INFERNO_MARKER)) continue;
180
-
181
- const lines = content.split("\n");
182
- const markerIdx = lines.findIndex(l => l.includes(INFERNO_MARKER));
183
- const beforeMarker = lines.slice(0, markerIdx).join("\n").trim();
184
-
185
- if (!beforeMarker || beforeMarker === "#!/bin/sh" || beforeMarker === "#!/bin/bash") {
186
- items.push({ type: "rm", path: hookRel, desc: "infernoflow-only hook" });
187
- } else {
188
- items.push({ type: "edit", path: hookRel, desc: "remove infernoflow section (preserve existing hooks)" });
189
- }
190
- }
191
- return items;
192
- }
193
-
194
- // ── executors ─────────────────────────────────────────────────────────────────
195
-
196
- function removeInfernoDir(cwd, plan, dryRun) {
197
- for (const item of plan) {
198
- if (item.type === "skip") continue;
199
- const full = path.join(cwd, item.path);
200
- if (dryRun) continue;
201
- try {
202
- if (item.type === "rmdir") {
203
- fs.rmSync(full, { recursive: true, force: true });
204
- } else {
205
- fs.unlinkSync(full);
206
- }
207
- } catch {}
208
- }
209
- }
210
-
211
- function removeClaudeMd(cwd, dryRun) {
212
- if (dryRun) return;
213
- try { fs.unlinkSync(path.join(cwd, CLAUDE_MD)); } catch {}
214
- }
215
-
216
- function removeClaudeDir(cwd, plan, dryRun) {
217
- const settingsPath = path.join(cwd, CLAUDE_DIR, "settings.json");
218
- for (const item of plan) {
219
- if (dryRun) continue;
220
- if (item.type === "rm") {
221
- try { fs.unlinkSync(path.join(cwd, item.path)); } catch {}
222
- } else if (item.type === "edit") {
223
- try {
224
- const cfg = readJSON(settingsPath);
225
- if (cfg?.tools) {
226
- cfg.tools = cfg.tools.filter(t => !t.startsWith("mcp__infernoflow"));
227
- }
228
- fs.writeFileSync(settingsPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
229
- } catch {}
230
- }
231
- }
232
- }
233
-
234
- function removeCursorMcpServer(cwd, plan, dryRun) {
235
- if (dryRun) return;
236
- for (const item of plan) {
237
- if (item.type === "rm") {
238
- try { fs.unlinkSync(path.join(cwd, item.path)); } catch {}
239
- } else if (item.type === "edit" && item.path === CURSOR_HOOKS_JSON) {
240
- try {
241
- const cfg = readJSON(path.join(cwd, CURSOR_HOOKS_JSON));
242
- if (cfg?.hooks) {
243
- cfg.hooks = cfg.hooks.filter(h => !(h.name || h.command || "").includes("inferno"));
244
- }
245
- fs.writeFileSync(path.join(cwd, CURSOR_HOOKS_JSON), JSON.stringify(cfg, null, 2) + "\n", "utf8");
246
- } catch {}
247
- }
248
- }
249
- }
250
-
251
- function removeCursorMcpJson(cwd, plan, dryRun) {
252
- const mcpPath = path.join(cwd, CURSOR_MCP);
253
- for (const item of plan) {
254
- if (dryRun) continue;
255
- if (item.type === "rm") {
256
- try { fs.unlinkSync(mcpPath); } catch {}
257
- } else if (item.type === "edit") {
258
- try {
259
- const cfg = readJSON(mcpPath);
260
- if (cfg?.mcpServers?.infernoflow) delete cfg.mcpServers.infernoflow;
261
- fs.writeFileSync(mcpPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
262
- } catch {}
263
- }
264
- }
265
- }
266
-
267
- function removeClaudeJson(plan, dryRun) {
268
- for (const item of plan) {
269
- if (dryRun) continue;
270
- const p = item._realPath || CLAUDE_JSON;
271
- if (item.type === "edit") {
272
- try {
273
- const cfg = readJSON(p);
274
- if (cfg?.mcpServers?.infernoflow) delete cfg.mcpServers.infernoflow;
275
- fs.writeFileSync(p, JSON.stringify(cfg, null, 2) + "\n", "utf8");
276
- } catch {}
277
- }
278
- }
279
- }
280
-
281
- function removeGitHooks(cwd, plan, dryRun) {
282
- for (const item of plan) {
283
- const hookPath = path.join(cwd, item.path);
284
- if (dryRun) continue;
285
- if (item.type === "rm") {
286
- try { fs.unlinkSync(hookPath); } catch {}
287
- } else if (item.type === "edit") {
288
- try {
289
- const content = readFile(hookPath);
290
- const lines = content.split("\n");
291
- const markerIdx = lines.findIndex(l => l.includes(INFERNO_MARKER));
292
- const preserved = lines.slice(0, markerIdx).join("\n").trimEnd();
293
- fs.writeFileSync(hookPath, preserved + "\n", "utf8");
294
- } catch {}
295
- }
296
- }
297
- }
298
-
299
- // ── entry point ───────────────────────────────────────────────────────────────
300
-
301
- export async function uninstallCommand(args = []) {
302
- const has = f => args.includes(f);
303
- const dryRun = has("--dry-run") || has("--dry");
304
- const keepMem = has("--keep-memory");
305
- const keepInf = has("--keep-inferno");
306
- const skipPrompt= has("--yes") || has("-y");
307
- const jsonMode = has("--json");
308
-
309
- const cwd = process.cwd();
310
-
311
- // ── Build the full removal plan ──────────────────────────────────────────
312
- const plan = {
313
- infernoDir: planInfernoDir(cwd, keepMem, keepInf),
314
- claudeMd: planClaudeMd(cwd),
315
- claudeDir: planClaudeDir(cwd),
316
- cursorMcpServer: planCursorMcpServer(cwd),
317
- cursorMcpJson: planCursorMcpJson(cwd),
318
- claudeJson: planClaudeJson(),
319
- gitHooks: planGitHooks(cwd),
320
- };
321
-
322
- const allItems = Object.values(plan).flat();
323
- const actionItems = allItems.filter(i => i.type !== "skip");
324
-
325
- if (jsonMode) {
326
- console.log(JSON.stringify({ dryRun, keepMemory: keepMem, keepInferno: keepInf, plan, actionCount: actionItems.length }, null, 2));
327
- return;
328
- }
329
-
330
- const SEP = gray(" " + "─".repeat(52));
331
-
332
- console.log();
333
- console.log(" " + bold("🔥 infernoflow uninstall"));
334
- if (dryRun) console.log(yellow(" DRY RUN — nothing will be changed"));
335
- console.log(SEP);
336
-
337
- if (actionItems.length === 0) {
338
- console.log();
339
- console.log(green(" ✔ Nothing to remove — infernoflow is not installed in this project"));
340
- console.log();
341
- return;
342
- }
343
-
344
- // ── Print the plan ───────────────────────────────────────────────────────
345
- console.log();
346
- console.log(" " + bold("Will remove:"));
347
- console.log();
348
-
349
- const typeIcon = { rm: red(" ✖"), rmdir: red(" ✖"), edit: yellow(" ~"), skip: gray(" ·") };
350
-
351
- for (const item of allItems) {
352
- const icon = typeIcon[item.type] || " ?";
353
- const label = item.desc ? gray(` (${item.desc})`) : "";
354
- console.log(`${icon} ${item.path}${label}`);
355
- }
356
-
357
- if (keepMem) {
358
- console.log();
359
- console.log(gray(" ℹ inferno/sessions.jsonl will be preserved (--keep-memory)"));
360
- }
361
-
362
- console.log();
363
-
364
- // ── Confirm ──────────────────────────────────────────────────────────────
365
- if (!dryRun && !skipPrompt) {
366
- const ans = await prompt(" Continue? " + gray("[y/N] ") );
367
- if (!ans.trim().toLowerCase().startsWith("y")) {
368
- console.log(gray("\n Aborted — nothing changed.\n"));
369
- return;
370
- }
371
- console.log();
372
- }
373
-
374
- if (dryRun) {
375
- console.log(gray(" ↑ Dry run complete — run without --dry-run to apply\n"));
376
- return;
377
- }
378
-
379
- // ── Execute ──────────────────────────────────────────────────────────────
380
- removeInfernoDir(cwd, plan.infernoDir, false);
381
- if (plan.claudeMd.length) removeClaudeMd(cwd, false);
382
- if (plan.claudeDir.length) removeClaudeDir(cwd, plan.claudeDir, false);
383
- if (plan.cursorMcpServer.length) removeCursorMcpServer(cwd, plan.cursorMcpServer, false);
384
- if (plan.cursorMcpJson.length) removeCursorMcpJson(cwd, plan.cursorMcpJson, false);
385
- if (plan.claudeJson.length) removeClaudeJson(plan.claudeJson, false);
386
- if (plan.gitHooks.length) removeGitHooks(cwd, plan.gitHooks, false);
387
-
388
- // ── Summary ──────────────────────────────────────────────────────────────
389
- console.log(SEP);
390
- console.log();
391
- console.log(green(" ✔ infernoflow removed from this project"));
392
- console.log();
393
-
394
- const edited = actionItems.filter(i => i.type === "edit");
395
- const removed = actionItems.filter(i => i.type === "rm" || i.type === "rmdir");
396
-
397
- if (removed.length) console.log(gray(` Deleted: `) + removed.map(i => i.path).join(", "));
398
- if (edited.length) console.log(gray(` Edited: `) + edited.map(i => i.path).join(", "));
399
-
400
- if (keepMem) {
401
- console.log();
402
- console.log(gray(" Session memory kept → inferno/sessions.jsonl"));
403
- console.log(gray(" Re-run infernoflow init to restore the rest."));
404
- }
405
- console.log();
406
- }
1
+ import*as u from"node:fs";import*as r from"node:path";import*as A from"node:os";import*as K from"node:readline";import{bold as I,gray as a,green as D,yellow as J,red as N}from"../ui/output.mjs";const y="inferno",O="CLAUDE.md",k=".claude",S=".cursor",E=r.join(S,"inferno-mcp-server.mjs"),P="inferno-mcp-server.mjs",d=r.join(S,"hooks.json"),b=r.join(S,"hooks","inferno-session-draft.mjs"),v=r.join(S,"mcp.json"),w=r.join(A.homedir(),".claude.json"),L=[".git/hooks/post-commit",".git/hooks/pre-push"],C="# infernoflow";function h(n){return u.existsSync(n)}function m(n){try{return JSON.parse(u.readFileSync(n,"utf8"))}catch{return null}}function F(n){try{return u.readFileSync(n,"utf8")}catch{return null}}function W(n){return new Promise(e=>{const s=K.createInterface({input:process.stdin,output:process.stdout});s.question(n,o=>{s.close(),e(o)})})}function $(n,e,s){const o=[],t=r.join(n,y);if(!h(t))return o;if(s)return o.push({type:"skip",path:y,reason:"--keep-inferno"}),o;if(e){const i=u.readdirSync(t);for(const c of i)if(c==="sessions.jsonl")o.push({type:"skip",path:r.join(y,c),reason:"--keep-memory"});else{const l=r.join(t,c);u.statSync(l).isDirectory()?o.push({type:"rmdir",path:r.join(y,c)}):o.push({type:"rm",path:r.join(y,c)})}}else o.push({type:"rmdir",path:y});return o}function G(n){const e=r.join(n,O);return h(e)?[{type:"rm",path:O}]:[]}function T(n){const e=[],s=r.join(n,k,"settings.json");if(!h(s))return e;const o=m(s),t=o?.tools?.some?.(c=>c.startsWith?.("mcp__infernoflow")),i=o&&Object.keys(o).some(c=>c==="tools"?(o.tools||[]).some(l=>!l.startsWith("mcp__infernoflow")):c!=="tools");return t&&!i?e.push({type:"rm",path:r.join(k,"settings.json"),desc:"auto-approved tools"}):t&&e.push({type:"edit",path:r.join(k,"settings.json"),desc:"remove infernoflow tools (preserve other content)"}),e}function V(n){const e=[];h(r.join(n,E))&&e.push({type:"rm",path:E}),h(r.join(n,P))&&e.push({type:"rm",path:P}),h(r.join(n,b))&&e.push({type:"rm",path:b});const s=r.join(n,d);return h(s)&&((m(s)?.hooks||[]).every(c=>(c.name||c.command||"").includes("inferno"))?e.push({type:"rm",path:d,desc:"infernoflow-only hooks config"}):e.push({type:"edit",path:d,desc:"remove infernoflow hook entry (preserve others)"})),e}function q(n){const e=r.join(n,v);if(!h(e))return[];const s=m(e);return s?.mcpServers?.infernoflow?Object.keys(s.mcpServers||{}).filter(t=>t!=="infernoflow").length===0&&Object.keys(s).length===1?[{type:"rm",path:v,desc:"infernoflow-only file"}]:[{type:"edit",path:v,desc:'remove "infernoflow" key (preserve other servers)'}]:[]}function Y(){return h(w)?m(w)?.mcpServers?.infernoflow?[{type:"edit",path:"~/.claude.json",desc:'remove "infernoflow" MCP entry (preserve other entries)',_realPath:w}]:[]:[]}function z(n){const e=[];for(const s of L){const o=r.join(n,s);if(!h(o))continue;const t=F(o);if(!t?.includes(C))continue;const i=t.split(`
2
+ `),c=i.findIndex(f=>f.includes(C)),l=i.slice(0,c).join(`
3
+ `).trim();!l||l==="#!/bin/sh"||l==="#!/bin/bash"?e.push({type:"rm",path:s,desc:"infernoflow-only hook"}):e.push({type:"edit",path:s,desc:"remove infernoflow section (preserve existing hooks)"})}return e}function B(n,e,s){for(const o of e){if(o.type==="skip")continue;const t=r.join(n,o.path);if(!s)try{o.type==="rmdir"?u.rmSync(t,{recursive:!0,force:!0}):u.unlinkSync(t)}catch{}}}function Q(n,e){if(!e)try{u.unlinkSync(r.join(n,O))}catch{}}function X(n,e,s){const o=r.join(n,k,"settings.json");for(const t of e)if(!s){if(t.type==="rm")try{u.unlinkSync(r.join(n,t.path))}catch{}else if(t.type==="edit")try{const i=m(o);i?.tools&&(i.tools=i.tools.filter(c=>!c.startsWith("mcp__infernoflow"))),u.writeFileSync(o,JSON.stringify(i,null,2)+`
4
+ `,"utf8")}catch{}}}function Z(n,e,s){if(!s){for(const o of e)if(o.type==="rm")try{u.unlinkSync(r.join(n,o.path))}catch{}else if(o.type==="edit"&&o.path===d)try{const t=m(r.join(n,d));t?.hooks&&(t.hooks=t.hooks.filter(i=>!(i.name||i.command||"").includes("inferno"))),u.writeFileSync(r.join(n,d),JSON.stringify(t,null,2)+`
5
+ `,"utf8")}catch{}}}function oo(n,e,s){const o=r.join(n,v);for(const t of e)if(!s){if(t.type==="rm")try{u.unlinkSync(o)}catch{}else if(t.type==="edit")try{const i=m(o);i?.mcpServers?.infernoflow&&delete i.mcpServers.infernoflow,u.writeFileSync(o,JSON.stringify(i,null,2)+`
6
+ `,"utf8")}catch{}}}function eo(n,e){for(const s of n){if(e)continue;const o=s._realPath||w;if(s.type==="edit")try{const t=m(o);t?.mcpServers?.infernoflow&&delete t.mcpServers.infernoflow,u.writeFileSync(o,JSON.stringify(t,null,2)+`
7
+ `,"utf8")}catch{}}}function no(n,e,s){for(const o of e){const t=r.join(n,o.path);if(!s){if(o.type==="rm")try{u.unlinkSync(t)}catch{}else if(o.type==="edit")try{const c=F(t).split(`
8
+ `),l=c.findIndex(g=>g.includes(C)),f=c.slice(0,l).join(`
9
+ `).trimEnd();u.writeFileSync(t,f+`
10
+ `,"utf8")}catch{}}}}async function ro(n=[]){const e=p=>n.includes(p),s=e("--dry-run")||e("--dry"),o=e("--keep-memory"),t=e("--keep-inferno"),i=e("--yes")||e("-y"),c=e("--json"),l=process.cwd(),f={infernoDir:$(l,o,t),claudeMd:G(l),claudeDir:T(l),cursorMcpServer:V(l),cursorMcpJson:q(l),claudeJson:Y(),gitHooks:z(l)},g=Object.values(f).flat(),j=g.filter(p=>p.type!=="skip");if(c){console.log(JSON.stringify({dryRun:s,keepMemory:o,keepInferno:t,plan:f,actionCount:j.length},null,2));return}const R=a(" "+"\u2500".repeat(52));if(console.log(),console.log(" "+I("\u{1F525} infernoflow uninstall")),s&&console.log(J(" DRY RUN \u2014 nothing will be changed")),console.log(R),j.length===0){console.log(),console.log(D(" \u2714 Nothing to remove \u2014 infernoflow is not installed in this project")),console.log();return}console.log(),console.log(" "+I("Will remove:")),console.log();const U={rm:N(" \u2716"),rmdir:N(" \u2716"),edit:J(" ~"),skip:a(" \xB7")};for(const p of g){const x=U[p.type]||" ?",H=p.desc?a(` (${p.desc})`):"";console.log(`${x} ${p.path}${H}`)}if(o&&(console.log(),console.log(a(" \u2139 inferno/sessions.jsonl will be preserved (--keep-memory)"))),console.log(),!s&&!i){if(!(await W(" Continue? "+a("[y/N] "))).trim().toLowerCase().startsWith("y")){console.log(a(`
11
+ Aborted \u2014 nothing changed.
12
+ `));return}console.log()}if(s){console.log(a(` \u2191 Dry run complete \u2014 run without --dry-run to apply
13
+ `));return}B(l,f.infernoDir,!1),f.claudeMd.length&&Q(l,!1),f.claudeDir.length&&X(l,f.claudeDir,!1),f.cursorMcpServer.length&&Z(l,f.cursorMcpServer,!1),f.cursorMcpJson.length&&oo(l,f.cursorMcpJson,!1),f.claudeJson.length&&eo(f.claudeJson,!1),f.gitHooks.length&&no(l,f.gitHooks,!1),console.log(R),console.log(),console.log(D(" \u2714 infernoflow removed from this project")),console.log();const M=j.filter(p=>p.type==="edit"),_=j.filter(p=>p.type==="rm"||p.type==="rmdir");_.length&&console.log(a(" Deleted: ")+_.map(p=>p.path).join(", ")),M.length&&console.log(a(" Edited: ")+M.map(p=>p.path).join(", ")),o&&(console.log(),console.log(a(" Session memory kept \u2192 inferno/sessions.jsonl")),console.log(a(" Re-run infernoflow init to restore the rest."))),console.log()}export{ro as uninstallCommand};
@@ -1,153 +1,20 @@
1
- /**
2
- * infernoflow upgrade
3
- *
4
- * Converts a lite infernoflow setup (3 files) into the full setup
5
- * scenarios/, CHANGELOG.md, package.json scripts, and optionally hooks.
6
- *
7
- * Run this when a small project grows and needs the full capability contract,
8
- * coverage tracking, and changelog management.
9
- *
10
- * Usage:
11
- * infernoflow upgrade Interactive upgrade
12
- * infernoflow upgrade --yes Skip prompts, upgrade everything
13
- * infernoflow upgrade --dry-run Show what would be created
14
- */
15
-
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
19
-
20
- const INFERNO_DIR = "inferno";
21
-
22
- function readJSON(f) { try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch { return null; } }
23
-
24
- export async function upgradeCommand(args) {
25
- const cwd = process.cwd();
26
- const dryRun = args.includes("--dry-run");
27
- const yes = args.includes("--yes") || args.includes("-y");
28
-
29
- console.log("\n " + bold("🔥 infernoflow upgrade"));
30
- console.log(" " + "─".repeat(50) + "\n");
31
-
32
- const infernoDir = path.join(cwd, INFERNO_DIR);
33
-
34
- if (!fs.existsSync(infernoDir)) {
35
- console.error(red(" ✘ inferno/ not found — run: infernoflow init --lite first\n"));
36
- process.exit(1);
37
- }
38
-
39
- const liteMark = path.join(infernoDir, ".lite");
40
- const isLite = fs.existsSync(liteMark);
41
- const contract = readJSON(path.join(infernoDir, "contract.json"));
42
- const policyId = contract?.policyId || path.basename(cwd);
43
- const caps = contract?.capabilities || [];
44
-
45
- if (!isLite) {
46
- console.log(yellow(" ⚠ This project is already on the full setup — nothing to upgrade.\n"));
47
- return;
48
- }
49
-
50
- console.log(gray(` Project: ${policyId}`));
51
- console.log(gray(` Capabilities: ${caps.length || 0}`));
52
- console.log(gray(` Mode: lite → full\n`));
53
-
54
- const created = [];
55
-
56
- const write = (relPath, content) => {
57
- const full = path.join(cwd, relPath);
58
- if (fs.existsSync(full)) {
59
- console.log(gray(` skipped (exists): ${relPath}`));
60
- return;
61
- }
62
- if (dryRun) {
63
- console.log(cyan(` would create: ${relPath}`));
64
- return;
65
- }
66
- fs.mkdirSync(path.dirname(full), { recursive: true });
67
- fs.writeFileSync(full, content, "utf8");
68
- console.log(green(` ✔ Created: ${relPath}`));
69
- created.push(relPath);
70
- };
71
-
72
- // ── scenarios/ ──────────────────────────────────────────────────────────────
73
- if (caps.length) {
74
- const scenario = {
75
- scenarioId: "happy_path",
76
- description: "Basic happy-path covering all capabilities",
77
- capabilitiesCovered: caps,
78
- steps: caps.map(c => ({ action: c, expect: `${c} works as expected` })),
79
- };
80
- write(
81
- path.join(INFERNO_DIR, "scenarios", "happy_path.json"),
82
- JSON.stringify(scenario, null, 2) + "\n"
83
- );
84
- } else {
85
- if (!dryRun) fs.mkdirSync(path.join(infernoDir, "scenarios"), { recursive: true });
86
- console.log(gray(" created: inferno/scenarios/ (empty — add scenarios as you define capabilities)"));
87
- }
88
-
89
- // ── CHANGELOG.md ──────────────────────────────────────────────────────────
90
- write(
91
- path.join(INFERNO_DIR, "CHANGELOG.md"),
92
- `# Changelog — ${policyId}\n\n## Unreleased\n\n- Upgraded from lite setup\n\n## 0.1.0 — Initial release\n\n- Project initialized with infernoflow\n`
93
- );
94
-
95
- // ── Upgrade contract.json — add rules + remove lite flag ──────────────────
96
- if (!dryRun && contract) {
97
- contract.rules = {
98
- docsRequiredOnCapabilityChange: true,
99
- requireScenarioForEachCapability: false, // warning only, not error
100
- requireChangelogOnCapabilityChange: true,
101
- };
102
- delete contract.lite;
103
- fs.writeFileSync(path.join(infernoDir, "contract.json"), JSON.stringify(contract, null, 2) + "\n");
104
- console.log(green(" ✔ Updated: inferno/contract.json (added rules)"));
105
- created.push("inferno/contract.json");
106
- }
107
-
108
- // ── package.json scripts ───────────────────────────────────────────────────
109
- const pkgPath = path.join(cwd, "package.json");
110
- if (fs.existsSync(pkgPath) && !dryRun) {
111
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
112
- pkg.scripts = pkg.scripts || {};
113
- let changed = false;
114
- const toAdd = {
115
- "inferno:check": "infernoflow check",
116
- "inferno:context": "infernoflow context",
117
- "inferno:theme": "infernoflow theme",
118
- };
119
- for (const [k, v] of Object.entries(toAdd)) {
120
- if (!pkg.scripts[k]) { pkg.scripts[k] = v; changed = true; }
121
- }
122
- if (changed) {
123
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
124
- console.log(green(" ✔ Updated: package.json scripts (inferno:check, inferno:context, inferno:theme)"));
125
- created.push("package.json");
126
- }
127
- } else if (dryRun) {
128
- console.log(cyan(" would update: package.json scripts"));
129
- }
130
-
131
- // ── Remove .lite marker ────────────────────────────────────────────────────
132
- if (!dryRun && fs.existsSync(liteMark)) {
133
- fs.unlinkSync(liteMark);
134
- console.log(green(" ✔ Removed .lite marker — now on full setup"));
135
- }
136
-
137
- console.log();
138
-
139
- if (dryRun) {
140
- console.log(yellow(" ⚑ Dry run — nothing written. Remove --dry-run to apply.\n"));
141
- return;
142
- }
143
-
144
- if (!created.length) {
145
- console.log(gray(" Nothing new to create — already fully set up.\n"));
146
- return;
147
- }
148
-
149
- console.log(" " + bold("Upgrade complete!"));
150
- console.log(" " + cyan("→") + " Run " + cyan("infernoflow check") + " to validate the contract");
151
- console.log(" " + cyan("→") + " Run " + cyan("infernoflow vibe") + " to start auto-sync mode");
152
- console.log();
153
- }
1
+ import*as n from"node:fs";import*as o from"node:path";import{bold as k,cyan as r,gray as c,green as g,yellow as x,red as O}from"../ui/output.mjs";const m="inferno";function N(l){try{return JSON.parse(n.readFileSync(l,"utf8"))}catch{return null}}async function F(l){const f=process.cwd(),i=l.includes("--dry-run"),v=l.includes("--yes")||l.includes("-y");console.log(`
2
+ `+k("\u{1F525} infernoflow upgrade")),console.log(" "+"\u2500".repeat(50)+`
3
+ `);const a=o.join(f,m);n.existsSync(a)||(console.error(O(` \u2718 inferno/ not found \u2014 run: infernoflow init --lite first
4
+ `)),process.exit(1));const y=o.join(a,".lite"),b=n.existsSync(y),s=N(o.join(a,"contract.json")),j=s?.policyId||o.basename(f),d=s?.capabilities||[];if(!b){console.log(x(` \u26A0 This project is already on the full setup \u2014 nothing to upgrade.
5
+ `));return}console.log(c(` Project: ${j}`)),console.log(c(` Capabilities: ${d.length||0}`)),console.log(c(` Mode: lite \u2192 full
6
+ `));const u=[],w=(e,t)=>{const p=o.join(f,e);if(n.existsSync(p)){console.log(c(` skipped (exists): ${e}`));return}if(i){console.log(r(` would create: ${e}`));return}n.mkdirSync(o.dirname(p),{recursive:!0}),n.writeFileSync(p,t,"utf8"),console.log(g(` \u2714 Created: ${e}`)),u.push(e)};if(d.length){const e={scenarioId:"happy_path",description:"Basic happy-path covering all capabilities",capabilitiesCovered:d,steps:d.map(t=>({action:t,expect:`${t} works as expected`}))};w(o.join(m,"scenarios","happy_path.json"),JSON.stringify(e,null,2)+`
7
+ `)}else i||n.mkdirSync(o.join(a,"scenarios"),{recursive:!0}),console.log(c(" created: inferno/scenarios/ (empty \u2014 add scenarios as you define capabilities)"));w(o.join(m,"CHANGELOG.md"),`# Changelog \u2014 ${j}
8
+
9
+ ## Unreleased
10
+
11
+ - Upgraded from lite setup
12
+
13
+ ## 0.1.0 \u2014 Initial release
14
+
15
+ - Project initialized with infernoflow
16
+ `),!i&&s&&(s.rules={docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!1,requireChangelogOnCapabilityChange:!0},delete s.lite,n.writeFileSync(o.join(a,"contract.json"),JSON.stringify(s,null,2)+`
17
+ `),console.log(g(" \u2714 Updated: inferno/contract.json (added rules)")),u.push("inferno/contract.json"));const h=o.join(f,"package.json");if(n.existsSync(h)&&!i){const e=JSON.parse(n.readFileSync(h,"utf8"));e.scripts=e.scripts||{};let t=!1;const p={"inferno:check":"infernoflow check","inferno:context":"infernoflow context","inferno:theme":"infernoflow theme"};for(const[S,C]of Object.entries(p))e.scripts[S]||(e.scripts[S]=C,t=!0);t&&(n.writeFileSync(h,JSON.stringify(e,null,2)+`
18
+ `),console.log(g(" \u2714 Updated: package.json scripts (inferno:check, inferno:context, inferno:theme)")),u.push("package.json"))}else i&&console.log(r(" would update: package.json scripts"));if(!i&&n.existsSync(y)&&(n.unlinkSync(y),console.log(g(" \u2714 Removed .lite marker \u2014 now on full setup"))),console.log(),i){console.log(x(` \u2691 Dry run \u2014 nothing written. Remove --dry-run to apply.
19
+ `));return}if(!u.length){console.log(c(` Nothing new to create \u2014 already fully set up.
20
+ `));return}console.log(" "+k("Upgrade complete!")),console.log(" "+r("\u2192")+" Run "+r("infernoflow check")+" to validate the contract"),console.log(" "+r("\u2192")+" Run "+r("infernoflow vibe")+" to start auto-sync mode"),console.log()}export{F as upgradeCommand};