infernoflow 0.43.8 → 0.43.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as s from"node:path";import*as
|
|
2
|
-
`,"utf8"),o||g("Updated "+d("package.json")+" scripts"))}const
|
|
1
|
+
import*as i from"node:fs";import*as s from"node:path";import*as L from"node:readline";import{fileURLToPath as ae}from"node:url";import{header as pe,ok as g,warn as P,done as q,nextSteps as fe,bold as D,cyan as d,yellow as $,gray as I}from"../ui/output.mjs";import{discoverProjectSignals as J,reviewCapabilitiesInteractive as de,writeAdoptionBaseline as ue,buildAdoptionReport as me,summarizeCapabilities as ye,buildSignalsReport as ge}from"./adopt.mjs";import{installCursorHooksArtifacts as he}from"../cursorHooksInstall.mjs";import{ensureAmpDir as we,appendEntry as ke,writeDefaultConfig as je}from"../amp/io.mjs";import{installVsCodeCopilotHooksArtifacts as Se}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as z}from"../ruleFiles.mjs";import{autoSetupMcp as V}from"./setup.mjs";const ve=s.dirname(ae(import.meta.url));function be(){return s.resolve(ve,"../../templates")}function G(e,o,t=""){return new Promise(n=>{const r=t?I(` (${t})`):"";e.question(` ${o}${r}: `,l=>{n(l.trim()||t)})})}function W(e,...o){for(const t of o){const n=e.indexOf(t);if(n!==-1&&e[n+1]&&!e[n+1].startsWith("-"))return e[n+1]}return null}function H(e,o,t,n=!1){return i.existsSync(o)&&!t?(n||P("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),n||g("Created: "+d(s.relative(process.cwd(),o))),!0)}function Ce(e,o,t){i.mkdirSync(o,{recursive:!0});for(const n of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,n.name),l=s.join(o,n.name);n.isDirectory()?Ce(r,l,t):H(r,l,t)}}function Oe(e,o=!1){const t=s.join(e,"package.json");if(!i.existsSync(t))return;const n=JSON.parse(i.readFileSync(t,"utf8"));n.scripts=n.scripts||{};let r=!1;const l={"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,k]of Object.entries(l))n.scripts[y]||(n.scripts[y]=k,r=!0);r&&(i.writeFileSync(t,JSON.stringify(n,null,2)+`
|
|
2
|
+
`,"utf8"),o||g("Updated "+d("package.json")+" scripts"))}const X="# --- infernoflow (developer-local AI memory; do not commit) ---",xe="# --- /infernoflow ---",Ie=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function B(e,{silent:o=!1}={}){const t=s.join(e,".gitignore");let n="";if(i.existsSync(t)&&(n=i.readFileSync(t,"utf8")),n.includes(X))return!1;const r=["",X,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...Ie,xe,""].join(`
|
|
3
3
|
`),l=n.length===0||n.endsWith(`
|
|
4
4
|
`)?"":`
|
|
5
|
-
`;return i.writeFileSync(t,n+l+r,"utf8"),o||g("Updated "+d(".gitignore")+
|
|
6
|
-
`)}function
|
|
7
|
-
`)}function
|
|
8
|
-
`)}function
|
|
5
|
+
`;return i.writeFileSync(t,n+l+r,"utf8"),o||g("Updated "+d(".gitignore")+I(" (memory stays local across branch switches)")),!0}function M(e){const o=s.join(e,"package.json");if(i.existsSync(o))try{const t=JSON.parse(i.readFileSync(o,"utf8"));if(t.name)return t.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(e)}function Te(e,o,t){const n={policyId:o,policyVersion:1,capabilities:t,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(n,null,2)+`
|
|
6
|
+
`)}function Ae(e,o){const t={schemaVersion:1,capabilities:o.map(n=>({id:n,title:n.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(t,null,2)+`
|
|
7
|
+
`)}function Ee(e,o){i.mkdirSync(e,{recursive:!0});const t={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(n=>({action:n,expect:`${n} works as expected`}))};i.writeFileSync(s.join(e,"happy_path.json"),JSON.stringify(t,null,2)+`
|
|
8
|
+
`)}function Z(e,o){const t=`# Changelog \u2014 ${o}
|
|
9
9
|
|
|
10
10
|
## Unreleased
|
|
11
11
|
|
|
@@ -14,41 +14,41 @@ import*as i from"node:fs";import*as s from"node:path";import*as G from"node:read
|
|
|
14
14
|
## 0.1.0 \u2014 Initial release
|
|
15
15
|
|
|
16
16
|
- Project initialized with infernoflow
|
|
17
|
-
`;i.writeFileSync(e,t)}async function
|
|
17
|
+
`;i.writeFileSync(e,t)}async function Pe(e,o){const{bold:t,cyan:n,gray:r,green:l,yellow:y,red:k}=await import("../ui/output.mjs");console.log(`
|
|
18
18
|
`+t("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
|
|
19
19
|
`),console.log(r(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(r(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const p=s.join(e,"inferno");i.existsSync(p)&&!o&&(console.log(y(` \u26A0 inferno/ already exists. Use --force to overwrite.
|
|
20
|
-
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const
|
|
20
|
+
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const h=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const C=L.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(b=>{C.question(r(" What does this project do? (one line, Enter to skip): "),O=>{C.close(),b(O.trim())})})}const T={policyId:h,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(T,null,2)+`
|
|
21
21
|
`),i.writeFileSync(s.join(p,"capabilities.json"),JSON.stringify([],null,2)+`
|
|
22
|
-
`),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(l(" \u2714 Created inferno/contract.json")),console.log(l(" \u2714 Created inferno/capabilities.json")),console.log(l(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+n("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+n("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+n("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+n("infernoflow upgrade")+r(" \u2014 expand to full setup when you need it")),console.log()}const
|
|
23
|
-
> `),l=y=>new Promise(k=>{const p=
|
|
22
|
+
`),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(l(" \u2714 Created inferno/contract.json")),console.log(l(" \u2714 Created inferno/capabilities.json")),console.log(l(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+n("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+n("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+n("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+n("infernoflow upgrade")+r(" \u2014 expand to full setup when you need it")),console.log()}const Fe=/^(?:node|npm|npx|yarn|pnpm|bun|git|cd|mkdir|rm|ls|cat|echo|type|dir|copy|del|move|python|python3|pip|go|cargo|java|gradle|mvn|docker|kubectl|curl|wget|ssh|scp|chmod|chown|sudo|brew|apt|yum)\b/i,Ne=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Re=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function _e(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:Fe.test(o)?{kind:"command",value:o}:Ne.test(o)?{kind:"command",value:o}:Re.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function $e({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:t,cyan:n}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
|
|
23
|
+
> `),l=y=>new Promise(k=>{const p=L.createInterface({input:process.stdin,output:process.stdout});let h=!1;p.on("SIGINT",()=>{h=!0,p.close(),k(null)}),p.on("close",()=>{h&&k(null)}),p.question(y,S=>{p.close(),k(S)})});for(let y=0;y<2;y++){const k=await l(y===0?r:" "+o("> "));if(k===null)return console.log(),"";const p=_e(k);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+t("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+n('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+t("\u26A0")+" Multi-line paste detected \u2014 log a single gotcha at a time."),console.log(" "+o(" Try one short sentence:"));continue}if(p.kind==="tooShort"){console.log(" "+t("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function Ge(e,o,t){const{bold:n,cyan:r,gray:l,green:y,yellow:k}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),h=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(h))&&!o){const j=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
|
|
24
24
|
`+n("\u{1F525} infernoflow")+l(` \u2014 already set up
|
|
25
|
-
`)),console.log(" "+y("\u2714")+" "+
|
|
26
|
-
`),V(e)
|
|
25
|
+
`)),console.log(" "+y("\u2714")+" "+j+` found
|
|
26
|
+
`),B(e),z(e);try{V(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
|
|
27
27
|
`)),i.existsSync(p)||console.log(l(" Tip: run ")+r("infernoflow amp migrate")+l(` to move legacy memory into .ai-memory/
|
|
28
28
|
`)),console.log(l(` For contracts & CI gates: infernoflow init --mode full
|
|
29
|
-
`));return}const
|
|
29
|
+
`));return}const C=M(e);console.log(`
|
|
30
30
|
`+n("\u{1F525} infernoflow")+l(` \u2014 let's get you set up (30 seconds)
|
|
31
|
-
`)),console.log(" Detected: "+r(
|
|
32
|
-
`),
|
|
31
|
+
`)),console.log(" Detected: "+r(C)+`
|
|
32
|
+
`),we(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),je(e,{project:C,config:{autoCapture:!0}}),B(e);const b=z(e);for(const j of b)(j.created||j.updated)&&g((j.created?"Created: ":"Updated: ")+r(j.rel));try{V(e)}catch(j){P("MCP auto-setup skipped: "+j.message)}const O=await $e({yes:t});O&&(ke(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:O,source:"init"}),console.log(`
|
|
33
33
|
`+y("\u2714")+" First gotcha logged!")),console.log(`
|
|
34
34
|
`+y("\u2714")+` You're set up. Quick commands:
|
|
35
35
|
`),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
|
|
36
36
|
`)),console.log(l(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
|
|
37
37
|
`)),console.log(l(` Want contracts & CI gates? Run: infernoflow init --mode full
|
|
38
|
-
`))}async function
|
|
39
|
-
`),i.writeFileSync(s.join(m,"capabilities.json"),JSON.stringify({capabilities:
|
|
40
|
-
`);for(const f of
|
|
41
|
-
`);
|
|
38
|
+
`))}async function Ve(e){const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),n=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),l=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),y=l==="full"||l==="contract",k=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!y&&!k){await Ge(o,t,n);return}if(e.includes("--lite")){await Pe(o,t);return}const p=e.indexOf("--template"),h=p!==-1?e[p+1]:null;if(h){let a;try{a=await import("../templates/index.mjs")}catch{}const c=a?.getTemplate(h);if(!c){const f=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";P(`Unknown template: ${h}. Available: ${f}`),process.exit(1)}const m=s.join(o,"inferno"),E=s.join(m,"scenarios");i.existsSync(m)||i.mkdirSync(m,{recursive:!0}),i.existsSync(E)||i.mkdirSync(E,{recursive:!0});const _=M(o),w=c.capabilities;i.writeFileSync(s.join(m,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:w.map(f=>f.id)},null,2)+`
|
|
39
|
+
`),i.writeFileSync(s.join(m,"capabilities.json"),JSON.stringify({capabilities:w.map(f=>({id:f.id,description:f.description,since:new Date().toISOString().slice(0,10),source:`template:${h}`}))},null,2)+`
|
|
40
|
+
`);for(const f of w)i.writeFileSync(s.join(E,`${f.id}.json`),JSON.stringify({id:`${f.id}-happy-path`,capability:f.id,description:`Happy path for ${f.description||f.id}`,steps:[{action:"invoke",target:f.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[f.id]},null,2)+`
|
|
41
|
+
`);Z(s.join(m,"CHANGELOG.md"),_),i.writeFileSync(s.join(m,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
|
|
42
42
|
|
|
43
|
-
> Template: ${
|
|
43
|
+
> Template: ${h} \u2014 ${c.description}
|
|
44
44
|
|
|
45
45
|
## Hint
|
|
46
46
|
${c.contextHint}
|
|
47
47
|
|
|
48
|
-
## Capabilities (${
|
|
49
|
-
${
|
|
48
|
+
## Capabilities (${w.length})
|
|
49
|
+
${w.map(f=>`- \`${f.id}\`: ${f.description}`).join(`
|
|
50
50
|
`)}
|
|
51
|
-
`),c.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(c.scripts).forEach(([f,
|
|
52
|
-
`));const
|
|
53
|
-
`)),
|
|
54
|
-
`,"utf8"),u||g("Created: "+d("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(v,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${
|
|
51
|
+
`),c.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(c.scripts).forEach(([f,x])=>console.log(` ${D(f)}: ${I(x)}`)),console.log()),q(`Initialised from template ${D(d(h))} \u2014 ${D(String(w.length))} capabilities`),console.log(),info(`Run ${d("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),T=e.includes("--vscode-copilot-hooks"),C=e.includes("--report-json"),b=e.includes("--report-json-only"),O=e.includes("--report-human-only"),j=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=b;b&&O&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||pe("init");const v=s.join(o,"inferno"),Q=s.join(o,".github","workflows");i.existsSync(v)&&!t&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),P("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const F=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let N=F,A=Y.split(",").map(a=>a.trim());if(r){let c=J(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});if(!n&&!b){const w=L.createInterface({input:process.stdin,output:process.stdout}),f=c.developmentProfile||{},x=f.detected||{};console.log(I(` Review inferred development stack (press Enter to accept detected values)
|
|
52
|
+
`));const re=await G(w,"Language",f.language||x.language||"unknown"),ce=await G(w,"Framework",f.framework||x.framework||"unknown"),le=await G(w,"Project type",f.projectType||x.projectType||"unknown");w.close(),c=J(o,{language:re,framework:ce,projectType:le})}const m=c.capabilities,E=ye(m);b?console.log(JSON.stringify({mode:"adopt",policyId:F,inferredCapabilities:E,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)):(console.log(),console.log(I(me(m))),console.log(),console.log(I(ge(c))),console.log(),C&&!O&&(console.log(JSON.stringify({mode:"adopt",policyId:F,inferredCapabilities:E,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)),console.log()));const _=await de(m,n);N=F,A=_.map(w=>w.id)}else if(!n){const a=L.createInterface({input:process.stdin,output:process.stdout});console.log(I(` Press Enter to accept defaults
|
|
53
|
+
`)),N=await G(a,"Project / policy name",F),A=(await G(a,"Capabilities (comma-separated)",Y)).split(",").map(m=>m.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(v,{recursive:!0}),r){const a=A.map(m=>({id:m,title:m.replace(/([A-Z])/g," $1").trim()})),c=J(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});ue(v,N,a,c),u||(g("Created: "+d("inferno/contract.json")),g("Created: "+d("inferno/capabilities.json")),g("Created: "+d("inferno/scenarios/adoption_baseline.json")),g("Created: "+d("inferno/adoption_profile.json")),g("Created: "+d("inferno/CHANGELOG.md")))}else Te(s.join(v,"contract.json"),N,A),u||g("Created: "+d("inferno/contract.json")),Ae(s.join(v,"capabilities.json"),A),u||g("Created: "+d("inferno/capabilities.json")),Ee(s.join(v,"scenarios"),A),u||g("Created: "+d("inferno/scenarios/happy_path.json")),Z(s.join(v,"CHANGELOG.md"),N),u||g("Created: "+d("inferno/CHANGELOG.md"));const R=be(),ee=s.join(R,"scripts","inferno-doc-gate.mjs"),oe=s.join(o,"scripts","inferno-doc-gate.mjs");H(ee,oe,t,u);const ne=s.join(R,"scripts","inferno-install-hooks.mjs"),te=s.join(o,"scripts","inferno-install-hooks.mjs");H(ne,te,t,u);const ie=s.join(R,"ci","github-inferno-check.yml"),se=s.join(Q,"infernoflow-check.yml");if(H(ie,se,t,u),Oe(o,u),S&&he({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),T&&Se({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),r){const a=s.join(v,"context-state.json");let c={};try{c=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const m=J(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});c.stack=m.developmentProfile,i.writeFileSync(a,JSON.stringify(c,null,2)+`
|
|
54
|
+
`,"utf8"),u||g("Created: "+d("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(v,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${$("\u{1F4A1}")} ${D("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${d("infernoflow ai setup")} \u2014 takes 60 seconds`)),fe([d("infernoflow status")+" \u2014 see your contract at a glance",d("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+$("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+$("inferno/scenarios/*.json")+" files for edge cases","Add "+d("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...T?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!T?["Optional: "+d("infernoflow install-cursor-hooks")+" or "+d("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Ve as initCommand};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
${
|
|
1
|
+
import*as n from"node:fs";import*as l from"node:path";import*as w from"node:os";import{fileURLToPath as C}from"node:url";import{execSync as j}from"node:child_process";import{detectIdeContext as h}from"../ai/ideDetection.mjs";import{header as _,ok as y,warn as x,info as S,done as M,cyan as f,yellow as $,bold as g,green as d}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";const k=l.dirname(C(import.meta.url));function v(){return l.resolve(k,"../../templates")}function J(s){try{return j(`npx infernoflow ${s}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(t){return t.stdout||t.stderr||t.message}}const P=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function O(s){const t=l.join(w.homedir(),".claude.json");let c={};if(n.existsSync(t))try{c=JSON.parse(n.readFileSync(t,"utf8"))}catch{c={}}c.mcpServers||(c.mcpServers={});const o=c.mcpServers.infernoflow;if(o&&o.args&&o.args[0]===s)return{updated:!1};c.mcpServers.infernoflow={command:"node",args:[s]};const r=JSON.stringify(c,null,2).replace(/\u0000+/g,"");return n.writeFileSync(t,r,"utf8"),{updated:!0,path:t}}function F(s,t){const c=l.join(s,".vscode"),o=l.join(c,"mcp.json");let r={};if(n.existsSync(o))try{r=JSON.parse(n.readFileSync(o,"utf8"))}catch{r={}}r.servers||(r.servers={});const i=r.servers.infernoflow;return i&&i.args&&i.args[0]===t?{updated:!1}:(r.servers.infernoflow={type:"stdio",command:"node",args:[t]},n.mkdirSync(c,{recursive:!0}),n.writeFileSync(o,JSON.stringify(r,null,2)+`
|
|
2
|
+
`,"utf8"),{updated:!0,path:o})}function N(s,t){const c=l.join(s,".claude"),o=l.join(c,"settings.json");let r={};if(n.existsSync(o))try{r=JSON.parse(n.readFileSync(o,"utf8"))}catch{r={}}const i=new Set(r.allowedTools||[]);for(const a of P)i.add(`mcp__infernoflow__${a}`);const p={...r,allowedTools:[...i]};return n.mkdirSync(c,{recursive:!0}),n.writeFileSync(o,JSON.stringify(p,null,2),"utf8"),o}function R(s,{silent:t=!1}={}){const c=v(),o=t?()=>{}:e=>y(e),r=t?()=>{}:e=>x(e),i={mcpServer:!1,claudeJson:!1,claudeSettings:!1},p=l.join(c,"cursor","inferno-mcp-server.mjs"),a=l.join(s,".cursor","inferno-mcp-server.mjs");try{n.existsSync(a)||(n.mkdirSync(l.dirname(a),{recursive:!0}),n.copyFileSync(p,a),i.mcpServer=!0,o("Copied MCP server \u2192 "+f(".cursor/inferno-mcp-server.mjs")))}catch(e){r("MCP server copy skipped: "+e.message)}try{O(a).updated&&(i.claudeJson=!0,o("Registered MCP server in "+f("~/.claude.json")))}catch(e){r("~/.claude.json update skipped: "+e.message)}try{F(s,a).updated&&(i.vscodeMcp=!0,o("Registered MCP server in "+f(".vscode/mcp.json")+gray(" (Copilot Chat)")))}catch(e){r(".vscode/mcp.json update skipped: "+e.message)}try{const e=l.join(s,".cursor","mcp.json");let u={};if(n.existsSync(e))try{u=JSON.parse(n.readFileSync(e,"utf8"))}catch{u={}}u.mcpServers||(u.mcpServers={});const m=u.mcpServers.infernoflow;(!m||!m.args||m.args[0]!==a)&&(u.mcpServers.infernoflow={command:"node",args:[a],env:{}},n.mkdirSync(l.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(u,null,2)+`
|
|
3
|
+
`,"utf8"),i.cursorMcp=!0,o("Registered MCP server in "+f(".cursor/mcp.json")))}catch(e){r(".cursor/mcp.json update skipped: "+e.message)}try{N(s,!1),i.claudeSettings=!0,o("Pre-approved infernoflow tools in "+f(".claude/settings.json"))}catch(e){r(".claude/settings.json skipped: "+e.message)}return i}async function H(s){const t=process.cwd(),c=s.includes("--force")||s.includes("-f"),o=s.includes("--yes")||s.includes("-y"),r=v();_("infernoflow setup");const{ideDetected:i}=h("auto");S(`IDE detected: ${g(i==="cursor"?"Cursor":i==="vscode"?"VS Code":i==="windsurf"?"Windsurf":"unknown")}`);const a=l.join(t,".ai-memory");n.existsSync(a)?y(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
|
|
4
|
+
${$(".ai-memory/")} not found \u2014 running init ...
|
|
5
|
+
`),J(o?"init --yes":"init")),console.log(),S("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const e=R(t,{silent:!1});console.log(),M("infernoflow ready"),console.log(`
|
|
6
|
+
${g("What was set up:")}`),console.log(` ${d("\u2714")} MCP server installed \u2192 ${f(".cursor/inferno-mcp-server.mjs")}`),e.cursorMcp&&console.log(` ${d("\u2714")} Cursor MCP config \u2192 ${f(".cursor/mcp.json")}`),e.vscodeMcp&&console.log(` ${d("\u2714")} VS Code Copilot MCP config \u2192 ${f(".vscode/mcp.json")}`),e.claudeJson&&console.log(` ${d("\u2714")} Claude Code MCP config \u2192 ${f("~/.claude.json")}`),e.claudeSettings&&console.log(` ${d("\u2714")} Auto-approved tools \u2192 ${f(".claude/settings.json")}`),console.log(),console.log(` ${g("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${f('"call the amp_write tool with a test note"')}`),console.log()}export{P as MCP_TOOLS,R as autoSetupMcp,H as setupCommand,O as updateClaudeJson,F as updateVscodeMcpJson,N as writeClaudeSettings};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as r from"node:fs";import*as o from"node:path";import{installInfernoDraftTooling as v}from"./draftToolingInstall.mjs";function h(f){const{cwd:s,templatesRoot:t,force:m,silent:e}=f,a=f.logOk||(()=>{}),l=f.logWarn||(()=>{});function p(n,c){return r.existsSync(c)&&!m?(e||l("Skipped (exists): "+o.relative(s,c)),!1):(r.mkdirSync(o.dirname(c),{recursive:!0}),r.copyFileSync(n,c),e||a("Created: "+o.relative(s,c)),!0)}v({cwd:s,templatesRoot:t,force:m,silent:e,logOk:a,logWarn:l});const u=o.join(t,"cursor","hooks.json"),j=o.join(s,".cursor","hooks.json"),k=o.join(t,"cursor","hooks","inferno-session-draft.mjs"),S=o.join(s,".cursor","hooks","inferno-session-draft.mjs");p(u,j),p(k,S);const d=o.join(t,"cursor","inferno-mcp-server.mjs"),y=o.join(s,"inferno-mcp-server.mjs");p(d,y);const i=o.join(s,".cursor","mcp.json");if(!r.existsSync(i)||m){let n={};if(r.existsSync(i))try{n=JSON.parse(r.readFileSync(i,"utf8"))}catch{}n.mcpServers||(n.mcpServers={}),n.mcpServers.infernoflow={command:"node",args:["./inferno-mcp-server.mjs"],env:{}},r.mkdirSync(o.dirname(i),{recursive:!0}),r.writeFileSync(i,JSON.stringify(n,null,2),"utf8"),e||a("Created: .cursor/mcp.json")}else e||l("Skipped (exists): .cursor/mcp.json")}export{h as installCursorHooksArtifacts};
|
|
1
|
+
import*as r from"node:fs";import*as o from"node:path";import{installInfernoDraftTooling as v}from"./draftToolingInstall.mjs";function h(f){const{cwd:s,templatesRoot:t,force:m,silent:e}=f,a=f.logOk||(()=>{}),l=f.logWarn||(()=>{});function p(n,c){return r.existsSync(c)&&!m?(e||l("Skipped (exists): "+o.relative(s,c)),!1):(r.mkdirSync(o.dirname(c),{recursive:!0}),r.copyFileSync(n,c),e||a("Created: "+o.relative(s,c)),!0)}v({cwd:s,templatesRoot:t,force:m,silent:e,logOk:a,logWarn:l});const u=o.join(t,"cursor","hooks.json"),j=o.join(s,".cursor","hooks.json"),k=o.join(t,"cursor","hooks","inferno-session-draft.mjs"),S=o.join(s,".cursor","hooks","inferno-session-draft.mjs");p(u,j),p(k,S);const d=o.join(t,"cursor","inferno-mcp-server.mjs"),y=o.join(s,".cursor","inferno-mcp-server.mjs");p(d,y);const i=o.join(s,".cursor","mcp.json");if(!r.existsSync(i)||m){let n={};if(r.existsSync(i))try{n=JSON.parse(r.readFileSync(i,"utf8"))}catch{}n.mcpServers||(n.mcpServers={}),n.mcpServers.infernoflow={command:"node",args:["./inferno-mcp-server.mjs"],env:{}},r.mkdirSync(o.dirname(i),{recursive:!0}),r.writeFileSync(i,JSON.stringify(n,null,2),"utf8"),e||a("Created: .cursor/mcp.json")}else e||l("Skipped (exists): .cursor/mcp.json")}export{h as installCursorHooksArtifacts};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import*as n from"node:fs";import*as u from"node:path";const l="<!-- infernoflow:start -->",a="<!-- infernoflow:end -->",d=[".cursorrules","CLAUDE.md",u.join(".github","copilot-instructions.md")];function m(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!!`, `not working`, `still broken`, `no change`, `same error`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.join(`
|
|
2
|
+
`)}function y(){return[l,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",m(),"",'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',a].join(`
|
|
3
|
+
`)}function g(e,s){const o=u.dirname(e);if(n.existsSync(o)||n.mkdirSync(o,{recursive:!0}),!n.existsSync(e))return n.writeFileSync(e,s+`
|
|
4
|
+
`,"utf8"),{created:!0,updated:!1};const t=n.readFileSync(e,"utf8"),i=t.indexOf(l),r=t.indexOf(a);if(i===-1||r===-1){const f=s+`
|
|
5
|
+
|
|
6
|
+
`+t;return n.writeFileSync(e,f,"utf8"),{created:!1,updated:!0}}const h=t.slice(0,i),p=t.slice(r+a.length),c=h+s+p;return c===t?{created:!1,updated:!1}:(n.writeFileSync(e,c,"utf8"),{created:!1,updated:!0})}function w(e){const s=y(),o=[];for(const t of d){const i=u.join(e,t);try{const r=g(i,s);o.push({rel:t,...r})}catch(r){o.push({rel:t,error:r.message})}}return o}export{w as writeInitRuleFiles};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infernoflow",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.9",
|
|
4
4
|
"description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|