infernoflow 0.43.12 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +87 -149
  2. package/dist/bin/infernoflow.mjs +30 -33
  3. package/dist/lib/amp/io.mjs +8 -8
  4. package/dist/lib/cleanTree.mjs +12 -0
  5. package/dist/lib/commands/ai.mjs +2 -2
  6. package/dist/lib/commands/amp.mjs +4 -4
  7. package/dist/lib/commands/ask.mjs +2 -2
  8. package/dist/lib/commands/context.mjs +18 -18
  9. package/dist/lib/commands/doctor.mjs +2 -3
  10. package/dist/lib/commands/init.mjs +31 -32
  11. package/dist/lib/commands/log.mjs +13 -19
  12. package/dist/lib/commands/recap.mjs +3 -3
  13. package/dist/lib/commands/refresh.mjs +5 -0
  14. package/dist/lib/commands/setup.mjs +6 -6
  15. package/dist/lib/commands/status.mjs +6 -7
  16. package/dist/lib/commands/switch.mjs +5 -5
  17. package/dist/lib/commands/sync.mjs +41 -0
  18. package/dist/lib/git/branch.mjs +2 -0
  19. package/dist/lib/mcpRuntime.mjs +1 -0
  20. package/dist/lib/projectRoot.mjs +1 -0
  21. package/dist/lib/ruleFiles.mjs +9 -8
  22. package/dist/lib/upgradeCheck.mjs +1 -1
  23. package/dist/templates/cursor/inferno-mcp-server.mjs +200 -325
  24. package/package.json +13 -5
  25. package/dist/lib/commands/changelog.mjs +0 -21
  26. package/dist/lib/commands/ci.mjs +0 -3
  27. package/dist/lib/commands/claudeMd.mjs +0 -116
  28. package/dist/lib/commands/coverage.mjs +0 -2
  29. package/dist/lib/commands/demo.mjs +0 -113
  30. package/dist/lib/commands/diff.mjs +0 -5
  31. package/dist/lib/commands/explain.mjs +0 -8
  32. package/dist/lib/commands/feedback.mjs +0 -12
  33. package/dist/lib/commands/graph.mjs +0 -76
  34. package/dist/lib/commands/impact.mjs +0 -2
  35. package/dist/lib/commands/implement.mjs +0 -7
  36. package/dist/lib/commands/monorepo.mjs +0 -4
  37. package/dist/lib/commands/notify.mjs +0 -4
  38. package/dist/lib/commands/prImpact.mjs +0 -2
  39. package/dist/lib/commands/publish.mjs +0 -21
  40. package/dist/lib/commands/review.mjs +0 -24
  41. package/dist/lib/commands/run.mjs +0 -10
  42. package/dist/lib/commands/scaffold.mjs +0 -124
  43. package/dist/lib/commands/scan.mjs +0 -42
  44. package/dist/lib/commands/stability.mjs +0 -2
  45. package/dist/lib/commands/stats.mjs +0 -4
  46. package/dist/lib/commands/suggest.mjs +0 -62
  47. package/dist/lib/commands/syncAuto.mjs +0 -1
  48. package/dist/lib/commands/test.mjs +0 -6
  49. package/dist/lib/commands/theme.mjs +0 -18
  50. package/dist/lib/commands/upgrade.mjs +0 -20
  51. package/dist/lib/commands/watch.mjs +0 -7
  52. package/dist/lib/commands/why.mjs +0 -4
@@ -1,4 +1,4 @@
1
- import*as m from"node:fs";import*as u from"node:path";import{bold as l,cyan as d,gray as s,green as h,yellow as S,red as v}from"../ui/output.mjs";import{AMP_VERSION as y,ampPaths as M,readEntries as A,migrateLegacy as b}from"../amp/io.mjs";const j=new Set(["gotcha","decision","attempt","note","detection","pattern"]),x=new Set(["copilot","cursor","claude","windsurf","other"]);function k(o,n){const e=[];return(!o||typeof o!="object")&&e.push("not an object"),o.type?j.has(o.type)||e.push(`type "${o.type}" not in AMP enum`):e.push("missing required field: type"),o.msg?typeof o.msg!="string"?e.push("msg must be a string"):o.msg.length>500&&e.push(`msg too long (${o.msg.length} > 500)`):e.push("missing required field: msg"),o.ts==null?e.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&e.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&e.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!x.has(o.tool)&&e.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&e.push("confidence out of range [0,1]"),e}function E(o){const{sessions:n}=M(o);if(!m.existsSync(n))return{sessions:n,entries:[],lines:[]};const e=m.readFileSync(n,"utf8").split(`
2
- `).filter(Boolean),a=e.map((c,r)=>{try{return{ok:!0,line:r+1,value:JSON.parse(c)}}catch(t){return{ok:!1,line:r+1,value:null,error:t.message}}});return{sessions:n,entries:a,lines:e}}function P(o){const{sessions:n,isAmp:e,root:a}=M(o),c=u.join(o,".ai-memory"),r=u.join(o,"inferno"),t=m.existsSync(c),g=m.existsSync(r)&&m.existsSync(u.join(r,"sessions.jsonl"));if(console.log(),console.log(` ${l("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${l(y)}`),console.log(` Conformance level ${l("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!t&&!g){console.log(` ${S("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${l("Layout:")}`),console.log(` .ai-memory/ ${t?h("\u2714 present")+(e?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?S("\u26A0 legacy")+(e?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(n)){const f=A(o);if(console.log(` ${l("Memory:")}`),console.log(` file ${s(u.relative(o,n))}`),console.log(` entries ${l(String(f.length))}`),f.length){const i=new Map;for(const $ of f)i.set($.type,(i.get($.type)||0)+1);const p=[...i.entries()].map(([$,w])=>`${$}:${w}`).join(" ");console.log(` breakdown ${s(p)}`)}}g&&!t&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function N(o){console.log(),console.log(` ${l("\u{1F525} infernoflow amp migrate")}`),console.log();const n=b(o);n.migrated>0?(console.log(` ${h("\u2714")} Migrated ${l(String(n.migrated))} entr${n.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${n.reason}${s(".")}`),console.log()}function O(o){console.log(),console.log(` ${l("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:n,entries:e}=E(o);if(!e.length){console.log(` ${s("No entries to validate at")} ${d(u.relative(o,n))}`),console.log();return}let a=0,c=0,r=0;const t=[];for(const i of e){if(!i.ok){c++,t.push({line:i.line,error:`parse error: ${i.error}`});continue}const p=k(i.value,i.line);p.length?(r++,t.push({line:i.line,error:p.join("; ")})):a++}for(const i of t.slice(0,10))console.log(` ${v("\u2718")} line ${i.line} ${s(i.error)}`);t.length>10&&console.log(` ${s(`\u2026 ${t.length-10} more`)}`),t.length&&console.log();const g=e.length,f=c===0&&r===0;console.log(f?` ${h("\u2714")} ${l(`${a}/${g}`)} entries conform to AMP v${y}.`:` ${v("\u2718")} ${l(`${a}/${g}`)} entries OK \xB7 ${c} parse \xB7 ${r} schema`),console.log(),f||process.exit(1)}function T(){console.log(y)}async function I(o){const n=o[1],e=process.cwd();if(!n||n==="status")return P(e);if(n==="migrate")return N(e);if(n==="validate")return O(e);if(n==="version")return T();if(n==="--help"||n==="-h")return P(e);console.error(v(`
3
- Unknown amp verb: ${n}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
4
- `)),process.exit(1)}export{I as ampCommand};
1
+ import*as m from"node:fs";import*as p from"node:path";import{bold as r,cyan as d,gray as s,green as h,yellow as S,red as y}from"../ui/output.mjs";import{AMP_VERSION as v,ampPaths as b,readEntries as P,migrateLegacy as w}from"../amp/io.mjs";const A=new Set(["gotcha","decision","attempt","note","detection","pattern"]),x=new Set(["copilot","cursor","claude","windsurf","other"]);function E(o,e){const n=[];return(!o||typeof o!="object")&&n.push("not an object"),o.type?A.has(o.type)||n.push(`type "${o.type}" not in AMP enum`):n.push("missing required field: type"),o.msg?typeof o.msg!="string"?n.push("msg must be a string"):o.msg.length>500&&n.push(`msg too long (${o.msg.length} > 500)`):n.push("missing required field: msg"),o.ts==null?n.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&n.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&n.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!x.has(o.tool)&&n.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&n.push("confidence out of range [0,1]"),n}function k(o){const e=b(o),n=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile],c=[...new Set(n.filter(Boolean))];try{if(m.existsSync(e.branchesDir))for(const t of m.readdirSync(e.branchesDir)){if(!t.endsWith(".jsonl"))continue;const l=p.join(e.branchesDir,t);c.includes(l)||c.push(l)}}catch{}const a=[];for(const t of c){if(!m.existsSync(t))continue;let l;try{l=m.readFileSync(t,"utf8").split(`
2
+ `).filter(Boolean)}catch{continue}l.forEach((g,f)=>{try{a.push({ok:!0,file:t,line:f+1,value:JSON.parse(g)})}catch(i){a.push({ok:!1,file:t,line:f+1,value:null,error:i.message})}})}return{sessions:e.sessions,entries:a,lines:[],files:c}}function j(o){const{sessions:e,isAmp:n,root:c}=b(o),a=p.join(o,".ai-memory"),t=p.join(o,"inferno"),l=m.existsSync(a),g=m.existsSync(t)&&m.existsSync(p.join(t,"sessions.jsonl"));if(console.log(),console.log(` ${r("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${r(v)}`),console.log(` Conformance level ${r("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!l&&!g){console.log(` ${S("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${r("Layout:")}`),console.log(` .ai-memory/ ${l?h("\u2714 present")+(n?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?S("\u26A0 legacy")+(n?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(e)){const f=P(o);if(console.log(` ${r("Memory:")}`),console.log(` file ${s(p.relative(o,e))}`),console.log(` entries ${r(String(f.length))}`),f.length){const i=new Map;for(const u of f)i.set(u.type,(i.get(u.type)||0)+1);const $=[...i.entries()].map(([u,M])=>`${u}:${M}`).join(" ");console.log(` breakdown ${s($)}`)}}g&&!l&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function D(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp migrate")}`),console.log();const e=w(o);e.migrated>0?(console.log(` ${h("\u2714")} Migrated ${r(String(e.migrated))} entr${e.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${e.reason}${s(".")}`),console.log()}function F(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:e,entries:n}=k(o);if(!n.length){console.log(` ${s("No entries to validate at")} ${d(p.relative(o,e))}`),console.log();return}let c=0,a=0,t=0;const l=[];for(const i of n){const $=p.relative(o,i.file);if(!i.ok){a++,l.push({file:$,line:i.line,error:`parse error: ${i.error}`});continue}const u=E(i.value,i.line);u.length?(t++,l.push({file:$,line:i.line,error:u.join("; ")})):c++}for(const i of l.slice(0,10))console.log(` ${y("\u2718")} ${i.file}:${i.line} ${s(i.error)}`);l.length>10&&console.log(` ${s(`\u2026 ${l.length-10} more`)}`),l.length&&console.log();const g=n.length,f=a===0&&t===0;console.log(f?` ${h("\u2714")} ${r(`${c}/${g}`)} entries conform to AMP v${v}.`:` ${y("\u2718")} ${r(`${c}/${g}`)} entries OK \xB7 ${a} parse \xB7 ${t} schema`),console.log(),f||process.exit(1)}function N(){console.log(v)}async function L(o){const e=o[1],n=process.cwd();if(!e||e==="status")return j(n);if(e==="migrate")return D(n);if(e==="validate")return F(n);if(e==="version")return N();if(e==="--help"||e==="-h")return j(n);console.error(y(`
3
+ Unknown amp verb: ${e}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
4
+ `)),process.exit(1)}export{L as ampCommand};
@@ -1,3 +1,3 @@
1
- import"node:fs";import*as j from"node:path";import{bold as O,cyan as S,gray as s,green as k,yellow as C,red as L}from"../ui/output.mjs";import{readEntries as W}from"../amp/io.mjs";const q="inferno",b=j.join(q,"sessions.jsonl"),d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},I={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:C,decision:k,attempt:S,preference:S,theme:S,note:s,error:L,handoff:s};function x(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(o=>o.length>1)}function M(t,o){const i=[t.summary||"",t.type||""].join(" ").toLowerCase(),n=x(i);let l=0;for(const r of o)(t.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function _(t){if(!t)return"";const o=new Date(t),i=Date.now()-o.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:o.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function F(t,o){const i=t.type||"note",n=I[i]||"\xB7",l=N[i]||s,r=t.result?s(` [${t.result}]`):"",e=t.agent?s(` \u2014 ${t.agent}`):"",f=s(` (${_(t.ts)})`);let a=t.summary||"";if(o)for(const u of o){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(t){return W(t)}function Y(t,o,i,n){let l=t;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return o.length>0?r=l.map(e=>({entry:e,score:M(e,o)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}),r.slice(0,n||20)}async function z(t=[]){const o=t,i=o.indexOf("--type"),n=i!==-1?o[i+1]:null,l=o.indexOf("--limit")!==-1?o.indexOf("--limit"):o.indexOf("-n"),r=l!==-1?parseInt(o[l+1]||"20",10):15,e=o.includes("--json"),f=o.includes("--recent")||o.includes("-r"),a=o.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:x(p),D=process.cwd(),y=P(D);if(y.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
1
+ import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as R,yellow as I,red as W}from"../ui/output.mjs";import{readEntries as j}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},D={gotcha:I,decision:R,attempt:T,preference:T,theme:T,note:s,error:W,handoff:s};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function q(o,t){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let l=0;for(const r of t)(o.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function L(o){if(!o)return"";const t=new Date(o),i=Date.now()-t.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function M(o,t){const i=o.type||"note",n=S[i]||"\xB7",l=D[i]||s,r=o.result?s(` [${o.result}]`):"",e=o.agent?s(` \u2014 ${o.agent}`):"",f=s(` (${L(o.ts)})`);let a=o.summary||"";if(t)for(const u of t){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(o){return j(o)}function Y(o,t,i,n){let l=o;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return t.length>0?r=l.map(e=>({entry:e,score:q(e,t)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),r.slice(0,n||20)}async function U(o=[]){const t=o,i=t.indexOf("--type"),n=i!==-1?t[i+1]:null,l=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),r=l!==-1?parseInt(t[l+1]||"20",10):15,e=t.includes("--json"),f=t.includes("--recent")||t.includes("-r"),a=t.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:E(p),k=process.cwd(),m=P(k);if(m.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
2
2
  No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
3
- `));return}const m=f?y.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(y,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:y.length,matched:m.length,results:m.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${S(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),m.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of m){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const R=Object.keys(d).sort((c,g)=>d[c]-d[g]);let E=0;for(const c of R){const g=w.get(c);if(!g?.length)continue;console.log();const h=N[c]||s;console.log(h(` ${I[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:T}of g)console.log(),F(T,$),E++}console.log(),console.log(s(` ${E} result${E!==1?"s":""} from ${y.length} total entries`)),m.length===r&&y.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{z as askCommand};
3
+ `));return}const y=f?m.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(m,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${T(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of y){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const C=Object.keys(d).sort((c,g)=>d[c]-d[g]);let x=0;for(const c of C){const g=w.get(c);if(!g?.length)continue;console.log();const h=D[c]||s;console.log(h(` ${S[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:N}of g)console.log(),M(N,$),x++}console.log(),console.log(s(` ${x} result${x!==1?"s":""} from ${m.length} total entries`)),y.length===r&&m.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{U as askCommand};
@@ -1,31 +1,31 @@
1
- import g from"node:fs";import b from"node:path";import{execSync as h}from"node:child_process";import{bold as I,gray as d,cyan as l,red as q,green as a,yellow as F}from"../ui/output.mjs";import{buildCursorImplementPrompt as wt,buildGenericImplementPrompt as yt}from"../ui/prompts.mjs";import{detectDrift as kt}from"../git/detect-drift.mjs";function Ct(e){try{const n=process.platform;if(n==="win32")h("clip",{input:e});else if(n==="darwin")h("pbcopy",{input:e});else try{h("xclip -selection clipboard",{input:e})}catch{h("xsel --clipboard --input",{input:e})}return!0}catch{return!1}}const w="inferno",P=b.join(w,"CONTEXT.md"),z=b.join(w,"context-state.json");function Q(e){try{return JSON.parse(g.readFileSync(e,"utf8"))}catch{return null}}function W(e){try{return g.readFileSync(e,"utf8")}catch{return null}}function Y(){const e=W(z);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function Z(e){g.writeFileSync(z,JSON.stringify(e,null,2),"utf8")}function E(e){return e?new Date(e).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function St(e,n){if(!e)return[];const i=[];let c=null;for(const r of e.split(`
2
- `))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w"),$=i("--decision")||i("-d"),tt=n("--show")||n("-s"),G=n("--copy")||n("-c"),et=n("--cursor"),nt=n("--copilot"),ot=n("--reset"),it=n("--watch"),N=n("--auto-commit")||n("--auto-push"),v=n("--auto-push"),R=parseInt(i("--interval")||"30",10)*1e3;console.log(`
1
+ import p from"node:fs";import b from"node:path";import{execSync as h}from"node:child_process";import{bold as I,gray as d,cyan as l,red as H,green as a,yellow as F}from"../ui/output.mjs";import{buildCursorImplementPrompt as wt,buildGenericImplementPrompt as yt}from"../ui/prompts.mjs";import{detectDrift as Ct}from"../git/detect-drift.mjs";function kt(e){try{const n=process.platform;if(n==="win32")h("clip",{input:e});else if(n==="darwin")h("pbcopy",{input:e});else try{h("xclip -selection clipboard",{input:e})}catch{h("xsel --clipboard --input",{input:e})}return!0}catch{return!1}}const w="inferno",O=b.join(w,"CONTEXT.md"),K=b.join(w,"context-state.json");function M(e){try{return JSON.parse(p.readFileSync(e,"utf8"))}catch{return null}}function W(e){try{return p.readFileSync(e,"utf8")}catch{return null}}function q(){const e=W(K);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function z(e){p.writeFileSync(K,JSON.stringify(e,null,2),"utf8")}function E(e){return e?new Date(e).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function St(e,n){if(!e)return[];const i=[];let c=null;for(const r of e.split(`
2
+ `))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w"),x=i("--decision")||i("-d"),Q=n("--show")||n("-s"),G=n("--copy")||n("-c"),Y=n("--cursor"),Z=n("--copilot"),tt=n("--reset"),et=n("--watch"),P=n("--auto-commit")||n("--auto-push"),N=n("--auto-push"),R=parseInt(i("--interval")||"30",10)*1e3;console.log(`
3
3
  `+I("\uFFFD\uFFFD\uFFFD infernoflow \u2014 context")),console.log(" "+"\u2500".repeat(50)+`
4
- `),g.existsSync(w)||(console.error(q(" \u2718 inferno/ not found")),console.error(d(` \u2192 Run: infernoflow init
5
- `)),process.exit(1));const f=Q(b.join(w,"contract.json")),D=Q(b.join(w,"capabilities.json")),st=W(b.join(w,"CHANGELOG.md"));(!f||!D)&&(console.error(q(` \u2718 Missing contract.json or capabilities.json
6
- `)),process.exit(1));let t=Y();ot&&(t={},console.log(F(` \u26A0 State reset
7
- `))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"'))),$&&(t.decisions||(t.decisions=[]),t.decisions.push({text:$,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+$+'"'))),(c||r||$)&&Z(t);const j=D.capabilities||[],A=j.length===(f.capabilities||[]).length,U=St(st,3),T=String(f.policyVersion).replace(/^v/i,""),ct=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),B=A?"\u2713 validated":"\u26A0 out of sync",L=t.intent||"describe the exact task to implement",J={task:L,contract:f,caps:D,scenarios:[],state:t},rt=wt(J),lt=yt(J),at=j.map(o=>"- **"+o.id+"** \u2014 "+o.title).join(`
8
- `),dt=U.length>0?U.map(o=>"### "+o.title+`
4
+ `),p.existsSync(w)||(console.error(H(" \u2718 inferno/ not found")),console.error(d(` \u2192 Run: infernoflow init
5
+ `)),process.exit(1));const f=M(b.join(w,"contract.json")),v=M(b.join(w,"capabilities.json")),nt=W(b.join(w,"CHANGELOG.md"));(!f||!v)&&(console.error(H(` \u2718 Missing contract.json or capabilities.json
6
+ `)),process.exit(1));let t=q();tt&&(t={},console.log(F(` \u26A0 State reset
7
+ `))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"'))),x&&(t.decisions||(t.decisions=[]),t.decisions.push({text:x,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+x+'"'))),(c||r||x)&&z(t);const $=v.capabilities||[],A=$.length===(f.capabilities||[]).length,U=St(nt,3),D=String(f.policyVersion).replace(/^v/i,""),ot=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),B=A?"\u2713 validated":"\u26A0 out of sync",T=t.intent||"describe the exact task to implement",J={task:T,contract:f,caps:v,scenarios:[],state:t},it=wt(J),st=yt(J),ct=$.map(o=>"- **"+o.id+"** \u2014 "+o.title).join(`
8
+ `),rt=U.length>0?U.map(o=>"### "+o.title+`
9
9
  `+o.items.map(u=>" - "+u).join(`
10
10
  `)).join(`
11
11
 
12
- `):"_No recent changes_",pt=t.intent?t.intent+" _("+E(t.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',gt=t.working?t.working+" _("+E(t.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',ut=t.decisions&&t.decisions.length>0?t.decisions.slice(-5).map(o=>"- "+o.text+" _("+E(o.date)+")_").join(`
13
- `):"_No decisions recorded_",x=["# Project Context \u2014 "+f.policyId+" v"+T,"> Generated by infernoflow | "+ct+" | "+B,"","---","","## What this system does","",at,"","---","","## Recent changes","",dt,"","---","","## Current state","","- **Capabilities:** "+j.length,"- **Version:** v"+T,"- **Sync:** "+B,"","---","","## What I am working on right now","",gt,"","---","","## Intent \u2014 what I want to build next","",pt,"","---","","## Decisions & notes","",ut,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${L}" --mode both`,"```","","### Cursor Agent Prompt","","```text",rt,"```","","### Generic Agent Prompt","","```text",lt,"```","","---","_Paste this block at the start of any new AI session._"].join(`
14
- `);if(tt||(g.writeFileSync(P,x,"utf8"),console.log(a(`
15
- \u2714 Context written \u2192 `+P))),G){const o=Ct(x);console.log(o?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):F(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(et&&(g.writeFileSync(".cursorrules",x,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),nt&&(g.existsSync(".github")||g.mkdirSync(".github"),g.writeFileSync(".github/copilot-instructions.md",x,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
16
- `+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+T),console.log(" Capabilities "+j.length+" registered"),console.log(" Sync "+(A?a("\u2713 in sync"):F("\u26A0 check needed"))),console.log(" Working on "+(t.working?l(t.working):d("not set"))),console.log(" Intent "+(t.intent?l(t.intent):d("not set"))),console.log(" Decisions "+(t.decisions?t.decisions.length:0)+` recorded
17
- `),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${L}" --mode both`)+`
12
+ `):"_No recent changes_",lt=t.intent?t.intent+" _("+E(t.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',at=t.working?t.working+" _("+E(t.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',dt=t.decisions&&t.decisions.length>0?t.decisions.slice(-5).map(o=>"- "+o.text+" _("+E(o.date)+")_").join(`
13
+ `):"_No decisions recorded_",j=["# Project Context \u2014 "+f.policyId+" v"+D,"> Generated by infernoflow | "+ot+" | "+B,"","---","","## What this system does","",ct,"","---","","## Recent changes","",rt,"","---","","## Current state","","- **Capabilities:** "+$.length,"- **Version:** v"+D,"- **Sync:** "+B,"","---","","## What I am working on right now","",at,"","---","","## Intent \u2014 what I want to build next","",lt,"","---","","## Decisions & notes","",dt,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${T}" --mode both`,"```","","### Cursor Agent Prompt","","```text",it,"```","","### Generic Agent Prompt","","```text",st,"```","","---","_Paste this block at the start of any new AI session._"].join(`
14
+ `);if(Q||(p.writeFileSync(O,j,"utf8"),console.log(a(`
15
+ \u2714 Context written \u2192 `+O))),G){const o=kt(j);console.log(o?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):F(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(Y&&(p.writeFileSync(".cursorrules",j,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),Z&&(p.existsSync(".github")||p.mkdirSync(".github"),p.writeFileSync(".github/copilot-instructions.md",j,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
16
+ `+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+D),console.log(" Capabilities "+$.length+" registered"),console.log(" Sync "+(A?a("\u2713 in sync"):F("\u26A0 check needed"))),console.log(" Working on "+(t.working?l(t.working):d("not set"))),console.log(" Intent "+(t.intent?l(t.intent):d("not set"))),console.log(" Decisions "+(t.decisions?t.decisions.length:0)+` recorded
17
+ `),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${T}" --mode both`)+`
18
18
  `),G?(console.log(" "+I("Ready to use:")),console.log(" "+l("\u2192")+" Paste into Claude / Cursor / Copilot with "+l("Ctrl+V")+`
19
19
  `)):(console.log(" "+I("Ready to use:")),console.log(" "+l("1.")+" Open "+l("inferno/CONTEXT.md")),console.log(" "+l("2.")+" Copy everything"),console.log(" "+l("3.")+" Paste at the start of your next AI session"),console.log(" "+d(" tip: use --copy to skip steps 1-2 automatically")+`
20
- `)),it){let _=function(p){try{return h(p,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},X=function(p){try{return h(`git status --porcelain "${p}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},H=function(p,s,O){const k=`chore: update context [${s.length>0?s.slice(0,3).join(", "):`${O} files`}]`;return _(`git add "${p}"`)?X(p)?{ok:!1,reason:"nothing to commit"}:_(`git commit -m "${k}"`)?{ok:!0,msg:k}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},K=function(){return _("git push")};var It=_,Ft=X,$t=H,jt=K;const o=v?"auto-push":N?"auto-commit":"watch";console.log(" "+l("\u{1F441} Watch mode active")+d(` \u2014 polling every ${R/1e3}s`+(v?" \xB7 will commit + push on change":N?" \xB7 will commit on change":""))),console.log(" "+d(`Press Ctrl+C to stop
21
- `));let u="",V=null;const M=async()=>{try{const p=process.cwd(),s=kt(p,{sinceCommits:1}),O=s.changedFiles.sort().join("|");if(O===u||(u=O,s.changedFiles.length===0))return;const y=s.affectedCapabilities.map(S=>S.id),k=y.length>0?`Working on: ${y.join(", ")} (${s.changedFiles.length} files changed)`:`${s.changedFiles.length} files changed \u2014 no capability match yet`,C=Y();if(C.working!==k){C.working=k,C.workingUpdated=new Date().toISOString(),Z(C),await bt(e.filter(m=>m!=="--watch"&&m!=="--auto-commit"&&m!=="--auto-push"));const S=W(P),ft=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
20
+ `)),et){let L=function(g){try{return h(g,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},gt=function(g){try{return h(`git status --porcelain "${g}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},pt=function(g,s,_){const C=`chore: update context [${s.length>0?s.slice(0,3).join(", "):`${_} files`}]`;return L(`git add "${g}"`)?gt(g)?{ok:!1,reason:"nothing to commit"}:L(`git commit -m "${C}"`)?{ok:!0,msg:C}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},ut=function(){return L("git push")};const o=N?"auto-push":P?"auto-commit":"watch";console.log(" "+l("\u{1F441} Watch mode active")+d(` \u2014 polling every ${R/1e3}s`+(N?" \xB7 will commit + push on change":P?" \xB7 will commit on change":""))),console.log(" "+d(`Press Ctrl+C to stop
21
+ `));let u="",V=null;const X=async()=>{try{const g=process.cwd(),s=Ct(g,{sinceCommits:1}),_=s.changedFiles.sort().join("|");if(_===u||(u=_,s.changedFiles.length===0))return;const y=s.affectedCapabilities.map(S=>S.id),C=y.length>0?`Working on: ${y.join(", ")} (${s.changedFiles.length} files changed)`:`${s.changedFiles.length} files changed \u2014 no capability match yet`,k=q();if(k.working!==C){k.working=C,k.workingUpdated=new Date().toISOString(),z(k),await bt(e.filter(m=>m!=="--watch"&&m!=="--auto-commit"&&m!=="--auto-push"));const S=W(O),ft=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
22
22
  ${a("\u2714")} [${ft}] Context updated \u2014 ${y.length} capabilities affected
23
23
  ${d(s.changedFiles.slice(0,3).join(", ")+(s.changedFiles.length>3?` +${s.changedFiles.length-3} more`:""))}
24
- `),N&&S!==V){V=S;const m=H(P,y,s.changedFiles.length);if(m.ok){if(process.stderr.write(` ${a("\u2714")} Committed: ${d(m.msg)}
25
- `),v){const ht=K();process.stderr.write(ht?` ${a("\u2714")} Pushed to origin
24
+ `),P&&S!==V){V=S;const m=pt(O,y,s.changedFiles.length);if(m.ok){if(process.stderr.write(` ${a("\u2714")} Committed: ${d(m.msg)}
25
+ `),N){const ht=ut();process.stderr.write(ht?` ${a("\u2714")} Pushed to origin
26
26
  `:` ${F("\u26A0")} Push failed \u2014 will retry next change
27
27
  `)}}else process.stderr.write(` ${F("\u26A0")} Commit skipped: ${d(m.reason)}
28
- `)}}}catch{}};await M();const mt=setInterval(M,R);process.on("SIGINT",()=>{clearInterval(mt),process.stderr.write(`
28
+ `)}}}catch{}};await X();const mt=setInterval(X,R);process.on("SIGINT",()=>{clearInterval(mt),process.stderr.write(`
29
29
  `+d(`Watch stopped.
30
30
 
31
31
  `)),process.exit(0)}),await new Promise(()=>{})}}export{bt as contextCommand};
@@ -1,4 +1,3 @@
1
- import*as s from"node:fs";import*as l from"node:path";import*as y from"node:os";import*as v from"node:http";import{execSync as C,spawnSync as $}from"node:child_process";import{fileURLToPath as O}from"node:url";import{bold as b,cyan as N,gray as x,green as w,yellow as j,red as S}from"../ui/output.mjs";import{detectAvailableProviders as A}from"../ai/providerRouter.mjs";function a(n,e){try{const o=e();return{label:n,...o}}catch(o){return{label:n,status:"error",message:o.message,fix:null}}}function c(n,e){return{status:"pass",message:n,detail:e||null,fix:null}}function u(n,e){return{status:"warn",message:n,detail:null,fix:e||null}}function p(n,e){return{status:"fail",message:n,detail:null,fix:e||null}}function _(){const n=process.version,e=parseInt(n.slice(1).split(".")[0],10);return e>=20?c(`Node.js ${n}`,"Node 20+ recommended"):e>=18?c(`Node.js ${n}`):p(`Node.js ${n} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function P(){try{const n=$("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return n.status===0?c(`infernoflow v${n.stdout.trim()} installed`):c("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return c("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function E(n){try{return C("git rev-parse --git-dir",{cwd:n,stdio:"ignore"}),c("Git repository detected")}catch{return p("Not a git repository","git init && git add . && git commit -m 'init'")}}function I(n){const e=l.join(n,"inferno");return s.existsSync(e)?c("inferno/ directory exists"):p("inferno/ not found","infernoflow init")}function T(n){const e=l.join(n,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(l.join(e,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){const i=l.join(e,"sessions.jsonl");if(!s.existsSync(i))return c("Memory mode \u2014 sessions.jsonl will be created on first log");let t=0;try{t=s.readFileSync(i,"utf8").split(`
2
- `).filter(Boolean).length}catch{}return c(`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const i of["contract.json","capabilities.json"]){const t=l.join(e,i);if(s.existsSync(t))try{const d=(JSON.parse(s.readFileSync(t,"utf8")).capabilities||[]).length;return c(`${i} valid \u2014 ${d} capabilities`)}catch{return p(`${i} contains invalid JSON`,`Fix the JSON syntax in inferno/${i}`)}}return p("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function k(n){try{return JSON.parse(s.readFileSync(l.join(n,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function G(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","scenarios");if(!s.existsSync(e))return u("No scenarios/ directory","infernoflow init");const o=s.readdirSync(e).filter(i=>i.endsWith(".json"));return o.length?c(`${o.length} scenario file${o.length!==1?"s":""} found`):u("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function F(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","CHANGELOG.md");return s.existsSync(e)?c("inferno/CHANGELOG.md exists"):u("No inferno/CHANGELOG.md","infernoflow init")}function M(n){if(k(n))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const e=l.join(n,"inferno","CONTEXT.md");if(!s.existsSync(e))return u("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(e).mtimeMs)/(1e3*60*60*24);return o>7?u(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):c(`CONTEXT.md present (${Math.round(o)}d old)`)}function D(n){const e=l.join(n,".git","hooks"),o=l.join(e,"post-commit"),i=l.join(e,"pre-push"),t=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),r=s.existsSync(i)&&s.readFileSync(i,"utf8").includes("infernoflow");return t&&r?c("Git hooks installed (post-commit + pre-push)"):u(t||r?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function L(n){const e=[l.join(n,".cursor","mcp.json"),l.join(n,".mcp.json"),l.join(y.homedir(),".cursor","mcp.json"),l.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),l.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of e)if(s.existsSync(o))try{const i=JSON.parse(s.readFileSync(o,"utf8")),t=i.mcpServers||i.mcp_servers||{};if(Object.keys(t).some(r=>r.toLowerCase().includes("inferno")))return c(`MCP server configured in ${l.basename(o)}`)}catch{}return u("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function R(n){const e=A(n),o=Object.entries(e).filter(([,i])=>i).map(([i])=>i);return o.length?c(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):u("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
1
+ import*as s from"node:fs";import*as a from"node:path";import*as y from"node:os";import*as O from"node:http";import{execSync as P,spawnSync as v}from"node:child_process";import{fileURLToPath as A}from"node:url";import{bold as C,cyan as b,gray as j,green as w,yellow as S,red as k}from"../ui/output.mjs";import{detectAvailableProviders as _}from"../ai/providerRouter.mjs";import{readEntries as I}from"../amp/io.mjs";function u(e,n){try{const o=n();return{label:e,...o}}catch(o){return{label:e,status:"error",message:o.message,fix:null}}}function c(e,n){return{status:"pass",message:e,detail:n||null,fix:null}}function l(e,n){return{status:"warn",message:e,detail:null,fix:n||null}}function h(e,n){return{status:"fail",message:e,detail:null,fix:n||null}}function E(){const e=process.version,n=parseInt(e.slice(1).split(".")[0],10);return n>=20?c(`Node.js ${e}`,"Node 20+ recommended"):n>=18?c(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function M(){try{const e=v("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return e.status===0?c(`infernoflow v${e.stdout.trim()} installed`):c("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return c("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function T(e){try{return P("git rev-parse --git-dir",{cwd:e,stdio:"ignore"}),c("Git repository detected")}catch{return h("Not a git repository","git init && git add . && git commit -m 'init'")}}function F(e){const n=a.join(e,"inferno");return s.existsSync(n)?c("inferno/ directory exists"):h("inferno/ not found","infernoflow init")}function G(e){const n=a.join(e,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(a.join(n,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){let t=0;try{t=I(e).length}catch{}return c(t===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const t of["contract.json","capabilities.json"]){const r=a.join(n,t);if(s.existsSync(r))try{const m=(JSON.parse(s.readFileSync(r,"utf8")).capabilities||[]).length;return c(`${t} valid \u2014 ${m} capabilities`)}catch{return h(`${t} contains invalid JSON`,`Fix the JSON syntax in inferno/${t}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function $(e){try{return JSON.parse(s.readFileSync(a.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function L(e){if($(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=a.join(e,"inferno","scenarios");if(!s.existsSync(n))return l("No scenarios/ directory","infernoflow init");const o=s.readdirSync(n).filter(t=>t.endsWith(".json"));return o.length?c(`${o.length} scenario file${o.length!==1?"s":""} found`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function R(e){if($(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=a.join(e,"inferno","CHANGELOG.md");return s.existsSync(n)?c("inferno/CHANGELOG.md exists"):l("No inferno/CHANGELOG.md","infernoflow init")}function D(e){if($(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const n=a.join(e,"inferno","CONTEXT.md");if(!s.existsSync(n))return l("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(n).mtimeMs)/(1e3*60*60*24);return o>7?l(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):c(`CONTEXT.md present (${Math.round(o)}d old)`)}function J(e){const n=a.join(e,".git","hooks"),o=a.join(n,"post-commit"),t=a.join(n,"pre-push"),r=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),i=s.existsSync(t)&&s.readFileSync(t,"utf8").includes("infernoflow");return r&&i?c("Git hooks installed (post-commit + pre-push)"):l(r||i?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function H(e){const n=[a.join(e,".cursor","mcp.json"),a.join(e,".mcp.json"),a.join(y.homedir(),".cursor","mcp.json"),a.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),a.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of n)if(s.existsSync(o))try{const t=JSON.parse(s.readFileSync(o,"utf8")),r=t.mcpServers||t.mcp_servers||{};if(Object.keys(r).some(i=>i.toLowerCase().includes("inferno")))return c(`MCP server configured in ${a.basename(o)}`)}catch{}return l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function W(e,n){const o=a.join(e,".ai-memory",".mcp-runtime.json");if(!s.existsSync(o))return c("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(s.readFileSync(o,"utf8"))}catch{return l(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?l(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===n?c(`MCP runtime v${t.version} matches CLI`):n.startsWith("0.0.0")||t.version.startsWith("0.0.0")?c(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):l(`MCP server is running v${t.version} but CLI is v${n} \u2014 restart your AI tool to load the new code.`,"Quit and reopen Cursor / Claude Code / VS Code (the long-running MCP process keeps the old code in memory until restart)")}function X(e){const n=_(e),o=Object.entries(n).filter(([,t])=>t).map(([t])=>t);return o.length?c(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):l("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
3
2
  Or install Ollama (ollama.com) for free local AI
4
- Or use VS Code with GitHub Copilot (zero config)`)}async function J(){return new Promise(n=>{const e=v.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{n(c("Ollama running on localhost:11434"))});e.on("error",()=>n({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),e.on("timeout",()=>{e.destroy(),n({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function H(){const n=l.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(n))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const e=JSON.parse(s.readFileSync(n,"utf8")),o=e.user?.login||e.user?.name||e.user?.email||"unknown";if(e.mode==="supabase"&&e.access_token){if(e.expires_at){const i=new Date(e.expires_at).getTime();if(Date.now()>i)return u(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return c(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return e.mode==="device-flow"&&e.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:e.access_token?u(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return u("Credentials file unreadable","infernoflow logout && infernoflow login")}}function X(){try{const n=O(import.meta.url),e=l.resolve(l.dirname(n),"..","..","bin","infernoflow.mjs");if(!s.existsSync(e))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const i=[...s.readFileSync(e,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],t=[],r=l.resolve(l.dirname(e),"..");for(const d of i){const m=d[1],h=l.join(r,"lib",m);s.existsSync(h)||t.push(m)}return t.length?p(`${t.length} routed command(s) missing module files: ${t.slice(0,3).join(", ")}${t.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):c(`All ${i.length} routed commands resolve to real files`)}catch(n){return{status:"info",message:`Router integrity check skipped: ${n.message}`,fix:null,detail:null}}}function K(n){const e=l.join(n,"package.json");if(!s.existsSync(e))return c("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(e,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const i=o.scripts||{},t=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),r=[];for(const[m,h]of Object.entries(i)){const f=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(h));if(!f)continue;const g=f[1];t.has(g)||r.push({scriptName:m,verb:g})}if(r.length===0)return c("npm scripts use current command surface");const d=r.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return u(`package.json references ${r.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function V(n){const e=l.join(n,".gitignore");if(!s.existsSync(e))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(e,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?c(".gitignore excludes node_modules"):u(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function W(n,e){const o=n.filter(t=>t.status==="warn"&&t.fix),i=[];for(const t of o){const r=t.fix;if(r.startsWith("infernoflow ")){const d=r.slice(12).split(" ");$("infernoflow",d,{cwd:e,encoding:"utf8",timeout:3e4}).status===0&&i.push(t.label)}}return i}function Y(n){return n==="pass"?w("\u2714"):n==="warn"?j("\u26A0"):n==="fail"?S("\u2717"):x("\xB7")}function z(n,e){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const r of n)o[r.status]=(o[r.status]||0)+1;console.log(),console.log(` ${b("\u{1F525} infernoflow doctor")}`),console.log();const i=Math.max(...n.map(r=>r.label.length))+2;for(const r of n)console.log(` ${Y(r.status)} ${b(r.label.padEnd(i))} ${r.message}`),r.detail&&console.log(` ${" ".repeat(i)} ${x(r.detail)}`),r.fix&&(r.status==="warn"||r.status==="fail")&&console.log(` ${" ".repeat(i)} ${N("fix:")} ${x(r.fix)}`);console.log();const t=o.fail>0?S("issues found"):o.warn>0?j("warnings"):w("all good");console.log(` ${t} \u2014 ${w(String(o.pass))} pass \xB7 ${j(String(o.warn))} warn \xB7 ${S(String(o.fail))} fail (${e}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${N("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function U(n){const e=n.slice(1),o=e.includes("--json"),i=e.includes("--fix"),t=process.cwd(),r=Date.now(),d=[a("Node.js version",()=>_()),a("infernoflow CLI",()=>P()),a("Git repository",()=>E(t)),a("inferno/ directory",()=>I(t)),a("Contract / mode",()=>T(t)),a("Scenarios",()=>G(t)),a("Changelog",()=>F(t)),a("CONTEXT.md",()=>M(t)),a("Git hooks",()=>D(t)),a("MCP server",()=>L(t)),a("AI providers",()=>R(t)),a("Cloud sync",()=>H()),a(".gitignore",()=>V(t)),a("Router integrity",()=>X()),a("npm scripts",()=>K(t)),await J().then(f=>({label:"Ollama (local AI)",...f}))],m=Date.now()-r;if(i){const f=W(d,t);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),U(["doctor","--json"])}if(o){const f={pass:0,warn:0,fail:0,info:0};d.forEach(g=>f[g.status]=(f[g.status]||0)+1),console.log(JSON.stringify({ok:f.fail===0,counts:f,results:d,elapsed:m}));return}z(d,m),d.some(f=>f.status==="fail")&&process.exit(1)}export{U as doctorCommand};
3
+ Or use VS Code with GitHub Copilot (zero config)`)}async function K(){return new Promise(e=>{const n=O.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{e(c("Ollama running on localhost:11434"))});n.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),n.on("timeout",()=>{n.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function U(){const e=a.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(e))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const n=JSON.parse(s.readFileSync(e,"utf8")),o=n.user?.login||n.user?.name||n.user?.email||"unknown";if(n.mode==="supabase"&&n.access_token){if(n.expires_at){const t=new Date(n.expires_at).getTime();if(Date.now()>t)return l(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return c(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return n.mode==="device-flow"&&n.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:n.access_token?l(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return l("Credentials file unreadable","infernoflow logout && infernoflow login")}}function V(){try{const e=A(import.meta.url),n=a.resolve(a.dirname(e),"..","..","bin","infernoflow.mjs");if(!s.existsSync(n))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...s.readFileSync(n,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=a.resolve(a.dirname(n),"..");for(const m of t){const d=m[1],p=a.join(i,"lib",d);s.existsSync(p)||r.push(d)}return r.length?h(`${r.length} routed command(s) missing module files: ${r.slice(0,3).join(", ")}${r.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):c(`All ${t.length} routed commands resolve to real files`)}catch(e){return{status:"info",message:`Router integrity check skipped: ${e.message}`,fix:null,detail:null}}}function Y(e){const n=a.join(e,"package.json");if(!s.existsSync(n))return c("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(n,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},r=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),i=[];for(const[d,p]of Object.entries(t)){const x=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!x)continue;const f=x[1];r.has(f)||i.push({scriptName:d,verb:f})}if(i.length===0)return c("npm scripts use current command surface");const m=i.map(d=>`${d.scriptName} \u2192 infernoflow ${d.verb}`).join(", ");return l(`package.json references ${i.length} deprecated command(s): ${m}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function z(e){const n=a.join(e,".gitignore");if(!s.existsSync(n))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(n,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?c(".gitignore excludes node_modules"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function q(e,n){const o=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of o){const i=r.fix;if(i.startsWith("infernoflow ")){const m=i.slice(12).split(" ");v("infernoflow",m,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function B(e){return e==="pass"?w("\u2714"):e==="warn"?S("\u26A0"):e==="fail"?k("\u2717"):j("\xB7")}function Q(e,n){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)o[i.status]=(o[i.status]||0)+1;console.log(),console.log(` ${C("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${B(i.status)} ${C(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${j(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${b("fix:")} ${j(i.fix)}`);console.log();const r=o.fail>0?k("issues found"):o.warn>0?S("warnings"):w("all good");console.log(` ${r} \u2014 ${w(String(o.pass))} pass \xB7 ${S(String(o.warn))} warn \xB7 ${k(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${b("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function Z(e){const n=e.slice(1),o=n.includes("--json"),t=n.includes("--fix"),r=process.cwd(),i=Date.now();let m="0.0.0-unknown";try{const{fileURLToPath:f}=await import("node:url"),g=a.dirname(f(import.meta.url)),N=a.join(g,"..","..","package.json");m=JSON.parse(s.readFileSync(N,"utf8")).version||m}catch{}const d=[u("Node.js version",()=>E()),u("infernoflow CLI",()=>M()),u("Git repository",()=>T(r)),u("inferno/ directory",()=>F(r)),u("Contract / mode",()=>G(r)),u("Scenarios",()=>L(r)),u("Changelog",()=>R(r)),u("CONTEXT.md",()=>D(r)),u("Git hooks",()=>J(r)),u("MCP server",()=>H(r)),u("MCP runtime version",()=>W(r,m)),u("AI providers",()=>X(r)),u("Cloud sync",()=>U()),u(".gitignore",()=>z(r)),u("Router integrity",()=>V()),u("npm scripts",()=>Y(r)),await K().then(f=>({label:"Ollama (local AI)",...f}))],p=Date.now()-i;if(t){const f=q(d,r);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),Z(["doctor","--json"])}if(o){const f={pass:0,warn:0,fail:0,info:0};d.forEach(g=>f[g.status]=(f[g.status]||0)+1),console.log(JSON.stringify({ok:f.fail===0,counts:f,results:d,elapsed:p}));return}Q(d,p),d.some(f=>f.status==="fail")&&process.exit(1)}export{Z as doctorCommand};
@@ -1,11 +1,11 @@
1
- import*as i from"node:fs";import*as s from"node:path";import*as G from"node:readline";import{fileURLToPath as pe}from"node:url";import{header as fe,ok as g,warn as P,done as q,nextSteps as de,bold as L,cyan as f,yellow as $,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ue,writeAdoptionBaseline as me,buildAdoptionReport as ye,summarizeCapabilities as ge,buildSignalsReport as he}from"./adopt.mjs";import{installCursorHooksArtifacts as we}from"../cursorHooksInstall.mjs";import{ensureAmpDir as ke,appendEntry as je,writeDefaultConfig as Se}from"../amp/io.mjs";import{installVsCodeCopilotHooksArtifacts as ve}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as z}from"../ruleFiles.mjs";import{autoSetupMcp as V}from"./setup.mjs";const be=s.dirname(pe(import.meta.url));function Ce(){return s.resolve(be,"../../templates")}function D(e,o,t=""){return new Promise(n=>{const r=t?v(` (${t})`):"";e.question(` ${o}${r}: `,c=>{n(c.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 J(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: "+f(s.relative(process.cwd(),o))),!0)}function Oe(e,o,t){i.mkdirSync(o,{recursive:!0});for(const n of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,n.name),c=s.join(o,n.name);n.isDirectory()?Oe(r,c,t):J(r,c,t)}}function Ie(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 c={"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[m,k]of Object.entries(c))n.scripts[m]||(n.scripts[m]=k,r=!0);r&&(i.writeFileSync(t,JSON.stringify(n,null,2)+`
2
- `,"utf8"),o||g("Updated "+f("package.json")+" scripts"))}const X="# --- infernoflow (developer-local AI memory; do not commit) ---",xe="# --- /infernoflow ---",B=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function Z(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.",...B,xe,""].join(`
3
- `),c=n.length===0||n.endsWith(`
1
+ import*as i from"node:fs";import*as s from"node:path";import*as G from"node:readline";import{fileURLToPath as de}from"node:url";import{header as ue,ok as g,warn as E,info as q,done as z,nextSteps as me,bold as L,cyan as f,yellow as $,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ye,writeAdoptionBaseline as ge,buildAdoptionReport as we,summarizeCapabilities as he,buildSignalsReport as ke}from"./adopt.mjs";import{installCursorHooksArtifacts as je}from"../cursorHooksInstall.mjs";import{ensureAmpDir as Se,appendEntry as V,writeDefaultConfig as ve}from"../amp/io.mjs";import{applyCleanTreePolicy as X}from"../cleanTree.mjs";import{installVsCodeCopilotHooksArtifacts as be}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as B}from"../ruleFiles.mjs";import{autoSetupMcp as Z}from"./setup.mjs";const Ce=s.dirname(de(import.meta.url));function Oe(){return s.resolve(Ce,"../../templates")}function D(e,o,n=""){return new Promise(t=>{const r=n?v(` (${n})`):"";e.question(` ${o}${r}: `,c=>{t(c.trim()||n)})})}function W(e,...o){for(const n of o){const t=e.indexOf(n);if(t!==-1&&e[t+1]&&!e[t+1].startsWith("-"))return e[t+1]}return null}function J(e,o,n,t=!1){return i.existsSync(o)&&!n?(t||E("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),t||g("Created: "+f(s.relative(process.cwd(),o))),!0)}function Ie(e,o,n){i.mkdirSync(o,{recursive:!0});for(const t of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,t.name),c=s.join(o,t.name);t.isDirectory()?Ie(r,c,n):J(r,c,n)}}function xe(e,o=!1){const n=s.join(e,"package.json");if(!i.existsSync(n))return;const t=JSON.parse(i.readFileSync(n,"utf8"));t.scripts=t.scripts||{};let r=!1;const c={"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[m,j]of Object.entries(c))t.scripts[m]||(t.scripts[m]=j,r=!0);r&&(i.writeFileSync(n,JSON.stringify(t,null,2)+`
2
+ `,"utf8"),o||g("Updated "+f("package.json")+" scripts"))}const Q="# --- infernoflow (developer-local AI memory; do not commit) ---",Ae="# --- /infernoflow ---",ee=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function Ze(e,{silent:o=!1}={}){const n=s.join(e,".gitignore");let t="";if(i.existsSync(n)&&(t=i.readFileSync(n,"utf8")),t.includes(Q))return!1;const r=["",Q,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...ee,Ae,""].join(`
3
+ `),c=t.length===0||t.endsWith(`
4
4
  `)?"":`
5
- `;if(i.writeFileSync(t,n+c+r,"utf8"),!o){g("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const m of B)console.log(v(" + ")+f(m));console.log(v(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(v(" To commit them instead: delete the `")+f("# --- infernoflow ---")+v("` block in .gitignore."))}return!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 Ae(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 Te(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 Q(e,o){const t=`# Changelog \u2014 ${o}
5
+ `;if(i.writeFileSync(n,t+c+r,"utf8"),!o){g("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const m of ee)console.log(v(" + ")+f(m));console.log(v(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(v(" To commit them instead: delete the `")+f("# --- infernoflow ---")+v("` block in .gitignore."))}return!0}function M(e){const o=s.join(e,"package.json");if(i.existsSync(o))try{const n=JSON.parse(i.readFileSync(o,"utf8"));if(n.name)return n.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(e)}function Te(e,o,n){const t={policyId:o,policyVersion:1,capabilities:n,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(t,null,2)+`
6
+ `)}function Pe(e,o){const n={schemaVersion:1,capabilities:o.map(t=>({id:t,title:t.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(n,null,2)+`
7
+ `)}function Ee(e,o){i.mkdirSync(e,{recursive:!0});const n={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(t=>({action:t,expect:`${t} works as expected`}))};i.writeFileSync(s.join(e,"happy_path.json"),JSON.stringify(n,null,2)+`
8
+ `)}function oe(e,o){const n=`# Changelog \u2014 ${o}
9
9
 
10
10
  ## Unreleased
11
11
 
@@ -14,42 +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 Pe(e,o){const{bold:t,cyan:n,gray:r,green:c,yellow:m,red:k}=await import("../ui/output.mjs");console.log(`
18
- `+t("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
17
+ `;i.writeFileSync(e,n)}async function Ne(e,o){const{bold:n,cyan:t,gray:r,green:c,yellow:m,red:j}=await import("../ui/output.mjs");console.log(`
18
+ `+n("\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(m(` \u26A0 inferno/ already exists. Use --force to overwrite.
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 O=G.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(C=>{O.question(r(" What does this project do? (one line, Enter to skip): "),I=>{O.close(),C(I.trim())})})}const A={policyId:h,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(A,null,2)+`
20
+ `)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const w=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const O=G.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(C=>{O.question(r(" What does this project do? (one line, Enter to skip): "),I=>{O.close(),C(I.trim())})})}const A={policyId:w,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(A,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(c(" \u2714 Created inferno/contract.json")),console.log(c(" \u2714 Created inferno/capabilities.json")),console.log(c(" \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 Ne=/^(?: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,Fe=/\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}:Ne.test(o)?{kind:"command",value:o}:Fe.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
- > `),c=m=>new Promise(k=>{const p=G.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(m,S=>{p.close(),k(S)})});for(let m=0;m<2;m++){const k=await c(m===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 De(e,o,t){const{bold:n,cyan:r,gray:c,green:m,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
- `+n("\u{1F525} infernoflow")+c(` \u2014 already set up
25
- `)),console.log(" "+m("\u2714")+" "+j+` found
26
- `),Z(e),z(e);try{V(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
22
+ `),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(c(" \u2714 Created inferno/contract.json")),console.log(c(" \u2714 Created inferno/capabilities.json")),console.log(c(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+n("Ready. Start using it:")),console.log(" "+t("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+t("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+t("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+t("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,Re=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,_e=/(?:^|\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}:Re.test(o)?{kind:"command",value:o}:_e.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function De({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:n,cyan:t}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
23
+ > `),c=m=>new Promise(j=>{const p=G.createInterface({input:process.stdin,output:process.stdout});let w=!1;p.on("SIGINT",()=>{w=!0,p.close(),j(null)}),p.on("close",()=>{w&&j(null)}),p.question(m,S=>{p.close(),j(S)})});for(let m=0;m<2;m++){const j=await c(m===0?r:" "+o("> "));if(j===null)return console.log(),"";const p=$e(j);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+n("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+t('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+n("\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(" "+n("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function Ge(e,o,n){const{bold:t,cyan:r,gray:c,green:m,yellow:j}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),w=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(w))&&!o){const h=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
24
+ `+t("\u{1F525} infernoflow")+c(` \u2014 already set up
25
+ `)),console.log(" "+m("\u2714")+" "+h+` found
26
+ `),X(e),B(e);try{Z(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
27
27
  `)),i.existsSync(p)||console.log(c(" Tip: run ")+r("infernoflow amp migrate")+c(` to move legacy memory into .ai-memory/
28
28
  `)),console.log(c(` For contracts & CI gates: infernoflow init --mode full
29
29
  `));return}const O=M(e);console.log(`
30
- `+n("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
30
+ `+t("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
31
31
  `)),console.log(" Detected: "+r(O)+`
32
- `),ke(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),Se(e,{project:O,config:{autoCapture:!0}}),Z(e);const C=z(e);for(const j of C)(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 I=await $e({yes:t});I&&(je(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:I,source:"init"}),console.log(`
33
- `+m("\u2714")+" First gotcha logged!")),console.log(`
32
+ `),Se(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),ve(e,{project:O,config:{autoCapture:!0}}),X(e);const C=B(e);for(const h of C)"created"in h&&(h.created||h.updated)&&g((h.created?"Created: ":"Updated: ")+r(h.rel));try{Z(e)}catch(h){E("MCP auto-setup skipped: "+h.message)}const I=await De({yes:n});I?(V(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:I,source:"init"}),console.log(`
33
+ `+m("\u2714")+" First gotcha logged!")):n&&V(e,{ts:new Date().toISOString(),agent:"infernoflow",type:"note",summary:"infernoflow init complete \u2014 memory loop is live. Run `infernoflow status` to verify.",source:"init"}),console.log(`
34
34
  `+m("\u2714")+` You're set up. Quick commands:
35
- `),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
35
+ `),console.log(" "+r("infernoflow status")+c(" \u2014 verify the loop end-to-end")),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
36
36
  `)),console.log(c(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
37
- `)),console.log(c(` Want contracts & CI gates? Run: infernoflow init --mode full
38
- `))}function Ge(){console.log(["infernoflow init \u2014 set up persistent AI memory in this project","","Usage:"," infernoflow init Memory-first setup (default, 60 sec)"," infernoflow init --lite Minimal: 3 files, no scripts, no workflows"," infernoflow init --mode full Contracts + CI gates (advanced)"," infernoflow init --adopt Detect existing capabilities from code"," infernoflow init --template <name> Start from a project template","","Options:"," -y, --yes Non-interactive; accept all defaults"," -f, --force Overwrite existing inferno/ or .ai-memory/"," --cursor-hooks Also install Cursor IDE hooks (advanced)"," --vscode-copilot-hooks Also install VS Code Copilot hooks (advanced)"," --lang <lang> Override language detection (with --adopt)"," --framework <name> Override framework detection (with --adopt)"," --project-type <type> Override project-type detection (with --adopt)"," -h, --help Show this help","","Default flow writes .ai-memory/ + AI rule files + MCP server config, then asks","for one first gotcha. Press Enter to skip \u2014 never blocks."].join(`
39
- `))}async function Xe(e){if(e.includes("--help")||e.includes("-h")){Ge();return}const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),n=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),c=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),m=c==="full"||c==="contract",k=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!m&&!k){await De(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 l=a?.getTemplate(h);if(!l){const d=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";P(`Unknown template: ${h}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),E=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(E)||i.mkdirSync(E,{recursive:!0});const _=M(o),w=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:w.map(d=>d.id)},null,2)+`
40
- `),i.writeFileSync(s.join(y,"capabilities.json"),JSON.stringify({capabilities:w.map(d=>({id:d.id,description:d.description,since:new Date().toISOString().slice(0,10),source:`template:${h}`}))},null,2)+`
41
- `);for(const d of w)i.writeFileSync(s.join(E,`${d.id}.json`),JSON.stringify({id:`${d.id}-happy-path`,capability:d.id,description:`Happy path for ${d.description||d.id}`,steps:[{action:"invoke",target:d.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[d.id]},null,2)+`
42
- `);Q(s.join(y,"CHANGELOG.md"),_),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
37
+ `))}function Le(){console.log(["infernoflow init \u2014 set up persistent AI memory in this project","","Usage:"," infernoflow init Memory-first setup (default, 60 sec)"," infernoflow init --lite Minimal: 3 files, no scripts, no workflows"," infernoflow init --mode full Contracts + CI gates (advanced)"," infernoflow init --adopt Detect existing capabilities from code"," infernoflow init --template <name> Start from a project template","","Options:"," -y, --yes Non-interactive; accept all defaults"," -f, --force Overwrite existing inferno/ or .ai-memory/"," --cursor-hooks Also install Cursor IDE hooks (advanced)"," --vscode-copilot-hooks Also install VS Code Copilot hooks (advanced)"," --lang <lang> Override language detection (with --adopt)"," --framework <name> Override framework detection (with --adopt)"," --project-type <type> Override project-type detection (with --adopt)"," -h, --help Show this help","","Default flow writes .ai-memory/ + AI rule files + MCP server config, then asks","for one first gotcha. Press Enter to skip \u2014 never blocks."].join(`
38
+ `))}async function Qe(e){if(e.includes("--help")||e.includes("-h")){Le();return}const o=process.cwd(),n=e.includes("--force")||e.includes("-f"),t=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),c=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),m=c==="full"||c==="contract",j=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!m&&!j){await Ge(o,n,t);return}if(e.includes("--lite")){await Ne(o,n);return}const p=e.indexOf("--template"),w=p!==-1?e[p+1]:null;if(w){let a;try{a=await import("../templates/index.mjs")}catch{}const l=a?.getTemplate(w);if(!l){const d=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";E(`Unknown template: ${w}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),P=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(P)||i.mkdirSync(P,{recursive:!0});const _=M(o),k=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:k.map(d=>d.id)},null,2)+`
39
+ `),i.writeFileSync(s.join(y,"capabilities.json"),JSON.stringify({capabilities:k.map(d=>({id:d.id,description:d.description,since:new Date().toISOString().slice(0,10),source:`template:${w}`}))},null,2)+`
40
+ `);for(const d of k)i.writeFileSync(s.join(P,`${d.id}.json`),JSON.stringify({id:`${d.id}-happy-path`,capability:d.id,description:`Happy path for ${d.description||d.id}`,steps:[{action:"invoke",target:d.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[d.id]},null,2)+`
41
+ `);oe(s.join(y,"CHANGELOG.md"),_),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
43
42
 
44
- > Template: ${h} \u2014 ${l.description}
43
+ > Template: ${w} \u2014 ${l.description}
45
44
 
46
45
  ## Hint
47
46
  ${l.contextHint}
48
47
 
49
- ## Capabilities (${w.length})
50
- ${w.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
48
+ ## Capabilities (${k.length})
49
+ ${k.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
51
50
  `)}
52
- `),l.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,x])=>console.log(` ${L(d)}: ${v(x)}`)),console.log()),q(`Initialised from template ${L(f(h))} \u2014 ${L(String(w.length))} capabilities`),console.log(),info(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),A=e.includes("--vscode-copilot-hooks"),O=e.includes("--report-json"),C=e.includes("--report-json-only"),I=e.includes("--report-human-only"),j=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=C;C&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||fe("init");const b=s.join(o,"inferno"),ee=s.join(o,".github","workflows");i.existsSync(b)&&!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 N=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=N,T=Y.split(",").map(a=>a.trim());if(r){let l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});if(!n&&!C){const w=G.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},x=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
53
- `));const le=await D(w,"Language",d.language||x.language||"unknown"),ce=await D(w,"Framework",d.framework||x.framework||"unknown"),ae=await D(w,"Project type",d.projectType||x.projectType||"unknown");w.close(),l=H(o,{language:le,framework:ce,projectType:ae})}const y=l.capabilities,E=ge(y);C?console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)):(console.log(),console.log(v(ye(y))),console.log(),console.log(v(he(l))),console.log(),O&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)),console.log()));const _=await ue(y,n);F=N,T=_.map(w=>w.id)}else if(!n){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
54
- `)),F=await D(a,"Project / policy name",N),T=(await D(a,"Capabilities (comma-separated)",Y)).split(",").map(y=>y.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(b,{recursive:!0}),r){const a=T.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});me(b,F,a,l),u||(g("Created: "+f("inferno/contract.json")),g("Created: "+f("inferno/capabilities.json")),g("Created: "+f("inferno/scenarios/adoption_baseline.json")),g("Created: "+f("inferno/adoption_profile.json")),g("Created: "+f("inferno/CHANGELOG.md")))}else Ae(s.join(b,"contract.json"),F,T),u||g("Created: "+f("inferno/contract.json")),Te(s.join(b,"capabilities.json"),T),u||g("Created: "+f("inferno/capabilities.json")),Ee(s.join(b,"scenarios"),T),u||g("Created: "+f("inferno/scenarios/happy_path.json")),Q(s.join(b,"CHANGELOG.md"),F),u||g("Created: "+f("inferno/CHANGELOG.md"));const R=Ce(),oe=s.join(R,"scripts","inferno-doc-gate.mjs"),ne=s.join(o,"scripts","inferno-doc-gate.mjs");J(oe,ne,t,u);const te=s.join(R,"scripts","inferno-install-hooks.mjs"),ie=s.join(o,"scripts","inferno-install-hooks.mjs");J(te,ie,t,u);const se=s.join(R,"ci","github-inferno-check.yml"),re=s.join(ee,"infernoflow-check.yml");if(J(se,re,t,u),Ie(o,u),S&&we({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),A&&ve({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});l.stack=y.developmentProfile,i.writeFileSync(a,JSON.stringify(l,null,2)+`
55
- `,"utf8"),u||g("Created: "+f("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(b,"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}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),de([f("infernoflow status")+" \u2014 see your contract at a glance",f("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 "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...A?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!A?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Z as ensureGitignoreEntries,Xe as initCommand};
51
+ `),l.scripts&&(q("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,x])=>console.log(` ${L(d)}: ${v(x)}`)),console.log()),z(`Initialised from template ${L(f(w))} \u2014 ${L(String(k.length))} capabilities`),console.log(),q(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),A=e.includes("--vscode-copilot-hooks"),O=e.includes("--report-json"),C=e.includes("--report-json-only"),I=e.includes("--report-human-only"),h=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=C;C&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||ue("init");const b=s.join(o,"inferno"),ne=s.join(o,".github","workflows");i.existsSync(b)&&!n&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),E("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const N=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=N,T=Y.split(",").map(a=>a.trim());if(r){let l=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});if(!t&&!C){const k=G.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},x=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
52
+ `));const ae=await D(k,"Language",d.language||x.language||"unknown"),pe=await D(k,"Framework",d.framework||x.framework||"unknown"),fe=await D(k,"Project type",d.projectType||x.projectType||"unknown");k.close(),l=H(o,{language:ae,framework:pe,projectType:fe})}const y=l.capabilities,P=he(y);C?console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:P,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)):(console.log(),console.log(v(we(y))),console.log(),console.log(v(ke(l))),console.log(),O&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:P,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)),console.log()));const _=await ye(y,t);F=N,T=_.map(k=>k.id)}else if(!t){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
53
+ `)),F=await D(a,"Project / policy name",N),T=(await D(a,"Capabilities (comma-separated)",Y)).split(",").map(y=>y.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(b,{recursive:!0}),r){const a=T.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});ge(b,F,a,l),u||(g("Created: "+f("inferno/contract.json")),g("Created: "+f("inferno/capabilities.json")),g("Created: "+f("inferno/scenarios/adoption_baseline.json")),g("Created: "+f("inferno/adoption_profile.json")),g("Created: "+f("inferno/CHANGELOG.md")))}else Te(s.join(b,"contract.json"),F,T),u||g("Created: "+f("inferno/contract.json")),Pe(s.join(b,"capabilities.json"),T),u||g("Created: "+f("inferno/capabilities.json")),Ee(s.join(b,"scenarios"),T),u||g("Created: "+f("inferno/scenarios/happy_path.json")),oe(s.join(b,"CHANGELOG.md"),F),u||g("Created: "+f("inferno/CHANGELOG.md"));const R=Oe(),te=s.join(R,"scripts","inferno-doc-gate.mjs"),ie=s.join(o,"scripts","inferno-doc-gate.mjs");J(te,ie,n,u);const se=s.join(R,"scripts","inferno-install-hooks.mjs"),re=s.join(o,"scripts","inferno-install-hooks.mjs");J(se,re,n,u);const le=s.join(R,"ci","github-inferno-check.yml"),ce=s.join(ne,"infernoflow-check.yml");if(J(le,ce,n,u),xe(o,u),S&&je({cwd:o,templatesRoot:R,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||E(a)}}),A&&be({cwd:o,templatesRoot:R,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||E(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});l.stack=y.developmentProfile,i.writeFileSync(a,JSON.stringify(l,null,2)+`
54
+ `,"utf8"),u||g("Created: "+f("inferno/context-state.json"))}if(!u){z("infernoflow initialized!");const a=s.join(b,"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}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),me([f("infernoflow status")+" \u2014 see your contract at a glance",f("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 "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...A?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!A?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Ze as ensureGitignoreEntries,Qe as initCommand};
@@ -1,20 +1,14 @@
1
- import*as f from"node:fs";import*as m from"node:path";import"node:os";import{bold as j,cyan as T,gray as n,green as F,red as b}from"../ui/output.mjs";import{ampPaths as A,appendEntry as k,readEntries as D,AMP_MARKERS as E}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as _}from"../ruleFiles.mjs";function C(){return A(process.cwd())}function P(){try{const e=D(process.cwd()),l=e.filter(o=>o.type==="gotcha"),i=e.filter(o=>o.type==="decision"),t=e.filter(o=>o.type==="attempt"&&(o.result==="failed"||o.result==="partial")),s=["# Project Context (auto-generated by infernoflow)","",`> Last updated: ${new Date().toISOString()}`,""];if(l.length){s.push("## \u26A0\uFE0F Known Gotchas (Read These First)","");for(const o of l)s.push(`- ${o.summary}`);s.push("")}if(i.length){s.push("## \u2713 Decisions In Effect","");for(const o of i)s.push(`- ${o.summary}`);s.push("")}if(t.length){s.push("## \u274C Things That Don't Work (Don't Try These)","");for(const o of t)s.push(`- ${o.summary}`);s.push("")}const p=s.join(`
2
- `),g=`${E.start}
3
- ${p}
4
- ${E.end}
5
- `,a=process.cwd(),d=o=>{let c="";try{c=f.readFileSync(o,"utf8")}catch{}const w=c.indexOf(E.start),S=c.indexOf(E.end);let $;w!==-1&&S!==-1&&S>w?$=c.slice(0,w)+g+c.slice(S+E.end.length).replace(/^\n+/,""):c?$=c.replace(/\s+$/,"")+`
6
-
7
- `+g:$=g,f.writeFileSync(o,$,"utf8")},O=m.join(a,"CLAUDE.md");f.existsSync(O)&&d(O);const I=m.join(a,".cursorrules");(f.existsSync(I)||f.existsSync(m.join(a,".cursor")))&&d(I);const y=m.join(a,".github","copilot-instructions.md");f.existsSync(m.join(a,".github"))&&d(y)}catch{}}const N=["note","attempt","decision","gotcha","preference","theme","handoff","error"],L=["worked","failed","partial","unknown"];function U(){return D(process.cwd())}function W(e,{auto:l=!1,quiet:i=!1}={}){const t=process.cwd(),s=m.join(t,".ai-memory"),p=m.join(t,"inferno");if(l&&!f.existsSync(s)&&!f.existsSync(p))return!1;!f.existsSync(s)&&!f.existsSync(p)&&(i||console.error(b(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
8
- `)),process.exit(1)),k(t,e);try{_(t)}catch{}return!0}function M(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function V(e,l){const i=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),t=e.type||"note",s=t==="gotcha"?"\x1B[33m":t==="decision"?"\x1B[36m":t==="theme"?"\x1B[35m":t==="preference"?"\x1B[34m":t==="attempt"?"\x1B[90m":t==="error"?"\x1B[31m":"\x1B[0m",p="\x1B[0m",g=e.result?` [${e.result}]`:"",a=e.agent&&e.agent!=="human"?n(` (${e.agent})`):"";return` ${n(String(l+1).padStart(3))} ${n(i)} ${s}${t}${p}${g} ${e.summary}${a}`}async function Y(e){const l=r=>e.includes(r),i=(r,u)=>{const h=e.indexOf(r);return h!==-1&&e[h+1]?e[h+1]:u},t=l("--show"),s=l("--clear"),p=l("--json"),g=l("--auto"),a=l("--quiet"),d=i("--source",null);if(t||p){const r=U(),u=e[e.indexOf("--show")+1],h=u&&/^\d+$/.test(u)?parseInt(u):20,x=r.slice(-h);if(p){console.log(JSON.stringify(x,null,2));return}if(console.log(`
9
- `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!x.length){console.log(n(`
1
+ import*as g from"node:fs";import*as $ from"node:path";import"node:os";import{bold as N,cyan as k,gray as t,green as b,red as x}from"../ui/output.mjs";import{ampPaths as T,appendEntry as P,readEntries as C}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as v}from"../ruleFiles.mjs";function U(){return T(process.cwd())}const A=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],F=["worked","failed","partial","unknown"];function V(){return C(process.cwd())}function q(e,{auto:l=!1,quiet:o=!1}={}){const s=process.cwd(),f=$.join(s,".ai-memory"),i=$.join(s,"inferno");if(l&&!g.existsSync(f)&&!g.existsSync(i))return!1;!g.existsSync(f)&&!g.existsSync(i)&&(o||console.error(x(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
+ `)),process.exit(1)),P(s,e);try{v(s)}catch{}return!0}function W(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function B(e,l){const o=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=e.type||"note",f=s==="gotcha"?"\x1B[33m":s==="decision"?"\x1B[36m":s==="theme"?"\x1B[35m":s==="preference"?"\x1B[34m":s==="attempt"?"\x1B[90m":s==="error"?"\x1B[31m":"\x1B[0m",i="\x1B[0m",d=e.result?` [${e.result}]`:"",u=e.agent&&e.agent!=="human"?t(` (${e.agent})`):"";return` ${t(String(l+1).padStart(3))} ${t(o)} ${f}${s}${i}${d} ${e.summary}${u}`}async function K(e){const l=n=>e.includes(n),o=(n,r)=>{const a=e.indexOf(n);return a!==-1&&e[a+1]?e[a+1]:r},s=l("--show"),f=l("--clear"),i=l("--json"),d=l("--auto"),u=l("--quiet"),h=o("--source",null);if(s||i){const n=V(),r=e[e.indexOf("--show")+1],a=r&&/^\d+$/.test(r)?parseInt(r):20,m=n.slice(-a);if(i){console.log(JSON.stringify(m,null,2));return}if(console.log(`
3
+ `+N("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!m.length){console.log(t(`
10
4
  No entries yet. Start logging with: infernoflow log "<what happened>"
11
- `));return}console.log(n(` Showing last ${x.length} of ${r.length} entries
12
- `)),x.forEach((v,R)=>console.log(V(v,r.length-x.length+R))),console.log();return}if(s){const{sessions:r}=C();if(!f.existsSync(r)){console.log(n(` Nothing to clear.
13
- `));return}const u=r.replace(".jsonl",`-archive-${Date.now()}.jsonl`);f.renameSync(r,u),console.log(F(` \u2714 Session log archived \u2192 ${m.basename(u)}
14
- `));return}const O=new Set([i("--type",""),i("--result",""),i("--agent",""),i("--source","")].filter(Boolean)),y=e.slice(1).filter(r=>!r.startsWith("--")&&!O.has(r)).join(" ").trim();if(!y){console.log(`
15
- `+j("\u{1F525} infernoflow log")+` \u2014 append to session memory
16
- `),console.log(n(" Usage:")),console.log(n(' infernoflow log "what happened"')),console.log(n(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(n(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(n(' infernoflow log "switched to dark mode" --type theme')),console.log(n(" infernoflow log --show Print last 20 entries")),console.log(n(" infernoflow log --json Print as JSON")),console.log(),console.log(n(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(n(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(n(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
17
- `));return}const o=i("--type","note"),c=i("--result",null),w=i("--agent",M());N.includes(o)||(a||console.error(b(` \u2718 Invalid type: ${o}. Valid: ${N.join(", ")}
18
- `)),process.exit(1)),c&&!L.includes(c)&&(a||console.error(b(` \u2718 Invalid result: ${c}. Valid: ${L.join(", ")}
19
- `)),process.exit(1));const S={ts:new Date().toISOString(),agent:w,type:o,summary:y,...c?{result:c}:{},...d?{source:d}:{},...g?{auto:!0}:{}};if(W(S,{auto:g,quiet:a})&&(P(),!a)){const r=o!=="note"?T(` [${o}]`):"",u=c?n(` \u2192 ${c}`):"",h=d?n(` (via ${d})`):"";console.log(F(` \u2714 Logged${r}${u}${h}: `)+y+`
20
- `)}}export{Y as logCommand};
5
+ `));return}console.log(t(` Showing last ${m.length} of ${n.length} entries
6
+ `)),m.forEach((R,_)=>console.log(B(R,n.length-m.length+_))),console.log();return}if(f){const{sessions:n}=U();if(!g.existsSync(n)){console.log(t(` Nothing to clear.
7
+ `));return}const r=n.replace(".jsonl",`-archive-${Date.now()}.jsonl`);g.renameSync(n,r),console.log(b(` \u2714 Session log archived \u2192 ${$.basename(r)}
8
+ `));return}const L=new Set([o("--type",""),o("--result",""),o("--agent",""),o("--source",""),o("--file",""),o("--line",""),o("--tags","")].filter(Boolean)),y=e.slice(1).filter(n=>!n.startsWith("--")&&!L.has(n)).join(" ").trim();if(!y){console.log(`
9
+ `+N("\u{1F525} infernoflow log")+` \u2014 append to session memory
10
+ `),console.log(t(" Usage:")),console.log(t(' infernoflow log "what happened"')),console.log(t(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(t(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(t(' infernoflow log "switched to dark mode" --type theme')),console.log(t(" infernoflow log --show Print last 20 entries")),console.log(t(" infernoflow log --json Print as JSON")),console.log(),console.log(t(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(t(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(t(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
11
+ `));return}const p=o("--type","note"),c=o("--result",null),j=o("--agent",W()),E=o("--file",null),w=o("--line",null),O=o("--tags",null);A.includes(p)||(u||console.error(x(` \u2718 Invalid type: ${p}. Valid: ${A.join(", ")}
12
+ `)),process.exit(1)),c&&!F.includes(c)&&(u||console.error(x(` \u2718 Invalid result: ${c}. Valid: ${F.join(", ")}
13
+ `)),process.exit(1));const I=w&&/^\d+$/.test(w)?parseInt(w,10):null,S=O?O.split(",").map(n=>n.trim()).filter(Boolean):null,D={ts:new Date().toISOString(),agent:j,type:p,summary:y,...c?{result:c}:{},...h?{source:h}:{},...E?{file:E}:{},...I?{line:I}:{},...S&&S.length?{tags:S}:{},...d?{auto:!0}:{}};if(q(D,{auto:d,quiet:u})){if(e.includes("--refresh-rules"))try{v(process.cwd())}catch{}if(!u){const n=p!=="note"?k(` [${p}]`):"",r=c?t(` \u2192 ${c}`):"",a=h?t(` (via ${h})`):"";console.log(b(` \u2714 Logged${n}${r}${a}: `)+y+`
14
+ `)}}}export{K as logCommand};
@@ -1,5 +1,5 @@
1
- import*as C from"node:fs";import*as D from"node:path";import{execSync as M}from"node:child_process";import{bold as b,cyan as u,gray as n,green as k,yellow as $,red as I}from"../ui/output.mjs";import{ampPaths as L,readEntries as P}from"../amp/io.mjs";const E="inferno";function Q(){return L(process.cwd()).sessions}const R=D.join(E,"contract.json");function v(e){try{return JSON.parse(C.readFileSync(e,"utf8"))}catch{return null}}function A(e,s){if(s){const o=s.match(/^(\d+)h$/i),t=s.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(t)return new Date(Date.now()-parseInt(t[1])*864e5);const c=new Date(s);if(!isNaN(c))return c}for(let o=e.length-1;o>=0;o--)if(e[o].type==="handoff"){const t=new Date(e[o].ts||0),c=new Date(Date.now()-864e5);return t>c?t:c}return new Date(Date.now()-864e5)}function B(e){const s=process.cwd(),o=p=>{try{return M(p,{cwd:s,encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}},t=e.toISOString().slice(0,19),c=o("git diff --cached --name-only"),m=o("git diff --name-only"),r=o(`git log --since="${t}" --name-only --pretty=format:""`);return[...new Set([...c.split(`
1
+ import*as C from"node:fs";import*as D from"node:path";import{execSync as M}from"node:child_process";import{bold as b,cyan as u,gray as n,green as k,yellow as $,red as I}from"../ui/output.mjs";import{readEntries as L}from"../amp/io.mjs";const x="inferno",R=D.join(x,"contract.json");function v(e){try{return JSON.parse(C.readFileSync(e,"utf8"))}catch{return null}}function P(e,s){if(s){const o=s.match(/^(\d+)h$/i),t=s.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(t)return new Date(Date.now()-parseInt(t[1])*864e5);const c=new Date(s);if(!isNaN(c.getTime()))return c}for(let o=e.length-1;o>=0;o--)if(e[o].type==="handoff"){const t=new Date(e[o].ts||0),c=new Date(Date.now()-864e5);return t>c?t:c}return new Date(Date.now()-864e5)}function A(e){const s=process.cwd(),o=p=>{try{return M(p,{cwd:s,encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}},t=e.toISOString().slice(0,19),c=o("git diff --cached --name-only"),m=o("git diff --name-only"),r=o(`git log --since="${t}" --name-only --pretty=format:""`);return[...new Set([...c.split(`
2
2
  `),...m.split(`
3
3
  `),...r.split(`
4
- `)].map(p=>p.trim()).filter(Boolean))]}function _(e,s){const o=[{keywords:["auth","login","logout","session","jwt","token","password"],topic:"authentication"},{keywords:["stripe","payment","checkout","billing","subscription"],topic:"payments"},{keywords:["upload","file","s3","storage","bucket","cdn"],topic:"file handling"},{keywords:["email","sendgrid","ses","smtp","nodemailer","twilio"],topic:"notifications"},{keywords:["db","database","prisma","mongoose","postgres","mysql","migration"],topic:"database"},{keywords:["deploy","docker","ci","workflow","action","kubernetes"],topic:"deployment"},{keywords:["cache","redis","memcache"],topic:"caching"},{keywords:["test","spec","jest","vitest","cypress","playwright"],topic:"testing"},{keywords:["config","env",".env","environment","secret"],topic:"configuration"},{keywords:["api","route","endpoint","controller","handler"],topic:"API routes"},{keywords:["ui","component","style","css","tailwind","theme"],topic:"UI/styles"}],t=s.map(r=>(r.summary||"").toLowerCase()).join(" "),c=[],m=new Set;for(const r of o){if(m.has(r.topic))continue;const y=e.filter(g=>r.keywords.some(w=>g.toLowerCase().includes(w)));!y.length||r.keywords.some(g=>t.includes(g))||(m.add(r.topic),c.push({topic:r.topic,files:y.slice(0,3),suggestedType:"gotcha"}))}return c}function J(e){const s=new Set(e.map(c=>c.type));let o=0;const t=[];return e.length>0?(o+=20,t.push({ok:!0,label:`${e.length} entr${e.length!==1?"ies":"y"} logged`})):t.push({ok:!1,label:"nothing logged this session"}),s.has("gotcha")?(o+=35,t.push({ok:!0,label:"gotchas captured"})):t.push({ok:!1,label:"no gotchas (most valuable \u2014 log landmines!)"}),s.has("decision")?(o+=25,t.push({ok:!0,label:"decisions recorded"})):t.push({ok:!1,label:"no decisions recorded"}),s.has("attempt")&&(o+=10,t.push({ok:!0,label:"attempts tracked"})),s.has("preference")&&(o+=10,t.push({ok:!0,label:"preferences noted"})),{score:Math.min(o,100),checks:t}}function U(e){if(!e)return"";const s=new Date(e),o=Date.now()-s.getTime(),t=Math.floor(o/6e4);if(t<60)return`${t}m ago`;const c=Math.floor(o/36e5);return c<24?`${c}h ago`:`${Math.floor(o/864e5)}d ago`}const G={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u25C8",note:"\xB7",error:"\u2717",handoff:"\u2192"},H={gotcha:$,decision:k,attempt:u,preference:u,theme:u,note:n,error:I,handoff:n};function Y(e){const s=H[e.type]||n,o=G[e.type]||"\xB7",t=e.result?n(` [${e.result}]`):"",c=n(` (${U(e.ts)})`);console.log(` ${s(o+" "+(e.type||"note").padEnd(11))}${t}${c}`),console.log(` ${e.summary}`)}async function V(e=[]){const s=e,o=s.includes("--json"),t=s.includes("--brief"),c=s.indexOf("--since"),m=c!==-1?s[c+1]:null,r=process.cwd();!C.existsSync(D.join(r,E))&&!C.existsSync(D.join(r,".ai-memory"))&&(o||console.error(I(` \u2718 not initialized \u2014 run: infernoflow init
5
- `)),process.exit(1));const y=P(r),p=A(y,m),g=y.filter(a=>new Date(a.ts||0)>p),w=B(p),h=_(w,g),{score:l,checks:O}=J(g),j=v(D.join(r,R));if(o){console.log(JSON.stringify({sessionStart:p.toISOString(),entries:g,changedFiles:w,unloggedTopics:h,health:{score:l,checks:O}},null,2));return}if(t){const a=l>=80?"A":l>=60?"B":l>=40?"C":"D",f=l>=60?k:l>=40?$:I;console.log(f(`Session health: ${a} (${l}/100)`)+n(` \u2014 ${g.length} entries logged`)),h.length&&console.log($(` ${h.length} topic${h.length!==1?"s":""} changed but not logged: `)+h.map(i=>i.topic).join(", "));return}const S=n(" "+"\u2500".repeat(52));console.log(),console.log(" "+b("\u{1F525} infernoflow recap")),j?.policyId&&console.log(n(` Project: ${j.policyId}`));const T=p.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(n(` Session since: ${T}`)),console.log(S),console.log(),console.log(" "+b("Captured this session")),console.log(),g.length===0)console.log(n(" Nothing logged yet this session."));else{const a=["gotcha","decision","attempt","preference","theme","note","error"],f=new Map;for(const i of g){const d=i.type||"note";f.has(d)||f.set(d,[]),f.get(d).push(i)}for(const i of a){const d=f.get(i);if(d?.length)for(const F of d)console.log(),Y(F)}}if(h.length>0){console.log(),console.log(S),console.log(),console.log(" "+b("Changed but not logged")+n(" (git diff since session start)")),console.log();for(const{topic:a,files:f}of h){console.log($(` ? ${a}`));for(const i of f)console.log(n(` ${i}`))}console.log(),console.log(n(" Any gotchas or decisions from these areas worth capturing?")),console.log(n(" Run: ")+u('infernoflow log "<what happened>" --type gotcha'))}else w.length>0&&(console.log(),console.log(S),console.log(),console.log(k(" \u2714 ")+n(`${w.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log(S),console.log(),console.log(" "+b("Session health")),console.log();const N=l>=80?"A":l>=60?"B":l>=40?"C":"D",x=l>=60?k:l>=40?$:I;console.log(` ${x(b(`${N}`))} ${x(`${l}/100`)}`),console.log();for(const{ok:a,label:f}of O){const i=a?k(" \u2714"):$(" \xB7");console.log(`${i} ${a?f:n(f)}`)}{const a=g.filter(d=>d.type==="gotcha").length,f=g.filter(d=>d.type==="decision").length,i=[];if(a===0?i.push(u('infernoflow log "..." --type gotcha')+n(" \u2014 adds 35 pts")):a<3&&l<80&&i.push(n(` ${3-a} more gotcha(s) would push you higher`)),f===0&&i.push(u('infernoflow log "..." --type decision')+n(" \u2014 adds 25 pts")),l>=60&&l<80&&i.push(n(" Almost B! One more entry gets you there.")),l>=80&&i.push(k(" Great session \u2014 your handoff will be excellent.")),i.length){console.log(),console.log(n(" How to improve:"));for(const d of i)console.log(" "+d)}}(g.length>0||h.length>0)&&(console.log(),console.log(S),console.log(),console.log(n(" Before your next session:")),console.log(n(" ")+u("infernoflow switch")+n(" \u2014 generate a handoff summary for the next AI agent")),console.log(n(" ")+u("infernoflow ask --recent")+n(" \u2014 review what's in memory before starting"))),console.log()}export{V as recapCommand};
4
+ `)].map(p=>p.trim()).filter(Boolean))]}function B(e,s){const o=[{keywords:["auth","login","logout","session","jwt","token","password"],topic:"authentication"},{keywords:["stripe","payment","checkout","billing","subscription"],topic:"payments"},{keywords:["upload","file","s3","storage","bucket","cdn"],topic:"file handling"},{keywords:["email","sendgrid","ses","smtp","nodemailer","twilio"],topic:"notifications"},{keywords:["db","database","prisma","mongoose","postgres","mysql","migration"],topic:"database"},{keywords:["deploy","docker","ci","workflow","action","kubernetes"],topic:"deployment"},{keywords:["cache","redis","memcache"],topic:"caching"},{keywords:["test","spec","jest","vitest","cypress","playwright"],topic:"testing"},{keywords:["config","env",".env","environment","secret"],topic:"configuration"},{keywords:["api","route","endpoint","controller","handler"],topic:"API routes"},{keywords:["ui","component","style","css","tailwind","theme"],topic:"UI/styles"}],t=s.map(r=>(r.summary||"").toLowerCase()).join(" "),c=[],m=new Set;for(const r of o){if(m.has(r.topic))continue;const y=e.filter(g=>r.keywords.some(w=>g.toLowerCase().includes(w)));!y.length||r.keywords.some(g=>t.includes(g))||(m.add(r.topic),c.push({topic:r.topic,files:y.slice(0,3),suggestedType:"gotcha"}))}return c}function _(e){const s=new Set(e.map(c=>c.type));let o=0;const t=[];return e.length>0?(o+=20,t.push({ok:!0,label:`${e.length} entr${e.length!==1?"ies":"y"} logged`})):t.push({ok:!1,label:"nothing logged this session"}),s.has("gotcha")?(o+=35,t.push({ok:!0,label:"gotchas captured"})):t.push({ok:!1,label:"no gotchas (most valuable \u2014 log landmines!)"}),s.has("decision")?(o+=25,t.push({ok:!0,label:"decisions recorded"})):t.push({ok:!1,label:"no decisions recorded"}),s.has("attempt")&&(o+=10,t.push({ok:!0,label:"attempts tracked"})),s.has("preference")&&(o+=10,t.push({ok:!0,label:"preferences noted"})),{score:Math.min(o,100),checks:t}}function J(e){if(!e)return"";const s=new Date(e),o=Date.now()-s.getTime(),t=Math.floor(o/6e4);if(t<60)return`${t}m ago`;const c=Math.floor(o/36e5);return c<24?`${c}h ago`:`${Math.floor(o/864e5)}d ago`}const U={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u25C8",note:"\xB7",error:"\u2717",handoff:"\u2192"},G={gotcha:$,decision:k,attempt:u,preference:u,theme:u,note:n,error:I,handoff:n};function H(e){const s=G[e.type]||n,o=U[e.type]||"\xB7",t=e.result?n(` [${e.result}]`):"",c=n(` (${J(e.ts)})`);console.log(` ${s(o+" "+(e.type||"note").padEnd(11))}${t}${c}`),console.log(` ${e.summary}`)}async function Q(e=[]){const s=e,o=s.includes("--json"),t=s.includes("--brief"),c=s.indexOf("--since"),m=c!==-1?s[c+1]:null,r=process.cwd();!C.existsSync(D.join(r,x))&&!C.existsSync(D.join(r,".ai-memory"))&&(o||console.error(I(` \u2718 not initialized \u2014 run: infernoflow init
5
+ `)),process.exit(1));const y=L(r),p=P(y,m),g=y.filter(a=>new Date(a.ts||0)>p),w=A(p),h=B(w,g),{score:l,checks:O}=_(g),j=v(D.join(r,R));if(o){console.log(JSON.stringify({sessionStart:p.toISOString(),entries:g,changedFiles:w,unloggedTopics:h,health:{score:l,checks:O}},null,2));return}if(t){const a=l>=80?"A":l>=60?"B":l>=40?"C":"D",f=l>=60?k:l>=40?$:I;console.log(f(`Session health: ${a} (${l}/100)`)+n(` \u2014 ${g.length} entries logged`)),h.length&&console.log($(` ${h.length} topic${h.length!==1?"s":""} changed but not logged: `)+h.map(i=>i.topic).join(", "));return}const S=n(" "+"\u2500".repeat(52));console.log(),console.log(" "+b("\u{1F525} infernoflow recap")),j?.policyId&&console.log(n(` Project: ${j.policyId}`));const E=p.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(n(` Session since: ${E}`)),console.log(S),console.log(),console.log(" "+b("Captured this session")),console.log(),g.length===0)console.log(n(" Nothing logged yet this session."));else{const a=["gotcha","decision","attempt","preference","theme","note","error"],f=new Map;for(const i of g){const d=i.type||"note";f.has(d)||f.set(d,[]),f.get(d).push(i)}for(const i of a){const d=f.get(i);if(d?.length)for(const F of d)console.log(),H(F)}}if(h.length>0){console.log(),console.log(S),console.log(),console.log(" "+b("Changed but not logged")+n(" (git diff since session start)")),console.log();for(const{topic:a,files:f}of h){console.log($(` ? ${a}`));for(const i of f)console.log(n(` ${i}`))}console.log(),console.log(n(" Any gotchas or decisions from these areas worth capturing?")),console.log(n(" Run: ")+u('infernoflow log "<what happened>" --type gotcha'))}else w.length>0&&(console.log(),console.log(S),console.log(),console.log(k(" \u2714 ")+n(`${w.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log(S),console.log(),console.log(" "+b("Session health")),console.log();const N=l>=80?"A":l>=60?"B":l>=40?"C":"D",T=l>=60?k:l>=40?$:I;console.log(` ${T(b(`${N}`))} ${T(`${l}/100`)}`),console.log();for(const{ok:a,label:f}of O){const i=a?k(" \u2714"):$(" \xB7");console.log(`${i} ${a?f:n(f)}`)}{const a=g.filter(d=>d.type==="gotcha").length,f=g.filter(d=>d.type==="decision").length,i=[];if(a===0?i.push(u('infernoflow log "..." --type gotcha')+n(" \u2014 adds 35 pts")):a<3&&l<80&&i.push(n(` ${3-a} more gotcha(s) would push you higher`)),f===0&&i.push(u('infernoflow log "..." --type decision')+n(" \u2014 adds 25 pts")),l>=60&&l<80&&i.push(n(" Almost B! One more entry gets you there.")),l>=80&&i.push(k(" Great session \u2014 your handoff will be excellent.")),i.length){console.log(),console.log(n(" How to improve:"));for(const d of i)console.log(" "+d)}}(g.length>0||h.length>0)&&(console.log(),console.log(S),console.log(),console.log(n(" Before your next session:")),console.log(n(" ")+u("infernoflow switch")+n(" \u2014 generate a handoff summary for the next AI agent")),console.log(n(" ")+u("infernoflow ask --recent")+n(" \u2014 review what's in memory before starting"))),console.log()}export{Q as recapCommand};
@@ -0,0 +1,5 @@
1
+ import{refreshRuleFilesFromMemory as f}from"../ruleFiles.mjs";import{findProjectRoot as u}from"../projectRoot.mjs";import{bold as t,cyan as d,gray as e,green as g,red as c}from"../ui/output.mjs";async function h(n){const i=n.includes("--dry-run")||n.includes("-n"),l=n.includes("--json"),r=u(process.cwd());if(i){if(l){console.log(JSON.stringify({dryRun:!0,projectRoot:r},null,2));return}console.log(`
2
+ `+t("\u{1F525} infernoflow refresh")+e(" \u2014 dry run")),console.log(" "+e("Project root: ")+d(r)),console.log(" "+e("Would rewrite: ")+".cursorrules, CLAUDE.md, .github/copilot-instructions.md"),console.log();return}let s;try{s=f(r)}catch(o){l?console.log(JSON.stringify({ok:!1,error:o.message},null,2)):console.error(c(`
3
+ \u2718 refresh failed: `)+o.message+`
4
+ `),process.exit(1)}if(l){console.log(JSON.stringify({ok:!0,projectRoot:r,results:s},null,2));return}console.log(`
5
+ `+t("\u{1F525} infernoflow refresh")),console.log(" "+"\u2500".repeat(50));for(const o of s)"error"in o&&o.error?console.log(" "+c("\u2718 ")+o.rel+e(" \u2014 "+o.error)):"created"in o&&(o.created||o.updated)?console.log(" "+g("\u2714 ")+o.rel+e(o.created?" \u2014 created":" \u2014 updated")):console.log(" "+e("\xB7 ")+o.rel+e(" \u2014 unchanged"));console.log()}export{h as refreshCommand};