infernoflow 0.37.0 → 0.37.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/dist/bin/infernoflow.mjs +29 -277
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/ask.mjs +4 -299
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +30 -135
- package/dist/lib/commands/cloud.mjs +10 -773
- package/dist/lib/commands/context.mjs +34 -346
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/feedback.mjs +12 -216
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +11 -378
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +45 -631
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +18 -248
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/recap.mjs +6 -380
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +11 -1118
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/stats.mjs +5 -402
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/switch.mjs +13 -517
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -195
- package/dist/lib/commands/uninstall.mjs +13 -406
- package/dist/lib/commands/upgrade.mjs +20 -153
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/telemetry.mjs +19 -269
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -343
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -95
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/package.json +2 -4
- package/scripts/postinstall.js +2 -2
|
@@ -1,489 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
* ${
|
|
5
|
+
* ${o}
|
|
144
6
|
*
|
|
145
|
-
* @capability ${
|
|
7
|
+
* @capability ${t}
|
|
146
8
|
* @stability experimental
|
|
147
9
|
*/
|
|
148
|
-
${
|
|
10
|
+
${u}
|
|
149
11
|
|
|
150
|
-
//
|
|
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 ${
|
|
14
|
+
export class ${n} extends Error {
|
|
153
15
|
constructor(message: string, public readonly code?: string) {
|
|
154
16
|
super(message);
|
|
155
|
-
this.name = "${
|
|
17
|
+
this.name = "${n}";
|
|
156
18
|
}
|
|
157
19
|
}
|
|
158
20
|
|
|
159
|
-
//
|
|
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 ${
|
|
23
|
+
export interface ${s}Input {
|
|
162
24
|
// TODO: define input fields
|
|
163
25
|
}
|
|
164
26
|
|
|
165
|
-
export interface ${
|
|
27
|
+
export interface ${s}Result {
|
|
166
28
|
// TODO: define result fields
|
|
167
29
|
success: boolean;
|
|
168
30
|
}
|
|
169
31
|
|
|
170
|
-
//
|
|
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
|
-
* ${
|
|
35
|
+
* ${r} \u2014 primary entry point for ${e}.
|
|
174
36
|
* TODO: implement this function.
|
|
175
37
|
*/
|
|
176
|
-
export async function ${
|
|
38
|
+
export async function ${r}(input: ${s}Input): Promise<${s}Result> {
|
|
177
39
|
// TODO: implement
|
|
178
|
-
throw new ${
|
|
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
|
-
* ${
|
|
45
|
+
* ${o}
|
|
192
46
|
*
|
|
193
|
-
* @capability ${
|
|
47
|
+
* @capability ${t}
|
|
194
48
|
* @stability experimental
|
|
195
49
|
*/
|
|
196
|
-
${
|
|
50
|
+
${u}
|
|
197
51
|
|
|
198
|
-
//
|
|
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 ${
|
|
54
|
+
export class ${n} extends Error {
|
|
201
55
|
constructor(message, code) {
|
|
202
56
|
super(message);
|
|
203
|
-
this.name = "${
|
|
57
|
+
this.name = "${n}";
|
|
204
58
|
this.code = code;
|
|
205
59
|
}
|
|
206
60
|
}
|
|
207
61
|
|
|
208
|
-
//
|
|
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
|
-
* ${
|
|
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 ${
|
|
71
|
+
export async function ${r}(input = {}) {
|
|
218
72
|
// TODO: implement
|
|
219
|
-
throw new ${
|
|
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
|
-
${
|
|
78
|
+
${o}
|
|
231
79
|
|
|
232
|
-
capability: ${
|
|
80
|
+
capability: ${t}
|
|
233
81
|
stability: experimental
|
|
234
82
|
"""
|
|
235
83
|
|
|
236
84
|
from typing import Any
|
|
237
85
|
|
|
238
86
|
|
|
239
|
-
class ${
|
|
240
|
-
"""Raised when ${
|
|
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 ${
|
|
247
|
-
"""Primary entry point for ${
|
|
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 ${
|
|
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
|
-
// ${
|
|
102
|
+
// ${o}
|
|
261
103
|
//
|
|
262
|
-
// capability: ${
|
|
104
|
+
// capability: ${t}
|
|
263
105
|
// stability: experimental
|
|
264
|
-
package ${
|
|
106
|
+
package ${i}
|
|
265
107
|
|
|
266
108
|
import "errors"
|
|
267
109
|
|
|
268
|
-
// Err${
|
|
269
|
-
var Err${
|
|
110
|
+
// Err${d(t)} is returned when ${e} operations fail.
|
|
111
|
+
var Err${d(t)} = errors.New("${t}: operation failed")
|
|
270
112
|
|
|
271
|
-
// ${
|
|
113
|
+
// ${d(r)} is the primary entry point for ${e}.
|
|
272
114
|
// TODO: implement this function.
|
|
273
|
-
func ${
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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};
|