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.
Files changed (78) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +31 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -320
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/monorepo.mjs +4 -428
  37. package/dist/lib/commands/notify.mjs +4 -258
  38. package/dist/lib/commands/onboard.mjs +4 -296
  39. package/dist/lib/commands/prComment.mjs +2 -361
  40. package/dist/lib/commands/prImpact.mjs +2 -157
  41. package/dist/lib/commands/publish.mjs +15 -316
  42. package/dist/lib/commands/report.mjs +28 -272
  43. package/dist/lib/commands/review.mjs +9 -223
  44. package/dist/lib/commands/run.mjs +8 -336
  45. package/dist/lib/commands/scaffold.mjs +54 -419
  46. package/dist/lib/commands/scan.mjs +5 -558
  47. package/dist/lib/commands/scout.mjs +2 -291
  48. package/dist/lib/commands/setup.mjs +5 -310
  49. package/dist/lib/commands/share.mjs +13 -196
  50. package/dist/lib/commands/snapshot.mjs +3 -383
  51. package/dist/lib/commands/stability.mjs +2 -293
  52. package/dist/lib/commands/status.mjs +4 -172
  53. package/dist/lib/commands/suggest.mjs +21 -563
  54. package/dist/lib/commands/syncAuto.mjs +1 -96
  55. package/dist/lib/commands/synthesize.mjs +10 -228
  56. package/dist/lib/commands/teamSync.mjs +2 -388
  57. package/dist/lib/commands/test.mjs +6 -363
  58. package/dist/lib/commands/version.mjs +2 -282
  59. package/dist/lib/commands/vibe.mjs +7 -357
  60. package/dist/lib/commands/watch.mjs +4 -203
  61. package/dist/lib/commands/why.mjs +4 -358
  62. package/dist/lib/cursorHooksInstall.mjs +1 -60
  63. package/dist/lib/draftToolingInstall.mjs +7 -68
  64. package/dist/lib/git/detect-drift.mjs +4 -208
  65. package/dist/lib/learning/adapt.mjs +6 -101
  66. package/dist/lib/learning/observe.mjs +1 -119
  67. package/dist/lib/learning/patternDetector.mjs +1 -298
  68. package/dist/lib/learning/profile.mjs +2 -279
  69. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  70. package/dist/lib/templates/index.mjs +1 -131
  71. package/dist/lib/ui/errors.mjs +1 -142
  72. package/dist/lib/ui/output.mjs +6 -72
  73. package/dist/lib/ui/prompts.mjs +6 -147
  74. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  75. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  76. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  77. package/dist/templates/github-app/app-manifest.json +20 -0
  78. package/package.json +1 -1
@@ -1,239 +1,10 @@
1
- /**
2
- * infernoflow export
3
- *
4
- * Export the capability contract to external formats so it can travel
5
- * outside the repo — into API docs, service catalogs, spreadsheets, wikis.
6
- *
7
- * Formats:
8
- * openapi OpenAPI 3.1 JSON (stubs for each capability)
9
- * backstage Backstage catalog-info.yaml
10
- * csv Spreadsheet-ready CSV
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
- * infernoflow generate-skills
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: [${(contract?.capabilities || []).join(", ")}]
10
+ - Current capabilities: [${(n?.capabilities||[]).join(", ")}]
53
11
 
54
12
  ## Naming conventions (detected from this developer's history)
55
- - Capability IDs use **${naming}** (e.g. ${verbs.split(", ").slice(0, 2).map(v => v + "Item").join(", ")})
56
- - Preferred action verbs: ${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
- ${clusterRules || "- No clusters detected yet build more features to train this"}
18
+ ${c||"- No clusters detected yet \u2014 build more features to train this"}
61
19
 
62
- ## Session style (${sessionStyle})
63
- ${sessionStyle === "experienced"
64
- ? "- Skip basic explanations this developer knows the codebase well\n- Be direct and minimal in responses"
65
- : "- Include brief context for non-obvious decisions\n- Explain infernoflow commands when used"}
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 ${profile.changelogVerbosity === "detailed" ? "detailed (include context and impact)" : "brief (one line, action-focused)"}
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:** ${contract?.policyId || "unknown"}
86
- - **Stack:** ${framework} / ${profile.stack?.language || "unknown"} (${profile.stack?.projectType || "unknown"})
87
- - **Capabilities:** ${capabilities.join(", ")}${(contract?.capabilities || []).length > 6 ? ` +${(contract?.capabilities || []).length - 6} more` : ""}
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\` generate a contract update prompt
97
- - \`infernoflow_apply\` apply a JSON response
98
- - \`infernoflow_implement\` get a structured coding prompt
99
- - \`infernoflow_git_drift\` see what capabilities may have drifted
100
- - \`infernoflow_check\` validate contract is in sync
101
- - \`infernoflow_status\` quick health check
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\` \`infernoflow_apply\` completed
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
- return `# Naming Guide — auto-generated from your capability history
122
-
123
- ## Detected style: ${naming}
61
+ ## Detected style: ${n}
124
62
 
125
63
  ### Your preferred action verbs
126
- ${verbs.map(v => `- **${v}**`).join("\n")}
64
+ ${i.map(t=>`- **${t}**`).join(`
65
+ `)}
127
66
 
128
67
  ### Examples matching your style
129
- ${examples}
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 reject any that don't
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 ${naming}?
140
- 4. Is it unique not already in contract.json?
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
- ${clusterChecks || "- [ ] Review existing capabilities do any need updating?"}
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\` must pass
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};