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,103 +1,7 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { execSync } from "node:child_process";
4
- import { header, section, info, warn, cyan, gray, errorAndExit } from "../ui/output.mjs";
5
- import {
6
- loadImplementContext,
7
- buildCursorImplementPrompt,
8
- buildGenericImplementPrompt,
9
- } from "../ui/prompts.mjs";
10
-
11
- function getFlagValue(args, flag) {
12
- const idx = args.indexOf(flag);
13
- return idx !== -1 && args[idx + 1] ? args[idx + 1] : null;
14
- }
15
-
16
- function extractTask(args) {
17
- const skipNextFor = new Set(["--mode"]);
18
- const parts = [];
19
- for (let i = 0; i < args.length; i += 1) {
20
- const token = args[i];
21
- if (token.startsWith("-")) {
22
- if (skipNextFor.has(token)) i += 1;
23
- continue;
24
- }
25
- if (i === 0) continue; // command name
26
- parts.push(token);
27
- }
28
- return parts.join(" ").trim();
29
- }
30
-
31
- function copyToClipboard(text) {
32
- try {
33
- const p = process.platform;
34
- if (p === "win32") execSync("clip", { input: text });
35
- else if (p === "darwin") execSync("pbcopy", { input: text });
36
- else {
37
- try { execSync("xclip -selection clipboard", { input: text }); }
38
- catch { execSync("xsel --clipboard --input", { input: text }); }
39
- }
40
- return true;
41
- } catch {
42
- return false;
43
- }
44
- }
45
-
46
- export async function implementCommand(args = []) {
47
- header("implement");
48
-
49
- const cwd = process.cwd();
50
- const infernoDir = path.join(cwd, "inferno");
51
- if (!fs.existsSync(infernoDir)) {
52
- errorAndExit("inferno/ not found", "Run: infernoflow init");
53
- }
54
-
55
- const mode = (getFlagValue(args, "--mode") || "both").toLowerCase();
56
- const copyFlag = args.includes("--copy") || args.includes("-c");
57
- if (!["cursor", "generic", "both"].includes(mode)) {
58
- errorAndExit("Invalid --mode value", "Use: --mode cursor|generic|both");
59
- }
60
-
61
- const rawTask = extractTask(args);
62
- if (!rawTask) {
63
- errorAndExit("No task provided", 'Usage: infernoflow implement "your task description"');
64
- }
65
-
66
- const context = loadImplementContext(cwd);
67
- const cursorPrompt = buildCursorImplementPrompt({ task: rawTask, ...context });
68
- const genericPrompt = buildGenericImplementPrompt({ task: rawTask, ...context });
69
-
70
- info(`Task: ${cyan(rawTask)}`);
71
- info(`Mode: ${cyan(mode)}`);
72
- warn("If you hit model high-load/resource-exhausted, retry with Auto/another model.");
73
-
74
- if (mode === "cursor" || mode === "both") {
75
- section("Cursor Agent Prompt");
76
- console.log();
77
- console.log(gray("─".repeat(50)));
78
- console.log(cursorPrompt);
79
- console.log(gray("─".repeat(50)));
80
- }
81
-
82
- if (mode === "generic" || mode === "both") {
83
- section("Generic Agent Prompt");
84
- console.log();
85
- console.log(gray("─".repeat(50)));
86
- console.log(genericPrompt);
87
- console.log(gray("─".repeat(50)));
88
- }
89
-
90
- if (copyFlag) {
91
- const textToCopy =
92
- mode === "cursor"
93
- ? cursorPrompt
94
- : mode === "generic"
95
- ? genericPrompt
96
- : `## Cursor Agent Prompt\n\n${cursorPrompt}\n\n## Generic Agent Prompt\n\n${genericPrompt}`;
97
- const ok = copyToClipboard(textToCopy);
98
- if (ok) info(`Copied ${mode} prompt${mode === "both" ? "s" : ""} to clipboard.`);
99
- else warn("Clipboard copy failed. Copy from terminal output.");
100
- }
101
-
102
- console.log();
103
- }
1
+ import y from"node:fs";import b from"node:path";import{execSync as c}from"node:child_process";import{header as k,section as f,info as m,warn as d,cyan as h,gray as s,errorAndExit as u}from"../ui/output.mjs";import{loadImplementContext as w,buildCursorImplementPrompt as x,buildGenericImplementPrompt as C}from"../ui/prompts.mjs";function P(o,n){const t=o.indexOf(n);return t!==-1&&o[t+1]?o[t+1]:null}function A(o){const n=new Set(["--mode"]),t=[];for(let e=0;e<o.length;e+=1){const r=o[e];if(r.startsWith("-")){n.has(r)&&(e+=1);continue}e!==0&&t.push(r)}return t.join(" ").trim()}function $(o){try{const n=process.platform;if(n==="win32")c("clip",{input:o});else if(n==="darwin")c("pbcopy",{input:o});else try{c("xclip -selection clipboard",{input:o})}catch{c("xsel --clipboard --input",{input:o})}return!0}catch{return!1}}async function j(o=[]){k("implement");const n=process.cwd(),t=b.join(n,"inferno");y.existsSync(t)||u("inferno/ not found","Run: infernoflow init");const e=(P(o,"--mode")||"both").toLowerCase(),r=o.includes("--copy")||o.includes("-c");["cursor","generic","both"].includes(e)||u("Invalid --mode value","Use: --mode cursor|generic|both");const i=A(o);i||u("No task provided",'Usage: infernoflow implement "your task description"');const a=w(n),l=x({task:i,...a}),p=C({task:i,...a});if(m(`Task: ${h(i)}`),m(`Mode: ${h(e)}`),d("If you hit model high-load/resource-exhausted, retry with Auto/another model."),(e==="cursor"||e==="both")&&(f("Cursor Agent Prompt"),console.log(),console.log(s("\u2500".repeat(50))),console.log(l),console.log(s("\u2500".repeat(50)))),(e==="generic"||e==="both")&&(f("Generic Agent Prompt"),console.log(),console.log(s("\u2500".repeat(50))),console.log(p),console.log(s("\u2500".repeat(50)))),r){const g=e==="cursor"?l:e==="generic"?p:`## Cursor Agent Prompt
2
+
3
+ ${l}
4
+
5
+ ## Generic Agent Prompt
6
+
7
+ ${p}`;$(g)?m(`Copied ${e} prompt${e==="both"?"s":""} to clipboard.`):d("Clipboard copy failed. Copy from terminal output.")}console.log()}export{j as implementCommand};
@@ -1,482 +1,30 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import * as readline from "node:readline";
4
- import { fileURLToPath } from "node:url";
5
- import { header, ok, warn, done, nextSteps, cyan, yellow, gray } from "../ui/output.mjs";
6
- import {
7
- discoverProjectSignals,
8
- reviewCapabilitiesInteractive,
9
- writeAdoptionBaseline,
10
- buildAdoptionReport,
11
- summarizeCapabilities,
12
- buildSignalsReport,
13
- } from "./adopt.mjs";
14
- import { installCursorHooksArtifacts } from "../cursorHooksInstall.mjs";
15
- import { installVsCodeCopilotHooksArtifacts } from "../vsCodeCopilotHooksInstall.mjs";
16
-
17
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
-
19
- function getTemplatesRoot() {
20
- return path.resolve(__dirname, "../../templates");
21
- }
22
-
23
- function ask(rl, question, defaultVal = "") {
24
- return new Promise(resolve => {
25
- const hint = defaultVal ? gray(` (${defaultVal})`) : "";
26
- rl.question(` ${question}${hint}: `, answer => {
27
- resolve(answer.trim() || defaultVal);
28
- });
29
- });
30
- }
31
-
32
- function getArgValue(args, ...flags) {
33
- for (const flag of flags) {
34
- const i = args.indexOf(flag);
35
- if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) return args[i + 1];
36
- }
37
- return null;
38
- }
39
-
40
- function copyFile(src, dst, force, silent = false) {
41
- if (fs.existsSync(dst) && !force) {
42
- if (!silent) warn("Skipped (exists): " + path.relative(process.cwd(), dst));
43
- return false;
44
- }
45
- fs.mkdirSync(path.dirname(dst), { recursive: true });
46
- fs.copyFileSync(src, dst);
47
- if (!silent) ok("Created: " + cyan(path.relative(process.cwd(), dst)));
48
- return true;
49
- }
50
-
51
- function copyDirDeep(srcDir, dstDir, force) {
52
- fs.mkdirSync(dstDir, { recursive: true });
53
- for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
54
- const src = path.join(srcDir, entry.name);
55
- const dst = path.join(dstDir, entry.name);
56
- if (entry.isDirectory()) copyDirDeep(src, dst, force);
57
- else copyFile(src, dst, force);
58
- }
59
- }
60
-
61
- function upsertScripts(cwd, silent = false) {
62
- const pkgPath = path.join(cwd, "package.json");
63
- if (!fs.existsSync(pkgPath)) return;
64
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
65
- pkg.scripts = pkg.scripts || {};
66
- let changed = false;
67
- const toAdd = {
68
- "inferno:check": "infernoflow check",
69
- "inferno:status": "infernoflow status",
70
- "inferno:gate": "infernoflow doc-gate",
71
- "inferno:impact": "infernoflow pr-impact --json",
72
- "inferno:sync": "infernoflow sync --auto --json",
73
- "inferno:run": "infernoflow run \"sync check\" --provider auto --json",
74
- "inferno:hooks": "node scripts/inferno-install-hooks.mjs"
75
- };
76
- for (const [k, v] of Object.entries(toAdd)) {
77
- if (!pkg.scripts[k]) { pkg.scripts[k] = v; changed = true; }
78
- }
79
- if (changed) {
80
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
81
- if (!silent) ok("Updated " + cyan("package.json") + " scripts");
82
- }
83
- }
84
-
85
- function detectProjectName(cwd) {
86
- const pkgPath = path.join(cwd, "package.json");
87
- if (fs.existsSync(pkgPath)) {
88
- try {
89
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
90
- if (pkg.name) return pkg.name.replace(/[^a-z0-9_-]/gi, "_");
91
- } catch {}
92
- }
93
- return path.basename(cwd);
94
- }
95
-
96
- function writeContract(contractPath, policyId, capabilities) {
97
- const contract = {
98
- policyId,
99
- policyVersion: 1,
100
- capabilities,
101
- rules: {
102
- docsRequiredOnCapabilityChange: true,
103
- requireScenarioForEachCapability: true,
104
- requireChangelogOnCapabilityChange: true
105
- }
106
- };
107
- fs.writeFileSync(contractPath, JSON.stringify(contract, null, 2) + "\n");
108
- }
109
-
110
- function writeCapabilities(capsPath, capabilities) {
111
- const registry = {
112
- schemaVersion: 1,
113
- capabilities: capabilities.map(id => ({
114
- id,
115
- title: id.replace(/([A-Z])/g, " $1").trim(),
116
- since: "0.1.0"
117
- }))
118
- };
119
- fs.writeFileSync(capsPath, JSON.stringify(registry, null, 2) + "\n");
120
- }
121
-
122
- function writeScenario(scenariosDir, capabilities) {
123
- fs.mkdirSync(scenariosDir, { recursive: true });
124
- const scenario = {
125
- scenarioId: "happy_path",
126
- description: "Basic happy-path flow covering all capabilities",
127
- capabilitiesCovered: capabilities,
128
- steps: capabilities.map(c => ({
129
- action: c,
130
- expect: `${c} works as expected`
131
- }))
132
- };
133
- fs.writeFileSync(
134
- path.join(scenariosDir, "happy_path.json"),
135
- JSON.stringify(scenario, null, 2) + "\n"
136
- );
137
- }
138
-
139
- function writeChangelog(changelogPath, policyId) {
140
- const content = `# Changelog — ${policyId}
1
+ import*as r from"node:fs";import*as t from"node:path";import*as G from"node:readline";import{fileURLToPath as ne}from"node:url";import{header as oe,ok as d,warn as $,done as D,nextSteps as ie,cyan as l,yellow as N,gray as S}from"../ui/output.mjs";import{discoverProjectSignals as I,reviewCapabilitiesInteractive as te,writeAdoptionBaseline as se,buildAdoptionReport as re,summarizeCapabilities as ae,buildSignalsReport as ce}from"./adopt.mjs";import{installCursorHooksArtifacts as le}from"../cursorHooksInstall.mjs";import{installVsCodeCopilotHooksArtifacts as pe}from"../vsCodeCopilotHooksInstall.mjs";const fe=t.dirname(ne(import.meta.url));function de(){return t.resolve(fe,"../../templates")}function P(e,n,i=""){return new Promise(s=>{const u=i?S(` (${i})`):"";e.question(` ${n}${u}: `,y=>{s(y.trim()||i)})})}function J(e,...n){for(const i of n){const s=e.indexOf(i);if(s!==-1&&e[s+1]&&!e[s+1].startsWith("-"))return e[s+1]}return null}function T(e,n,i,s=!1){return r.existsSync(n)&&!i?(s||$("Skipped (exists): "+t.relative(process.cwd(),n)),!1):(r.mkdirSync(t.dirname(n),{recursive:!0}),r.copyFileSync(e,n),s||d("Created: "+l(t.relative(process.cwd(),n))),!0)}function ue(e,n,i){r.mkdirSync(n,{recursive:!0});for(const s of r.readdirSync(e,{withFileTypes:!0})){const u=t.join(e,s.name),y=t.join(n,s.name);s.isDirectory()?ue(u,y,i):T(u,y,i)}}function me(e,n=!1){const i=t.join(e,"package.json");if(!r.existsSync(i))return;const s=JSON.parse(r.readFileSync(i,"utf8"));s.scripts=s.scripts||{};let u=!1;const y={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[g,v]of Object.entries(y))s.scripts[g]||(s.scripts[g]=v,u=!0);u&&(r.writeFileSync(i,JSON.stringify(s,null,2)+`
2
+ `,"utf8"),n||d("Updated "+l("package.json")+" scripts"))}function U(e){const n=t.join(e,"package.json");if(r.existsSync(n))try{const i=JSON.parse(r.readFileSync(n,"utf8"));if(i.name)return i.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return t.basename(e)}function ye(e,n,i){const s={policyId:n,policyVersion:1,capabilities:i,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};r.writeFileSync(e,JSON.stringify(s,null,2)+`
3
+ `)}function ge(e,n){const i={schemaVersion:1,capabilities:n.map(s=>({id:s,title:s.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};r.writeFileSync(e,JSON.stringify(i,null,2)+`
4
+ `)}function we(e,n){r.mkdirSync(e,{recursive:!0});const i={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:n,steps:n.map(s=>({action:s,expect:`${s} works as expected`}))};r.writeFileSync(t.join(e,"happy_path.json"),JSON.stringify(i,null,2)+`
5
+ `)}function K(e,n){const i=`# Changelog \u2014 ${n}
141
6
 
142
7
  ## Unreleased
143
8
 
144
9
  - Initial capabilities defined
145
10
 
146
- ## 0.1.0 Initial release
11
+ ## 0.1.0 \u2014 Initial release
147
12
 
148
13
  - Project initialized with infernoflow
149
- `;
150
- fs.writeFileSync(changelogPath, content);
151
- }
152
-
153
- export async function initCommand(args) {
154
- const cwd = process.cwd();
155
- const force = args.includes("--force") || args.includes("-f");
156
- const yes = args.includes("--yes") || args.includes("-y");
157
- const adopt = args.includes("--adopt");
158
-
159
- // ── Template shortcut ──────────────────────────────────────────────────────
160
- const templateIdx = args.indexOf("--template");
161
- const templateName = templateIdx !== -1 ? args[templateIdx + 1] : null;
162
-
163
- if (templateName) {
164
- let tmplMod;
165
- try { tmplMod = await import("../templates/index.mjs"); } catch {}
166
- const tmpl = tmplMod?.getTemplate(templateName);
167
- if (!tmpl) {
168
- const available = tmplMod ? tmplMod.listTemplates().map(t => t.name).join(", ") : "rest-api, nextjs, cli, graphql, monorepo";
169
- warn(`Unknown template: ${templateName}. Available: ${available}`);
170
- process.exit(1);
171
- }
172
-
173
- const infernoDir = path.join(cwd, "inferno");
174
- const scenDir = path.join(infernoDir, "scenarios");
175
- if (!fs.existsSync(infernoDir)) fs.mkdirSync(infernoDir, { recursive: true });
176
- if (!fs.existsSync(scenDir)) fs.mkdirSync(scenDir, { recursive: true });
177
-
178
- const policyId = detectProjectName(cwd);
179
- const caps = tmpl.capabilities;
180
-
181
- // Write contract.json
182
- fs.writeFileSync(path.join(infernoDir, "contract.json"), JSON.stringify({
183
- policyId, policyVersion: 1,
184
- capabilities: caps.map(c => c.id),
185
- }, null, 2) + "\n");
186
-
187
- // Write capabilities.json
188
- fs.writeFileSync(path.join(infernoDir, "capabilities.json"), JSON.stringify({
189
- capabilities: caps.map(c => ({
190
- id: c.id, description: c.description,
191
- since: new Date().toISOString().slice(0, 10), source: `template:${templateName}`,
192
- })),
193
- }, null, 2) + "\n");
194
-
195
- // Write one scenario per capability
196
- for (const cap of caps) {
197
- fs.writeFileSync(path.join(scenDir, `${cap.id}.json`), JSON.stringify({
198
- id: `${cap.id}-happy-path`, capability: cap.id,
199
- description: `Happy path for ${cap.description || cap.id}`,
200
- steps: [
201
- { action: "invoke", target: cap.id, input: {} },
202
- { action: "assert", field: "status", value: "success" },
203
- ],
204
- capabilitiesCovered: [cap.id],
205
- }, null, 2) + "\n");
206
- }
207
-
208
- // Write CHANGELOG.md
209
- writeChangelog(path.join(infernoDir, "CHANGELOG.md"), policyId);
210
-
211
- // Write CONTEXT.md hint
212
- fs.writeFileSync(path.join(infernoDir, "CONTEXT.md"),
213
- `# ${policyId} — infernoflow context\n\n> Template: ${templateName} — ${tmpl.description}\n\n## Hint\n${tmpl.contextHint}\n\n## Capabilities (${caps.length})\n${caps.map(c => `- \`${c.id}\`: ${c.description}`).join("\n")}\n`
214
- );
215
-
216
- if (tmpl.scripts) {
217
- info(`Suggested package.json scripts for this template:`);
218
- Object.entries(tmpl.scripts).forEach(([k, v]) => console.log(` ${bold(k)}: ${gray(v)}`));
219
- console.log();
220
- }
221
-
222
- done(`Initialised from template ${bold(cyan(templateName))} — ${bold(String(caps.length))} capabilities`);
223
- console.log();
224
- info(`Run ${cyan("infernoflow vibe")} to start vibe coding mode`);
225
- console.log();
226
- return;
227
- }
228
- const cursorHooks = args.includes("--cursor-hooks");
229
- const vscodeCopilotHooks = args.includes("--vscode-copilot-hooks");
230
- const reportJson = args.includes("--report-json");
231
- const reportJsonOnly = args.includes("--report-json-only");
232
- const reportHumanOnly = args.includes("--report-human-only");
233
- const langOverride = getArgValue(args, "--lang");
234
- const frameworkOverride = getArgValue(args, "--framework");
235
- const projectTypeOverride = getArgValue(args, "--project-type");
236
- const silent = reportJsonOnly;
237
-
238
- if (reportJsonOnly && reportHumanOnly) {
239
- console.error("Error: --report-json-only and --report-human-only cannot be used together.");
240
- process.exit(1);
241
- }
242
-
243
- if (!silent) {
244
- header("init");
245
- }
246
-
247
- const infernoDir = path.join(cwd, "inferno");
248
- const workflowsDir = path.join(cwd, ".github", "workflows");
249
- if (fs.existsSync(infernoDir) && !force) {
250
- if (silent) {
251
- console.log(JSON.stringify({ ok: false, error: "inferno_exists", hint: "Use --force to overwrite" }, null, 2));
252
- process.exit(1);
253
- }
254
- warn("inferno/ already exists. Use --force to overwrite.");
255
- console.log();
256
- process.exit(0);
257
- }
258
-
259
- const detectedName = detectProjectName(cwd);
260
- const defaultCaps = "CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";
261
-
262
- let policyId = detectedName;
263
- let capabilities = defaultCaps.split(",").map(c => c.trim());
264
-
265
- if (adopt) {
266
- const profileOverrides = {
267
- language: langOverride || undefined,
268
- framework: frameworkOverride || undefined,
269
- projectType: projectTypeOverride || undefined,
270
- };
271
- let signals = discoverProjectSignals(cwd, profileOverrides);
272
- if (!yes && !reportJsonOnly) {
273
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
274
- const profile = signals.developmentProfile || {};
275
- const detected = profile.detected || {};
276
- console.log(gray(" Review inferred development stack (press Enter to accept detected values)\n"));
277
- const language = await ask(rl, "Language", profile.language || detected.language || "unknown");
278
- const framework = await ask(rl, "Framework", profile.framework || detected.framework || "unknown");
279
- const projectType = await ask(rl, "Project type", profile.projectType || detected.projectType || "unknown");
280
- rl.close();
281
- signals = discoverProjectSignals(cwd, { language, framework, projectType });
282
- }
283
- const inferred = signals.capabilities;
284
- const summarized = summarizeCapabilities(inferred);
285
- if (reportJsonOnly) {
286
- console.log(
287
- JSON.stringify(
288
- {
289
- mode: "adopt",
290
- policyId: detectedName,
291
- inferredCapabilities: summarized,
292
- components: signals.components,
293
- displayFields: signals.displayFields,
294
- externalLibraries: signals.externalLibraries,
295
- uiLayout: signals.uiLayout,
296
- styling: signals.styling,
297
- developmentProfile: signals.developmentProfile,
298
- apiCalls: signals.apiCalls,
299
- },
300
- null,
301
- 2
302
- )
303
- );
304
- } else {
305
- console.log();
306
- console.log(gray(buildAdoptionReport(inferred)));
307
- console.log();
308
- console.log(gray(buildSignalsReport(signals)));
309
- console.log();
310
- if (reportJson && !reportHumanOnly) {
311
- console.log(
312
- JSON.stringify(
313
- {
314
- mode: "adopt",
315
- policyId: detectedName,
316
- inferredCapabilities: summarized,
317
- components: signals.components,
318
- displayFields: signals.displayFields,
319
- externalLibraries: signals.externalLibraries,
320
- uiLayout: signals.uiLayout,
321
- styling: signals.styling,
322
- developmentProfile: signals.developmentProfile,
323
- apiCalls: signals.apiCalls,
324
- },
325
- null,
326
- 2
327
- )
328
- );
329
- console.log();
330
- }
331
- }
332
- const reviewed = await reviewCapabilitiesInteractive(inferred, yes);
333
- policyId = detectedName;
334
- capabilities = reviewed.map((c) => c.id);
335
- } else if (!yes) {
336
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
337
- console.log(gray(" Press Enter to accept defaults\n"));
338
- policyId = await ask(rl, "Project / policy name", detectedName);
339
- const capsRaw = await ask(rl, "Capabilities (comma-separated)", defaultCaps);
340
- capabilities = capsRaw.split(",").map(c => c.trim()).filter(Boolean);
341
- rl.close();
342
- console.log();
343
- }
344
-
345
- // Write files
346
- fs.mkdirSync(infernoDir, { recursive: true });
347
-
348
- if (adopt) {
349
- const capDetails = capabilities.map((id) => ({
350
- id,
351
- title: id.replace(/([A-Z])/g, " $1").trim(),
352
- }));
353
- const signals = discoverProjectSignals(cwd, {
354
- language: langOverride || undefined,
355
- framework: frameworkOverride || undefined,
356
- projectType: projectTypeOverride || undefined,
357
- });
358
- writeAdoptionBaseline(infernoDir, policyId, capDetails, signals);
359
- if (!silent) {
360
- ok("Created: " + cyan("inferno/contract.json"));
361
- ok("Created: " + cyan("inferno/capabilities.json"));
362
- ok("Created: " + cyan("inferno/scenarios/adoption_baseline.json"));
363
- ok("Created: " + cyan("inferno/adoption_profile.json"));
364
- ok("Created: " + cyan("inferno/CHANGELOG.md"));
365
- }
366
- } else {
367
- writeContract(path.join(infernoDir, "contract.json"), policyId, capabilities);
368
- if (!silent) ok("Created: " + cyan("inferno/contract.json"));
369
-
370
- writeCapabilities(path.join(infernoDir, "capabilities.json"), capabilities);
371
- if (!silent) ok("Created: " + cyan("inferno/capabilities.json"));
372
-
373
- writeScenario(path.join(infernoDir, "scenarios"), capabilities);
374
- if (!silent) ok("Created: " + cyan("inferno/scenarios/happy_path.json"));
375
-
376
- writeChangelog(path.join(infernoDir, "CHANGELOG.md"), policyId);
377
- if (!silent) ok("Created: " + cyan("inferno/CHANGELOG.md"));
378
- }
379
-
380
- // Copy doc-gate script
381
- const templates = getTemplatesRoot();
382
- const srcScript = path.join(templates, "scripts", "inferno-doc-gate.mjs");
383
- const dstScript = path.join(cwd, "scripts", "inferno-doc-gate.mjs");
384
- copyFile(srcScript, dstScript, force, silent);
385
- const srcHookScript = path.join(templates, "scripts", "inferno-install-hooks.mjs");
386
- const dstHookScript = path.join(cwd, "scripts", "inferno-install-hooks.mjs");
387
- copyFile(srcHookScript, dstHookScript, force, silent);
388
- const srcWorkflow = path.join(templates, "ci", "github-inferno-check.yml");
389
- const dstWorkflow = path.join(workflowsDir, "infernoflow-check.yml");
390
- copyFile(srcWorkflow, dstWorkflow, force, silent);
391
-
392
- upsertScripts(cwd, silent);
393
-
394
- if (cursorHooks) {
395
- installCursorHooksArtifacts({
396
- cwd,
397
- templatesRoot: templates,
398
- force,
399
- silent,
400
- logOk: (msg) => {
401
- if (!silent) ok(msg);
402
- },
403
- logWarn: (msg) => {
404
- if (!silent) warn(msg);
405
- },
406
- });
407
- }
408
- if (vscodeCopilotHooks) {
409
- installVsCodeCopilotHooksArtifacts({
410
- cwd,
411
- templatesRoot: templates,
412
- force,
413
- silent,
414
- logOk: (msg) => {
415
- if (!silent) ok(msg);
416
- },
417
- logWarn: (msg) => {
418
- if (!silent) warn(msg);
419
- },
420
- });
421
- }
422
-
423
- if (adopt) {
424
- const statePath = path.join(infernoDir, "context-state.json");
425
- let state = {};
426
- try {
427
- state = JSON.parse(fs.readFileSync(statePath, "utf8"));
428
- } catch {}
429
- const signals = discoverProjectSignals(cwd, {
430
- language: langOverride || undefined,
431
- framework: frameworkOverride || undefined,
432
- projectType: projectTypeOverride || undefined,
433
- });
434
- state.stack = signals.developmentProfile;
435
- fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
436
- if (!silent) ok("Created: " + cyan("inferno/context-state.json"));
437
- }
438
-
439
- if (!silent) {
440
- done("infernoflow initialized!");
441
-
442
- // AI provider nudge — show once at init if nothing is configured
443
- const intPath = path.join(infernoDir, "integrations.json");
444
- const hasAiKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY ||
445
- process.env.GOOGLE_AI_API_KEY || process.env.OPENROUTER_API_KEY ||
446
- process.env.GEMINI_API_KEY;
447
- if (!hasAiKey && !fs.existsSync(intPath)) {
448
- console.log();
449
- console.log(` ${yellow("💡")} ${bold("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`);
450
- console.log(` ${cyan("infernoflow ai setup")} — takes 60 seconds`);
451
- }
452
-
453
- nextSteps([
454
- cyan("infernoflow status") + " — see your contract at a glance",
455
- cyan("infernoflow check") + " — validate everything",
456
- (adopt ? "Review inferred baseline in " : "Edit ") + yellow("inferno/capabilities.json") + (adopt ? " and refine IDs/titles" : " to describe each capability in detail"),
457
- "Add more " + yellow("inferno/scenarios/*.json") + " files for edge cases",
458
- "Add " + cyan("inferno:check") + " to your CI pipeline",
459
- ...(cursorHooks
460
- ? [
461
- "Restart Cursor — hooks write assistant text to " + yellow("inferno/CONTEXT.draft.md"),
462
- "Promote when ready: " + cyan("npm run inferno:promote-draft -- --append-notes"),
463
- ]
464
- : []),
465
- ...(vscodeCopilotHooks
466
- ? [
467
- "Restart VS Code — Copilot hooks append prompts + assistant (from transcript) to " +
468
- yellow("inferno/CONTEXT.draft.md"),
469
- "Promote when ready: " + cyan("npm run inferno:promote-draft -- --append-notes"),
470
- ]
471
- : []),
472
- ...(!cursorHooks && !vscodeCopilotHooks
473
- ? [
474
- "Optional: " +
475
- cyan("infernoflow install-cursor-hooks") +
476
- " or " +
477
- cyan("infernoflow install-vscode-copilot-hooks"),
478
- ]
479
- : []),
480
- ]);
481
- }
482
- }
14
+ `;r.writeFileSync(e,i)}async function Ce(e){const n=process.cwd(),i=e.includes("--force")||e.includes("-f"),s=e.includes("--yes")||e.includes("-y"),u=e.includes("--adopt"),y=e.indexOf("--template"),g=y!==-1?e[y+1]:null;if(g){let a;try{a=await import("../templates/index.mjs")}catch{}const o=a?.getTemplate(g);if(!o){const c=a?a.listTemplates().map(j=>j.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";$(`Unknown template: ${g}. Available: ${c}`),process.exit(1)}const f=t.join(n,"inferno"),h=t.join(f,"scenarios");r.existsSync(f)||r.mkdirSync(f,{recursive:!0}),r.existsSync(h)||r.mkdirSync(h,{recursive:!0});const x=U(n),m=o.capabilities;r.writeFileSync(t.join(f,"contract.json"),JSON.stringify({policyId:x,policyVersion:1,capabilities:m.map(c=>c.id)},null,2)+`
15
+ `),r.writeFileSync(t.join(f,"capabilities.json"),JSON.stringify({capabilities:m.map(c=>({id:c.id,description:c.description,since:new Date().toISOString().slice(0,10),source:`template:${g}`}))},null,2)+`
16
+ `);for(const c of m)r.writeFileSync(t.join(h,`${c.id}.json`),JSON.stringify({id:`${c.id}-happy-path`,capability:c.id,description:`Happy path for ${c.description||c.id}`,steps:[{action:"invoke",target:c.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[c.id]},null,2)+`
17
+ `);K(t.join(f,"CHANGELOG.md"),x),r.writeFileSync(t.join(f,"CONTEXT.md"),`# ${x} \u2014 infernoflow context
18
+
19
+ > Template: ${g} \u2014 ${o.description}
20
+
21
+ ## Hint
22
+ ${o.contextHint}
23
+
24
+ ## Capabilities (${m.length})
25
+ ${m.map(c=>`- \`${c.id}\`: ${c.description}`).join(`
26
+ `)}
27
+ `),o.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(o.scripts).forEach(([c,j])=>console.log(` ${bold(c)}: ${S(j)}`)),console.log()),D(`Initialised from template ${bold(l(g))} \u2014 ${bold(String(m.length))} capabilities`),console.log(),info(`Run ${l("infernoflow vibe")} to start vibe coding mode`),console.log();return}const v=e.includes("--cursor-hooks"),E=e.includes("--vscode-copilot-hooks"),q=e.includes("--report-json"),A=e.includes("--report-json-only"),H=e.includes("--report-human-only"),F=J(e,"--lang"),_=J(e,"--framework"),R=J(e,"--project-type"),p=A;A&&H&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),p||oe("init");const w=t.join(n,"inferno"),z=t.join(n,".github","workflows");r.existsSync(w)&&!i&&(p&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),$("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const C=U(n),L="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let b=C,k=L.split(",").map(a=>a.trim());if(u){let o=I(n,{language:F||void 0,framework:_||void 0,projectType:R||void 0});if(!s&&!A){const m=G.createInterface({input:process.stdin,output:process.stdout}),c=o.developmentProfile||{},j=c.detected||{};console.log(S(` Review inferred development stack (press Enter to accept detected values)
28
+ `));const Z=await P(m,"Language",c.language||j.language||"unknown"),Q=await P(m,"Framework",c.framework||j.framework||"unknown"),ee=await P(m,"Project type",c.projectType||j.projectType||"unknown");m.close(),o=I(n,{language:Z,framework:Q,projectType:ee})}const f=o.capabilities,h=ae(f);A?console.log(JSON.stringify({mode:"adopt",policyId:C,inferredCapabilities:h,components:o.components,displayFields:o.displayFields,externalLibraries:o.externalLibraries,uiLayout:o.uiLayout,styling:o.styling,developmentProfile:o.developmentProfile,apiCalls:o.apiCalls},null,2)):(console.log(),console.log(S(re(f))),console.log(),console.log(S(ce(o))),console.log(),q&&!H&&(console.log(JSON.stringify({mode:"adopt",policyId:C,inferredCapabilities:h,components:o.components,displayFields:o.displayFields,externalLibraries:o.externalLibraries,uiLayout:o.uiLayout,styling:o.styling,developmentProfile:o.developmentProfile,apiCalls:o.apiCalls},null,2)),console.log()));const x=await te(f,s);b=C,k=x.map(m=>m.id)}else if(!s){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(S(` Press Enter to accept defaults
29
+ `)),b=await P(a,"Project / policy name",C),k=(await P(a,"Capabilities (comma-separated)",L)).split(",").map(f=>f.trim()).filter(Boolean),a.close(),console.log()}if(r.mkdirSync(w,{recursive:!0}),u){const a=k.map(f=>({id:f,title:f.replace(/([A-Z])/g," $1").trim()})),o=I(n,{language:F||void 0,framework:_||void 0,projectType:R||void 0});se(w,b,a,o),p||(d("Created: "+l("inferno/contract.json")),d("Created: "+l("inferno/capabilities.json")),d("Created: "+l("inferno/scenarios/adoption_baseline.json")),d("Created: "+l("inferno/adoption_profile.json")),d("Created: "+l("inferno/CHANGELOG.md")))}else ye(t.join(w,"contract.json"),b,k),p||d("Created: "+l("inferno/contract.json")),ge(t.join(w,"capabilities.json"),k),p||d("Created: "+l("inferno/capabilities.json")),we(t.join(w,"scenarios"),k),p||d("Created: "+l("inferno/scenarios/happy_path.json")),K(t.join(w,"CHANGELOG.md"),b),p||d("Created: "+l("inferno/CHANGELOG.md"));const O=de(),W=t.join(O,"scripts","inferno-doc-gate.mjs"),Y=t.join(n,"scripts","inferno-doc-gate.mjs");T(W,Y,i,p);const V=t.join(O,"scripts","inferno-install-hooks.mjs"),B=t.join(n,"scripts","inferno-install-hooks.mjs");T(V,B,i,p);const X=t.join(O,"ci","github-inferno-check.yml"),M=t.join(z,"infernoflow-check.yml");if(T(X,M,i,p),me(n,p),v&&le({cwd:n,templatesRoot:O,force:i,silent:p,logOk:a=>{p||d(a)},logWarn:a=>{p||$(a)}}),E&&pe({cwd:n,templatesRoot:O,force:i,silent:p,logOk:a=>{p||d(a)},logWarn:a=>{p||$(a)}}),u){const a=t.join(w,"context-state.json");let o={};try{o=JSON.parse(r.readFileSync(a,"utf8"))}catch{}const f=I(n,{language:F||void 0,framework:_||void 0,projectType:R||void 0});o.stack=f.developmentProfile,r.writeFileSync(a,JSON.stringify(o,null,2)+`
30
+ `,"utf8"),p||d("Created: "+l("inferno/context-state.json"))}if(!p){D("infernoflow initialized!");const a=t.join(w,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!r.existsSync(a)&&(console.log(),console.log(` ${N("\u{1F4A1}")} ${bold("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${l("infernoflow ai setup")} \u2014 takes 60 seconds`)),ie([l("infernoflow status")+" \u2014 see your contract at a glance",l("infernoflow check")+" \u2014 validate everything",(u?"Review inferred baseline in ":"Edit ")+N("inferno/capabilities.json")+(u?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+N("inferno/scenarios/*.json")+" files for edge cases","Add "+l("inferno:check")+" to your CI pipeline",...v?["Restart Cursor \u2014 hooks write assistant text to "+N("inferno/CONTEXT.draft.md"),"Promote when ready: "+l("npm run inferno:promote-draft -- --append-notes")]:[],...E?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+N("inferno/CONTEXT.draft.md"),"Promote when ready: "+l("npm run inferno:promote-draft -- --append-notes")]:[],...!v&&!E?["Optional: "+l("infernoflow install-cursor-hooks")+" or "+l("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Ce as initCommand};
@@ -1,36 +1 @@
1
- import * as path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { header, ok, warn, done, nextSteps, cyan, yellow } from "../ui/output.mjs";
4
- import { installCursorHooksArtifacts } from "../cursorHooksInstall.mjs";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- function getTemplatesRoot() {
9
- return path.resolve(__dirname, "../../templates");
10
- }
11
-
12
- export async function installCursorHooksCommand(args) {
13
- const cwd = process.cwd();
14
- const force = args.includes("--force") || args.includes("-f");
15
-
16
- header("install-cursor-hooks");
17
-
18
- installCursorHooksArtifacts({
19
- cwd,
20
- templatesRoot: getTemplatesRoot(),
21
- force,
22
- silent: false,
23
- logOk: (msg) => ok(msg),
24
- logWarn: (msg) => warn(msg),
25
- });
26
-
27
- done("Cursor draft hooks installed");
28
-
29
- nextSteps([
30
- "Restart Cursor (or reload window) so " + yellow(".cursor/hooks.json") + " is picked up",
31
- "Use Agent chat — each assistant reply appends to " + yellow("inferno/CONTEXT.draft.md") + " (gitignored)",
32
- cyan("npm run inferno:promote-draft") + " — preview draft",
33
- cyan("npm run inferno:promote-draft -- --append-notes") + " — merge into inferno/CONTEXT.md under Decisions",
34
- cyan("npm run inferno:promote-draft -- --clear") + " — discard draft",
35
- ]);
36
- }
1
+ import*as t from"node:path";import{fileURLToPath as i}from"node:url";import{header as d,ok as m,warn as p,done as l,nextSteps as f,cyan as r,yellow as n}from"../ui/output.mjs";import{installCursorHooksArtifacts as c}from"../cursorHooksInstall.mjs";const u=t.dirname(i(import.meta.url));function h(){return t.resolve(u,"../../templates")}async function g(e){const s=process.cwd(),a=e.includes("--force")||e.includes("-f");d("install-cursor-hooks"),c({cwd:s,templatesRoot:h(),force:a,silent:!1,logOk:o=>m(o),logWarn:o=>p(o)}),l("Cursor draft hooks installed"),f(["Restart Cursor (or reload window) so "+n(".cursor/hooks.json")+" is picked up","Use Agent chat \u2014 each assistant reply appends to "+n("inferno/CONTEXT.draft.md")+" (gitignored)",r("npm run inferno:promote-draft")+" \u2014 preview draft",r("npm run inferno:promote-draft -- --append-notes")+" \u2014 merge into inferno/CONTEXT.md under Decisions",r("npm run inferno:promote-draft -- --clear")+" \u2014 discard draft"])}export{g as installCursorHooksCommand};