infernoflow 0.32.8 → 0.32.9

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 (78) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +31 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -321
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/monorepo.mjs +4 -428
  37. package/dist/lib/commands/notify.mjs +4 -258
  38. package/dist/lib/commands/onboard.mjs +4 -296
  39. package/dist/lib/commands/prComment.mjs +2 -361
  40. package/dist/lib/commands/prImpact.mjs +2 -157
  41. package/dist/lib/commands/publish.mjs +15 -316
  42. package/dist/lib/commands/report.mjs +28 -272
  43. package/dist/lib/commands/review.mjs +9 -223
  44. package/dist/lib/commands/run.mjs +8 -336
  45. package/dist/lib/commands/scaffold.mjs +54 -419
  46. package/dist/lib/commands/scan.mjs +5 -558
  47. package/dist/lib/commands/scout.mjs +2 -291
  48. package/dist/lib/commands/setup.mjs +5 -310
  49. package/dist/lib/commands/share.mjs +13 -196
  50. package/dist/lib/commands/snapshot.mjs +3 -383
  51. package/dist/lib/commands/stability.mjs +2 -293
  52. package/dist/lib/commands/status.mjs +4 -172
  53. package/dist/lib/commands/suggest.mjs +21 -563
  54. package/dist/lib/commands/syncAuto.mjs +1 -96
  55. package/dist/lib/commands/synthesize.mjs +10 -228
  56. package/dist/lib/commands/teamSync.mjs +2 -388
  57. package/dist/lib/commands/test.mjs +6 -363
  58. package/dist/lib/commands/version.mjs +2 -282
  59. package/dist/lib/commands/vibe.mjs +7 -357
  60. package/dist/lib/commands/watch.mjs +4 -203
  61. package/dist/lib/commands/why.mjs +4 -358
  62. package/dist/lib/cursorHooksInstall.mjs +1 -60
  63. package/dist/lib/draftToolingInstall.mjs +7 -68
  64. package/dist/lib/git/detect-drift.mjs +4 -208
  65. package/dist/lib/learning/adapt.mjs +6 -101
  66. package/dist/lib/learning/observe.mjs +1 -119
  67. package/dist/lib/learning/patternDetector.mjs +1 -298
  68. package/dist/lib/learning/profile.mjs +2 -279
  69. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  70. package/dist/lib/templates/index.mjs +1 -131
  71. package/dist/lib/ui/errors.mjs +1 -142
  72. package/dist/lib/ui/output.mjs +6 -72
  73. package/dist/lib/ui/prompts.mjs +6 -147
  74. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  75. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  76. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  77. package/dist/templates/github-app/app-manifest.json +20 -0
  78. package/package.json +1 -1
@@ -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};