infernoflow 0.44.17 → 0.44.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -143,7 +143,7 @@ infernoflow prune --apply --max-age-days 14 # one-off cleanup
143
143
 
144
144
  **Rotation** archives stale `note` / `attempt` / `detection` entries to `.ai-memory/archive/sessions-YYYY-MM.jsonl` — invisible to the merged read (so the AI, sidebar, `ask`, and `refresh` stop surfacing them) but still on disk if you want them back. `gotcha`, `decision`, `pattern`, and `bookmark` entries are **never auto-pruned** — that's the knowledge you logged infernoflow FOR.
145
145
 
146
- **Two-tier bodies (new in 0.44.10):** any entry can carry a rich `detail` — stored in `.ai-memory/details/<id>.md`, loaded on demand via `readDetail()`, and **never injected into rule files**. The lean index stays lean; you pay for the body only when you open it. `log --detail`, `--detail-file`, MCP `amp_write` `detail`, and the new `amp_bookmark` tool all feed it.
146
+ **Two-tier bodies:** any entry can carry a rich `detail` — stored in a single `.ai-memory/details.jsonl` (consolidated in 0.44.18; older per-entry `details/<id>.md` sidecars auto-migrate on the next write), loaded on demand via `readDetail()`, and **never injected into rule files**. The lean index stays lean; you pay for the body only when you open it. `log --detail`, `--detail-file`, MCP `amp_write` `detail`, and the new `amp_bookmark` tool all feed it.
147
147
 
148
148
  ---
149
149
 
@@ -1,20 +1,27 @@
1
- import*as s from"node:fs";import*as J from"node:os";import*as r from"node:path";import{findProjectRoot as O}from"../projectRoot.mjs";import{getBranchInfo as M}from"../git/branch.mjs";const T="1.0",_=new Set(["gotcha","decision","attempt","note","detection","pattern"]),P=new Set(["copilot","cursor","claude","windsurf","other"]);function L(e){let t=r.basename(r.resolve(e));try{const o=r.join(e,".ai-memory","amp.json");if(s.existsSync(o)){const n=JSON.parse(s.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function W(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const l=JSON.parse(s.readFileSync(r.join(t,"amp.json"),"utf8"));l&&typeof l.globalDir=="string"&&l.globalDir.trim()&&(o=l.globalDir.trim())}catch{}if(!o)return r.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=r.join(J.homedir(),n.slice(1).replace(/^[\/\\]/,""))),r.isAbsolute(n)||(n=r.resolve(e,n));const c=L(e);return r.join(n,c,"global.jsonl")}function m(e,t={}){const o=t.literal?r.resolve(e):O(e),n=r.join(o,".ai-memory"),c=r.join(o,"inferno"),l=t.forWrite||s.existsSync(n)||!s.existsSync(c),a=l?n:c,i=l,u=r.join(a,"branches"),f=M(o),h=r.join(u,`${f.currentSlug}.jsonl`),g=f.defaultSlug?r.join(u,`${f.defaultSlug}.jsonl`):null,S=Y(o,a);return{root:a,projectRoot:o,isAmp:i,sessions:r.join(a,"sessions.jsonl"),config:r.join(a,i?"amp.json":"config.json"),handoff:r.join(a,i?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:h,defaultBranchFile:g,branch:f}}function Y(e,t){try{return W(e,t)}catch{return r.join(t,"global.jsonl")}}function x(e){const t=O(e),o=r.join(t,".ai-memory");return s.existsSync(o)||s.mkdirSync(o,{recursive:!0}),o}const B="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function E(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=B[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=B[Math.floor(Math.random()*32)];return t+o}function N(e){const t={...e.meta||{}};let o=e.type||"note";_.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result),e.detailRef&&(t.detailRef=e.detailRef);let n;const c=e.agent;c&&P.has(c)?n=c:c&&(t.agent=c);const l=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),a=e.confidence!=null?e.confidence:e.auto?.7:void 0,i={type:o,msg:e.summary||e.msg||"",ts:l,id:e.id||`amp_${E()}`};return e.file&&(i.file=e.file),e.line&&(i.line=e.line),e.function&&(i.function=e.function),e.tags&&e.tags.length&&(i.tags=e.tags),e.source&&(i.source=e.source),n&&(i.tool=n),e.session&&(i.session=e.session),a!=null&&(i.confidence=a),Object.keys(t).length&&(i.meta=t),i}function G(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),t.detailRef&&(n.detailRef=t.detailRef),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:c,agent:l,result:a,detailRef:i,...u}=t;return Object.keys(u).length&&(n.meta=u),n}function U(e){if(!e||!s.existsSync(e))return[];try{return s.readFileSync(e,"utf8").split(`
2
- `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(G)}catch{return[]}}function ne(e){const t=m(e),o=new Set,n=[],c=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],l=[...new Set(c.filter(Boolean))];for(const a of l)for(const i of U(a)){const u=i.id||`${i.ts}|${i.summary}`;o.has(u)||(o.add(u),n.push(i))}return n.sort((a,i)=>{const u=typeof a.ts=="number"?a.ts:Date.parse(a.ts||0),f=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-f})}function H(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}function oe(e,t){x(e);const o=m(e,{forWrite:!0}),n=t.id||`amp_${E()}`,c={...t,id:n};if(typeof t.detail=="string"&&t.detail.trim()){const u=`details/${n}.md`,f=r.join(o.root,"details",`${n}.md`);try{s.mkdirSync(r.dirname(f),{recursive:!0}),s.writeFileSync(f,t.detail,"utf8"),c.detailRef=u}catch{}}delete c.detail;const l=H(o,c),a=N(c),i=JSON.stringify(a)+`
3
- `;if(s.mkdirSync(r.dirname(l),{recursive:!0}),s.appendFileSync(l,i,"utf8"),l!==o.sessions)try{s.mkdirSync(r.dirname(o.sessions),{recursive:!0}),s.appendFileSync(o.sessions,i,"utf8")}catch{}return a}function ie(e,t){const o=m(e);return r.join(o.root,"details",`${t}.md`)}function se(e,t){if(!t)return null;const o=t.detailRef||t.meta&&t.meta.detailRef;if(!o||typeof o!="string")return null;const n=m(e),c=r.isAbsolute(o)?o:r.join(n.root,o);try{return s.readFileSync(c,"utf8")}catch{return null}}function re(e,t){if(!t)return{removed:0,files:[]};const o=m(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(s.existsSync(o.branchesDir))for(const i of s.readdirSync(o.branchesDir))i.endsWith(".jsonl")&&n.push(r.join(o.branchesDir,i))}catch{}const c=[...new Set(n)];let l=0;const a=[];for(const i of c){if(!s.existsSync(i))continue;let u;try{u=s.readFileSync(i,"utf8").split(`
4
- `)}catch{continue}const f=[];let h=0;for(const g of u){if(!g.trim())continue;let S;try{S=JSON.parse(g).id}catch{f.push(g);continue}if(S===t){h++,l++;continue}f.push(g)}h>0&&(s.writeFileSync(i,f.length?f.join(`
1
+ import*as i from"node:fs";import*as M from"node:os";import*as a from"node:path";import{findProjectRoot as R}from"../projectRoot.mjs";import{getBranchInfo as P}from"../git/branch.mjs";const T="1.0",L=new Set(["gotcha","decision","attempt","note","detection","pattern"]),W=new Set(["copilot","cursor","claude","windsurf","other"]);function Y(e){let t=a.basename(a.resolve(e));try{const o=a.join(e,".ai-memory","amp.json");if(i.existsSync(o)){const n=JSON.parse(i.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function G(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const f=JSON.parse(i.readFileSync(a.join(t,"amp.json"),"utf8"));f&&typeof f.globalDir=="string"&&f.globalDir.trim()&&(o=f.globalDir.trim())}catch{}if(!o)return a.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=a.join(M.homedir(),n.slice(1).replace(/^[\/\\]/,""))),a.isAbsolute(n)||(n=a.resolve(e,n));const c=Y(e);return a.join(n,c,"global.jsonl")}function m(e,t={}){const o=t.literal?a.resolve(e):R(e),n=a.join(o,".ai-memory"),c=a.join(o,"inferno"),f=t.forWrite||i.existsSync(n)||!i.existsSync(c),s=f?n:c,r=f,u=a.join(s,"branches"),l=P(o),h=a.join(u,`${l.currentSlug}.jsonl`),g=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,S=U(o,s);return{root:s,projectRoot:o,isAmp:r,sessions:a.join(s,"sessions.jsonl"),config:a.join(s,r?"amp.json":"config.json"),handoff:a.join(s,r?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:h,defaultBranchFile:g,branch:l}}function U(e,t){try{return G(e,t)}catch{return a.join(t,"global.jsonl")}}function v(e){const t=R(e),o=a.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const B="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function w(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=B[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=B[Math.floor(Math.random()*32)];return t+o}function E(e){const t={...e.meta||{}};let o=e.type||"note";L.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result),e.detailRef&&(t.detailRef=e.detailRef);let n;const c=e.agent;c&&W.has(c)?n=c:c&&(t.agent=c);const f=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),s=e.confidence!=null?e.confidence:e.auto?.7:void 0,r={type:o,msg:e.summary||e.msg||"",ts:f,id:e.id||`amp_${w()}`};return e.file&&(r.file=e.file),e.line&&(r.line=e.line),e.function&&(r.function=e.function),e.tags&&e.tags.length&&(r.tags=e.tags),e.source&&(r.source=e.source),n&&(r.tool=n),e.session&&(r.session=e.session),s!=null&&(r.confidence=s),Object.keys(t).length&&(r.meta=t),r}function H(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),t.detailRef&&(n.detailRef=t.detailRef),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:c,agent:f,result:s,detailRef:r,...u}=t;return Object.keys(u).length&&(n.meta=u),n}function V(e){if(!e||!i.existsSync(e))return[];try{return i.readFileSync(e,"utf8").split(`
2
+ `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(H)}catch{return[]}}function ae(e){const t=m(e),o=new Set,n=[],c=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],f=[...new Set(c.filter(Boolean))];for(const s of f)for(const r of V(s)){const u=r.id||`${r.ts}|${r.summary}`;o.has(u)||(o.add(u),n.push(r))}return n.sort((s,r)=>{const u=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0),l=typeof r.ts=="number"?r.ts:Date.parse(r.ts||0);return u-l})}function z(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}const J=new Set;function b(e){return a.join(e,"details.jsonl")}function q(e,t,o){const n=b(e);i.mkdirSync(a.dirname(n),{recursive:!0}),i.appendFileSync(n,JSON.stringify({id:t,body:o})+`
3
+ `,"utf8")}function K(e,t){if(!t)return null;let o;try{o=i.readFileSync(b(e),"utf8")}catch{return null}let n=null;for(const c of o.split(`
4
+ `))if(c.trim())try{const f=JSON.parse(c);f.id===t&&typeof f.body=="string"&&(n=f.body)}catch{}return n}function Q(e,t){const o=b(e);let n;try{n=i.readFileSync(o,"utf8")}catch{return!1}const c=[];let f=!1;for(const s of n.split(`
5
+ `))if(s.trim()){try{if(JSON.parse(s).id===t){f=!0;continue}}catch{}c.push(s)}return f&&i.writeFileSync(o,c.length?c.join(`
5
6
  `)+`
6
- `:"","utf8"),a.push(i))}try{const i=r.join(o.root,"details",`${t}.md`);s.existsSync(i)&&(s.unlinkSync(i),a.push(i))}catch{}return{removed:l,files:a}}const V=30,z=["note","attempt","detection"],w=new Set(["gotcha","decision","pattern"]);function q(e){const t=e&&e.config&&e.config.rotation&&typeof e.config.rotation=="object"?e.config.rotation:{};return{archiveAfterDays:Number.isInteger(t.archiveAfterDays)&&t.archiveAfterDays>0?t.archiveAfterDays:V,archivableTypes:Array.isArray(t.archivableTypes)&&t.archivableTypes.length?t.archivableTypes:z,auto:t.auto===!0}}function K(e){const t=[e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(s.existsSync(e.branchesDir))for(const o of s.readdirSync(e.branchesDir))o.endsWith(".jsonl")&&t.push(r.join(e.branchesDir,o))}catch{}return[...new Set(t)]}function Q(e,t){const o=new Date(t),n=o.getUTCFullYear(),c=String(o.getUTCMonth()+1).padStart(2,"0");return r.join(e,"archive",`sessions-${n}-${c}.jsonl`)}function ce(e,t={}){const o=m(e,{forWrite:!t.dryRun}),n=q(X(e)),c=Number.isFinite(t.maxAgeDays)?t.maxAgeDays:n.archiveAfterDays,l=t.archive!==!1,a=new Set((Array.isArray(t.types)&&t.types.length?t.types:n.archivableTypes).filter(p=>!w.has(p))),i=Date.now()-c*24*60*60*1e3,u=K(o),f={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!t.dryRun},h=new Map,g=new Set,S=new Set,R=new Set;for(const p of u){if(!s.existsSync(p))continue;let b;try{b=s.readFileSync(p,"utf8").split(`
7
- `)}catch{continue}const F=[];let v=0;for(const j of b){if(!j.trim())continue;let y;try{y=JSON.parse(j)}catch{F.push(j);continue}const d=y&&y.id,D=y&&typeof y.type=="string"?y.type:"note",A=typeof y.ts=="number"?y.ts:Date.parse(y.ts||0),I=Number.isFinite(A)&&A<i,$=Array.isArray(y.tags)&&y.tags.includes("bookmark"),C=a.has(D)&&!w.has(D)&&!$;if(d&&!S.has(d)&&(S.add(d),f.scanned++),d||f.scanned++,I&&C){if(v++,(!d||!g.has(d))&&(d&&g.add(d),f.archived++,f.byType[D]=(f.byType[D]||0)+1,l)){const k=Q(o.root,A);h.has(k)||h.set(k,[]),h.get(k).push(j)}}else d&&!R.has(d)&&(R.add(d),f.kept++),d||f.kept++,F.push(j)}v>0&&!t.dryRun?(s.writeFileSync(p,F.length?F.join(`
7
+ `:"","utf8"),f}function X(e){const t=a.join(e,"details");if(!i.existsSync(t))return 0;let o;try{o=i.readdirSync(t).filter(s=>s.endsWith(".md"))}catch{return 0}if(!o.length)return 0;const n=new Set;try{const s=i.readFileSync(b(e),"utf8");for(const r of s.split(`
8
+ `))if(r.trim())try{n.add(JSON.parse(r).id)}catch{}}catch{}let c="",f=0;for(const s of o){const r=s.replace(/\.md$/,"");if(n.has(r))continue;let u;try{u=i.readFileSync(a.join(t,s),"utf8")}catch{continue}c+=JSON.stringify({id:r,body:u})+`
9
+ `,f++}if(c)try{i.appendFileSync(b(e),c,"utf8")}catch{return 0}for(const s of o)try{i.unlinkSync(a.join(t,s))}catch{}try{i.rmdirSync(t)}catch{}return f}function fe(e,t){v(e);const o=m(e,{forWrite:!0});if(!J.has(o.root)){J.add(o.root);try{X(o.root)}catch{}}const n=t.id||`amp_${w()}`,c={...t,id:n};if(typeof t.detail=="string"&&t.detail.trim())try{q(o.root,n,t.detail),c.detailRef="details.jsonl"}catch{}delete c.detail;const f=z(o,c),s=E(c),r=JSON.stringify(s)+`
10
+ `;if(i.mkdirSync(a.dirname(f),{recursive:!0}),i.appendFileSync(f,r,"utf8"),f!==o.sessions)try{i.mkdirSync(a.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,r,"utf8")}catch{}return s}function le(e,t){const o=m(e);return a.join(o.root,"details",`${t}.md`)}function ue(e,t){if(!t)return null;const o=t.detailRef||t.meta&&t.meta.detailRef;if(!o)return null;const n=m(e),c=t.id||t.meta&&t.meta.id,f=K(n.root,c);if(f!=null)return f;if(typeof o=="string"&&o!=="details.jsonl"){const s=a.isAbsolute(o)?o:a.join(n.root,o);try{return i.readFileSync(s,"utf8")}catch{return null}}return null}function de(e,t){if(!t)return{removed:0,files:[]};const o=m(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(i.existsSync(o.branchesDir))for(const r of i.readdirSync(o.branchesDir))r.endsWith(".jsonl")&&n.push(a.join(o.branchesDir,r))}catch{}const c=[...new Set(n)];let f=0;const s=[];for(const r of c){if(!i.existsSync(r))continue;let u;try{u=i.readFileSync(r,"utf8").split(`
11
+ `)}catch{continue}const l=[];let h=0;for(const g of u){if(!g.trim())continue;let S;try{S=JSON.parse(g).id}catch{l.push(g);continue}if(S===t){h++,f++;continue}l.push(g)}h>0&&(i.writeFileSync(r,l.length?l.join(`
8
12
  `)+`
9
- `:"","utf8"),f.files.push(p)):v>0&&f.files.push(p)}if(!t.dryRun&&l&&h.size>0)for(const[p,b]of h)try{s.mkdirSync(r.dirname(p),{recursive:!0}),s.appendFileSync(p,b.join(`
13
+ `:"","utf8"),s.push(r))}try{Q(o.root,t)}catch{}try{const r=a.join(o.root,"details",`${t}.md`);i.existsSync(r)&&(i.unlinkSync(r),s.push(r))}catch{}return{removed:f,files:s}}const Z=30,ee=["note","attempt","detection"],I=new Set(["gotcha","decision","pattern"]);function te(e){const t=e&&e.config&&e.config.rotation&&typeof e.config.rotation=="object"?e.config.rotation:{};return{archiveAfterDays:Number.isInteger(t.archiveAfterDays)&&t.archiveAfterDays>0?t.archiveAfterDays:Z,archivableTypes:Array.isArray(t.archivableTypes)&&t.archivableTypes.length?t.archivableTypes:ee,auto:t.auto===!0}}function ne(e){const t=[e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(i.existsSync(e.branchesDir))for(const o of i.readdirSync(e.branchesDir))o.endsWith(".jsonl")&&t.push(a.join(e.branchesDir,o))}catch{}return[...new Set(t)]}function oe(e,t){const o=new Date(t),n=o.getUTCFullYear(),c=String(o.getUTCMonth()+1).padStart(2,"0");return a.join(e,"archive",`sessions-${n}-${c}.jsonl`)}function ye(e,t={}){const o=m(e,{forWrite:!t.dryRun}),n=te(ie(e)),c=Number.isFinite(t.maxAgeDays)?t.maxAgeDays:n.archiveAfterDays,f=t.archive!==!1,s=new Set((Array.isArray(t.types)&&t.types.length?t.types:n.archivableTypes).filter(p=>!I.has(p))),r=Date.now()-c*24*60*60*1e3,u=ne(o),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!t.dryRun},h=new Map,g=new Set,S=new Set,N=new Set;for(const p of u){if(!i.existsSync(p))continue;let F;try{F=i.readFileSync(p,"utf8").split(`
14
+ `)}catch{continue}const D=[];let A=0;for(const j of F){if(!j.trim())continue;let y;try{y=JSON.parse(j)}catch{D.push(j);continue}const d=y&&y.id,x=y&&typeof y.type=="string"?y.type:"note",O=typeof y.ts=="number"?y.ts:Date.parse(y.ts||0),C=Number.isFinite(O)&&O<r,_=Array.isArray(y.tags)&&y.tags.includes("bookmark"),$=s.has(x)&&!I.has(x)&&!_;if(d&&!S.has(d)&&(S.add(d),l.scanned++),d||l.scanned++,C&&$){if(A++,(!d||!g.has(d))&&(d&&g.add(d),l.archived++,l.byType[x]=(l.byType[x]||0)+1,f)){const k=oe(o.root,O);h.has(k)||h.set(k,[]),h.get(k).push(j)}}else d&&!N.has(d)&&(N.add(d),l.kept++),d||l.kept++,D.push(j)}A>0&&!t.dryRun?(i.writeFileSync(p,D.length?D.join(`
10
15
  `)+`
11
- `,"utf8")}catch{}return f}function X(e){const{config:t}=m(e);try{return JSON.parse(s.readFileSync(t,"utf8"))}catch{return null}}function ae(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:T,project:t.project||r.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200,protocolStyle:"compact"},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
12
- `,"utf8"),!0}function fe(e,t={}){x(e);const{config:o}=m(e,{forWrite:!0});let n={};try{n=JSON.parse(s.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=T),n.project||(n.project=r.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},s.writeFileSync(o,JSON.stringify(n,null,2)+`
13
- `,"utf8"),n.config.injection}function le(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!s.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),c=r.join(n,"sessions.jsonl");if(s.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};x(e);const l=s.readFileSync(o,"utf8").split(`
14
- `).filter(Boolean);let a=0;for(const i of l)try{const u=JSON.parse(i),f=N(u);s.appendFileSync(c,JSON.stringify(f)+`
15
- `,"utf8"),a++}catch{}return s.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
16
+ `:"","utf8"),l.files.push(p)):A>0&&l.files.push(p)}if(!t.dryRun&&f&&h.size>0)for(const[p,F]of h)try{i.mkdirSync(a.dirname(p),{recursive:!0}),i.appendFileSync(p,F.join(`
17
+ `)+`
18
+ `,"utf8")}catch{}return l}function ie(e){const{config:t}=m(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function he(e,t={}){v(e);const{config:o}=m(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:T,project:t.project||a.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200,protocolStyle:"compact"},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
19
+ `,"utf8"),!0}function pe(e,t={}){v(e);const{config:o}=m(e,{forWrite:!0});let n={};try{n=JSON.parse(i.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=T),n.project||(n.project=a.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},i.writeFileSync(o,JSON.stringify(n,null,2)+`
20
+ `,"utf8"),n.config.injection}function ge(e){const t=a.join(e,"inferno"),o=a.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(e,".ai-memory"),c=a.join(n,"sessions.jsonl");if(i.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};v(e);const f=i.readFileSync(o,"utf8").split(`
21
+ `).filter(Boolean);let s=0;for(const r of f)try{const u=JSON.parse(r),l=E(u);i.appendFileSync(c,JSON.stringify(l)+`
22
+ `,"utf8"),s++}catch{}return i.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
16
23
 
17
- Copied ${a} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
24
+ Copied ${s} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
18
25
 
19
26
  The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
20
- `,"utf8"),{migrated:a,reason:"ok"}}export{T as AMP_VERSION,m as ampPaths,oe as appendEntry,re as deleteEntry,ie as detailPath,x as ensureAmpDir,G as fromAmp,E as generateULID,le as migrateLegacy,L as projectSlug,ce as pruneEntries,X as readConfig,se as readDetail,ne as readEntries,W as resolveGlobalFile,q as resolveRotationSettings,N as toAmp,fe as updateInjectionConfig,ae as writeDefaultConfig};
27
+ `,"utf8"),{migrated:s,reason:"ok"}}export{T as AMP_VERSION,m as ampPaths,fe as appendEntry,de as deleteEntry,le as detailPath,v as ensureAmpDir,H as fromAmp,w as generateULID,ge as migrateLegacy,Y as projectSlug,ye as pruneEntries,ie as readConfig,ue as readDetail,ae as readEntries,G as resolveGlobalFile,te as resolveRotationSettings,E as toAmp,pe as updateInjectionConfig,he as writeDefaultConfig};
@@ -1,11 +1,11 @@
1
- import*as n from"node:fs";import*as i from"node:path";import*as H from"node:readline";import{fileURLToPath as ye}from"node:url";import{header as ge,ok as w,warn as T,info as z,done as V,nextSteps as he,bold as J,cyan as f,yellow as G,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as M,reviewCapabilitiesInteractive as we,writeAdoptionBaseline as ke,buildAdoptionReport as je,summarizeCapabilities as Se,buildSignalsReport as ve}from"./adopt.mjs";import{installCursorHooksArtifacts as be}from"../cursorHooksInstall.mjs";import{ensureAmpDir as Ce,appendEntry as X,writeDefaultConfig as xe}from"../amp/io.mjs";import{applyCleanTreePolicy as B}from"../cleanTree.mjs";import{installVsCodeCopilotHooksArtifacts as Oe}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as Z}from"../ruleFiles.mjs";import{autoSetupMcp as Q}from"./setup.mjs";const Ie=i.dirname(ye(import.meta.url));function W(){return i.resolve(Ie,"../../templates")}function L(e,o,t=""){return new Promise(s=>{const r=t?v(` (${t})`):"";e.question(` ${o}${r}: `,l=>{s(l.trim()||t)})})}function Y(e,...o){for(const t of o){const s=e.indexOf(t);if(s!==-1&&e[s+1]&&!e[s+1].startsWith("-"))return e[s+1]}return null}function U(e,o,t,s=!1){return n.existsSync(o)&&!t?(s||T("Skipped (exists): "+i.relative(process.cwd(),o)),!1):(n.mkdirSync(i.dirname(o),{recursive:!0}),n.copyFileSync(e,o),s||w("Created: "+f(i.relative(process.cwd(),o))),!0)}function Ae(e,o,t){n.mkdirSync(o,{recursive:!0});for(const s of n.readdirSync(e,{withFileTypes:!0})){const r=i.join(e,s.name),l=i.join(o,s.name);s.isDirectory()?Ae(r,l,t):U(r,l,t)}}function ee(e,o){const t=i.join(W(),"skills");if(!n.existsSync(t))return 0;let s=0;const r=(l,d)=>{n.mkdirSync(d,{recursive:!0});for(const y of n.readdirSync(l,{withFileTypes:!0})){const a=i.join(l,y.name),u=i.join(d,y.name);if(y.isDirectory()){r(a,u);continue}n.existsSync(u)&&!o||(n.mkdirSync(i.dirname(u),{recursive:!0}),n.copyFileSync(a,u),s++,w("Created: "+f(i.relative(e,u))))}};return r(t,i.join(e,".claude","skills")),s}function oe(e,o){const t=i.join(W(),"agents");if(!n.existsSync(t))return 0;let s=0;const r=(l,d)=>{n.mkdirSync(d,{recursive:!0});for(const y of n.readdirSync(l,{withFileTypes:!0})){const a=i.join(l,y.name),u=i.join(d,y.name);if(y.isDirectory()){r(a,u);continue}n.existsSync(u)&&!o||(n.mkdirSync(i.dirname(u),{recursive:!0}),n.copyFileSync(a,u),s++,w("Created: "+f(i.relative(e,u))))}};return r(t,i.join(e,".claude","agents")),s}function Te(e,o=!1){const t=i.join(e,"package.json");if(!n.existsSync(t))return;const s=JSON.parse(n.readFileSync(t,"utf8"));s.scripts=s.scripts||{};let r=!1;const l={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[d,y]of Object.entries(l))s.scripts[d]||(s.scripts[d]=y,r=!0);r&&(n.writeFileSync(t,JSON.stringify(s,null,2)+`
2
- `,"utf8"),o||w("Updated "+f("package.json")+" scripts"))}const ne="# --- infernoflow (developer-local AI memory; do not commit) ---",Ee="# --- /infernoflow ---",te=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function oo(e,{silent:o=!1}={}){const t=i.join(e,".gitignore");let s="";if(n.existsSync(t)&&(s=n.readFileSync(t,"utf8")),s.includes(ne))return!1;const r=["",ne,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...te,Ee,""].join(`
1
+ import*as n from"node:fs";import*as i from"node:path";import*as H from"node:readline";import{fileURLToPath as ge}from"node:url";import{header as he,ok as k,warn as T,info as z,done as X,nextSteps as we,bold as J,cyan as p,yellow as F,gray as j}from"../ui/output.mjs";import{discoverProjectSignals as M,reviewCapabilitiesInteractive as ke,writeAdoptionBaseline as je,buildAdoptionReport as Se,summarizeCapabilities as ve,buildSignalsReport as Ce}from"./adopt.mjs";import{installCursorHooksArtifacts as be}from"../cursorHooksInstall.mjs";import{ensureAmpDir as xe,appendEntry as B,writeDefaultConfig as Oe}from"../amp/io.mjs";import{applyCleanTreePolicy as Z}from"../cleanTree.mjs";import{installVsCodeCopilotHooksArtifacts as Ie}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as Q}from"../ruleFiles.mjs";import{autoSetupMcp as ee}from"./setup.mjs";const Ae=i.dirname(ge(import.meta.url));function Y(){return i.resolve(Ae,"../../templates")}function L(e,o,t=""){return new Promise(s=>{const r=t?j(` (${t})`):"";e.question(` ${o}${r}: `,l=>{s(l.trim()||t)})})}function q(e,...o){for(const t of o){const s=e.indexOf(t);if(s!==-1&&e[s+1]&&!e[s+1].startsWith("-"))return e[s+1]}return null}function U(e,o,t,s=!1){return n.existsSync(o)&&!t?(s||T("Skipped (exists): "+i.relative(process.cwd(),o)),!1):(n.mkdirSync(i.dirname(o),{recursive:!0}),n.copyFileSync(e,o),s||k("Created: "+p(i.relative(process.cwd(),o))),!0)}function Te(e,o,t){n.mkdirSync(o,{recursive:!0});for(const s of n.readdirSync(e,{withFileTypes:!0})){const r=i.join(e,s.name),l=i.join(o,s.name);s.isDirectory()?Te(r,l,t):U(r,l,t)}}function oe(e,o){const t=i.join(Y(),"skills");if(!n.existsSync(t))return 0;let s=0;const r=(l,d)=>{n.mkdirSync(d,{recursive:!0});for(const g of n.readdirSync(l,{withFileTypes:!0})){const a=i.join(l,g.name),u=i.join(d,g.name);if(g.isDirectory()){r(a,u);continue}n.existsSync(u)&&!o||(n.mkdirSync(i.dirname(u),{recursive:!0}),n.copyFileSync(a,u),s++,k("Created: "+p(i.relative(e,u))))}};return r(t,i.join(e,".claude","skills")),s}function ne(e,o){const t=i.join(Y(),"agents");if(!n.existsSync(t))return 0;let s=0;const r=(l,d)=>{n.mkdirSync(d,{recursive:!0});for(const g of n.readdirSync(l,{withFileTypes:!0})){const a=i.join(l,g.name),u=i.join(d,g.name);if(g.isDirectory()){r(a,u);continue}n.existsSync(u)&&!o||(n.mkdirSync(i.dirname(u),{recursive:!0}),n.copyFileSync(a,u),s++,k("Created: "+p(i.relative(e,u))))}};return r(t,i.join(e,".claude","agents")),s}function te(){console.log(""),console.log(" "+F("\u21BB Restart Claude Code / Cursor")+j(" to activate the infernoflow skill + memory-keeper agent.")),console.log(" "+j("Editors only load newly-added .claude/ skills & agents on restart. Verify with ")+p("/skills")+j(" and ")+p("@agent-")+j("."))}function Ee(e,o=!1){const t=i.join(e,"package.json");if(!n.existsSync(t))return;const s=JSON.parse(n.readFileSync(t,"utf8"));s.scripts=s.scripts||{};let r=!1;const l={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[d,g]of Object.entries(l))s.scripts[d]||(s.scripts[d]=g,r=!0);r&&(n.writeFileSync(t,JSON.stringify(s,null,2)+`
2
+ `,"utf8"),o||k("Updated "+p("package.json")+" scripts"))}const ie="# --- infernoflow (developer-local AI memory; do not commit) ---",Pe="# --- /infernoflow ---",se=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function no(e,{silent:o=!1}={}){const t=i.join(e,".gitignore");let s="";if(n.existsSync(t)&&(s=n.readFileSync(t,"utf8")),s.includes(ie))return!1;const r=["",ie,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...se,Pe,""].join(`
3
3
  `),l=s.length===0||s.endsWith(`
4
4
  `)?"":`
5
- `;if(n.writeFileSync(t,s+l+r,"utf8"),!o){w("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const d of te)console.log(v(" + ")+f(d));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 K(e){const o=i.join(e,"package.json");if(n.existsSync(o))try{const t=JSON.parse(n.readFileSync(o,"utf8"));if(t.name)return t.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return i.basename(e)}function Pe(e,o,t){const s={policyId:o,policyVersion:1,capabilities:t,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};n.writeFileSync(e,JSON.stringify(s,null,2)+`
6
- `)}function Fe(e,o){const t={schemaVersion:1,capabilities:o.map(s=>({id:s,title:s.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};n.writeFileSync(e,JSON.stringify(t,null,2)+`
7
- `)}function Ne(e,o){n.mkdirSync(e,{recursive:!0});const t={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(s=>({action:s,expect:`${s} works as expected`}))};n.writeFileSync(i.join(e,"happy_path.json"),JSON.stringify(t,null,2)+`
8
- `)}function ie(e,o){const t=`# Changelog \u2014 ${o}
5
+ `;if(n.writeFileSync(t,s+l+r,"utf8"),!o){k("Updated "+p(".gitignore")+j(" \u2014 added entries:"));for(const d of se)console.log(j(" + ")+p(d));console.log(j(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(j(" To commit them instead: delete the `")+p("# --- infernoflow ---")+j("` block in .gitignore."))}return!0}function K(e){const o=i.join(e,"package.json");if(n.existsSync(o))try{const t=JSON.parse(n.readFileSync(o,"utf8"));if(t.name)return t.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return i.basename(e)}function Fe(e,o,t){const s={policyId:o,policyVersion:1,capabilities:t,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};n.writeFileSync(e,JSON.stringify(s,null,2)+`
6
+ `)}function Ne(e,o){const t={schemaVersion:1,capabilities:o.map(s=>({id:s,title:s.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};n.writeFileSync(e,JSON.stringify(t,null,2)+`
7
+ `)}function Re(e,o){n.mkdirSync(e,{recursive:!0});const t={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(s=>({action:s,expect:`${s} works as expected`}))};n.writeFileSync(i.join(e,"happy_path.json"),JSON.stringify(t,null,2)+`
8
+ `)}function re(e,o){const t=`# Changelog \u2014 ${o}
9
9
 
10
10
  ## Unreleased
11
11
 
@@ -14,41 +14,41 @@ import*as n from"node:fs";import*as i from"node:path";import*as H from"node:read
14
14
  ## 0.1.0 \u2014 Initial release
15
15
 
16
16
  - Project initialized with infernoflow
17
- `;n.writeFileSync(e,t)}async function Re(e,o){const{bold:t,cyan:s,gray:r,green:l,yellow:d,red:y}=await import("../ui/output.mjs");console.log(`
17
+ `;n.writeFileSync(e,t)}async function _e(e,o){const{bold:t,cyan:s,gray:r,green:l,yellow:d,red:g}=await import("../ui/output.mjs");console.log(`
18
18
  `+t("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
19
19
  `),console.log(r(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(r(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const a=i.join(e,"inferno");n.existsSync(a)&&!o&&(console.log(d(` \u26A0 inferno/ already exists. Use --force to overwrite.
20
- `)),process.exit(0)),n.mkdirSync(a,{recursive:!0});const u=K(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const E=H.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(O=>{E.question(r(" What does this project do? (one line, Enter to skip): "),I=>{E.close(),O(I.trim())})})}const x={policyId:u,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};n.writeFileSync(i.join(a,"contract.json"),JSON.stringify(x,null,2)+`
20
+ `)),process.exit(0)),n.mkdirSync(a,{recursive:!0});const u=K(e);let v="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const E=H.createInterface({input:process.stdin,output:process.stdout});v=await new Promise(x=>{E.question(r(" What does this project do? (one line, Enter to skip): "),O=>{E.close(),x(O.trim())})})}const b={policyId:u,policyVersion:1,lite:!0,capabilities:[],intent:v||void 0};n.writeFileSync(i.join(a,"contract.json"),JSON.stringify(b,null,2)+`
21
21
  `),n.writeFileSync(i.join(a,"capabilities.json"),JSON.stringify([],null,2)+`
22
- `),n.writeFileSync(i.join(a,"sessions.jsonl"),"","utf8"),n.writeFileSync(i.join(a,".lite"),"1","utf8"),console.log(l(" \u2714 Created inferno/contract.json")),console.log(l(" \u2714 Created inferno/capabilities.json")),console.log(l(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+s("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+s("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+s("infernoflow init --mode full")+r(" \u2014 expand to full setup when you need it")),console.log()}const _e=/^(?: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,$e=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,De=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function Ge(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:_e.test(o)?{kind:"command",value:o}:$e.test(o)?{kind:"command",value:o}:De.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function Le({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:t,cyan:s}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
23
- > `),l=d=>new Promise(y=>{const a=H.createInterface({input:process.stdin,output:process.stdout});let u=!1;a.on("SIGINT",()=>{u=!0,a.close(),y(null)}),a.on("close",()=>{u&&y(null)}),a.question(d,S=>{a.close(),y(S)})});for(let d=0;d<2;d++){const y=await l(d===0?r:" "+o("> "));if(y===null)return console.log(),"";const a=Ge(y);if(a.kind==="ok")return a.value;if(a.kind==="empty")return"";if(a.kind==="command"){console.log(" "+t("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+s('"API returns 202 not 200 on async upload"'));continue}if(a.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(a.kind==="tooShort"){console.log(" "+t("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function He(e,o,t){const{bold:s,cyan:r,gray:l,green:d,yellow:y}=await import("../ui/output.mjs"),a=i.join(e,".ai-memory"),u=i.join(e,"inferno"),S=i.join(a,"sessions.jsonl"),x=n.existsSync(S),E=n.existsSync(i.join(u,"sessions.jsonl"));if((x||E)&&!o){const k=n.existsSync(a)?".ai-memory/":"inferno/ (legacy)";console.log(`
22
+ `),n.writeFileSync(i.join(a,"sessions.jsonl"),"","utf8"),n.writeFileSync(i.join(a,".lite"),"1","utf8"),console.log(l(" \u2714 Created inferno/contract.json")),console.log(l(" \u2714 Created inferno/capabilities.json")),console.log(l(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+s("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+s("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+s("infernoflow init --mode full")+r(" \u2014 expand to full setup when you need it")),console.log()}const $e=/^(?: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,De=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Ge=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function Le(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:$e.test(o)?{kind:"command",value:o}:De.test(o)?{kind:"command",value:o}:Ge.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function He({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:t,cyan:s}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
23
+ > `),l=d=>new Promise(g=>{const a=H.createInterface({input:process.stdin,output:process.stdout});let u=!1;a.on("SIGINT",()=>{u=!0,a.close(),g(null)}),a.on("close",()=>{u&&g(null)}),a.question(d,v=>{a.close(),g(v)})});for(let d=0;d<2;d++){const g=await l(d===0?r:" "+o("> "));if(g===null)return console.log(),"";const a=Le(g);if(a.kind==="ok")return a.value;if(a.kind==="empty")return"";if(a.kind==="command"){console.log(" "+t("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+s('"API returns 202 not 200 on async upload"'));continue}if(a.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(a.kind==="tooShort"){console.log(" "+t("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function Je(e,o,t){const{bold:s,cyan:r,gray:l,green:d,yellow:g}=await import("../ui/output.mjs"),a=i.join(e,".ai-memory"),u=i.join(e,"inferno"),v=i.join(a,"sessions.jsonl"),b=n.existsSync(v),E=n.existsSync(i.join(u,"sessions.jsonl"));if((b||E)&&!o){const h=n.existsSync(a)?".ai-memory/":"inferno/ (legacy)";console.log(`
24
24
  `+s("\u{1F525} infernoflow")+l(` \u2014 already set up
25
- `)),console.log(" "+d("\u2714")+" "+k+` found
26
- `),B(e),Z(e);try{Q(e,{silent:!0})}catch{}ee(e,o),oe(e,o),console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
25
+ `)),console.log(" "+d("\u2714")+" "+h+` found
26
+ `),Z(e),Q(e);try{ee(e,{silent:!0})}catch{}const W=oe(e,o),I=ne(e,o);W+I>0&&te(),console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
27
27
  `)),n.existsSync(a)||console.log(l(" Tip: run ")+r("infernoflow amp migrate")+l(` to move legacy memory into .ai-memory/
28
28
  `)),console.log(l(` For contracts & CI gates: infernoflow init --mode full
29
- `));return}const I=K(e);console.log(`
29
+ `));return}const O=K(e);console.log(`
30
30
  `+s("\u{1F525} infernoflow")+l(` \u2014 let's get you set up (30 seconds)
31
- `)),console.log(" Detected: "+r(I)+`
32
- `),Ce(e),n.existsSync(S)||n.writeFileSync(S,"","utf8"),xe(e,{project:I,config:{autoCapture:!0}}),B(e);const N=Z(e);for(const k of N)"created"in k&&(k.created||k.updated)&&w((k.created?"Created: ":"Updated: ")+r(k.rel));ee(e,o),oe(e,o);try{Q(e)}catch(k){T("MCP auto-setup skipped: "+k.message)}const P=await Le({yes:t});P?(X(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:P,source:"init"}),console.log(`
33
- `+d("\u2714")+" First gotcha logged!")):t&&X(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(`
31
+ `)),console.log(" Detected: "+r(O)+`
32
+ `),xe(e),n.existsSync(v)||n.writeFileSync(v,"","utf8"),Oe(e,{project:O,config:{autoCapture:!0}}),Z(e);const N=Q(e);for(const h of N)"created"in h&&(h.created||h.updated)&&k((h.created?"Created: ":"Updated: ")+r(h.rel));const R=oe(e,o),_=ne(e,o);R+_>0&&te();try{ee(e)}catch(h){T("MCP auto-setup skipped: "+h.message)}const y=await He({yes:t});y?(B(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:y,source:"init"}),console.log(`
33
+ `+d("\u2714")+" First gotcha logged!")):t&&B(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
  `+d("\u2714")+` You're set up. Quick commands:
35
35
  `),console.log(" "+r("infernoflow status")+l(" \u2014 verify the loop end-to-end")),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
36
36
  `)),console.log(l(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
37
- `))}function Je(){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 no(e){if(e.includes("--help")||e.includes("-h")){Je();return}const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),s=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),l=e.find(p=>p.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),d=l==="full"||l==="contract",y=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!d&&!y){await He(o,t,s);return}if(e.includes("--lite")){await Re(o,t);return}const a=e.indexOf("--template"),u=a!==-1?e[a+1]:null;if(u){let p;try{p=await import("../templates/index.mjs")}catch{}const c=p?.getTemplate(u);if(!c){const m=p?p.listTemplates().map(A=>A.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";T(`Unknown template: ${u}. Available: ${m}`),process.exit(1)}const h=i.join(o,"inferno"),F=i.join(h,"scenarios");n.existsSync(h)||n.mkdirSync(h,{recursive:!0}),n.existsSync(F)||n.mkdirSync(F,{recursive:!0});const D=K(o),j=c.capabilities;n.writeFileSync(i.join(h,"contract.json"),JSON.stringify({policyId:D,policyVersion:1,capabilities:j.map(m=>m.id)},null,2)+`
39
- `),n.writeFileSync(i.join(h,"capabilities.json"),JSON.stringify({capabilities:j.map(m=>({id:m.id,description:m.description,since:new Date().toISOString().slice(0,10),source:`template:${u}`}))},null,2)+`
40
- `);for(const m of j)n.writeFileSync(i.join(F,`${m.id}.json`),JSON.stringify({id:`${m.id}-happy-path`,capability:m.id,description:`Happy path for ${m.description||m.id}`,steps:[{action:"invoke",target:m.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[m.id]},null,2)+`
41
- `);ie(i.join(h,"CHANGELOG.md"),D),n.writeFileSync(i.join(h,"CONTEXT.md"),`# ${D} \u2014 infernoflow context
37
+ `))}function Me(){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 to(e){if(e.includes("--help")||e.includes("-h")){Me();return}const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),s=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),l=e.find(f=>f.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),d=l==="full"||l==="contract",g=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!d&&!g){await Je(o,t,s);return}if(e.includes("--lite")){await _e(o,t);return}const a=e.indexOf("--template"),u=a!==-1?e[a+1]:null;if(u){let f;try{f=await import("../templates/index.mjs")}catch{}const c=f?.getTemplate(u);if(!c){const m=f?f.listTemplates().map(A=>A.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";T(`Unknown template: ${u}. Available: ${m}`),process.exit(1)}const w=i.join(o,"inferno"),P=i.join(w,"scenarios");n.existsSync(w)||n.mkdirSync(w,{recursive:!0}),n.existsSync(P)||n.mkdirSync(P,{recursive:!0});const G=K(o),S=c.capabilities;n.writeFileSync(i.join(w,"contract.json"),JSON.stringify({policyId:G,policyVersion:1,capabilities:S.map(m=>m.id)},null,2)+`
39
+ `),n.writeFileSync(i.join(w,"capabilities.json"),JSON.stringify({capabilities:S.map(m=>({id:m.id,description:m.description,since:new Date().toISOString().slice(0,10),source:`template:${u}`}))},null,2)+`
40
+ `);for(const m of S)n.writeFileSync(i.join(P,`${m.id}.json`),JSON.stringify({id:`${m.id}-happy-path`,capability:m.id,description:`Happy path for ${m.description||m.id}`,steps:[{action:"invoke",target:m.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[m.id]},null,2)+`
41
+ `);re(i.join(w,"CHANGELOG.md"),G),n.writeFileSync(i.join(w,"CONTEXT.md"),`# ${G} \u2014 infernoflow context
42
42
 
43
43
  > Template: ${u} \u2014 ${c.description}
44
44
 
45
45
  ## Hint
46
46
  ${c.contextHint}
47
47
 
48
- ## Capabilities (${j.length})
49
- ${j.map(m=>`- \`${m.id}\`: ${m.description}`).join(`
48
+ ## Capabilities (${S.length})
49
+ ${S.map(m=>`- \`${m.id}\`: ${m.description}`).join(`
50
50
  `)}
51
- `),c.scripts&&(z("Suggested package.json scripts for this template:"),Object.entries(c.scripts).forEach(([m,A])=>console.log(` ${J(m)}: ${v(A)}`)),console.log()),V(`Initialised from template ${J(f(u))} \u2014 ${J(String(j.length))} capabilities`),console.log(),z(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),x=e.includes("--vscode-copilot-hooks"),E=e.includes("--report-json"),O=e.includes("--report-json-only"),I=e.includes("--report-human-only"),N=Y(e,"--lang"),P=Y(e,"--framework"),k=Y(e,"--project-type"),g=O;O&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),g||ge("init");const b=i.join(o,"inferno"),se=i.join(o,".github","workflows");n.existsSync(b)&&!t&&(g&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),T("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const R=K(o),q="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let _=R,C=q.split(",").map(p=>p.trim());if(r||d){let c=M(o,{language:N||void 0,framework:P||void 0,projectType:k||void 0});if(!s&&!O){const j=H.createInterface({input:process.stdin,output:process.stdout}),m=c.developmentProfile||{},A=m.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
52
- `));const de=await L(j,"Language",m.language||A.language||"unknown"),ue=await L(j,"Framework",m.framework||A.framework||"unknown"),me=await L(j,"Project type",m.projectType||A.projectType||"unknown");j.close(),c=M(o,{language:de,framework:ue,projectType:me})}const h=c.capabilities,F=Se(h);O?console.log(JSON.stringify({mode:"adopt",policyId:R,inferredCapabilities:F,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)):(console.log(),console.log(v(je(h))),console.log(),console.log(v(ve(c))),console.log(),E&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:R,inferredCapabilities:F,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)),console.log()));const D=await we(h,s);_=R,C=D.map(j=>j.id),C.length===0&&(C=["ExampleCapability"],g||T("No capabilities detected from the code \u2014 wrote a placeholder. Edit "+f("inferno/capabilities.json")+" to describe what this project does."))}else if(!s){const p=H.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
53
- `)),_=await L(p,"Project / policy name",R),C=(await L(p,"Capabilities (comma-separated)",q)).split(",").map(h=>h.trim()).filter(Boolean),p.close(),console.log()}if(n.mkdirSync(b,{recursive:!0}),r){const p=C.map(h=>({id:h,title:h.replace(/([A-Z])/g," $1").trim()})),c=M(o,{language:N||void 0,framework:P||void 0,projectType:k||void 0});ke(b,_,p,c),g||(w("Created: "+f("inferno/contract.json")),w("Created: "+f("inferno/capabilities.json")),w("Created: "+f("inferno/scenarios/adoption_baseline.json")),w("Created: "+f("inferno/adoption_profile.json")),w("Created: "+f("inferno/CHANGELOG.md")))}else Pe(i.join(b,"contract.json"),_,C),g||w("Created: "+f("inferno/contract.json")),Fe(i.join(b,"capabilities.json"),C),g||w("Created: "+f("inferno/capabilities.json")),Ne(i.join(b,"scenarios"),C),g||w("Created: "+f("inferno/scenarios/happy_path.json")),ie(i.join(b,"CHANGELOG.md"),_),g||w("Created: "+f("inferno/CHANGELOG.md"));const $=W(),re=i.join($,"scripts","inferno-doc-gate.mjs"),le=i.join(o,"scripts","inferno-doc-gate.mjs");U(re,le,t,g);const ce=i.join($,"scripts","inferno-install-hooks.mjs"),ae=i.join(o,"scripts","inferno-install-hooks.mjs");U(ce,ae,t,g);const pe=i.join($,"ci","github-inferno-check.yml"),fe=i.join(se,"infernoflow-check.yml");if(U(pe,fe,t,g),Te(o,g),S&&be({cwd:o,templatesRoot:$,force:t,silent:g,logOk:p=>{g||w(p)},logWarn:p=>{g||T(p)}}),x&&Oe({cwd:o,templatesRoot:$,force:t,silent:g,logOk:p=>{g||w(p)},logWarn:p=>{g||T(p)}}),r){const p=i.join(b,"context-state.json");let c={};try{c=JSON.parse(n.readFileSync(p,"utf8"))}catch{}const h=M(o,{language:N||void 0,framework:P||void 0,projectType:k||void 0});c.stack=h.developmentProfile,n.writeFileSync(p,JSON.stringify(c,null,2)+`
54
- `,"utf8"),g||w("Created: "+f("inferno/context-state.json"))}if(!g){V("infernoflow initialized!");const p=i.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)&&!n.existsSync(p)&&(console.log(),console.log(` ${G("\u{1F4A1}")} ${J("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),he([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+G("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+G("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+G("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...x?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+G("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!x?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{oo as ensureGitignoreEntries,no as initCommand};
51
+ `),c.scripts&&(z("Suggested package.json scripts for this template:"),Object.entries(c.scripts).forEach(([m,A])=>console.log(` ${J(m)}: ${j(A)}`)),console.log()),X(`Initialised from template ${J(p(u))} \u2014 ${J(String(S.length))} capabilities`),console.log(),z(`Run ${p("infernoflow vibe")} to start vibe coding mode`),console.log();return}const v=e.includes("--cursor-hooks"),b=e.includes("--vscode-copilot-hooks"),E=e.includes("--report-json"),x=e.includes("--report-json-only"),O=e.includes("--report-human-only"),N=q(e,"--lang"),R=q(e,"--framework"),_=q(e,"--project-type"),y=x;x&&O&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),y||he("init");const h=i.join(o,"inferno"),W=i.join(o,".github","workflows");n.existsSync(h)&&!t&&(y&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),T("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const I=K(o),V="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let $=I,C=V.split(",").map(f=>f.trim());if(r||d){let c=M(o,{language:N||void 0,framework:R||void 0,projectType:_||void 0});if(!s&&!x){const S=H.createInterface({input:process.stdin,output:process.stdout}),m=c.developmentProfile||{},A=m.detected||{};console.log(j(` Review inferred development stack (press Enter to accept detected values)
52
+ `));const ue=await L(S,"Language",m.language||A.language||"unknown"),me=await L(S,"Framework",m.framework||A.framework||"unknown"),ye=await L(S,"Project type",m.projectType||A.projectType||"unknown");S.close(),c=M(o,{language:ue,framework:me,projectType:ye})}const w=c.capabilities,P=ve(w);x?console.log(JSON.stringify({mode:"adopt",policyId:I,inferredCapabilities:P,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)):(console.log(),console.log(j(Se(w))),console.log(),console.log(j(Ce(c))),console.log(),E&&!O&&(console.log(JSON.stringify({mode:"adopt",policyId:I,inferredCapabilities:P,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)),console.log()));const G=await ke(w,s);$=I,C=G.map(S=>S.id),C.length===0&&(C=["ExampleCapability"],y||T("No capabilities detected from the code \u2014 wrote a placeholder. Edit "+p("inferno/capabilities.json")+" to describe what this project does."))}else if(!s){const f=H.createInterface({input:process.stdin,output:process.stdout});console.log(j(` Press Enter to accept defaults
53
+ `)),$=await L(f,"Project / policy name",I),C=(await L(f,"Capabilities (comma-separated)",V)).split(",").map(w=>w.trim()).filter(Boolean),f.close(),console.log()}if(n.mkdirSync(h,{recursive:!0}),r){const f=C.map(w=>({id:w,title:w.replace(/([A-Z])/g," $1").trim()})),c=M(o,{language:N||void 0,framework:R||void 0,projectType:_||void 0});je(h,$,f,c),y||(k("Created: "+p("inferno/contract.json")),k("Created: "+p("inferno/capabilities.json")),k("Created: "+p("inferno/scenarios/adoption_baseline.json")),k("Created: "+p("inferno/adoption_profile.json")),k("Created: "+p("inferno/CHANGELOG.md")))}else Fe(i.join(h,"contract.json"),$,C),y||k("Created: "+p("inferno/contract.json")),Ne(i.join(h,"capabilities.json"),C),y||k("Created: "+p("inferno/capabilities.json")),Re(i.join(h,"scenarios"),C),y||k("Created: "+p("inferno/scenarios/happy_path.json")),re(i.join(h,"CHANGELOG.md"),$),y||k("Created: "+p("inferno/CHANGELOG.md"));const D=Y(),le=i.join(D,"scripts","inferno-doc-gate.mjs"),ce=i.join(o,"scripts","inferno-doc-gate.mjs");U(le,ce,t,y);const ae=i.join(D,"scripts","inferno-install-hooks.mjs"),pe=i.join(o,"scripts","inferno-install-hooks.mjs");U(ae,pe,t,y);const fe=i.join(D,"ci","github-inferno-check.yml"),de=i.join(W,"infernoflow-check.yml");if(U(fe,de,t,y),Ee(o,y),v&&be({cwd:o,templatesRoot:D,force:t,silent:y,logOk:f=>{y||k(f)},logWarn:f=>{y||T(f)}}),b&&Ie({cwd:o,templatesRoot:D,force:t,silent:y,logOk:f=>{y||k(f)},logWarn:f=>{y||T(f)}}),r){const f=i.join(h,"context-state.json");let c={};try{c=JSON.parse(n.readFileSync(f,"utf8"))}catch{}const w=M(o,{language:N||void 0,framework:R||void 0,projectType:_||void 0});c.stack=w.developmentProfile,n.writeFileSync(f,JSON.stringify(c,null,2)+`
54
+ `,"utf8"),y||k("Created: "+p("inferno/context-state.json"))}if(!y){X("infernoflow initialized!");const f=i.join(h,"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)&&!n.existsSync(f)&&(console.log(),console.log(` ${F("\u{1F4A1}")} ${J("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${p("infernoflow ai setup")} \u2014 takes 60 seconds`)),we([p("infernoflow status")+" \u2014 see your contract at a glance",p("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+F("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+F("inferno/scenarios/*.json")+" files for edge cases","Add "+p("inferno:check")+" to your CI pipeline",...v?["Restart Cursor \u2014 hooks write assistant text to "+F("inferno/CONTEXT.draft.md"),"Promote when ready: "+p("npm run inferno:promote-draft -- --append-notes")]:[],...b?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+F("inferno/CONTEXT.draft.md"),"Promote when ready: "+p("npm run inferno:promote-draft -- --append-notes")]:[],...!v&&!b?["Optional: "+p("infernoflow install-cursor-hooks")+" or "+p("infernoflow install-vscode-copilot-hooks")]:[]])}}export{no as ensureGitignoreEntries,to as initCommand};
@@ -1,9 +1,9 @@
1
- import*as c from"node:fs";import*as a from"node:path";import*as S from"node:os";import{fileURLToPath as D}from"node:url";import{execSync as F}from"node:child_process";import{detectIdeContext as J}from"../ai/ideDetection.mjs";import{header as A,ok as k,warn as O,info as w,done as N,cyan as u,yellow as j,bold as y,green as f,gray as C}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as H}from"../amp/io.mjs";import{injectionPatchFromArgs as I}from"./refresh.mjs";const T=a.dirname(D(import.meta.url));function v(){return a.resolve(T,"../../templates")}function U(r){try{return F(`npx infernoflow ${r}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(n){return n.stdout||n.stderr||n.message}}function m(r){if(!c.existsSync(r))return{data:{},existed:!1,corrupt:!1,backup:null};let n;try{n=c.readFileSync(r,"utf8")}catch{return{data:{},existed:!0,corrupt:!1,backup:null}}try{const i=JSON.parse(n);return!i||typeof i!="object"||Array.isArray(i)?{data:{},existed:!0,corrupt:!1,backup:null}:{data:i,existed:!0,corrupt:!1,backup:null}}catch{let i=null;try{i=`${r}.corrupt-${Date.now()}.bak`,c.writeFileSync(i,n,"utf8")}catch{i=null}return{data:{},existed:!0,corrupt:!0,backup:i}}}function L(){const r=S.homedir();return process.platform==="win32"?a.join(r,"AppData","Roaming","Claude","claude_desktop_config.json"):process.platform==="darwin"?a.join(r,"Library","Application Support","Claude","claude_desktop_config.json"):a.join(r,".config","Claude","claude_desktop_config.json")}function V(r){const n=L(),i=a.dirname(n);if(!c.existsSync(i)&&!c.existsSync(n))return{updated:!1,skipped:"not-installed"};const{data:t,backup:o}=m(n);t.mcpServers||(t.mcpServers={});const s=t.mcpServers.infernoflow;if(s&&Array.isArray(s.args)&&s.args[0]===r)return{updated:!1,backup:o};t.mcpServers.infernoflow={command:"node",args:[r]};const p=JSON.stringify(t,null,2);return c.mkdirSync(i,{recursive:!0}),c.writeFileSync(n,p,"utf8"),{updated:!0,path:n,backup:o}}function E(r){const n=a.join(r,".git");if(!c.existsSync(n))return{installed:!1,skipped:"not-a-git-repo"};const i=a.join(n,"hooks"),t=a.join(i,"post-commit"),o='infernoflow log "commit: $(git log -1 --pretty=%s)" --type note --source git-hook --auto --quiet >/dev/null 2>&1 || true';try{if(c.mkdirSync(i,{recursive:!0}),c.existsSync(t)){const s=c.readFileSync(t,"utf8");if(s.includes("infernoflow"))return{installed:!1,already:!0};const p=s.replace(/\s*$/,"")+`
1
+ import*as a from"node:fs";import*as u from"node:path";import*as v from"node:os";import{fileURLToPath as $}from"node:url";import{execSync as N}from"node:child_process";import{detectIdeContext as I}from"../ai/ideDetection.mjs";import{header as J,ok as h,warn as M,info as w,done as D,cyan as d,yellow as C,bold as k,green as m,gray as j}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as E}from"../amp/io.mjs";import{findProjectRoot as T}from"../projectRoot.mjs";import{injectionPatchFromArgs as A}from"./refresh.mjs";const L=u.dirname($(import.meta.url));function _(){return u.resolve(L,"../../templates")}function W(t){try{return N(`npx infernoflow ${t}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(i){return i.stdout||i.stderr||i.message}}function y(t){if(!a.existsSync(t))return{data:{},existed:!1,corrupt:!1,backup:null};let i;try{i=a.readFileSync(t,"utf8")}catch{return{data:{},existed:!0,corrupt:!1,backup:null}}try{const s=JSON.parse(i);return!s||typeof s!="object"||Array.isArray(s)?{data:{},existed:!0,corrupt:!1,backup:null}:{data:s,existed:!0,corrupt:!1,backup:null}}catch{let s=null;try{s=`${t}.corrupt-${Date.now()}.bak`,a.writeFileSync(s,i,"utf8")}catch{s=null}return{data:{},existed:!0,corrupt:!0,backup:s}}}function H(){const t=v.homedir();return process.platform==="win32"?u.join(t,"AppData","Roaming","Claude","claude_desktop_config.json"):process.platform==="darwin"?u.join(t,"Library","Application Support","Claude","claude_desktop_config.json"):u.join(t,".config","Claude","claude_desktop_config.json")}function U(t,i){const s=H(),n=u.dirname(s);if(!a.existsSync(n)&&!a.existsSync(s))return{updated:!1,skipped:"not-installed"};const{data:r,backup:e}=y(s);r.mcpServers||(r.mcpServers={});const l=r.mcpServers.infernoflow;if(l&&Array.isArray(l.args)&&l.args[0]===t&&l.env&&l.env.INFERNOFLOW_PROJECT_DIR===i)return{updated:!1,backup:e};const c={...l&&l.env?l.env:{}};i&&(c.INFERNOFLOW_PROJECT_DIR=i),r.mcpServers.infernoflow={command:"node",args:[t],env:c};const p=JSON.stringify(r,null,2);return a.mkdirSync(n,{recursive:!0}),a.writeFileSync(s,p,"utf8"),{updated:!0,path:s,backup:e}}function V(t){const i=u.join(t,".git");if(!a.existsSync(i))return{installed:!1,skipped:"not-a-git-repo"};const s=u.join(i,"hooks"),n=u.join(s,"post-commit"),r='infernoflow log "commit: $(git log -1 --pretty=%s)" --type note --source git-hook --auto --quiet >/dev/null 2>&1 || true';try{if(a.mkdirSync(s,{recursive:!0}),a.existsSync(n)){const e=a.readFileSync(n,"utf8");if(e.includes("infernoflow"))return{installed:!1,already:!0};const l=e.replace(/\s*$/,"")+`
2
2
 
3
3
  # infernoflow auto-capture
4
- `+o+`
5
- `;c.writeFileSync(t,p,"utf8")}else{const s=["#!/bin/sh","# infernoflow: auto-capture the commit subject into session memory.","# Best-effort and non-blocking \u2014 never fails a commit.",o,""].join(`
6
- `);c.writeFileSync(t,s,"utf8")}try{c.chmodSync(t,493)}catch{}return{installed:!0,path:t}}catch(s){return{installed:!1,error:s.message}}}const W=`#!/usr/bin/env node
4
+ `+r+`
5
+ `;a.writeFileSync(n,l,"utf8")}else{const e=["#!/bin/sh","# infernoflow: auto-capture the commit subject into session memory.","# Best-effort and non-blocking \u2014 never fails a commit.",r,""].join(`
6
+ `);a.writeFileSync(n,e,"utf8")}try{a.chmodSync(n,493)}catch{}return{installed:!0,path:n}}catch(e){return{installed:!1,error:e.message}}}const q=`#!/usr/bin/env node
7
7
  // infernoflow UserPromptSubmit hook (Claude Code / Cursor).
8
8
  // Logs a best-effort 'attempt' entry when the user's prompt shows frustration,
9
9
  // so the highest-value capture signal doesn't depend on the model remembering.
@@ -27,9 +27,9 @@ if (prompt && MARKERS.some((re) => re.test(prompt))) {
27
27
  } catch {}
28
28
  }
29
29
  process.exit(0);
30
- `;function q(r){const n=a.join(r,".claude","hooks"),i=a.join(n,"log-frustration.mjs");try{c.mkdirSync(n,{recursive:!0}),c.writeFileSync(i,W,"utf8");try{c.chmodSync(i,493)}catch{}}catch(l){return{installed:!1,error:l.message}}const t=a.join(r,".claude","settings.json"),{data:o}=m(t);(!o.hooks||typeof o.hooks!="object")&&(o.hooks={}),Array.isArray(o.hooks.UserPromptSubmit)||(o.hooks.UserPromptSubmit=[]);const s="node .claude/hooks/log-frustration.mjs",p=o.hooks.UserPromptSubmit.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(e=>typeof e?.command=="string"&&e.command.includes("log-frustration.mjs")));p||o.hooks.UserPromptSubmit.push({hooks:[{type:"command",command:s}]});try{c.mkdirSync(a.dirname(t),{recursive:!0}),c.writeFileSync(t,JSON.stringify(o,null,2),"utf8")}catch(l){return{installed:!0,registered:!1,error:l.message}}return{installed:!0,registered:!p}}const K=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function G(r){const n=a.join(S.homedir(),".claude.json"),{data:i,backup:t}=m(n);i.mcpServers||(i.mcpServers={});const o=i.mcpServers.infernoflow;if(o&&o.args&&o.args[0]===r)return{updated:!1,backup:t};i.mcpServers.infernoflow={command:"node",args:[r]};const s=JSON.stringify(i,null,2).replace(/\u0000+/g,"");return c.writeFileSync(n,s,"utf8"),{updated:!0,path:n,backup:t}}function B(r,n){const i=a.join(r,".vscode"),t=a.join(i,"mcp.json"),{data:o,backup:s}=m(t);o.servers||(o.servers={});const p=o.servers.infernoflow;return p&&p.args&&p.args[0]===n?{updated:!1,backup:s}:(o.servers.infernoflow={type:"stdio",command:"node",args:[n]},c.mkdirSync(i,{recursive:!0}),c.writeFileSync(t,JSON.stringify(o,null,2)+`
31
- `,"utf8"),{updated:!0,path:t,backup:s})}function z(r,n){const i=a.join(r,".claude"),t=a.join(i,"settings.json"),{data:o}=m(t),s=new Set(o.allowedTools||[]);for(const l of K)s.add(`mcp__infernoflow__${l}`);const p={...o,allowedTools:[...s]};return c.mkdirSync(i,{recursive:!0}),c.writeFileSync(t,JSON.stringify(p,null,2),"utf8"),t}function Q(r,{silent:n=!1}={}){const i=v(),t=n?()=>{}:e=>k(e),o=n?()=>{}:e=>O(e),s={mcpServer:!1,claudeJson:!1,claudeSettings:!1,claudeDesktop:!1,gitHooks:!1,captureHook:!1,backups:[]},p=a.join(i,"cursor","inferno-mcp-server.mjs"),l=a.join(r,".cursor","inferno-mcp-server.mjs");try{c.existsSync(l)||(c.mkdirSync(a.dirname(l),{recursive:!0}),c.copyFileSync(p,l),s.mcpServer=!0,t("Copied MCP server \u2192 "+u(".cursor/inferno-mcp-server.mjs")))}catch(e){o("MCP server copy skipped: "+e.message)}try{const e=G(l);e.updated&&(s.claudeJson=!0,t("Registered MCP server in "+u("~/.claude.json"))),e.backup&&(s.backups.push(e.backup),o("~/.claude.json was unreadable \u2014 backed up to "+e.backup+" before rewrite"))}catch(e){o("~/.claude.json update skipped: "+e.message)}try{B(r,l).updated&&(s.vscodeMcp=!0,t("Registered MCP server in "+u(".vscode/mcp.json")+C(" (Copilot Chat)")))}catch(e){o(".vscode/mcp.json update skipped: "+e.message)}try{const e=a.join(r,".cursor","mcp.json"),{data:d}=m(e);d.mcpServers||(d.mcpServers={});const g=d.mcpServers.infernoflow;(!g||!g.args||g.args[0]!==l)&&(d.mcpServers.infernoflow={command:"node",args:[l],env:{}},c.mkdirSync(a.dirname(e),{recursive:!0}),c.writeFileSync(e,JSON.stringify(d,null,2)+`
32
- `,"utf8"),s.cursorMcp=!0,t("Registered MCP server in "+u(".cursor/mcp.json")))}catch(e){o(".cursor/mcp.json update skipped: "+e.message)}try{z(r,!1),s.claudeSettings=!0,t("Pre-approved infernoflow tools in "+u(".claude/settings.json"))}catch(e){o(".claude/settings.json skipped: "+e.message)}try{const e=V(l);e.updated&&(s.claudeDesktop=!0,t("Registered MCP server in "+u("claude_desktop_config.json")+C(" (Claude Desktop app)"))),e.backup&&(s.backups.push(e.backup),o("Claude Desktop config was unreadable \u2014 backed up to "+e.backup))}catch(e){o("Claude Desktop config skipped: "+e.message)}try{E(r).installed&&(s.gitHooks=!0,t("Installed git post-commit hook \u2192 "+u(".git/hooks/post-commit")))}catch(e){o("git hook install skipped: "+e.message)}try{q(r).installed&&(s.captureHook=!0,t("Installed capture hook \u2192 "+u(".claude/hooks/log-frustration.mjs")))}catch(e){o("capture hook install skipped: "+e.message)}return s}async function ce(r){const n=process.cwd(),i=r.includes("--force")||r.includes("-f"),t=r.includes("--yes")||r.includes("-y"),o=v();A("infernoflow setup");const{ideDetected:s}=J("auto");w(`IDE detected: ${y(s==="cursor"?"Cursor":s==="vscode"?"VS Code":s==="windsurf"?"Windsurf":"unknown")}`);const l=a.join(n,".ai-memory");c.existsSync(l)?k(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
33
- ${j(".ai-memory/")} not found \u2014 running init ...
34
- `),U(t?"init --yes":"init"));const e=I(r);if(Object.keys(e).length)try{H(n,e),k("Injection config updated \u2192 "+JSON.stringify(e))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const d=Q(n,{silent:!1});console.log(),N("infernoflow ready"),console.log(`
35
- ${y("What was set up:")}`),console.log(` ${f("\u2714")} MCP server installed \u2192 ${u(".cursor/inferno-mcp-server.mjs")}`),d.cursorMcp&&console.log(` ${f("\u2714")} Cursor MCP config \u2192 ${u(".cursor/mcp.json")}`),d.vscodeMcp&&console.log(` ${f("\u2714")} VS Code Copilot MCP config \u2192 ${u(".vscode/mcp.json")}`),d.claudeJson&&console.log(` ${f("\u2714")} Claude Code MCP config \u2192 ${u("~/.claude.json")}`),d.claudeSettings&&console.log(` ${f("\u2714")} Auto-approved tools \u2192 ${u(".claude/settings.json")}`),d.claudeDesktop&&console.log(` ${f("\u2714")} Claude Desktop MCP config \u2192 ${u("claude_desktop_config.json")}`),d.gitHooks&&console.log(` ${f("\u2714")} Git post-commit hook \u2192 ${u(".git/hooks/post-commit")}`),d.captureHook&&console.log(` ${f("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${u(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:g}=await import("../mcpRuntime.mjs"),{readFileSync:b}=await import("node:fs"),{dirname:_,join:x}=await import("node:path"),{fileURLToPath:$}=await import("node:url"),P=_($(import.meta.url)),M=x(P,"..","..","package.json"),R=JSON.parse(b(M,"utf8")).version,h=g(n,R);h&&(console.log(),console.log(` ${j("\u26A0")} ${y("Restart required:")} ${h.message}`))}catch{}console.log(),console.log(` ${y("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${u('"call the amp_write tool with a test note"')}`),console.log()}export{K as MCP_TOOLS,Q as autoSetupMcp,L as claudeDesktopConfigPath,q as installClaudeCodeCaptureHook,E as installGitHooks,ce as setupCommand,V as updateClaudeDesktopConfig,G as updateClaudeJson,B as updateVscodeMcpJson,z as writeClaudeSettings};
30
+ `;function K(t){const i=u.join(t,".claude","hooks"),s=u.join(i,"log-frustration.mjs");try{a.mkdirSync(i,{recursive:!0}),a.writeFileSync(s,q,"utf8");try{a.chmodSync(s,493)}catch{}}catch(c){return{installed:!1,error:c.message}}const n=u.join(t,".claude","settings.json"),{data:r}=y(n);(!r.hooks||typeof r.hooks!="object")&&(r.hooks={}),Array.isArray(r.hooks.UserPromptSubmit)||(r.hooks.UserPromptSubmit=[]);const e="node .claude/hooks/log-frustration.mjs",l=r.hooks.UserPromptSubmit.some(c=>Array.isArray(c?.hooks)&&c.hooks.some(p=>typeof p?.command=="string"&&p.command.includes("log-frustration.mjs")));l||r.hooks.UserPromptSubmit.push({hooks:[{type:"command",command:e}]});try{a.mkdirSync(u.dirname(n),{recursive:!0}),a.writeFileSync(n,JSON.stringify(r,null,2),"utf8")}catch(c){return{installed:!0,registered:!1,error:c.message}}return{installed:!0,registered:!l}}const G=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function B(t,i){const s=u.join(v.homedir(),".claude.json"),{data:n,backup:r}=y(s);n.mcpServers||(n.mcpServers={});const e=n.mcpServers.infernoflow;if(e&&e.args&&e.args[0]===t&&e.env&&e.env.INFERNOFLOW_PROJECT_DIR===i)return{updated:!1,backup:r};const l={...e&&e.env?e.env:{}};i&&(l.INFERNOFLOW_PROJECT_DIR=i),n.mcpServers.infernoflow={command:"node",args:[t],env:l};const c=JSON.stringify(n,null,2).replace(/\u0000+/g,"");return a.writeFileSync(s,c,"utf8"),{updated:!0,path:s,backup:r}}function z(t,i,s){const n=u.join(t,".vscode"),r=u.join(n,"mcp.json"),{data:e,backup:l}=y(r);e.servers||(e.servers={});const c=e.servers.infernoflow;if(c&&c.args&&c.args[0]===i&&c.env&&c.env.INFERNOFLOW_PROJECT_DIR===s)return{updated:!1,backup:l};const p={...c&&c.env?c.env:{}};return s&&(p.INFERNOFLOW_PROJECT_DIR=s),e.servers.infernoflow={type:"stdio",command:"node",args:[i],env:p},a.mkdirSync(n,{recursive:!0}),a.writeFileSync(r,JSON.stringify(e,null,2)+`
31
+ `,"utf8"),{updated:!0,path:r,backup:l}}function Q(t,i){const s=u.join(t,".claude"),n=u.join(s,"settings.json"),{data:r}=y(n),e=new Set(r.allowedTools||[]);for(const c of G)e.add(`mcp__infernoflow__${c}`);const l={...r,allowedTools:[...e]};return a.mkdirSync(s,{recursive:!0}),a.writeFileSync(n,JSON.stringify(l,null,2),"utf8"),n}function X(t,{silent:i=!1}={}){const s=_(),n=i?()=>{}:o=>h(o),r=i?()=>{}:o=>M(o),e={mcpServer:!1,claudeJson:!1,claudeSettings:!1,claudeDesktop:!1,gitHooks:!1,captureHook:!1,backups:[]},l=u.join(s,"cursor","inferno-mcp-server.mjs"),c=u.join(t,".cursor","inferno-mcp-server.mjs"),p=(()=>{try{return T(t)}catch{return t}})();try{a.existsSync(c)||(a.mkdirSync(u.dirname(c),{recursive:!0}),a.copyFileSync(l,c),e.mcpServer=!0,n("Copied MCP server \u2192 "+d(".cursor/inferno-mcp-server.mjs")))}catch(o){r("MCP server copy skipped: "+o.message)}try{const o=B(c,p);o.updated&&(e.claudeJson=!0,n("Registered MCP server in "+d("~/.claude.json"))),o.backup&&(e.backups.push(o.backup),r("~/.claude.json was unreadable \u2014 backed up to "+o.backup+" before rewrite"))}catch(o){r("~/.claude.json update skipped: "+o.message)}try{z(t,c,p).updated&&(e.vscodeMcp=!0,n("Registered MCP server in "+d(".vscode/mcp.json")+j(" (Copilot Chat)")))}catch(o){r(".vscode/mcp.json update skipped: "+o.message)}try{const o=u.join(t,".cursor","mcp.json"),{data:g}=y(o);g.mcpServers||(g.mcpServers={});const f=g.mcpServers.infernoflow;(!f||!f.args||f.args[0]!==c||!f.env||f.env.INFERNOFLOW_PROJECT_DIR!==p)&&(g.mcpServers.infernoflow={command:"node",args:[c],env:{...f&&f.env?f.env:{},INFERNOFLOW_PROJECT_DIR:p}},a.mkdirSync(u.dirname(o),{recursive:!0}),a.writeFileSync(o,JSON.stringify(g,null,2)+`
32
+ `,"utf8"),e.cursorMcp=!0,n("Registered MCP server in "+d(".cursor/mcp.json")))}catch(o){r(".cursor/mcp.json update skipped: "+o.message)}try{Q(t,!1),e.claudeSettings=!0,n("Pre-approved infernoflow tools in "+d(".claude/settings.json"))}catch(o){r(".claude/settings.json skipped: "+o.message)}try{const o=U(c,p);o.updated&&(e.claudeDesktop=!0,n("Registered MCP server in "+d("claude_desktop_config.json")+j(" (Claude Desktop app)"))),o.backup&&(e.backups.push(o.backup),r("Claude Desktop config was unreadable \u2014 backed up to "+o.backup))}catch(o){r("Claude Desktop config skipped: "+o.message)}try{V(t).installed&&(e.gitHooks=!0,n("Installed git post-commit hook \u2192 "+d(".git/hooks/post-commit")))}catch(o){r("git hook install skipped: "+o.message)}try{K(t).installed&&(e.captureHook=!0,n("Installed capture hook \u2192 "+d(".claude/hooks/log-frustration.mjs")))}catch(o){r("capture hook install skipped: "+o.message)}return e}async function le(t){const i=process.cwd(),s=t.includes("--force")||t.includes("-f"),n=t.includes("--yes")||t.includes("-y"),r=_();J("infernoflow setup");const{ideDetected:e}=I("auto");w(`IDE detected: ${k(e==="cursor"?"Cursor":e==="vscode"?"VS Code":e==="windsurf"?"Windsurf":"unknown")}`);const c=u.join(i,".ai-memory");a.existsSync(c)?h(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
33
+ ${C(".ai-memory/")} not found \u2014 running init ...
34
+ `),W(n?"init --yes":"init"));const p=A(t);if(Object.keys(p).length)try{E(i,p),h("Injection config updated \u2192 "+JSON.stringify(p))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const o=X(i,{silent:!1});console.log(),D("infernoflow ready"),console.log(`
35
+ ${k("What was set up:")}`),console.log(` ${m("\u2714")} MCP server installed \u2192 ${d(".cursor/inferno-mcp-server.mjs")}`),o.cursorMcp&&console.log(` ${m("\u2714")} Cursor MCP config \u2192 ${d(".cursor/mcp.json")}`),o.vscodeMcp&&console.log(` ${m("\u2714")} VS Code Copilot MCP config \u2192 ${d(".vscode/mcp.json")}`),o.claudeJson&&console.log(` ${m("\u2714")} Claude Code MCP config \u2192 ${d("~/.claude.json")}`),o.claudeSettings&&console.log(` ${m("\u2714")} Auto-approved tools \u2192 ${d(".claude/settings.json")}`),o.claudeDesktop&&console.log(` ${m("\u2714")} Claude Desktop MCP config \u2192 ${d("claude_desktop_config.json")}`),o.gitHooks&&console.log(` ${m("\u2714")} Git post-commit hook \u2192 ${d(".git/hooks/post-commit")}`),o.captureHook&&console.log(` ${m("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${d(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:g}=await import("../mcpRuntime.mjs"),{readFileSync:f}=await import("node:fs"),{dirname:b,join:R}=await import("node:path"),{fileURLToPath:O}=await import("node:url"),P=b(O(import.meta.url)),x=R(P,"..","..","package.json"),F=JSON.parse(f(x,"utf8")).version,S=g(i,F);S&&(console.log(),console.log(` ${C("\u26A0")} ${k("Restart required:")} ${S.message}`))}catch{}console.log(),console.log(` ${k("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${d('"call the amp_write tool with a test note"')}`),console.log()}export{G as MCP_TOOLS,X as autoSetupMcp,H as claudeDesktopConfigPath,K as installClaudeCodeCaptureHook,V as installGitHooks,le as setupCommand,U as updateClaudeDesktopConfig,B as updateClaudeJson,z as updateVscodeMcpJson,Q as writeClaudeSettings};
@@ -114,6 +114,7 @@ const INFERNOFLOW_BIN = resolveInfernoflowBin();
114
114
  let ampIo = null;
115
115
  let refreshRuleFiles = null;
116
116
  let harvestSnapshot = null;
117
+ let findProjectRoot = null;
117
118
  if (INFERNOFLOW_ROOT) {
118
119
  try {
119
120
  for (const c of [
@@ -134,9 +135,37 @@ if (INFERNOFLOW_ROOT) {
134
135
  ]) {
135
136
  if (fs.existsSync(c)) { harvestSnapshot = (await import(pathToFileURL(c).href)).harvestSnapshot; break; }
136
137
  }
138
+ for (const c of [
139
+ path.join(INFERNOFLOW_ROOT, "lib", "projectRoot.mjs"),
140
+ path.join(INFERNOFLOW_ROOT, "dist", "lib", "projectRoot.mjs"),
141
+ ]) {
142
+ if (fs.existsSync(c)) { findProjectRoot = (await import(pathToFileURL(c).href)).findProjectRoot; break; }
143
+ }
137
144
  } catch { /* swallow — fallback path handles it */ }
138
145
  }
139
146
 
147
+ // ── Project directory resolution ───────────────────────────────────────────
148
+ // NEVER trust process.cwd() to locate .ai-memory. IDEs and the desktop bridge
149
+ // routinely launch this server from the wrong place — a monorepo parent, or
150
+ // C:\WINDOWS\system32 on Windows — so every amp_* tool looked for .ai-memory
151
+ // relative to that wrong cwd and failed with "not initialized". Resolve the
152
+ // real project root once, in priority order:
153
+ // 1. INFERNOFLOW_PROJECT_DIR — explicit override (init can bake this into the MCP config)
154
+ // 2. WORKSPACE_FOLDER_PATHS[0] — the folder the IDE actually opened (Cursor / VS Code set this)
155
+ // 3. findProjectRoot(cwd) — walk up to .ai-memory / .git / a manifest (subfolder case)
156
+ // 4. process.cwd() — last resort
157
+ function resolveProjectDir() {
158
+ const hint = process.env.INFERNOFLOW_PROJECT_DIR
159
+ || (process.env.WORKSPACE_FOLDER_PATHS || "").split(path.delimiter).filter(Boolean)[0];
160
+ const start = (hint && fs.existsSync(hint)) ? hint : process.cwd();
161
+ if (typeof findProjectRoot === "function") {
162
+ try { return findProjectRoot(start); } catch { /* fall through to start */ }
163
+ }
164
+ return start;
165
+ }
166
+ const PROJECT_DIR = resolveProjectDir();
167
+ process.stderr.write(`[infernoflow MCP] project dir: ${PROJECT_DIR}\n`);
168
+
140
169
  // ── Clean-tree policy: regenerate rule files ONCE at boot ──────────────────
141
170
  // Historically, every amp_write rewrote CLAUDE.md / .cursorrules. That
142
171
  // dirtied tracked files dozens of times per session and blocked git
@@ -145,7 +174,7 @@ if (INFERNOFLOW_ROOT) {
145
174
  // the session. amp_read serves runtime queries; rule-file content is
146
175
  // for cold-start injection only.
147
176
  if (refreshRuleFiles) {
148
- try { refreshRuleFiles(process.cwd()); } catch { /* non-fatal */ }
177
+ try { refreshRuleFiles(PROJECT_DIR); } catch { /* non-fatal */ }
149
178
  }
150
179
 
151
180
  // ── Boot stamp: record which MCP version is running ──────────────────────
@@ -163,7 +192,7 @@ try {
163
192
  try { runtimeVersion = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version || runtimeVersion; }
164
193
  catch {}
165
194
  }
166
- const memDir = path.join(process.cwd(), ".ai-memory");
195
+ const memDir = path.join(PROJECT_DIR, ".ai-memory");
167
196
  if (fs.existsSync(memDir)) {
168
197
  fs.writeFileSync(path.join(memDir, ".mcp-runtime.json"), JSON.stringify({
169
198
  version: runtimeVersion,
@@ -200,7 +229,7 @@ function runCmd(args, env = {}) {
200
229
  : `"${INFERNOFLOW_BIN}" ${args}`;
201
230
  return execSync(cmd, {
202
231
  encoding: "utf8",
203
- cwd: process.cwd(),
232
+ cwd: PROJECT_DIR,
204
233
  timeout: 30000,
205
234
  env: { ...process.env, ...env },
206
235
  });
@@ -231,7 +260,7 @@ function isCmdError(result) {
231
260
  const TOOLS = [
232
261
  // ── AMP-spec memory tools (the product) ──────────────────────────────────
233
262
  { name: "amp_read", description: "AMP: read session memory entries with optional filters.", inputSchema: { type: "object", properties: { file: { type: "string" }, type: { type: "string", enum: ["gotcha","decision","attempt","note","detection","pattern"] }, query: { type: "string" }, limit: { type: "number" } } } },
234
- { name: "amp_write", description: "AMP: log a new entry. Required: type + msg (one sentence). Optional: file, line, tags, detail. Use 'detail' for a rich multi-paragraph body (repro steps, code, full reasoning, or a session snapshot) — it's stored in a sidecar and loaded on demand, so it never bloats the always-on memory index.", inputSchema: { type: "object", properties: { type: { type: "string", enum: ["gotcha","decision","attempt","note","detection","pattern"] }, msg: { type: "string" }, file: { type: "string" }, line: { type: "number" }, tags: { type: "array", items: { type: "string" } }, detail: { type: "string", description: "Optional rich body (Tier-2). Stored in details/<id>.md; NOT injected into rule files. Put the long-form context here; keep 'msg' to one summary sentence." } }, required: ["type","msg"] } },
263
+ { name: "amp_write", description: "AMP: log a new entry. Required: type + msg (one sentence). Optional: file, line, tags, detail. Use 'detail' for a rich multi-paragraph body (repro steps, code, full reasoning, or a session snapshot) — it's stored in a sidecar and loaded on demand, so it never bloats the always-on memory index.", inputSchema: { type: "object", properties: { type: { type: "string", enum: ["gotcha","decision","attempt","note","detection","pattern"] }, msg: { type: "string" }, file: { type: "string" }, line: { type: "number" }, tags: { type: "array", items: { type: "string" } }, detail: { type: "string", description: "Optional rich body (Tier-2). Stored in the consolidated details store; NOT injected into rule files. Put the long-form context here; keep 'msg' to one summary sentence." } }, required: ["type","msg"] } },
235
264
  { name: "amp_search", description: "AMP: search entries by keyword. Optional type filter.", inputSchema: { type: "object", properties: { query: { type: "string" }, type: { type: "string", enum: ["gotcha","decision","attempt","note","detection","pattern"] } }, required: ["query"] } },
236
265
  { name: "amp_bookmark", description: "AMP: drop a named session bookmark — a resume point. Required: label (short name). Optional: note. If note is OMITTED, the current session transcript is auto-captured as the bookmark's context (the 'save everything here' resume point). Use when the user says 'bookmark this' / 'mark this point', or before a risky change / when the context window is filling up, so the exact state can be recalled later and appears in the next session's handoff. Bookmarks are never auto-pruned.", inputSchema: { type: "object", properties: { label: { type: "string" }, note: { type: "string", description: "Optional explicit context. Omit to auto-capture the session transcript instead. Stored in a sidecar; not injected into rule files." } }, required: ["label"] } },
237
266
  { name: "amp_handoff", description: "AMP: generate the handoff document for the next AI session. format=markdown|json (default: markdown).", inputSchema: { type: "object", properties: { format: { type: "string", enum: ["markdown","json"] } } } },
@@ -246,7 +275,7 @@ const TOOLS = [
246
275
 
247
276
  // ── git drift detection (inline — no external imports in this template file) ─
248
277
  function detectGitDrift(sinceCommits) {
249
- const cwd = process.cwd();
278
+ const cwd = PROJECT_DIR;
250
279
  const infernoDir = path.join(cwd, "inferno");
251
280
 
252
281
  const runGit = (cmd) => {
@@ -402,7 +431,7 @@ function handleTool(id, name, input) {
402
431
  if (input.tags && input.tags.length) entry.tags = input.tags;
403
432
  if (input.detail && String(input.detail).trim()) entry.detail = String(input.detail);
404
433
  try {
405
- const written = ampIo.appendEntry(process.cwd(), entry);
434
+ const written = ampIo.appendEntry(PROJECT_DIR, entry);
406
435
  // NOTE: rule-file refresh deliberately NOT called here — clean-tree
407
436
  // policy regenerates them once at MCP boot only. Doing it on every
408
437
  // write dirties tracked files and blocks `git checkout`. Within a
@@ -446,10 +475,10 @@ function handleTool(id, name, input) {
446
475
  if (input.note && String(input.note).trim()) {
447
476
  entry.detail = String(input.note);
448
477
  } else if (harvestSnapshot) {
449
- try { const snap = harvestSnapshot(process.cwd()); if (snap) entry.detail = snap; } catch { /* best-effort */ }
478
+ try { const snap = harvestSnapshot(PROJECT_DIR); if (snap) entry.detail = snap; } catch { /* best-effort */ }
450
479
  }
451
480
  try {
452
- const written = ampIo.appendEntry(process.cwd(), entry);
481
+ const written = ampIo.appendEntry(PROJECT_DIR, entry);
453
482
  text = `🔖 Bookmark saved: ${written.msg} (${written.id})` +
454
483
  (written.meta && written.meta.detailRef ? `\n context: ${written.meta.detailRef}` : "");
455
484
  } catch (err) {
@@ -467,8 +496,8 @@ function handleTool(id, name, input) {
467
496
  return sendError(id, -32000, `infernoflow switch failed: ${switchResult.message}\n${switchResult.stderr || switchResult.stdout || ""}`.trim());
468
497
  }
469
498
  try {
470
- const ampPath = path.join(process.cwd(), ".ai-memory", "handoff.md");
471
- const legacyPath = path.join(process.cwd(), "inferno", "HANDOFF.md");
499
+ const ampPath = path.join(PROJECT_DIR, ".ai-memory", "handoff.md");
500
+ const legacyPath = path.join(PROJECT_DIR, "inferno", "HANDOFF.md");
472
501
  const target = fs.existsSync(ampPath) ? ampPath : legacyPath;
473
502
  text = fs.readFileSync(target, "utf8");
474
503
  if (input.format === "json") {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.44.17",
4
- "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
3
+ "version": "0.44.19",
4
+ "description": "Persistent memory for AI coding sessions — captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "infernoflow": "dist/bin/infernoflow.mjs"