infernoflow 0.32.8 → 0.33.0

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 (81) 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 +34 -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/log.mjs +16 -0
  37. package/dist/lib/commands/monorepo.mjs +4 -428
  38. package/dist/lib/commands/notify.mjs +4 -258
  39. package/dist/lib/commands/onboard.mjs +4 -296
  40. package/dist/lib/commands/prComment.mjs +2 -361
  41. package/dist/lib/commands/prImpact.mjs +2 -157
  42. package/dist/lib/commands/publish.mjs +15 -316
  43. package/dist/lib/commands/report.mjs +28 -272
  44. package/dist/lib/commands/review.mjs +9 -223
  45. package/dist/lib/commands/run.mjs +8 -336
  46. package/dist/lib/commands/scaffold.mjs +54 -419
  47. package/dist/lib/commands/scan.mjs +5 -558
  48. package/dist/lib/commands/scout.mjs +2 -291
  49. package/dist/lib/commands/setup.mjs +5 -310
  50. package/dist/lib/commands/share.mjs +13 -196
  51. package/dist/lib/commands/snapshot.mjs +3 -383
  52. package/dist/lib/commands/stability.mjs +2 -293
  53. package/dist/lib/commands/status.mjs +4 -172
  54. package/dist/lib/commands/suggest.mjs +21 -563
  55. package/dist/lib/commands/syncAuto.mjs +1 -96
  56. package/dist/lib/commands/synthesize.mjs +10 -228
  57. package/dist/lib/commands/teamSync.mjs +2 -388
  58. package/dist/lib/commands/test.mjs +6 -363
  59. package/dist/lib/commands/theme.mjs +18 -0
  60. package/dist/lib/commands/version.mjs +2 -282
  61. package/dist/lib/commands/vibe.mjs +7 -357
  62. package/dist/lib/commands/watch.mjs +4 -203
  63. package/dist/lib/commands/why.mjs +4 -358
  64. package/dist/lib/cursorHooksInstall.mjs +1 -60
  65. package/dist/lib/draftToolingInstall.mjs +7 -68
  66. package/dist/lib/git/detect-drift.mjs +4 -208
  67. package/dist/lib/learning/adapt.mjs +6 -101
  68. package/dist/lib/learning/observe.mjs +1 -119
  69. package/dist/lib/learning/patternDetector.mjs +1 -298
  70. package/dist/lib/learning/profile.mjs +2 -279
  71. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  72. package/dist/lib/templates/index.mjs +1 -131
  73. package/dist/lib/theme/scanner.mjs +4 -0
  74. package/dist/lib/ui/errors.mjs +1 -142
  75. package/dist/lib/ui/output.mjs +6 -72
  76. package/dist/lib/ui/prompts.mjs +6 -147
  77. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  78. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  79. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  80. package/dist/templates/github-app/app-manifest.json +20 -0
  81. 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};