infernoflow 0.43.11 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/bin/infernoflow.mjs +30 -33
  2. package/dist/lib/amp/io.mjs +8 -8
  3. package/dist/lib/cleanTree.mjs +12 -0
  4. package/dist/lib/commands/ai.mjs +2 -2
  5. package/dist/lib/commands/amp.mjs +4 -4
  6. package/dist/lib/commands/ask.mjs +2 -2
  7. package/dist/lib/commands/context.mjs +18 -18
  8. package/dist/lib/commands/doctor.mjs +2 -3
  9. package/dist/lib/commands/init.mjs +31 -32
  10. package/dist/lib/commands/log.mjs +13 -19
  11. package/dist/lib/commands/recap.mjs +3 -3
  12. package/dist/lib/commands/refresh.mjs +5 -0
  13. package/dist/lib/commands/status.mjs +6 -7
  14. package/dist/lib/commands/switch.mjs +5 -5
  15. package/dist/lib/commands/sync.mjs +41 -0
  16. package/dist/lib/git/branch.mjs +2 -0
  17. package/dist/lib/projectRoot.mjs +1 -0
  18. package/dist/lib/ruleFiles.mjs +9 -5
  19. package/dist/lib/upgradeCheck.mjs +1 -1
  20. package/dist/templates/cursor/inferno-mcp-server.mjs +170 -325
  21. package/package.json +13 -5
  22. package/dist/lib/commands/changelog.mjs +0 -21
  23. package/dist/lib/commands/ci.mjs +0 -3
  24. package/dist/lib/commands/claudeMd.mjs +0 -116
  25. package/dist/lib/commands/coverage.mjs +0 -2
  26. package/dist/lib/commands/demo.mjs +0 -113
  27. package/dist/lib/commands/diff.mjs +0 -5
  28. package/dist/lib/commands/explain.mjs +0 -8
  29. package/dist/lib/commands/feedback.mjs +0 -12
  30. package/dist/lib/commands/graph.mjs +0 -76
  31. package/dist/lib/commands/impact.mjs +0 -2
  32. package/dist/lib/commands/implement.mjs +0 -7
  33. package/dist/lib/commands/monorepo.mjs +0 -4
  34. package/dist/lib/commands/notify.mjs +0 -4
  35. package/dist/lib/commands/prImpact.mjs +0 -2
  36. package/dist/lib/commands/publish.mjs +0 -21
  37. package/dist/lib/commands/review.mjs +0 -24
  38. package/dist/lib/commands/run.mjs +0 -10
  39. package/dist/lib/commands/scaffold.mjs +0 -124
  40. package/dist/lib/commands/scan.mjs +0 -42
  41. package/dist/lib/commands/stability.mjs +0 -2
  42. package/dist/lib/commands/stats.mjs +0 -4
  43. package/dist/lib/commands/suggest.mjs +0 -62
  44. package/dist/lib/commands/syncAuto.mjs +0 -1
  45. package/dist/lib/commands/test.mjs +0 -6
  46. package/dist/lib/commands/theme.mjs +0 -18
  47. package/dist/lib/commands/upgrade.mjs +0 -20
  48. package/dist/lib/commands/watch.mjs +0 -7
  49. package/dist/lib/commands/why.mjs +0 -4
@@ -1,11 +1,11 @@
1
- import*as i from"node:fs";import*as s from"node:path";import*as G from"node:readline";import{fileURLToPath as pe}from"node:url";import{header as fe,ok as g,warn as P,done as q,nextSteps as de,bold as L,cyan as f,yellow as $,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ue,writeAdoptionBaseline as me,buildAdoptionReport as ye,summarizeCapabilities as ge,buildSignalsReport as he}from"./adopt.mjs";import{installCursorHooksArtifacts as we}from"../cursorHooksInstall.mjs";import{ensureAmpDir as ke,appendEntry as je,writeDefaultConfig as Se}from"../amp/io.mjs";import{installVsCodeCopilotHooksArtifacts as ve}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as z}from"../ruleFiles.mjs";import{autoSetupMcp as V}from"./setup.mjs";const be=s.dirname(pe(import.meta.url));function Ce(){return s.resolve(be,"../../templates")}function D(e,o,t=""){return new Promise(n=>{const r=t?v(` (${t})`):"";e.question(` ${o}${r}: `,c=>{n(c.trim()||t)})})}function W(e,...o){for(const t of o){const n=e.indexOf(t);if(n!==-1&&e[n+1]&&!e[n+1].startsWith("-"))return e[n+1]}return null}function J(e,o,t,n=!1){return i.existsSync(o)&&!t?(n||P("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),n||g("Created: "+f(s.relative(process.cwd(),o))),!0)}function Oe(e,o,t){i.mkdirSync(o,{recursive:!0});for(const n of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,n.name),c=s.join(o,n.name);n.isDirectory()?Oe(r,c,t):J(r,c,t)}}function Ie(e,o=!1){const t=s.join(e,"package.json");if(!i.existsSync(t))return;const n=JSON.parse(i.readFileSync(t,"utf8"));n.scripts=n.scripts||{};let r=!1;const c={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[m,k]of Object.entries(c))n.scripts[m]||(n.scripts[m]=k,r=!0);r&&(i.writeFileSync(t,JSON.stringify(n,null,2)+`
2
- `,"utf8"),o||g("Updated "+f("package.json")+" scripts"))}const X="# --- infernoflow (developer-local AI memory; do not commit) ---",xe="# --- /infernoflow ---",B=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function Z(e,{silent:o=!1}={}){const t=s.join(e,".gitignore");let n="";if(i.existsSync(t)&&(n=i.readFileSync(t,"utf8")),n.includes(X))return!1;const r=["",X,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...B,xe,""].join(`
3
- `),c=n.length===0||n.endsWith(`
1
+ import*as i from"node:fs";import*as s from"node:path";import*as G from"node:readline";import{fileURLToPath as de}from"node:url";import{header as ue,ok as g,warn as E,info as q,done as z,nextSteps as me,bold as L,cyan as f,yellow as $,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ye,writeAdoptionBaseline as ge,buildAdoptionReport as we,summarizeCapabilities as he,buildSignalsReport as ke}from"./adopt.mjs";import{installCursorHooksArtifacts as je}from"../cursorHooksInstall.mjs";import{ensureAmpDir as Se,appendEntry as V,writeDefaultConfig as ve}from"../amp/io.mjs";import{applyCleanTreePolicy as X}from"../cleanTree.mjs";import{installVsCodeCopilotHooksArtifacts as be}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as B}from"../ruleFiles.mjs";import{autoSetupMcp as Z}from"./setup.mjs";const Ce=s.dirname(de(import.meta.url));function Oe(){return s.resolve(Ce,"../../templates")}function D(e,o,n=""){return new Promise(t=>{const r=n?v(` (${n})`):"";e.question(` ${o}${r}: `,c=>{t(c.trim()||n)})})}function W(e,...o){for(const n of o){const t=e.indexOf(n);if(t!==-1&&e[t+1]&&!e[t+1].startsWith("-"))return e[t+1]}return null}function J(e,o,n,t=!1){return i.existsSync(o)&&!n?(t||E("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),t||g("Created: "+f(s.relative(process.cwd(),o))),!0)}function Ie(e,o,n){i.mkdirSync(o,{recursive:!0});for(const t of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,t.name),c=s.join(o,t.name);t.isDirectory()?Ie(r,c,n):J(r,c,n)}}function xe(e,o=!1){const n=s.join(e,"package.json");if(!i.existsSync(n))return;const t=JSON.parse(i.readFileSync(n,"utf8"));t.scripts=t.scripts||{};let r=!1;const c={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[m,j]of Object.entries(c))t.scripts[m]||(t.scripts[m]=j,r=!0);r&&(i.writeFileSync(n,JSON.stringify(t,null,2)+`
2
+ `,"utf8"),o||g("Updated "+f("package.json")+" scripts"))}const Q="# --- infernoflow (developer-local AI memory; do not commit) ---",Ae="# --- /infernoflow ---",ee=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function Ze(e,{silent:o=!1}={}){const n=s.join(e,".gitignore");let t="";if(i.existsSync(n)&&(t=i.readFileSync(n,"utf8")),t.includes(Q))return!1;const r=["",Q,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...ee,Ae,""].join(`
3
+ `),c=t.length===0||t.endsWith(`
4
4
  `)?"":`
5
- `;if(i.writeFileSync(t,n+c+r,"utf8"),!o){g("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const m of B)console.log(v(" + ")+f(m));console.log(v(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(v(" To commit them instead: delete the `")+f("# --- infernoflow ---")+v("` block in .gitignore."))}return!0}function M(e){const o=s.join(e,"package.json");if(i.existsSync(o))try{const t=JSON.parse(i.readFileSync(o,"utf8"));if(t.name)return t.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(e)}function Ae(e,o,t){const n={policyId:o,policyVersion:1,capabilities:t,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(n,null,2)+`
6
- `)}function Te(e,o){const t={schemaVersion:1,capabilities:o.map(n=>({id:n,title:n.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(t,null,2)+`
7
- `)}function Ee(e,o){i.mkdirSync(e,{recursive:!0});const t={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(n=>({action:n,expect:`${n} works as expected`}))};i.writeFileSync(s.join(e,"happy_path.json"),JSON.stringify(t,null,2)+`
8
- `)}function Q(e,o){const t=`# Changelog \u2014 ${o}
5
+ `;if(i.writeFileSync(n,t+c+r,"utf8"),!o){g("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const m of ee)console.log(v(" + ")+f(m));console.log(v(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(v(" To commit them instead: delete the `")+f("# --- infernoflow ---")+v("` block in .gitignore."))}return!0}function M(e){const o=s.join(e,"package.json");if(i.existsSync(o))try{const n=JSON.parse(i.readFileSync(o,"utf8"));if(n.name)return n.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(e)}function Te(e,o,n){const t={policyId:o,policyVersion:1,capabilities:n,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(t,null,2)+`
6
+ `)}function Pe(e,o){const n={schemaVersion:1,capabilities:o.map(t=>({id:t,title:t.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(n,null,2)+`
7
+ `)}function Ee(e,o){i.mkdirSync(e,{recursive:!0});const n={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(t=>({action:t,expect:`${t} works as expected`}))};i.writeFileSync(s.join(e,"happy_path.json"),JSON.stringify(n,null,2)+`
8
+ `)}function oe(e,o){const n=`# Changelog \u2014 ${o}
9
9
 
10
10
  ## Unreleased
11
11
 
@@ -14,42 +14,41 @@ import*as i from"node:fs";import*as s from"node:path";import*as G from"node:read
14
14
  ## 0.1.0 \u2014 Initial release
15
15
 
16
16
  - Project initialized with infernoflow
17
- `;i.writeFileSync(e,t)}async function Pe(e,o){const{bold:t,cyan:n,gray:r,green:c,yellow:m,red:k}=await import("../ui/output.mjs");console.log(`
18
- `+t("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
17
+ `;i.writeFileSync(e,n)}async function Ne(e,o){const{bold:n,cyan:t,gray:r,green:c,yellow:m,red:j}=await import("../ui/output.mjs");console.log(`
18
+ `+n("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
19
19
  `),console.log(r(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(r(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const p=s.join(e,"inferno");i.existsSync(p)&&!o&&(console.log(m(` \u26A0 inferno/ already exists. Use --force to overwrite.
20
- `)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const h=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const O=G.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(C=>{O.question(r(" What does this project do? (one line, Enter to skip): "),I=>{O.close(),C(I.trim())})})}const A={policyId:h,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(A,null,2)+`
20
+ `)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const w=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const O=G.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(C=>{O.question(r(" What does this project do? (one line, Enter to skip): "),I=>{O.close(),C(I.trim())})})}const A={policyId:w,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(A,null,2)+`
21
21
  `),i.writeFileSync(s.join(p,"capabilities.json"),JSON.stringify([],null,2)+`
22
- `),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(c(" \u2714 Created inferno/contract.json")),console.log(c(" \u2714 Created inferno/capabilities.json")),console.log(c(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+n("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+n("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+n("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+n("infernoflow upgrade")+r(" \u2014 expand to full setup when you need it")),console.log()}const Ne=/^(?:node|npm|npx|yarn|pnpm|bun|git|cd|mkdir|rm|ls|cat|echo|type|dir|copy|del|move|python|python3|pip|go|cargo|java|gradle|mvn|docker|kubectl|curl|wget|ssh|scp|chmod|chown|sudo|brew|apt|yum)\b/i,Fe=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Re=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function _e(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:Ne.test(o)?{kind:"command",value:o}:Fe.test(o)?{kind:"command",value:o}:Re.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function $e({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:t,cyan:n}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
23
- > `),c=m=>new Promise(k=>{const p=G.createInterface({input:process.stdin,output:process.stdout});let h=!1;p.on("SIGINT",()=>{h=!0,p.close(),k(null)}),p.on("close",()=>{h&&k(null)}),p.question(m,S=>{p.close(),k(S)})});for(let m=0;m<2;m++){const k=await c(m===0?r:" "+o("> "));if(k===null)return console.log(),"";const p=_e(k);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+t("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+n('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+t("\u26A0")+" Multi-line paste detected \u2014 log a single gotcha at a time."),console.log(" "+o(" Try one short sentence:"));continue}if(p.kind==="tooShort"){console.log(" "+t("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function De(e,o,t){const{bold:n,cyan:r,gray:c,green:m,yellow:k}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),h=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(h))&&!o){const j=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
24
- `+n("\u{1F525} infernoflow")+c(` \u2014 already set up
25
- `)),console.log(" "+m("\u2714")+" "+j+` found
26
- `),Z(e),z(e);try{V(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
22
+ `),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(c(" \u2714 Created inferno/contract.json")),console.log(c(" \u2714 Created inferno/capabilities.json")),console.log(c(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+n("Ready. Start using it:")),console.log(" "+t("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+t("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+t("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+t("infernoflow upgrade")+r(" \u2014 expand to full setup when you need it")),console.log()}const Fe=/^(?:node|npm|npx|yarn|pnpm|bun|git|cd|mkdir|rm|ls|cat|echo|type|dir|copy|del|move|python|python3|pip|go|cargo|java|gradle|mvn|docker|kubectl|curl|wget|ssh|scp|chmod|chown|sudo|brew|apt|yum)\b/i,Re=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,_e=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function $e(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:Fe.test(o)?{kind:"command",value:o}:Re.test(o)?{kind:"command",value:o}:_e.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function De({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:n,cyan:t}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
23
+ > `),c=m=>new Promise(j=>{const p=G.createInterface({input:process.stdin,output:process.stdout});let w=!1;p.on("SIGINT",()=>{w=!0,p.close(),j(null)}),p.on("close",()=>{w&&j(null)}),p.question(m,S=>{p.close(),j(S)})});for(let m=0;m<2;m++){const j=await c(m===0?r:" "+o("> "));if(j===null)return console.log(),"";const p=$e(j);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+n("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+t('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+n("\u26A0")+" Multi-line paste detected \u2014 log a single gotcha at a time."),console.log(" "+o(" Try one short sentence:"));continue}if(p.kind==="tooShort"){console.log(" "+n("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function Ge(e,o,n){const{bold:t,cyan:r,gray:c,green:m,yellow:j}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),w=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(w))&&!o){const h=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
24
+ `+t("\u{1F525} infernoflow")+c(` \u2014 already set up
25
+ `)),console.log(" "+m("\u2714")+" "+h+` found
26
+ `),X(e),B(e);try{Z(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
27
27
  `)),i.existsSync(p)||console.log(c(" Tip: run ")+r("infernoflow amp migrate")+c(` to move legacy memory into .ai-memory/
28
28
  `)),console.log(c(` For contracts & CI gates: infernoflow init --mode full
29
29
  `));return}const O=M(e);console.log(`
30
- `+n("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
30
+ `+t("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
31
31
  `)),console.log(" Detected: "+r(O)+`
32
- `),ke(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),Se(e,{project:O,config:{autoCapture:!0}}),Z(e);const C=z(e);for(const j of C)(j.created||j.updated)&&g((j.created?"Created: ":"Updated: ")+r(j.rel));try{V(e)}catch(j){P("MCP auto-setup skipped: "+j.message)}const I=await $e({yes:t});I&&(je(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:I,source:"init"}),console.log(`
33
- `+m("\u2714")+" First gotcha logged!")),console.log(`
32
+ `),Se(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),ve(e,{project:O,config:{autoCapture:!0}}),X(e);const C=B(e);for(const h of C)"created"in h&&(h.created||h.updated)&&g((h.created?"Created: ":"Updated: ")+r(h.rel));try{Z(e)}catch(h){E("MCP auto-setup skipped: "+h.message)}const I=await De({yes:n});I?(V(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:I,source:"init"}),console.log(`
33
+ `+m("\u2714")+" First gotcha logged!")):n&&V(e,{ts:new Date().toISOString(),agent:"infernoflow",type:"note",summary:"infernoflow init complete \u2014 memory loop is live. Run `infernoflow status` to verify.",source:"init"}),console.log(`
34
34
  `+m("\u2714")+` You're set up. Quick commands:
35
- `),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
35
+ `),console.log(" "+r("infernoflow status")+c(" \u2014 verify the loop end-to-end")),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
36
36
  `)),console.log(c(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
37
- `)),console.log(c(` Want contracts & CI gates? Run: infernoflow init --mode full
38
- `))}function Ge(){console.log(["infernoflow init \u2014 set up persistent AI memory in this project","","Usage:"," infernoflow init Memory-first setup (default, 60 sec)"," infernoflow init --lite Minimal: 3 files, no scripts, no workflows"," infernoflow init --mode full Contracts + CI gates (advanced)"," infernoflow init --adopt Detect existing capabilities from code"," infernoflow init --template <name> Start from a project template","","Options:"," -y, --yes Non-interactive; accept all defaults"," -f, --force Overwrite existing inferno/ or .ai-memory/"," --cursor-hooks Also install Cursor IDE hooks (advanced)"," --vscode-copilot-hooks Also install VS Code Copilot hooks (advanced)"," --lang <lang> Override language detection (with --adopt)"," --framework <name> Override framework detection (with --adopt)"," --project-type <type> Override project-type detection (with --adopt)"," -h, --help Show this help","","Default flow writes .ai-memory/ + AI rule files + MCP server config, then asks","for one first gotcha. Press Enter to skip \u2014 never blocks."].join(`
39
- `))}async function Xe(e){if(e.includes("--help")||e.includes("-h")){Ge();return}const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),n=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),c=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),m=c==="full"||c==="contract",k=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!m&&!k){await De(o,t,n);return}if(e.includes("--lite")){await Pe(o,t);return}const p=e.indexOf("--template"),h=p!==-1?e[p+1]:null;if(h){let a;try{a=await import("../templates/index.mjs")}catch{}const l=a?.getTemplate(h);if(!l){const d=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";P(`Unknown template: ${h}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),E=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(E)||i.mkdirSync(E,{recursive:!0});const _=M(o),w=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:w.map(d=>d.id)},null,2)+`
40
- `),i.writeFileSync(s.join(y,"capabilities.json"),JSON.stringify({capabilities:w.map(d=>({id:d.id,description:d.description,since:new Date().toISOString().slice(0,10),source:`template:${h}`}))},null,2)+`
41
- `);for(const d of w)i.writeFileSync(s.join(E,`${d.id}.json`),JSON.stringify({id:`${d.id}-happy-path`,capability:d.id,description:`Happy path for ${d.description||d.id}`,steps:[{action:"invoke",target:d.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[d.id]},null,2)+`
42
- `);Q(s.join(y,"CHANGELOG.md"),_),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
37
+ `))}function Le(){console.log(["infernoflow init \u2014 set up persistent AI memory in this project","","Usage:"," infernoflow init Memory-first setup (default, 60 sec)"," infernoflow init --lite Minimal: 3 files, no scripts, no workflows"," infernoflow init --mode full Contracts + CI gates (advanced)"," infernoflow init --adopt Detect existing capabilities from code"," infernoflow init --template <name> Start from a project template","","Options:"," -y, --yes Non-interactive; accept all defaults"," -f, --force Overwrite existing inferno/ or .ai-memory/"," --cursor-hooks Also install Cursor IDE hooks (advanced)"," --vscode-copilot-hooks Also install VS Code Copilot hooks (advanced)"," --lang <lang> Override language detection (with --adopt)"," --framework <name> Override framework detection (with --adopt)"," --project-type <type> Override project-type detection (with --adopt)"," -h, --help Show this help","","Default flow writes .ai-memory/ + AI rule files + MCP server config, then asks","for one first gotcha. Press Enter to skip \u2014 never blocks."].join(`
38
+ `))}async function Qe(e){if(e.includes("--help")||e.includes("-h")){Le();return}const o=process.cwd(),n=e.includes("--force")||e.includes("-f"),t=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),c=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),m=c==="full"||c==="contract",j=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!m&&!j){await Ge(o,n,t);return}if(e.includes("--lite")){await Ne(o,n);return}const p=e.indexOf("--template"),w=p!==-1?e[p+1]:null;if(w){let a;try{a=await import("../templates/index.mjs")}catch{}const l=a?.getTemplate(w);if(!l){const d=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";E(`Unknown template: ${w}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),P=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(P)||i.mkdirSync(P,{recursive:!0});const _=M(o),k=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:k.map(d=>d.id)},null,2)+`
39
+ `),i.writeFileSync(s.join(y,"capabilities.json"),JSON.stringify({capabilities:k.map(d=>({id:d.id,description:d.description,since:new Date().toISOString().slice(0,10),source:`template:${w}`}))},null,2)+`
40
+ `);for(const d of k)i.writeFileSync(s.join(P,`${d.id}.json`),JSON.stringify({id:`${d.id}-happy-path`,capability:d.id,description:`Happy path for ${d.description||d.id}`,steps:[{action:"invoke",target:d.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[d.id]},null,2)+`
41
+ `);oe(s.join(y,"CHANGELOG.md"),_),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
43
42
 
44
- > Template: ${h} \u2014 ${l.description}
43
+ > Template: ${w} \u2014 ${l.description}
45
44
 
46
45
  ## Hint
47
46
  ${l.contextHint}
48
47
 
49
- ## Capabilities (${w.length})
50
- ${w.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
48
+ ## Capabilities (${k.length})
49
+ ${k.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
51
50
  `)}
52
- `),l.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,x])=>console.log(` ${L(d)}: ${v(x)}`)),console.log()),q(`Initialised from template ${L(f(h))} \u2014 ${L(String(w.length))} capabilities`),console.log(),info(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),A=e.includes("--vscode-copilot-hooks"),O=e.includes("--report-json"),C=e.includes("--report-json-only"),I=e.includes("--report-human-only"),j=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=C;C&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||fe("init");const b=s.join(o,"inferno"),ee=s.join(o,".github","workflows");i.existsSync(b)&&!t&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),P("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const N=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=N,T=Y.split(",").map(a=>a.trim());if(r){let l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});if(!n&&!C){const w=G.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},x=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
53
- `));const le=await D(w,"Language",d.language||x.language||"unknown"),ce=await D(w,"Framework",d.framework||x.framework||"unknown"),ae=await D(w,"Project type",d.projectType||x.projectType||"unknown");w.close(),l=H(o,{language:le,framework:ce,projectType:ae})}const y=l.capabilities,E=ge(y);C?console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)):(console.log(),console.log(v(ye(y))),console.log(),console.log(v(he(l))),console.log(),O&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)),console.log()));const _=await ue(y,n);F=N,T=_.map(w=>w.id)}else if(!n){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
54
- `)),F=await D(a,"Project / policy name",N),T=(await D(a,"Capabilities (comma-separated)",Y)).split(",").map(y=>y.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(b,{recursive:!0}),r){const a=T.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});me(b,F,a,l),u||(g("Created: "+f("inferno/contract.json")),g("Created: "+f("inferno/capabilities.json")),g("Created: "+f("inferno/scenarios/adoption_baseline.json")),g("Created: "+f("inferno/adoption_profile.json")),g("Created: "+f("inferno/CHANGELOG.md")))}else Ae(s.join(b,"contract.json"),F,T),u||g("Created: "+f("inferno/contract.json")),Te(s.join(b,"capabilities.json"),T),u||g("Created: "+f("inferno/capabilities.json")),Ee(s.join(b,"scenarios"),T),u||g("Created: "+f("inferno/scenarios/happy_path.json")),Q(s.join(b,"CHANGELOG.md"),F),u||g("Created: "+f("inferno/CHANGELOG.md"));const R=Ce(),oe=s.join(R,"scripts","inferno-doc-gate.mjs"),ne=s.join(o,"scripts","inferno-doc-gate.mjs");J(oe,ne,t,u);const te=s.join(R,"scripts","inferno-install-hooks.mjs"),ie=s.join(o,"scripts","inferno-install-hooks.mjs");J(te,ie,t,u);const se=s.join(R,"ci","github-inferno-check.yml"),re=s.join(ee,"infernoflow-check.yml");if(J(se,re,t,u),Ie(o,u),S&&we({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),A&&ve({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});l.stack=y.developmentProfile,i.writeFileSync(a,JSON.stringify(l,null,2)+`
55
- `,"utf8"),u||g("Created: "+f("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(b,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${$("\u{1F4A1}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),de([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+$("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+$("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...A?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!A?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Z as ensureGitignoreEntries,Xe as initCommand};
51
+ `),l.scripts&&(q("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,x])=>console.log(` ${L(d)}: ${v(x)}`)),console.log()),z(`Initialised from template ${L(f(w))} \u2014 ${L(String(k.length))} capabilities`),console.log(),q(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),A=e.includes("--vscode-copilot-hooks"),O=e.includes("--report-json"),C=e.includes("--report-json-only"),I=e.includes("--report-human-only"),h=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=C;C&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||ue("init");const b=s.join(o,"inferno"),ne=s.join(o,".github","workflows");i.existsSync(b)&&!n&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),E("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const N=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=N,T=Y.split(",").map(a=>a.trim());if(r){let l=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});if(!t&&!C){const k=G.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},x=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
52
+ `));const ae=await D(k,"Language",d.language||x.language||"unknown"),pe=await D(k,"Framework",d.framework||x.framework||"unknown"),fe=await D(k,"Project type",d.projectType||x.projectType||"unknown");k.close(),l=H(o,{language:ae,framework:pe,projectType:fe})}const y=l.capabilities,P=he(y);C?console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:P,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)):(console.log(),console.log(v(we(y))),console.log(),console.log(v(ke(l))),console.log(),O&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:P,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)),console.log()));const _=await ye(y,t);F=N,T=_.map(k=>k.id)}else if(!t){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
53
+ `)),F=await D(a,"Project / policy name",N),T=(await D(a,"Capabilities (comma-separated)",Y)).split(",").map(y=>y.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(b,{recursive:!0}),r){const a=T.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});ge(b,F,a,l),u||(g("Created: "+f("inferno/contract.json")),g("Created: "+f("inferno/capabilities.json")),g("Created: "+f("inferno/scenarios/adoption_baseline.json")),g("Created: "+f("inferno/adoption_profile.json")),g("Created: "+f("inferno/CHANGELOG.md")))}else Te(s.join(b,"contract.json"),F,T),u||g("Created: "+f("inferno/contract.json")),Pe(s.join(b,"capabilities.json"),T),u||g("Created: "+f("inferno/capabilities.json")),Ee(s.join(b,"scenarios"),T),u||g("Created: "+f("inferno/scenarios/happy_path.json")),oe(s.join(b,"CHANGELOG.md"),F),u||g("Created: "+f("inferno/CHANGELOG.md"));const R=Oe(),te=s.join(R,"scripts","inferno-doc-gate.mjs"),ie=s.join(o,"scripts","inferno-doc-gate.mjs");J(te,ie,n,u);const se=s.join(R,"scripts","inferno-install-hooks.mjs"),re=s.join(o,"scripts","inferno-install-hooks.mjs");J(se,re,n,u);const le=s.join(R,"ci","github-inferno-check.yml"),ce=s.join(ne,"infernoflow-check.yml");if(J(le,ce,n,u),xe(o,u),S&&je({cwd:o,templatesRoot:R,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||E(a)}}),A&&be({cwd:o,templatesRoot:R,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||E(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=H(o,{language:h||void 0,framework:U||void 0,projectType:K||void 0});l.stack=y.developmentProfile,i.writeFileSync(a,JSON.stringify(l,null,2)+`
54
+ `,"utf8"),u||g("Created: "+f("inferno/context-state.json"))}if(!u){z("infernoflow initialized!");const a=s.join(b,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${$("\u{1F4A1}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),me([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+$("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+$("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...A?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!A?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Ze as ensureGitignoreEntries,Qe as initCommand};
@@ -1,20 +1,14 @@
1
- import*as f from"node:fs";import*as m from"node:path";import"node:os";import{bold as j,cyan as A,gray as o,green as D,red as b}from"../ui/output.mjs";import{ampPaths as R,appendEntry as k,readEntries as N,AMP_MARKERS as E}from"../amp/io.mjs";function _(){return R(process.cwd())}function C(){try{const e=N(process.cwd()),l=e.filter(n=>n.type==="gotcha"),i=e.filter(n=>n.type==="decision"),s=e.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial")),t=["# Project Context (auto-generated by infernoflow)","",`> Last updated: ${new Date().toISOString()}`,""];if(l.length){t.push("## \u26A0\uFE0F Known Gotchas (Read These First)","");for(const n of l)t.push(`- ${n.summary}`);t.push("")}if(i.length){t.push("## \u2713 Decisions In Effect","");for(const n of i)t.push(`- ${n.summary}`);t.push("")}if(s.length){t.push("## \u274C Things That Don't Work (Don't Try These)","");for(const n of s)t.push(`- ${n.summary}`);t.push("")}const p=t.join(`
2
- `),g=`${E.start}
3
- ${p}
4
- ${E.end}
5
- `,a=process.cwd(),d=n=>{let c="";try{c=f.readFileSync(n,"utf8")}catch{}const w=c.indexOf(E.start),S=c.indexOf(E.end);let $;w!==-1&&S!==-1&&S>w?$=c.slice(0,w)+g+c.slice(S+E.end.length).replace(/^\n+/,""):c?$=c.replace(/\s+$/,"")+`
6
-
7
- `+g:$=g,f.writeFileSync(n,$,"utf8")},O=m.join(a,"CLAUDE.md");f.existsSync(O)&&d(O);const I=m.join(a,".cursorrules");(f.existsSync(I)||f.existsSync(m.join(a,".cursor")))&&d(I);const y=m.join(a,".github","copilot-instructions.md");f.existsSync(m.join(a,".github"))&&d(y)}catch{}}const F=["note","attempt","decision","gotcha","preference","theme","handoff","error"],L=["worked","failed","partial","unknown"];function P(){return N(process.cwd())}function U(e,{auto:l=!1,quiet:i=!1}={}){const s=process.cwd(),t=m.join(s,".ai-memory"),p=m.join(s,"inferno");return l&&!f.existsSync(t)&&!f.existsSync(p)?!1:(!f.existsSync(t)&&!f.existsSync(p)&&(i||console.error(b(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
8
- `)),process.exit(1)),k(s,e),!0)}function W(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function V(e,l){const i=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=e.type||"note",t=s==="gotcha"?"\x1B[33m":s==="decision"?"\x1B[36m":s==="theme"?"\x1B[35m":s==="preference"?"\x1B[34m":s==="attempt"?"\x1B[90m":s==="error"?"\x1B[31m":"\x1B[0m",p="\x1B[0m",g=e.result?` [${e.result}]`:"",a=e.agent&&e.agent!=="human"?o(` (${e.agent})`):"";return` ${o(String(l+1).padStart(3))} ${o(i)} ${t}${s}${p}${g} ${e.summary}${a}`}async function J(e){const l=r=>e.includes(r),i=(r,u)=>{const h=e.indexOf(r);return h!==-1&&e[h+1]?e[h+1]:u},s=l("--show"),t=l("--clear"),p=l("--json"),g=l("--auto"),a=l("--quiet"),d=i("--source",null);if(s||p){const r=P(),u=e[e.indexOf("--show")+1],h=u&&/^\d+$/.test(u)?parseInt(u):20,x=r.slice(-h);if(p){console.log(JSON.stringify(x,null,2));return}if(console.log(`
9
- `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!x.length){console.log(o(`
1
+ import*as g from"node:fs";import*as $ from"node:path";import"node:os";import{bold as N,cyan as k,gray as t,green as b,red as x}from"../ui/output.mjs";import{ampPaths as T,appendEntry as P,readEntries as C}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as v}from"../ruleFiles.mjs";function U(){return T(process.cwd())}const A=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],F=["worked","failed","partial","unknown"];function V(){return C(process.cwd())}function q(e,{auto:l=!1,quiet:o=!1}={}){const s=process.cwd(),f=$.join(s,".ai-memory"),i=$.join(s,"inferno");if(l&&!g.existsSync(f)&&!g.existsSync(i))return!1;!g.existsSync(f)&&!g.existsSync(i)&&(o||console.error(x(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
+ `)),process.exit(1)),P(s,e);try{v(s)}catch{}return!0}function W(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function B(e,l){const o=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=e.type||"note",f=s==="gotcha"?"\x1B[33m":s==="decision"?"\x1B[36m":s==="theme"?"\x1B[35m":s==="preference"?"\x1B[34m":s==="attempt"?"\x1B[90m":s==="error"?"\x1B[31m":"\x1B[0m",i="\x1B[0m",d=e.result?` [${e.result}]`:"",u=e.agent&&e.agent!=="human"?t(` (${e.agent})`):"";return` ${t(String(l+1).padStart(3))} ${t(o)} ${f}${s}${i}${d} ${e.summary}${u}`}async function K(e){const l=n=>e.includes(n),o=(n,r)=>{const a=e.indexOf(n);return a!==-1&&e[a+1]?e[a+1]:r},s=l("--show"),f=l("--clear"),i=l("--json"),d=l("--auto"),u=l("--quiet"),h=o("--source",null);if(s||i){const n=V(),r=e[e.indexOf("--show")+1],a=r&&/^\d+$/.test(r)?parseInt(r):20,m=n.slice(-a);if(i){console.log(JSON.stringify(m,null,2));return}if(console.log(`
3
+ `+N("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!m.length){console.log(t(`
10
4
  No entries yet. Start logging with: infernoflow log "<what happened>"
11
- `));return}console.log(o(` Showing last ${x.length} of ${r.length} entries
12
- `)),x.forEach((v,T)=>console.log(V(v,r.length-x.length+T))),console.log();return}if(t){const{sessions:r}=_();if(!f.existsSync(r)){console.log(o(` Nothing to clear.
13
- `));return}const u=r.replace(".jsonl",`-archive-${Date.now()}.jsonl`);f.renameSync(r,u),console.log(D(` \u2714 Session log archived \u2192 ${m.basename(u)}
14
- `));return}const O=new Set([i("--type",""),i("--result",""),i("--agent",""),i("--source","")].filter(Boolean)),y=e.slice(1).filter(r=>!r.startsWith("--")&&!O.has(r)).join(" ").trim();if(!y){console.log(`
15
- `+j("\u{1F525} infernoflow log")+` \u2014 append to session memory
16
- `),console.log(o(" Usage:")),console.log(o(' infernoflow log "what happened"')),console.log(o(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(o(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(o(' infernoflow log "switched to dark mode" --type theme')),console.log(o(" infernoflow log --show Print last 20 entries")),console.log(o(" infernoflow log --json Print as JSON")),console.log(),console.log(o(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(o(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(o(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
17
- `));return}const n=i("--type","note"),c=i("--result",null),w=i("--agent",W());F.includes(n)||(a||console.error(b(` \u2718 Invalid type: ${n}. Valid: ${F.join(", ")}
18
- `)),process.exit(1)),c&&!L.includes(c)&&(a||console.error(b(` \u2718 Invalid result: ${c}. Valid: ${L.join(", ")}
19
- `)),process.exit(1));const S={ts:new Date().toISOString(),agent:w,type:n,summary:y,...c?{result:c}:{},...d?{source:d}:{},...g?{auto:!0}:{}};if(U(S,{auto:g,quiet:a})&&(C(),!a)){const r=n!=="note"?A(` [${n}]`):"",u=c?o(` \u2192 ${c}`):"",h=d?o(` (via ${d})`):"";console.log(D(` \u2714 Logged${r}${u}${h}: `)+y+`
20
- `)}}export{J as logCommand};
5
+ `));return}console.log(t(` Showing last ${m.length} of ${n.length} entries
6
+ `)),m.forEach((R,_)=>console.log(B(R,n.length-m.length+_))),console.log();return}if(f){const{sessions:n}=U();if(!g.existsSync(n)){console.log(t(` Nothing to clear.
7
+ `));return}const r=n.replace(".jsonl",`-archive-${Date.now()}.jsonl`);g.renameSync(n,r),console.log(b(` \u2714 Session log archived \u2192 ${$.basename(r)}
8
+ `));return}const L=new Set([o("--type",""),o("--result",""),o("--agent",""),o("--source",""),o("--file",""),o("--line",""),o("--tags","")].filter(Boolean)),y=e.slice(1).filter(n=>!n.startsWith("--")&&!L.has(n)).join(" ").trim();if(!y){console.log(`
9
+ `+N("\u{1F525} infernoflow log")+` \u2014 append to session memory
10
+ `),console.log(t(" Usage:")),console.log(t(' infernoflow log "what happened"')),console.log(t(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(t(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(t(' infernoflow log "switched to dark mode" --type theme')),console.log(t(" infernoflow log --show Print last 20 entries")),console.log(t(" infernoflow log --json Print as JSON")),console.log(),console.log(t(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(t(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(t(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
11
+ `));return}const p=o("--type","note"),c=o("--result",null),j=o("--agent",W()),E=o("--file",null),w=o("--line",null),O=o("--tags",null);A.includes(p)||(u||console.error(x(` \u2718 Invalid type: ${p}. Valid: ${A.join(", ")}
12
+ `)),process.exit(1)),c&&!F.includes(c)&&(u||console.error(x(` \u2718 Invalid result: ${c}. Valid: ${F.join(", ")}
13
+ `)),process.exit(1));const I=w&&/^\d+$/.test(w)?parseInt(w,10):null,S=O?O.split(",").map(n=>n.trim()).filter(Boolean):null,D={ts:new Date().toISOString(),agent:j,type:p,summary:y,...c?{result:c}:{},...h?{source:h}:{},...E?{file:E}:{},...I?{line:I}:{},...S&&S.length?{tags:S}:{},...d?{auto:!0}:{}};if(q(D,{auto:d,quiet:u})){if(e.includes("--refresh-rules"))try{v(process.cwd())}catch{}if(!u){const n=p!=="note"?k(` [${p}]`):"",r=c?t(` \u2192 ${c}`):"",a=h?t(` (via ${h})`):"";console.log(b(` \u2714 Logged${n}${r}${a}: `)+y+`
14
+ `)}}}export{K as logCommand};
@@ -1,5 +1,5 @@
1
- import*as C from"node:fs";import*as D from"node:path";import{execSync as M}from"node:child_process";import{bold as b,cyan as u,gray as n,green as k,yellow as $,red as I}from"../ui/output.mjs";import{ampPaths as L,readEntries as P}from"../amp/io.mjs";const E="inferno";function Q(){return L(process.cwd()).sessions}const R=D.join(E,"contract.json");function v(e){try{return JSON.parse(C.readFileSync(e,"utf8"))}catch{return null}}function A(e,s){if(s){const o=s.match(/^(\d+)h$/i),t=s.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(t)return new Date(Date.now()-parseInt(t[1])*864e5);const c=new Date(s);if(!isNaN(c))return c}for(let o=e.length-1;o>=0;o--)if(e[o].type==="handoff"){const t=new Date(e[o].ts||0),c=new Date(Date.now()-864e5);return t>c?t:c}return new Date(Date.now()-864e5)}function B(e){const s=process.cwd(),o=p=>{try{return M(p,{cwd:s,encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}},t=e.toISOString().slice(0,19),c=o("git diff --cached --name-only"),m=o("git diff --name-only"),r=o(`git log --since="${t}" --name-only --pretty=format:""`);return[...new Set([...c.split(`
1
+ import*as C from"node:fs";import*as D from"node:path";import{execSync as M}from"node:child_process";import{bold as b,cyan as u,gray as n,green as k,yellow as $,red as I}from"../ui/output.mjs";import{readEntries as L}from"../amp/io.mjs";const x="inferno",R=D.join(x,"contract.json");function v(e){try{return JSON.parse(C.readFileSync(e,"utf8"))}catch{return null}}function P(e,s){if(s){const o=s.match(/^(\d+)h$/i),t=s.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(t)return new Date(Date.now()-parseInt(t[1])*864e5);const c=new Date(s);if(!isNaN(c.getTime()))return c}for(let o=e.length-1;o>=0;o--)if(e[o].type==="handoff"){const t=new Date(e[o].ts||0),c=new Date(Date.now()-864e5);return t>c?t:c}return new Date(Date.now()-864e5)}function A(e){const s=process.cwd(),o=p=>{try{return M(p,{cwd:s,encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}},t=e.toISOString().slice(0,19),c=o("git diff --cached --name-only"),m=o("git diff --name-only"),r=o(`git log --since="${t}" --name-only --pretty=format:""`);return[...new Set([...c.split(`
2
2
  `),...m.split(`
3
3
  `),...r.split(`
4
- `)].map(p=>p.trim()).filter(Boolean))]}function _(e,s){const o=[{keywords:["auth","login","logout","session","jwt","token","password"],topic:"authentication"},{keywords:["stripe","payment","checkout","billing","subscription"],topic:"payments"},{keywords:["upload","file","s3","storage","bucket","cdn"],topic:"file handling"},{keywords:["email","sendgrid","ses","smtp","nodemailer","twilio"],topic:"notifications"},{keywords:["db","database","prisma","mongoose","postgres","mysql","migration"],topic:"database"},{keywords:["deploy","docker","ci","workflow","action","kubernetes"],topic:"deployment"},{keywords:["cache","redis","memcache"],topic:"caching"},{keywords:["test","spec","jest","vitest","cypress","playwright"],topic:"testing"},{keywords:["config","env",".env","environment","secret"],topic:"configuration"},{keywords:["api","route","endpoint","controller","handler"],topic:"API routes"},{keywords:["ui","component","style","css","tailwind","theme"],topic:"UI/styles"}],t=s.map(r=>(r.summary||"").toLowerCase()).join(" "),c=[],m=new Set;for(const r of o){if(m.has(r.topic))continue;const y=e.filter(g=>r.keywords.some(w=>g.toLowerCase().includes(w)));!y.length||r.keywords.some(g=>t.includes(g))||(m.add(r.topic),c.push({topic:r.topic,files:y.slice(0,3),suggestedType:"gotcha"}))}return c}function J(e){const s=new Set(e.map(c=>c.type));let o=0;const t=[];return e.length>0?(o+=20,t.push({ok:!0,label:`${e.length} entr${e.length!==1?"ies":"y"} logged`})):t.push({ok:!1,label:"nothing logged this session"}),s.has("gotcha")?(o+=35,t.push({ok:!0,label:"gotchas captured"})):t.push({ok:!1,label:"no gotchas (most valuable \u2014 log landmines!)"}),s.has("decision")?(o+=25,t.push({ok:!0,label:"decisions recorded"})):t.push({ok:!1,label:"no decisions recorded"}),s.has("attempt")&&(o+=10,t.push({ok:!0,label:"attempts tracked"})),s.has("preference")&&(o+=10,t.push({ok:!0,label:"preferences noted"})),{score:Math.min(o,100),checks:t}}function U(e){if(!e)return"";const s=new Date(e),o=Date.now()-s.getTime(),t=Math.floor(o/6e4);if(t<60)return`${t}m ago`;const c=Math.floor(o/36e5);return c<24?`${c}h ago`:`${Math.floor(o/864e5)}d ago`}const G={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u25C8",note:"\xB7",error:"\u2717",handoff:"\u2192"},H={gotcha:$,decision:k,attempt:u,preference:u,theme:u,note:n,error:I,handoff:n};function Y(e){const s=H[e.type]||n,o=G[e.type]||"\xB7",t=e.result?n(` [${e.result}]`):"",c=n(` (${U(e.ts)})`);console.log(` ${s(o+" "+(e.type||"note").padEnd(11))}${t}${c}`),console.log(` ${e.summary}`)}async function V(e=[]){const s=e,o=s.includes("--json"),t=s.includes("--brief"),c=s.indexOf("--since"),m=c!==-1?s[c+1]:null,r=process.cwd();!C.existsSync(D.join(r,E))&&!C.existsSync(D.join(r,".ai-memory"))&&(o||console.error(I(` \u2718 not initialized \u2014 run: infernoflow init
5
- `)),process.exit(1));const y=P(r),p=A(y,m),g=y.filter(a=>new Date(a.ts||0)>p),w=B(p),h=_(w,g),{score:l,checks:O}=J(g),j=v(D.join(r,R));if(o){console.log(JSON.stringify({sessionStart:p.toISOString(),entries:g,changedFiles:w,unloggedTopics:h,health:{score:l,checks:O}},null,2));return}if(t){const a=l>=80?"A":l>=60?"B":l>=40?"C":"D",f=l>=60?k:l>=40?$:I;console.log(f(`Session health: ${a} (${l}/100)`)+n(` \u2014 ${g.length} entries logged`)),h.length&&console.log($(` ${h.length} topic${h.length!==1?"s":""} changed but not logged: `)+h.map(i=>i.topic).join(", "));return}const S=n(" "+"\u2500".repeat(52));console.log(),console.log(" "+b("\u{1F525} infernoflow recap")),j?.policyId&&console.log(n(` Project: ${j.policyId}`));const T=p.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(n(` Session since: ${T}`)),console.log(S),console.log(),console.log(" "+b("Captured this session")),console.log(),g.length===0)console.log(n(" Nothing logged yet this session."));else{const a=["gotcha","decision","attempt","preference","theme","note","error"],f=new Map;for(const i of g){const d=i.type||"note";f.has(d)||f.set(d,[]),f.get(d).push(i)}for(const i of a){const d=f.get(i);if(d?.length)for(const F of d)console.log(),Y(F)}}if(h.length>0){console.log(),console.log(S),console.log(),console.log(" "+b("Changed but not logged")+n(" (git diff since session start)")),console.log();for(const{topic:a,files:f}of h){console.log($(` ? ${a}`));for(const i of f)console.log(n(` ${i}`))}console.log(),console.log(n(" Any gotchas or decisions from these areas worth capturing?")),console.log(n(" Run: ")+u('infernoflow log "<what happened>" --type gotcha'))}else w.length>0&&(console.log(),console.log(S),console.log(),console.log(k(" \u2714 ")+n(`${w.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log(S),console.log(),console.log(" "+b("Session health")),console.log();const N=l>=80?"A":l>=60?"B":l>=40?"C":"D",x=l>=60?k:l>=40?$:I;console.log(` ${x(b(`${N}`))} ${x(`${l}/100`)}`),console.log();for(const{ok:a,label:f}of O){const i=a?k(" \u2714"):$(" \xB7");console.log(`${i} ${a?f:n(f)}`)}{const a=g.filter(d=>d.type==="gotcha").length,f=g.filter(d=>d.type==="decision").length,i=[];if(a===0?i.push(u('infernoflow log "..." --type gotcha')+n(" \u2014 adds 35 pts")):a<3&&l<80&&i.push(n(` ${3-a} more gotcha(s) would push you higher`)),f===0&&i.push(u('infernoflow log "..." --type decision')+n(" \u2014 adds 25 pts")),l>=60&&l<80&&i.push(n(" Almost B! One more entry gets you there.")),l>=80&&i.push(k(" Great session \u2014 your handoff will be excellent.")),i.length){console.log(),console.log(n(" How to improve:"));for(const d of i)console.log(" "+d)}}(g.length>0||h.length>0)&&(console.log(),console.log(S),console.log(),console.log(n(" Before your next session:")),console.log(n(" ")+u("infernoflow switch")+n(" \u2014 generate a handoff summary for the next AI agent")),console.log(n(" ")+u("infernoflow ask --recent")+n(" \u2014 review what's in memory before starting"))),console.log()}export{V as recapCommand};
4
+ `)].map(p=>p.trim()).filter(Boolean))]}function B(e,s){const o=[{keywords:["auth","login","logout","session","jwt","token","password"],topic:"authentication"},{keywords:["stripe","payment","checkout","billing","subscription"],topic:"payments"},{keywords:["upload","file","s3","storage","bucket","cdn"],topic:"file handling"},{keywords:["email","sendgrid","ses","smtp","nodemailer","twilio"],topic:"notifications"},{keywords:["db","database","prisma","mongoose","postgres","mysql","migration"],topic:"database"},{keywords:["deploy","docker","ci","workflow","action","kubernetes"],topic:"deployment"},{keywords:["cache","redis","memcache"],topic:"caching"},{keywords:["test","spec","jest","vitest","cypress","playwright"],topic:"testing"},{keywords:["config","env",".env","environment","secret"],topic:"configuration"},{keywords:["api","route","endpoint","controller","handler"],topic:"API routes"},{keywords:["ui","component","style","css","tailwind","theme"],topic:"UI/styles"}],t=s.map(r=>(r.summary||"").toLowerCase()).join(" "),c=[],m=new Set;for(const r of o){if(m.has(r.topic))continue;const y=e.filter(g=>r.keywords.some(w=>g.toLowerCase().includes(w)));!y.length||r.keywords.some(g=>t.includes(g))||(m.add(r.topic),c.push({topic:r.topic,files:y.slice(0,3),suggestedType:"gotcha"}))}return c}function _(e){const s=new Set(e.map(c=>c.type));let o=0;const t=[];return e.length>0?(o+=20,t.push({ok:!0,label:`${e.length} entr${e.length!==1?"ies":"y"} logged`})):t.push({ok:!1,label:"nothing logged this session"}),s.has("gotcha")?(o+=35,t.push({ok:!0,label:"gotchas captured"})):t.push({ok:!1,label:"no gotchas (most valuable \u2014 log landmines!)"}),s.has("decision")?(o+=25,t.push({ok:!0,label:"decisions recorded"})):t.push({ok:!1,label:"no decisions recorded"}),s.has("attempt")&&(o+=10,t.push({ok:!0,label:"attempts tracked"})),s.has("preference")&&(o+=10,t.push({ok:!0,label:"preferences noted"})),{score:Math.min(o,100),checks:t}}function J(e){if(!e)return"";const s=new Date(e),o=Date.now()-s.getTime(),t=Math.floor(o/6e4);if(t<60)return`${t}m ago`;const c=Math.floor(o/36e5);return c<24?`${c}h ago`:`${Math.floor(o/864e5)}d ago`}const U={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u25C8",note:"\xB7",error:"\u2717",handoff:"\u2192"},G={gotcha:$,decision:k,attempt:u,preference:u,theme:u,note:n,error:I,handoff:n};function H(e){const s=G[e.type]||n,o=U[e.type]||"\xB7",t=e.result?n(` [${e.result}]`):"",c=n(` (${J(e.ts)})`);console.log(` ${s(o+" "+(e.type||"note").padEnd(11))}${t}${c}`),console.log(` ${e.summary}`)}async function Q(e=[]){const s=e,o=s.includes("--json"),t=s.includes("--brief"),c=s.indexOf("--since"),m=c!==-1?s[c+1]:null,r=process.cwd();!C.existsSync(D.join(r,x))&&!C.existsSync(D.join(r,".ai-memory"))&&(o||console.error(I(` \u2718 not initialized \u2014 run: infernoflow init
5
+ `)),process.exit(1));const y=L(r),p=P(y,m),g=y.filter(a=>new Date(a.ts||0)>p),w=A(p),h=B(w,g),{score:l,checks:O}=_(g),j=v(D.join(r,R));if(o){console.log(JSON.stringify({sessionStart:p.toISOString(),entries:g,changedFiles:w,unloggedTopics:h,health:{score:l,checks:O}},null,2));return}if(t){const a=l>=80?"A":l>=60?"B":l>=40?"C":"D",f=l>=60?k:l>=40?$:I;console.log(f(`Session health: ${a} (${l}/100)`)+n(` \u2014 ${g.length} entries logged`)),h.length&&console.log($(` ${h.length} topic${h.length!==1?"s":""} changed but not logged: `)+h.map(i=>i.topic).join(", "));return}const S=n(" "+"\u2500".repeat(52));console.log(),console.log(" "+b("\u{1F525} infernoflow recap")),j?.policyId&&console.log(n(` Project: ${j.policyId}`));const E=p.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(n(` Session since: ${E}`)),console.log(S),console.log(),console.log(" "+b("Captured this session")),console.log(),g.length===0)console.log(n(" Nothing logged yet this session."));else{const a=["gotcha","decision","attempt","preference","theme","note","error"],f=new Map;for(const i of g){const d=i.type||"note";f.has(d)||f.set(d,[]),f.get(d).push(i)}for(const i of a){const d=f.get(i);if(d?.length)for(const F of d)console.log(),H(F)}}if(h.length>0){console.log(),console.log(S),console.log(),console.log(" "+b("Changed but not logged")+n(" (git diff since session start)")),console.log();for(const{topic:a,files:f}of h){console.log($(` ? ${a}`));for(const i of f)console.log(n(` ${i}`))}console.log(),console.log(n(" Any gotchas or decisions from these areas worth capturing?")),console.log(n(" Run: ")+u('infernoflow log "<what happened>" --type gotcha'))}else w.length>0&&(console.log(),console.log(S),console.log(),console.log(k(" \u2714 ")+n(`${w.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log(S),console.log(),console.log(" "+b("Session health")),console.log();const N=l>=80?"A":l>=60?"B":l>=40?"C":"D",T=l>=60?k:l>=40?$:I;console.log(` ${T(b(`${N}`))} ${T(`${l}/100`)}`),console.log();for(const{ok:a,label:f}of O){const i=a?k(" \u2714"):$(" \xB7");console.log(`${i} ${a?f:n(f)}`)}{const a=g.filter(d=>d.type==="gotcha").length,f=g.filter(d=>d.type==="decision").length,i=[];if(a===0?i.push(u('infernoflow log "..." --type gotcha')+n(" \u2014 adds 35 pts")):a<3&&l<80&&i.push(n(` ${3-a} more gotcha(s) would push you higher`)),f===0&&i.push(u('infernoflow log "..." --type decision')+n(" \u2014 adds 25 pts")),l>=60&&l<80&&i.push(n(" Almost B! One more entry gets you there.")),l>=80&&i.push(k(" Great session \u2014 your handoff will be excellent.")),i.length){console.log(),console.log(n(" How to improve:"));for(const d of i)console.log(" "+d)}}(g.length>0||h.length>0)&&(console.log(),console.log(S),console.log(),console.log(n(" Before your next session:")),console.log(n(" ")+u("infernoflow switch")+n(" \u2014 generate a handoff summary for the next AI agent")),console.log(n(" ")+u("infernoflow ask --recent")+n(" \u2014 review what's in memory before starting"))),console.log()}export{Q as recapCommand};
@@ -0,0 +1,5 @@
1
+ import{refreshRuleFilesFromMemory as f}from"../ruleFiles.mjs";import{findProjectRoot as u}from"../projectRoot.mjs";import{bold as t,cyan as d,gray as e,green as g,red as c}from"../ui/output.mjs";async function h(n){const i=n.includes("--dry-run")||n.includes("-n"),l=n.includes("--json"),r=u(process.cwd());if(i){if(l){console.log(JSON.stringify({dryRun:!0,projectRoot:r},null,2));return}console.log(`
2
+ `+t("\u{1F525} infernoflow refresh")+e(" \u2014 dry run")),console.log(" "+e("Project root: ")+d(r)),console.log(" "+e("Would rewrite: ")+".cursorrules, CLAUDE.md, .github/copilot-instructions.md"),console.log();return}let s;try{s=f(r)}catch(o){l?console.log(JSON.stringify({ok:!1,error:o.message},null,2)):console.error(c(`
3
+ \u2718 refresh failed: `)+o.message+`
4
+ `),process.exit(1)}if(l){console.log(JSON.stringify({ok:!0,projectRoot:r,results:s},null,2));return}console.log(`
5
+ `+t("\u{1F525} infernoflow refresh")),console.log(" "+"\u2500".repeat(50));for(const o of s)"error"in o&&o.error?console.log(" "+c("\u2718 ")+o.rel+e(" \u2014 "+o.error)):"created"in o&&(o.created||o.updated)?console.log(" "+g("\u2714 ")+o.rel+e(o.created?" \u2014 created":" \u2014 updated")):console.log(" "+e("\xB7 ")+o.rel+e(" \u2014 unchanged"));console.log()}export{h as refreshCommand};
@@ -1,7 +1,6 @@
1
- import*as o from"node:fs";import*as l from"node:path";import{header as L,ok as V,fail as J,warn as U,section as $,bold as r,cyan as w,yellow as m,gray as n,green as d,red as R,white as W}from"../ui/output.mjs";import{ampPaths as _}from"../amp/io.mjs";function E(a){const s=Math.floor((Date.now()-a)/1e3);return s<60?"just now":s<3600?`${Math.floor(s/60)}m ago`:s<86400?`${Math.floor(s/3600)}h ago`:`${Math.floor(s/86400)}d ago`}function z(a,s){const f=new Set;if(o.existsSync(a))for(const i of o.readdirSync(a).filter(y=>y.endsWith(".json")))try{(JSON.parse(o.readFileSync(l.join(a,i),"utf8")).capabilitiesCovered||[]).forEach(M=>f.add(M))}catch{}return{covered:s.filter(i=>f.has(i)),uncovered:s.filter(i=>!f.has(i))}}async function K(a=[]){const s=a.includes("--json"),f=process.cwd(),i=l.join(f,"inferno"),y=l.join(f,".ai-memory");s||L("status"),!o.existsSync(i)&&!o.existsSync(y)&&(s&&(console.log(JSON.stringify({ok:!1,error:"not_initialized",hint:"Run: infernoflow init"},null,2)),process.exit(1)),J("not initialized \u2014 neither .ai-memory/ nor inferno/ found","Run: infernoflow init"),console.log(),process.exit(1));const M=l.join(y,"amp.json"),k=l.join(i,"config.json"),S=l.join(i,"contract.json"),D=o.existsSync(M)||o.existsSync(y),H=(()=>{try{return JSON.parse(o.readFileSync(k,"utf8")).mode||null}catch{return null}})();if(D||H==="memory"||!o.existsSync(S)&&o.existsSync(k)){const t=_(f).sessions,e=o.existsSync(t)?o.readFileSync(t,"utf8").split(`
2
- `).filter(Boolean).map(g=>{try{return JSON.parse(g)}catch{return null}}).filter(Boolean):[],c=e.filter(g=>g.type==="gotcha").length,h=e.filter(g=>g.type==="decision").length,u=e.filter(g=>g.type==="attempt").length,p=e[e.length-1];if(s){console.log(JSON.stringify({ok:!0,mode:"memory",entries:e.length,gotchas:c,decisions:h,attempts:u,lastEntry:p?p.ts:null},null,2));return}$("Session memory"),console.log(` ${n("entries")} ${r(String(e.length))}`),console.log(` ${n("gotchas")} ${r(String(c))}`),console.log(` ${n("decisions")} ${r(String(h))}`),console.log(` ${n("attempts")} ${r(String(u))}`),p&&console.log(` ${n("last entry")} ${n(E(new Date(p.ts).getTime()))}`),console.log(),e.length===0?console.log(` ${m("\u25CF")} ${r(m("empty"))} ${n("\u2014 log your first gotcha:")} ${w('infernoflow log "..." --type gotcha')}`):console.log(` ${d("\u25CF")} ${r(d("ready"))} ${n("\u2014 run")} ${w("infernoflow recap")} ${n("for the full session summary")}`),o.existsSync(S)?console.log():console.log(`
3
- ${n("Want capability contracts + CI gates? Run:")} ${w("infernoflow init --mode full")}
4
- `);return}o.existsSync(S)||(s&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),J("contract.json not found"),console.log(),process.exit(1));const j=JSON.parse(o.readFileSync(S,"utf8")),C=j.capabilities||[],F=o.statSync(S),N=l.join(i,"scenarios"),O=l.join(i,"CHANGELOG.md"),P=l.join(i,"capabilities.json"),{covered:I,uncovered:x}=z(N,C),G=o.existsSync(O)&&/##\s+Unreleased/i.test(o.readFileSync(O,"utf8")),v=[];x.length>0&&v.push(`${x.length} capabilities without scenario coverage`),G||v.push("CHANGELOG missing ## Unreleased section");const b=v.length===0;if(s){const t={ok:b,driftReasons:v,project:{policyId:j.policyId||null,policyVersion:j.policyVersion||null,lastChange:E(F.mtimeMs)},capabilities:{total:C.length,uncovered:x},changelog:{hasUnreleased:G}};console.log(JSON.stringify(t,null,2)),process.exit(b?0:1)}b||($("Drift"),v.forEach(t=>console.log(` ${m("\u26A0")} ${t}`))),$("Project"),console.log(` ${n("policy")} ${r(j.policyId||"\u2014")}`),console.log(` ${n("version")} ${r("v"+(j.policyVersion||"?"))}`),console.log(` ${n("last change")} ${n(E(F.mtimeMs))}`),$(`Capabilities ${n("("+C.length+")")}`);let A={};if(o.existsSync(P))try{(JSON.parse(o.readFileSync(P,"utf8")).capabilities||[]).forEach(e=>{A[e.id]=e})}catch{}if(C.forEach(t=>{const e=A[t],h=I.includes(t)?d("\u2714"):R("\u2718"),u=e?.title?n(` \u2014 ${e.title}`):"",p=e?.since?n(` [${e.since}]`):"";console.log(` ${h} ${W(t)}${u}${p}`)}),x.length>0?console.log(`
5
- ${m("\u26A0")} ${x.length} capability(ies) lack scenario coverage`):console.log(`
6
- ${d("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),o.existsSync(N)){const t=o.readdirSync(N).filter(e=>e.endsWith(".json"));t.length===0?U("No scenario files \u2014 add .json files to inferno/scenarios/"):t.forEach(e=>{try{const c=JSON.parse(o.readFileSync(l.join(N,e),"utf8")),h=c.steps?.length||0,u=(c.capabilitiesCovered||[]).length;console.log(` ${d("\u2714")} ${w(e)} ${n(`\u2014 ${h} steps, ${u} caps covered`)}`)}catch{console.log(` ${R("\u2718")} ${w(e)} ${n("\u2014 invalid JSON")}`)}})}else U("scenarios/ directory not found");if($("Changelog"),o.existsSync(O)){const t=o.readFileSync(O,"utf8");/##\s+Unreleased/i.test(t)?V("Has ## Unreleased section"):J("Missing ## Unreleased section"),t.split(`
7
- `).filter(c=>/^##\s/.test(c)).slice(0,3).forEach(c=>console.log(` ${n(c)}`))}else J("inferno/CHANGELOG.md not found");console.log(),console.log(b?` ${d("\u25CF")} ${r(d("ready"))} ${n("\u2014 run infernoflow check for full validation")}`:` ${m("\u25CF")} ${r(m("needs attention"))} ${n("\u2014 run infernoflow check for details")}`),console.log()}export{K as statusCommand};
1
+ import*as e from"node:fs";import*as l from"node:path";import{header as I,ok as L,fail as b,warn as A,section as $,bold as r,cyan as v,yellow as u,gray as n,green as h,red as R,white as V}from"../ui/output.mjs";import{readEntries as W}from"../amp/io.mjs";function M(f){const t=Math.floor((Date.now()-f)/1e3);return t<60?"just now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`}function _(f,t){const g=new Set;if(e.existsSync(f))for(const i of e.readdirSync(f).filter(y=>y.endsWith(".json")))try{(JSON.parse(e.readFileSync(l.join(f,i),"utf8")).capabilitiesCovered||[]).forEach(E=>g.add(E))}catch{}return{covered:t.filter(i=>g.has(i)),uncovered:t.filter(i=>!g.has(i))}}async function K(f=[]){const t=f.includes("--json"),g=process.cwd(),i=l.join(g,"inferno"),y=l.join(g,".ai-memory");t||I("status"),!e.existsSync(i)&&!e.existsSync(y)&&(t&&(console.log(JSON.stringify({ok:!1,error:"not_initialized",hint:"Run: infernoflow init"},null,2)),process.exit(1)),b("not initialized \u2014 neither .ai-memory/ nor inferno/ found","Run: infernoflow init"),console.log(),process.exit(1));const E=l.join(y,"amp.json"),J=l.join(i,"config.json"),m=l.join(i,"contract.json"),U=e.existsSync(E)||e.existsSync(y),D=(()=>{try{return JSON.parse(e.readFileSync(J,"utf8")).mode||null}catch{return null}})();if(U||D==="memory"||!e.existsSync(m)&&e.existsSync(J)){const o=W(g),s=o.filter(d=>d.type==="gotcha").length,c=o.filter(d=>d.type==="decision").length,p=o.filter(d=>d.type==="attempt").length,a=o[o.length-1];if(t){console.log(JSON.stringify({ok:!0,mode:"memory",entries:o.length,gotchas:s,decisions:c,attempts:p,lastEntry:a?a.ts:null},null,2));return}$("Session memory"),console.log(` ${n("entries")} ${r(String(o.length))}`),console.log(` ${n("gotchas")} ${r(String(s))}`),console.log(` ${n("decisions")} ${r(String(c))}`),console.log(` ${n("attempts")} ${r(String(p))}`),a&&console.log(` ${n("last entry")} ${n(M(new Date(a.ts).getTime()))}`),console.log(),o.length===0?console.log(` ${u("\u25CF")} ${r(u("empty"))} ${n("\u2014 log your first gotcha:")} ${v('infernoflow log "..." --type gotcha')}`):console.log(` ${h("\u25CF")} ${r(h("ready"))} ${n("\u2014 run")} ${v("infernoflow recap")} ${n("for the full session summary")}`),e.existsSync(m)?console.log():console.log(`
2
+ ${n("Want capability contracts + CI gates? Run:")} ${v("infernoflow init --mode full")}
3
+ `);return}e.existsSync(m)||(t&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),b("contract.json not found"),console.log(),process.exit(1));const S=JSON.parse(e.readFileSync(m,"utf8")),w=S.capabilities||[],k=e.statSync(m),C=l.join(i,"scenarios"),N=l.join(i,"CHANGELOG.md"),F=l.join(i,"capabilities.json"),{covered:H,uncovered:j}=_(C,w),G=e.existsSync(N)&&/##\s+Unreleased/i.test(e.readFileSync(N,"utf8")),x=[];j.length>0&&x.push(`${j.length} capabilities without scenario coverage`),G||x.push("CHANGELOG missing ## Unreleased section");const O=x.length===0;if(t){const o={ok:O,driftReasons:x,project:{policyId:S.policyId||null,policyVersion:S.policyVersion||null,lastChange:M(k.mtimeMs)},capabilities:{total:w.length,uncovered:j},changelog:{hasUnreleased:G}};console.log(JSON.stringify(o,null,2)),process.exit(O?0:1)}O||($("Drift"),x.forEach(o=>console.log(` ${u("\u26A0")} ${o}`))),$("Project"),console.log(` ${n("policy")} ${r(S.policyId||"\u2014")}`),console.log(` ${n("version")} ${r("v"+(S.policyVersion||"?"))}`),console.log(` ${n("last change")} ${n(M(k.mtimeMs))}`),$(`Capabilities ${n("("+w.length+")")}`);let P={};if(e.existsSync(F))try{(JSON.parse(e.readFileSync(F,"utf8")).capabilities||[]).forEach(s=>{P[s.id]=s})}catch{}if(w.forEach(o=>{const s=P[o],p=H.includes(o)?h("\u2714"):R("\u2718"),a=s?.title?n(` \u2014 ${s.title}`):"",d=s?.since?n(` [${s.since}]`):"";console.log(` ${p} ${V(o)}${a}${d}`)}),j.length>0?console.log(`
4
+ ${u("\u26A0")} ${j.length} capability(ies) lack scenario coverage`):console.log(`
5
+ ${h("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),e.existsSync(C)){const o=e.readdirSync(C).filter(s=>s.endsWith(".json"));o.length===0?A("No scenario files \u2014 add .json files to inferno/scenarios/"):o.forEach(s=>{try{const c=JSON.parse(e.readFileSync(l.join(C,s),"utf8")),p=c.steps?.length||0,a=(c.capabilitiesCovered||[]).length;console.log(` ${h("\u2714")} ${v(s)} ${n(`\u2014 ${p} steps, ${a} caps covered`)}`)}catch{console.log(` ${R("\u2718")} ${v(s)} ${n("\u2014 invalid JSON")}`)}})}else A("scenarios/ directory not found");if($("Changelog"),e.existsSync(N)){const o=e.readFileSync(N,"utf8");/##\s+Unreleased/i.test(o)?L("Has ## Unreleased section"):b("Missing ## Unreleased section"),o.split(`
6
+ `).filter(c=>/^##\s/.test(c)).slice(0,3).forEach(c=>console.log(` ${n(c)}`))}else b("inferno/CHANGELOG.md not found");console.log(),console.log(O?` ${h("\u25CF")} ${r(h("ready"))} ${n("\u2014 run infernoflow check for full validation")}`:` ${u("\u25CF")} ${r(u("needs attention"))} ${n("\u2014 run infernoflow check for details")}`),console.log()}export{K as statusCommand};
@@ -1,11 +1,11 @@
1
- import*as b from"node:fs";import*as m from"node:path";import"node:os";import{execSync as S}from"node:child_process";import{bold as V,cyan as $,gray as it,green as rt,yellow as J,red as pt}from"../ui/output.mjs";import{ampPaths as ut,readEntries as dt,appendEntry as ht}from"../amp/io.mjs";const T="inferno";function z(){return ut(process.cwd())}const gt=m.join(T,"HANDOFF.md"),Ft=m.join(T,"sessions.jsonl"),K=m.join(T,"context-state.json"),X=m.join(T,"contract.json"),q=m.join(T,"theme.json"),Q=m.join(T,"adoption_profile.json");function u(t){try{return JSON.parse(b.readFileSync(t,"utf8"))}catch{return null}}function mt(t){try{return b.readFileSync(t,"utf8")}catch{return null}}function Y(t){return t?new Date(t).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}):"unknown"}function Z(t){if(t<0)return"unknown";const i=Math.floor(t/36e5),l=Math.floor(t%36e5/6e4);return i>0?`${i}h ${l}m`:`${l}m`}function tt(){return dt(process.cwd())}function et(t,i,l){if(l)return new Date(0);if(i){const o=i.match(/^(\d+)h$/i),c=i.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(c)return new Date(Date.now()-parseInt(c[1])*864e5);const s=new Date(i);if(!isNaN(s))return s}for(let o=t.length-1;o>=0;o--)if(t[o].type==="handoff"){const c=new Date(t[o].ts||0),s=new Date(Date.now()-864e5);return c>s?c:s}return new Date(Date.now()-864e5)}function yt(t){try{const i=process.platform;if(i==="win32")S("clip",{input:t});else if(i==="darwin")S("pbcopy",{input:t});else try{S("xclip -selection clipboard",{input:t})}catch{S("xsel --clipboard --input",{input:t})}return!0}catch{return!1}}function ot(){if(process.env.CURSOR_SESSION)return"Cursor";if(process.env.COPILOT_SESSION)return"GitHub Copilot";if(process.env.CLAUDE_CODE_SESSION)return"Claude Code";if(process.env.WINDSURF_SESSION)return"Windsurf";if(process.env.TERM_PROGRAM==="vscode")return"VS Code";const t=u(Q);return t?.ide?t.ide:null}function wt(){try{const t=S("git diff --stat HEAD 2>/dev/null || git diff --cached --stat 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();return t||S("git log --stat -1 --pretty= 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function ct(t){try{const i=t&&t.getTime()>0?`--after="${t.toISOString()}"`:"-10",l=S(`git log ${i} --name-only --pretty=format: 2>/dev/null`,{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();if(!l)return[];const o={};for(const c of l.split(`
1
+ import*as b from"node:fs";import*as m from"node:path";import"node:os";import{execSync as S}from"node:child_process";import{bold as V,cyan as $,gray as it,green as rt,yellow as J,red as pt}from"../ui/output.mjs";import{ampPaths as ut,readEntries as dt,appendEntry as ht}from"../amp/io.mjs";const F="inferno";function z(){return ut(process.cwd())}const gt=m.join(F,"HANDOFF.md"),Tt=m.join(F,"sessions.jsonl"),K=m.join(F,"context-state.json"),X=m.join(F,"contract.json"),q=m.join(F,"theme.json"),Q=m.join(F,"adoption_profile.json");function u(t){try{return JSON.parse(b.readFileSync(t,"utf8"))}catch{return null}}function mt(t){try{return b.readFileSync(t,"utf8")}catch{return null}}function Y(t){return t?new Date(t).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}):"unknown"}function Z(t){if(t<0)return"unknown";const i=Math.floor(t/36e5),l=Math.floor(t%36e5/6e4);return i>0?`${i}h ${l}m`:`${l}m`}function tt(){return dt(process.cwd())}function et(t,i,l){if(l)return new Date(0);if(i){const o=i.match(/^(\d+)h$/i),c=i.match(/^(\d+)d$/i);if(o)return new Date(Date.now()-parseInt(o[1])*36e5);if(c)return new Date(Date.now()-parseInt(c[1])*864e5);const s=new Date(i);if(!isNaN(s.getTime()))return s}for(let o=t.length-1;o>=0;o--)if(t[o].type==="handoff"){const c=new Date(t[o].ts||0),s=new Date(Date.now()-864e5);return c>s?c:s}return new Date(Date.now()-864e5)}function yt(t){try{const i=process.platform;if(i==="win32")S("clip",{input:t});else if(i==="darwin")S("pbcopy",{input:t});else try{S("xclip -selection clipboard",{input:t})}catch{S("xsel --clipboard --input",{input:t})}return!0}catch{return!1}}function ot(){if(process.env.CURSOR_SESSION)return"Cursor";if(process.env.COPILOT_SESSION)return"GitHub Copilot";if(process.env.CLAUDE_CODE_SESSION)return"Claude Code";if(process.env.WINDSURF_SESSION)return"Windsurf";if(process.env.TERM_PROGRAM==="vscode")return"VS Code";const t=u(Q);return t?.ide?t.ide:null}function wt(){try{const t=S("git diff --stat HEAD 2>/dev/null || git diff --cached --stat 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();return t||S("git log --stat -1 --pretty= 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function ct(t){try{const i=t&&t.getTime()>0?`--after="${t.toISOString()}"`:"-10",l=S(`git log ${i} --name-only --pretty=format: 2>/dev/null`,{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();if(!l)return[];const o={};for(const c of l.split(`
2
2
  `)){const s=c.trim();s&&(o[s]=(o[s]||0)+1)}return Object.entries(o).sort((c,s)=>s[1]-c[1]).slice(0,5).map(([c,s])=>({file:c,edits:s}))}catch{return[]}}function nt(t){try{const i=t?`--after="${t.toISOString()}"`:"-5",l=S(`git log ${i} --pretty=format:"%h %s" 2>/dev/null`,{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();return l?l.split(`
3
- `).filter(Boolean):[]}catch{return[]}}function lt(t){const i=[],l=t.filter(o=>o.type==="attempt"&&(o.result==="failed"||o.result==="partial"||!o.result));for(const o of l)t.find(s=>s.type==="attempt"&&s.result==="worked"&&new Date(s.ts)>new Date(o.ts)&&s.summary.toLowerCase().includes(o.summary.split(" ")[0].toLowerCase()))||i.push({text:o.summary,ts:o.ts,kind:"unresolved-attempt"});for(const o of t)/\b(TODO|WIP|FIXME|BLOCKED|pending)\b/i.test(o.summary)&&(i.find(c=>c.text===o.summary)||i.push({text:o.summary,ts:o.ts,kind:"flagged"}));return i.slice(0,8)}function St(t,i,l){const o=u(K)||{},c=u(X)||{},s=u(q),A=u(Q),D=tt(),a=et(D,i,l),N=D.filter(n=>new Date(n.ts||0)>a),H=D.slice(-5),I=new Date,d=I.toLocaleString("en-GB",{day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}),j=c.policyId||m.basename(process.cwd()),E=c.policyVersion||"?",O=(c.capabilities||[]).slice(0,20),x=ot(),M=a.getTime()>0?I-a:-1,k=Z(M),L=a.getTime()>0?a.getTime().toString(16).slice(-6).toUpperCase():"ALL",C=nt(a.getTime()>0?a:null),_=wt(),R=ct(a.getTime()>0?a:null),y=N.length>0?N:H,r=y.filter(n=>n.type==="gotcha"),h=y.filter(n=>n.type==="decision"),v=y.filter(n=>n.type==="attempt").filter(n=>n.result==="failed"||n.result==="partial"),G=y.filter(n=>n.type==="preference"),W=y.slice(-8),g=lt(y),st=a.getTime()===0?"all time":a.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),w=["sessions.jsonl"];(o.working||o.intent)&&w.push("context-state.json"),s&&w.push("theme.json"),c.capabilities?.length&&w.push("contract.json"),A&&w.push("adoption_profile.json"),C.length&&w.push("git log");const B=r.length,P=h.length,at=v.length;let F=Math.min(B*20,40)+Math.min(P*15,30)+Math.min(at*15,20);F=Math.min(F,100);const ft=F>=80?"A":F>=60?"B":F>=40?"C":F>=20?"D":"F",e=[`# \u{1F525} infernoflow Handoff \u2014 ${j}`,`> Generated: ${d}${t?` | Handing off to: **${t}**`:""}`,`> Session: **#${L}** \xB7 ${k} \xB7 **${N.length} entries** \xB7 Health: **${ft}** (${F}/100)`,`> Sources: ${w.join(" \xB7 ")}${x?` \xB7 IDE: ${x}`:""}`,"","---",""];if((o.working||o.intent)&&(e.push("## \u{1F3AF} Working on",""),o.working&&e.push(`**${o.working}** _(${Y(o.workingUpdated)})_`),o.intent&&e.push(`Intent: ${o.intent} _(${Y(o.intentUpdated)})_`),e.push("")),r.length&&(e.push(`## \u26A0\uFE0F STOP \u2014 Read These Before Doing Anything (${r.length} gotcha${r.length===1?"":"s"})`,""),r.forEach((n,p)=>{e.push(`${p+1}. **${n.summary}**`);const f=n.file||n.source;if(f&&/[\\/.]/.test(f)){const U=n.line?`${f}:${n.line}`:f;e.push(` \u2192 File: \`${U}\``)}}),e.push("")),h.length&&(e.push("## \u2713 Decisions In Effect \u2014 Follow These",""),h.forEach((n,p)=>{const f=n.result?` \u2192 **${n.result}**`:"";e.push(`${p+1}. ${n.summary}${f}`)}),e.push("")),v.length&&(e.push("## \u274C Already Tried \u2014 Don't Repeat",""),v.forEach((n,p)=>{const f=n.file||n.source,U=f&&/[\\/.]/.test(f)?` (\`${f}\`)`:"";e.push(`${p+1}. ${n.summary}${U} _(${Y(n.ts)})_`)}),e.push("")),R.length){e.push("## \u{1F4C1} Hot Files This Session","");for(const{file:n,edits:p}of R)e.push(`- \`${n}\` \u2014 ${p} edit${p!==1?"s":""}`);e.push("")}if(G.length){e.push("## Developer preferences","");for(const n of G)e.push(`- ${n.summary}`);e.push("")}if(C.length||_){if(e.push("## Git activity this session",""),C.length){e.push("**Commits:**");for(const n of C)e.push(`- \`${n}\``);e.push("")}_&&(e.push("**Uncommitted changes:**"),e.push("```"),e.push(_.split(`
3
+ `).filter(Boolean):[]}catch{return[]}}function lt(t){const i=[],l=t.filter(o=>o.type==="attempt"&&(o.result==="failed"||o.result==="partial"||!o.result));for(const o of l)t.find(s=>s.type==="attempt"&&s.result==="worked"&&new Date(s.ts)>new Date(o.ts)&&s.summary.toLowerCase().includes(o.summary.split(" ")[0].toLowerCase()))||i.push({text:o.summary,ts:o.ts,kind:"unresolved-attempt"});for(const o of t)/\b(TODO|WIP|FIXME|BLOCKED|pending)\b/i.test(o.summary)&&(i.find(c=>c.text===o.summary)||i.push({text:o.summary,ts:o.ts,kind:"flagged"}));return i.slice(0,8)}function St(t,i,l){const o=u(K)||{},c=u(X)||{},s=u(q),A=u(Q),D=tt(),a=et(D,i,l),N=D.filter(n=>new Date(n.ts||0)>a),H=D.slice(-5),I=new Date,d=I.toLocaleString("en-GB",{day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}),j=c.policyId||m.basename(process.cwd()),E=c.policyVersion||"?",O=(c.capabilities||[]).slice(0,20),x=ot(),M=a.getTime()>0?I.getTime()-a.getTime():-1,k=Z(M),L=a.getTime()>0?a.getTime().toString(16).slice(-6).toUpperCase():"ALL",C=nt(a.getTime()>0?a:null),_=wt(),R=ct(a.getTime()>0?a:null),y=N.length>0?N:H,r=y.filter(n=>n.type==="gotcha"),h=y.filter(n=>n.type==="decision"),v=y.filter(n=>n.type==="attempt").filter(n=>n.result==="failed"||n.result==="partial"),G=y.filter(n=>n.type==="preference"),W=y.slice(-8),g=lt(y),st=a.getTime()===0?"all time":a.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),w=["sessions.jsonl"];(o.working||o.intent)&&w.push("context-state.json"),s&&w.push("theme.json"),c.capabilities?.length&&w.push("contract.json"),A&&w.push("adoption_profile.json"),C.length&&w.push("git log");const B=r.length,P=h.length,at=v.length;let T=Math.min(B*20,40)+Math.min(P*15,30)+Math.min(at*15,20);T=Math.min(T,100);const ft=T>=80?"A":T>=60?"B":T>=40?"C":T>=20?"D":"F",e=[`# \u{1F525} infernoflow Handoff \u2014 ${j}`,`> Generated: ${d}${t?` | Handing off to: **${t}**`:""}`,`> Session: **#${L}** \xB7 ${k} \xB7 **${N.length} entries** \xB7 Health: **${ft}** (${T}/100)`,`> Sources: ${w.join(" \xB7 ")}${x?` \xB7 IDE: ${x}`:""}`,"","---",""];if((o.working||o.intent)&&(e.push("## \u{1F3AF} Working on",""),o.working&&e.push(`**${o.working}** _(${Y(o.workingUpdated)})_`),o.intent&&e.push(`Intent: ${o.intent} _(${Y(o.intentUpdated)})_`),e.push("")),r.length&&(e.push(`## \u26A0\uFE0F STOP \u2014 Read These Before Doing Anything (${r.length} gotcha${r.length===1?"":"s"})`,""),r.forEach((n,p)=>{e.push(`${p+1}. **${n.summary}**`);const f=n.file||n.source;if(f&&/[\\/.]/.test(f)){const U=n.line?`${f}:${n.line}`:f;e.push(` \u2192 File: \`${U}\``)}}),e.push("")),h.length&&(e.push("## \u2713 Decisions In Effect \u2014 Follow These",""),h.forEach((n,p)=>{const f=n.result?` \u2192 **${n.result}**`:"";e.push(`${p+1}. ${n.summary}${f}`)}),e.push("")),v.length&&(e.push("## \u274C Already Tried \u2014 Don't Repeat",""),v.forEach((n,p)=>{const f=n.file||n.source,U=f&&/[\\/.]/.test(f)?` (\`${f}\`)`:"";e.push(`${p+1}. ${n.summary}${U} _(${Y(n.ts)})_`)}),e.push("")),R.length){e.push("## \u{1F4C1} Hot Files This Session","");for(const{file:n,edits:p}of R)e.push(`- \`${n}\` \u2014 ${p} edit${p!==1?"s":""}`);e.push("")}if(G.length){e.push("## Developer preferences","");for(const n of G)e.push(`- ${n.summary}`);e.push("")}if(C.length||_){if(e.push("## Git activity this session",""),C.length){e.push("**Commits:**");for(const n of C)e.push(`- \`${n}\``);e.push("")}_&&(e.push("**Uncommitted changes:**"),e.push("```"),e.push(_.split(`
4
4
  `).slice(0,15).join(`
5
5
  `)),e.push("```"),e.push(""))}if(s){if(e.push("## Design system",""),s.fonts?.primary&&e.push(`- **Font:** ${s.fonts.primary}${s.fonts.mono?` \xB7 mono: ${s.fonts.mono}`:""}`),s.colors?.mode&&e.push(`- **Mode:** ${s.colors.mode}`),s.colors?.palette){const n=Object.entries(s.colors.palette).map(([p,f])=>`${p}=${f}`).join(" ");e.push(`- **Palette:** ${n}`)}if(s.cssVars&&Object.keys(s.cssVars).length){const n=Object.entries(s.cssVars).slice(0,6).map(([p,f])=>`${p}: ${f}`).join(" | ");e.push(`- **CSS vars:** ${n}`)}s.framework&&e.push(`- **Framework:** ${s.framework}`),e.push("","> \u26A0 Always match these exactly. Do not introduce new colors or fonts.","")}return O.length&&(e.push("## Capability contract",""),e.push(`Project: **${j}** v${E}`),e.push(`Capabilities: ${O.join(", ")}`),e.push("")),e.push("---"),e.push(`_Session #${L} \xB7 ${k} \xB7 Generated by infernoflow._`),e.join(`
6
- `)}async function Tt(t){const i=r=>t.includes(r),l=r=>{const h=t.indexOf(r);return h!==-1&&t[h+1]?t[h+1]:null},o=i("--show")||i("-s"),c=i("--copy")||i("-c"),s=i("--json"),A=i("--all"),D=l("--since"),a=l("--to")||t.find(r=>!r.startsWith("-")&&!["switch"].includes(r))||null;console.log(`
6
+ `)}async function Ft(t){const i=r=>t.includes(r),l=r=>{const h=t.indexOf(r);return h!==-1&&t[h+1]?t[h+1]:null},o=i("--show")||i("-s"),c=i("--copy")||i("-c"),s=i("--json"),A=i("--all"),D=l("--since"),a=l("--to")||t.find(r=>!r.startsWith("-")&&!["switch"].includes(r))||null;console.log(`
7
7
  `+V("\u{1F525} infernoflow \u2014 switch")),console.log(" "+"\u2500".repeat(50)+`
8
- `);const N=m.join(process.cwd(),".ai-memory");if(!b.existsSync(T)&&!b.existsSync(N)&&(console.error(pt(` \u2718 not initialized \u2014 run: infernoflow init
8
+ `);const N=m.join(process.cwd(),".ai-memory");if(!b.existsSync(F)&&!b.existsSync(N)&&(console.error(pt(` \u2718 not initialized \u2014 run: infernoflow init
9
9
  `)),process.exit(1)),o){const r=mt(gt);if(!r){console.log(J(` \u26A0 No HANDOFF.md yet \u2014 run: infernoflow switch
10
10
  `));return}console.log(r);return}const H=St(a,D,A);if(s){const r=u(K)||{},h=u(X)||{},v=u(q),G=u(Q),W=tt(),g=et(W,D,A),st=W.filter(P=>new Date(P.ts||0)>g),w=nt(g.getTime()>0?g:null),B=ot();console.log(JSON.stringify({state:r,contract:{policyId:h.policyId,policyVersion:h.policyVersion,capabilities:h.capabilities},theme:v,adoption:G,sessions:st,commits:w,ide:B,sessionStart:g.toISOString(),sessionId:g.getTime()>0?g.getTime().toString(16).slice(-6).toUpperCase():"ALL",sessionDuration:Z(g.getTime()>0?Date.now()-g.getTime():-1),generatedAt:new Date().toISOString()},null,2));return}b.writeFileSync(z().handoff,H,"utf8"),console.log(rt(" \u2714 Written \u2192 "+m.relative(process.cwd(),z().handoff)+`
11
- `));const I=tt(),d=et(I,D,A),j=I.filter(r=>new Date(r.ts||0)>d),E=u(K)||{},O=u(q),x=u(X)||{},M=nt(d.getTime()>0?d:null),k=ct(d.getTime()>0?d:null),L=ot(),C=j.length>0?j:I.slice(-5),_=lt(C),R=Z(d.getTime()>0?Date.now()-d.getTime():-1),y=d.getTime()>0?d.getTime().toString(16).slice(-6).toUpperCase():"ALL";if(console.log(" "+V("Handoff ready")),console.log(" "+"\u2500".repeat(50)),console.log(" "+it("Session #"+y+" \xB7 "+R)),E.working&&console.log(" Working on "+$(E.working)),E.intent&&console.log(" Intent "+$(E.intent)),console.log(" Memory "+j.length+" entries this session (total: "+I.length+")"),_.length&&console.log(" Open threads "+J(_.length+" unresolved")),M.length&&console.log(" Git commits "+M.length+" this session"),k.length&&console.log(" Hot files "+k.map(r=>$(r.file)).join(", ")),console.log(" Capabilities "+(x.capabilities||[]).length+" registered"),O?.fonts?.primary&&console.log(" Font "+O.fonts.primary),O?.colors?.mode&&console.log(" Color mode "+O.colors.mode),L&&console.log(" IDE "+L),a&&console.log(" Handing off \u2192 "+$(a)),console.log(),c){const r=yt(H);console.log(r?rt(" \u2714 Copied to clipboard \u2014 paste at the start of your next AI session"):J(" \u26A0 Clipboard failed \u2014 open inferno/HANDOFF.md manually"))}else console.log(" "+V("Ready to use:")),console.log(" "+$("1.")+" Open "+$("inferno/HANDOFF.md")),console.log(" "+$("2.")+" Copy all"),console.log(" "+$("3.")+" Paste at the start of your next AI session"),console.log(" "+it(" tip: use --copy to skip steps 1-2 automatically"));if(console.log(),b.existsSync(z().sessions)){const r={ts:new Date().toISOString(),agent:"infernoflow",type:"handoff",summary:a?`Handed off to ${a}`:"Handoff generated"};ht(process.cwd(),r)}}export{Tt as switchCommand};
11
+ `));const I=tt(),d=et(I,D,A),j=I.filter(r=>new Date(r.ts||0)>d),E=u(K)||{},O=u(q),x=u(X)||{},M=nt(d.getTime()>0?d:null),k=ct(d.getTime()>0?d:null),L=ot(),C=j.length>0?j:I.slice(-5),_=lt(C),R=Z(d.getTime()>0?Date.now()-d.getTime():-1),y=d.getTime()>0?d.getTime().toString(16).slice(-6).toUpperCase():"ALL";if(console.log(" "+V("Handoff ready")),console.log(" "+"\u2500".repeat(50)),console.log(" "+it("Session #"+y+" \xB7 "+R)),E.working&&console.log(" Working on "+$(E.working)),E.intent&&console.log(" Intent "+$(E.intent)),console.log(" Memory "+j.length+" entries this session (total: "+I.length+")"),_.length&&console.log(" Open threads "+J(_.length+" unresolved")),M.length&&console.log(" Git commits "+M.length+" this session"),k.length&&console.log(" Hot files "+k.map(r=>$(r.file)).join(", ")),console.log(" Capabilities "+(x.capabilities||[]).length+" registered"),O?.fonts?.primary&&console.log(" Font "+O.fonts.primary),O?.colors?.mode&&console.log(" Color mode "+O.colors.mode),L&&console.log(" IDE "+L),a&&console.log(" Handing off \u2192 "+$(a)),console.log(),c){const r=yt(H);console.log(r?rt(" \u2714 Copied to clipboard \u2014 paste at the start of your next AI session"):J(" \u26A0 Clipboard failed \u2014 open inferno/HANDOFF.md manually"))}else console.log(" "+V("Ready to use:")),console.log(" "+$("1.")+" Open "+$("inferno/HANDOFF.md")),console.log(" "+$("2.")+" Copy all"),console.log(" "+$("3.")+" Paste at the start of your next AI session"),console.log(" "+it(" tip: use --copy to skip steps 1-2 automatically"));if(console.log(),b.existsSync(z().sessions)){const r={ts:new Date().toISOString(),agent:"infernoflow",type:"handoff",summary:a?`Handed off to ${a}`:"Handoff generated"};ht(process.cwd(),r)}}export{Ft as switchCommand};
@@ -0,0 +1,41 @@
1
+ import*as s from"node:fs";import*as u from"node:path";import{ampPaths as h,projectSlug as O}from"../amp/io.mjs";import{findProjectRoot as x}from"../projectRoot.mjs";import{bold as y,cyan as a,gray as e,green as p,yellow as D,red as v}from"../ui/output.mjs";function S(o){try{return JSON.parse(s.readFileSync(u.join(o,"amp.json"),"utf8"))}catch{return null}}function j(o,t){s.mkdirSync(o,{recursive:!0}),s.writeFileSync(u.join(o,"amp.json"),JSON.stringify(t,null,2)+`
2
+ `,"utf8")}function F(o){try{return s.readFileSync(o,"utf8").split(`
3
+ `).filter(r=>r.trim().length>0).length}catch{return 0}}function N(o,t){return o?{source:"env (INFERNOFLOW_GLOBAL_DIR)",value:o}:t?{source:"amp.json (globalDir)",value:t}:{source:"default (in-project)",value:null}}function R({jsonOut:o}={}){const t=x(process.cwd()),r=h(process.cwd()),n=S(r.root)||{},l=N(process.env.INFERNOFLOW_GLOBAL_DIR,n.globalDir),i=O(t),m=s.existsSync(r.globalFile),g=m?F(r.globalFile):0,f=u.join(r.root,"global.jsonl"),c=l.value&&f!==r.globalFile&&s.existsSync(f),d=c?F(f):0;if(o){console.log(JSON.stringify({projectRoot:t,projectSlug:i,configured:!!l.value,source:l.source,configuredPath:l.value,resolvedFile:r.globalFile,exists:m,entries:g,orphan:c,orphanLines:d},null,2));return}console.log(`
4
+ `+y("\u{1F525} infernoflow sync \u2014 status")),console.log(" "+"\u2500".repeat(58)),console.log(" "+e("Project ")+a(i)),console.log(" "+e("Source ")+l.source),l.value&&console.log(" "+e("Configured path ")+a(l.value)),console.log(" "+e("Resolved file ")+a(r.globalFile)),console.log(" "+e("Status ")+(m?p(`${g} entries`):e("not yet created"))),c&&(console.log(""),console.log(" "+D("\u26A0 Orphan local file detected")),console.log(" "+e(" "+f+" ("+d+" entries)")),console.log(" "+e(" Run ")+a("infernoflow sync migrate")+e(" to merge it into the synced location."))),console.log(""),l.value||(console.log(" "+e("Tip: point at a synced folder (iCloud/Dropbox/etc.) to share personal")),console.log(" "+e(" preferences across your own machines:")),console.log(" "+a(" infernoflow sync set ~/Dropbox/infernoflow-memory")),console.log(""))}function J(o,{jsonOut:t}={}){o||(console.error(v(`
5
+ \u2718 usage: infernoflow sync set <path>
6
+ `)),process.exit(1));const r=h(process.cwd(),{forWrite:!0}),n=S(r.root)||{},l=n.globalDir||null;if(n.globalDir=o,j(r.root,n),t){console.log(JSON.stringify({ok:!0,previous:l,current:o,file:u.join(r.root,"amp.json")},null,2));return}console.log(`
7
+ `+p("\u2714 ")+"globalDir set to "+a(o)),l&&l!==o&&console.log(" "+e(" (was: "+l+")")),console.log(" "+e(" Run ")+a("infernoflow sync migrate")+e(` to move existing entries.
8
+ `))}function L({jsonOut:o}={}){const t=h(process.cwd(),{forWrite:!0}),r=S(t.root)||{},n=r.globalDir||null;if(!n){if(o){console.log(JSON.stringify({ok:!0,changed:!1},null,2));return}console.log(`
9
+ `+e(`globalDir was not set \u2014 nothing to clear.
10
+ `));return}if(delete r.globalDir,j(t.root,r),o){console.log(JSON.stringify({ok:!0,changed:!0,previous:n},null,2));return}console.log(`
11
+ `+p("\u2714 ")+"globalDir cleared "+e("(was "+n+")")),console.log(" "+e(` global.jsonl is now in-project again. Old synced file is left in place.
12
+ `))}function k({jsonOut:o,dryRun:t}={}){const r=h(process.cwd()),n=u.join(r.root,"global.jsonl"),l=r.globalFile;if(n===l){if(o){console.log(JSON.stringify({ok:!0,migrated:0,reason:"sync not configured"},null,2));return}console.log(`
13
+ `+e(`Sync not configured \u2014 nothing to migrate.
14
+ `));return}if(!s.existsSync(n)){if(o){console.log(JSON.stringify({ok:!0,migrated:0,reason:"no local global.jsonl"},null,2));return}console.log(`
15
+ `+e(`No local global.jsonl to migrate.
16
+ `));return}const i=s.readFileSync(n,"utf8").split(`
17
+ `).filter(Boolean),g=s.existsSync(l)?s.readFileSync(l,"utf8").split(`
18
+ `).filter(Boolean):[],f=new Set,c=[];for(const w of[...g,...i])try{const b=JSON.parse(w).id||w;if(f.has(b))continue;f.add(b),c.push(w)}catch{}if(t){if(o){console.log(JSON.stringify({ok:!0,dryRun:!0,wouldWrite:l,fromLocal:i.length,existingTarget:g.length,afterMerge:c.length},null,2));return}console.log(`
19
+ `+y("\u{1F525} infernoflow sync migrate")+e(" \u2014 dry run")),console.log(" "+e("From ")+a(n)+e(" ("+i.length+" entries)")),console.log(" "+e("To ")+a(l)+e(" ("+g.length+" existing)")),console.log(" "+e("After ")+p(c.length+" entries (deduped by id)")),console.log("");return}s.mkdirSync(u.dirname(l),{recursive:!0}),s.writeFileSync(l,c.join(`
20
+ `)+(c.length?`
21
+ `:""),"utf8");const d=n.replace(/\.jsonl$/,`-archive-${Date.now()}.jsonl`);if(s.renameSync(n,d),o){console.log(JSON.stringify({ok:!0,migrated:i.length,afterMerge:c.length,target:l,archivedAs:d},null,2));return}console.log(`
22
+ `+p("\u2714 ")+"Migrated "+c.length+" entries to "+a(l)),console.log(" "+e(" Local file archived \u2192 "+u.basename(d)+`
23
+ `))}async function B(o){const t=o.includes("--json"),r=o.includes("--dry-run")||o.includes("-n"),n=o.slice(1).find(i=>!i.startsWith("-"));if(!n||n==="status"||n==="--help"||n==="-h"){if(n==="--help"||n==="-h"){console.log(`
24
+ ${y("\u{1F525} infernoflow sync")} ${e("\u2014 cross-machine personal memory")}
25
+
26
+ ${y("Usage:")}
27
+ infernoflow sync Show current setup
28
+ infernoflow sync status Same as bare invocation
29
+ infernoflow sync set <path> Configure synced directory
30
+ infernoflow sync clear Remove configuration
31
+ infernoflow sync migrate [--dry-run] Move local global.jsonl into sync
32
+
33
+ ${y("Recommended:")}
34
+ point at an OS-synced folder (iCloud / Dropbox / OneDrive / Syncthing).
35
+ Zero new infrastructure; the OS handles sync.
36
+
37
+ ${a("infernoflow sync set ~/Dropbox/infernoflow-memory")}
38
+ `);return}return R({jsonOut:t})}const l=o.slice(1).filter(i=>!i.startsWith("-"));if(n==="set")return J(l[1],{jsonOut:t});if(n==="clear")return L({jsonOut:t});if(n==="migrate")return k({jsonOut:t,dryRun:r});console.error(v(`
39
+ \u2718 Unknown sync verb: ${n}
40
+ `)),console.error(e(` Run: infernoflow sync --help
41
+ `)),process.exit(1)}export{B as syncCommand};
@@ -0,0 +1,2 @@
1
+ import{execSync as h}from"node:child_process";import*as o from"node:fs";import*as n from"node:path";function l(r){return r&&r.replace(/\//g,"__").replace(/[^A-Za-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase()||"no-branch"}function y(r,t){try{return h(`git ${t}`,{cwd:r,encoding:"utf8",timeout:1500,stdio:["ignore","pipe","ignore"]}).trim()||null}catch{return null}}const f=new Map;function S(){f.clear()}function x(r){let t=n.resolve(r);for(;;){if(o.existsSync(n.join(t,".git")))return!0;const e=n.dirname(t);if(e===t)return!1;t=e}}function d(r){let t=n.resolve(r);for(;;){const e=n.join(t,".git");if(o.existsSync(e))return e;const i=n.dirname(t);if(i===t)return null;t=i}}function m(r=process.cwd()){const t=d(r);if(!t)return"no-git";let e=t;try{if(o.statSync(t).isFile()){const u=o.readFileSync(t,"utf8").trim().match(/^gitdir:\s*(.+)$/);u&&(e=n.resolve(n.dirname(t),u[1].trim()))}}catch{}let i;try{i=o.readFileSync(n.join(e,"HEAD"),"utf8").trim()}catch{return"no-branch"}const c=i.match(/^ref:\s+refs\/heads\/(.+)$/);return c?c[1]:"no-branch"}function g(r=process.cwd()){const t=d(r);if(!t)return null;let e=t;try{if(o.statSync(t).isFile()){const a=o.readFileSync(t,"utf8").trim().match(/^gitdir:\s*(.+)$/);a&&(e=n.resolve(n.dirname(t),a[1].trim()))}}catch{}try{const s=o.readFileSync(n.join(e,"refs","remotes","origin","HEAD"),"utf8").trim().match(/^ref:\s+refs\/remotes\/origin\/(.+)$/);if(s)return s[1]}catch{}let i="";try{i=o.readFileSync(n.join(e,"packed-refs"),"utf8")}catch{}for(const c of["main","master","trunk","develop","dev"])if(o.existsSync(n.join(e,"refs","heads",c))||i.includes(`refs/heads/${c}
2
+ `))return c;return null}function v(r=process.cwd()){const t=n.resolve(r),e=f.get(t);if(e)return e;const i=m(r),c=g(r),s={current:i,currentSlug:l(i),default:c,defaultSlug:c?l(c):null,isSynthetic:i==="no-git"||i==="no-branch"};return f.set(t,s),s}export{S as _resetBranchCache,v as getBranchInfo,m as getCurrentBranch,g as getDefaultBranch,x as hasGit,l as slugifyBranch};
@@ -0,0 +1 @@
1
+ import*as a from"node:fs";import*as t from"node:path";const d=["package.json","Cargo.toml","pyproject.toml","go.mod","Gemfile","composer.json","deno.json","deno.jsonc","build.gradle","build.gradle.kts","pom.xml","mix.exs"],j=[/\.sln$/i,/\.csproj$/i,/\.fsproj$/i,/\.vbproj$/i],f=new Map;function h(r){for(const n of d)if(a.existsSync(t.join(r,n)))return!0;return!1}function y(r){try{for(const n of a.readdirSync(r))for(const e of j)if(e.test(n))return!0}catch{}return!1}function x(r=process.cwd()){const n=t.resolve(r);if(f.has(n))return f.get(n);let e=n;for(;;){if(a.existsSync(t.join(e,".ai-memory"))||a.existsSync(t.join(e,"inferno")))return c(n,e);const o=t.dirname(e);if(o===e)break;e=o}for(e=n;;){if(a.existsSync(t.join(e,".git"))||h(e)||y(e))return c(n,e);const o=t.dirname(e);if(o===e)break;e=o}return c(n,n)}function c(r,n){return f.set(r,n),n}function S(){f.clear()}function g(r,n=6){const e=[],o=new Set,p=new Set(["node_modules",".git","dist","build","out","bin","obj",".next",".nuxt",".angular",".svelte-kit","coverage","vendor","target",".venv","venv","__pycache__",".pytest_cache"]);function u(s,m){if(m>n||o.has(s))return;o.add(s);let l;try{l=a.readdirSync(s,{withFileTypes:!0})}catch{return}for(const i of l)i.isDirectory()&&(p.has(i.name)||(i.name===".ai-memory"&&e.push({kind:"amp",path:t.join(s,i.name)}),i.name==="inferno"&&e.push({kind:"legacy",path:t.join(s,i.name)}),u(t.join(s,i.name),m+1)))}return u(t.resolve(r),0),e}export{S as _resetProjectRootCache,g as findAllMemoryDirs,x as findProjectRoot};
@@ -1,6 +1,10 @@
1
- import*as n from"node:fs";import*as u from"node:path";const l="<!-- infernoflow:start -->",a="<!-- infernoflow:end -->",d=[".cursorrules","CLAUDE.md",u.join(".github","copilot-instructions.md")];function m(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!!`, `not working`, `still broken`, `no change`, `same error`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.join(`
2
- `)}function y(){return[l,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",m(),"",'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',a].join(`
3
- `)}function g(e,s){const o=u.dirname(e);if(n.existsSync(o)||n.mkdirSync(o,{recursive:!0}),!n.existsSync(e))return n.writeFileSync(e,s+`
4
- `,"utf8"),{created:!0,updated:!1};const t=n.readFileSync(e,"utf8"),i=t.indexOf(l),r=t.indexOf(a);if(i===-1||r===-1){const f=s+`
1
+ import*as a from"node:fs";import*as p from"node:path";import{execSync as w}from"node:child_process";import{readEntries as b}from"./amp/io.mjs";const m="<!-- infernoflow:start -->",h="<!-- infernoflow:end -->",d=[".cursorrules","CLAUDE.md",p.join(".github","copilot-instructions.md")];function y(){const e=[];return e.push("### Memory protocol \u2014 capture as you go"),e.push(""),e.push("You have the `amp_write` MCP tool. **Use it proactively** (without the user asking) whenever you detect any of these moments. Capture the lesson in one sentence so the next AI session inherits it."),e.push(""),e.push("| When you see\u2026 | Call `amp_write` with\u2026 |"),e.push("|---|---|"),e.push("| User types `!!!`, `not working`, `still broken`, `no change`, `same error`, repeated frustration | `{type: 'attempt', msg: '<one-line: what was tried and how it failed>', file: '<active file>'}` |"),e.push("| You generate a plan, numbered steps, or `Plan:` / `Approach:` block | `{type: 'note', msg: 'Plan: <one-line summary>', file: '<focus file>'}` |"),e.push("| User picks X over Y, decides an architectural approach, or you both agree on a direction | `{type: 'decision', msg: '<X> chosen over <Y> because <reason>', file: '<file>'}` |"),e.push("| Discovered non-obvious behavior, API quirk, or `aha \u2014 turns out\u2026` moment | `{type: 'gotcha', msg: '<one-line>', file: '<file>', line: <line if known>}` |"),e.push("| User says `switching` / `back to X`, or ~30 min passed without logging | `{type: 'note', msg: 'Session snapshot \u2014 worked on: <X>. Open threads: <Y>.'}` |"),e.push(""),e.push("**Do NOT log:** typos, syntax errors, obvious bugs visible in the code, anything the next AI can infer by re-reading the file."),e.push("**Do log:** anything that taught you something non-derivable from code."),e.push("**Be concise:** entries should be one sentence. Future-you will skim them."),e.join(`
2
+ `)}function A(){return[m,"<!-- Auto-managed by infernoflow. Don't edit between these markers. -->","## Project memory (infernoflow)","",y(),"",'_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._',h].join(`
3
+ `)}function x(e){const r=e.indexOf("<!-- AMP:START -->"),o=e.indexOf("<!-- AMP:END -->");if(r===-1||o===-1||o<=r)return e;const t=e.slice(0,r).replace(/\s+$/,""),i=e.slice(o+16).replace(/^\s+/,"");return(t?t+(i?`
5
4
 
6
- `+t;return n.writeFileSync(e,f,"utf8"),{created:!1,updated:!0}}const h=t.slice(0,i),p=t.slice(r+a.length),c=h+s+p;return c===t?{created:!1,updated:!1}:(n.writeFileSync(e,c,"utf8"),{created:!1,updated:!0})}function w(e){const s=y(),o=[];for(const t of d){const i=u.join(e,t);try{const r=g(i,s);o.push({rel:t,...r})}catch(r){o.push({rel:t,error:r.message})}}return o}export{w as writeInitRuleFiles};
5
+ `:""):"")+i}function g(e,r){const o=p.dirname(e);if(a.existsSync(o)||a.mkdirSync(o,{recursive:!0}),!a.existsSync(e))return a.writeFileSync(e,r+`
6
+ `,"utf8"),{created:!0,updated:!1};let t=a.readFileSync(e,"utf8");t=x(t);const i=t.indexOf(m),c=t.indexOf(h);if(i===-1||c===-1){const u=r+`
7
+
8
+ `+t;return a.writeFileSync(e,u,"utf8"),{created:!1,updated:!0}}const l=t.slice(0,i),s=t.slice(c+h.length),n=l+r+s;return n===t?{created:!1,updated:!1}:(a.writeFileSync(e,n,"utf8"),{created:!1,updated:!0})}function D(e){const r=A(),o=[];for(const t of d){const i=p.join(e,t);try{const c=g(i,r);o.push({rel:t,...c})}catch(c){o.push({rel:t,error:c.message})}}return o}function I(e){return b(e)}function S(e,r=10){try{return w(`git log --pretty=format:"%h%x09%ad%x09%s" --date=short -n ${r}`,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split(`
9
+ `).filter(Boolean).map(t=>{const[i,c,l]=t.split(" ");return{hash:(i||"").slice(0,7),date:c||"",subject:l||""}})}catch{return[]}}function k(e,r=10,o=10){const t=I(e),i=S(e,o);t.sort((n,u)=>{const f=typeof n.ts=="number"?n.ts:Date.parse(n.ts||0);return(typeof u.ts=="number"?u.ts:Date.parse(u.ts||0))-f});const c=t.slice(0,r),l={gotcha:"\u26A0",decision:"\u2713",attempt:"\u2717",note:"\xB7",detection:"\u25CB",pattern:"\u25C7"},s=[];if(s.push(m),s.push("<!-- Auto-managed by infernoflow. Don't edit between these markers. -->"),s.push("## Project memory (infernoflow)"),s.push(""),s.push(y()),s.push(""),i.length>0){s.push("### Recent commits");for(const n of i)s.push(`- \`${n.hash}\` _${n.date}_ ${n.subject}`);s.push("")}if(c.length>0){s.push("### Recent memory");for(const n of c){const u=n.file?` (\`${n.file}${n.line?":"+n.line:""}\`)`:"",f=(n.msg||n.summary||"").replace(/\n/g," ");s.push(`- ${l[n.type]||"\xB7"} **${n.type||"note"}**${u}: ${f}`)}s.push("")}return t.length===0&&i.length===0&&s.push('_No entries yet. They\'ll appear here as you and your AI tools log them \u2014 run `infernoflow log "..."` or call `amp_write` from any MCP-aware AI._'),s.push(h),s.join(`
10
+ `)}function R(e){const r=k(e),o=[];for(const t of d){const i=p.join(e,t);try{const c=g(i,r);o.push({rel:t,...c})}catch(c){o.push({rel:t,error:c.message})}}return o}export{R as refreshRuleFilesFromMemory,D as writeInitRuleFiles};
@@ -1,3 +1,3 @@
1
- import*as o from"node:fs";import*as a from"node:path";const u=new Set(["--help","-h","--version","-v","commands","doctor","setup","init","uninstall"]);function p(t){const n=a.join(t,".ai-memory"),e=a.join(t,"inferno");return o.existsSync(n)?a.join(n,".last-cli-version"):o.existsSync(e)?a.join(e,".last-cli-version"):null}function d(t){try{return o.readFileSync(t,"utf8").trim()}catch{return""}}async function m(t,n){if(!t||n&&u.has(n))return;const e=process.cwd(),c=p(e);if(!c)return;const s=d(c);if(s===t)return;let f=!1,l=s==="";try{const{writeInitRuleFiles:i}=await import("./ruleFiles.mjs");i(e)}catch{}try{const{ensureGitignoreEntries:i}=await import("./commands/init.mjs");i(e,{silent:!0})}catch{}try{const{autoSetupMcp:i}=await import("./commands/setup.mjs"),r=i(e,{silent:!0});f=!!(r&&(r.mcpServer||r.claudeJson||r.cursorMcp||r.vscodeMcp||r.claudeSettings))}catch{}try{o.writeFileSync(c,t,"utf8")}catch{}if(f||l)try{const{gray:i}=await import("./ui/output.mjs"),r=s?`upgraded ${s} \u2192 ${t}`:`initialized for ${t}`;process.stderr.write(i(` infernoflow: ${r}, wired MCP servers + rule files
1
+ import*as o from"node:fs";import*as a from"node:path";const p=new Set(["--help","-h","--version","-v","commands","doctor","setup","init","uninstall"]);function u(t){const n=a.join(t,".ai-memory"),e=a.join(t,"inferno");return o.existsSync(n)?a.join(n,".last-cli-version"):o.existsSync(e)?a.join(e,".last-cli-version"):null}function d(t){try{return o.readFileSync(t,"utf8").trim()}catch{return""}}async function m(t,n){if(!t||n&&p.has(n))return;const e=process.cwd(),c=u(e);if(!c)return;const s=d(c);if(s===t)return;let l=!1,f=s==="";try{const{refreshRuleFilesFromMemory:i}=await import("./ruleFiles.mjs");i(e)}catch{}try{const{applyCleanTreePolicy:i}=await import("./cleanTree.mjs");i(e)}catch{}try{const{autoSetupMcp:i}=await import("./commands/setup.mjs"),r=i(e,{silent:!0});l=!!(r&&(r.mcpServer||r.claudeJson||r.cursorMcp||r.vscodeMcp||r.claudeSettings))}catch{}try{o.writeFileSync(c,t,"utf8")}catch{}if(l||f)try{const{gray:i}=await import("./ui/output.mjs"),r=s?`upgraded ${s} \u2192 ${t}`:`initialized for ${t}`;process.stderr.write(i(` infernoflow: ${r}, wired MCP servers + rule files
2
2
  `))}catch{process.stderr.write(` infernoflow: refreshed for v${t}
3
3
  `)}}export{m as runUpgradeBackfillIfNeeded};