infernoflow 0.37.1 → 0.37.4

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 +71 -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 -520
  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,320 +1,9 @@
1
- /**
2
- * infernoflow adopt
3
- *
4
- * Interactive adoption wizard for existing projects.
5
- * Takes a project from zero to fully wired in one guided flow.
6
- *
7
- * Steps:
8
- * 1. Detect project type, framework, language
9
- * 2. Scout capabilities in source files
10
- * 3. Interactive approve / rename / skip each candidate
11
- * 4. Write capabilities.json + contract.json
12
- * 5. Generate starter scenarios
13
- * 6. Install git hooks
14
- * 7. Regenerate CONTEXT.md
15
- * 8. Show health score and next steps
16
- *
17
- * Usage:
18
- * infernoflow adopt Full interactive wizard
19
- * infernoflow adopt --yes Auto-approve all (non-interactive)
20
- * infernoflow adopt --dir src Custom source directory
21
- * infernoflow adopt --json Machine-readable, implies --yes
22
- */
23
-
24
- import * as fs from "node:fs";
25
- import * as path from "node:path";
26
- import * as readline from "node:readline";
27
- import { spawnSync } from "node:child_process";
28
- import { done, warn, info, bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
29
-
30
- // ── Detection ─────────────────────────────────────────────────────────────────
31
-
32
- function detectProject(cwd) {
33
- const has = (...files) => files.some(f => fs.existsSync(path.join(cwd, f)));
34
- const read = (f) => { try { return JSON.parse(fs.readFileSync(path.join(cwd, f), "utf8")); } catch { return {}; } };
35
-
36
- const pkg = has("package.json") ? read("package.json") : {};
37
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
38
-
39
- let framework = "unknown", lang = "javascript", type = "backend";
40
-
41
- if (has("tsconfig.json")) lang = "typescript";
42
- if (has("Pipfile","pyproject.toml","requirements.txt")) lang = "python";
43
- if (has("Gemfile")) lang = "ruby";
44
- if (has("go.mod")) lang = "go";
45
- if (has("Cargo.toml")) lang = "rust";
46
-
47
- if (deps["next"]) { framework = "nextjs"; type = "fullstack"; }
48
- else if (deps["nuxt"]) { framework = "nuxt"; type = "fullstack"; }
49
- else if (deps["react"]) { framework = "react"; type = "frontend"; }
50
- else if (deps["vue"]) { framework = "vue"; type = "frontend"; }
51
- else if (deps["@angular/core"]) { framework = "angular"; type = "frontend"; }
52
- else if (deps["express"]) { framework = "express"; type = "backend"; }
53
- else if (deps["fastify"]) { framework = "fastify"; type = "backend"; }
54
- else if (deps["hono"]) { framework = "hono"; type = "backend"; }
55
- else if (deps["@nestjs/core"]) { framework = "nestjs"; type = "backend"; }
56
- else if (deps["graphql"]) { framework = "graphql"; type = "backend"; }
57
- else if (has("config/routes.rb")) { framework = "rails"; type = "fullstack"; }
58
- else if (has("manage.py")) { framework = "django"; type = "backend"; }
59
-
60
- if (pkg.bin || has("bin/")) type = "cli";
61
- if (has("nx.json","turbo.json","pnpm-workspace.yaml")) type = "monorepo";
62
-
63
- return {
64
- name: pkg.name || path.basename(cwd),
65
- version: pkg.version || "0.1.0",
66
- description: pkg.description || "",
67
- lang, framework, type,
68
- };
69
- }
70
-
71
- // ── Scout ─────────────────────────────────────────────────────────────────────
72
-
73
- const SRC_EXTS = new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".py",".rb"]);
74
- const SKIP_DIRS = new Set(["node_modules",".git","dist","build",".next","__pycache__","vendor","coverage","inferno"]);
75
-
76
- const PATTERNS = [
77
- {
78
- regex: /(?:app|router|server)\s*\.\s*(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
79
- extract: m => ({ id: routeId(m[2], m[1]), hint: `${m[1].toUpperCase()} ${m[2]}`, confidence: 0.85 }),
80
- },
81
- {
82
- filePattern: /[/\\](pages[/\\]api|app[/\\]api)[/\\](.+)\.(js|ts|mjs)$/,
83
- extract: fp => {
84
- const m = fp.match(/[/\\](pages[/\\]api|app[/\\]api)[/\\](.+)\.(js|ts|mjs)$/);
85
- if (!m) return null;
86
- const r = m[2].replace(/[/\\]index$/, "").replace(/\[([^\]]+)\]/g, ":$1");
87
- return { id: routeId("/" + r, "api"), hint: `Next.js API: /${r}`, confidence: 0.9 };
88
- },
89
- },
90
- {
91
- regex: /export\s+(?:async\s+)?(?:function|class|const)\s+([A-Z][A-Za-z0-9]+)/g,
92
- extract: m => ({ id: camelId(m[1]), hint: `export ${m[1]}`, confidence: 0.65 }),
93
- },
94
- ];
95
-
96
- function routeId(r, method) {
97
- const id = r.replace(/^\//, "").replace(/\/:?[^/]+/g, "").replace(/\//g, "-")
98
- .replace(/[^a-zA-Z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
99
- return id || method.toLowerCase() + "-root";
100
- }
101
- function camelId(name) {
102
- return name.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "").replace(/-+/g, "-");
103
- }
104
-
105
- function* walk(dir) {
106
- if (!fs.existsSync(dir)) return;
107
- for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
108
- if (SKIP_DIRS.has(e.name)) continue;
109
- const full = path.join(dir, e.name);
110
- if (e.isDirectory()) yield* walk(full);
111
- else if (e.isFile() && SRC_EXTS.has(path.extname(e.name))) yield full;
112
- }
113
- }
114
-
115
- function scout(dirs, cwd) {
116
- const seen = new Map();
117
- const add = (id, r, file) => {
118
- if (!seen.has(id) || r.confidence > seen.get(id).confidence)
119
- seen.set(id, { ...r, source: path.relative(cwd, file) });
120
- };
121
-
122
- for (const dir of dirs) {
123
- for (const file of walk(dir)) {
124
- for (const pat of PATTERNS) {
125
- if (pat.filePattern && pat.filePattern.test(file)) {
126
- const r = pat.extract(file);
127
- if (r && r.id.length >= 3) add(r.id, r, file);
128
- }
129
- }
130
- let content;
131
- try { content = fs.readFileSync(file, "utf8"); } catch { continue; }
132
- for (const pat of PATTERNS) {
133
- if (!pat.regex) continue;
134
- pat.regex.lastIndex = 0;
135
- let m;
136
- while ((m = pat.regex.exec(content)) !== null) {
137
- const r = pat.extract(m);
138
- if (r && r.id.length >= 3 && !["index","app","main","root","default","handler"].includes(r.id))
139
- add(r.id, r, file);
140
- }
141
- }
142
- }
143
- }
144
- return [...seen.values()].filter(c => c.confidence >= 0.5).sort((a, b) => b.confidence - a.confidence);
145
- }
146
-
147
- // ── Interactive review ────────────────────────────────────────────────────────
148
-
149
- function ask(rl, q) { return new Promise(r => rl.question(q, r)); }
150
-
151
- async function review(candidates, autoYes) {
152
- if (autoYes) return candidates.filter(c => c.confidence >= 0.6);
153
-
154
- const approved = [];
155
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
156
-
157
- console.log();
158
- console.log(` ${bold("Review candidates")} ${cyan("y")} approve · ${yellow("e")} edit · ${red("n")} skip · ${gray("a")} approve all · ${gray("q")} stop`);
159
- console.log();
160
-
161
- for (let i = 0; i < candidates.length; i++) {
162
- const c = candidates[i];
163
- const conf = `${Math.round(c.confidence * 100)}%`;
164
- const col = c.confidence >= 0.8 ? green : c.confidence >= 0.65 ? yellow : gray;
165
- process.stdout.write(
166
- ` [${i + 1}/${candidates.length}] ${bold(c.id.padEnd(30))} ${col(conf)} ${gray(c.hint)}\n` +
167
- ` ${gray(c.source)}\n > `
168
- );
169
-
170
- const ans = (await ask(rl, "")).trim().toLowerCase();
171
- if (ans === "q") break;
172
- if (ans === "a") {
173
- for (let j = i; j < candidates.length; j++) approved.push(candidates[j]);
174
- break;
175
- }
176
- if (ans === "n") { console.log(` ${gray("skipped")}\n`); continue; }
177
-
178
- let id = c.id;
179
- if (ans === "e") {
180
- const newId = (await ask(rl, ` New id (${c.id}): `)).trim();
181
- if (newId && /^[a-z0-9-_]+$/.test(newId)) id = newId;
182
- }
183
- approved.push({ ...c, id });
184
- console.log(` ${green("✔")} ${bold(id)}\n`);
185
- }
186
-
187
- rl.close();
188
- return approved;
189
- }
190
-
191
- // ── Writers ───────────────────────────────────────────────────────────────────
192
-
193
- function writeFiles(infernoDir, approved, project) {
194
- // contract.json
195
- const cp = path.join(infernoDir, "contract.json");
196
- const existing = fs.existsSync(cp) ? JSON.parse(fs.readFileSync(cp, "utf8")) : {};
197
- fs.writeFileSync(cp, JSON.stringify({
198
- ...existing,
199
- policyId: existing.policyId || project.name.replace(/[^a-zA-Z0-9-]/g, "-"),
200
- policyVersion: existing.policyVersion || 1,
201
- capabilities: approved.map(c => c.id),
202
- }, null, 2) + "\n");
203
-
204
- // capabilities.json
205
- fs.writeFileSync(path.join(infernoDir, "capabilities.json"), JSON.stringify({
206
- capabilities: approved.map(c => ({
207
- id: c.id, description: c.hint,
208
- since: new Date().toISOString().slice(0, 10), source: "adopt",
209
- })),
210
- }, null, 2) + "\n");
211
-
212
- // Starter scenarios
213
- const scenDir = path.join(infernoDir, "scenarios");
214
- if (!fs.existsSync(scenDir)) fs.mkdirSync(scenDir, { recursive: true });
215
- for (const c of approved) {
216
- const sp = path.join(scenDir, `${c.id}.json`);
217
- if (fs.existsSync(sp)) continue;
218
- fs.writeFileSync(sp, JSON.stringify({
219
- id: `${c.id}-happy-path`, capability: c.id,
220
- description: `Happy path for ${c.description || c.id}`,
221
- steps: [{ action: "invoke", target: c.id, input: {} }, { action: "assert", field: "status", value: "success" }],
222
- capabilitiesCovered: [c.id],
223
- }, null, 2) + "\n");
224
- }
225
- }
226
-
227
- function runCli(args, cwd) {
228
- return spawnSync("infernoflow", args, {
229
- cwd, encoding: "utf8", timeout: 30_000,
230
- stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, NO_COLOR: "1" },
231
- });
232
- }
233
-
234
- // ── Entry ─────────────────────────────────────────────────────────────────────
235
-
236
- export async function adoptWizardCommand(rawArgs) {
237
- const args = rawArgs.slice(1);
238
- const autoYes = args.includes("--yes") || args.includes("-y") || args.includes("--json");
239
- const jsonMode = args.includes("--json");
240
- const cwd = process.cwd();
241
-
242
- const dirIdx = args.indexOf("--dir");
243
- const dirs = dirIdx !== -1
244
- ? args[dirIdx + 1].split(",").map(d => path.resolve(cwd, d.trim()))
245
- : ["src","lib","app","api","pages","routes","controllers","services","handlers"]
246
- .map(d => path.join(cwd, d)).filter(d => fs.existsSync(d));
247
- if (!dirs.length) dirs.push(cwd);
248
-
249
- const project = detectProject(cwd);
250
- const infernoDir = path.join(cwd, "inferno");
251
- if (!fs.existsSync(infernoDir)) {
252
- fs.mkdirSync(infernoDir, { recursive: true });
253
- fs.mkdirSync(path.join(infernoDir, "scenarios"), { recursive: true });
254
- }
255
-
256
- if (!jsonMode) {
257
- console.log();
258
- console.log(` ${bold("🔥 infernoflow adopt")} ${gray("— existing project wizard")}`);
259
- console.log();
260
- console.log(` ${bold("Project:")} ${cyan(project.name)} v${project.version}`);
261
- console.log(` ${bold("Language:")} ${project.lang} ${bold("Framework:")} ${project.framework} ${bold("Type:")} ${project.type}`);
262
- console.log();
263
- info(`Scanning ${dirs.map(d => path.relative(cwd, d) || ".").join(", ")} …`);
264
- }
265
-
266
- const candidates = scout(dirs, cwd);
267
-
268
- if (!candidates.length) {
269
- if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "No capabilities detected." })); return; }
270
- warn("No capabilities detected. Use --dir src,lib,api");
271
- return;
272
- }
273
-
274
- if (!jsonMode) {
275
- console.log();
276
- console.log(` ${bold(`${candidates.length} capability candidates detected`)}`);
277
- }
278
-
279
- const approved = await review(candidates, autoYes);
280
-
281
- if (!approved.length) {
282
- if (jsonMode) { console.log(JSON.stringify({ ok: false, error: "No capabilities approved." })); return; }
283
- warn("Nothing approved — exiting.");
284
- return;
285
- }
286
-
287
- writeFiles(infernoDir, approved, project);
288
- runCli(["context"], cwd);
289
-
290
- // Offer hooks in interactive mode
291
- let hooksInstalled = false;
292
- if (!autoYes) {
293
- const rl3 = readline.createInterface({ input: process.stdin, output: process.stdout });
294
- const ans = (await ask(rl3, `\n Install git hooks for auto-sync? ${gray("[Y/n]")} `)).trim().toLowerCase();
295
- rl3.close();
296
- if (ans !== "n") { runCli(["setup", "--yes"], cwd); hooksInstalled = true; }
297
- } else {
298
- runCli(["setup", "--yes"], cwd); hooksInstalled = true;
299
- }
300
-
301
- if (jsonMode) {
302
- console.log(JSON.stringify({ ok: true, project, detected: candidates.length, approved: approved.length, capabilities: approved.map(c => c.id), hooksInstalled }));
303
- return;
304
- }
305
-
306
- console.log();
307
- done(`${bold(String(approved.length))} capabilities adopted`);
308
- console.log();
309
- console.log(` ${green("✔")} ${bold("inferno/contract.json")} written`);
310
- console.log(` ${green("✔")} ${bold("inferno/capabilities.json")} written`);
311
- console.log(` ${green("✔")} ${bold("inferno/scenarios/")} scaffolded`);
312
- console.log(` ${green("✔")} ${bold("inferno/CONTEXT.md")} generated`);
313
- if (hooksInstalled) console.log(` ${green("✔")} Git hooks installed`);
314
- console.log();
315
- console.log(` ${bold("Next steps:")}`);
316
- console.log(` ${cyan("1.")} Paste ${bold("inferno/CONTEXT.md")} into Claude or Cursor`);
317
- console.log(` ${cyan("2.")} Run ${bold("infernoflow vibe")} to keep everything in sync automatically`);
318
- console.log(` ${cyan("3.")} Run ${bold("infernoflow health")} to see your starting score`);
319
- console.log();
320
- }
1
+ import*as p from"node:fs";import*as d from"node:path";import*as w from"node:readline";import{spawnSync as I}from"node:child_process";import{done as O,warn as S,info as P,bold as l,cyan as m,gray as y,green as $,yellow as k,red as F}from"../ui/output.mjs";function _(o){const t=(...a)=>a.some(f=>p.existsSync(d.join(o,f))),r=a=>{try{return JSON.parse(p.readFileSync(d.join(o,a),"utf8"))}catch{return{}}},c=t("package.json")?r("package.json"):{},e={...c.dependencies,...c.devDependencies};let n="unknown",s="javascript",i="backend";return t("tsconfig.json")&&(s="typescript"),t("Pipfile","pyproject.toml","requirements.txt")&&(s="python"),t("Gemfile")&&(s="ruby"),t("go.mod")&&(s="go"),t("Cargo.toml")&&(s="rust"),e.next?(n="nextjs",i="fullstack"):e.nuxt?(n="nuxt",i="fullstack"):e.react?(n="react",i="frontend"):e.vue?(n="vue",i="frontend"):e["@angular/core"]?(n="angular",i="frontend"):e.express?(n="express",i="backend"):e.fastify?(n="fastify",i="backend"):e.hono?(n="hono",i="backend"):e["@nestjs/core"]?(n="nestjs",i="backend"):e.graphql?(n="graphql",i="backend"):t("config/routes.rb")?(n="rails",i="fullstack"):t("manage.py")&&(n="django",i="backend"),(c.bin||t("bin/"))&&(i="cli"),t("nx.json","turbo.json","pnpm-workspace.yaml")&&(i="monorepo"),{name:c.name||d.basename(o),version:c.version||"0.1.0",description:c.description||"",lang:s,framework:n,type:i}}const T=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".py",".rb"]),J=new Set(["node_modules",".git","dist","build",".next","__pycache__","vendor","coverage","inferno"]),b=[{regex:/(?:app|router|server)\s*\.\s*(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/gi,extract:o=>({id:v(o[2],o[1]),hint:`${o[1].toUpperCase()} ${o[2]}`,confidence:.85})},{filePattern:/[/\\](pages[/\\]api|app[/\\]api)[/\\](.+)\.(js|ts|mjs)$/,extract:o=>{const t=o.match(/[/\\](pages[/\\]api|app[/\\]api)[/\\](.+)\.(js|ts|mjs)$/);if(!t)return null;const r=t[2].replace(/[/\\]index$/,"").replace(/\[([^\]]+)\]/g,":$1");return{id:v("/"+r,"api"),hint:`Next.js API: /${r}`,confidence:.9}}},{regex:/export\s+(?:async\s+)?(?:function|class|const)\s+([A-Z][A-Za-z0-9]+)/g,extract:o=>({id:A(o[1]),hint:`export ${o[1]}`,confidence:.65})}];function v(o,t){return o.replace(/^\//,"").replace(/\/:?[^/]+/g,"").replace(/\//g,"-").replace(/[^a-zA-Z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"").toLowerCase()||t.toLowerCase()+"-root"}function A(o){return o.replace(/([A-Z])/g,"-$1").toLowerCase().replace(/^-/,"").replace(/-+/g,"-")}function*N(o){if(p.existsSync(o))for(const t of p.readdirSync(o,{withFileTypes:!0})){if(J.has(t.name))continue;const r=d.join(o,t.name);t.isDirectory()?yield*N(r):t.isFile()&&T.has(d.extname(t.name))&&(yield r)}}function L(o,t){const r=new Map,c=(e,n,s)=>{(!r.has(e)||n.confidence>r.get(e).confidence)&&r.set(e,{...n,source:d.relative(t,s)})};for(const e of o)for(const n of N(e)){for(const i of b)if(i.filePattern&&i.filePattern.test(n)){const a=i.extract(n);a&&a.id.length>=3&&c(a.id,a,n)}let s;try{s=p.readFileSync(n,"utf8")}catch{continue}for(const i of b){if(!i.regex)continue;i.regex.lastIndex=0;let a;for(;(a=i.regex.exec(s))!==null;){const f=i.extract(a);f&&f.id.length>=3&&!["index","app","main","root","default","handler"].includes(f.id)&&c(f.id,f,n)}}}return[...r.values()].filter(e=>e.confidence>=.5).sort((e,n)=>n.confidence-e.confidence)}function j(o,t){return new Promise(r=>o.question(t,r))}async function R(o,t){if(t)return o.filter(e=>e.confidence>=.6);const r=[],c=w.createInterface({input:process.stdin,output:process.stdout});console.log(),console.log(` ${l("Review candidates")} ${m("y")} approve \xB7 ${k("e")} edit \xB7 ${F("n")} skip \xB7 ${y("a")} approve all \xB7 ${y("q")} stop`),console.log();for(let e=0;e<o.length;e++){const n=o[e],s=`${Math.round(n.confidence*100)}%`,i=n.confidence>=.8?$:n.confidence>=.65?k:y;process.stdout.write(` [${e+1}/${o.length}] ${l(n.id.padEnd(30))} ${i(s)} ${y(n.hint)}
2
+ ${y(n.source)}
3
+ > `);const a=(await j(c,"")).trim().toLowerCase();if(a==="q")break;if(a==="a"){for(let u=e;u<o.length;u++)r.push(o[u]);break}if(a==="n"){console.log(` ${y("skipped")}
4
+ `);continue}let f=n.id;if(a==="e"){const u=(await j(c,` New id (${n.id}): `)).trim();u&&/^[a-z0-9-_]+$/.test(u)&&(f=u)}r.push({...n,id:f}),console.log(` ${$("\u2714")} ${l(f)}
5
+ `)}return c.close(),r}function q(o,t,r){const c=d.join(o,"contract.json"),e=p.existsSync(c)?JSON.parse(p.readFileSync(c,"utf8")):{};p.writeFileSync(c,JSON.stringify({...e,policyId:e.policyId||r.name.replace(/[^a-zA-Z0-9-]/g,"-"),policyVersion:e.policyVersion||1,capabilities:t.map(s=>s.id)},null,2)+`
6
+ `),p.writeFileSync(d.join(o,"capabilities.json"),JSON.stringify({capabilities:t.map(s=>({id:s.id,description:s.hint,since:new Date().toISOString().slice(0,10),source:"adopt"}))},null,2)+`
7
+ `);const n=d.join(o,"scenarios");p.existsSync(n)||p.mkdirSync(n,{recursive:!0});for(const s of t){const i=d.join(n,`${s.id}.json`);p.existsSync(i)||p.writeFileSync(i,JSON.stringify({id:`${s.id}-happy-path`,capability:s.id,description:`Happy path for ${s.description||s.id}`,steps:[{action:"invoke",target:s.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[s.id]},null,2)+`
8
+ `)}}function x(o,t){return I("infernoflow",o,{cwd:t,encoding:"utf8",timeout:3e4,stdio:["ignore","pipe","pipe"],env:{...process.env,NO_COLOR:"1"}})}async function Z(o){const t=o.slice(1),r=t.includes("--yes")||t.includes("-y")||t.includes("--json"),c=t.includes("--json"),e=process.cwd(),n=t.indexOf("--dir"),s=n!==-1?t[n+1].split(",").map(g=>d.resolve(e,g.trim())):["src","lib","app","api","pages","routes","controllers","services","handlers"].map(g=>d.join(e,g)).filter(g=>p.existsSync(g));s.length||s.push(e);const i=_(e),a=d.join(e,"inferno");p.existsSync(a)||(p.mkdirSync(a,{recursive:!0}),p.mkdirSync(d.join(a,"scenarios"),{recursive:!0})),c||(console.log(),console.log(` ${l("\u{1F525} infernoflow adopt")} ${y("\u2014 existing project wizard")}`),console.log(),console.log(` ${l("Project:")} ${m(i.name)} v${i.version}`),console.log(` ${l("Language:")} ${i.lang} ${l("Framework:")} ${i.framework} ${l("Type:")} ${i.type}`),console.log(),P(`Scanning ${s.map(g=>d.relative(e,g)||".").join(", ")} \u2026`));const f=L(s,e);if(!f.length){if(c){console.log(JSON.stringify({ok:!1,error:"No capabilities detected."}));return}S("No capabilities detected. Use --dir src,lib,api");return}c||(console.log(),console.log(` ${l(`${f.length} capability candidates detected`)}`));const u=await R(f,r);if(!u.length){if(c){console.log(JSON.stringify({ok:!1,error:"No capabilities approved."}));return}S("Nothing approved \u2014 exiting.");return}q(a,u,i),x(["context"],e);let h=!1;if(r)x(["setup","--yes"],e),h=!0;else{const g=w.createInterface({input:process.stdin,output:process.stdout}),C=(await j(g,`
9
+ Install git hooks for auto-sync? ${y("[Y/n]")} `)).trim().toLowerCase();g.close(),C!=="n"&&(x(["setup","--yes"],e),h=!0)}if(c){console.log(JSON.stringify({ok:!0,project:i,detected:f.length,approved:u.length,capabilities:u.map(g=>g.id),hooksInstalled:h}));return}console.log(),O(`${l(String(u.length))} capabilities adopted`),console.log(),console.log(` ${$("\u2714")} ${l("inferno/contract.json")} written`),console.log(` ${$("\u2714")} ${l("inferno/capabilities.json")} written`),console.log(` ${$("\u2714")} ${l("inferno/scenarios/")} scaffolded`),console.log(` ${$("\u2714")} ${l("inferno/CONTEXT.md")} generated`),h&&console.log(` ${$("\u2714")} Git hooks installed`),console.log(),console.log(` ${l("Next steps:")}`),console.log(` ${m("1.")} Paste ${l("inferno/CONTEXT.md")} into Claude or Cursor`),console.log(` ${m("2.")} Run ${l("infernoflow vibe")} to keep everything in sync automatically`),console.log(` ${m("3.")} Run ${l("infernoflow health")} to see your starting score`),console.log()}export{Z as adoptWizardCommand};
@@ -1,191 +1,5 @@
1
- /**
2
- * infernoflow agent
3
- *
4
- * Manage and run auto-synthesized agents.
5
- *
6
- * Sub-commands:
7
- * infernoflow agent list — list all saved agents
8
- * infernoflow agent run <name> — execute an agent's command sequence
9
- * infernoflow agent show <name> — print agent definition
10
- * infernoflow agent delete <name> — remove an agent
11
- */
12
-
13
- import * as fs from "node:fs";
14
- import * as path from "node:path";
15
- import { execSync } from "node:child_process";
16
- import { header, ok, warn, info, done, bold, cyan, green, gray, red } from "../ui/output.mjs";
17
- import { observeCommandStart } from "../learning/observe.mjs";
18
-
19
- function findInfernoDir(cwd) {
20
- const d = path.join(cwd, "inferno");
21
- return fs.existsSync(d) ? d : null;
22
- }
23
-
24
- function agentsDir(infernoDir) {
25
- return path.join(infernoDir, "agents");
26
- }
27
-
28
- function loadAgents(infernoDir) {
29
- const dir = agentsDir(infernoDir);
30
- if (!fs.existsSync(dir)) return [];
31
- return fs.readdirSync(dir)
32
- .filter(f => f.endsWith(".json"))
33
- .map(f => {
34
- try { return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); }
35
- catch { return null; }
36
- })
37
- .filter(Boolean);
38
- }
39
-
40
- function findAgent(infernoDir, name) {
41
- const agents = loadAgents(infernoDir);
42
- return agents.find(a => a.name === name || a.id === name);
43
- }
44
-
45
- function runStep(cmd, args, cwd) {
46
- const fullCmd = `npx infernoflow ${cmd} ${args.join(" ")}`.trim();
47
- try {
48
- const out = execSync(fullCmd, {
49
- cwd,
50
- encoding: "utf8",
51
- timeout: 60_000,
52
- stdio: ["inherit", "pipe", "inherit"],
53
- });
54
- return { ok: true, output: out };
55
- } catch (err) {
56
- return { ok: false, output: err.stdout || err.message };
57
- }
58
- }
59
-
60
- // ── Sub-commands ──────────────────────────────────────────────────────────────
61
-
62
- function listAgents(infernoDir) {
63
- const agents = loadAgents(infernoDir);
64
- if (agents.length === 0) {
65
- info("No agents saved yet.");
66
- info(`Run ${cyan("infernoflow synthesize")} to auto-generate agents from your workflow patterns.`);
67
- return;
68
- }
69
-
70
- header("infernoflow agents");
71
- console.log(`\n ${bold(String(agents.length))} saved agent${agents.length !== 1 ? "s" : ""}:\n`);
72
- for (const agent of agents) {
73
- const steps = (agent.steps || []).map(s => s.command).join(" → ");
74
- const conf = agent.confidence ? gray(`${Math.round(agent.confidence * 100)}% confidence`) : "";
75
- console.log(` ${green("▶")} ${bold(agent.name)} ${conf}`);
76
- console.log(` ${gray(agent.description || steps)}`);
77
- console.log(` Steps: ${cyan(steps)}`);
78
- console.log();
79
- }
80
- console.log(` Run: ${cyan("infernoflow agent run <name>")}`);
81
- }
82
-
83
- async function runAgent(infernoDir, cwd, name) {
84
- const agent = findAgent(infernoDir, name);
85
- if (!agent) {
86
- warn(`Agent "${name}" not found.`);
87
- warn(`Run ${cyan("infernoflow agent list")} to see available agents.`);
88
- process.exit(1);
89
- }
90
-
91
- header(`infernoflow agent run: ${agent.name}`);
92
- info(agent.description || agent.name);
93
- console.log();
94
-
95
- const steps = agent.steps || [];
96
- let allOk = true;
97
-
98
- for (let i = 0; i < steps.length; i++) {
99
- const step = steps[i];
100
- const cmd = typeof step === "string" ? step : step.command;
101
- const args = typeof step === "string" ? [] : (step.args || []);
102
-
103
- console.log(` ${bold(`[${i + 1}/${steps.length}]`)} ${cyan(`infernoflow ${cmd} ${args.join(" ")}`.trim())}`);
104
-
105
- const result = runStep(cmd, args, cwd);
106
- if (result.output) {
107
- const lines = result.output.trim().split("\n");
108
- for (const line of lines) console.log(` ${gray(line)}`);
109
- }
110
-
111
- if (!result.ok) {
112
- warn(`Step "${cmd}" exited with an error — stopping agent.`);
113
- allOk = false;
114
- break;
115
- }
116
-
117
- ok(`Step ${i + 1} complete`);
118
- console.log();
119
- }
120
-
121
- if (allOk) {
122
- done(`Agent "${agent.name}" completed all ${steps.length} steps`);
123
- } else {
124
- console.log(`\n ${red("✘")} Agent stopped early. Fix the error above and re-run.`);
125
- process.exit(1);
126
- }
127
- }
128
-
129
- function showAgent(infernoDir, name) {
130
- const agent = findAgent(infernoDir, name);
131
- if (!agent) {
132
- warn(`Agent "${name}" not found.`);
133
- process.exit(1);
134
- }
135
- console.log(JSON.stringify(agent, null, 2));
136
- }
137
-
138
- function deleteAgent(infernoDir, name) {
139
- const agent = findAgent(infernoDir, name);
140
- if (!agent) {
141
- warn(`Agent "${name}" not found.`);
142
- process.exit(1);
143
- }
144
- const filePath = path.join(agentsDir(infernoDir), `${agent.name}.json`);
145
- fs.unlinkSync(filePath);
146
- ok(`Deleted agent: ${agent.name}`);
147
- }
148
-
149
- // ── Main ──────────────────────────────────────────────────────────────────────
150
-
151
- export async function agentCommand(args) {
152
- const cwd = process.cwd();
153
- const infernoDir = findInfernoDir(cwd);
154
-
155
- if (!infernoDir) {
156
- warn("inferno/ not found — run infernoflow init first");
157
- process.exit(1);
158
- }
159
-
160
- observeCommandStart(infernoDir, "agent");
161
-
162
- const sub = args[0];
163
- const name = args[1];
164
-
165
- switch (sub) {
166
- case "list":
167
- case undefined:
168
- listAgents(infernoDir);
169
- break;
170
-
171
- case "run":
172
- if (!name) { warn("Usage: infernoflow agent run <name>"); process.exit(1); }
173
- await runAgent(infernoDir, cwd, name);
174
- break;
175
-
176
- case "show":
177
- if (!name) { warn("Usage: infernoflow agent show <name>"); process.exit(1); }
178
- showAgent(infernoDir, name);
179
- break;
180
-
181
- case "delete":
182
- case "remove":
183
- if (!name) { warn("Usage: infernoflow agent delete <name>"); process.exit(1); }
184
- deleteAgent(infernoDir, name);
185
- break;
186
-
187
- default:
188
- // Treat unknown arg as agent name to run (convenience shortcut)
189
- await runAgent(infernoDir, cwd, sub);
190
- }
191
- }
1
+ import*as c from"node:fs";import*as g from"node:path";import{execSync as v}from"node:child_process";import{header as y,ok as x,warn as r,info as p,done as C,bold as m,cyan as f,green as D,gray as $,red as N}from"../ui/output.mjs";import{observeCommandStart as O}from"../learning/observe.mjs";function R(t){const e=g.join(t,"inferno");return c.existsSync(e)?e:null}function S(t){return g.join(t,"agents")}function k(t){const e=S(t);return c.existsSync(e)?c.readdirSync(e).filter(n=>n.endsWith(".json")).map(n=>{try{return JSON.parse(c.readFileSync(g.join(e,n),"utf8"))}catch{return null}}).filter(Boolean):[]}function w(t,e){return k(t).find(o=>o.name===e||o.id===e)}function U(t,e,n){const o=`npx infernoflow ${t} ${e.join(" ")}`.trim();try{return{ok:!0,output:v(o,{cwd:n,encoding:"utf8",timeout:6e4,stdio:["inherit","pipe","inherit"]})}}catch(s){return{ok:!1,output:s.stdout||s.message}}}function F(t){const e=k(t);if(e.length===0){p("No agents saved yet."),p(`Run ${f("infernoflow synthesize")} to auto-generate agents from your workflow patterns.`);return}y("infernoflow agents"),console.log(`
2
+ ${m(String(e.length))} saved agent${e.length!==1?"s":""}:
3
+ `);for(const n of e){const o=(n.steps||[]).map(l=>l.command).join(" \u2192 "),s=n.confidence?$(`${Math.round(n.confidence*100)}% confidence`):"";console.log(` ${D("\u25B6")} ${m(n.name)} ${s}`),console.log(` ${$(n.description||o)}`),console.log(` Steps: ${f(o)}`),console.log()}console.log(` Run: ${f("infernoflow agent run <name>")}`)}async function A(t,e,n){const o=w(t,n);o||(r(`Agent "${n}" not found.`),r(`Run ${f("infernoflow agent list")} to see available agents.`),process.exit(1)),y(`infernoflow agent run: ${o.name}`),p(o.description||o.name),console.log();const s=o.steps||[];let l=!0;for(let i=0;i<s.length;i++){const a=s[i],u=typeof a=="string"?a:a.command,h=typeof a=="string"?[]:a.args||[];console.log(` ${m(`[${i+1}/${s.length}]`)} ${f(`infernoflow ${u} ${h.join(" ")}`.trim())}`);const d=U(u,h,e);if(d.output){const b=d.output.trim().split(`
4
+ `);for(const j of b)console.log(` ${$(j)}`)}if(!d.ok){r(`Step "${u}" exited with an error \u2014 stopping agent.`),l=!1;break}x(`Step ${i+1} complete`),console.log()}l?C(`Agent "${o.name}" completed all ${s.length} steps`):(console.log(`
5
+ ${N("\u2718")} Agent stopped early. Fix the error above and re-run.`),process.exit(1))}function J(t,e){const n=w(t,e);n||(r(`Agent "${e}" not found.`),process.exit(1)),console.log(JSON.stringify(n,null,2))}function z(t,e){const n=w(t,e);n||(r(`Agent "${e}" not found.`),process.exit(1));const o=g.join(S(t),`${n.name}.json`);c.unlinkSync(o),x(`Deleted agent: ${n.name}`)}async function P(t){const e=process.cwd(),n=R(e);n||(r("inferno/ not found \u2014 run infernoflow init first"),process.exit(1)),O(n,"agent");const o=t[0],s=t[1];switch(o){case"list":case void 0:F(n);break;case"run":s||(r("Usage: infernoflow agent run <name>"),process.exit(1)),await A(n,e,s);break;case"show":s||(r("Usage: infernoflow agent show <name>"),process.exit(1)),J(n,s);break;case"delete":case"remove":s||(r("Usage: infernoflow agent delete <name>"),process.exit(1)),z(n,s);break;default:await A(n,e,o)}}export{P as agentCommand};