infernoflow 0.41.0 → 0.42.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.
- package/CHANGELOG.md +12 -0
- package/dist/lib/amp/io.mjs +11 -0
- package/dist/lib/commands/ask.mjs +2 -3
- package/dist/lib/commands/init.mjs +32 -33
- package/dist/lib/commands/log.mjs +18 -16
- package/dist/lib/commands/recap.mjs +5 -6
- package/dist/lib/commands/stats.mjs +4 -5
- package/dist/lib/commands/status.mjs +7 -7
- package/dist/lib/commands/switch.mjs +10 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog — infernoflow
|
|
2
2
|
|
|
3
|
+
## 0.42.0 — 2026-05-03
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **AMP-compliant on-disk format** — infernoflow now speaks the [AI Memory Protocol v1.0](docs/protocol/PROTOCOL.md) natively. New projects get `.ai-memory/sessions.jsonl` (the AMP canonical layout) instead of `inferno/sessions.jsonl`. Entries on disk use the AMP wire format: `msg` instead of `summary`, Unix-ms integer `ts`, ULID `id` on every entry, AMP type enum (gotcha/decision/attempt/note/detection/pattern), `meta` for tool-specific extras.
|
|
7
|
+
- **Lossless round-trip for infernoflow-specific fields** — `result`, `agent: "human"`, `auto: true`, and the extra entry types (`preference`, `theme`, `handoff`, `error`) are preserved via `meta.subtype` / `meta.result` / `meta.agent` / `confidence`. Read paths translate AMP shape back to infernoflow's familiar internal shape so the rest of the codebase doesn't need to change.
|
|
8
|
+
- **AMP injection markers** — auto-update of `CLAUDE.md`, `.cursorrules`, and `.github/copilot-instructions.md` now wraps the generated section with `<!-- AMP:START -->` / `<!-- AMP:END -->` so other AMP-compliant tools can edit-in-place without trampling each other.
|
|
9
|
+
- **Backward compat** — projects with the legacy `inferno/sessions.jsonl` keep working unchanged. Both layouts are read transparently; writes always target `.ai-memory/`.
|
|
10
|
+
|
|
11
|
+
### Internal
|
|
12
|
+
- `lib/amp/io.mjs` is the single source of truth for file paths, entry shape, ULID generation, and translation. ~270 lines, zero external dependencies. Plumbing for the upcoming `@amp/core` npm publish (Phase B) and `amp_*` MCP tool aliasing (Phase C).
|
|
13
|
+
- `infernoflow amp migrate` (coming in next release) will copy legacy `inferno/sessions.jsonl` → `.ai-memory/sessions.jsonl` with shape translation. Until then, projects can stay on the legacy layout indefinitely.
|
|
14
|
+
|
|
3
15
|
## 0.41.0 — 2026-05-03
|
|
4
16
|
|
|
5
17
|
### Changed
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import*as i from"node:fs";import*as c from"node:path";const p="1.0",A={start:"<!-- AMP:START -->",end:"<!-- AMP:END -->"},S=new Set(["gotcha","decision","attempt","note","detection","pattern"]),j=new Set(["copilot","cursor","claude","windsurf","other"]);function a(e,t={}){const o=c.join(e,".ai-memory"),n=c.join(e,"inferno"),r=t.forWrite||i.existsSync(o)||!i.existsSync(n),u=r?o:n,f=r;return{root:u,isAmp:f,sessions:c.join(u,"sessions.jsonl"),config:c.join(u,f?"amp.json":"config.json"),handoff:c.join(u,f?"handoff.md":"HANDOFF.md")}}function l(e){const t=c.join(e,".ai-memory");return i.existsSync(t)||i.mkdirSync(t,{recursive:!0}),t}const y="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function h(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=y[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=y[Math.floor(Math.random()*32)];return t+o}function g(e){const t={...e.meta||{}};let o=e.type||"note";S.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result);let n;const r=e.agent;r&&j.has(r)?n=r:r&&(t.agent=r);const u=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),f=e.confidence!=null?e.confidence:e.auto?.7:void 0,s={type:o,msg:e.summary||e.msg||"",ts:u,id:e.id||`amp_${h()}`};return e.file&&(s.file=e.file),e.line&&(s.line=e.line),e.function&&(s.function=e.function),e.tags&&e.tags.length&&(s.tags=e.tags),e.source&&(s.source=e.source),n&&(s.tool=n),e.session&&(s.session=e.session),f!=null&&(s.confidence=f),Object.keys(t).length&&(s.meta=t),s}function x(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:r,agent:u,result:f,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function D(e){const{sessions:t}=a(e);return i.existsSync(t)?i.readFileSync(t,"utf8").split(`
|
|
2
|
+
`).filter(Boolean).map(o=>{try{return JSON.parse(o)}catch{return null}}).filter(Boolean).map(x):[]}function M(e,t){l(e);const{sessions:o}=a(e,{forWrite:!0}),n=g(t);return i.appendFileSync(o,JSON.stringify(n)+`
|
|
3
|
+
`,"utf8"),n}function O(e){const{config:t}=a(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function N(e,t={}){l(e);const{config:o}=a(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:p,project:t.project||c.basename(e),stack:t.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
|
|
4
|
+
`,"utf8"),!0}function E(e){const t=c.join(e,"inferno"),o=c.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=c.join(e,".ai-memory"),r=c.join(n,"sessions.jsonl");if(i.existsSync(r))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};l(e);const u=i.readFileSync(o,"utf8").split(`
|
|
5
|
+
`).filter(Boolean);let f=0;for(const s of u)try{const d=JSON.parse(s),m=g(d);i.appendFileSync(r,JSON.stringify(m)+`
|
|
6
|
+
`,"utf8"),f++}catch{}return i.writeFileSync(c.join(n,"MIGRATED.md"),`# Migrated from inferno/
|
|
7
|
+
|
|
8
|
+
Copied ${f} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
|
|
9
|
+
|
|
10
|
+
The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
|
|
11
|
+
`,"utf8"),{migrated:f,reason:"ok"}}export{A as AMP_MARKERS,p as AMP_VERSION,a as ampPaths,M as appendEntry,l as ensureAmpDir,x as fromAmp,h as generateULID,E as migrateLegacy,O as readConfig,D as readEntries,g as toAmp,N as writeDefaultConfig};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).filter(Boolean).map(r=>{try{return JSON.parse(r)}catch{return null}}).filter(Boolean):[]}function v(t,e,r,n){let i=t;r&&(i=i.filter(o=>(o.type||"note")===r));let c;return e.length>0?c=i.map(o=>({entry:o,score:M(o,e)})).filter(({score:o})=>o>0).sort((o,f)=>{if(f.score!==o.score)return f.score-o.score;const a=y[o.entry.type]??9,p=y[f.entry.type]??9;return a!==p?a-p:new Date(f.entry.ts||0)-new Date(o.entry.ts||0)}):c=i.map(o=>({entry:o,score:1})).sort((o,f)=>{const a=y[o.entry.type]??9,p=y[f.entry.type]??9;return a!==p?a-p:new Date(f.entry.ts||0)-new Date(o.entry.ts||0)}),c.slice(0,n||20)}async function J(t=[]){const e=t,r=e.indexOf("--type"),n=r!==-1?e[r+1]:null,i=e.indexOf("--limit")!==-1?e.indexOf("--limit"):e.indexOf("-n"),c=i!==-1?parseInt(e[i+1]||"20",10):15,o=e.includes("--json"),f=e.includes("--recent")||e.includes("-r"),a=e.slice(1),g=a.filter((l,u)=>!(l.startsWith("--")||u>0&&a[u-1].startsWith("--"))).join(" ").trim(),$=f?[]:j(g),R=process.cwd(),d=Y(R);if(d.length===0){if(o){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
|
|
1
|
+
import"node:fs";import*as j from"node:path";import{bold as O,cyan as S,gray as s,green as k,yellow as C,red as L}from"../ui/output.mjs";import{readEntries as W}from"../amp/io.mjs";const q="inferno",b=j.join(q,"sessions.jsonl"),d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},I={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:C,decision:k,attempt:S,preference:S,theme:S,note:s,error:L,handoff:s};function x(t){return t.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(o=>o.length>1)}function M(t,o){const i=[t.summary||"",t.type||""].join(" ").toLowerCase(),n=x(i);let l=0;for(const r of o)(t.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function _(t){if(!t)return"";const o=new Date(t),i=Date.now()-o.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:o.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function F(t,o){const i=t.type||"note",n=I[i]||"\xB7",l=N[i]||s,r=t.result?s(` [${t.result}]`):"",e=t.agent?s(` \u2014 ${t.agent}`):"",f=s(` (${_(t.ts)})`);let a=t.summary||"";if(o)for(const u of o){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(t){return W(t)}function Y(t,o,i,n){let l=t;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return o.length>0?r=l.map(e=>({entry:e,score:M(e,o)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0)-new Date(e.entry.ts||0)}),r.slice(0,n||20)}async function z(t=[]){const o=t,i=o.indexOf("--type"),n=i!==-1?o[i+1]:null,l=o.indexOf("--limit")!==-1?o.indexOf("--limit"):o.indexOf("-n"),r=l!==-1?parseInt(o[l+1]||"20",10):15,e=o.includes("--json"),f=o.includes("--recent")||o.includes("-r"),a=o.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:x(p),D=process.cwd(),y=P(D);if(y.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
|
|
3
2
|
No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
|
|
4
|
-
`));return}const m=f?
|
|
3
|
+
`));return}const m=f?y.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(y,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:y.length,matched:m.length,results:m.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${S(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),m.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of m){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const R=Object.keys(d).sort((c,g)=>d[c]-d[g]);let E=0;for(const c of R){const g=w.get(c);if(!g?.length)continue;console.log();const h=N[c]||s;console.log(h(` ${I[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:T}of g)console.log(),F(T,$),E++}console.log(),console.log(s(` ${E} result${E!==1?"s":""} from ${y.length} total entries`)),m.length===r&&y.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{z as askCommand};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as s from"node:path";import*as
|
|
2
|
-
`,"utf8"),
|
|
3
|
-
`)}function
|
|
4
|
-
`)}function
|
|
5
|
-
`)}function z(o
|
|
1
|
+
import*as i from"node:fs";import*as s from"node:path";import*as L from"node:readline";import{fileURLToPath as se}from"node:url";import{header as re,ok as g,warn as $,done as q,nextSteps as ce,bold as J,cyan as d,yellow as R,gray as A}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as le,writeAdoptionBaseline as ae,buildAdoptionReport as pe,summarizeCapabilities as fe,buildSignalsReport as de}from"./adopt.mjs";import{installCursorHooksArtifacts as ue}from"../cursorHooksInstall.mjs";import{ensureAmpDir as me,appendEntry as ye,writeDefaultConfig as ge}from"../amp/io.mjs";import{installVsCodeCopilotHooksArtifacts as we}from"../vsCodeCopilotHooksInstall.mjs";const he=s.dirname(se(import.meta.url));function ke(){return s.resolve(he,"../../templates")}function _(e,o,n=""){return new Promise(t=>{const r=n?A(` (${n})`):"";e.question(` ${o}${r}: `,l=>{t(l.trim()||n)})})}function M(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 D(e,o,n,t=!1){return i.existsSync(o)&&!n?(t||$("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),t||g("Created: "+d(s.relative(process.cwd(),o))),!0)}function je(e,o,n){i.mkdirSync(o,{recursive:!0});for(const t of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,t.name),l=s.join(o,t.name);t.isDirectory()?je(r,l,n):D(r,l,n)}}function Se(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 l={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[y,k]of Object.entries(l))t.scripts[y]||(t.scripts[y]=k,r=!0);r&&(i.writeFileSync(n,JSON.stringify(t,null,2)+`
|
|
2
|
+
`,"utf8"),o||g("Updated "+d("package.json")+" scripts"))}function G(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 ve(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)+`
|
|
3
|
+
`)}function be(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)+`
|
|
4
|
+
`)}function Ce(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)+`
|
|
5
|
+
`)}function z(e,o){const n=`# Changelog \u2014 ${o}
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
@@ -11,42 +11,41 @@ import*as i from"node:fs";import*as s from"node:path";import*as J from"node:read
|
|
|
11
11
|
## 0.1.0 \u2014 Initial release
|
|
12
12
|
|
|
13
13
|
- Project initialized with infernoflow
|
|
14
|
-
`;i.writeFileSync(
|
|
14
|
+
`;i.writeFileSync(e,n)}async function Oe(e,o){const{bold:n,cyan:t,gray:r,green:l,yellow:y,red:k}=await import("../ui/output.mjs");console.log(`
|
|
15
15
|
`+n("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
|
|
16
|
-
`),console.log(
|
|
17
|
-
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const
|
|
16
|
+
`),console.log(r(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(r(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const p=s.join(e,"inferno");i.existsSync(p)&&!o&&(console.log(y(` \u26A0 inferno/ already exists. Use --force to overwrite.
|
|
17
|
+
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const w=G(e);let j="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const b=L.createInterface({input:process.stdin,output:process.stdout});j=await new Promise(S=>{b.question(r(" What does this project do? (one line, Enter to skip): "),x=>{b.close(),S(x.trim())})})}const O={policyId:w,policyVersion:1,lite:!0,capabilities:[],intent:j||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(O,null,2)+`
|
|
18
18
|
`),i.writeFileSync(s.join(p,"capabilities.json"),JSON.stringify([],null,2)+`
|
|
19
|
-
`),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(
|
|
20
|
-
> `),
|
|
21
|
-
`+t("\u{1F525} infernoflow")+
|
|
22
|
-
`)),console.log(" "+y("\u2714")+`
|
|
23
|
-
`),console.log(" Quick commands:"),console.log(" "+
|
|
24
|
-
`)),console.log(
|
|
25
|
-
`))
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
`+y("\u2714")+" First gotcha logged!")}console.log(`
|
|
19
|
+
`),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(l(" \u2714 Created inferno/contract.json")),console.log(l(" \u2714 Created inferno/capabilities.json")),console.log(l(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+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 xe=/^(?: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,Ie=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Te=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function Ae(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:xe.test(o)?{kind:"command",value:o}:Ie.test(o)?{kind:"command",value:o}:Te.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function Pe({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?
|
|
20
|
+
> `),l=y=>new Promise(k=>{const p=L.createInterface({input:process.stdin,output:process.stdout});let w=!1;p.on("SIGINT",()=>{w=!0,p.close(),k(null)}),p.on("close",()=>{w&&k(null)}),p.question(y,j=>{p.close(),k(j)})});for(let y=0;y<2;y++){const k=await l(y===0?r:" "+o("> "));if(k===null)return console.log(),"";const p=Ae(k);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 Fe(e,o,n){const{bold:t,cyan:r,gray:l,green:y,yellow:k}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),w=s.join(e,"inferno"),j=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(w))&&!o){const x=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
|
|
21
|
+
`+t("\u{1F525} infernoflow")+l(` \u2014 already set up
|
|
22
|
+
`)),console.log(" "+y("\u2714")+" "+x+` found
|
|
23
|
+
`),console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
|
|
24
|
+
`)),i.existsSync(p)||console.log(l(" Tip: run ")+r("infernoflow amp migrate")+l(` to move legacy memory into .ai-memory/
|
|
25
|
+
`)),console.log(l(` For contracts & CI gates: infernoflow init --mode full
|
|
26
|
+
`));return}const b=G(e);console.log(`
|
|
27
|
+
`+t("\u{1F525} infernoflow")+l(` \u2014 let's get you set up (30 seconds)
|
|
28
|
+
`)),console.log(" Detected: "+r(b)+`
|
|
29
|
+
`),me(e),i.existsSync(j)||i.writeFileSync(j,"","utf8"),ge(e,{project:b,config:{autoCapture:!0}});const S=await Pe({yes:n});S&&(ye(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:S,source:"init"}),console.log(`
|
|
30
|
+
`+y("\u2714")+" First gotcha logged!")),console.log(`
|
|
32
31
|
`+y("\u2714")+` You're set up. Quick commands:
|
|
33
|
-
`),console.log(" "+
|
|
34
|
-
`)),console.log(
|
|
35
|
-
`)),console.log(
|
|
36
|
-
`))}async function
|
|
37
|
-
`),i.writeFileSync(s.join(m,"capabilities.json"),JSON.stringify({capabilities:h.map(f=>({id:f.id,description:f.description,since:new Date().toISOString().slice(0,10),source:`template:${
|
|
38
|
-
`);for(const f of h)i.writeFileSync(s.join(
|
|
32
|
+
`),console.log(" "+r('infernoflow log "..."')+l(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+l(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+l(` \u2014 session summary
|
|
33
|
+
`)),console.log(l(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
|
|
34
|
+
`)),console.log(l(` Want contracts & CI gates? Run: infernoflow init --mode full
|
|
35
|
+
`))}async function Ge(e){const o=process.cwd(),n=e.includes("--force")||e.includes("-f"),t=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),l=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),y=l==="full"||l==="contract",k=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!y&&!k){await Fe(o,n,t);return}if(e.includes("--lite")){await Oe(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 c=a?.getTemplate(w);if(!c){const f=a?a.listTemplates().map(C=>C.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";$(`Unknown template: ${w}. Available: ${f}`),process.exit(1)}const m=s.join(o,"inferno"),T=s.join(m,"scenarios");i.existsSync(m)||i.mkdirSync(m,{recursive:!0}),i.existsSync(T)||i.mkdirSync(T,{recursive:!0});const N=G(o),h=c.capabilities;i.writeFileSync(s.join(m,"contract.json"),JSON.stringify({policyId:N,policyVersion:1,capabilities:h.map(f=>f.id)},null,2)+`
|
|
36
|
+
`),i.writeFileSync(s.join(m,"capabilities.json"),JSON.stringify({capabilities:h.map(f=>({id:f.id,description:f.description,since:new Date().toISOString().slice(0,10),source:`template:${w}`}))},null,2)+`
|
|
37
|
+
`);for(const f of h)i.writeFileSync(s.join(T,`${f.id}.json`),JSON.stringify({id:`${f.id}-happy-path`,capability:f.id,description:`Happy path for ${f.description||f.id}`,steps:[{action:"invoke",target:f.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[f.id]},null,2)+`
|
|
39
38
|
`);z(s.join(m,"CHANGELOG.md"),N),i.writeFileSync(s.join(m,"CONTEXT.md"),`# ${N} \u2014 infernoflow context
|
|
40
39
|
|
|
41
|
-
> Template: ${
|
|
40
|
+
> Template: ${w} \u2014 ${c.description}
|
|
42
41
|
|
|
43
42
|
## Hint
|
|
44
|
-
${
|
|
43
|
+
${c.contextHint}
|
|
45
44
|
|
|
46
45
|
## Capabilities (${h.length})
|
|
47
46
|
${h.map(f=>`- \`${f.id}\`: ${f.description}`).join(`
|
|
48
47
|
`)}
|
|
49
|
-
`),
|
|
50
|
-
`));const ne=await
|
|
51
|
-
`)),F=await
|
|
52
|
-
`,"utf8"),u||
|
|
48
|
+
`),c.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(c.scripts).forEach(([f,C])=>console.log(` ${J(f)}: ${A(C)}`)),console.log()),q(`Initialised from template ${J(d(w))} \u2014 ${J(String(h.length))} capabilities`),console.log(),info(`Run ${d("infernoflow vibe")} to start vibe coding mode`),console.log();return}const j=e.includes("--cursor-hooks"),O=e.includes("--vscode-copilot-hooks"),b=e.includes("--report-json"),S=e.includes("--report-json-only"),x=e.includes("--report-human-only"),U=M(e,"--lang"),W=M(e,"--framework"),K=M(e,"--project-type"),u=S;S&&x&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||re("init");const v=s.join(o,"inferno"),V=s.join(o,".github","workflows");i.existsSync(v)&&!n&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),$("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const P=G(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=P,I=Y.split(",").map(a=>a.trim());if(r){let c=H(o,{language:U||void 0,framework:W||void 0,projectType:K||void 0});if(!t&&!S){const h=L.createInterface({input:process.stdin,output:process.stdout}),f=c.developmentProfile||{},C=f.detected||{};console.log(A(` Review inferred development stack (press Enter to accept detected values)
|
|
49
|
+
`));const ne=await _(h,"Language",f.language||C.language||"unknown"),te=await _(h,"Framework",f.framework||C.framework||"unknown"),ie=await _(h,"Project type",f.projectType||C.projectType||"unknown");h.close(),c=H(o,{language:ne,framework:te,projectType:ie})}const m=c.capabilities,T=fe(m);S?console.log(JSON.stringify({mode:"adopt",policyId:P,inferredCapabilities:T,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)):(console.log(),console.log(A(pe(m))),console.log(),console.log(A(de(c))),console.log(),b&&!x&&(console.log(JSON.stringify({mode:"adopt",policyId:P,inferredCapabilities:T,components:c.components,displayFields:c.displayFields,externalLibraries:c.externalLibraries,uiLayout:c.uiLayout,styling:c.styling,developmentProfile:c.developmentProfile,apiCalls:c.apiCalls},null,2)),console.log()));const N=await le(m,t);F=P,I=N.map(h=>h.id)}else if(!t){const a=L.createInterface({input:process.stdin,output:process.stdout});console.log(A(` Press Enter to accept defaults
|
|
50
|
+
`)),F=await _(a,"Project / policy name",P),I=(await _(a,"Capabilities (comma-separated)",Y)).split(",").map(m=>m.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(v,{recursive:!0}),r){const a=I.map(m=>({id:m,title:m.replace(/([A-Z])/g," $1").trim()})),c=H(o,{language:U||void 0,framework:W||void 0,projectType:K||void 0});ae(v,F,a,c),u||(g("Created: "+d("inferno/contract.json")),g("Created: "+d("inferno/capabilities.json")),g("Created: "+d("inferno/scenarios/adoption_baseline.json")),g("Created: "+d("inferno/adoption_profile.json")),g("Created: "+d("inferno/CHANGELOG.md")))}else ve(s.join(v,"contract.json"),F,I),u||g("Created: "+d("inferno/contract.json")),be(s.join(v,"capabilities.json"),I),u||g("Created: "+d("inferno/capabilities.json")),Ce(s.join(v,"scenarios"),I),u||g("Created: "+d("inferno/scenarios/happy_path.json")),z(s.join(v,"CHANGELOG.md"),F),u||g("Created: "+d("inferno/CHANGELOG.md"));const E=ke(),X=s.join(E,"scripts","inferno-doc-gate.mjs"),B=s.join(o,"scripts","inferno-doc-gate.mjs");D(X,B,n,u);const Z=s.join(E,"scripts","inferno-install-hooks.mjs"),Q=s.join(o,"scripts","inferno-install-hooks.mjs");D(Z,Q,n,u);const ee=s.join(E,"ci","github-inferno-check.yml"),oe=s.join(V,"infernoflow-check.yml");if(D(ee,oe,n,u),Se(o,u),j&&ue({cwd:o,templatesRoot:E,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||$(a)}}),O&&we({cwd:o,templatesRoot:E,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||$(a)}}),r){const a=s.join(v,"context-state.json");let c={};try{c=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const m=H(o,{language:U||void 0,framework:W||void 0,projectType:K||void 0});c.stack=m.developmentProfile,i.writeFileSync(a,JSON.stringify(c,null,2)+`
|
|
51
|
+
`,"utf8"),u||g("Created: "+d("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(v,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${R("\u{1F4A1}")} ${J("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${d("infernoflow ai setup")} \u2014 takes 60 seconds`)),ce([d("infernoflow status")+" \u2014 see your contract at a glance",d("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+R("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+R("inferno/scenarios/*.json")+" files for edge cases","Add "+d("inferno:check")+" to your CI pipeline",...j?["Restart Cursor \u2014 hooks write assistant text to "+R("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...O?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+R("inferno/CONTEXT.draft.md"),"Promote when ready: "+d("npm run inferno:promote-draft -- --append-notes")]:[],...!j&&!O?["Optional: "+d("infernoflow install-cursor-hooks")+" or "+d("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Ge as initCommand};
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import*as l from"node:fs";import*as p from"node:path";import"node:os";import{bold as N,cyan as A,gray as t,green as D,red as b}from"../ui/output.mjs";import{readCredentials as R,isLoggedIn as _}from"../cloud/credentials.mjs";import{pushEntry as C}from"../cloud/supabase.mjs";import{ampPaths as P,appendEntry as U,readEntries as F,AMP_MARKERS as E}from"../amp/io.mjs";function L(){return P(process.cwd())}function W(){try{const e=F(process.cwd()),a=e.filter(n=>n.type==="gotcha"),c=e.filter(n=>n.type==="decision"),r=e.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial")),s=["# Project Context (auto-generated by infernoflow)","",`> Last updated: ${new Date().toISOString()}`,""];if(a.length){s.push("## \u26A0\uFE0F Known Gotchas (Read These First)","");for(const n of a)s.push(`- ${n.summary}`);s.push("")}if(c.length){s.push("## \u2713 Decisions In Effect","");for(const n of c)s.push(`- ${n.summary}`);s.push("")}if(r.length){s.push("## \u274C Things That Don't Work (Don't Try These)","");for(const n of r)s.push(`- ${n.summary}`);s.push("")}const d=s.join(`
|
|
2
|
+
`),m=`${E.start}
|
|
3
|
+
${d}
|
|
4
|
+
${E.end}
|
|
5
|
+
`,u=process.cwd(),h=n=>{let i="";try{i=l.readFileSync(n,"utf8")}catch{}const $=i.indexOf(E.start),w=i.indexOf(E.end);let x;$!==-1&&w!==-1&&w>$?x=i.slice(0,$)+m+i.slice(w+E.end.length).replace(/^\n+/,""):i?x=i.replace(/\s+$/,"")+`
|
|
6
|
+
|
|
7
|
+
`+m:x=m,l.writeFileSync(n,x,"utf8")},I=p.join(u,"CLAUDE.md");l.existsSync(I)&&h(I);const j=p.join(u,".cursorrules");(l.existsSync(j)||l.existsSync(p.join(u,".cursor")))&&h(j);const S=p.join(u,".github","copilot-instructions.md");l.existsSync(p.join(u,".github"))&&h(S)}catch{}}const T=["note","attempt","decision","gotcha","preference","theme","handoff","error"],v=["worked","failed","partial","unknown"];function V(){return F(process.cwd())}function q(e,{auto:a=!1,quiet:c=!1}={}){const r=process.cwd(),s=p.join(r,".ai-memory"),d=p.join(r,"inferno");return a&&!l.existsSync(s)&&!l.existsSync(d)?!1:(!l.existsSync(s)&&!l.existsSync(d)&&(c||console.error(b(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
|
|
8
|
+
`)),process.exit(1)),U(r,e),!0)}function G(){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 M(e,a){const c=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),r=e.type||"note",s=r==="gotcha"?"\x1B[33m":r==="decision"?"\x1B[36m":r==="theme"?"\x1B[35m":r==="preference"?"\x1B[34m":r==="attempt"?"\x1B[90m":r==="error"?"\x1B[31m":"\x1B[0m",d="\x1B[0m",m=e.result?` [${e.result}]`:"",u=e.agent&&e.agent!=="human"?t(` (${e.agent})`):"";return` ${t(String(a+1).padStart(3))} ${t(c)} ${s}${r}${d}${m} ${e.summary}${u}`}async function H(e){const a=o=>e.includes(o),c=(o,f)=>{const g=e.indexOf(o);return g!==-1&&e[g+1]?e[g+1]:f},r=a("--show"),s=a("--clear"),d=a("--json"),m=a("--auto"),u=a("--quiet"),h=c("--source",null);if(r||d){const o=V(),f=e[e.indexOf("--show")+1],g=f&&/^\d+$/.test(f)?parseInt(f):20,y=o.slice(-g);if(d){console.log(JSON.stringify(y,null,2));return}if(console.log(`
|
|
9
|
+
`+N("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!y.length){console.log(t(`
|
|
8
10
|
No entries yet. Start logging with: infernoflow log "<what happened>"
|
|
9
|
-
`));return}console.log(
|
|
10
|
-
`)),
|
|
11
|
-
`));return}const
|
|
12
|
-
`));return}const
|
|
11
|
+
`));return}console.log(t(` Showing last ${y.length} of ${o.length} entries
|
|
12
|
+
`)),y.forEach((O,k)=>console.log(M(O,o.length-y.length+k))),console.log();return}if(s){const{sessions:o}=L();if(!l.existsSync(o)){console.log(t(` Nothing to clear.
|
|
13
|
+
`));return}const f=o.replace(".jsonl",`-archive-${Date.now()}.jsonl`);l.renameSync(o,f),console.log(D(` \u2714 Session log archived \u2192 ${p.basename(f)}
|
|
14
|
+
`));return}const I=new Set([c("--type",""),c("--result",""),c("--agent",""),c("--source","")].filter(Boolean)),S=e.slice(1).filter(o=>!o.startsWith("--")&&!I.has(o)).join(" ").trim();if(!S){console.log(`
|
|
13
15
|
`+N("\u{1F525} infernoflow log")+` \u2014 append to session memory
|
|
14
|
-
`),console.log(
|
|
15
|
-
`));return}const
|
|
16
|
-
`)),process.exit(1)),
|
|
17
|
-
`)),process.exit(1));const
|
|
18
|
-
`)}}}export{
|
|
16
|
+
`),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>
|
|
17
|
+
`));return}const n=c("--type","note"),i=c("--result",null),$=c("--agent",G());T.includes(n)||(u||console.error(b(` \u2718 Invalid type: ${n}. Valid: ${T.join(", ")}
|
|
18
|
+
`)),process.exit(1)),i&&!v.includes(i)&&(u||console.error(b(` \u2718 Invalid result: ${i}. Valid: ${v.join(", ")}
|
|
19
|
+
`)),process.exit(1));const w={ts:new Date().toISOString(),agent:$,type:n,summary:S,...i?{result:i}:{},...h?{source:h}:{},...m?{auto:!0}:{}};if(q(w,{auto:m,quiet:u})){W();try{if(_()){const o=R(),f=o.user_token||o.user?.id||o.user?.login||"anonymous",g=(()=>{try{const{config:y}=L(),O=JSON.parse(l.readFileSync(y,"utf8"));return O.project||O.projectId||p.basename(process.cwd())}catch{return p.basename(process.cwd())}})();await C(w,f,g)}}catch{}if(!u){const o=n!=="note"?A(` [${n}]`):"",f=i?t(` \u2192 ${i}`):"",g=h?t(` (via ${h})`):"";console.log(D(` \u2714 Logged${o}${f}${g}: `)+S+`
|
|
20
|
+
`)}}}export{H as logCommand};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`),...
|
|
3
|
-
`),...
|
|
4
|
-
`)].map(
|
|
5
|
-
`)),process.exit(1));const y=
|
|
6
|
-
`).filter(Boolean).forEach(r=>{try{y.push(JSON.parse(r))}catch{}});const p=R(y,w),d=y.filter(r=>new Date(r.ts||0)>p),D=A(p),h=_(D,d),{score:i,checks:E}=J(d),N=P(b.join(a,B));if(o){console.log(JSON.stringify({sessionStart:p.toISOString(),entries:d,changedFiles:D,unloggedTopics:h,health:{score:i,checks:E}},null,2));return}if(t){const r=i>=80?"A":i>=60?"B":i>=40?"C":"D",g=i>=60?S:i>=40?k:C;console.log(g(`Session health: ${r} (${i}/100)`)+n(` \u2014 ${d.length} entries logged`)),h.length&&console.log(k(` ${h.length} topic${h.length!==1?"s":""} changed but not logged: `)+h.map(l=>l.topic).join(", "));return}const $=n(" "+"\u2500".repeat(52));console.log(),console.log(" "+I("\u{1F525} infernoflow recap")),N?.policyId&&console.log(n(` Project: ${N.policyId}`));const F=p.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(n(` Session since: ${F}`)),console.log($),console.log(),console.log(" "+I("Captured this session")),console.log(),d.length===0)console.log(n(" Nothing logged yet this session."));else{const r=["gotcha","decision","attempt","preference","theme","note","error"],g=new Map;for(const l of d){const f=l.type||"note";g.has(f)||g.set(f,[]),g.get(f).push(l)}for(const l of r){const f=g.get(l);if(f?.length)for(const L of f)console.log(),Y(L)}}if(h.length>0){console.log(),console.log($),console.log(),console.log(" "+I("Changed but not logged")+n(" (git diff since session start)")),console.log();for(const{topic:r,files:g}of h){console.log(k(` ? ${r}`));for(const l of g)console.log(n(` ${l}`))}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 D.length>0&&(console.log(),console.log($),console.log(),console.log(S(" \u2714 ")+n(`${D.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log($),console.log(),console.log(" "+I("Session health")),console.log();const T=i>=80?"A":i>=60?"B":i>=40?"C":"D",x=i>=60?S:i>=40?k:C;console.log(` ${x(I(`${T}`))} ${x(`${i}/100`)}`),console.log();for(const{ok:r,label:g}of E){const l=r?S(" \u2714"):k(" \xB7");console.log(`${l} ${r?g:n(g)}`)}{const r=d.filter(f=>f.type==="gotcha").length,g=d.filter(f=>f.type==="decision").length,l=[];if(r===0?l.push(u('infernoflow log "..." --type gotcha')+n(" \u2014 adds 35 pts")):r<3&&i<80&&l.push(n(` ${3-r} more gotcha(s) would push you higher`)),g===0&&l.push(u('infernoflow log "..." --type decision')+n(" \u2014 adds 25 pts")),i>=60&&i<80&&l.push(n(" Almost B! One more entry gets you there.")),i>=80&&l.push(S(" Great session \u2014 your handoff will be excellent.")),l.length){console.log(),console.log(n(" How to improve:"));for(const f of l)console.log(" "+f)}}(d.length>0||h.length>0)&&(console.log(),console.log($),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{K as recapCommand};
|
|
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(`
|
|
2
|
+
`),...m.split(`
|
|
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};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`)),process.exit(1));const n=L(c);if(e){console.log(JSON.stringify(n,null,2));return}if(a){const l=[];n.memory.total&&l.push(`${n.memory.total} memory entries`),n.context.estimatedTokens&&l.push(`${d(n.context.estimatedTokens)} tokens/session`),n.coverage.total&&l.push(`${n.coverage.pct}% capability coverage`),n.savings.estimatedTokens&&l.push(`${d(n.savings.estimatedTokens)} tokens saved`),console.log(l.join(" \xB7 ")||"No data yet \u2014 run infernoflow init + infernoflow log");return}P(n)}export{D as statsCommand};
|
|
1
|
+
import*as d from"node:fs";import*as a from"node:path";import{bold as g,cyan as b,gray as t,green as u,yellow as E,red as j}from"../ui/output.mjs";import{ampPaths as I,readEntries as N}from"../amp/io.mjs";const y="inferno";function $(){return I(process.cwd()).sessions}const O=a.join(y,"CONTEXT.md"),A=a.join(y,"theme.json"),C=a.join(y,"scan.json"),M=a.join(y,"contract.json"),P=a.join(y,"capabilities.json");function v(o){try{return JSON.parse(d.readFileSync(o,"utf8"))}catch{return null}}function L(o){return Math.ceil((o||"").length/4)}const x={gotcha:400,decision:200,attempt:250,preference:150,note:100,theme:300,handoff:500,error:200};function F(o){const e={ok:!1,memory:{total:0,byType:{},oldestEntry:null,newestEntry:null,sessionsTracked:0},context:{sizeBytes:0,estimatedTokens:0,hasIntent:!1,hasWorking:!1},theme:{captured:!1,fonts:0,colors:0,cssVars:0,framework:null},coverage:{total:0,withAnalysis:0,pct:0},chains:{total:0,resolved:0},contract:{policyId:null,capabilities:0,isLite:!1},savings:{estimatedTokens:0,breakdown:{}}},i=v(a.join(o,M));i&&(e.contract.policyId=i.policyId,e.contract.capabilities=(i.capabilities||[]).length,e.contract.isLite=!!i.lite,e.ok=!0);const c=v(a.join(o,P));if(c){const s=Array.isArray(c)?c:c.capabilities||[];e.coverage.total=s.length,e.coverage.withAnalysis=s.filter(m=>m.codeAnalysis).length,e.coverage.pct=e.coverage.total?Math.round(e.coverage.withAnalysis/e.coverage.total*100):0}const n=$();if(d.existsSync(n)){const s=N(process.cwd());e.memory.total=s.length;for(const k of s){const h=k.type||"note";e.memory.byType[h]=(e.memory.byType[h]||0)+1;const T=x[h]||100;e.savings.estimatedTokens+=T,e.savings.breakdown[h]=(e.savings.breakdown[h]||0)+T}s.length&&(e.memory.oldestEntry=s[0].ts,e.memory.newestEntry=s[s.length-1].ts);const m=new Set(s.map(k=>{const h=k.ts;return typeof h=="number"?new Date(h).toISOString().slice(0,10):(h||"").slice(0,10)}));e.memory.sessionsTracked=m.size}const l=a.join(o,O);if(d.existsSync(l)){const s=d.readFileSync(l,"utf8");e.context.sizeBytes=Buffer.byteLength(s,"utf8"),e.context.estimatedTokens=L(s),e.context.hasIntent=s.includes("## Intent"),e.context.hasWorking=s.includes("## Working on")}const r=v(a.join(o,A));r&&(e.theme.captured=!0,e.theme.fonts=Object.keys(r.fonts||{}).filter(s=>r.fonts[s]).length,e.theme.colors=Object.keys(r.colors?.palette||{}).length,e.theme.cssVars=Object.keys(r.cssVars||{}).length,e.theme.framework=r.framework||null);const f=v(a.join(o,C));if(f?.httpChains){const s=Object.values(f.httpChains).flat();e.chains.total=s.length,e.chains.resolved=s.filter(m=>m.resolved).length}return e}function w(o,e,i=20){const c=e>0?Math.round(o/e*i):0;return"\u2588".repeat(c)+"\u2591".repeat(i-c)}function S(o){return o>=80?u:o>=40?E:j}function B(o){if(!o)return"never";const e=new Date(o),c=Date.now()-e.getTime(),n=Math.floor(c/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function p(o){return o>=1e3?`~${Math.round(o/100)/10}k`:`~${o}`}function D(o){const e=t(" "+"\u2500".repeat(52));console.log(),console.log(" "+g("\u{1F525} infernoflow stats")),o.contract.policyId&&console.log(t(` Project: ${o.contract.policyId}${o.contract.isLite?" (lite)":""}`)),console.log(e),console.log(),console.log(" "+g("Session memory")+t(" ("+a.relative(process.cwd(),$())+")")),console.log();const i=o.memory.total;if(i===0)console.log(t(' No entries yet \u2014 run: infernoflow log "<what happened>" --type gotcha'));else{const n=["gotcha","decision","attempt","preference","theme","note","handoff","error"],l=Math.max(...Object.values(o.memory.byType));for(const r of n){const f=o.memory.byType[r]||0;if(f===0)continue;const s=w(f,l,16),m=r.padEnd(12);console.log(` ${t(m)} ${b(s)} ${f}`)}console.log(),console.log(t(" Total entries: ")+g(i)),console.log(t(" Sessions tracked: ")+o.memory.sessionsTracked),o.memory.newestEntry&&console.log(t(" Last entry: ")+B(o.memory.newestEntry))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Context injection")+t(" (per session start)")),console.log(),o.context.sizeBytes===0?console.log(t(" No CONTEXT.md yet \u2014 run: infernoflow context")):(console.log(t(" Size: ")+`${Math.round(o.context.sizeBytes/1024*10)/10} KB`),console.log(t(" Tokens: ")+g(p(o.context.estimatedTokens))+t(" injected into every session")),o.context.hasIntent&&console.log(t(" ")+u("\u2714")+t(" Intent captured")),o.context.hasWorking&&console.log(t(" ")+u("\u2714")+t(" Working state captured"))),console.log(),console.log(e),console.log(),console.log(" "+g("Capability coverage")+t(" (code analysis via infernoflow scan)")),console.log(),o.coverage.total===0)console.log(t(" No capabilities yet \u2014 run: infernoflow init"));else{const n=S(o.coverage.pct),l=w(o.coverage.withAnalysis,o.coverage.total,24);if(console.log(` ${n(l)} ${g(o.coverage.pct+"%")} (${o.coverage.withAnalysis}/${o.coverage.total})`),o.coverage.pct<100){const r=o.coverage.total-o.coverage.withAnalysis;console.log(t(`
|
|
2
|
+
${r} capabilities without code analysis`)),console.log(t(" Run: infernoflow scan to enrich them"))}}if(o.chains.total>0){console.log(),console.log(e),console.log(),console.log(" "+g("HTTP call chains")+t(" (end-to-end resolution)")),console.log();const n=Math.round(o.chains.resolved/o.chains.total*100),l=S(n),r=w(o.chains.resolved,o.chains.total,20);console.log(` ${l(r)} ${g(n+"%")} resolved (${o.chains.resolved}/${o.chains.total} call chains)`),o.chains.resolved<o.chains.total&&console.log(t(`
|
|
3
|
+
Unresolved calls may be to external services or missing route files`))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Design system")+t(" (inferno/theme.json)")),console.log(),!o.theme.captured)console.log(t(" Not captured yet \u2014 run: infernoflow theme"));else{const n=[];o.theme.fonts&&n.push(`${o.theme.fonts} font${o.theme.fonts!==1?"s":""}`),o.theme.colors&&n.push(`${o.theme.colors} colors`),o.theme.cssVars&&n.push(`${o.theme.cssVars} CSS vars`),o.theme.framework&&n.push(`${o.theme.framework}`),console.log(t(" ")+u("\u2714")+" "+n.join(" \xB7 ")),console.log(t(" AI agents always use the correct fonts and colors for this project"))}console.log(),console.log(e),console.log(),console.log(" "+g("Estimated token savings")+t(" (vs re-discovering from scratch)")),console.log();const c=o.savings.estimatedTokens;if(c===0)console.log(t(" No session entries yet \u2014 start logging to track savings"));else{const n=Math.max(o.memory.sessionsTracked,1),l=Math.round(c/n);console.log(" Total saved: "+g(u(p(c)+" tokens"))),console.log(" Per session: "+g(p(l)+" tokens")),console.log(),console.log(t(" Breakdown:"));const r=["gotcha","handoff","attempt","decision","theme","preference","note","error"];for(const f of r){const s=o.savings.breakdown[f];if(!s)continue;const m=o.memory.byType[f]||0;console.log(t(` ${f.padEnd(12)} ${m}\xD7 \xD7 ${x[f]||100} = `)+b(p(s)))}console.log(),console.log(t(" * Estimates based on typical back-and-forth cost per entry type.")),console.log(t(" Actual savings vary with model, project complexity, and session length."))}console.log(),console.log(e),console.log()}async function R(o=[]){const e=o.includes("--json"),i=o.includes("--brief"),c=process.cwd();!d.existsSync(a.join(c,y))&&!d.existsSync(a.join(c,".ai-memory"))&&(console.error(j(` \u2718 not initialized \u2014 run: infernoflow init
|
|
4
|
+
`)),process.exit(1));const n=F(c);if(e){console.log(JSON.stringify(n,null,2));return}if(i){const l=[];n.memory.total&&l.push(`${n.memory.total} memory entries`),n.context.estimatedTokens&&l.push(`${p(n.context.estimatedTokens)} tokens/session`),n.coverage.total&&l.push(`${n.coverage.pct}% capability coverage`),n.savings.estimatedTokens&&l.push(`${p(n.savings.estimatedTokens)} tokens saved`),console.log(l.join(" \xB7 ")||"No data yet \u2014 run infernoflow init + infernoflow log");return}D(n)}export{R as statsCommand};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`).filter(Boolean).map(
|
|
3
|
-
${
|
|
4
|
-
`);return}
|
|
5
|
-
${
|
|
6
|
-
${
|
|
7
|
-
`).filter(
|
|
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 v,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(h=>h.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"),h=l.join(f,".ai-memory");s||L("status"),!o.existsSync(i)&&!o.existsSync(h)&&(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(h,"amp.json"),k=l.join(i,"config.json"),w=l.join(i,"contract.json"),D=o.existsSync(M)||o.existsSync(h),H=(()=>{try{return JSON.parse(o.readFileSync(k,"utf8")).mode||null}catch{return null}})();if(D||H==="memory"||!o.existsSync(w)&&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,y=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:y,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(y))}`),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:")} ${v('infernoflow log "..." --type gotcha')}`):console.log(` ${d("\u25CF")} ${r(d("ready"))} ${n("\u2014 run")} ${v("infernoflow recap")} ${n("for the full session summary")}`),console.log(`
|
|
3
|
+
${n("Want capability contracts + CI gates? Run:")} ${v("infernoflow init --mode full")}
|
|
4
|
+
`);return}o.existsSync(w)||(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 S=JSON.parse(o.readFileSync(w,"utf8")),C=S.capabilities||[],F=o.statSync(w),N=l.join(i,"scenarios"),O=l.join(i,"CHANGELOG.md"),P=l.join(i,"capabilities.json"),{covered:I,uncovered:j}=z(N,C),G=o.existsSync(O)&&/##\s+Unreleased/i.test(o.readFileSync(O,"utf8")),x=[];j.length>0&&x.push(`${j.length} capabilities without scenario coverage`),G||x.push("CHANGELOG missing ## Unreleased section");const b=x.length===0;if(s){const t={ok:b,driftReasons:x,project:{policyId:S.policyId||null,policyVersion:S.policyVersion||null,lastChange:E(F.mtimeMs)},capabilities:{total:C.length,uncovered:j},changelog:{hasUnreleased:G}};console.log(JSON.stringify(t,null,2)),process.exit(b?0:1)}b||($("Drift"),x.forEach(t=>console.log(` ${m("\u26A0")} ${t}`))),$("Project"),console.log(` ${n("policy")} ${r(S.policyId||"\u2014")}`),console.log(` ${n("version")} ${r("v"+(S.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],y=I.includes(t)?d("\u2714"):R("\u2718"),u=e?.title?n(` \u2014 ${e.title}`):"",p=e?.since?n(` [${e.since}]`):"";console.log(` ${y} ${W(t)}${u}${p}`)}),j.length>0?console.log(`
|
|
5
|
+
${m("\u26A0")} ${j.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")),y=c.steps?.length||0,u=(c.capabilitiesCovered||[]).length;console.log(` ${d("\u2714")} ${v(e)} ${n(`\u2014 ${y} steps, ${u} caps covered`)}`)}catch{console.log(` ${R("\u2718")} ${v(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,13 +1,11 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`)
|
|
3
|
-
`)){const
|
|
4
|
-
`).filter(Boolean):[]}catch{return[]}}function it(e){const i=[],l=e.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial"||!n.result));for(const n of l)e.find(s=>s.type==="attempt"&&s.result==="worked"&&new Date(s.ts)>new Date(n.ts)&&s.summary.toLowerCase().includes(n.summary.split(" ")[0].toLowerCase()))||i.push({text:n.summary,ts:n.ts,kind:"unresolved-attempt"});for(const n of e)/\b(TODO|WIP|FIXME|BLOCKED|pending)\b/i.test(n.summary)&&(i.find(c=>c.text===n.summary)||i.push({text:n.summary,ts:n.ts,kind:"flagged"}));return i.slice(0,8)}function ft(e,i,l){const n=u(J)||{},c=u(K)||{},s=u(X),C=u(q),w=Q(),a=Y(w,i,l),_=w.filter(o=>new Date(o.ts||0)>a),T=w.slice(-5),f=new Date,N=f.toLocaleString("en-GB",{day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}),$=c.policyId||O.basename(process.cwd()),j=c.policyVersion||"?",v=(c.capabilities||[]).slice(0,20),b=Z(),H=a.getTime()>0?f-a:-1,k=z(H),x=a.getTime()>0?a.getTime().toString(16).slice(-6).toUpperCase():"ALL",D=tt(a.getTime()>0?a:null),E=at(),G=st(a.getTime()>0?a:null),r=_.length>0?_:T,g=r.filter(o=>o.type==="gotcha"),R=r.filter(o=>o.type==="decision"),M=r.filter(o=>o.type==="attempt").filter(o=>o.result==="failed"||o.result==="partial"),A=r.filter(o=>o.type==="preference"),d=r.slice(-8),B=it(r),W=a.getTime()===0?"all time":a.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),h=["sessions.jsonl"];(n.working||n.intent)&&h.push("context-state.json"),s&&h.push("theme.json"),c.capabilities?.length&&h.push("contract.json"),C&&h.push("adoption_profile.json"),D.length&&h.push("git log");const t=[`# \u{1F525} infernoflow Handoff \u2014 ${$}`,`> Generated: ${N}${e?` | Handing off to: **${e}**`:""}`,`> Session: **#${x}** \xB7 ${k} \xB7 **${_.length} entries** since ${W}`,`> Sources: ${h.join(" \xB7 ")}${b?` \xB7 IDE: ${b}`:""}`,"","---","","## Pick up here",""];if(n.working||n.intent?(n.working&&t.push(`**Working on:** ${n.working} _(${F(n.workingUpdated)})_`),n.intent&&t.push(`**Intent:** ${n.intent} _(${F(n.intentUpdated)})_`),t.push("")):t.push('_No working state set. Run: `infernoflow context --working "..."` to set it._',""),B.length){t.push("## \u{1F513} Open threads \u2014 not yet resolved","");for(const o of B){const p=o.kind==="flagged"?"[flagged]":"[unresolved]";t.push(`- ${p} ${o.text} _(${F(o.ts)})_`)}t.push("")}if(g.length){t.push("## \u26A0 Gotchas \u2014 read these first","");for(const o of g)t.push(`- ${o.summary} _(${F(o.ts)})_`);t.push("")}if(R.length){t.push("## Decisions made","");for(const o of R){const p=o.result?` \u2192 **${o.result}**`:"";t.push(`- ${o.summary}${p} _(${F(o.ts)})_`)}t.push("")}if(M.length){t.push("## \u2717 Already tried \u2014 don't repeat","");for(const o of M)t.push(`- ${o.summary} _(${F(o.ts)})_`);t.push("")}if(G.length){t.push("## \u{1F4C1} Hot Files This Session","");for(const{file:o,edits:p}of G)t.push(`- \`${o}\` \u2014 ${p} edit${p!==1?"s":""}`);t.push("")}if(A.length){t.push("## Developer preferences","");for(const o of A)t.push(`- ${o.summary}`);t.push("")}if(D.length||E){if(t.push("## Git activity this session",""),D.length){t.push("**Commits:**");for(const o of D)t.push(`- \`${o}\``);t.push("")}E&&(t.push("**Uncommitted changes:**"),t.push("```"),t.push(E.split(`
|
|
1
|
+
import*as E from"node:fs";import*as g from"node:path";import"node:os";import{execSync as w}from"node:child_process";import{bold as W,cyan as S,gray as et,green as ot,yellow as B,red as it}from"../ui/output.mjs";import{ampPaths as rt,readEntries as ct,appendEntry as lt}from"../amp/io.mjs";const I="inferno";function V(){return rt(process.cwd())}const at=g.join(I,"HANDOFF.md"),wt=g.join(I,"sessions.jsonl"),J=g.join(I,"context-state.json"),z=g.join(I,"contract.json"),K=g.join(I,"theme.json"),X=g.join(I,"adoption_profile.json");function f(o){try{return JSON.parse(E.readFileSync(o,"utf8"))}catch{return null}}function pt(o){try{return E.readFileSync(o,"utf8")}catch{return null}}function F(o){return o?new Date(o).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}):"unknown"}function q(o){if(o<0)return"unknown";const i=Math.floor(o/36e5),l=Math.floor(o%36e5/6e4);return i>0?`${i}h ${l}m`:`${l}m`}function Q(){return ct(process.cwd())}function Y(o,i,l){if(l)return new Date(0);if(i){const n=i.match(/^(\d+)h$/i),c=i.match(/^(\d+)d$/i);if(n)return new Date(Date.now()-parseInt(n[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 n=o.length-1;n>=0;n--)if(o[n].type==="handoff"){const c=new Date(o[n].ts||0),s=new Date(Date.now()-864e5);return c>s?c:s}return new Date(Date.now()-864e5)}function ft(o){try{const i=process.platform;if(i==="win32")w("clip",{input:o});else if(i==="darwin")w("pbcopy",{input:o});else try{w("xclip -selection clipboard",{input:o})}catch{w("xsel --clipboard --input",{input:o})}return!0}catch{return!1}}function Z(){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 o=f(X);return o?.ide?o.ide:null}function ut(){try{const o=w("git diff --stat HEAD 2>/dev/null || git diff --cached --stat 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();return o||w("git log --stat -1 --pretty= 2>/dev/null",{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()||null}catch{return null}}function nt(o){try{const i=o&&o.getTime()>0?`--after="${o.toISOString()}"`:"-10",l=w(`git log ${i} --name-only --pretty=format: 2>/dev/null`,{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim();if(!l)return[];const n={};for(const c of l.split(`
|
|
2
|
+
`)){const s=c.trim();s&&(n[s]=(n[s]||0)+1)}return Object.entries(n).sort((c,s)=>s[1]-c[1]).slice(0,5).map(([c,s])=>({file:c,edits:s}))}catch{return[]}}function tt(o){try{const i=o?`--after="${o.toISOString()}"`:"-5",l=w(`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 st(o){const i=[],l=o.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial"||!n.result));for(const n of l)o.find(s=>s.type==="attempt"&&s.result==="worked"&&new Date(s.ts)>new Date(n.ts)&&s.summary.toLowerCase().includes(n.summary.split(" ")[0].toLowerCase()))||i.push({text:n.summary,ts:n.ts,kind:"unresolved-attempt"});for(const n of o)/\b(TODO|WIP|FIXME|BLOCKED|pending)\b/i.test(n.summary)&&(i.find(c=>c.text===n.summary)||i.push({text:n.summary,ts:n.ts,kind:"flagged"}));return i.slice(0,8)}function dt(o,i,l){const n=f(J)||{},c=f(z)||{},s=f(K),b=f(X),$=Q(),a=Y($,i,l),k=$.filter(e=>new Date(e.ts||0)>a),H=$.slice(-5),D=new Date,u=D.toLocaleString("en-GB",{day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}),_=c.policyId||g.basename(process.cwd()),j=c.policyVersion||"?",O=(c.capabilities||[]).slice(0,20),x=Z(),R=a.getTime()>0?D-a:-1,N=q(R),A=a.getTime()>0?a.getTime().toString(16).slice(-6).toUpperCase():"ALL",T=tt(a.getTime()>0?a:null),C=ut(),G=nt(a.getTime()>0?a:null),m=k.length>0?k:H,r=m.filter(e=>e.type==="gotcha"),h=m.filter(e=>e.type==="decision"),M=m.filter(e=>e.type==="attempt").filter(e=>e.result==="failed"||e.result==="partial"),P=m.filter(e=>e.type==="preference"),L=m.slice(-8),d=st(m),U=a.getTime()===0?"all time":a.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),y=["sessions.jsonl"];(n.working||n.intent)&&y.push("context-state.json"),s&&y.push("theme.json"),c.capabilities?.length&&y.push("contract.json"),b&&y.push("adoption_profile.json"),T.length&&y.push("git log");const t=[`# \u{1F525} infernoflow Handoff \u2014 ${_}`,`> Generated: ${u}${o?` | Handing off to: **${o}**`:""}`,`> Session: **#${A}** \xB7 ${N} \xB7 **${k.length} entries** since ${U}`,`> Sources: ${y.join(" \xB7 ")}${x?` \xB7 IDE: ${x}`:""}`,"","---","","## Pick up here",""];if(n.working||n.intent?(n.working&&t.push(`**Working on:** ${n.working} _(${F(n.workingUpdated)})_`),n.intent&&t.push(`**Intent:** ${n.intent} _(${F(n.intentUpdated)})_`),t.push("")):t.push('_No working state set. Run: `infernoflow context --working "..."` to set it._',""),d.length){t.push("## \u{1F513} Open threads \u2014 not yet resolved","");for(const e of d){const p=e.kind==="flagged"?"[flagged]":"[unresolved]";t.push(`- ${p} ${e.text} _(${F(e.ts)})_`)}t.push("")}if(r.length){t.push("## \u26A0 Gotchas \u2014 read these first","");for(const e of r)t.push(`- ${e.summary} _(${F(e.ts)})_`);t.push("")}if(h.length){t.push("## Decisions made","");for(const e of h){const p=e.result?` \u2192 **${e.result}**`:"";t.push(`- ${e.summary}${p} _(${F(e.ts)})_`)}t.push("")}if(M.length){t.push("## \u2717 Already tried \u2014 don't repeat","");for(const e of M)t.push(`- ${e.summary} _(${F(e.ts)})_`);t.push("")}if(G.length){t.push("## \u{1F4C1} Hot Files This Session","");for(const{file:e,edits:p}of G)t.push(`- \`${e}\` \u2014 ${p} edit${p!==1?"s":""}`);t.push("")}if(P.length){t.push("## Developer preferences","");for(const e of P)t.push(`- ${e.summary}`);t.push("")}if(T.length||C){if(t.push("## Git activity this session",""),T.length){t.push("**Commits:**");for(const e of T)t.push(`- \`${e}\``);t.push("")}C&&(t.push("**Uncommitted changes:**"),t.push("```"),t.push(C.split(`
|
|
5
4
|
`).slice(0,15).join(`
|
|
6
|
-
`)),t.push("```"),t.push(""))}if(s){if(t.push("## Design system",""),s.fonts?.primary&&t.push(`- **Font:** ${s.fonts.primary}${s.fonts.mono?` \xB7 mono: ${s.fonts.mono}`:""}`),s.colors?.mode&&t.push(`- **Mode:** ${s.colors.mode}`),s.colors?.palette){const
|
|
7
|
-
`)}async function
|
|
8
|
-
`+
|
|
9
|
-
`),
|
|
10
|
-
`)),process.exit(1)),n){const r=
|
|
11
|
-
`));return}console.log(r);return}const
|
|
12
|
-
`));const
|
|
13
|
-
`,"utf8")}}export{gt as switchCommand};
|
|
5
|
+
`)),t.push("```"),t.push(""))}if(s){if(t.push("## Design system",""),s.fonts?.primary&&t.push(`- **Font:** ${s.fonts.primary}${s.fonts.mono?` \xB7 mono: ${s.fonts.mono}`:""}`),s.colors?.mode&&t.push(`- **Mode:** ${s.colors.mode}`),s.colors?.palette){const e=Object.entries(s.colors.palette).map(([p,v])=>`${p}=${v}`).join(" ");t.push(`- **Palette:** ${e}`)}if(s.cssVars&&Object.keys(s.cssVars).length){const e=Object.entries(s.cssVars).slice(0,6).map(([p,v])=>`${p}: ${v}`).join(" | ");t.push(`- **CSS vars:** ${e}`)}s.framework&&t.push(`- **Framework:** ${s.framework}`),t.push("","> \u26A0 Always match these exactly. Do not introduce new colors or fonts.","")}if(O.length&&(t.push("## Capability contract",""),t.push(`Project: **${_}** v${j}`),t.push(`Capabilities: ${O.join(", ")}`),t.push("")),L.length){t.push("## Recent session log","");for(const e of L){const p=e.result?` [${e.result}]`:"",v=e.source?` {${e.source}}`:"";t.push(`- **${e.type}**${p}${v}: ${e.summary} _(${F(e.ts)})_`)}t.push("")}return t.push("---"),t.push(`_Session #${A} \xB7 ${N} \xB7 Generated by infernoflow._`),t.join(`
|
|
6
|
+
`)}async function St(o){const i=r=>o.includes(r),l=r=>{const h=o.indexOf(r);return h!==-1&&o[h+1]?o[h+1]:null},n=i("--show")||i("-s"),c=i("--copy")||i("-c"),s=i("--json"),b=i("--all"),$=l("--since"),a=l("--to")||o.find(r=>!r.startsWith("-")&&!["switch"].includes(r))||null;console.log(`
|
|
7
|
+
`+W("\u{1F525} infernoflow \u2014 switch")),console.log(" "+"\u2500".repeat(50)+`
|
|
8
|
+
`);const k=g.join(process.cwd(),".ai-memory");if(!E.existsSync(I)&&!E.existsSync(k)&&(console.error(it(` \u2718 not initialized \u2014 run: infernoflow init
|
|
9
|
+
`)),process.exit(1)),n){const r=pt(at);if(!r){console.log(B(` \u26A0 No HANDOFF.md yet \u2014 run: infernoflow switch
|
|
10
|
+
`));return}console.log(r);return}const H=dt(a,$,b);if(s){const r=f(J)||{},h=f(z)||{},M=f(K),P=f(X),L=Q(),d=Y(L,$,b),U=L.filter(e=>new Date(e.ts||0)>d),y=tt(d.getTime()>0?d:null),t=Z();console.log(JSON.stringify({state:r,contract:{policyId:h.policyId,policyVersion:h.policyVersion,capabilities:h.capabilities},theme:M,adoption:P,sessions:U,commits:y,ide:t,sessionStart:d.toISOString(),sessionId:d.getTime()>0?d.getTime().toString(16).slice(-6).toUpperCase():"ALL",sessionDuration:q(d.getTime()>0?Date.now()-d.getTime():-1),generatedAt:new Date().toISOString()},null,2));return}E.writeFileSync(V().handoff,H,"utf8"),console.log(ot(" \u2714 Written \u2192 "+g.relative(process.cwd(),V().handoff)+`
|
|
11
|
+
`));const D=Q(),u=Y(D,$,b),_=D.filter(r=>new Date(r.ts||0)>u),j=f(J)||{},O=f(K),x=f(z)||{},R=tt(u.getTime()>0?u:null),N=nt(u.getTime()>0?u:null),A=Z(),T=_.length>0?_:D.slice(-5),C=st(T),G=q(u.getTime()>0?Date.now()-u.getTime():-1),m=u.getTime()>0?u.getTime().toString(16).slice(-6).toUpperCase():"ALL";if(console.log(" "+W("Handoff ready")),console.log(" "+"\u2500".repeat(50)),console.log(" "+et("Session #"+m+" \xB7 "+G)),j.working&&console.log(" Working on "+S(j.working)),j.intent&&console.log(" Intent "+S(j.intent)),console.log(" Memory "+_.length+" entries this session (total: "+D.length+")"),C.length&&console.log(" Open threads "+B(C.length+" unresolved")),R.length&&console.log(" Git commits "+R.length+" this session"),N.length&&console.log(" Hot files "+N.map(r=>S(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),A&&console.log(" IDE "+A),a&&console.log(" Handing off \u2192 "+S(a)),console.log(),c){const r=ft(H);console.log(r?ot(" \u2714 Copied to clipboard \u2014 paste at the start of your next AI session"):B(" \u26A0 Clipboard failed \u2014 open inferno/HANDOFF.md manually"))}else console.log(" "+W("Ready to use:")),console.log(" "+S("1.")+" Open "+S("inferno/HANDOFF.md")),console.log(" "+S("2.")+" Copy all"),console.log(" "+S("3.")+" Paste at the start of your next AI session"),console.log(" "+et(" tip: use --copy to skip steps 1-2 automatically"));if(console.log(),E.existsSync(V().sessions)){const r={ts:new Date().toISOString(),agent:"infernoflow",type:"handoff",summary:a?`Handed off to ${a}`:"Handoff generated"};lt(process.cwd(),r)}}export{St as switchCommand};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infernoflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|