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.
- package/dist/bin/infernoflow.mjs +68 -0
- package/dist/lib/ai/ideDetection.mjs +1 -0
- package/dist/lib/ai/localProvider.mjs +1 -0
- package/dist/lib/ai/providerRouter.mjs +1 -0
- package/dist/lib/commands/adopt.mjs +20 -0
- package/dist/lib/commands/check.mjs +3 -0
- package/dist/lib/commands/context.mjs +20 -0
- package/dist/lib/commands/docGate.mjs +2 -0
- package/dist/lib/commands/implement.mjs +7 -0
- package/dist/lib/commands/init.mjs +17 -0
- package/dist/lib/commands/installCursorHooks.mjs +1 -0
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -0
- package/dist/lib/commands/prImpact.mjs +2 -0
- package/dist/lib/commands/run.mjs +10 -0
- package/dist/lib/commands/status.mjs +4 -0
- package/dist/lib/commands/suggest.mjs +62 -0
- package/dist/lib/commands/syncAuto.mjs +1 -0
- package/dist/lib/cursorHooksInstall.mjs +1 -0
- package/dist/lib/draftToolingInstall.mjs +8 -0
- package/dist/lib/ui/output.mjs +6 -0
- package/dist/lib/ui/prompts.mjs +6 -0
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -0
- package/package.json +48 -44
- package/bin/infernoflow.mjs +0 -138
- package/lib/ai/ideDetection.mjs +0 -31
- package/lib/ai/localProvider.mjs +0 -88
- package/lib/ai/providerRouter.mjs +0 -73
- package/lib/commands/adopt.mjs +0 -768
- package/lib/commands/check.mjs +0 -179
- package/lib/commands/context.mjs +0 -164
- package/lib/commands/docGate.mjs +0 -81
- package/lib/commands/implement.mjs +0 -103
- package/lib/commands/init.mjs +0 -401
- package/lib/commands/installCursorHooks.mjs +0 -36
- package/lib/commands/installVsCodeCopilotHooks.mjs +0 -37
- package/lib/commands/prImpact.mjs +0 -157
- package/lib/commands/run.mjs +0 -338
- package/lib/commands/status.mjs +0 -172
- package/lib/commands/suggest.mjs +0 -501
- package/lib/commands/syncAuto.mjs +0 -96
- package/lib/cursorHooksInstall.mjs +0 -39
- package/lib/draftToolingInstall.mjs +0 -69
- package/lib/ui/output.mjs +0 -72
- package/lib/ui/prompts.mjs +0 -147
- package/lib/vsCodeCopilotHooksInstall.mjs +0 -42
- /package/{templates → dist/templates}/ci/github-inferno-check.yml +0 -0
- /package/{templates → dist/templates}/cursor/hooks/inferno-session-draft.mjs +0 -0
- /package/{templates → dist/templates}/cursor/hooks.json +0 -0
- /package/{templates → dist/templates}/github-hooks/infernoflow-drafts.json +0 -0
- /package/{templates → dist/templates}/inferno/CHANGELOG.md +0 -0
- /package/{templates → dist/templates}/inferno/capabilities.json +0 -0
- /package/{templates → dist/templates}/inferno/contract.json +0 -0
- /package/{templates → dist/templates}/inferno/scenarios/happy_path.json +0 -0
- /package/{templates → dist/templates}/scripts/inferno-doc-gate.mjs +0 -0
- /package/{templates → dist/templates}/scripts/inferno-install-hooks.mjs +0 -0
- /package/{templates → dist/templates}/scripts/inferno-promote-draft.mjs +0 -0
- /package/{templates → dist/templates}/scripts/inferno-vscode-copilot-hook.mjs +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync as d}from"node:fs";import{dirname as m,join as f}from"node:path";import{fileURLToPath as u}from"node:url";import{bold as e,gray as t,red as r}from"../lib/ui/output.mjs";const h=m(u(import.meta.url)),y=JSON.parse(d(f(h,"..","package.json"),"utf8")),i=y.version||"0.0.0",s={init:"Scaffold inferno/ in your project (or adopt existing project)","install-cursor-hooks":"Install Cursor hooks: draft agent replies to inferno/CONTEXT.draft.md","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview): draft to inferno/CONTEXT.draft.md",check:"Validate contract, capabilities, scenarios, changelog",status:"Show contract health at a glance","pr-impact":"Summarize PR impact on capabilities and docs",sync:"Run deterministic inferno sync flow",run:"One-command detect/propose/apply/validate flow","doc-gate":"Fail if code changed but docs were not updated",suggest:"Generate AI prompt + apply capability updates",implement:"Generate code-agent implementation prompt(s)",context:"Generate AI-ready context for new sessions"},c={init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),"pr-impact":async o=>(await import("../lib/commands/prImpact.mjs")).prImpactCommand(o),sync:async o=>(await import("../lib/commands/syncAuto.mjs")).syncCommand(o),run:async o=>(await import("../lib/commands/run.mjs")).runCommand(o),suggest:async o=>(await import("../lib/commands/suggest.mjs")).suggestCommand(o),implement:async o=>(await import("../lib/commands/implement.mjs")).implementCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"doc-gate":async o=>(await import("../lib/commands/docGate.mjs")).docGateCommand(o)};function g(){const o=Object.keys(s),l=Math.max(...o.map(a=>a.length),8)+1;return Object.entries(s).map(([a,p])=>` ${a.padEnd(l," ")}${p}`).join(`
|
|
3
|
+
`)}const w=`
|
|
4
|
+
${e("\u{1F525} infernoflow")} ${t("v"+i)}
|
|
5
|
+
${t("The forge for liquid code \u2014 keep every AI session in sync")}
|
|
6
|
+
|
|
7
|
+
${e("Usage:")}
|
|
8
|
+
infernoflow <command> [options]
|
|
9
|
+
|
|
10
|
+
${e("Commands:")}
|
|
11
|
+
${g()}
|
|
12
|
+
|
|
13
|
+
${e("init options:")}
|
|
14
|
+
--cursor-hooks Also install Cursor hooks (draft \u2192 inferno/CONTEXT.draft.md)
|
|
15
|
+
--vscode-copilot-hooks Also install VS Code + Copilot hooks (.github/hooks \u2014 Preview)
|
|
16
|
+
--adopt Infer capabilities from an existing codebase
|
|
17
|
+
--lang <name> Override detected language (e.g. ts, js, py)
|
|
18
|
+
--framework <name> Override detected framework (e.g. react, angular, express)
|
|
19
|
+
--project-type <t> Override project type (frontend|backend|fullstack|cli|library)
|
|
20
|
+
--report-json Print inferred adoption report as JSON
|
|
21
|
+
--report-json-only Print JSON report only (no human-readable logs)
|
|
22
|
+
--report-human-only Print only human-readable adoption report (no JSON block)
|
|
23
|
+
--yes, -y Skip prompts and accept inferred/default values
|
|
24
|
+
--force, -f Overwrite existing inferno/ files
|
|
25
|
+
|
|
26
|
+
${e("install-cursor-hooks options:")}
|
|
27
|
+
--force, -f Overwrite .cursor/hooks.json and hook scripts if they exist
|
|
28
|
+
|
|
29
|
+
${e("install-vscode-copilot-hooks options:")}
|
|
30
|
+
--force, -f Overwrite .github/hooks/infernoflow-drafts.json and scripts if they exist
|
|
31
|
+
|
|
32
|
+
${e("context options:")}
|
|
33
|
+
--intent "..." What you plan to build next
|
|
34
|
+
--working "..." What you are building right now
|
|
35
|
+
--decision "..." Record a decision or note
|
|
36
|
+
--show Print context without writing file
|
|
37
|
+
--copy, -c Copy context to clipboard instantly
|
|
38
|
+
--reset Clear all stored state
|
|
39
|
+
|
|
40
|
+
${e("implement options:")}
|
|
41
|
+
--mode <type> cursor | generic | both (default: both)
|
|
42
|
+
--copy, -c Copy generated prompt(s) to clipboard
|
|
43
|
+
|
|
44
|
+
${e("run options:")}
|
|
45
|
+
--dry-run Execute full flow without writing files
|
|
46
|
+
--json Emit machine-readable events and result payload
|
|
47
|
+
--no-rollback Keep changes even if validation fails
|
|
48
|
+
--provider <type> auto | agent | local | prompt (default: auto)
|
|
49
|
+
--ide <name> auto | cursor | vscode | windsurf (default: auto)
|
|
50
|
+
|
|
51
|
+
${e("Typical workflow:")}
|
|
52
|
+
${t('1. infernoflow context --intent "what I want to build"')}
|
|
53
|
+
${t("2. [paste inferno/CONTEXT.md into Claude / Cursor / Copilot]")}
|
|
54
|
+
${t("3. [build the feature]")}
|
|
55
|
+
${t('4. infernoflow suggest "what I built"')}
|
|
56
|
+
${t("5. infernoflow check")}
|
|
57
|
+
|
|
58
|
+
${e("Machine output:")}
|
|
59
|
+
${t("status --json")}
|
|
60
|
+
${t("check --json")}
|
|
61
|
+
${t("doc-gate --json")}
|
|
62
|
+
${t("pr-impact --json")}
|
|
63
|
+
${t("sync --auto --json")}
|
|
64
|
+
${t('run "task" --json')}
|
|
65
|
+
`,[,,n,...k]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(w),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(i),process.exit(0));const C=Object.keys(c);C.includes(n)||(console.error(r(`
|
|
66
|
+
Unknown command: ${n}`)),console.error(t(`Run: infernoflow --help
|
|
67
|
+
`)),process.exit(1));const b=[n,...k];c[n](b).catch(o=>{console.error(r(`
|
|
68
|
+
Error: `)+o.message),process.exit(1)});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function A(_="auto"){const e=process.env,n=String(_||"auto").toLowerCase(),u=!!(e.CURSOR_TRACE_ID||e.CURSOR_AGENT||e.CURSOR_SESSION_ID||(e.VSCODE_GIT_ASKPASS_NODE||"").toLowerCase().includes("cursor")||(e.VSCODE_GIT_ASKPASS_MAIN||"").toLowerCase().includes("cursor")),E=!!(e.VSCODE_PID||e.VSCODE_CWD||e.GITHUB_COPILOT_AGENT),c=!!(e.WINDSURF||e.CODEIUM||e.WINDSURF_SESSION_ID);let s="unknown";u?s="cursor":E?s="vscode":c&&(s="windsurf"),n!=="auto"&&["cursor","vscode","windsurf"].includes(n)&&(s=n);const t=e.INFERNO_AGENT_AVAILABLE,r=t!=null?t==="1"||t==="true":s!=="unknown",o=[];return s!=="unknown"?o.push(`IDE_${s.toUpperCase()}_DETECTED`):o.push("IDE_UNKNOWN"),r?o.push("IDE_AGENT_AVAILABLE"):o.push("IDE_AGENT_UNAVAILABLE"),{ideDetected:s,agentAvailable:r,reasonCodes:o}}export{A as detectIdeContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const E=45e3;function _(e){const t=new AbortController,n=setTimeout(()=>t.abort(),e);return{controller:t,timer:n}}async function p(e,t){const n=process.env.INFERNO_LOCAL_ENDPOINT||"http://127.0.0.1:11434/api/generate",s=process.env.INFERNO_LOCAL_MODEL||"llama3.1:8b",{controller:a,timer:c}=_(t);try{const r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},signal:a.signal,body:JSON.stringify({model:s,prompt:e,stream:!1})});if(!r.ok){const l=await r.text();throw new Error(`local_model_http_${r.status}: ${l.slice(0,240)}`)}const o=await r.json();if(!o?.response||typeof o.response!="string")throw new Error("local_model_invalid_response");return o.response.trim()}finally{clearTimeout(c)}}async function m(e,t){const n=process.env.INFERNO_LOCAL_ENDPOINT||"http://127.0.0.1:1234/v1/chat/completions",s=process.env.INFERNO_LOCAL_MODEL||"local-model",a=process.env.INFERNO_LOCAL_API_KEY||"local",{controller:c,timer:r}=_(t);try{const o=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},signal:c.signal,body:JSON.stringify({model:s,temperature:.1,messages:[{role:"system",content:"Return JSON only."},{role:"user",content:e}]})});if(!o.ok){const O=await o.text();throw new Error(`local_model_http_${o.status}: ${O.slice(0,240)}`)}const i=(await o.json())?.choices?.[0]?.message?.content;if(!i||typeof i!="string")throw new Error("local_model_invalid_response");return i.trim()}finally{clearTimeout(r)}}async function N(e,t={}){if(process.env.INFERNO_LOCAL_MOCK_RESPONSE)return process.env.INFERNO_LOCAL_MOCK_RESPONSE;const n=(process.env.INFERNO_LOCAL_PROVIDER||"ollama").toLowerCase(),s=Number(t.timeoutMs||process.env.INFERNO_LOCAL_TIMEOUT_MS||45e3);return n==="openai"?m(e,s):p(e,s)}export{N as generateWithLocalModel};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{detectIdeContext as r}from"./ideDetection.mjs";async function l(d="auto",i="auto"){const a=String(d||"auto").toLowerCase(),e=r(i),t=[...e.reasonCodes];return a==="local"?(t.push("LOCAL_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"local",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="prompt"?(t.push("PROMPT_PROVIDER_SELECTED"),{providerRequested:a,providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):a==="agent"?e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:a,providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("EXPLICIT_AGENT_REQUIRED"),{providerRequested:a,providerResolved:"none",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t,error:"agent_unavailable"}):e.agentAvailable?(t.push("IDE_AGENT_SELECTED"),{providerRequested:"auto",providerResolved:"agent",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t}):(t.push("FALLBACK_PROMPT_MODE"),{providerRequested:"auto",providerResolved:"prompt",ideDetected:e.ideDetected,agentAvailable:e.agentAvailable,reasonCodes:t})}export{l as resolveProvider};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import*as C from"node:fs";import*as b from"node:path";import*as D from"node:readline";function M(t){return t.replace(/[^a-zA-Z0-9]+/g," ").trim().split(/\s+/).filter(Boolean).map(l=>l[0].toUpperCase()+l.slice(1)).join("")}function z(t){return t.replace(/([A-Z])/g," $1").trim()}function S(t){try{return C.readFileSync(t,"utf8")}catch{return""}}const R=[{id:"CreateItem",title:"Create Item",regex:/\b(post|create|add)\b/i},{id:"ReadItems",title:"Read Items",regex:/\b(get|read|list|fetch)\b/i},{id:"UpdateItem",title:"Update Item",regex:/\b(put|patch|update|edit)\b/i},{id:"DeleteItem",title:"Delete Item",regex:/\b(delete|remove)\b/i},{id:"SearchItems",title:"Search Items",regex:/\bsearch\b/i},{id:"FilterItems",title:"Filter Items",regex:/\bfilter\b/i},{id:"SetDueDate",title:"Set Due Date",regex:/\bdueDate|deadline|due\b/i},{id:"SetPriority",title:"Set Priority",regex:/\bpriority\b/i},{id:"ToggleComplete",title:"Toggle Complete",regex:/\bcomplete|completed|toggle\b/i},{id:"ClearCompleted",title:"Clear Completed",regex:/\bclearCompleted|clear completed\b/i}];function K(t){return J(t).capabilities}function U(t){const l=[],r=["src","server","app","backend","frontend","api","Controllers"];for(const i of r){const n=b.join(t,i);if(!C.existsSync(n))continue;const e=[n];for(;e.length;){const c=e.pop();for(const s of C.readdirSync(c,{withFileTypes:!0})){const o=b.join(c,s.name);if(s.isDirectory()){if(["node_modules",".git","dist","build"].includes(s.name))continue;e.push(o)}else/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm|cs|csproj)$/.test(s.name)&&l.push(o)}}}for(const i of C.readdirSync(t,{withFileTypes:!0}))i.isFile()&&/^(Program\.cs|.+\.csproj)$/i.test(i.name)&&l.push(b.join(t,i.name));return l}function O(t,l){const r=new Set;for(const i of t){const n=b.relative(l,i),e=S(i),c=e.matchAll(/\bclass\s+([A-Z][A-Za-z0-9_]*?(?:Component|Page|View|Widget|Card))\b/g);for(const A of c)r.add(A[1]);const s=e.matchAll(/\bselector\s*:\s*["']([^"']+)["']/g);for(const A of s)r.add(A[1]);const o=e.matchAll(/\bfunction\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);for(const A of o)/component|page|view|card|chart|dashboard/i.test(A[1])&&r.add(A[1]);const p=n.match(/([^/\\]+)\.(component|page|view|widget|card)\.(ts|tsx|js|jsx)$/i);p&&r.add(p[1])}return Array.from(r).sort()}function H(t){const l=new Set,r=new Set,i=new Set(["if","for","while","const","let","var","return","function","class","import","export","null","undefined","true","false","string","number","boolean","any","unknown","never","selector","templateUrl","styleUrl","standalone","imports","providers","providedIn","options","scales","responsive","display","title","type","label","component","service","routes","appConfig","ApplicationConfig"]),n=e=>{e&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(e)&&(e.length<=1||i.has(e)||/^[A-Z0-9_]+$/.test(e)||l.add(e))};for(const e of t){const c=S(e);if(/\.(html|htm)$/i.test(e)){const s=c.matchAll(/\{\{\s*(?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const f of s)n(f[1]);const o=c.matchAll(/\[\(ngModel\)\]\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const f of o)n(f[1]);const p=c.matchAll(/\[[a-zA-Z0-9_-]+\]\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const f of p)n(f[1]);const A=c.matchAll(/\*ngIf\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const f of A)n(f[1])}if(/\.(ts|tsx|js|jsx|mjs|cjs)$/i.test(e)){const s=c.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:async\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/g);for(const d of s)r.add(d[1]);const o=c.matchAll(/\bthis\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g);for(const d of o)n(d[1]);const p=c.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:readonly\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::|=)/g);for(const d of p)n(d[1]);const A=c.matchAll(/@Input\([^)]*\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*[:=]/g);for(const d of A)n(d[1]);const f=c.matchAll(/forEach\(\((\w+)\)\s*=>/g);for(const d of f){const w=d[1],j=new RegExp(`\\b${w}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\b`,"g");for(const g of c.matchAll(j))n(g[1])}}}return Array.from(l).filter(e=>!r.has(e)).sort().slice(0,80)}function W(t){const l=new Set,r=b.join(t,"package.json");if(!C.existsSync(r))return[];try{const i=JSON.parse(S(r)||"{}"),n={...i.dependencies||{},...i.devDependencies||{}};for(const e of Object.keys(n))l.add(e)}catch{}return Array.from(l).sort()}function G(t,l,r){const i=l.filter(s=>/\.(css|scss|sass|less|styl)$/i.test(s)).map(s=>b.relative(t,s)).sort(),n=[],e=s=>r.includes(s);e("tailwindcss")&&n.push("Tailwind CSS"),e("bootstrap")&&n.push("Bootstrap"),r.some(s=>s.startsWith("@angular/material"))&&n.push("Angular Material"),e("antd")&&n.push("Ant Design"),e("styled-components")&&n.push("styled-components"),(e("emotion")||e("@emotion/react"))&&n.push("Emotion");const c=new Set;for(const s of l){if(!/\.(css|scss|sass|less|styl|html|htm|ts|tsx|js|jsx|mjs|cjs)$/i.test(s))continue;const o=S(s);for(const p of o.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)/g))c.add(`--${p[1]}`)}return{cssFrameworks:n,styleFileCount:i.length,styleFilesSample:i.slice(0,12),designTokens:Array.from(c).sort().slice(0,24)}}function N(t){let l=!1,r=!1;const i=new Set;for(const e of t){if(!/\.(html|htm|tsx|jsx|ts|js|mjs|cjs)$/i.test(e))continue;const c=S(e);/\bgrid\b|grid-template|grid-cols-|display:\s*grid/i.test(c)&&(l=!0),/\bflex\b|display:\s*flex|flex-row|flex-col|justify-|items-/i.test(c)&&(r=!0);for(const s of c.matchAll(/<(main|header|footer|section|aside|nav)\b/gi))i.add(s[1].toLowerCase());for(const s of c.matchAll(/class(?:Name)?\s*=\s*["'`][^"'`]*(dashboard|chart|card|sidebar|content|toolbar|filter|panel|table)[^"'`]*["'`]/gi)){const o=s[1].toLowerCase();i.add(o==="filter"?"filters":o)}}return{layoutType:l&&r?"mixed":l?"grid":r?"flex":"unknown",usesGrid:l,usesFlex:r,sections:Array.from(i).sort()}}function B(t,l,r,i={}){const n={ts:0,js:0,py:0,java:0,go:0,rb:0,rs:0,cs:0,php:0};for(const g of l){const y=b.extname(g).toLowerCase();(y===".ts"||y===".tsx")&&(n.ts+=1),(y===".js"||y===".jsx"||y===".mjs"||y===".cjs")&&(n.js+=1),y===".py"&&(n.py+=1),y===".java"&&(n.java+=1),y===".go"&&(n.go+=1),y===".rb"&&(n.rb+=1),y===".rs"&&(n.rs+=1),y===".cs"&&(n.cs+=1),y===".php"&&(n.php+=1)}const e=Object.entries(n).sort((g,y)=>y[1]-g[1]),c=e[0]?.[1]>0?e[0][0]:"unknown";let s="unknown",o=!1,p=!1,A=!1;for(const g of l){const y=b.basename(g).toLowerCase();if(y.endsWith(".csproj")){const k=S(g);/Microsoft\.NET\.Sdk\.Web/i.test(k)&&(o=!0),(/Blazor/i.test(k)||/Microsoft\.AspNetCore\.Components/i.test(k))&&(A=!0)}if(y==="program.cs"){const k=S(g);/app\.Map(Get|Post|Put|Delete|Patch)\s*\(/i.test(k)&&(p=!0)}}const f=g=>r.includes(g);r.some(g=>g.startsWith("@angular/"))?s="angular":f("react")?s="react":f("vue")?s="vue":f("svelte")?s="svelte":f("next")?s="nextjs":f("nuxt")?s="nuxt":f("express")?s="express":f("@nestjs/core")?s="nestjs":f("fastify")?s="fastify":f("flask")?s="flask":f("django")?s="django":f("spring-boot")?s="spring":A?s="blazor":p?s="minimalapi":(o||n.cs>0)&&(s="aspnet");let d="fullstack";const w=["src","frontend","app"].some(g=>C.existsSync(b.join(t,g))),j=["server","backend","api"].some(g=>C.existsSync(b.join(t,g)));return["react","angular","vue","svelte","nextjs","nuxt"].includes(s)&&(d="frontend"),["express","nestjs","fastify","flask","django","spring","aspnet","minimalapi"].includes(s)&&(d="backend"),w&&j&&(d="fullstack"),!w&&!j&&(d="library"),s==="blazor"&&(d="frontend"),{language:i.language||c,framework:i.framework||s,projectType:i.projectType||d,detected:{language:c,framework:s,projectType:d}}}function q(t,l){const r=[],i=new Set,n=s=>{let o=String(s||"").trim();return o?(o=o.replace(/https?:\/\/[^/]+/gi,""),o=o.replace(/\$\{[^}]+\}/g,"{var}"),o=o.replace(/\{[A-Za-z_][A-Za-z0-9_]*\}/g,"{var}"),o=o.replace(/:[A-Za-z_][A-Za-z0-9_]*/g,"{var}"),o=o.replace(/\/\d+(?=\/|$)/g,"/{id}"),o=o.replace(/=[^&\s]+/g,"={value}"),o=o.replace(/\/+/g,"/"),o):""},e=s=>{const o=n(s.endpointPattern);if(!o)return;const p=`${s.method}|${o}|${s.sourceFile}|${s.style}`;i.has(p)||(i.add(p),r.push({...s,endpointPattern:o}))};for(const s of l){if(!/\.(ts|tsx|js|jsx|mjs|cjs|cs)$/i.test(s))continue;const o=b.relative(t,s),p=S(s);if(!(/service|api|client|controller|program\.cs/i.test(o)||/HttpClient|fetch\(|app\.Map(Get|Post|Put|Delete|Patch)\(/i.test(p)))continue;const f=p.replace(/\r\n/g,`
|
|
2
|
+
`),d={},w=u=>{let a=String(u||"").trim();if(!a)return"";for(a=a.replace(/;+$/,"").trim(),a=a.replace(/\(\s*$/,"");a.startsWith("'")&&a.endsWith("'")||a.startsWith('"')&&a.endsWith('"')||a.startsWith("`")&&a.endsWith("`");)a=a.slice(1,-1).trim();return a},j=u=>{const a=String(u||"").trim();return a?!!(/^https?:\/\//i.test(a)||a.startsWith("/")||/\bapi\b/i.test(a)||/\$\{[^}]+\}/.test(a)||/\?[^=\s]+=?/.test(a)||/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./${}-]+$/.test(a)):!1},g=(u,a)=>{!u||!a||(d[u]=w(a))},y=/(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;for(const u of f.matchAll(y))g(u[1],u[2]);const k=/(?:public|private|protected)?\s*(?:readonly\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;for(const u of f.matchAll(k))g(u[1],u[2]);const F=u=>{const a=w(u);if(!a)return"";if(/^['"`][\s\S]*['"`]$/.test(a))return a.replace(/^['"`]|['"`]$/g,"");if(d[a]&&j(d[a]))return d[a];const m=a.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);if(m&&d[m[1]]&&j(d[m[1]]))return d[m[1]];const $=a.split("+").map(h=>h.trim()).filter(Boolean);if($.length>1){const h=$.map(x=>{if(/^['"`][\s\S]*['"`]$/.test(x))return x.replace(/^['"`]|['"`]$/g,"");if(d[x]&&j(d[x]))return d[x];const v=x.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);return v&&d[v[1]]&&j(d[v[1]])?d[v[1]]:`{${x}}`}).join("");if(h)return h}const P=a.match(/^(.+?)\?(.+?):(.+)$/s);if(P){const h=F(P[2]),x=F(P[3]);if(h||x)return`${h||"{optionA}"} | ${x||"{optionB}"}`}return a},Z=/\.\s*(get|post|put|patch|delete)\s*(?:<[\s\S]*?>)?\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of f.matchAll(Z)){const a=u[1].toUpperCase(),m=F(u[2]);!m||!j(m)||e({method:a,endpointPattern:m,style:"httpClient",sourceFile:o})}const _=/\bfetch\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of f.matchAll(_)){const a=F(u[1]);if(!a||!j(a))continue;const m=u.index||0,$=f.slice(m,m+260),h=(/method\s*:\s*["'](GET|POST|PUT|PATCH|DELETE)["']/i.exec($)?.[1]||"GET").toUpperCase();e({method:h,endpointPattern:a,style:"fetch",sourceFile:o})}const I=/\baxios\.(get|post|put|patch|delete)\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of f.matchAll(I)){const a=u[1].toUpperCase(),m=F(u[2]);!m||!j(m)||e({method:a,endpointPattern:m,style:"axios",sourceFile:o})}const T=/\baxios\s*\(\s*\{([\s\S]*?)\}\s*\)/gi;for(const u of f.matchAll(T)){const a=u[1],m=/\bmethod\s*:\s*["']?(get|post|put|patch|delete)["']?/i.exec(a),$=/\burl\s*:\s*([^,\n]+)/i.exec(a),P=(m?.[1]||"get").toUpperCase(),h=F($?.[1]||"");!h||!j(h)||e({method:P,endpointPattern:h,style:"axios-config",sourceFile:o})}const E=/\.\s*request\s*\(\s*["'](GET|POST|PUT|PATCH|DELETE)["']\s*,\s*([\s\S]*?)(?:,|\))/gi;for(const u of f.matchAll(E)){const a=u[1].toUpperCase(),m=F(u[2]);!m||!j(m)||e({method:a,endpointPattern:m,style:"request",sourceFile:o})}if(/\.cs$/i.test(s)){const u=/\bapp\.Map(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"/gi;for(const h of f.matchAll(u))e({method:h[1].toUpperCase(),endpointPattern:h[2],style:"csharp-map",sourceFile:o});const a=/\[Route\("([^"]+)"\)\][\s\S]*?class\s+\w+/i.exec(f),m=a?a[1]:"",$=/\[(HttpGet|HttpPost|HttpPut|HttpDelete|HttpPatch)(?:\("([^"]*)"\))?\]/gi;for(const h of f.matchAll($)){const x=h[1].replace("Http","").toUpperCase(),v=h[2]||"",L=[m,v].filter(Boolean).join("/").replace(/\/+/g,"/").replace(/\[controller\]/gi,"{controller}");e({method:x,endpointPattern:L||m||"{controller-route}",style:"csharp-controller",sourceFile:o})}const P=/\b(GetAsync|PostAsync|PutAsync|DeleteAsync|SendAsync)\s*\(\s*"([^"]+)"/gi;for(const h of f.matchAll(P)){const x=h[1].replace("Async","").replace("Send","SEND").toUpperCase();e({method:x,endpointPattern:h[2],style:"csharp-httpclient",sourceFile:o})}}}const c=r.reduce((s,o)=>(s[o.method]=(s[o.method]||0)+1,s),{});return{totalCalls:r.length,byMethod:c,calls:r.slice(0,80)}}function J(t,l={}){const r=U(t),i=new Map,n=(o,p)=>{i.has(o.id)||i.set(o.id,{id:o.id,title:o.title,reason:"Detected from code signals",sourceFiles:new Set}),i.get(o.id).sourceFiles.add(b.relative(t,p))};for(const o of r){const p=S(o);for(const A of R)A.regex.test(p)&&n(A,o)}const e=b.join(t,"package.json");if(C.existsSync(e)){const o=JSON.parse(S(e)||"{}"),p=typeof o.name=="string"?o.name:b.basename(t);M(p)&&!i.size&&(i.set("ReadItems",{id:"ReadItems",title:"Read Items",reason:`Fallback default for ${p}`,sourceFiles:new Set}),i.set("CreateItem",{id:"CreateItem",title:"Create Item",reason:`Fallback default for ${p}`,sourceFiles:new Set}))}i.size||(i.set("CreateItem",{id:"CreateItem",title:"Create Item",reason:"Fallback default",sourceFiles:new Set}),i.set("ReadItems",{id:"ReadItems",title:"Read Items",reason:"Fallback default",sourceFiles:new Set}));const c=Array.from(i.values()).map(o=>({...o,sourceFiles:Array.from(o.sourceFiles||[])})),s=W(t);return{capabilities:c,components:O(r,t),displayFields:H(r),externalLibraries:s,uiLayout:N(r),styling:G(t,r,s),developmentProfile:B(t,r,s,l),apiCalls:q(t,r)}}async function Q(t,l=!1){if(l)return t;const r=D.createInterface({input:process.stdin,output:process.stdout}),i=t.map(c=>c.id).join(", "),n=await new Promise(c=>r.question(` Inferred capabilities (${i}). Press Enter to accept or type comma list: `,c));r.close();const e=String(n).trim();return e?e.split(",").map(c=>c.trim()).filter(Boolean).map(c=>({id:c,title:z(c),reason:"User provided during adopt review"})):t}function X(t){if(!t.length)return"No capabilities inferred.";const l=V(t),r=l.reduce((e,c)=>e+c.signalCount,0),i={high:l.filter(e=>e.confidence==="high").length,medium:l.filter(e=>e.confidence==="medium").length,low:l.filter(e=>e.confidence==="low").length},n=[];n.push("Adoption Analysis"),n.push("=".repeat(56)),n.push(`Capabilities detected : ${l.length}`),n.push(`Signal hits total : ${r}`),n.push(`Confidence mix : high=${i.high}, medium=${i.medium}, low=${i.low}`),n.push("-".repeat(56)),n.push("Capability Breakdown"),n.push("-".repeat(56)),n.push("Confidence Signals Capability"),n.push("-".repeat(56));for(const e of l){const c=e.confidence.toUpperCase().padEnd(10," "),s=String(e.signalCount).padEnd(7," ");if(n.push(`${c} ${s} ${e.id} (${e.title})`),e.signalCount>0){const o=e.sourceFiles.slice(0,3).join(", ");n.push(` sources: ${o}`),e.sourceFiles.length>3&&n.push(` more : +${e.sourceFiles.length-3} additional files`)}else n.push(" sources: inferred fallback (no strong code signal)")}return n.push("=".repeat(56)),n.join(`
|
|
3
|
+
`)}function Y(t){const l=(r,i,n=10)=>{const e=[`${r} (${i.length})`];if(e.push("-".repeat(56)),!i.length)return e.push(" - none"),e.join(`
|
|
4
|
+
`);for(const c of i.slice(0,n))e.push(` - ${c}`);return i.length>n&&e.push(` - ... +${i.length-n} more`),e.join(`
|
|
5
|
+
`)};return["Project Structure Signals","=".repeat(56),l("Components",t.components||[]),l("Display fields",t.displayFields||[]),l("External libraries",t.externalLibraries||[]),"UI layout","-".repeat(56),` - layout type: ${t.uiLayout?.layoutType||"unknown"}`,` - uses grid : ${t.uiLayout?.usesGrid?"yes":"no"}`,` - uses flex : ${t.uiLayout?.usesFlex?"yes":"no"}`,` - sections : ${(t.uiLayout?.sections||[]).slice(0,10).join(", ")||"none"}`,"Styling","-".repeat(56),` - frameworks : ${(t.styling?.cssFrameworks||[]).join(", ")||"none detected"}`,` - style files: ${t.styling?.styleFileCount??0}`,` - tokens : ${(t.styling?.designTokens||[]).slice(0,8).join(", ")||"none detected"}`,"Development profile","-".repeat(56),` - language : ${t.developmentProfile?.language||"unknown"} (auto: ${t.developmentProfile?.detected?.language||"unknown"})`,` - framework : ${t.developmentProfile?.framework||"unknown"} (auto: ${t.developmentProfile?.detected?.framework||"unknown"})`,` - project type: ${t.developmentProfile?.projectType||"unknown"} (auto: ${t.developmentProfile?.detected?.projectType||"unknown"})`,"API calls","-".repeat(56),` - total calls : ${t.apiCalls?.totalCalls??0}`,` - by method : ${Object.entries(t.apiCalls?.byMethod||{}).map(([r,i])=>`${r}:${i}`).join(", ")||"none"}`,...(t.apiCalls?.calls||[]).slice(0,6).map(r=>` - ${r.method} ${r.endpointPattern} [${r.style}] (${r.sourceFile})`),...(t.apiCalls?.calls||[]).length>6?[` - ... +${(t.apiCalls?.calls||[]).length-6} more`]:[],"=".repeat(56)].join(`
|
|
6
|
+
`)}function V(t){return t.map(l=>{const r=l.sourceFiles?.length||0,i=r>=3?"high":r>=1?"medium":"low";return{id:l.id,title:l.title,reason:l.reason,confidence:i,sourceFiles:l.sourceFiles||[],signalCount:r}})}function ee(t,l,r,i=null){const n=r.map(p=>p.id),e={policyId:l,policyVersion:1,capabilities:n,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};C.mkdirSync(b.join(t,"scenarios"),{recursive:!0}),C.writeFileSync(b.join(t,"contract.json"),JSON.stringify(e,null,2)+`
|
|
7
|
+
`);const c={schemaVersion:1,capabilities:r.map(p=>({id:p.id,title:p.title||z(p.id),since:"0.1.0"}))};C.writeFileSync(b.join(t,"capabilities.json"),JSON.stringify(c,null,2)+`
|
|
8
|
+
`);const s={scenarioId:"adoption_baseline",description:"Baseline inferred from existing codebase during adoption",capabilitiesCovered:n,steps:n.map(p=>({action:p,expect:`${p} behavior exists in the current project`}))};if(C.writeFileSync(b.join(t,"scenarios","adoption_baseline.json"),JSON.stringify(s,null,2)+`
|
|
9
|
+
`),i){const p={profileId:"adoption_profile",generatedAt:new Date().toISOString(),components:i.components||[],displayFields:i.displayFields||[],externalLibraries:i.externalLibraries||[],uiLayout:i.uiLayout||{layoutType:"unknown",usesGrid:!1,usesFlex:!1,sections:[]},styling:i.styling||{cssFrameworks:[],styleFileCount:0,styleFilesSample:[],designTokens:[]},developmentProfile:i.developmentProfile||{language:"unknown",framework:"unknown",projectType:"unknown",detected:{language:"unknown",framework:"unknown",projectType:"unknown"}},apiCalls:i.apiCalls||{totalCalls:0,byMethod:{},calls:[]}};C.writeFileSync(b.join(t,"adoption_profile.json"),JSON.stringify(p,null,2)+`
|
|
10
|
+
`)}const o=`# Changelog \u2014 ${l}
|
|
11
|
+
|
|
12
|
+
## Unreleased
|
|
13
|
+
|
|
14
|
+
- Adopted infernoflow into an existing project and generated baseline capabilities.
|
|
15
|
+
- Captured detected components, display fields, and external libraries in adoption profile.
|
|
16
|
+
|
|
17
|
+
## 0.1.0 \u2014 Adoption baseline
|
|
18
|
+
|
|
19
|
+
- Initial baseline generated by infernoflow init --adopt
|
|
20
|
+
`;C.writeFileSync(b.join(t,"CHANGELOG.md"),o,"utf8")}export{X as buildAdoptionReport,Y as buildSignalsReport,K as discoverCapabilities,J as discoverProjectSignals,Q as reviewCapabilitiesInteractive,V as summarizeCapabilities,ee as writeAdoptionBaseline};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import*as c from"node:fs";import*as l from"node:path";import{header as v,ok as f,fail as o,warn as E,section as g,done as A,errorAndExit as N,bold as w,red as I,yellow as J}from"../ui/output.mjs";import{docGateCommand as F}from"./docGate.mjs";function O(s,t=!1){try{return JSON.parse(c.readFileSync(s,"utf8"))}catch{if(t)throw new Error(`Cannot parse ${l.basename(s)}`);N(`Cannot parse ${l.basename(s)}`,`Check JSON syntax in: ${s}`)}}function H(s){return c.existsSync(s)?c.readdirSync(s).filter(t=>t.endsWith(".json")).map(t=>l.join(s,t)):[]}function L(s){const t=new Set;for(const a of s)try{(JSON.parse(c.readFileSync(a,"utf8")).capabilitiesCovered||[]).forEach(e=>t.add(e))}catch{}return t}async function B(s){const t=process.cwd(),a=l.join(t,"inferno"),m=s.includes("--skip-doc-gate"),e=s.includes("--json");e||v("check");const i=[],d=[];c.existsSync(a)||(e&&(console.log(JSON.stringify({ok:!1,errors:["inferno/ not found"]})),process.exit(1)),N("inferno/ not found","Run: infernoflow init"));const b=l.join(a,"contract.json"),S=l.join(a,"capabilities.json"),$=l.join(a,"scenarios"),C=l.join(a,"CHANGELOG.md");if(e||g("Contract"),!c.existsSync(b))o("contract.json not found","Run: infernoflow init"),i.push("contract.json missing");else{let r;try{r=O(b,e)}catch(p){i.push(p.message),e||(o(p.message),process.exit(1)),console.log(JSON.stringify({ok:!1,errors:i,warnings:d},null,2)),process.exit(1)}const h=r.capabilities||[];if(r.policyId?e||f(`policyId: ${w(r.policyId)}`):(o("policyId missing"),i.push("policyId missing")),Number.isInteger(r.policyVersion)?e||f(`policyVersion: ${w("v"+r.policyVersion)}`):(o("policyVersion must be an integer"),i.push("policyVersion invalid")),h.length===0?(o("capabilities array is empty"),i.push("no capabilities")):e||f(`${h.length} capabilities declared`),e||g("Capabilities Registry"),!c.existsSync(S))o("capabilities.json not found"),i.push("capabilities.json missing");else{let p;try{p=O(S,e)}catch(n){i.push(n.message),e||(o(n.message),process.exit(1)),console.log(JSON.stringify({ok:!1,errors:i,warnings:d},null,2)),process.exit(1)}const j=new Set((p.capabilities||[]).map(n=>n?.id).filter(Boolean)),x=h.filter(n=>!j.has(n));x.length>0?x.forEach(n=>{e||o(`"${n}" in contract but missing from capabilities.json`,"Add it to inferno/capabilities.json"),i.push(`"${n}" not registered`)}):e||f(`All ${j.size} capabilities registered`),e||g("Scenarios");const y=H($);if(y.length===0)E("No scenarios found"),d.push("no scenarios");else{const n=L(y),k=r?.rules?.requireScenarioForEachCapability!==!1,G=h.filter(u=>!n.has(u));e||f(`${y.length} scenario file(s) found`),G.length>0&&k?G.forEach(u=>{e||o(`"${u}" has no scenario coverage`,"Add to capabilitiesCovered in a scenario file"),i.push(`"${u}" uncovered`)}):e||f("All capabilities covered by scenarios")}}}if(e||g("Changelog"),!c.existsSync(C))o("inferno/CHANGELOG.md not found"),i.push("CHANGELOG missing");else{const r=c.readFileSync(C,"utf8");/##\s+Unreleased/i.test(r)?e||f("CHANGELOG.md has ## Unreleased section"):(o("Missing '## Unreleased' section","Add it to inferno/CHANGELOG.md"),i.push("CHANGELOG missing Unreleased"))}if(m||(e||g("Doc Gate"),await F({silent:e,captureExit:!0}).catch(()=>{i.push("doc-gate failed")})),e){console.log(JSON.stringify({ok:i.length===0,errors:i,warnings:d},null,2)),i.length>0&&process.exit(1);return}console.log(),i.length>0?(console.log(" "+I(`\u2718 check failed \u2014 ${i.length} error(s)
|
|
2
|
+
`)),process.exit(1)):d.length>0?console.log(" "+J(`\u26A0 check passed with ${d.length} warning(s)
|
|
3
|
+
`)):A("check passed \u2014 everything is in sync")}export{B as checkCommand};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import l from"node:fs";import d from"node:path";import{execSync as w}from"node:child_process";import{bold as f,gray as S,cyan as r,red as P,green as a,yellow as I}from"../ui/output.mjs";import{buildCursorImplementPrompt as q,buildGenericImplementPrompt as z}from"../ui/prompts.mjs";function K(t){try{const o=process.platform;if(o==="win32")w("clip",{input:t});else if(o==="darwin")w("pbcopy",{input:t});else try{w("xclip -selection clipboard",{input:t})}catch{w("xsel --clipboard --input",{input:t})}return!0}catch{return!1}}const g="inferno",D=d.join(g,"CONTEXT.md"),T=d.join(g,"context-state.json");function L(t){try{return JSON.parse(l.readFileSync(t,"utf8"))}catch{return null}}function v(t){try{return l.readFileSync(t,"utf8")}catch{return null}}function Q(){const t=v(T);if(!t)return{};try{return JSON.parse(t)}catch{return{}}}function Y(t){l.writeFileSync(T,JSON.stringify(t,null,2),"utf8")}function _(t){return t?new Date(t).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function Z(t,o){if(!t)return[];const i=[];let s=null;for(const c of t.split(`
|
|
2
|
+
`))if(c.startsWith("## ")){if(s&&i.length<o&&i.push(s),i.length>=o)break;s={title:c.replace("## ","").trim(),items:[]}}else s&&c.startsWith("- ")&&s.items.push(c.replace("- ","").trim());return s&&i.length<o&&i.push(s),i.filter(c=>c.items.length>0)}async function cn(t){const o=e=>t.includes(e),i=e=>{const u=t.indexOf(e);return u!==-1&&t[u+1]?t[u+1]:null},s=i("--intent")||i("-i"),c=i("--working")||i("-w"),m=i("--decision")||i("-d"),E=o("--show")||o("-s"),x=o("--copy")||o("-c"),W=o("--cursor"),G=o("--copilot"),R=o("--reset");console.log(`
|
|
3
|
+
`+f("\uFFFD\uFFFD\uFFFD infernoflow \u2014 context")),console.log(" "+"\u2500".repeat(50)+`
|
|
4
|
+
`),l.existsSync(g)||(console.error(P(" \u2718 inferno/ not found")),console.error(S(` \u2192 Run: infernoflow init
|
|
5
|
+
`)),process.exit(1));const p=L(d.join(g,"contract.json")),b=L(d.join(g,"capabilities.json")),A=v(d.join(g,"CHANGELOG.md"));(!p||!b)&&(console.error(P(` \u2718 Missing contract.json or capabilities.json
|
|
6
|
+
`)),process.exit(1));let n=Q();R&&(n={},console.log(I(` \u26A0 State reset
|
|
7
|
+
`))),s&&(n.intent=s,n.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+s+'"'))),c&&(n.working=c,n.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+c+'"'))),m&&(n.decisions||(n.decisions=[]),n.decisions.push({text:m,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+m+'"'))),(s||c||m)&&Y(n);const h=b.capabilities||[],j=h.length===(p.capabilities||[]).length,O=Z(A,3),C=String(p.policyVersion).replace(/^v/i,""),U=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),N=j?"\u2713 validated":"\u26A0 out of sync",k=n.intent||"describe the exact task to implement",F={task:k,contract:p,caps:b,scenarios:[],state:n},J=q(F),V=z(F),X=h.map(e=>"- **"+e.id+"** \u2014 "+e.title).join(`
|
|
8
|
+
`),B=O.length>0?O.map(e=>"### "+e.title+`
|
|
9
|
+
`+e.items.map(u=>" - "+u).join(`
|
|
10
|
+
`)).join(`
|
|
11
|
+
|
|
12
|
+
`):"_No recent changes_",$=n.intent?n.intent+" _("+_(n.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',H=n.working?n.working+" _("+_(n.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',M=n.decisions&&n.decisions.length>0?n.decisions.slice(-5).map(e=>"- "+e.text+" _("+_(e.date)+")_").join(`
|
|
13
|
+
`):"_No decisions recorded_",y=["# Project Context \u2014 "+p.policyId+" v"+C,"> Generated by infernoflow | "+U+" | "+N,"","---","","## What this system does","",X,"","---","","## Recent changes","",B,"","---","","## Current state","","- **Capabilities:** "+h.length,"- **Version:** v"+C,"- **Sync:** "+N,"","---","","## What I am working on right now","",H,"","---","","## Intent \u2014 what I want to build next","",$,"","---","","## Decisions & notes","",M,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${k}" --mode both`,"```","","### Cursor Agent Prompt","","```text",J,"```","","### Generic Agent Prompt","","```text",V,"```","","---","_Paste this block at the start of any new AI session._"].join(`
|
|
14
|
+
`);if(E||(l.writeFileSync(D,y,"utf8"),console.log(a(`
|
|
15
|
+
\u2714 Context written \u2192 `+D))),x){const e=K(y);console.log(e?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):I(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}W&&(l.writeFileSync(".cursorrules",y,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),G&&(l.existsSync(".github")||l.mkdirSync(".github"),l.writeFileSync(".github/copilot-instructions.md",y,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
|
|
16
|
+
`+f("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+p.policyId+" \u2014 v"+C),console.log(" Capabilities "+h.length+" registered"),console.log(" Sync "+(j?a("\u2713 in sync"):I("\u26A0 check needed"))),console.log(" Working on "+(n.working?r(n.working):S("not set"))),console.log(" Intent "+(n.intent?r(n.intent):S("not set"))),console.log(" Decisions "+(n.decisions?n.decisions.length:0)+` recorded
|
|
17
|
+
`),console.log(" "+f("Implementation Prompt")),console.log(" "+r("\u2192")+" Run "+r(`infernoflow implement "${k}" --mode both`)+`
|
|
18
|
+
`),x?(console.log(" "+f("Ready to use:")),console.log(" "+r("\u2192")+" Paste into Claude / Cursor / Copilot with "+r("Ctrl+V")+`
|
|
19
|
+
`)):(console.log(" "+f("Ready to use:")),console.log(" "+r("1.")+" Open "+r("inferno/CONTEXT.md")),console.log(" "+r("2.")+" Copy everything"),console.log(" "+r("3.")+" Paste at the start of your next AI session"),console.log(" "+S(" tip: use --copy to skip steps 1-2 automatically")+`
|
|
20
|
+
`))}export{cn as contextCommand};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{execSync as m}from"node:child_process";import{ok as f,fail as E,info as y,gray as d}from"../ui/output.mjs";function S(n){return m(n,{stdio:["ignore","pipe","pipe"]}).toString("utf8").trim()}const g=["src/","frontend/","backend/","app/","pages/","components/","Controllers/","Services/","Endpoints/","lib/","api/","server/"];async function C(n={}){const a=Array.isArray(n),t=a?!1:n?.silent||!1,p=a?!1:n?.captureExit||!1,l=a?n.includes("--json"):!!n?.json,u=process.env.BASE_SHA||"HEAD~1",h=process.env.HEAD_SHA||"HEAD";let o=[];try{const e=S(`git diff --name-only ${u}..${h}`);o=e?e.split(`
|
|
2
|
+
`).filter(Boolean):[]}catch{if(l){console.log(JSON.stringify({ok:!0,skipped:!0,reason:"no_git_available"},null,2));return}t||y(d("doc-gate skipped (no git available)"));return}if(o.length===0){if(l){console.log(JSON.stringify({ok:!0,changedFiles:0,changedCode:!1,changedInferno:!1},null,2));return}t||f("doc-gate: no changed files");return}const i=o.some(e=>g.some(r=>e.startsWith(r)||e.includes("/"+r))),s=o.some(e=>e.startsWith("inferno/")),c=o.filter(e=>g.some(r=>e.startsWith(r))).slice(0,5);if(l){const e={ok:!(i&&!s),changedFiles:o.length,changedCode:i,changedInferno:s,sampleCodeFiles:c,hint:i&&!s?"Update at least one file in inferno/ before committing":null};console.log(JSON.stringify(e,null,2)),e.ok||process.exit(1);return}if(i&&!s){if(t||(E("Code changed but inferno/ was NOT updated","Update at least one file in inferno/ before committing"),c.length&&(console.log(),c.forEach(e=>console.log(" "+d("\u2022 "+e))))),p)throw new Error("doc-gate failed");process.exit(1)}t||f("doc-gate: docs are up to date")}export{C as docGateCommand};
|
|
@@ -0,0 +1,7 @@
|
|
|
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};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import*as c from"node:fs";import*as r from"node:path";import*as _ from"node:readline";import{fileURLToPath as K}from"node:url";import{header as M,ok as p,warn as O,done as Q,nextSteps as Y,cyan as l,yellow as x,gray as C}from"../ui/output.mjs";import{discoverProjectSignals as P,reviewCapabilitiesInteractive as ee,writeAdoptionBaseline as oe,buildAdoptionReport as ne,summarizeCapabilities as ie,buildSignalsReport as te}from"./adopt.mjs";import{installCursorHooksArtifacts as re}from"../cursorHooksInstall.mjs";import{installVsCodeCopilotHooksArtifacts as se}from"../vsCodeCopilotHooksInstall.mjs";const ae=r.dirname(K(import.meta.url));function ce(){return r.resolve(ae,"../../templates")}function S(e,o,n=""){return new Promise(i=>{const f=n?C(` (${n})`):"";e.question(` ${o}${f}: `,d=>{i(d.trim()||n)})})}function L(e,...o){for(const n of o){const i=e.indexOf(n);if(i!==-1&&e[i+1]&&!e[i+1].startsWith("-"))return e[i+1]}return null}function T(e,o,n,i=!1){return c.existsSync(o)&&!n?(i||O("Skipped (exists): "+r.relative(process.cwd(),o)),!1):(c.mkdirSync(r.dirname(o),{recursive:!0}),c.copyFileSync(e,o),i||p("Created: "+l(r.relative(process.cwd(),o))),!0)}function le(e,o,n){c.mkdirSync(o,{recursive:!0});for(const i of c.readdirSync(e,{withFileTypes:!0})){const f=r.join(e,i.name),d=r.join(o,i.name);i.isDirectory()?le(f,d,n):T(f,d,n)}}function pe(e,o=!1){const n=r.join(e,"package.json");if(!c.existsSync(n))return;const i=JSON.parse(c.readFileSync(n,"utf8"));i.scripts=i.scripts||{};let f=!1;const d={"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[y,F]of Object.entries(d))i.scripts[y]||(i.scripts[y]=F,f=!0);f&&(c.writeFileSync(n,JSON.stringify(i,null,2)+`
|
|
2
|
+
`,"utf8"),o||p("Updated "+l("package.json")+" scripts"))}function fe(e){const o=r.join(e,"package.json");if(c.existsSync(o))try{const n=JSON.parse(c.readFileSync(o,"utf8"));if(n.name)return n.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return r.basename(e)}function de(e,o,n){const i={policyId:o,policyVersion:1,capabilities:n,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};c.writeFileSync(e,JSON.stringify(i,null,2)+`
|
|
3
|
+
`)}function ue(e,o){const n={schemaVersion:1,capabilities:o.map(i=>({id:i,title:i.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};c.writeFileSync(e,JSON.stringify(n,null,2)+`
|
|
4
|
+
`)}function me(e,o){c.mkdirSync(e,{recursive:!0});const n={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(i=>({action:i,expect:`${i} works as expected`}))};c.writeFileSync(r.join(e,"happy_path.json"),JSON.stringify(n,null,2)+`
|
|
5
|
+
`)}function ye(e,o){const n=`# Changelog \u2014 ${o}
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
8
|
+
|
|
9
|
+
- Initial capabilities defined
|
|
10
|
+
|
|
11
|
+
## 0.1.0 \u2014 Initial release
|
|
12
|
+
|
|
13
|
+
- Project initialized with infernoflow
|
|
14
|
+
`;c.writeFileSync(e,n)}async function Ce(e){const o=process.cwd(),n=e.includes("--force")||e.includes("-f"),i=e.includes("--yes")||e.includes("-y"),f=e.includes("--adopt"),d=e.includes("--cursor-hooks"),y=e.includes("--vscode-copilot-hooks"),F=e.includes("--report-json"),v=e.includes("--report-json-only"),I=e.includes("--report-human-only"),N=L(e,"--lang"),R=L(e,"--framework"),A=L(e,"--project-type"),s=v;v&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),s||M("init");const m=r.join(o,"inferno"),$=r.join(o,".github","workflows");c.existsSync(m)&&!n&&(s&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),O("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const k=fe(o),E="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let j=k,g=E.split(",").map(a=>a.trim());if(f){let t=P(o,{language:N||void 0,framework:R||void 0,projectType:A||void 0});if(!i&&!v){const w=_.createInterface({input:process.stdin,output:process.stdout}),b=t.developmentProfile||{},J=b.detected||{};console.log(C(` Review inferred development stack (press Enter to accept detected values)
|
|
15
|
+
`));const V=await S(w,"Language",b.language||J.language||"unknown"),X=await S(w,"Framework",b.framework||J.framework||"unknown"),Z=await S(w,"Project type",b.projectType||J.projectType||"unknown");w.close(),t=P(o,{language:V,framework:X,projectType:Z})}const u=t.capabilities,H=ie(u);v?console.log(JSON.stringify({mode:"adopt",policyId:k,inferredCapabilities:H,components:t.components,displayFields:t.displayFields,externalLibraries:t.externalLibraries,uiLayout:t.uiLayout,styling:t.styling,developmentProfile:t.developmentProfile,apiCalls:t.apiCalls},null,2)):(console.log(),console.log(C(ne(u))),console.log(),console.log(C(te(t))),console.log(),F&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:k,inferredCapabilities:H,components:t.components,displayFields:t.displayFields,externalLibraries:t.externalLibraries,uiLayout:t.uiLayout,styling:t.styling,developmentProfile:t.developmentProfile,apiCalls:t.apiCalls},null,2)),console.log()));const B=await ee(u,i);j=k,g=B.map(w=>w.id)}else if(!i){const a=_.createInterface({input:process.stdin,output:process.stdout});console.log(C(` Press Enter to accept defaults
|
|
16
|
+
`)),j=await S(a,"Project / policy name",k),g=(await S(a,"Capabilities (comma-separated)",E)).split(",").map(u=>u.trim()).filter(Boolean),a.close(),console.log()}if(c.mkdirSync(m,{recursive:!0}),f){const a=g.map(u=>({id:u,title:u.replace(/([A-Z])/g," $1").trim()})),t=P(o,{language:N||void 0,framework:R||void 0,projectType:A||void 0});oe(m,j,a,t),s||(p("Created: "+l("inferno/contract.json")),p("Created: "+l("inferno/capabilities.json")),p("Created: "+l("inferno/scenarios/adoption_baseline.json")),p("Created: "+l("inferno/adoption_profile.json")),p("Created: "+l("inferno/CHANGELOG.md")))}else de(r.join(m,"contract.json"),j,g),s||p("Created: "+l("inferno/contract.json")),ue(r.join(m,"capabilities.json"),g),s||p("Created: "+l("inferno/capabilities.json")),me(r.join(m,"scenarios"),g),s||p("Created: "+l("inferno/scenarios/happy_path.json")),ye(r.join(m,"CHANGELOG.md"),j),s||p("Created: "+l("inferno/CHANGELOG.md"));const h=ce(),G=r.join(h,"scripts","inferno-doc-gate.mjs"),U=r.join(o,"scripts","inferno-doc-gate.mjs");T(G,U,n,s);const z=r.join(h,"scripts","inferno-install-hooks.mjs"),D=r.join(o,"scripts","inferno-install-hooks.mjs");T(z,D,n,s);const W=r.join(h,"ci","github-inferno-check.yml"),q=r.join($,"infernoflow-check.yml");if(T(W,q,n,s),pe(o,s),d&&re({cwd:o,templatesRoot:h,force:n,silent:s,logOk:a=>{s||p(a)},logWarn:a=>{s||O(a)}}),y&&se({cwd:o,templatesRoot:h,force:n,silent:s,logOk:a=>{s||p(a)},logWarn:a=>{s||O(a)}}),f){const a=r.join(m,"context-state.json");let t={};try{t=JSON.parse(c.readFileSync(a,"utf8"))}catch{}const u=P(o,{language:N||void 0,framework:R||void 0,projectType:A||void 0});t.stack=u.developmentProfile,c.writeFileSync(a,JSON.stringify(t,null,2)+`
|
|
17
|
+
`,"utf8"),s||p("Created: "+l("inferno/context-state.json"))}s||(Q("infernoflow initialized!"),Y([l("infernoflow status")+" \u2014 see your contract at a glance",l("infernoflow check")+" \u2014 validate everything",(f?"Review inferred baseline in ":"Edit ")+x("inferno/capabilities.json")+(f?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+x("inferno/scenarios/*.json")+" files for edge cases","Add "+l("inferno:check")+" to your CI pipeline",...d?["Restart Cursor \u2014 hooks write assistant text to "+x("inferno/CONTEXT.draft.md"),"Promote when ready: "+l("npm run inferno:promote-draft -- --append-notes")]:[],...y?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+x("inferno/CONTEXT.draft.md"),"Promote when ready: "+l("npm run inferno:promote-draft -- --append-notes")]:[],...!d&&!y?["Optional: "+l("infernoflow install-cursor-hooks")+" or "+l("infernoflow install-vscode-copilot-hooks")]:[]]))}export{Ce as initCommand};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"node:path";import{fileURLToPath as a}from"node:url";import{header as l,ok as d,warn as p,done as m,nextSteps as c,cyan as n,yellow as r}from"../ui/output.mjs";import{installVsCodeCopilotHooksArtifacts as f}from"../vsCodeCopilotHooksInstall.mjs";const u=e.dirname(a(import.meta.url));function h(){return e.resolve(u,"../../templates")}async function g(t){const s=process.cwd(),i=t.includes("--force")||t.includes("-f");l("install-vscode-copilot-hooks"),f({cwd:s,templatesRoot:h(),force:i,silent:!1,logOk:o=>d(o),logWarn:o=>p(o)}),m("VS Code / Copilot draft hooks installed"),c(["Requires VS Code + GitHub Copilot and **Agent hooks (Preview)** \u2014 see "+r("https://code.visualstudio.com/docs/copilot/customization/hooks"),"Hooks load from "+r(".github/hooks/*.json")+" \u2014 restart VS Code or reload window after first install","Check the **GitHub Copilot Chat Hooks** output channel if nothing runs",n("npm run inferno:promote-draft")+" \u2014 preview draft",n("npm run inferno:promote-draft -- --append-notes")+" \u2014 merge into inferno/CONTEXT.md"])}export{g as installVsCodeCopilotHooksCommand};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{execSync as k}from"node:child_process";import*as S from"node:fs";import*as m from"node:path";import{header as w,section as y,ok as d,warn as E,fail as N,gray as $,cyan as g,yellow as _}from"../ui/output.mjs";const D=["src/","frontend/","backend/","app/","pages/","components/","lib/","api/","server/","Controllers/"];function A(n){return k(n,{stdio:["ignore","pipe","pipe"]}).toString("utf8").trim()}function I(n,t=null){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return t}}function F(n,t=""){try{return S.readFileSync(n,"utf8")}catch{return t}}function H(n,t){const a=A(n&&t?`git diff --name-only ${n}..${t}`:"git diff --name-only HEAD");return a?a.split(`
|
|
2
|
+
`).map(r=>r.trim()).filter(Boolean):[]}function O(n){const t=m.join(n,"inferno"),a=I(m.join(t,"contract.json"),{capabilities:[]}),r=I(m.join(t,"capabilities.json"),{capabilities:[]}),c=new Map((r.capabilities||[]).map(e=>[e.id,e.title||e.id]));return(a.capabilities||[]).map(e=>{const i=c.get(e)||e,l=new Set(`${e} ${i}`.replace(/([A-Z])/g," $1").toLowerCase().split(/[^a-z0-9]+/).filter(s=>s.length>=4));return{id:e,title:i,keywords:Array.from(l)}})}function R(n,t){const a=O(n),r=[];for(const c of a){const e=[];for(const i of t){const l=m.join(n,i),s=F(l,"").toLowerCase();s&&c.keywords.some(f=>s.includes(f))&&e.push(i)}e.length&&r.push({id:c.id,title:c.title,matchedFiles:e.slice(0,5)})}return r}async function x(n=[]){const t=n.includes("--json"),a=process.cwd(),r=process.env.BASE_SHA||null,c=process.env.HEAD_SHA||null;let e=[];try{e=H(r,c)}catch{const o={ok:!0,skipped:!0,reason:"no_git_available"};if(t){console.log(JSON.stringify(o,null,2));return}w("pr-impact"),E("git not available; cannot compute PR impact"),console.log();return}const i=e.filter(o=>D.some(C=>o.startsWith(C))),l=e.filter(o=>o.startsWith("inferno/")),s=R(a,i),f=i.length>0,h=f&&l.length===0,b=s.length>0?"high":f?"medium":"low",p=[];f&&p.push("CODE_CHANGED"),h&&p.push("INFERNO_NOT_UPDATED"),s.length>0&&p.push("CAPABILITY_HINT_MATCH"),p.length||p.push("NO_BEHAVIOR_SIGNAL");const u={ok:!h,base:r||"HEAD",head:c||"WORKTREE",changedFiles:e,changedCodeFiles:i,changedInfernoFiles:l,inferredBehaviorChange:f,impactedCapabilities:s,confidence:b,reasonCodes:p,recommendations:h?['Run infernoflow suggest "describe behavior change" and update inferno/',"Run infernoflow check --json"]:["Run infernoflow check --json to validate final state"]};t&&(console.log(JSON.stringify(u,null,2)),process.exit(u.ok?0:1)),w("pr-impact"),y("Diff Scope"),d(`Changed files: ${g(String(e.length))}`),d(`Code files: ${g(String(i.length))}`),d(`Inferno files: ${g(String(l.length))}`),y("Capability Impact"),s.length===0?E("No capability hints matched changed code files"):s.forEach(o=>{console.log(` ${g("\u2022")} ${o.id} ${$(`(${o.title})`)}`),o.matchedFiles.slice(0,3).forEach(C=>console.log(` ${$("- "+C)}`))}),y("Doc Sync"),h?N("Code changed but inferno/ was not updated","Run infernoflow suggest and then infernoflow check"):d("No immediate inferno drift signal from changed files"),d(`Confidence: ${g(b)}`),y("Suggested Next"),u.recommendations.forEach(o=>console.log(` ${_("\u2192")} ${o}`)),console.log(),process.exit(u.ok?0:1)}export{x as prImpactCommand};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import*as a from"node:fs";import*as p from"node:path";import{execFileSync as T}from"node:child_process";import{fileURLToPath as L}from"node:url";import{generateWithLocalModel as $}from"../ai/localProvider.mjs";import{resolveProvider as q}from"../ai/providerRouter.mjs";import{buildPrompt as W,loadSuggestContext as G,parseSuggestionJson as O,validateSuggestion as M,detectSuggestionConflicts as V,applyChanges as K}from"./suggest.mjs";import{header as U,section as B,ok as x,warn as F,fail as v,info as h,gray as j}from"../ui/output.mjs";const z=L(import.meta.url),H=p.dirname(z),Q=p.resolve(H,"..","..","bin","infernoflow.mjs");function D(t){try{const e=T(process.execPath,[Q,...t],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return{ok:!0,data:JSON.parse(e)}}catch(e){const n=e?.stdout?.toString?.()||"";try{return{ok:!1,data:JSON.parse(n)}}catch{return{ok:!1,data:{ok:!1,errors:["command_failed"]}}}}}function m(t,e,n,o,s={}){const r={ts:new Date().toISOString(),stage:n,status:o,...s};if(e.push(r),t)return;const c=`${n}: ${o}`;o==="ok"?x(c):o==="warn"?F(c):o==="fail"?v(c):h(c)}function X(t){const e=p.join(t,"inferno"),n=[],o=r=>{for(const c of a.readdirSync(r,{withFileTypes:!0})){const f=p.join(r,c.name);c.isDirectory()?o(f):n.push(f)}};a.existsSync(e)&&o(e);const s=new Map;return n.forEach(r=>s.set(r,a.readFileSync(r,"utf8"))),s}function Y(t,e){const n=p.join(t,"inferno");if(a.existsSync(n)){const o=[],s=r=>{for(const c of a.readdirSync(r,{withFileTypes:!0})){const f=p.join(r,c.name);c.isDirectory()?s(f):o.push(f)}};s(n),o.forEach(r=>{e.has(r)||a.unlinkSync(r)})}for(const[o,s]of e.entries())a.mkdirSync(p.dirname(o),{recursive:!0}),a.writeFileSync(o,s,"utf8")}function Z(t,e){const n=p.join(t,"inferno","runs");a.mkdirSync(n,{recursive:!0});const o=p.join(n,`${Date.now()}.json`);return a.writeFileSync(o,JSON.stringify(e,null,2)+`
|
|
2
|
+
`,"utf8"),p.relative(t,o)}function A(t,e,n=null){const o=t.indexOf(e);return o!==-1&&t[o+1]&&!t[o+1].startsWith("-")?t[o+1]:n}function ee(t){const e=new Set(["--provider","--ide"]),n=[];for(let o=1;o<t.length;o++){const s=t[o];if(s.startsWith("-")){e.has(s)&&(o+=1);continue}n.push(s)}return n.join(" ").trim()}function oe(t,e){return{summary:`Prompt fallback only: ${t}`,newCapabilities:[],removedCapabilities:[],updatedScenarios:[],changelogEntry:`- Prompt fallback mode for task: ${t} (no automatic contract mutation).`,_meta:{actionRequired:!0,nextStep:"Run infernoflow suggest or provide an agent bridge for automatic apply.",capabilitiesCount:(e?.capabilities||[]).length}}}async function te(t){if(process.env.INFERNO_AGENT_MOCK_RESPONSE)return process.env.INFERNO_AGENT_MOCK_RESPONSE;const e=process.env.INFERNO_AGENT_RESPONSE_FILE?process.env.INFERNO_AGENT_RESPONSE_FILE:p.join(process.cwd(),"inferno","agent-response.json");if(a.existsSync(e)){const r=a.readFileSync(e,"utf8");return a.unlinkSync(e),r}const n=p.join(process.cwd(),"inferno"),o=p.join(n,"agent-prompt.md");a.existsSync(o)&&a.unlinkSync(o),a.writeFileSync(o,t,"utf8"),process.stderr.write(`
|
|
3
|
+
\u2139 Prompt written to inferno/agent-prompt.md
|
|
4
|
+
`),process.stderr.write(` \u2192 Open it, paste into Cursor or Claude
|
|
5
|
+
`),process.stderr.write(` \u2192 Save the JSON reply to: inferno/agent-response.json
|
|
6
|
+
`),process.stderr.write(` Waiting up to 5 minutes...
|
|
7
|
+
|
|
8
|
+
`);const s=Date.now()+3e5;for(;Date.now()<s;)if(await new Promise(r=>setTimeout(r,1e3)),a.existsSync(e)){const r=a.readFileSync(e,"utf8");return a.unlinkSync(e),process.stderr.write(` \u2714 Response received
|
|
9
|
+
|
|
10
|
+
`),r}throw new Error("ide_agent_bridge_timeout")}async function le(t=[]){const e=t.includes("--json"),n=t.includes("--dry-run"),o=t.includes("--no-rollback"),s=(A(t,"--provider","auto")||"auto").toLowerCase(),r=(A(t,"--ide","auto")||"auto").toLowerCase(),c=ee(t)||"sync check",f=process.cwd(),l=[],y=[];e||U("run"),m(e,l,"init","info",{task:c,dryRun:n,noRollback:o});const b=D(["pr-impact","--json"]);m(e,l,"detect",b.data?.ok?"ok":"warn",{confidence:b.data?.confidence||"low"});const d=await q(s,r);if(y.push(...d.reasonCodes||[]),d.error==="agent_unavailable"){const i={ok:!1,error:"agent_unavailable",providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable,reasonCodes:y,events:l};e?console.log(JSON.stringify(i,null,2)):v("provider agent unavailable","Use --provider auto|local|prompt"),process.exit(1)}m(e,l,"route","ok",{providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable});const g=G(f);g?.contract||(e?console.log(JSON.stringify({ok:!1,error:"inferno_missing",events:l},null,2)):v("inferno/ missing or invalid"),process.exit(1));const _=W({description:c,contract:g.contract,capabilities:g.capabilities,scenarios:g.scenarios});let u;try{if(d.providerResolved==="local"){const i=await $(_);u=O(i)}else if(d.providerResolved==="agent"){const i=await te(_);u=O(i)}else u=oe(c,g.contract)}catch(i){const J={ok:!1,error:"proposal_failed",reason:String(i.message||i),reasonCodes:y,events:l};e?console.log(JSON.stringify(J,null,2)):v("proposal generation failed",i.message),process.exit(1)}m(e,l,"propose","ok",{newCapabilities:(u.newCapabilities||[]).length,removedCapabilities:(u.removedCapabilities||[]).length});const R=d.providerResolved==="prompt"?[]:M(u),E=d.providerResolved==="prompt"?[]:V(g.contract,u);if(R.length||E.length){const i={ok:!1,error:"invalid_suggestion",issues:[...R,...E],events:l};e?console.log(JSON.stringify(i,null,2)):v("suggestion invalid",i.issues[0]),process.exit(1)}const P=X(f);let w=!1,S=!1,N=!1;try{n?m(e,l,"apply","info",{dryRun:!0}):d.providerResolved==="prompt"?m(e,l,"apply","warn",{skipped:!0,reason:"prompt_fallback_requires_manual_step"}):(S=K({cwd:f,contract:g.contract,capabilities:g.capabilities,suggestion:u,version:g.version,quiet:e}),m(e,l,"apply","ok",{changed:S}));let i=D(["check","--json"]);if(process.env.INFERNO_TEST_FORCE_VALIDATE_FAIL==="1"&&(i={ok:!1,data:{ok:!1,errors:["forced_validation_failure"]}}),!i.ok||!i.data?.ok)throw new Error(`validation_failed:${(i.data?.errors||[]).join(",")}`);N=!0,m(e,l,"validate","ok")}catch(i){m(e,l,"validate","fail",{reason:String(i.message||i)}),!n&&!o&&(Y(f,P),w=!0,m(e,l,"rollback","ok"))}const I={task:c,dryRun:n,noRollback:o,rolledBack:w,applyChanged:S,suggestionSummary:u.summary||"",touchedCapabilities:[...(u.newCapabilities||[]).map(i=>i.id),...u.removedCapabilities||[]],events:l},C=Z(f,I),k={ok:N,mode:"run",task:c,dryRun:n,providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable,reasonCodes:Array.from(new Set(y)),rolledBack:w,applyChanged:S,artifactPath:C,events:l};e&&(console.log(JSON.stringify(k,null,2)),process.exit(k.ok?0:1)),B("Result"),h(`task: ${j(c)}`),h(`artifact: ${j(C)}`),k.ok?x("run completed"):F("run rolled back after failed validation"),console.log(),process.exit(k.ok?0:1)}export{le as runCommand};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import*as o from"node:fs";import*as a from"node:path";import{header as A,ok as H,fail as j,warn as J,section as p,bold as m,cyan as k,yellow as x,gray as t,green as u,red as G,white as P}from"../ui/output.mjs";function M(c){const e=Math.floor((Date.now()-c)/1e3);return e<60?"just now":e<3600?`${Math.floor(e/60)}m ago`:e<86400?`${Math.floor(e/3600)}h ago`:`${Math.floor(e/86400)}d ago`}function R(c,e){const g=new Set;if(o.existsSync(c))for(const i of o.readdirSync(c).filter(f=>f.endsWith(".json")))try{(JSON.parse(o.readFileSync(a.join(c,i),"utf8")).capabilitiesCovered||[]).forEach(l=>g.add(l))}catch{}return{covered:e.filter(i=>g.has(i)),uncovered:e.filter(i=>!g.has(i))}}async function I(c=[]){const e=c.includes("--json"),g=process.cwd(),i=a.join(g,"inferno");e||A("status"),o.existsSync(i)||(e&&(console.log(JSON.stringify({ok:!1,error:"inferno_not_found",hint:"Run: infernoflow init"},null,2)),process.exit(1)),j("inferno/ not found","Run: infernoflow init"),console.log(),process.exit(1));const f=a.join(i,"contract.json");o.existsSync(f)||(e&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),j("contract.json not found"),console.log(),process.exit(1));const l=JSON.parse(o.readFileSync(f,"utf8")),y=l.capabilities||[],N=o.statSync(f),$=a.join(i,"scenarios"),S=a.join(i,"CHANGELOG.md"),b=a.join(i,"capabilities.json"),{covered:F,uncovered:d}=R($,y),O=o.existsSync(S)&&/##\s+Unreleased/i.test(o.readFileSync(S,"utf8")),h=[];d.length>0&&h.push(`${d.length} capabilities without scenario coverage`),O||h.push("CHANGELOG missing ## Unreleased section");const v=h.length===0;if(e){const n={ok:v,driftReasons:h,project:{policyId:l.policyId||null,policyVersion:l.policyVersion||null,lastChange:M(N.mtimeMs)},capabilities:{total:y.length,uncovered:d},changelog:{hasUnreleased:O}};console.log(JSON.stringify(n,null,2)),process.exit(v?0:1)}v||(p("Drift"),h.forEach(n=>console.log(` ${x("\u26A0")} ${n}`))),p("Project"),console.log(` ${t("policy")} ${m(l.policyId||"\u2014")}`),console.log(` ${t("version")} ${m("v"+(l.policyVersion||"?"))}`),console.log(` ${t("last change")} ${t(M(N.mtimeMs))}`),p(`Capabilities ${t("("+y.length+")")}`);let E={};if(o.existsSync(b))try{(JSON.parse(o.readFileSync(b,"utf8")).capabilities||[]).forEach(s=>{E[s.id]=s})}catch{}if(y.forEach(n=>{const s=E[n],C=F.includes(n)?u("\u2714"):G("\u2718"),w=s?.title?t(` \u2014 ${s.title}`):"",U=s?.since?t(` [${s.since}]`):"";console.log(` ${C} ${P(n)}${w}${U}`)}),d.length>0?console.log(`
|
|
2
|
+
${x("\u26A0")} ${d.length} capability(ies) lack scenario coverage`):console.log(`
|
|
3
|
+
${u("\u2714")} All capabilities have scenario coverage`),p("Scenarios"),o.existsSync($)){const n=o.readdirSync($).filter(s=>s.endsWith(".json"));n.length===0?J("No scenario files \u2014 add .json files to inferno/scenarios/"):n.forEach(s=>{try{const r=JSON.parse(o.readFileSync(a.join($,s),"utf8")),C=r.steps?.length||0,w=(r.capabilitiesCovered||[]).length;console.log(` ${u("\u2714")} ${k(s)} ${t(`\u2014 ${C} steps, ${w} caps covered`)}`)}catch{console.log(` ${G("\u2718")} ${k(s)} ${t("\u2014 invalid JSON")}`)}})}else J("scenarios/ directory not found");if(p("Changelog"),o.existsSync(S)){const n=o.readFileSync(S,"utf8");/##\s+Unreleased/i.test(n)?H("Has ## Unreleased section"):j("Missing ## Unreleased section"),n.split(`
|
|
4
|
+
`).filter(r=>/^##\s/.test(r)).slice(0,3).forEach(r=>console.log(` ${t(r)}`))}else j("inferno/CHANGELOG.md not found");console.log(),console.log(v?` ${u("\u25CF")} ${m(u("ready"))} ${t("\u2014 run infernoflow check for full validation")}`:` ${x("\u25CF")} ${m(x("needs attention"))} ${t("\u2014 run infernoflow check for details")}`),console.log()}export{I as statusCommand};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import*as c from"node:fs";import*as l from"node:path";import*as J from"node:readline";import{header as V,ok as E,warn as F,info as U,done as R,section as D,nextSteps as W,bold as H,cyan as C,gray as A,yellow as Y,green as L,red as G,errorAndExit as I}from"../ui/output.mjs";function v(n){try{return JSON.parse(c.readFileSync(n,"utf8"))}catch{return null}}function T(n,e){return new Promise(i=>{n.question(e,d=>i(d.trim()))})}function q(n){return n.replace(/[-_]+/g," ").split(" ").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function z({description:n,contract:e,capabilities:i,scenarios:d}){const g=e.capabilities||[],f=(i?.capabilities||[]).map(u=>` - ${u.id}: ${u.title||u.id}`).join(`
|
|
2
|
+
`),s=d.map(u=>{const w=(u.capabilitiesCovered||[]).join(", "),y=(u.steps||[]).map(h=>` {action: "${h.action}", expect: "${h.expect}"}`).join(`
|
|
3
|
+
`);return` File: ${u._file}
|
|
4
|
+
capabilitiesCovered: [${w}]
|
|
5
|
+
steps:
|
|
6
|
+
${y}`}).join(`
|
|
7
|
+
|
|
8
|
+
`);return`You are a developer assistant for the infernoflow CLI tool.
|
|
9
|
+
|
|
10
|
+
Your job is to analyze a code change description and suggest updates to the infernoflow contract files.
|
|
11
|
+
|
|
12
|
+
## Current contract state
|
|
13
|
+
|
|
14
|
+
policyId: ${e.policyId}
|
|
15
|
+
policyVersion: ${e.policyVersion}
|
|
16
|
+
capabilities: [${g.join(", ")}]
|
|
17
|
+
|
|
18
|
+
## Current capabilities registry
|
|
19
|
+
${f||" (none)"}
|
|
20
|
+
|
|
21
|
+
## Current scenarios
|
|
22
|
+
${s||" (none)"}
|
|
23
|
+
|
|
24
|
+
## Developer's description of what changed
|
|
25
|
+
"${n}"
|
|
26
|
+
|
|
27
|
+
## Your task
|
|
28
|
+
|
|
29
|
+
Respond with ONLY a valid JSON object (no markdown, no explanation) in this exact format:
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
"summary": "one-line summary of what changed",
|
|
33
|
+
"newCapabilities": [
|
|
34
|
+
{ "id": "CapabilityName", "title": "Human readable title", "reason": "why this is a new capability" }
|
|
35
|
+
],
|
|
36
|
+
"removedCapabilities": ["CapabilityId"],
|
|
37
|
+
"updatedScenarios": [
|
|
38
|
+
{
|
|
39
|
+
"file": "existing_scenario_filename.json or new_scenario_name.json",
|
|
40
|
+
"isNew": false,
|
|
41
|
+
"capabilitiesCovered": ["CapabilityId1", "CapabilityId2"],
|
|
42
|
+
"stepsToAdd": [
|
|
43
|
+
{ "action": "CapabilityId", "expect": "what should happen" }
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"changelogEntry": "- Short description of the change for CHANGELOG.md"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Rules:
|
|
51
|
+
- Only suggest capabilities that are genuinely new behaviors the system gains
|
|
52
|
+
- Capability IDs must be PascalCase (e.g. SendEmail, not send_email)
|
|
53
|
+
- If nothing changed capability-wise, return empty arrays
|
|
54
|
+
- changelogEntry should start with "- "
|
|
55
|
+
- Keep it minimal and accurate`}function M(n){const e=[];if(!n||typeof n!="object")return["AI response must be a JSON object."];n.summary!=null&&typeof n.summary!="string"&&e.push('"summary" must be a string.'),Array.isArray(n.newCapabilities)||e.push('"newCapabilities" must be an array.'),Array.isArray(n.removedCapabilities)||e.push('"removedCapabilities" must be an array.'),Array.isArray(n.updatedScenarios)||e.push('"updatedScenarios" must be an array.'),n.changelogEntry!=null&&typeof n.changelogEntry!="string"&&e.push('"changelogEntry" must be a string.');for(const i of n.newCapabilities||[]){if(!i||typeof i!="object"){e.push('Each item in "newCapabilities" must be an object.');continue}(typeof i.id!="string"||!/^[A-Z][A-Za-z0-9]*$/.test(i.id))&&e.push("newCapabilities[].id must be PascalCase (example: SendEmail)."),(typeof i.title!="string"||!i.title.trim())&&e.push("newCapabilities[].title must be a non-empty string.")}for(const i of n.removedCapabilities||[])(typeof i!="string"||!i.trim())&&e.push("removedCapabilities[] must contain non-empty strings.");for(const i of n.updatedScenarios||[]){if(!i||typeof i!="object"){e.push('Each item in "updatedScenarios" must be an object.');continue}(typeof i.file!="string"||!i.file.endsWith(".json"))&&e.push("updatedScenarios[].file must be a .json filename."),typeof i.isNew!="boolean"&&e.push("updatedScenarios[].isNew must be boolean."),(!Array.isArray(i.capabilitiesCovered)||!Array.isArray(i.stepsToAdd))&&e.push("updatedScenarios[].capabilitiesCovered and stepsToAdd must be arrays.")}return e}function Z(n,e){const i=[],d=new Set(n.capabilities||[]),g=new Set((e.newCapabilities||[]).map(s=>s.id)),f=new Set(e.removedCapabilities||[]);for(const s of g)f.has(s)&&i.push(`Capability "${s}" appears in both newCapabilities and removedCapabilities.`),d.has(s)&&i.push(`Capability "${s}" already exists in contract capabilities.`);for(const s of f)d.has(s)||i.push(`Capability "${s}" cannot be removed because it does not exist in contract.`);return i}function K({cwd:n,contract:e,capabilities:i,suggestion:d,version:g,quiet:f=!1}){const s=l.join(n,"inferno"),u=l.join(s,"contract.json"),w=l.join(s,"capabilities.json"),y=l.join(s,"CHANGELOG.md"),h=l.join(s,"scenarios"),m=d.newCapabilities||[],S=d.removedCapabilities||[],k=d.updatedScenarios||[],P=d.changelogEntry||"";let $=!1;const N=[],p=(t,o)=>N.push({filePath:t,content:o});if(m.length>0||S.length>0){const t=[...e.capabilities.filter(b=>!S.includes(b)),...m.map(b=>b.id)],o=Number(e.policyVersion||1)+1,r={...e,capabilities:t,policyVersion:o};p(u,JSON.stringify(r,null,2)+`
|
|
56
|
+
`),f||E(`contract.json updated \u2192 policyVersion: v${o}`),$=!0}if(m.length>0||S.length>0){const t=i?{...i}:{schemaVersion:1,capabilities:[]};t.capabilities=(t.capabilities||[]).filter(o=>!S.includes(o.id));for(const o of m)t.capabilities.find(r=>r.id===o.id)||t.capabilities.push({id:o.id,title:o.title,since:g});p(w,JSON.stringify(t,null,2)+`
|
|
57
|
+
`),f||E("capabilities.json updated")}for(const t of k){const o=l.join(h,t.file);let r;if(t.isNew||!c.existsSync(o))r={scenarioId:t.file.replace(".json",""),description:d.summary||"",capabilitiesCovered:t.capabilitiesCovered||[],steps:t.stepsToAdd||[]},p(o,JSON.stringify(r,null,2)+`
|
|
58
|
+
`),f||E(`Created scenario: ${C(t.file)}`);else{r=v(o);const b=new Set(r.capabilitiesCovered||[]);(t.capabilitiesCovered||[]).forEach(O=>b.add(O)),r.capabilitiesCovered=[...b],r.steps=[...r.steps||[],...t.stepsToAdd||[]],p(o,JSON.stringify(r,null,2)+`
|
|
59
|
+
`),f||E(`Updated scenario: ${C(t.file)}`)}$=!0}if(P&&c.existsSync(y)){let t=c.readFileSync(y,"utf8");/##\s+Unreleased/i.test(t)&&(t=t.replace(/(##\s+Unreleased[^\n]*\n)/i,`$1
|
|
60
|
+
${P}
|
|
61
|
+
`),p(y,t),f||E("CHANGELOG.md updated"),$=!0)}const x=new Map;try{for(const t of N){c.existsSync(t.filePath)?x.set(t.filePath,c.readFileSync(t.filePath,"utf8")):x.set(t.filePath,null);const o=`${t.filePath}.tmp`;c.writeFileSync(o,t.content),c.renameSync(o,t.filePath)}}catch(t){for(const[o,r]of x.entries())r===null?c.existsSync(o)&&c.unlinkSync(o):c.writeFileSync(o,r);throw new Error(`Failed applying changes. Rolled back. Details: ${t.message}`)}return $}function B(n){const e=String(n||"").trim().replace(/^```json?\n?/,"").replace(/\n?```$/,"");return JSON.parse(e)}function ee(n){const e=l.join(n,"inferno"),i=l.join(e,"contract.json"),d=l.join(e,"capabilities.json"),g=l.join(e,"scenarios"),f=v(i),s=v(d),u=[];if(c.existsSync(g))for(const h of c.readdirSync(g).filter(m=>m.endsWith(".json"))){const m=v(l.join(g,h));m&&u.push({...m,_file:h})}let w="0.1.0";const y=l.join(n,"package.json");if(c.existsSync(y)){const h=v(y);h?.version&&(w=h.version)}return{contract:f,capabilities:s,scenarios:u,version:w}}async function te(n){const e=process.cwd(),i=l.join(e,"inferno");V("suggest"),c.existsSync(i)||I("inferno/ not found","Run: infernoflow init");const d=l.join(i,"contract.json"),g=l.join(i,"capabilities.json"),f=l.join(i,"scenarios"),s=v(d);s||I("contract.json not found or invalid");const u=v(g),w=[];if(c.existsSync(f))for(const a of c.readdirSync(f).filter(j=>j.endsWith(".json"))){const j=v(l.join(f,a));j&&w.push({...j,_file:a})}let y="0.1.0";const h=l.join(e,"package.json");if(c.existsSync(h)){const a=v(h);a?.version&&(y=a.version)}let S=n.filter(a=>!a.startsWith("-")).slice(1).join(" ");if(!S){const a=J.createInterface({input:process.stdin,output:process.stdout});console.log(A(" Describe what changed in your code (e.g. 'added email notifications'):")),S=await T(a,` ${C(">")} `),a.close(),console.log()}S||I("No description provided",'Usage: infernoflow suggest "what changed"');const k=z({description:S,contract:s,capabilities:u,scenarios:w});D("Generated Prompt"),console.log(),console.log(A("\u2500".repeat(50))),console.log(k),console.log(A("\u2500".repeat(50))),console.log(),U("Copy the prompt above and paste it into:"),console.log(` ${C("\u2022")} Claude \u2192 https://claude.ai`),console.log(` ${C("\u2022")} ChatGPT \u2192 https://chatgpt.com`),console.log(` ${C("\u2022")} Copilot, Cursor, or any AI you use`),console.log(),F("The AI will respond with a JSON object."),console.log();const P=J.createInterface({input:process.stdin,output:process.stdout});console.log(A(" Paste the AI's JSON response below, then press Enter twice:")),console.log();let $="",N=0;await new Promise(a=>{P.on("line",j=>{j.trim()===""?(N++,N>=2&&$.trim()&&a()):(N=0,$+=j+`
|
|
62
|
+
`)}),P.on("close",a)}),P.close();let p;try{p=B($)}catch{I("Could not parse the AI response as JSON","Make sure you copied the full JSON response from the AI")}const x=M(p);x.length>0&&I("AI response schema is invalid",x[0]+(x.length>1?` (+${x.length-1} more)`:""));const t=Z(s,p);t.length>0&&I("AI response contains conflicting capability operations",t[0]+(t.length>1?` (+${t.length-1} more)`:"")),D("Proposed Changes"),console.log(),p.summary&&(console.log(` ${H("Summary:")} ${p.summary}`),console.log());const o=p.newCapabilities||[],r=p.removedCapabilities||[],b=p.updatedScenarios||[];o.length===0&&r.length===0&&b.length===0&&(E("No capability changes detected \u2014 nothing to apply."),console.log(),process.exit(0)),o.length>0&&(console.log(` ${L("+")} New capabilities:`),o.forEach(a=>console.log(` ${L(a.id)} \u2014 ${A(a.title)}`)),console.log()),r.length>0&&(console.log(` ${G("-")} Removed capabilities:`),r.forEach(a=>console.log(` ${G(a)}`)),console.log()),b.length>0&&(console.log(` ${C("~")} Scenario updates:`),b.forEach(a=>{const j=a.isNew?L("[new]"):C("[update]");console.log(` ${j} ${a.file}`)}),console.log()),p.changelogEntry&&(console.log(` ${Y("\u{1F4DD}")} Changelog: ${A(p.changelogEntry)}`),console.log());const O=J.createInterface({input:process.stdin,output:process.stdout}),_=await T(O,` Apply these changes? ${A("(y/n)")} `);O.close(),console.log(),_.toLowerCase()!=="y"&&_.toLowerCase()!=="yes"&&(F("Cancelled \u2014 no changes made."),console.log(),process.exit(0)),D("Applying Changes"),console.log(),K({cwd:e,contract:s,capabilities:u,suggestion:p,version:y}),R("suggest complete!"),W([C("infernoflow status")+" \u2014 verify the updated contract",C("infernoflow check")+" \u2014 validate everything"])}export{K as applyChanges,z as buildPrompt,Z as detectSuggestionConflicts,ee as loadSuggestContext,B as parseSuggestionJson,v as readJson,te as suggestCommand,M as validateSuggestion};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{execFileSync as w}from"node:child_process";import*as y from"node:path";import{fileURLToPath as P}from"node:url";import{header as h,section as d,ok as e,warn as f,yellow as g,gray as p}from"../ui/output.mjs";const S=P(import.meta.url),C=y.dirname(S),N=y.resolve(C,"..","..","bin","infernoflow.mjs");function D(o){const n=w(process.execPath,[N,...o],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return JSON.parse(n)}function _(o){try{return{ok:!0,data:D(o)}}catch(n){const s=n?.stdout?.toString?.()||"";try{return{ok:!1,data:JSON.parse(s)}}catch{return{ok:!1,data:{ok:!1,errors:["command_failed"]}}}}}async function E(o=[]){const n=o.includes("--auto"),s=o.includes("--json"),u=o.includes("--dry-run");n||(s&&(console.log(JSON.stringify({ok:!1,error:"missing_required_flag",hint:"Use: infernoflow sync --auto"},null,2)),process.exit(1)),h("sync"),f("missing --auto flag"),console.log(` ${g("\u2192")} infernoflow sync --auto`),console.log(),process.exit(1));const c=_(["pr-impact","--json"]),i=!c.data?.ok,a=c.data?.confidence||"low",r=a==="high"?"auto":a==="medium"?"ask":"block",k=i?["Generate inferno update proposal (suggest)","Review changes","Validate with check --json"]:["No inferno drift detected","Validate with check --json"],t=_(["check","--json"]),l={ok:c.ok&&t.ok&&!!t.data?.ok,mode:"auto-skeleton",dryRun:u,needsSync:i,didApply:!1,confidence:a,policyDecision:r,actions:k,prImpact:c.data,postCheck:t.data,reasonCodes:[...i?["DRIFT_DETECTED"]:["NO_DRIFT"],`POLICY_${r.toUpperCase()}`,...r==="auto"?["AUTO_APPLY_DISABLED_IN_SKELETON"]:[]]};s&&(console.log(JSON.stringify(l,null,2)),process.exit(l.ok?0:1)),h("sync --auto"),d("State"),i?f("Inferno drift detected"):e("No inferno drift detected"),e(`Confidence: ${p(a)}`),e(`Policy decision: ${p(r)}`),e(`Apply mode: ${p("skeleton (no file writes)")}`),u&&e("Dry run enabled"),d("Plan"),k.forEach(m=>console.log(` ${g("\u2192")} ${m}`)),d("Validation"),t.ok&&t.data?.ok?e("Post-check passed"):f("Post-check failed; see infernoflow check --json"),console.log(),process.exit(l.ok?0:1)}export{E as syncCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"node:fs";import*as o from"node:path";import{installInfernoDraftTooling as d}from"./draftToolingInstall.mjs";function h(n){const{cwd:s,templatesRoot:i,force:c,silent:t}=n,f=n.logOk||(()=>{}),a=n.logWarn||(()=>{});function l(p,r){return e.existsSync(r)&&!c?(t||a("Skipped (exists): "+o.relative(s,r)),!1):(e.mkdirSync(o.dirname(r),{recursive:!0}),e.copyFileSync(p,r),t||f("Created: "+o.relative(s,r)),!0)}d({cwd:s,templatesRoot:i,force:c,silent:t,logOk:f,logWarn:a});const k=o.join(i,"cursor","hooks.json"),u=o.join(s,".cursor","hooks.json"),m=o.join(i,"cursor","hooks","inferno-session-draft.mjs"),j=o.join(s,".cursor","hooks","inferno-session-draft.mjs");l(k,u),l(m,j)}export{h as installCursorHooksArtifacts};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import*as t from"node:fs";import*as n from"node:path";const d=`
|
|
2
|
+
# infernoflow: agent draft (IDE hooks \u2014 review before commit)
|
|
3
|
+
inferno/CONTEXT.draft.md
|
|
4
|
+
`.trimStart();function S(o,e,c){const s=n.join(o,"package.json");if(!t.existsSync(s)){e||c("No package.json \u2014 add script manually: inferno:promote-draft");return}const r=JSON.parse(t.readFileSync(s,"utf8"));r.scripts=r.scripts||{},r.scripts["inferno:promote-draft"]||(r.scripts["inferno:promote-draft"]="node scripts/inferno-promote-draft.mjs",t.writeFileSync(s,JSON.stringify(r,null,2)+`
|
|
5
|
+
`,"utf8"),e||c("Updated package.json script: inferno:promote-draft"))}function y(o){const{cwd:e,templatesRoot:c,force:s,silent:r}=o,f=o.logOk||(()=>{}),l=o.logWarn||(()=>{});function m(p,a){return t.existsSync(a)&&!s?(r||l("Skipped (exists): "+n.relative(e,a)),!1):(t.mkdirSync(n.dirname(a),{recursive:!0}),t.copyFileSync(p,a),r||f("Created: "+n.relative(e,a)),!0)}const u=n.join(c,"scripts","inferno-promote-draft.mjs"),g=n.join(e,"scripts","inferno-promote-draft.mjs");m(u,g),S(e,r,f);const i=n.join(e,".gitignore");t.existsSync(i)?t.readFileSync(i,"utf8").includes("CONTEXT.draft.md")?r||f(".gitignore already mentions CONTEXT.draft.md"):(t.appendFileSync(i,`
|
|
6
|
+
${d}
|
|
7
|
+
`,"utf8"),r||f("Updated: "+n.relative(e,i))):(t.writeFileSync(i,`${d}
|
|
8
|
+
`,"utf8"),r||f("Created: "+n.relative(e,i)))}export{y as installInfernoDraftTooling};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const e={reset:"\x1B[0m",bold:"\x1B[1m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",cyan:"\x1B[36m",white:"\x1B[37m",gray:"\x1B[90m",orange:"\x1B[38;5;208m"},p=!process.stdout.isTTY||process.env.NO_COLOR;function r(o,n){return p?n:`${o}${n}${e.reset}`}const c=o=>r(e.bold,o),l=o=>r(e.red,o),g=o=>r(e.green,o),s=o=>r(e.yellow,o),i=o=>r(e.cyan,o),t=o=>r(e.gray,o),b=o=>r(e.white,o),u=o=>r(e.orange,o),d=o=>r(e.bold+e.red,o),f=o=>r(e.bold+e.green,o),m=o=>r(e.bold+e.yellow,o),a=o=>r(e.bold+e.orange,o);function w(o){return o.replace(/\x1b\[[0-9;]*m/g,"")}function y(o){const n=a("\u{1F525} infernoflow")+t(" \u2014 "+o);console.log(`
|
|
2
|
+
`+n),console.log(t("\u2500".repeat(50)))}function O(o){console.log(" "+g("\u2714")+" "+o)}function h(o,n){console.log(" "+l("\u2718")+" "+l(o)),n&&console.log(" "+t("\u2192 "+n))}function E(o){console.log(" "+s("\u26A0")+" "+s(o))}function $(o){console.log(" "+i("\u2139")+" "+o)}function C(o){console.log(`
|
|
3
|
+
`+c(b(o)))}function N(o){console.log(`
|
|
4
|
+
`+f("\u2728 "+o)+`
|
|
5
|
+
`)}function R(o){console.log(c("Next steps:")),o.forEach((n,x)=>{console.log(" "+t(x+1+".")+" "+n)}),console.log()}function T(o,n){console.error(`
|
|
6
|
+
`+d("Error: ")+l(o)),n&&console.error(t(" \u2192 "+n)),console.error(),process.exit(1)}export{c as bold,f as boldGreen,a as boldOrange,d as boldRed,m as boldYellow,i as cyan,N as done,T as errorAndExit,h as fail,t as gray,g as green,y as header,$ as info,R as nextSteps,O as ok,u as orange,l as red,C as section,E as warn,b as white,s as yellow};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import*as f from"node:readline";import*as d from"node:fs";import*as a from"node:path";function m(e,n=""){return new Promise(t=>{const o=n?` (${n})`:"",i=f.createInterface({input:process.stdin,output:process.stdout});i.question(` ${e}${o}: `,r=>{i.close(),t(r.trim()||n)})})}async function b(){const e=await m("Project / policy name",process.env._INFERNO_DEFAULT_POLICY||"my-project"),n=await m("Capabilities (comma-separated)","CreateTask, ReadTasks, UpdateTask, DeleteTask");return{policyId:e,capabilities:n.split(",").map(t=>t.trim()).filter(Boolean)}}function p(e){try{return JSON.parse(d.readFileSync(e,"utf8"))}catch{return null}}function k(e){const n=a.join(e,"inferno"),t=p(a.join(n,"contract.json"))||{},o=p(a.join(n,"capabilities.json"))||{capabilities:[]},i=p(a.join(n,"context-state.json"))||{},r=a.join(n,"scenarios"),c=[];if(d.existsSync(r))for(const l of d.readdirSync(r).filter(s=>s.endsWith(".json"))){const s=p(a.join(r,l));s&&c.push({file:l,scenario:s})}return{contract:t,caps:o,state:i,scenarios:c}}function y(e){const n=e?.capabilities||[];return n.length===0?"- none":n.map(t=>`- ${t.id}: ${t.title||t.id}`).join(`
|
|
2
|
+
`)}function h(e){return!e||e.length===0?"- none":e.map(({file:n,scenario:t})=>{const o=(t.capabilitiesCovered||[]).join(", ")||"none";return`- ${n}: covers [${o}]`}).join(`
|
|
3
|
+
`)}function u({contract:e,caps:n,scenarios:t,state:o}){const i=e?.policyId||"unknown-policy",r=e?.policyVersion??"unknown",c=(e?.capabilities||[]).join(", ")||"none",l=o?.working||"not set",s=o?.intent||"not set";return[`Project policyId: ${i}`,`Policy version: ${r}`,`Declared capabilities: [${c}]`,`Working on: ${l}`,`Intent: ${s}`,"","Capabilities registry:",y(n),"","Scenarios:",h(t)].join(`
|
|
4
|
+
`)}function j({task:e,contract:n,caps:t,scenarios:o,state:i}){return["You are a Cursor coding agent working inside my repository.","Implement the task end-to-end with minimal reliable changes.","",u({contract:n,caps:t,scenarios:o,state:i}),"",`Task: ${e}`,"","Requirements:","1) Propose smallest safe implementation.","2) Explain which files you changed and why.","3) Implement production-ready code.","4) Preserve backward compatibility unless explicitly requested.","5) Update tests or add smoke checks.","6) Provide run/verify commands.","7) If assumptions are needed, state briefly and proceed with sensible defaults.","","Output format:","- Plan (short)","- Code changes (by file)","- Tests updated/added","- Commands to run","- Acceptance checklist","","Quality bar:","- No TODO placeholders in final code","- Handle edge cases and errors","- Keep naming/style consistent","- Prefer simple maintainable solutions","","If model is overloaded (resource exhausted), retry with Auto/another model and continue deterministically."].join(`
|
|
5
|
+
`)}function g({task:e,contract:n,caps:t,scenarios:o,state:i}){return["You are my senior software engineer pair.","Implement this task end-to-end in my project.","",u({contract:n,caps:t,scenarios:o,state:i}),"",`Goal: ${e}`,"","Deliverables:","- Short implementation plan","- Exact file-level changes","- Test updates","- Verification commands","- Final acceptance checklist","","Constraints:","- Keep backward compatibility by default","- Make minimal reliable changes","- Handle edge cases and error states","- Keep output concise and actionable","","If you encounter temporary model high-load errors, retry and preserve the same output structure."].join(`
|
|
6
|
+
`)}export{j as buildCursorImplementPrompt,g as buildGenericImplementPrompt,k as loadImplementContext,b as promptInit};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"node:fs";import*as o from"node:path";import{installInfernoDraftTooling as j}from"./draftToolingInstall.mjs";function h(t){const{cwd:i,templatesRoot:r,force:c,silent:s}=t,f=t.logOk||(()=>{}),l=t.logWarn||(()=>{});function a(d,n){return e.existsSync(n)&&!c?(s||l("Skipped (exists): "+o.relative(i,n)),!1):(e.mkdirSync(o.dirname(n),{recursive:!0}),e.copyFileSync(d,n),s||f("Created: "+o.relative(i,n)),!0)}j({cwd:i,templatesRoot:r,force:c,silent:s,logOk:f,logWarn:l});const p=o.join(r,"github-hooks","infernoflow-drafts.json"),k=o.join(i,".github","hooks","infernoflow-drafts.json"),m=o.join(r,"scripts","inferno-vscode-copilot-hook.mjs"),u=o.join(i,"scripts","inferno-vscode-copilot-hook.mjs");a(p,k),a(m,u)}export{h as installVsCodeCopilotHooksArtifacts};
|
package/package.json
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "infernoflow",
|
|
3
|
+
"version": "0.10.14",
|
|
4
|
+
"description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"infernoflow": "dist/bin/infernoflow.mjs"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/bin",
|
|
14
|
+
"dist/lib",
|
|
15
|
+
"dist/templates",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node scripts/smoke.mjs && node scripts/json-smoke.mjs && node scripts/json-negative-smoke.mjs && node scripts/implement-smoke.mjs && node scripts/adopt-smoke.mjs && node scripts/pr-impact-smoke.mjs && node scripts/sync-smoke.mjs && node scripts/run-smoke.mjs",
|
|
21
|
+
"test:help": "node bin/infernoflow.mjs --help",
|
|
22
|
+
"build": "node build.mjs",
|
|
23
|
+
"prepublishOnly": "node build.mjs"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"cli",
|
|
27
|
+
"capabilities",
|
|
28
|
+
"contract",
|
|
29
|
+
"documentation",
|
|
30
|
+
"ai",
|
|
31
|
+
"liquid-code",
|
|
32
|
+
"dx",
|
|
33
|
+
"developer-tools"
|
|
34
|
+
],
|
|
35
|
+
"author": "infernoflow",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/ronmiz/infernoflow.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/ronmiz/infernoflow#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/ronmiz/infernoflow/issues"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"esbuild": "^0.28.0"
|
|
47
|
+
}
|
|
48
|
+
}
|