infernoflow 0.10.13 → 0.10.14

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 (57) hide show
  1. package/dist/bin/infernoflow.mjs +68 -0
  2. package/dist/lib/ai/ideDetection.mjs +1 -0
  3. package/dist/lib/ai/localProvider.mjs +1 -0
  4. package/dist/lib/ai/providerRouter.mjs +1 -0
  5. package/dist/lib/commands/adopt.mjs +20 -0
  6. package/dist/lib/commands/check.mjs +3 -0
  7. package/dist/lib/commands/context.mjs +20 -0
  8. package/dist/lib/commands/docGate.mjs +2 -0
  9. package/dist/lib/commands/implement.mjs +7 -0
  10. package/dist/lib/commands/init.mjs +17 -0
  11. package/dist/lib/commands/installCursorHooks.mjs +1 -0
  12. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -0
  13. package/dist/lib/commands/prImpact.mjs +2 -0
  14. package/dist/lib/commands/run.mjs +10 -0
  15. package/dist/lib/commands/status.mjs +4 -0
  16. package/dist/lib/commands/suggest.mjs +62 -0
  17. package/dist/lib/commands/syncAuto.mjs +1 -0
  18. package/dist/lib/cursorHooksInstall.mjs +1 -0
  19. package/dist/lib/draftToolingInstall.mjs +8 -0
  20. package/dist/lib/ui/output.mjs +6 -0
  21. package/dist/lib/ui/prompts.mjs +6 -0
  22. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -0
  23. package/package.json +48 -44
  24. package/bin/infernoflow.mjs +0 -138
  25. package/lib/ai/ideDetection.mjs +0 -31
  26. package/lib/ai/localProvider.mjs +0 -88
  27. package/lib/ai/providerRouter.mjs +0 -73
  28. package/lib/commands/adopt.mjs +0 -768
  29. package/lib/commands/check.mjs +0 -179
  30. package/lib/commands/context.mjs +0 -164
  31. package/lib/commands/docGate.mjs +0 -81
  32. package/lib/commands/implement.mjs +0 -103
  33. package/lib/commands/init.mjs +0 -401
  34. package/lib/commands/installCursorHooks.mjs +0 -36
  35. package/lib/commands/installVsCodeCopilotHooks.mjs +0 -37
  36. package/lib/commands/prImpact.mjs +0 -157
  37. package/lib/commands/run.mjs +0 -338
  38. package/lib/commands/status.mjs +0 -172
  39. package/lib/commands/suggest.mjs +0 -501
  40. package/lib/commands/syncAuto.mjs +0 -96
  41. package/lib/cursorHooksInstall.mjs +0 -39
  42. package/lib/draftToolingInstall.mjs +0 -69
  43. package/lib/ui/output.mjs +0 -72
  44. package/lib/ui/prompts.mjs +0 -147
  45. package/lib/vsCodeCopilotHooksInstall.mjs +0 -42
  46. /package/{templates → dist/templates}/ci/github-inferno-check.yml +0 -0
  47. /package/{templates → dist/templates}/cursor/hooks/inferno-session-draft.mjs +0 -0
  48. /package/{templates → dist/templates}/cursor/hooks.json +0 -0
  49. /package/{templates → dist/templates}/github-hooks/infernoflow-drafts.json +0 -0
  50. /package/{templates → dist/templates}/inferno/CHANGELOG.md +0 -0
  51. /package/{templates → dist/templates}/inferno/capabilities.json +0 -0
  52. /package/{templates → dist/templates}/inferno/contract.json +0 -0
  53. /package/{templates → dist/templates}/inferno/scenarios/happy_path.json +0 -0
  54. /package/{templates → dist/templates}/scripts/inferno-doc-gate.mjs +0 -0
  55. /package/{templates → dist/templates}/scripts/inferno-install-hooks.mjs +0 -0
  56. /package/{templates → dist/templates}/scripts/inferno-promote-draft.mjs +0 -0
  57. /package/{templates → dist/templates}/scripts/inferno-vscode-copilot-hook.mjs +0 -0
@@ -1,179 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { header, ok, fail, warn, info, section, done, errorAndExit, cyan, bold, red, green, yellow, gray } from "../ui/output.mjs";
4
- import { docGateCommand } from "./docGate.mjs";
5
-
6
- function readJson(filePath, jsonOut = false) {
7
- try { return JSON.parse(fs.readFileSync(filePath, "utf8")); }
8
- catch (err) {
9
- if (jsonOut) {
10
- throw new Error(`Cannot parse ${path.basename(filePath)}`);
11
- }
12
- errorAndExit(
13
- `Cannot parse ${path.basename(filePath)}`,
14
- `Check JSON syntax in: ${filePath}`
15
- );
16
- }
17
- }
18
-
19
- function getScenarioFiles(scenariosDir) {
20
- if (!fs.existsSync(scenariosDir)) return [];
21
- return fs.readdirSync(scenariosDir)
22
- .filter(f => f.endsWith(".json"))
23
- .map(f => path.join(scenariosDir, f));
24
- }
25
-
26
- function getCovered(scenarioFiles) {
27
- const covered = new Set();
28
- for (const f of scenarioFiles) {
29
- try {
30
- const s = JSON.parse(fs.readFileSync(f, "utf8"));
31
- (s.capabilitiesCovered || []).forEach(c => covered.add(c));
32
- } catch {}
33
- }
34
- return covered;
35
- }
36
-
37
- export async function checkCommand(args) {
38
- const cwd = process.cwd();
39
- const infernoDir = path.join(cwd, "inferno");
40
- const skipDocGate = args.includes("--skip-doc-gate");
41
- const jsonOut = args.includes("--json");
42
-
43
- if (!jsonOut) header("check");
44
-
45
- const errors = [];
46
- const warnings = [];
47
-
48
- // ── inferno/ exists ─────────────────────────────────────────────
49
- if (!fs.existsSync(infernoDir)) {
50
- if (jsonOut) { console.log(JSON.stringify({ ok: false, errors: ["inferno/ not found"] })); process.exit(1); }
51
- errorAndExit("inferno/ not found", `Run: infernoflow init`);
52
- }
53
-
54
- // ── contract.json ───────────────────────────────────────────────
55
- const contractPath = path.join(infernoDir, "contract.json");
56
- const capsPath = path.join(infernoDir, "capabilities.json");
57
- const scenariosDir = path.join(infernoDir, "scenarios");
58
- const changelogPath = path.join(infernoDir, "CHANGELOG.md");
59
-
60
- if (!jsonOut) section("Contract");
61
- if (!fs.existsSync(contractPath)) {
62
- fail("contract.json not found", "Run: infernoflow init");
63
- errors.push("contract.json missing");
64
- } else {
65
- let contract;
66
- try {
67
- contract = readJson(contractPath, jsonOut);
68
- } catch (err) {
69
- errors.push(err.message);
70
- if (!jsonOut) {
71
- fail(err.message);
72
- process.exit(1);
73
- }
74
- console.log(JSON.stringify({ ok: false, errors, warnings }, null, 2));
75
- process.exit(1);
76
- }
77
- const caps = contract.capabilities || [];
78
-
79
- if (!contract.policyId) { fail("policyId missing"); errors.push("policyId missing"); }
80
- else if (!jsonOut) ok(`policyId: ${bold(contract.policyId)}`);
81
-
82
- if (!Number.isInteger(contract.policyVersion)) { fail("policyVersion must be an integer"); errors.push("policyVersion invalid"); }
83
- else if (!jsonOut) ok(`policyVersion: ${bold("v" + contract.policyVersion)}`);
84
-
85
- if (caps.length === 0) { fail("capabilities array is empty"); errors.push("no capabilities"); }
86
- else if (!jsonOut) ok(`${caps.length} capabilities declared`);
87
-
88
- // ── capabilities.json ────────────────────────────────────────
89
- if (!jsonOut) section("Capabilities Registry");
90
- if (!fs.existsSync(capsPath)) {
91
- fail("capabilities.json not found"); errors.push("capabilities.json missing");
92
- } else {
93
- let registry;
94
- try {
95
- registry = readJson(capsPath, jsonOut);
96
- } catch (err) {
97
- errors.push(err.message);
98
- if (!jsonOut) {
99
- fail(err.message);
100
- process.exit(1);
101
- }
102
- console.log(JSON.stringify({ ok: false, errors, warnings }, null, 2));
103
- process.exit(1);
104
- }
105
- const registryIds = new Set((registry.capabilities || []).map(c => c?.id).filter(Boolean));
106
-
107
- const missingInRegistry = caps.filter(c => !registryIds.has(c));
108
- if (missingInRegistry.length > 0) {
109
- missingInRegistry.forEach(c => {
110
- if (!jsonOut) fail(`"${c}" in contract but missing from capabilities.json`, `Add it to inferno/capabilities.json`);
111
- errors.push(`"${c}" not registered`);
112
- });
113
- } else if (!jsonOut) {
114
- ok(`All ${registryIds.size} capabilities registered`);
115
- }
116
-
117
- // ── scenarios ───────────────────────────────────────────────
118
- if (!jsonOut) section("Scenarios");
119
- const scenarioFiles = getScenarioFiles(scenariosDir);
120
- if (scenarioFiles.length === 0) {
121
- warn("No scenarios found"); warnings.push("no scenarios");
122
- } else {
123
- const covered = getCovered(scenarioFiles);
124
- const requireCoverage = contract?.rules?.requireScenarioForEachCapability !== false;
125
- const uncovered = caps.filter(c => !covered.has(c));
126
-
127
- if (!jsonOut) ok(`${scenarioFiles.length} scenario file(s) found`);
128
-
129
- if (uncovered.length > 0 && requireCoverage) {
130
- uncovered.forEach(c => {
131
- if (!jsonOut) fail(`"${c}" has no scenario coverage`, `Add to capabilitiesCovered in a scenario file`);
132
- errors.push(`"${c}" uncovered`);
133
- });
134
- } else if (!jsonOut) {
135
- ok(`All capabilities covered by scenarios`);
136
- }
137
- }
138
- }
139
- }
140
-
141
- // ── CHANGELOG ────────────────────────────────────────────────────
142
- if (!jsonOut) section("Changelog");
143
- if (!fs.existsSync(changelogPath)) {
144
- fail("inferno/CHANGELOG.md not found"); errors.push("CHANGELOG missing");
145
- } else {
146
- const txt = fs.readFileSync(changelogPath, "utf8");
147
- if (!/##\s+Unreleased/i.test(txt)) {
148
- fail("Missing '## Unreleased' section", "Add it to inferno/CHANGELOG.md");
149
- errors.push("CHANGELOG missing Unreleased");
150
- } else if (!jsonOut) {
151
- ok("CHANGELOG.md has ## Unreleased section");
152
- }
153
- }
154
-
155
- // ── doc-gate ─────────────────────────────────────────────────────
156
- if (!skipDocGate) {
157
- if (!jsonOut) section("Doc Gate");
158
- await docGateCommand({ silent: jsonOut, captureExit: true }).catch(() => {
159
- errors.push("doc-gate failed");
160
- });
161
- }
162
-
163
- // ── Summary ──────────────────────────────────────────────────────
164
- if (jsonOut) {
165
- console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
166
- if (errors.length > 0) process.exit(1);
167
- return;
168
- }
169
-
170
- console.log();
171
- if (errors.length > 0) {
172
- console.log(" " + red(`✘ check failed — ${errors.length} error(s)\n`));
173
- process.exit(1);
174
- } else if (warnings.length > 0) {
175
- console.log(" " + yellow(`⚠ check passed with ${warnings.length} warning(s)\n`));
176
- } else {
177
- done("check passed — everything is in sync");
178
- }
179
- }
@@ -1,164 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { execSync } from "node:child_process";
4
- import { bold, gray, cyan, red, green, yellow } from "../ui/output.mjs";
5
- import { buildCursorImplementPrompt, buildGenericImplementPrompt } from "../ui/prompts.mjs";
6
-
7
- function copyToClipboard(text) {
8
- try {
9
- const p = process.platform;
10
- if (p === "win32") execSync("clip", { input: text });
11
- else if (p === "darwin") execSync("pbcopy", { input: text });
12
- else { try { execSync("xclip -selection clipboard", { input: text }); } catch { execSync("xsel --clipboard --input", { input: text }); } }
13
- return true;
14
- } catch { return false; }
15
- }
16
-
17
- const INFERNO_DIR = "inferno";
18
- const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
19
- const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
20
-
21
- function readJSON(f) { try { return JSON.parse(fs.readFileSync(f,"utf8")); } catch { return null; } }
22
- function readFile(f) { try { return fs.readFileSync(f,"utf8"); } catch { return null; } }
23
- function loadState() { const r=readFile(STATE_FILE); if(!r) return {}; try { return JSON.parse(r); } catch { return {}; } }
24
- function saveState(s) { fs.writeFileSync(STATE_FILE,JSON.stringify(s,null,2),"utf8"); }
25
- function fmtDate(iso) { if(!iso) return "unknown"; return new Date(iso).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}); }
26
- function parseChangelog(txt,max) {
27
- if(!txt) return [];
28
- const entries=[]; let cur=null;
29
- for(const line of txt.split("\n")) {
30
- if(line.startsWith("## ")) { if(cur&&entries.length<max) entries.push(cur); if(entries.length>=max) break; cur={title:line.replace("## ","").trim(),items:[]}; }
31
- else if(cur&&line.startsWith("- ")) cur.items.push(line.replace("- ","").trim());
32
- }
33
- if(cur&&entries.length<max) entries.push(cur);
34
- return entries.filter(e=>e.items.length>0);
35
- }
36
-
37
- export async function contextCommand(args) {
38
- const has = (f) => args.includes(f);
39
- const flag = (f) => { const i=args.indexOf(f); return i!==-1&&args[i+1]?args[i+1]:null; };
40
-
41
- const intent = flag("--intent") || flag("-i");
42
- const working = flag("--working") || flag("-w");
43
- const decision = flag("--decision") || flag("-d");
44
- const showOnly = has("--show") || has("-s");
45
- const copyFlag = has("--copy") || has("-c");
46
- const cursorFlag = has("--cursor");
47
- const copilotFlag = has("--copilot");
48
- const resetFlag= has("--reset");
49
-
50
- console.log("\n "+bold("��� infernoflow — context"));
51
- console.log(" "+"─".repeat(50)+"\n");
52
-
53
- if(!fs.existsSync(INFERNO_DIR)){
54
- console.error(red(" ✘ inferno/ not found"));
55
- console.error(gray(" → Run: infernoflow init\n"));
56
- process.exit(1);
57
- }
58
-
59
- const contract = readJSON(path.join(INFERNO_DIR,"contract.json"));
60
- const capabilities = readJSON(path.join(INFERNO_DIR,"capabilities.json"));
61
- const changelog = readFile(path.join(INFERNO_DIR,"CHANGELOG.md"));
62
-
63
- if(!contract||!capabilities){
64
- console.error(red(" ✘ Missing contract.json or capabilities.json\n"));
65
- process.exit(1);
66
- }
67
-
68
- let state = loadState();
69
- if(resetFlag){ state={}; console.log(yellow(" ⚠ State reset\n")); }
70
- if(intent) { state.intent=intent; state.intentUpdated=new Date().toISOString(); console.log(green(' ✔ Intent saved: "'+intent+'"')); }
71
- if(working) { state.working=working; state.workingUpdated=new Date().toISOString(); console.log(green(' ✔ Working on: "'+working+'"')); }
72
- if(decision) { if(!state.decisions) state.decisions=[]; state.decisions.push({text:decision,date:new Date().toISOString()}); console.log(green(' ✔ Decision recorded: "'+decision+'"')); }
73
- if(intent||working||decision) saveState(state);
74
-
75
- const capList = capabilities.capabilities||[];
76
- const allInSync = capList.length===(contract.capabilities||[]).length;
77
- const recent = parseChangelog(changelog,3);
78
- const version = String(contract.policyVersion).replace(/^v/i,"");
79
- const now = new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"});
80
- const syncBadge = allInSync?"✓ validated":"⚠ out of sync";
81
- const implementTask = state.intent || "describe the exact task to implement";
82
- const implementInput = { task: implementTask, contract, caps: capabilities, scenarios: [], state };
83
- const cursorPrompt = buildCursorImplementPrompt(implementInput);
84
- const genericPrompt = buildGenericImplementPrompt(implementInput);
85
-
86
- const capLines = capList.map(c=>"- **"+c.id+"** — "+c.title).join("\n");
87
- const chgLines = recent.length>0 ? recent.map(e=>"### "+e.title+"\n"+e.items.map(i=>" - "+i).join("\n")).join("\n\n") : "_No recent changes_";
88
- const intentLine = state.intent ? state.intent+" _("+fmtDate(state.intentUpdated)+")_" : "_Not set — run: infernoflow context --intent \"...\"_";
89
- const workingLine = state.working ? state.working+" _("+fmtDate(state.workingUpdated)+")_" : "_Not set — run: infernoflow context --working \"...\"_";
90
- const decLines = state.decisions&&state.decisions.length>0 ? state.decisions.slice(-5).map(d=>"- "+d.text+" _("+fmtDate(d.date)+")_").join("\n") : "_No decisions recorded_";
91
-
92
- const md = [
93
- "# Project Context — "+contract.policyId+" v"+version,
94
- "> Generated by infernoflow | "+now+" | "+syncBadge,
95
- "","---","",
96
- "## What this system does","",capLines,"","---","",
97
- "## Recent changes","",chgLines,"","---","",
98
- "## Current state","",
99
- "- **Capabilities:** "+capList.length,
100
- "- **Version:** v"+version,
101
- "- **Sync:** "+syncBadge,
102
- "","---","",
103
- "## What I am working on right now","",workingLine,"","---","",
104
- "## Intent — what I want to build next","",intentLine,"","---","",
105
- "## Decisions & notes","",decLines,"","---",
106
- "",
107
- "## Implementation Prompt Seed","",
108
- "Use this to start coding immediately with an agent:","",
109
- "```bash",
110
- `infernoflow implement "${implementTask}" --mode both`,
111
- "```",
112
- "",
113
- "### Cursor Agent Prompt","",
114
- "```text",
115
- cursorPrompt,
116
- "```",
117
- "",
118
- "### Generic Agent Prompt","",
119
- "```text",
120
- genericPrompt,
121
- "```",
122
- "",
123
- "---",
124
- "_Paste this block at the start of any new AI session._"
125
- ].join("\n");
126
-
127
- if(!showOnly){ fs.writeFileSync(CONTEXT_FILE,md,"utf8"); console.log(green("\n ✔ Context written → "+CONTEXT_FILE)); }
128
-
129
- if(copyFlag){
130
- const ok=copyToClipboard(md);
131
- console.log(ok ? green(" ✔ Copied to clipboard — paste with Ctrl+V") : yellow(" ⚠ Clipboard copy failed — open inferno/CONTEXT.md manually"));
132
- }
133
-
134
- if (cursorFlag) {
135
- fs.writeFileSync(".cursorrules", md, "utf8");
136
- console.log(green(" ✔ Written to .cursorrules — Cursor loads this automatically"));
137
- }
138
- if (copilotFlag) {
139
- if (!fs.existsSync(".github")) fs.mkdirSync(".github");
140
- fs.writeFileSync(".github/copilot-instructions.md", md, "utf8");
141
- console.log(green(" ✔ Written to .github/copilot-instructions.md — Copilot loads this automatically"));
142
- }
143
- console.log("\n "+bold("Context Summary"));
144
- console.log(" "+"─".repeat(50));
145
- console.log(" Project "+contract.policyId+" — v"+version);
146
- console.log(" Capabilities "+capList.length+" registered");
147
- console.log(" Sync "+(allInSync?green("✓ in sync"):yellow("⚠ check needed")));
148
- console.log(" Working on "+(state.working?cyan(state.working):gray("not set")));
149
- console.log(" Intent "+(state.intent ?cyan(state.intent) :gray("not set")));
150
- console.log(" Decisions "+(state.decisions?state.decisions.length:0)+" recorded\n");
151
- console.log(" "+bold("Implementation Prompt"));
152
- console.log(" "+cyan("→")+" Run "+cyan(`infernoflow implement "${implementTask}" --mode both`)+"\n");
153
-
154
- if(copyFlag){
155
- console.log(" "+bold("Ready to use:"));
156
- console.log(" "+cyan("→")+" Paste into Claude / Cursor / Copilot with "+cyan("Ctrl+V")+"\n");
157
- } else {
158
- console.log(" "+bold("Ready to use:"));
159
- console.log(" "+cyan("1.")+" Open "+cyan("inferno/CONTEXT.md"));
160
- console.log(" "+cyan("2.")+" Copy everything");
161
- console.log(" "+cyan("3.")+" Paste at the start of your next AI session");
162
- console.log(" "+gray(" tip: use --copy to skip steps 1-2 automatically")+"\n");
163
- }
164
- }
@@ -1,81 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { ok, fail, warn, info, gray } from "../ui/output.mjs";
3
-
4
- function sh(cmd) {
5
- return execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] }).toString("utf8").trim();
6
- }
7
-
8
- const CODE_PREFIXES = [
9
- "src/", "frontend/", "backend/",
10
- "app/", "pages/", "components/",
11
- "Controllers/", "Services/", "Endpoints/",
12
- "lib/", "api/", "server/"
13
- ];
14
-
15
- export async function docGateCommand(opts = {}) {
16
- const fromArgs = Array.isArray(opts);
17
- const silent = fromArgs ? false : (opts?.silent || false);
18
- const captureExit = fromArgs ? false : (opts?.captureExit || false);
19
- const jsonOut = fromArgs ? opts.includes("--json") : Boolean(opts?.json);
20
- const base = process.env.BASE_SHA || "HEAD~1";
21
- const head = process.env.HEAD_SHA || "HEAD";
22
-
23
- let files = [];
24
- try {
25
- const out = sh(`git diff --name-only ${base}..${head}`);
26
- files = out ? out.split("\n").filter(Boolean) : [];
27
- } catch {
28
- if (jsonOut) {
29
- console.log(JSON.stringify({ ok: true, skipped: true, reason: "no_git_available" }, null, 2));
30
- return;
31
- }
32
- if (!silent) info(gray("doc-gate skipped (no git available)"));
33
- return;
34
- }
35
-
36
- if (files.length === 0) {
37
- if (jsonOut) {
38
- console.log(JSON.stringify({ ok: true, changedFiles: 0, changedCode: false, changedInferno: false }, null, 2));
39
- return;
40
- }
41
- if (!silent) ok("doc-gate: no changed files");
42
- return;
43
- }
44
-
45
- const changedCode = files.some(f =>
46
- CODE_PREFIXES.some(p => f.startsWith(p) || f.includes("/" + p))
47
- );
48
- const changedInferno = files.some(f => f.startsWith("inferno/"));
49
- const codeFiles = files.filter(f => CODE_PREFIXES.some(p => f.startsWith(p))).slice(0, 5);
50
-
51
- if (jsonOut) {
52
- const payload = {
53
- ok: !(changedCode && !changedInferno),
54
- changedFiles: files.length,
55
- changedCode,
56
- changedInferno,
57
- sampleCodeFiles: codeFiles,
58
- hint: changedCode && !changedInferno ? "Update at least one file in inferno/ before committing" : null,
59
- };
60
- console.log(JSON.stringify(payload, null, 2));
61
- if (!payload.ok) process.exit(1);
62
- return;
63
- }
64
-
65
- if (changedCode && !changedInferno) {
66
- if (!silent) {
67
- fail(
68
- "Code changed but inferno/ was NOT updated",
69
- "Update at least one file in inferno/ before committing"
70
- );
71
- if (codeFiles.length) {
72
- console.log();
73
- codeFiles.forEach(f => console.log(" " + gray("• " + f)));
74
- }
75
- }
76
- if (captureExit) throw new Error("doc-gate failed");
77
- process.exit(1);
78
- }
79
-
80
- if (!silent) ok("doc-gate: docs are up to date");
81
- }
@@ -1,103 +0,0 @@
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
- }