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,489 +1,124 @@
1
- /**
2
- * infernoflow scaffold
3
- *
4
- * Generate a new capability — source file skeleton + contract registration
5
- * + placeholder scenario — pre-wired to the project's detected patterns.
6
- *
7
- * Usage:
8
- * infernoflow scaffold payment-refund
9
- * infernoflow scaffold payment-refund --dir src/payments
10
- * infernoflow scaffold payment-refund --lang ts --dry-run
11
- * infernoflow scaffold payment-refund --json
12
- */
13
-
14
- import * as fs from "node:fs";
15
- import * as path from "node:path";
16
- import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
17
-
18
- // ── helpers ───────────────────────────────────────────────────────────────────
19
-
20
- function loadJson(p) {
21
- try { return JSON.parse(fs.readFileSync(p, "utf8")); }
22
- catch { return null; }
23
- }
24
-
25
- function saveJson(p, data) {
26
- fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n");
27
- }
28
-
29
- // ── name derivers ─────────────────────────────────────────────────────────────
30
-
31
- /** payment-refund → PaymentRefund */
32
- function toPascalCase(id) {
33
- return id.split(/[-_]/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join("");
34
- }
35
-
36
- /** payment-refund → paymentRefund */
37
- function toCamelCase(id) {
38
- const p = toPascalCase(id);
39
- return p.charAt(0).toLowerCase() + p.slice(1);
40
- }
41
-
42
- /** payment-refund → "Payment Refund" */
43
- function toTitle(id) {
44
- return id.split(/[-_]/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
45
- }
46
-
47
- /**
48
- * Derive a primary function name from the cap ID.
49
- * payment-refund → refundPayment, user-auth → authenticateUser
50
- */
51
- function primaryFnName(id) {
52
- const parts = id.split(/[-_]/);
53
- if (parts.length === 1) return toCamelCase(id);
54
-
55
- // Common verb patterns: if second word is a verb-like term, flip order
56
- const VERBS = ["auth", "login", "logout", "register", "refresh", "validate",
57
- "verify", "process", "refund", "charge", "send", "fetch",
58
- "create", "update", "delete", "get", "list", "search",
59
- "sync", "import", "export", "scan", "check", "notify"];
60
-
61
- const last = parts[parts.length - 1];
62
- const first = parts[0];
63
-
64
- // If last part looks like a noun and first part looks verb-like, use as-is
65
- if (VERBS.includes(first)) {
66
- // auth-user → authenticateUser style (expand verb)
67
- const verbExpand = { auth: "authenticate", get: "get", list: "list",
68
- send: "send", check: "check", notify: "notify" };
69
- const verb = verbExpand[first] || first;
70
- const rest = parts.slice(1).map((w, i) =>
71
- i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w
72
- ).join("");
73
- return verb + rest.charAt(0).toUpperCase() + rest.slice(1);
74
- }
75
-
76
- // Default: flip last+first — payment-refund → refundPayment
77
- if (VERBS.includes(last)) {
78
- const noun = parts.slice(0, -1).map((w, i) =>
79
- i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w
80
- ).join("");
81
- return last + noun;
82
- }
83
-
84
- return toCamelCase(id);
85
- }
86
-
87
- // ── language detector ─────────────────────────────────────────────────────────
88
-
89
- function detectLang(scan, profile, cwd) {
90
- // 1. From scan source files
91
- if (scan?.capabilities?.length) {
92
- const files = scan.capabilities.flatMap(c => c.codeAnalysis?.sourceFiles || []);
93
- const exts = files.map(f => path.extname(f));
94
- if (exts.filter(e => e === ".ts").length > exts.filter(e => e === ".js").length) return "ts";
95
- if (exts.includes(".py")) return "py";
96
- if (exts.includes(".go")) return "go";
97
- if (exts.some(e => e === ".js" || e === ".mjs")) return "js";
98
- }
99
-
100
- // 2. From profile
101
- const lang = profile?.language || profile?.lang;
102
- if (lang) return lang.toLowerCase().replace("javascript", "js").replace("typescript", "ts");
103
-
104
- // 3. From project files
105
- if (fs.existsSync(path.join(cwd, "tsconfig.json"))) return "ts";
106
- if (fs.existsSync(path.join(cwd, "pyproject.toml"))) return "py";
107
- if (fs.existsSync(path.join(cwd, "go.mod"))) return "go";
108
-
109
- return "js";
110
- }
111
-
112
- function detectSrcDir(scan, cwd) {
113
- if (!scan?.capabilities?.length) return null;
114
- const files = scan.capabilities.flatMap(c => c.codeAnalysis?.sourceFiles || []);
115
- if (!files.length) return null;
116
-
117
- // Count dir prefixes
118
- const dirCount = {};
119
- for (const f of files) {
120
- const dir = path.dirname(f).split("/")[0];
121
- dirCount[dir] = (dirCount[dir] || 0) + 1;
122
- }
123
- const top = Object.entries(dirCount).sort((a, b) => b[1] - a[1])[0];
124
- return top ? top[0] : null;
125
- }
126
-
127
- function detectServices(scan) {
128
- if (!scan?.capabilities?.length) return [];
129
- const all = scan.capabilities.flatMap(c => c.codeAnalysis?.services || []);
130
- return [...new Set(all)];
131
- }
132
-
133
- // ── code generators ───────────────────────────────────────────────────────────
134
-
135
- function generateTs(id, name, description, fn, services) {
136
- const pascal = toPascalCase(id);
137
- const errorName = `${pascal}Error`;
138
- const imports = buildImports("ts", services);
139
-
140
- return `/**
141
- * ${name}
1
+ import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as _,gray as l,green as C,yellow as Z,red as b}from"../ui/output.mjs";function T(t){try{return JSON.parse(p.readFileSync(t,"utf8"))}catch{return null}}function J(t,e){p.writeFileSync(t,JSON.stringify(e,null,2)+`
2
+ `)}function d(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}function P(t){const e=d(t);return e.charAt(0).toLowerCase()+e.slice(1)}function K(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function Q(t){const e=t.split(/[-_]/);if(e.length===1)return P(t);const o=["auth","login","logout","register","refresh","validate","verify","process","refund","charge","send","fetch","create","update","delete","get","list","search","sync","import","export","scan","check","notify"],r=e[e.length-1],i=e[0];if(o.includes(i)){const n={auth:"authenticate",get:"get",list:"list",send:"send",check:"check",notify:"notify"}[i]||i,u=e.slice(1).map((g,A)=>A===0?g.charAt(0).toUpperCase()+g.slice(1):g).join("");return n+u.charAt(0).toUpperCase()+u.slice(1)}if(o.includes(r)){const s=e.slice(0,-1).map((n,u)=>u===0?n.charAt(0).toUpperCase()+n.slice(1):n).join("");return r+s}return P(t)}function X(t,e,o){if(t?.capabilities?.length){const s=t.capabilities.flatMap(n=>n.codeAnalysis?.sourceFiles||[]).map(n=>a.extname(n));if(s.filter(n=>n===".ts").length>s.filter(n=>n===".js").length)return"ts";if(s.includes(".py"))return"py";if(s.includes(".go"))return"go";if(s.some(n=>n===".js"||n===".mjs"))return"js"}const r=e?.language||e?.lang;return r?r.toLowerCase().replace("javascript","js").replace("typescript","ts"):p.existsSync(a.join(o,"tsconfig.json"))?"ts":p.existsSync(a.join(o,"pyproject.toml"))?"py":p.existsSync(a.join(o,"go.mod"))?"go":"js"}function Y(t,e){if(!t?.capabilities?.length)return null;const o=t.capabilities.flatMap(s=>s.codeAnalysis?.sourceFiles||[]);if(!o.length)return null;const r={};for(const s of o){const n=a.dirname(s).split("/")[0];r[n]=(r[n]||0)+1}const i=Object.entries(r).sort((s,n)=>n[1]-s[1])[0];return i?i[0]:null}function ee(t){if(!t?.capabilities?.length)return[];const e=t.capabilities.flatMap(o=>o.codeAnalysis?.services||[]);return[...new Set(e)]}function te(t,e,o,r,i){const s=d(t),n=`${s}Error`,u=L("ts",i);return`/**
3
+ * ${e}
142
4
  *
143
- * ${description}
5
+ * ${o}
144
6
  *
145
- * @capability ${id}
7
+ * @capability ${t}
146
8
  * @stability experimental
147
9
  */
148
- ${imports}
10
+ ${u}
149
11
 
150
- // ── errors ────────────────────────────────────────────────────────────────────
12
+ // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
151
13
 
152
- export class ${errorName} extends Error {
14
+ export class ${n} extends Error {
153
15
  constructor(message: string, public readonly code?: string) {
154
16
  super(message);
155
- this.name = "${errorName}";
17
+ this.name = "${n}";
156
18
  }
157
19
  }
158
20
 
159
- // ── types ─────────────────────────────────────────────────────────────────────
21
+ // \u2500\u2500 types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
160
22
 
161
- export interface ${pascal}Input {
23
+ export interface ${s}Input {
162
24
  // TODO: define input fields
163
25
  }
164
26
 
165
- export interface ${pascal}Result {
27
+ export interface ${s}Result {
166
28
  // TODO: define result fields
167
29
  success: boolean;
168
30
  }
169
31
 
170
- // ── implementation ────────────────────────────────────────────────────────────
32
+ // \u2500\u2500 implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
171
33
 
172
34
  /**
173
- * ${fn} primary entry point for ${name}.
35
+ * ${r} \u2014 primary entry point for ${e}.
174
36
  * TODO: implement this function.
175
37
  */
176
- export async function ${fn}(input: ${pascal}Input): Promise<${pascal}Result> {
38
+ export async function ${r}(input: ${s}Input): Promise<${s}Result> {
177
39
  // TODO: implement
178
- throw new ${errorName}("Not implemented yet");
40
+ throw new ${n}("Not implemented yet");
179
41
  }
180
- `;
181
- }
182
-
183
- function generateJs(id, name, description, fn, services) {
184
- const pascal = toPascalCase(id);
185
- const errorName = `${pascal}Error`;
186
- const imports = buildImports("js", services);
187
-
188
- return `/**
189
- * ${name}
42
+ `}function ne(t,e,o,r,i){const n=`${d(t)}Error`,u=L("js",i);return`/**
43
+ * ${e}
190
44
  *
191
- * ${description}
45
+ * ${o}
192
46
  *
193
- * @capability ${id}
47
+ * @capability ${t}
194
48
  * @stability experimental
195
49
  */
196
- ${imports}
50
+ ${u}
197
51
 
198
- // ── errors ────────────────────────────────────────────────────────────────────
52
+ // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
199
53
 
200
- export class ${errorName} extends Error {
54
+ export class ${n} extends Error {
201
55
  constructor(message, code) {
202
56
  super(message);
203
- this.name = "${errorName}";
57
+ this.name = "${n}";
204
58
  this.code = code;
205
59
  }
206
60
  }
207
61
 
208
- // ── implementation ────────────────────────────────────────────────────────────
62
+ // \u2500\u2500 implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
209
63
 
210
64
  /**
211
- * ${fn} primary entry point for ${name}.
65
+ * ${r} \u2014 primary entry point for ${e}.
212
66
  * TODO: implement this function.
213
67
  *
214
68
  * @param {object} input
215
69
  * @returns {Promise<object>}
216
70
  */
217
- export async function ${fn}(input = {}) {
71
+ export async function ${r}(input = {}) {
218
72
  // TODO: implement
219
- throw new ${errorName}("Not implemented yet");
73
+ throw new ${n}("Not implemented yet");
220
74
  }
221
- `;
222
- }
223
-
224
- function generatePy(id, name, description, fn) {
225
- const cls = toPascalCase(id);
226
-
227
- return `"""
228
- ${name}
75
+ `}function oe(t,e,o,r){const i=d(t);return`"""
76
+ ${e}
229
77
 
230
- ${description}
78
+ ${o}
231
79
 
232
- capability: ${id}
80
+ capability: ${t}
233
81
  stability: experimental
234
82
  """
235
83
 
236
84
  from typing import Any
237
85
 
238
86
 
239
- class ${cls}Error(Exception):
240
- """Raised when ${name} operations fail."""
87
+ class ${i}Error(Exception):
88
+ """Raised when ${e} operations fail."""
241
89
  def __init__(self, message: str, code: str | None = None):
242
90
  super().__init__(message)
243
91
  self.code = code
244
92
 
245
93
 
246
- async def ${fn.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '')}(input: dict[str, Any]) -> dict[str, Any]:
247
- """Primary entry point for ${name}.
94
+ async def ${r.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"")}(input: dict[str, Any]) -> dict[str, Any]:
95
+ """Primary entry point for ${e}.
248
96
 
249
97
  TODO: implement this function.
250
98
  """
251
- raise ${cls}Error("Not implemented yet")
252
- `;
253
- }
254
-
255
- function generateGo(id, name, description, fn) {
256
- const pkg = id.split("-")[0];
257
-
258
- return `// Package ${pkg} implements ${name}.
99
+ raise ${i}Error("Not implemented yet")
100
+ `}function se(t,e,o,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
259
101
  //
260
- // ${description}
102
+ // ${o}
261
103
  //
262
- // capability: ${id}
104
+ // capability: ${t}
263
105
  // stability: experimental
264
- package ${pkg}
106
+ package ${i}
265
107
 
266
108
  import "errors"
267
109
 
268
- // Err${toPascalCase(id)} is returned when ${name} operations fail.
269
- var Err${toPascalCase(id)} = errors.New("${id}: operation failed")
110
+ // Err${d(t)} is returned when ${e} operations fail.
111
+ var Err${d(t)} = errors.New("${t}: operation failed")
270
112
 
271
- // ${toPascalCase(fn)} is the primary entry point for ${name}.
113
+ // ${d(r)} is the primary entry point for ${e}.
272
114
  // TODO: implement this function.
273
- func ${toPascalCase(fn)}(input map[string]any) (map[string]any, error) {
274
- \treturn nil, Err${toPascalCase(id)}
275
- }
276
- `;
277
- }
278
-
279
- function buildImports(lang, services) {
280
- if (!services.length) return "";
281
- const lines = [];
282
- if (lang === "ts" || lang === "js") {
283
- // Suggest common client imports for detected services
284
- const serviceImports = {
285
- stripe: `// import Stripe from 'stripe';`,
286
- postgres: `// import { Pool } from 'pg';`,
287
- mysql: `// import mysql from 'mysql2/promise';`,
288
- redis: `// import { createClient } from 'redis';`,
289
- s3: `// import { S3Client } from '@aws-sdk/client-s3';`,
290
- sendgrid: `// import sgMail from '@sendgrid/mail';`,
291
- twilio: `// import twilio from 'twilio';`,
292
- openai: `// import OpenAI from 'openai';`,
293
- };
294
- for (const svc of services) {
295
- const imp = serviceImports[svc.toLowerCase()];
296
- if (imp) lines.push(imp);
297
- }
298
- }
299
- return lines.length ? lines.join("\n") + "\n" : "";
300
- }
301
-
302
- // ── scenario generator ────────────────────────────────────────────────────────
303
-
304
- function generateScenario(id, name, fn) {
305
- return {
306
- scenarioId: `${id}-happy-path`,
307
- description: `Happy path for ${name}`,
308
- capabilitiesCovered: [id],
309
- createdAt: new Date().toISOString(),
310
- steps: [
311
- { step: 1, action: `Call ${fn} with valid input`, expected: "Returns success result" },
312
- { step: 2, action: `Call ${fn} with invalid input`, expected: "Throws appropriate error" },
313
- ],
314
- };
315
- }
316
-
317
- // ── printer ───────────────────────────────────────────────────────────────────
318
-
319
- function printResult({ id, filePath, scenarioPath, lang, fn, dryRun }) {
320
- console.log();
321
- console.log(bold(` 🌊 ${green(id)}`));
322
- console.log(gray(" stability: experimental — free to evolve"));
323
- console.log();
324
- console.log(gray(" Generated:"));
325
- console.log(` ${green("+")} ${cyan(filePath)} ${gray(`(${lang} source skeleton)`)}`);
326
- console.log(` ${green("+")} ${cyan("inferno/capabilities.json")} ${gray("(capability registered)")}`);
327
- console.log(` ${green("+")} ${cyan(scenarioPath)} ${gray("(placeholder scenario)")}`);
328
- console.log();
329
- if (dryRun) {
330
- console.log(yellow(" [dry-run] — no files were written"));
331
- } else {
332
- console.log(gray(" Next steps:"));
333
- console.log(gray(` 1. Implement ${fn}() in ${filePath}`));
334
- console.log(gray(` 2. Run: infernoflow scan — to extract call graph`));
335
- console.log(gray(` 3. Run: infernoflow graph — to see dependencies`));
336
- console.log(gray(` 4. Run: infernoflow check — to validate contract`));
337
- }
338
- console.log();
339
- }
340
-
341
- // ── entry point ───────────────────────────────────────────────────────────────
342
-
343
- export async function scaffoldCommand(rawArgs) {
344
- const args = (rawArgs || []).slice(1);
345
- const dryRun = args.includes("--dry-run");
346
- const jsonMode = args.includes("--json");
347
-
348
- const langIdx = args.indexOf("--lang");
349
- const langArg = langIdx !== -1 ? args[langIdx + 1] : null;
350
-
351
- const dirIdx = args.indexOf("--dir");
352
- const dirArg = dirIdx !== -1 ? args[dirIdx + 1] : null;
353
-
354
- const descIdx = args.indexOf("--description");
355
- const descArg = descIdx !== -1 ? args[descIdx + 1] : null;
356
-
357
- // Cap ID: first non-flag arg (skip values after --lang, --dir, --description)
358
- const skipIdxs = new Set([langIdx + 1, dirIdx + 1, descIdx + 1].filter(i => i > 0));
359
- const capId = args.find((a, i) => !a.startsWith("--") && !skipIdxs.has(i));
360
-
361
- if (!capId) {
362
- console.error(red("✗ Usage: infernoflow scaffold <capability-id> [--dir <src>] [--lang ts|js|py|go] [--dry-run] [--json]"));
363
- console.error(gray(" Example: infernoflow scaffold payment-refund"));
364
- process.exit(1);
365
- }
366
-
367
- // Validate cap ID format
368
- if (!/^[a-z][a-z0-9-]*$/.test(capId)) {
369
- console.error(red(`✗ Invalid capability ID: "${capId}"`));
370
- console.error(gray(" Use lowercase kebab-case: payment-refund, user-auth, etc."));
371
- process.exit(1);
372
- }
373
-
374
- const cwd = process.cwd();
375
- const infernoDir = path.join(cwd, "inferno");
376
-
377
- // Load context
378
- const capsPath = path.join(infernoDir, "capabilities.json");
379
- if (!fs.existsSync(capsPath)) {
380
- console.error(red("✗ inferno/capabilities.json not found — run `infernoflow init` first."));
381
- process.exit(1);
382
- }
383
-
384
- let allCaps = [];
385
- const rawCaps = loadJson(capsPath);
386
- if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
387
-
388
- // Check duplicate
389
- if (allCaps.some(c => c.id === capId)) {
390
- console.error(red(`✗ Capability "${capId}" already exists in capabilities.json`));
391
- console.error(gray(" Use a different ID, or run: infernoflow why " + capId));
392
- process.exit(1);
393
- }
394
-
395
- const scan = loadJson(path.join(infernoDir, "scan.json"));
396
- const profile = loadJson(path.join(infernoDir, "developer-profile.json"));
397
-
398
- // Detect language
399
- const lang = langArg || detectLang(scan, profile, cwd);
400
-
401
- // Detect output directory
402
- const srcDir = dirArg || detectSrcDir(scan, cwd) || "src";
403
- const ext = { ts: ".ts", js: ".js", py: ".py", go: ".go" }[lang] || ".js";
404
-
405
- // Derive names
406
- const name = toTitle(capId);
407
- const description = descArg || `TODO: describe ${name}`;
408
- const fn = primaryFnName(capId);
409
- const services = detectServices(scan);
410
-
411
- // Generate code
412
- let code;
413
- if (lang === "ts") code = generateTs(capId, name, description, fn, services);
414
- else if (lang === "py") code = generatePy(capId, name, description, fn);
415
- else if (lang === "go") code = generateGo(capId, name, description, fn);
416
- else code = generateJs(capId, name, description, fn, services);
417
-
418
- // File path: srcDir/capId (replace dashes with nothing) + ext
419
- // e.g. payment-refund → src/paymentRefund.ts
420
- const fileName = toCamelCase(capId) + ext;
421
- const filePath = path.join(srcDir, fileName);
422
- const absFile = path.join(cwd, filePath);
423
-
424
- // Scenario path
425
- const scenarioPath = path.join("inferno", "scenarios", `${capId}.json`);
426
- const absScenario = path.join(cwd, scenarioPath);
427
- const scenario = generateScenario(capId, name, fn);
428
-
429
- // New capability entry
430
- const newCap = {
431
- id: capId,
432
- name,
433
- description,
434
- stability: "experimental",
435
- since: new Date().toISOString().slice(0, 10),
436
- };
437
-
438
- if (jsonMode) {
439
- const out = {
440
- capId,
441
- name,
442
- stability: "experimental",
443
- lang,
444
- filePath,
445
- scenarioPath,
446
- primaryFn: fn,
447
- dryRun,
448
- code,
449
- };
450
- console.log(JSON.stringify(out, null, 2));
451
- return;
452
- }
453
-
454
- console.log(gray(`\n infernoflow scaffold → ${bold(capId)}`));
455
- console.log(gray(" ──────────────────────────────────────────────────────────────"));
456
-
457
- if (!dryRun) {
458
- // Create source directory if needed
459
- const absDir = path.dirname(absFile);
460
- if (!fs.existsSync(absDir)) fs.mkdirSync(absDir, { recursive: true });
461
-
462
- // Write source file
463
- if (fs.existsSync(absFile)) {
464
- console.error(red(` ✗ File already exists: ${filePath}`));
465
- console.error(gray(" Delete it first or choose a different --dir"));
466
- process.exit(1);
467
- }
468
- fs.writeFileSync(absFile, code, "utf8");
469
-
470
- // Register capability
471
- allCaps.push(newCap);
472
- saveJson(capsPath, allCaps);
473
-
474
- // Write scenario
475
- const scenDir = path.join(cwd, "inferno", "scenarios");
476
- if (!fs.existsSync(scenDir)) fs.mkdirSync(scenDir, { recursive: true });
477
- if (!fs.existsSync(absScenario)) {
478
- saveJson(absScenario, scenario);
479
- }
480
- }
481
-
482
- // Show code preview
483
- const previewLines = code.split("\n").slice(0, 12).map(l => " " + l).join("\n");
484
- console.log(gray("\n Preview:"));
485
- console.log(gray(previewLines));
486
- console.log(gray(" ..."));
487
-
488
- printResult({ id: capId, filePath, scenarioPath, lang, fn, dryRun });
489
- }
115
+ func ${d(r)}(input map[string]any) (map[string]any, error) {
116
+ return nil, Err${d(t)}
117
+ }
118
+ `}function L(t,e){if(!e.length)return"";const o=[];if(t==="ts"||t==="js"){const r={stripe:"// import Stripe from 'stripe';",postgres:"// import { Pool } from 'pg';",mysql:"// import mysql from 'mysql2/promise';",redis:"// import { createClient } from 'redis';",s3:"// import { S3Client } from '@aws-sdk/client-s3';",sendgrid:"// import sgMail from '@sendgrid/mail';",twilio:"// import twilio from 'twilio';",openai:"// import OpenAI from 'openai';"};for(const i of e){const s=r[i.toLowerCase()];s&&o.push(s)}}return o.length?o.join(`
119
+ `)+`
120
+ `:""}function re(t,e,o){return{scenarioId:`${t}-happy-path`,description:`Happy path for ${e}`,capabilitiesCovered:[t],createdAt:new Date().toISOString(),steps:[{step:1,action:`Call ${o} with valid input`,expected:"Returns success result"},{step:2,action:`Call ${o} with invalid input`,expected:"Throws appropriate error"}]}}function ie({id:t,filePath:e,scenarioPath:o,lang:r,fn:i,dryRun:s}){console.log(),console.log(U(` \u{1F30A} ${C(t)}`)),console.log(l(" stability: experimental \u2014 free to evolve")),console.log(),console.log(l(" Generated:")),console.log(` ${C("+")} ${_(e)} ${l(`(${r} source skeleton)`)}`),console.log(` ${C("+")} ${_("inferno/capabilities.json")} ${l("(capability registered)")}`),console.log(` ${C("+")} ${_(o)} ${l("(placeholder scenario)")}`),console.log(),s?console.log(Z(" [dry-run] \u2014 no files were written")):(console.log(l(" Next steps:")),console.log(l(` 1. Implement ${i}() in ${e}`)),console.log(l(" 2. Run: infernoflow scan \u2014 to extract call graph")),console.log(l(" 3. Run: infernoflow graph \u2014 to see dependencies")),console.log(l(" 4. Run: infernoflow check \u2014 to validate contract"))),console.log()}async function le(t){const e=(t||[]).slice(1),o=e.includes("--dry-run"),r=e.includes("--json"),i=e.indexOf("--lang"),s=i!==-1?e[i+1]:null,n=e.indexOf("--dir"),u=n!==-1?e[n+1]:null,g=e.indexOf("--description"),A=g!==-1?e[g+1]:null,M=new Set([i+1,n+1,g+1].filter(f=>f>0)),c=e.find((f,v)=>!f.startsWith("--")&&!M.has(v));c||(console.error(b("\u2717 Usage: infernoflow scaffold <capability-id> [--dir <src>] [--lang ts|js|py|go] [--dry-run] [--json]")),console.error(l(" Example: infernoflow scaffold payment-refund")),process.exit(1)),/^[a-z][a-z0-9-]*$/.test(c)||(console.error(b(`\u2717 Invalid capability ID: "${c}"`)),console.error(l(" Use lowercase kebab-case: payment-refund, user-auth, etc.")),process.exit(1));const h=process.cwd(),D=a.join(h,"inferno"),I=a.join(D,"capabilities.json");p.existsSync(I)||(console.error(b("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let w=[];const S=T(I);S&&(w=Array.isArray(S)?S:S.capabilities||[]),w.some(f=>f.id===c)&&(console.error(b(`\u2717 Capability "${c}" already exists in capabilities.json`)),console.error(l(" Use a different ID, or run: infernoflow why "+c)),process.exit(1));const E=T(a.join(D,"scan.json")),q=T(a.join(D,"developer-profile.json")),x=s||X(E,q,h),z=u||Y(E,h)||"src",G={ts:".ts",js:".js",py:".py",go:".go"}[x]||".js",m=K(c),j=A||`TODO: describe ${m}`,y=Q(c),R=ee(E);let $;x==="ts"?$=te(c,m,j,y,R):x==="py"?$=oe(c,m,j,y):x==="go"?$=se(c,m,j,y):$=ne(c,m,j,y,R);const B=P(c)+G,O=a.join(z,B),N=a.join(h,O),k=a.join("inferno","scenarios",`${c}.json`),F=a.join(h,k),H=re(c,m,y),V={id:c,name:m,description:j,stability:"experimental",since:new Date().toISOString().slice(0,10)};if(r){console.log(JSON.stringify({capId:c,name:m,stability:"experimental",lang:x,filePath:O,scenarioPath:k,primaryFn:y,dryRun:o,code:$},null,2));return}if(console.log(l(`
121
+ infernoflow scaffold \u2192 ${U(c)}`)),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),!o){const f=a.dirname(N);p.existsSync(f)||p.mkdirSync(f,{recursive:!0}),p.existsSync(N)&&(console.error(b(` \u2717 File already exists: ${O}`)),console.error(l(" Delete it first or choose a different --dir")),process.exit(1)),p.writeFileSync(N,$,"utf8"),w.push(V),J(I,w);const v=a.join(h,"inferno","scenarios");p.existsSync(v)||p.mkdirSync(v,{recursive:!0}),p.existsSync(F)||J(F,H)}const W=$.split(`
122
+ `).slice(0,12).map(f=>" "+f).join(`
123
+ `);console.log(l(`
124
+ Preview:`)),console.log(l(W)),console.log(l(" ...")),ie({id:c,filePath:O,scenarioPath:k,lang:x,fn:y,dryRun:o})}export{le as scaffoldCommand};