infernoflow 0.32.7 → 0.32.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/infernoflow.mjs +84 -255
- 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/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 +25 -130
- package/dist/lib/commands/cloud.mjs +5 -521
- package/dist/lib/commands/context.mjs +31 -287
- 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/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +203 -320
- 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 +23 -475
- 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/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/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 +5 -558
- 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/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- 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/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/templates/index.mjs +1 -131
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -72
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
- package/dist/templates/github-app/GITHUB_APP.md +67 -0
- package/dist/templates/github-app/app-manifest.json +20 -0
- package/package.json +1 -1
|
@@ -1,239 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* markdown Confluence/Notion-ready Markdown table
|
|
12
|
-
* json Clean JSON (normalised, no internal infernoflow fields)
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* infernoflow export --format openapi
|
|
16
|
-
* infernoflow export --format backstage --out catalog-info.yaml
|
|
17
|
-
* infernoflow export --format csv --out capabilities.csv
|
|
18
|
-
* infernoflow export --format markdown
|
|
19
|
-
* infernoflow export --format json --out contract-export.json
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import * as fs from "node:fs";
|
|
23
|
-
import * as path from "node:path";
|
|
24
|
-
import { done, warn, info, bold, cyan, gray } from "../ui/output.mjs";
|
|
25
|
-
|
|
26
|
-
// ── Contract reader ───────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function readContract(infernoDir) {
|
|
29
|
-
for (const f of ["contract.json", "capabilities.json"]) {
|
|
30
|
-
const p = path.join(infernoDir, f);
|
|
31
|
-
if (!fs.existsSync(p)) continue;
|
|
32
|
-
try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch {}
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function normaliseCaps(contract) {
|
|
38
|
-
const raw = contract?.capabilities || [];
|
|
39
|
-
return raw.map(c => {
|
|
40
|
-
if (typeof c === "string") return { id: c, description: "", tags: [], status: "active" };
|
|
41
|
-
return {
|
|
42
|
-
id: c.id || c.name || "unknown",
|
|
43
|
-
description: c.description || "",
|
|
44
|
-
tags: c.tags || [],
|
|
45
|
-
status: c.status || "active",
|
|
46
|
-
since: c.since || "",
|
|
47
|
-
owner: c.owner || "",
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function readMeta(infernoDir) {
|
|
53
|
-
const pkgPath = path.join(path.dirname(infernoDir), "package.json");
|
|
54
|
-
try {
|
|
55
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
56
|
-
return { name: pkg.name || "my-service", version: pkg.version || "0.0.0", description: pkg.description || "" };
|
|
57
|
-
} catch {
|
|
58
|
-
return { name: path.basename(path.dirname(infernoDir)), version: "0.0.0", description: "" };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── Formatters ────────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
function toOpenApi(caps, meta) {
|
|
65
|
-
const paths = {};
|
|
66
|
-
for (const cap of caps) {
|
|
67
|
-
const route = `/${cap.id.replace(/_/g, "-")}`;
|
|
68
|
-
paths[route] = {
|
|
69
|
-
get: {
|
|
70
|
-
operationId: cap.id,
|
|
71
|
-
summary: cap.description || cap.id,
|
|
72
|
-
tags: cap.tags.length ? cap.tags : [meta.name],
|
|
73
|
-
parameters: [],
|
|
74
|
-
responses: {
|
|
75
|
-
"200": { description: "Success", content: { "application/json": { schema: { type: "object" } } } },
|
|
76
|
-
"401": { description: "Unauthorized" },
|
|
77
|
-
"500": { description: "Internal Server Error" },
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return JSON.stringify({
|
|
84
|
-
openapi: "3.1.0",
|
|
85
|
-
info: {
|
|
86
|
-
title: meta.name,
|
|
87
|
-
version: meta.version,
|
|
88
|
-
description: meta.description || `Capability contract for ${meta.name}`,
|
|
89
|
-
},
|
|
90
|
-
paths,
|
|
91
|
-
components: { schemas: {}, securitySchemes: {} },
|
|
92
|
-
}, null, 2);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function toBackstage(caps, meta) {
|
|
96
|
-
const tags = [...new Set(caps.flatMap(c => c.tags))].slice(0, 10);
|
|
97
|
-
const lines = [
|
|
98
|
-
`apiVersion: backstage.io/v1alpha1`,
|
|
99
|
-
`kind: Component`,
|
|
100
|
-
`metadata:`,
|
|
101
|
-
` name: ${meta.name}`,
|
|
102
|
-
` description: "${(meta.description || meta.name).replace(/"/g, '\\"')}"`,
|
|
103
|
-
` annotations:`,
|
|
104
|
-
` infernoflow/capability-count: "${caps.length}"`,
|
|
105
|
-
` infernoflow/generated-at: "${new Date().toISOString()}"`,
|
|
106
|
-
tags.length ? ` tags:\n${tags.map(t => ` - ${t}`).join("\n")}` : "",
|
|
107
|
-
`spec:`,
|
|
108
|
-
` type: service`,
|
|
109
|
-
` lifecycle: production`,
|
|
110
|
-
` owner: team-default`,
|
|
111
|
-
` providesApis: []`,
|
|
112
|
-
`---`,
|
|
113
|
-
`# Capabilities`,
|
|
114
|
-
...caps.map(c => [
|
|
115
|
-
`# ${c.id}`,
|
|
116
|
-
`# ${c.description || "(no description)"}`,
|
|
117
|
-
`# tags: ${c.tags.join(", ") || "none"}`,
|
|
118
|
-
`# status: ${c.status}`,
|
|
119
|
-
].join("\n")),
|
|
120
|
-
].filter(Boolean);
|
|
121
|
-
return lines.join("\n") + "\n";
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function toCsv(caps) {
|
|
125
|
-
const header = ["id", "description", "tags", "status", "since", "owner"];
|
|
126
|
-
const rows = caps.map(c => [
|
|
127
|
-
c.id,
|
|
128
|
-
(c.description || "").replace(/"/g, '""'),
|
|
129
|
-
c.tags.join("|"),
|
|
130
|
-
c.status,
|
|
131
|
-
c.since,
|
|
132
|
-
c.owner,
|
|
133
|
-
].map(v => `"${v}"`).join(","));
|
|
134
|
-
return [header.join(","), ...rows].join("\n") + "\n";
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function toMarkdown(caps, meta) {
|
|
138
|
-
const lines = [
|
|
139
|
-
`# ${meta.name} — Capability Contract`,
|
|
140
|
-
``,
|
|
141
|
-
`> Generated by infernoflow on ${new Date().toISOString().slice(0, 10)}`,
|
|
142
|
-
`> ${caps.length} capabilities tracked`,
|
|
143
|
-
``,
|
|
144
|
-
`| Capability | Description | Tags | Status |`,
|
|
145
|
-
`|---|---|---|---|`,
|
|
146
|
-
...caps.map(c =>
|
|
147
|
-
`| \`${c.id}\` | ${c.description || ""} | ${c.tags.join(", ") || "—"} | ${c.status} |`
|
|
148
|
-
),
|
|
149
|
-
``,
|
|
150
|
-
];
|
|
151
|
-
return lines.join("\n");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function toCleanJson(caps, meta) {
|
|
155
|
-
return JSON.stringify({
|
|
156
|
-
name: meta.name,
|
|
157
|
-
version: meta.version,
|
|
158
|
-
exportedAt: new Date().toISOString(),
|
|
159
|
-
capabilities: caps,
|
|
160
|
-
}, null, 2) + "\n";
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
164
|
-
|
|
165
|
-
const FORMAT_EXT = {
|
|
166
|
-
openapi: "openapi.json",
|
|
167
|
-
backstage: "catalog-info.yaml",
|
|
168
|
-
csv: "capabilities.csv",
|
|
169
|
-
markdown: "capabilities.md",
|
|
170
|
-
json: "capabilities-export.json",
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
export async function exportCommand(rawArgs) {
|
|
174
|
-
const args = rawArgs.slice(1);
|
|
175
|
-
const jsonMode = args.includes("--json");
|
|
176
|
-
const cwd = process.cwd();
|
|
177
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
178
|
-
|
|
179
|
-
if (!fs.existsSync(infernoDir)) {
|
|
180
|
-
const msg = "inferno/ not found. Run: infernoflow init";
|
|
181
|
-
if (jsonMode) console.log(JSON.stringify({ ok: false, error: msg }));
|
|
182
|
-
else warn(msg);
|
|
183
|
-
process.exit(1);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const fmtIdx = args.indexOf("--format");
|
|
187
|
-
const format = fmtIdx !== -1 ? args[fmtIdx + 1] : null;
|
|
188
|
-
const outIdx = args.indexOf("--out");
|
|
189
|
-
const outArg = outIdx !== -1 ? args[outIdx + 1] : null;
|
|
190
|
-
|
|
191
|
-
const validFormats = Object.keys(FORMAT_EXT);
|
|
192
|
-
|
|
193
|
-
if (!format || !validFormats.includes(format)) {
|
|
194
|
-
const msg = `Usage: infernoflow export --format <${validFormats.join("|")}> [--out <path>]`;
|
|
195
|
-
if (jsonMode) console.log(JSON.stringify({ ok: false, error: msg }));
|
|
196
|
-
else {
|
|
197
|
-
warn(msg);
|
|
198
|
-
console.log();
|
|
199
|
-
console.log(` ${gray("Available formats:")}`);
|
|
200
|
-
console.log(` ${bold("openapi")} — OpenAPI 3.1 JSON with a stub path per capability`);
|
|
201
|
-
console.log(` ${bold("backstage")} — Backstage catalog-info.yaml component definition`);
|
|
202
|
-
console.log(` ${bold("csv")} — Spreadsheet-ready CSV`);
|
|
203
|
-
console.log(` ${bold("markdown")} — Confluence/Notion table`);
|
|
204
|
-
console.log(` ${bold("json")} — Clean normalised JSON`);
|
|
205
|
-
console.log();
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const contract = readContract(infernoDir);
|
|
211
|
-
if (!contract) {
|
|
212
|
-
const msg = "No contract.json or capabilities.json found.";
|
|
213
|
-
if (jsonMode) console.log(JSON.stringify({ ok: false, error: msg }));
|
|
214
|
-
else warn(msg);
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const caps = normaliseCaps(contract);
|
|
219
|
-
const meta = readMeta(infernoDir);
|
|
220
|
-
const outPath = outArg || path.join(cwd, FORMAT_EXT[format]);
|
|
221
|
-
|
|
222
|
-
let output;
|
|
223
|
-
switch (format) {
|
|
224
|
-
case "openapi": output = toOpenApi(caps, meta); break;
|
|
225
|
-
case "backstage": output = toBackstage(caps, meta); break;
|
|
226
|
-
case "csv": output = toCsv(caps); break;
|
|
227
|
-
case "markdown": output = toMarkdown(caps, meta); break;
|
|
228
|
-
case "json": output = toCleanJson(caps, meta); break;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
fs.writeFileSync(outPath, output);
|
|
232
|
-
|
|
233
|
-
if (jsonMode) {
|
|
234
|
-
console.log(JSON.stringify({ ok: true, format, file: outPath, capabilities: caps.length }));
|
|
235
|
-
} else {
|
|
236
|
-
done(`Exported ${bold(String(caps.length))} capabilities → ${cyan(path.relative(cwd, outPath))}`);
|
|
237
|
-
console.log();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
1
|
+
import*as d from"node:fs";import*as a from"node:path";import{done as w,warn as u,bold as l,cyan as k,gray as h}from"../ui/output.mjs";function v(t){for(const o of["contract.json","capabilities.json"]){const n=a.join(t,o);if(d.existsSync(n))try{return JSON.parse(d.readFileSync(n,"utf8"))}catch{}}return null}function O(t){return(t?.capabilities||[]).map(n=>typeof n=="string"?{id:n,description:"",tags:[],status:"active"}:{id:n.id||n.name||"unknown",description:n.description||"",tags:n.tags||[],status:n.status||"active",since:n.since||"",owner:n.owner||""})}function x(t){const o=a.join(a.dirname(t),"package.json");try{const n=JSON.parse(d.readFileSync(o,"utf8"));return{name:n.name||"my-service",version:n.version||"0.0.0",description:n.description||""}}catch{return{name:a.basename(a.dirname(t)),version:"0.0.0",description:""}}}function C(t,o){const n={};for(const e of t){const i=`/${e.id.replace(/_/g,"-")}`;n[i]={get:{operationId:e.id,summary:e.description||e.id,tags:e.tags.length?e.tags:[o.name],parameters:[],responses:{200:{description:"Success",content:{"application/json":{schema:{type:"object"}}}},401:{description:"Unauthorized"},500:{description:"Internal Server Error"}}}}}return JSON.stringify({openapi:"3.1.0",info:{title:o.name,version:o.version,description:o.description||`Capability contract for ${o.name}`},paths:n,components:{schemas:{},securitySchemes:{}}},null,2)}function N(t,o){const n=[...new Set(t.flatMap(i=>i.tags))].slice(0,10);return["apiVersion: backstage.io/v1alpha1","kind: Component","metadata:",` name: ${o.name}`,` description: "${(o.description||o.name).replace(/"/g,'\\"')}"`," annotations:",` infernoflow/capability-count: "${t.length}"`,` infernoflow/generated-at: "${new Date().toISOString()}"`,n.length?` tags:
|
|
2
|
+
${n.map(i=>` - ${i}`).join(`
|
|
3
|
+
`)}`:"","spec:"," type: service"," lifecycle: production"," owner: team-default"," providesApis: []","---","# Capabilities",...t.map(i=>[`# ${i.id}`,`# ${i.description||"(no description)"}`,`# tags: ${i.tags.join(", ")||"none"}`,`# status: ${i.status}`].join(`
|
|
4
|
+
`))].filter(Boolean).join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`}function J(t){const o=["id","description","tags","status","since","owner"],n=t.map(e=>[e.id,(e.description||"").replace(/"/g,'""'),e.tags.join("|"),e.status,e.since,e.owner].map(i=>`"${i}"`).join(","));return[o.join(","),...n].join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`}function I(t,o){return[`# ${o.name} \u2014 Capability Contract`,"",`> Generated by infernoflow on ${new Date().toISOString().slice(0,10)}`,`> ${t.length} capabilities tracked`,"","| Capability | Description | Tags | Status |","|---|---|---|---|",...t.map(e=>`| \`${e.id}\` | ${e.description||""} | ${e.tags.join(", ")||"\u2014"} | ${e.status} |`),""].join(`
|
|
9
|
+
`)}function A(t,o){return JSON.stringify({name:o.name,version:o.version,exportedAt:new Date().toISOString(),capabilities:t},null,2)+`
|
|
10
|
+
`}const S={openapi:"openapi.json",backstage:"catalog-info.yaml",csv:"capabilities.csv",markdown:"capabilities.md",json:"capabilities-export.json"};async function D(t){const o=t.slice(1),n=o.includes("--json"),e=process.cwd(),i=a.join(e,"inferno");if(!d.existsSync(i)){const s="inferno/ not found. Run: infernoflow init";n?console.log(JSON.stringify({ok:!1,error:s})):u(s),process.exit(1)}const m=o.indexOf("--format"),p=m!==-1?o[m+1]:null,b=o.indexOf("--out"),$=b!==-1?o[b+1]:null,y=Object.keys(S);if(!p||!y.includes(p)){const s=`Usage: infernoflow export --format <${y.join("|")}> [--out <path>]`;n?console.log(JSON.stringify({ok:!1,error:s})):(u(s),console.log(),console.log(` ${h("Available formats:")}`),console.log(` ${l("openapi")} \u2014 OpenAPI 3.1 JSON with a stub path per capability`),console.log(` ${l("backstage")} \u2014 Backstage catalog-info.yaml component definition`),console.log(` ${l("csv")} \u2014 Spreadsheet-ready CSV`),console.log(` ${l("markdown")} \u2014 Confluence/Notion table`),console.log(` ${l("json")} \u2014 Clean normalised JSON`),console.log());return}const j=v(i);if(!j){const s="No contract.json or capabilities.json found.";n?console.log(JSON.stringify({ok:!1,error:s})):u(s),process.exit(1)}const r=O(j),f=x(i),g=$||a.join(e,S[p]);let c;switch(p){case"openapi":c=C(r,f);break;case"backstage":c=N(r,f);break;case"csv":c=J(r);break;case"markdown":c=I(r,f);break;case"json":c=A(r,f);break}d.writeFileSync(g,c),n?console.log(JSON.stringify({ok:!0,format:p,file:g,capabilities:r.length})):(w(`Exported ${l(String(r.length))} capabilities \u2192 ${k(a.relative(e,g))}`),console.log())}export{D as exportCommand};
|
|
@@ -1,90 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Reads inferno/developer-profile.json and generates personalised skill files:
|
|
5
|
-
* - inferno/generated-skills/cursor-rules.md → copy to .cursor/rules/infernoflow.md
|
|
6
|
-
* - inferno/generated-skills/quick-restore.md → session startup skill
|
|
7
|
-
* - inferno/generated-skills/naming-guide.md → detected naming conventions
|
|
8
|
-
* - inferno/generated-skills/feature-scaffold.md → personal feature checklist
|
|
9
|
-
*
|
|
10
|
-
* Run: infernoflow generate-skills [--cursor] [--force]
|
|
11
|
-
* --cursor Also copy generated rules to .cursor/rules/infernoflow.md
|
|
12
|
-
* --force Overwrite existing generated files
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import * as fs from "node:fs";
|
|
16
|
-
import * as path from "node:path";
|
|
17
|
-
import { readProfile } from "../learning/profile.mjs";
|
|
18
|
-
import { header, ok, warn, done, nextSteps, cyan, yellow, bold, gray } from "../ui/output.mjs";
|
|
19
|
-
|
|
20
|
-
const SKILLS_DIR = path.join("inferno", "generated-skills");
|
|
21
|
-
|
|
22
|
-
function write(filePath, content, force) {
|
|
23
|
-
if (fs.existsSync(filePath) && !force) {
|
|
24
|
-
warn(`Already exists (use --force to overwrite): ${path.relative(process.cwd(), filePath)}`);
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
28
|
-
fs.writeFileSync(filePath, content, "utf8");
|
|
29
|
-
ok(`Generated: ${path.relative(process.cwd(), filePath)}`);
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function buildCursorRules(profile, contract) {
|
|
34
|
-
const naming = profile.namingStyle !== "unknown" ? profile.namingStyle : "PascalCase";
|
|
35
|
-
const verbs = profile.preferredVerbs.length > 0 ? profile.preferredVerbs.slice(0, 5).join(", ") : "Add, Update, Remove";
|
|
36
|
-
const framework = profile.stack?.framework || "unknown";
|
|
37
|
-
const clusters = profile.featureClusters.slice(0, 3);
|
|
38
|
-
const sessionStyle = profile.sessionCount >= 20 ? "experienced" : profile.sessionCount >= 5 ? "intermediate" : "new";
|
|
39
|
-
|
|
40
|
-
const clusterRules = clusters.map(cluster =>
|
|
41
|
-
`- When touching [${cluster.slice(0, 3).join(", ")}], check whether related capabilities also need updating`
|
|
42
|
-
).join("\n");
|
|
43
|
-
|
|
44
|
-
return `# infernoflow — Cursor Rules (auto-generated)
|
|
45
|
-
# Project: ${contract?.policyId || "unknown"} | Stack: ${framework}
|
|
1
|
+
import*as l from"node:fs";import*as s from"node:path";import{readProfile as k}from"../learning/profile.mjs";import{header as y,ok as w,warn as b,done as $,nextSteps as C,cyan as m,yellow as j}from"../ui/output.mjs";const p=s.join("inferno","generated-skills");function f(e,n,i){return l.existsSync(e)&&!i?(b(`Already exists (use --force to overwrite): ${s.relative(process.cwd(),e)}`),!1):(l.mkdirSync(s.dirname(e),{recursive:!0}),l.writeFileSync(e,n,"utf8"),w(`Generated: ${s.relative(process.cwd(),e)}`),!0)}function S(e,n){const i=e.namingStyle!=="unknown"?e.namingStyle:"PascalCase",r=e.preferredVerbs.length>0?e.preferredVerbs.slice(0,5).join(", "):"Add, Update, Remove",t=e.stack?.framework||"unknown",o=e.featureClusters.slice(0,3),a=e.sessionCount>=20?"experienced":e.sessionCount>=5?"intermediate":"new",c=o.map(u=>`- When touching [${u.slice(0,3).join(", ")}], check whether related capabilities also need updating`).join(`
|
|
2
|
+
`);return`# infernoflow \u2014 Cursor Rules (auto-generated)
|
|
3
|
+
# Project: ${n?.policyId||"unknown"} | Stack: ${t}
|
|
46
4
|
# Regenerate with: infernoflow generate-skills --cursor
|
|
47
5
|
|
|
48
6
|
## Contract awareness
|
|
49
7
|
- This project uses infernoflow to track capability contracts
|
|
50
8
|
- After implementing any feature, always call \`infernoflow_run\` then \`infernoflow_apply\`
|
|
51
9
|
- Run \`infernoflow_check\` before every commit
|
|
52
|
-
- Current capabilities: [${(
|
|
10
|
+
- Current capabilities: [${(n?.capabilities||[]).join(", ")}]
|
|
53
11
|
|
|
54
12
|
## Naming conventions (detected from this developer's history)
|
|
55
|
-
- Capability IDs use **${
|
|
56
|
-
- Preferred action verbs: ${
|
|
13
|
+
- Capability IDs use **${i}** (e.g. ${r.split(", ").slice(0,2).map(u=>u+"Item").join(", ")})
|
|
14
|
+
- Preferred action verbs: ${r}
|
|
57
15
|
- Match this style when suggesting new capability names
|
|
58
16
|
|
|
59
17
|
## Feature clusters (capabilities this developer adds together)
|
|
60
|
-
${
|
|
18
|
+
${c||"- No clusters detected yet \u2014 build more features to train this"}
|
|
61
19
|
|
|
62
|
-
## Session style (${
|
|
63
|
-
${
|
|
64
|
-
|
|
65
|
-
|
|
20
|
+
## Session style (${a})
|
|
21
|
+
${a==="experienced"?`- Skip basic explanations \u2014 this developer knows the codebase well
|
|
22
|
+
- Be direct and minimal in responses`:`- Include brief context for non-obvious decisions
|
|
23
|
+
- Explain infernoflow commands when used`}
|
|
66
24
|
|
|
67
25
|
## Workflow reminders
|
|
68
26
|
- Start sessions with: \`infernoflow context --show\`
|
|
69
27
|
- Use \`infernoflow_git_drift\` to check what's changed before starting work
|
|
70
28
|
- Use \`infernoflow_implement\` to get a structured coding prompt before writing code
|
|
71
|
-
- Changelog entries should be ${
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function buildQuickRestore(profile, contract) {
|
|
76
|
-
const working = ""; // will be filled at runtime from context-state.json
|
|
77
|
-
const framework = profile.stack?.framework || "unknown";
|
|
78
|
-
const capabilities = (contract?.capabilities || []).slice(0, 6);
|
|
79
|
-
|
|
80
|
-
return `# Quick Restore — ${contract?.policyId || "this project"}
|
|
29
|
+
- Changelog entries should be ${e.changelogVerbosity==="detailed"?"detailed (include context and impact)":"brief (one line, action-focused)"}
|
|
30
|
+
`}function R(e,n){const r=e.stack?.framework||"unknown",t=(n?.capabilities||[]).slice(0,6);return`# Quick Restore \u2014 ${n?.policyId||"this project"}
|
|
81
31
|
# Paste this at the start of any new AI session to restore context instantly.
|
|
82
32
|
# Regenerate with: infernoflow generate-skills
|
|
83
33
|
|
|
84
34
|
## Project snapshot
|
|
85
|
-
- **Project:** ${
|
|
86
|
-
- **Stack:** ${
|
|
87
|
-
- **Capabilities:** ${
|
|
35
|
+
- **Project:** ${n?.policyId||"unknown"}
|
|
36
|
+
- **Stack:** ${r} / ${e.stack?.language||"unknown"} (${e.stack?.projectType||"unknown"})
|
|
37
|
+
- **Capabilities:** ${t.join(", ")}${(n?.capabilities||[]).length>6?` +${(n?.capabilities||[]).length-6} more`:""}
|
|
88
38
|
|
|
89
39
|
## How to start a session
|
|
90
40
|
1. Run: \`infernoflow context --show\`
|
|
@@ -93,64 +43,42 @@ function buildQuickRestore(profile, contract) {
|
|
|
93
43
|
4. Pick up where you left off
|
|
94
44
|
|
|
95
45
|
## infernoflow tools available (in Cursor / VS Code Agent mode)
|
|
96
|
-
- \`infernoflow_run\`
|
|
97
|
-
- \`infernoflow_apply\`
|
|
98
|
-
- \`infernoflow_implement\`
|
|
99
|
-
- \`infernoflow_git_drift\`
|
|
100
|
-
- \`infernoflow_check\`
|
|
101
|
-
- \`infernoflow_status\`
|
|
46
|
+
- \`infernoflow_run\` \u2014 generate a contract update prompt
|
|
47
|
+
- \`infernoflow_apply\` \u2014 apply a JSON response
|
|
48
|
+
- \`infernoflow_implement\` \u2014 get a structured coding prompt
|
|
49
|
+
- \`infernoflow_git_drift\` \u2014 see what capabilities may have drifted
|
|
50
|
+
- \`infernoflow_check\` \u2014 validate contract is in sync
|
|
51
|
+
- \`infernoflow_status\` \u2014 quick health check
|
|
102
52
|
|
|
103
53
|
## Definition of done (every feature branch)
|
|
104
54
|
- [ ] Code works as intended
|
|
105
|
-
- [ ] \`infernoflow_run\`
|
|
55
|
+
- [ ] \`infernoflow_run\` \u2192 \`infernoflow_apply\` completed
|
|
106
56
|
- [ ] \`infernoflow_check\` passes
|
|
107
57
|
- [ ] Commit message references the capability changed
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
function buildNamingGuide(profile) {
|
|
112
|
-
const naming = profile.namingStyle !== "unknown" ? profile.namingStyle : "PascalCase";
|
|
113
|
-
const verbs = profile.preferredVerbs.length > 0 ? profile.preferredVerbs : ["Create", "Read", "Update", "Delete", "Search"];
|
|
114
|
-
|
|
115
|
-
const examples = verbs.slice(0, 5).map(v => {
|
|
116
|
-
if (naming === "PascalCase") return ` - ${v}Item, ${v}Task, ${v}User`;
|
|
117
|
-
if (naming === "camelCase") return ` - ${v.toLowerCase()}Item, ${v.toLowerCase()}Task`;
|
|
118
|
-
return ` - ${v.toLowerCase()}-item, ${v.toLowerCase()}-task`;
|
|
119
|
-
}).join("\n");
|
|
58
|
+
`}function _(e){const n=e.namingStyle!=="unknown"?e.namingStyle:"PascalCase",i=e.preferredVerbs.length>0?e.preferredVerbs:["Create","Read","Update","Delete","Search"],r=i.slice(0,5).map(t=>n==="PascalCase"?` - ${t}Item, ${t}Task, ${t}User`:n==="camelCase"?` - ${t.toLowerCase()}Item, ${t.toLowerCase()}Task`:` - ${t.toLowerCase()}-item, ${t.toLowerCase()}-task`).join(`
|
|
59
|
+
`);return`# Naming Guide \u2014 auto-generated from your capability history
|
|
120
60
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
## Detected style: ${naming}
|
|
61
|
+
## Detected style: ${n}
|
|
124
62
|
|
|
125
63
|
### Your preferred action verbs
|
|
126
|
-
${
|
|
64
|
+
${i.map(t=>`- **${t}**`).join(`
|
|
65
|
+
`)}
|
|
127
66
|
|
|
128
67
|
### Examples matching your style
|
|
129
|
-
${
|
|
68
|
+
${r}
|
|
130
69
|
|
|
131
70
|
### Rules
|
|
132
71
|
- All capability IDs in \`inferno/contract.json\` must follow this style
|
|
133
|
-
- New capabilities suggested by AI should match
|
|
72
|
+
- New capabilities suggested by AI should match \u2014 reject any that don't
|
|
134
73
|
- If you rename a capability, update contract.json + capabilities.json + any scenarios
|
|
135
74
|
|
|
136
75
|
### When naming a new capability, ask:
|
|
137
76
|
1. Does it describe a single, discrete behavior? (If not, split it)
|
|
138
77
|
2. Does it start with one of your preferred verbs?
|
|
139
|
-
3. Is it in ${
|
|
140
|
-
4. Is it unique
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function buildFeatureScaffold(profile, contract) {
|
|
145
|
-
const clusters = profile.featureClusters.slice(0, 2);
|
|
146
|
-
const topCluster = clusters[0] || [];
|
|
147
|
-
const framework = profile.stack?.framework || "unknown";
|
|
148
|
-
|
|
149
|
-
const clusterChecks = topCluster.slice(0, 4).map(id =>
|
|
150
|
-
`- [ ] Does **${id}** need updating? (check inferno/capabilities.json)`
|
|
151
|
-
).join("\n");
|
|
152
|
-
|
|
153
|
-
return `# Feature Scaffold — ${framework} project
|
|
78
|
+
3. Is it in ${n}?
|
|
79
|
+
4. Is it unique \u2014 not already in contract.json?
|
|
80
|
+
`}function I(e,n){const r=e.featureClusters.slice(0,2)[0]||[],t=e.stack?.framework||"unknown",o=r.slice(0,4).map(a=>`- [ ] Does **${a}** need updating? (check inferno/capabilities.json)`).join(`
|
|
81
|
+
`);return`# Feature Scaffold \u2014 ${t} project
|
|
154
82
|
# Use this checklist whenever starting a new feature.
|
|
155
83
|
# Regenerate with: infernoflow generate-skills
|
|
156
84
|
|
|
@@ -166,13 +94,13 @@ function buildFeatureScaffold(profile, contract) {
|
|
|
166
94
|
- [ ] Verify it works end-to-end
|
|
167
95
|
|
|
168
96
|
## Capability cluster check
|
|
169
|
-
${
|
|
97
|
+
${o||"- [ ] Review existing capabilities \u2014 do any need updating?"}
|
|
170
98
|
|
|
171
99
|
## Contract update (required before merge)
|
|
172
100
|
- [ ] Run \`infernoflow_run\` with a description of what changed
|
|
173
101
|
- [ ] Review the suggested JSON
|
|
174
102
|
- [ ] Run \`infernoflow_apply\` with the JSON
|
|
175
|
-
- [ ] Run \`infernoflow_check\`
|
|
103
|
+
- [ ] Run \`infernoflow_check\` \u2014 must pass
|
|
176
104
|
|
|
177
105
|
## Commit message
|
|
178
106
|
- Reference the capability: "feat: add SearchItems endpoint (#42)"
|
|
@@ -182,58 +110,5 @@ ${clusterChecks || "- [ ] Review existing capabilities — do any need updating?
|
|
|
182
110
|
- [ ] Feature works
|
|
183
111
|
- [ ] \`infernoflow_check\` passes
|
|
184
112
|
- [ ] PR description mentions which capabilities changed
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export async function generateSkillsCommand(args) {
|
|
189
|
-
const cwd = process.cwd();
|
|
190
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
191
|
-
const installCursor = args.includes("--cursor");
|
|
192
|
-
|
|
193
|
-
header("generate-skills");
|
|
194
|
-
|
|
195
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
196
|
-
if (!fs.existsSync(infernoDir)) {
|
|
197
|
-
console.error(" ✘ inferno/ not found — run: infernoflow init\n");
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const profile = readProfile(infernoDir);
|
|
202
|
-
let contract = null;
|
|
203
|
-
try { contract = JSON.parse(fs.readFileSync(path.join(infernoDir, "contract.json"), "utf8")); } catch {}
|
|
204
|
-
|
|
205
|
-
const skillsDir = path.join(cwd, SKILLS_DIR);
|
|
206
|
-
|
|
207
|
-
// Generate all four skill files
|
|
208
|
-
write(path.join(skillsDir, "cursor-rules.md"), buildCursorRules(profile, contract), force);
|
|
209
|
-
write(path.join(skillsDir, "quick-restore.md"), buildQuickRestore(profile, contract), force);
|
|
210
|
-
write(path.join(skillsDir, "naming-guide.md"), buildNamingGuide(profile), force);
|
|
211
|
-
write(path.join(skillsDir, "feature-scaffold.md"), buildFeatureScaffold(profile, contract), force);
|
|
212
|
-
|
|
213
|
-
// Optionally install cursor rules
|
|
214
|
-
if (installCursor) {
|
|
215
|
-
const rulesDir = path.join(cwd, ".cursor", "rules");
|
|
216
|
-
fs.mkdirSync(rulesDir, { recursive: true });
|
|
217
|
-
const src = path.join(skillsDir, "cursor-rules.md");
|
|
218
|
-
const dst = path.join(rulesDir, "infernoflow.md");
|
|
219
|
-
fs.copyFileSync(src, dst);
|
|
220
|
-
ok(`Installed to: .cursor/rules/infernoflow.md`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const profileSummary = [
|
|
224
|
-
profile.namingStyle !== "unknown" ? `naming: ${profile.namingStyle}` : null,
|
|
225
|
-
profile.stack?.framework !== "unknown" ? `stack: ${profile.stack.framework}` : null,
|
|
226
|
-
profile.sessionCount > 0 ? `sessions: ${profile.sessionCount}` : null,
|
|
227
|
-
].filter(Boolean).join(" · ");
|
|
228
|
-
|
|
229
|
-
done(`Skills generated${profileSummary ? ` (${profileSummary})` : ""}`);
|
|
230
|
-
|
|
231
|
-
nextSteps([
|
|
232
|
-
`Review files in ${yellow(SKILLS_DIR + "/")}`,
|
|
233
|
-
`Copy to Cursor: ${cyan("infernoflow generate-skills --cursor")}`,
|
|
234
|
-
`Re-run any time to refresh after more sessions: ${cyan("infernoflow generate-skills --force")}`,
|
|
235
|
-
profile.sessionCount < 5
|
|
236
|
-
? `Run more commands to improve personalisation (${profile.sessionCount} sessions so far)`
|
|
237
|
-
: `Profile has ${profile.sessionCount} sessions — personalisation is well-trained`,
|
|
238
|
-
]);
|
|
239
|
-
}
|
|
113
|
+
`}async function A(e){const n=process.cwd(),i=e.includes("--force")||e.includes("-f"),r=e.includes("--cursor");y("generate-skills");const t=s.join(n,"inferno");l.existsSync(t)||(console.error(` \u2718 inferno/ not found \u2014 run: infernoflow init
|
|
114
|
+
`),process.exit(1));const o=k(t);let a=null;try{a=JSON.parse(l.readFileSync(s.join(t,"contract.json"),"utf8"))}catch{}const c=s.join(n,p);if(f(s.join(c,"cursor-rules.md"),S(o,a),i),f(s.join(c,"quick-restore.md"),R(o,a),i),f(s.join(c,"naming-guide.md"),_(o),i),f(s.join(c,"feature-scaffold.md"),I(o,a),i),r){const d=s.join(n,".cursor","rules");l.mkdirSync(d,{recursive:!0});const h=s.join(c,"cursor-rules.md"),g=s.join(d,"infernoflow.md");l.copyFileSync(h,g),w("Installed to: .cursor/rules/infernoflow.md")}const u=[o.namingStyle!=="unknown"?`naming: ${o.namingStyle}`:null,o.stack?.framework!=="unknown"?`stack: ${o.stack.framework}`:null,o.sessionCount>0?`sessions: ${o.sessionCount}`:null].filter(Boolean).join(" \xB7 ");$(`Skills generated${u?` (${u})`:""}`),C([`Review files in ${j(p+"/")}`,`Copy to Cursor: ${m("infernoflow generate-skills --cursor")}`,`Re-run any time to refresh after more sessions: ${m("infernoflow generate-skills --force")}`,o.sessionCount<5?`Run more commands to improve personalisation (${o.sessionCount} sessions so far)`:`Profile has ${o.sessionCount} sessions \u2014 personalisation is well-trained`])}export{A as generateSkillsCommand};
|