infernoflow 0.44.2 → 0.44.4
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/dist/bin/infernoflow.mjs +11 -11
- package/dist/lib/amp/io.mjs +9 -6
- package/dist/lib/commands/amp.mjs +4 -4
- package/dist/lib/commands/docGate.mjs +2 -2
- package/dist/lib/commands/doctor.mjs +2 -2
- package/dist/lib/commands/forget.mjs +10 -0
- package/dist/lib/commands/init.mjs +21 -21
- package/dist/lib/ruleFiles.mjs +7 -7
- package/dist/templates/cursor/hooks/inferno-session-draft.mjs +88 -0
- package/dist/templates/cursor/hooks.json +5 -0
- package/package.json +1 -1
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
(function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const
|
|
3
|
-
`)}const O={"Memory (the 5-command core)":["log","ask","switch","recap","status","refresh"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function
|
|
4
|
-
${
|
|
2
|
+
(function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const n={"\u2500":"-","\u2501":"-","\u2550":"=","\u2502":"|","\u2503":"|","\u2551":"|","\u250C":"+","\u2510":"+","\u2514":"+","\u2518":"+","\u251C":"+","\u2524":"+","\u252C":"+","\u2534":"+","\u253C":"+","\xB7":"*","\u2192":"->","\u2190":"<-","\u2714":"[OK]","\u2713":"[OK]","\u2718":"[X]","\u2717":"[X]","\u26A0":"[!]",\u2139:"[i]"},r=new RegExp(Object.keys(n).join("|"),"g"),l=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,f=c=>c.replace(r,p=>n[p]).replace(l,"");function u(c){const p=c.write.bind(c);c.write=function(a,...C){return typeof a=="string"?a=f(a):Buffer.isBuffer(a)&&(a=Buffer.from(f(a.toString("utf8")),"utf8")),p(a,...C)}}u(process.stdout),u(process.stderr)})();import{readFileSync as $}from"node:fs";import{dirname as k,join as y}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as g}from"../lib/ui/output.mjs";const A=k(v(import.meta.url));function I(o){for(const n of[y(o,"..","..","package.json"),y(o,"..","package.json")])try{return JSON.parse($(n,"utf8"))}catch{}return{version:"0.0.0-source"}}const M=I(A),m=M.version||"0.0.0",h={log:"Append to session memory (decisions, gotchas, failed attempts)",ask:"Query memory by keyword (gotchas surface first)",switch:"Generate a handoff doc for the next AI agent / session",recap:"End-of-session summary + health score + unlogged-change surfacing",status:"Quick health check \u2014 entries, gotchas, decisions, last activity",refresh:"Rebuild CLAUDE.md / .cursorrules / copilot-instructions.md from memory",forget:"Delete a memory entry by id or unique prefix (--last for the newest)",init:"Scaffold .ai-memory/ and wire the current IDE in one command",setup:"Re-run wiring (idempotent) \u2014 detects IDE, installs MCP + hooks",doctor:"Diagnose your setup \u2014 Node, git, contract, AI provider, MCP, hooks",context:"Generate AI-ready context for new sessions","install-cursor-hooks":"Install Cursor hooks (afterAgentResponse + stop)","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview)","generate-skills":"Generate Cursor rules + skill files from your developer profile",ai:"Manage AI providers \u2014 setup, status, test, clear",telemetry:"Opt-in anonymous telemetry (on | off | status)",uninstall:"Remove infernoflow from a project (--dry-run to preview)",check:"Validate contract, capabilities, scenarios, changelog",sync:"Cross-machine sync for personal memory \u2014 status/set/clear/migrate",amp:"AI Memory Protocol \u2014 status, migrate, validate (run: infernoflow amp)"},d={log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),refresh:async o=>(await import("../lib/commands/refresh.mjs")).refreshCommand(o),forget:async o=>(await import("../lib/commands/forget.mjs")).forgetCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),sync:async o=>(await import("../lib/commands/sync.mjs")).syncCommand(o),amp:async o=>(await import("../lib/commands/amp.mjs")).ampCommand(o)};function G(){const o=Object.keys(h),n=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(h).map(([r,l])=>` ${r.padEnd(n," ")}${l}`).join(`
|
|
3
|
+
`)}const O={"Memory (the 5-command core)":["log","ask","switch","recap","status","refresh","forget"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function b(){return Object.entries(O).map(([o,n])=>` ${i(o+":")}
|
|
4
|
+
${n.join(" ")}`).join(`
|
|
5
5
|
|
|
6
|
-
`)}const
|
|
6
|
+
`)}const w=Object.keys(d).length,E=`
|
|
7
7
|
${i("\u{1F525} infernoflow")} ${e("v"+m)}
|
|
8
8
|
${e("Persistent memory for AI coding sessions")}
|
|
9
9
|
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
${i("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
|
|
27
27
|
${t("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
|
|
28
28
|
|
|
29
|
-
${e("Run")} ${t("infernoflow commands")} ${e("to see all "+
|
|
29
|
+
${e("Run")} ${t("infernoflow commands")} ${e("to see all "+w+" commands grouped.")}
|
|
30
30
|
${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
|
|
31
|
-
`;import*as
|
|
32
|
-
${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+
|
|
33
|
-
`),console.log(
|
|
31
|
+
`;import*as R from"node:fs";import*as S from"node:path";try{const o=S.join(process.cwd(),"inferno");if(R.existsSync(o)){const{observeCommandStart:n}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&n(o,r)}}catch{}const[,,s,...x]=process.argv;(!s||s==="--help"||s==="-h")&&(console.log(E),process.exit(0)),(s==="--version"||s==="-v")&&(console.log(m),process.exit(0)),s==="commands"&&(console.log(`
|
|
32
|
+
${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+w+" commands")}
|
|
33
|
+
`),console.log(b()),console.log(`
|
|
34
34
|
${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
|
|
35
|
-
`),process.exit(0));const
|
|
36
|
-
Unknown command: ${
|
|
37
|
-
`)),process.exit(1));const
|
|
35
|
+
`),process.exit(0));const D=Object.keys(d);D.includes(s)||(console.error(g(`
|
|
36
|
+
Unknown command: ${s}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
|
|
37
|
+
`)),process.exit(1));const P=[s,...x];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(m,s)}catch{}d[s](P).catch(o=>{console.error(g(`
|
|
38
38
|
Error: `)+o.message),process.exit(1)});
|
package/dist/lib/amp/io.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as F from"node:os";import*as r from"node:path";import{findProjectRoot as
|
|
2
|
-
`).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(
|
|
3
|
-
`;if(i.mkdirSync(r.dirname(n),{recursive:!0}),i.appendFileSync(n,
|
|
4
|
-
|
|
5
|
-
`)
|
|
1
|
+
import*as i from"node:fs";import*as F from"node:os";import*as r from"node:path";import{findProjectRoot as h}from"../projectRoot.mjs";import{getBranchInfo as b}from"../git/branch.mjs";const x="1.0",D=new Set(["gotcha","decision","attempt","note","detection","pattern"]),O=new Set(["copilot","cursor","claude","windsurf","other"]);function B(e){let t=r.basename(r.resolve(e));try{const o=r.join(e,".ai-memory","amp.json");if(i.existsSync(o)){const n=JSON.parse(i.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function N(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const l=JSON.parse(i.readFileSync(r.join(t,"amp.json"),"utf8"));l&&typeof l.globalDir=="string"&&l.globalDir.trim()&&(o=l.globalDir.trim())}catch{}if(!o)return r.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=r.join(F.homedir(),n.slice(1).replace(/^[\/\\]/,""))),r.isAbsolute(n)||(n=r.resolve(e,n));const f=B(e);return r.join(n,f,"global.jsonl")}function y(e,t={}){const o=t.literal?r.resolve(e):h(e),n=r.join(o,".ai-memory"),f=r.join(o,"inferno"),l=t.forWrite||i.existsSync(n)||!i.existsSync(f),c=l?n:f,s=l,u=r.join(c,"branches"),a=b(o),d=r.join(u,`${a.currentSlug}.jsonl`),g=a.defaultSlug?r.join(u,`${a.defaultSlug}.jsonl`):null,p=A(o,c);return{root:c,projectRoot:o,isAmp:s,sessions:r.join(c,"sessions.jsonl"),config:r.join(c,s?"amp.json":"config.json"),handoff:r.join(c,s?"handoff.md":"HANDOFF.md"),globalFile:p,branchesDir:u,currentBranchFile:d,defaultBranchFile:g,branch:a}}function A(e,t){try{return N(e,t)}catch{return r.join(t,"global.jsonl")}}function m(e){const t=h(e),o=r.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const S="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function k(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=S[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=S[Math.floor(Math.random()*32)];return t+o}function j(e){const t={...e.meta||{}};let o=e.type||"note";D.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result);let n;const f=e.agent;f&&O.has(f)?n=f:f&&(t.agent=f);const l=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),c=e.confidence!=null?e.confidence:e.auto?.7:void 0,s={type:o,msg:e.summary||e.msg||"",ts:l,id:e.id||`amp_${k()}`};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),c!=null&&(s.confidence=c),Object.keys(t).length&&(s.meta=t),s}function v(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:f,agent:l,result:c,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function w(e){if(!e||!i.existsSync(e))return[];try{return i.readFileSync(e,"utf8").split(`
|
|
2
|
+
`).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(v)}catch{return[]}}function P(e){const t=y(e),o=new Set,n=[],f=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],l=[...new Set(f.filter(Boolean))];for(const c of l)for(const s of w(c)){const u=s.id||`${s.ts}|${s.summary}`;o.has(u)||(o.add(u),n.push(s))}return n.sort((c,s)=>{const u=typeof c.ts=="number"?c.ts:Date.parse(c.ts||0),a=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0);return u-a})}function J(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}function $(e,t){m(e);const o=y(e,{forWrite:!0}),n=J(o,t),f=j(t),l=JSON.stringify(f)+`
|
|
3
|
+
`;if(i.mkdirSync(r.dirname(n),{recursive:!0}),i.appendFileSync(n,l,"utf8"),n!==o.sessions)try{i.mkdirSync(r.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,l,"utf8")}catch{}return f}function C(e,t){if(!t)return{removed:0,files:[]};const o=y(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(i.existsSync(o.branchesDir))for(const s of i.readdirSync(o.branchesDir))s.endsWith(".jsonl")&&n.push(r.join(o.branchesDir,s))}catch{}const f=[...new Set(n)];let l=0;const c=[];for(const s of f){if(!i.existsSync(s))continue;let u;try{u=i.readFileSync(s,"utf8").split(`
|
|
4
|
+
`)}catch{continue}const a=[];let d=0;for(const g of u){if(!g.trim())continue;let p;try{p=JSON.parse(g).id}catch{a.push(g);continue}if(p===t){d++,l++;continue}a.push(g)}d>0&&(i.writeFileSync(s,a.length?a.join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`:"","utf8"),c.push(s))}return{removed:l,files:c}}function L(e){const{config:t}=y(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function R(e,t={}){m(e);const{config:o}=y(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:x,project:t.project||r.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)+`
|
|
7
|
+
`,"utf8"),!0}function W(e){const t=r.join(e,"inferno"),o=r.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=r.join(e,".ai-memory"),f=r.join(n,"sessions.jsonl");if(i.existsSync(f))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};m(e);const l=i.readFileSync(o,"utf8").split(`
|
|
8
|
+
`).filter(Boolean);let c=0;for(const s of l)try{const u=JSON.parse(s),a=j(u);i.appendFileSync(f,JSON.stringify(a)+`
|
|
6
9
|
`,"utf8"),c++}catch{}return i.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
|
|
7
10
|
|
|
8
11
|
Copied ${c} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
|
|
9
12
|
|
|
10
13
|
The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
|
|
11
|
-
`,"utf8"),{migrated:c,reason:"ok"}}export{x as AMP_VERSION,
|
|
14
|
+
`,"utf8"),{migrated:c,reason:"ok"}}export{x as AMP_VERSION,y as ampPaths,$ as appendEntry,C as deleteEntry,m as ensureAmpDir,v as fromAmp,k as generateULID,W as migrateLegacy,B as projectSlug,L as readConfig,P as readEntries,N as resolveGlobalFile,j as toAmp,R as writeDefaultConfig};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as m from"node:fs";import*as p from"node:path";import{bold as
|
|
2
|
-
`).filter(Boolean)}catch{continue}
|
|
3
|
-
Unknown amp verb: ${e}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
|
|
4
|
-
`)),process.exit(1)}export{
|
|
1
|
+
import*as m from"node:fs";import*as p from"node:path";import{bold as a,cyan as d,gray as s,green as $,yellow as w,red as y}from"../ui/output.mjs";import{AMP_VERSION as v,ampPaths as S,readEntries as M,migrateLegacy as P}from"../amp/io.mjs";const A=new Set(["gotcha","decision","attempt","note","detection","pattern"]),k=new Set(["copilot","cursor","claude","windsurf","other"]);function x(o,e){const n=[];return(!o||typeof o!="object")&&n.push("not an object"),o.type?A.has(o.type)||n.push(`type "${o.type}" not in AMP enum`):n.push("missing required field: type"),o.msg?typeof o.msg!="string"?n.push("msg must be a string"):o.msg.length>500&&n.push(`msg too long (${o.msg.length} > 500)`):n.push("missing required field: msg"),o.ts==null?n.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&n.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&n.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!k.has(o.tool)&&n.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&n.push("confidence out of range [0,1]"),n}function E(o){const e=S(o),n=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile],l=[...new Set(n.filter(Boolean))];try{if(m.existsSync(e.branchesDir))for(const r of m.readdirSync(e.branchesDir)){if(!r.endsWith(".jsonl"))continue;const t=p.join(e.branchesDir,r);l.includes(t)||l.push(t)}}catch{}const c=[];for(const r of l){if(!m.existsSync(r))continue;let t;try{t=m.readFileSync(r,"utf8").split(`
|
|
2
|
+
`).filter(Boolean)}catch{continue}t.forEach((g,f)=>{try{c.push({ok:!0,file:r,line:f+1,value:JSON.parse(g)})}catch(i){c.push({ok:!1,file:r,line:f+1,value:null,error:i.message})}})}return{sessions:e.sessions,entries:c,lines:[],files:l}}function b(o){const{sessions:e,isAmp:n,root:l}=S(o),c=p.join(o,".ai-memory"),r=p.join(o,"inferno"),t=m.existsSync(c),g=m.existsSync(r)&&m.existsSync(p.join(r,"sessions.jsonl"));if(console.log(),console.log(` ${a("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${a(v)}`),console.log(` Conformance level ${a("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!t&&!g){console.log(` ${w("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${a("Layout:")}`),console.log(` .ai-memory/ ${t?$("\u2714 present")+(n?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?w("\u26A0 legacy")+(n?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(e)){const f=M(o);if(console.log(` ${a("Memory:")}`),console.log(` file ${s(p.relative(o,e))}`),console.log(` entries ${a(String(f.length))}`),f.length){const i=new Map;for(const u of f)i.set(u.type,(i.get(u.type)||0)+1);const h=[...i.entries()].map(([u,j])=>`${u}:${j}`).join(" ");console.log(` breakdown ${s(h)}`)}}g&&!t&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function D(o){console.log(),console.log(` ${a("\u{1F525} infernoflow amp migrate")}`),console.log();const e=P(o);e.migrated>0?(console.log(` ${$("\u2714")} Migrated ${a(String(e.migrated))} entr${e.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${e.reason}${s(".")}`),console.log()}function C(o){console.log(),console.log(` ${a("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:e,entries:n}=E(o);if(!n.length){console.log(` ${s("No entries to validate at")} ${d(p.relative(o,e))}`),console.log();return}let l=0,c=0,r=0;const t=[];for(const i of n){const h=p.relative(o,i.file);if(!i.ok){c++,t.push({file:h,line:i.line,error:`parse error: ${i.error}`});continue}const u=x(i.value,i.line);u.length?(r++,t.push({file:h,line:i.line,error:u.join("; ")})):l++}for(const i of t.slice(0,10))console.log(` ${y("\u2718")} ${i.file}:${i.line} ${s(i.error)}`);t.length>10&&console.log(` ${s(`\u2026 ${t.length-10} more`)}`),t.length&&console.log();const g=n.length,f=c===0&&r===0;console.log(f?` ${$("\u2714")} ${a(`${l}/${g}`)} entries conform to AMP v${v}.`:` ${y("\u2718")} ${a(`${l}/${g}`)} entries OK \xB7 ${c} parse \xB7 ${r} schema`),console.log(),f||process.exit(1)}function F(){console.log(v)}async function T(o){const e=o[1],n=process.cwd(),l=o.slice(2);if(!e||e==="status")return b(n);if(e==="migrate")return D(n);if(e==="validate")return C(n);if(e==="version")return F();if(e==="--help"||e==="-h")return b(n);if(e==="write")return(await import("./log.mjs")).logCommand(["log",...l]);if(e==="read"||e==="search")return(await import("./ask.mjs")).askCommand(["ask",...l]);if(e==="handoff")return(await import("./switch.mjs")).switchCommand(["switch",...l]);if(e==="health")return(await import("./recap.mjs")).recapCommand(["recap","--brief",...l]);console.error(y(`
|
|
3
|
+
Unknown amp verb: ${e}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version | read | write | search | handoff | health)
|
|
4
|
+
`)),process.exit(1)}export{T as ampCommand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{execSync as
|
|
2
|
-
`).filter(Boolean):[]}catch{if(
|
|
1
|
+
import{execSync as g}from"node:child_process";import{ok as d,fail as k,info as u,gray as f}from"../ui/output.mjs";function E(n){return g(n,{stdio:["ignore","pipe","pipe"]}).toString("utf8").trim()}function S(){try{return g("git check-ignore -q inferno",{stdio:["ignore","ignore","ignore"]}),!0}catch{return!1}}const p=["src/","frontend/","backend/","app/","pages/","components/","Controllers/","Services/","Endpoints/","lib/","api/","server/"];async function O(n={}){const l=Array.isArray(n),i=l?!1:n?.silent||!1,h=l?!1:n?.captureExit||!1,t=l?n.includes("--json"):!!n?.json,m=process.env.BASE_SHA||"HEAD~1",y=process.env.HEAD_SHA||"HEAD";let o=[];try{const e=E(`git diff --name-only ${m}..${y}`);o=e?e.split(`
|
|
2
|
+
`).filter(Boolean):[]}catch{if(t){console.log(JSON.stringify({ok:!0,skipped:!0,reason:"no_git_available"},null,2));return}i||u(f("doc-gate skipped (no git available)"));return}if(o.length===0){if(t){console.log(JSON.stringify({ok:!0,changedFiles:0,changedCode:!1,changedInferno:!1},null,2));return}i||d("doc-gate: no changed files");return}if(S()){if(t){console.log(JSON.stringify({ok:!0,skipped:!0,reason:"inferno_gitignored"},null,2));return}i||u(f("doc-gate skipped \u2014 inferno/ is gitignored (memory-only mode)"));return}const r=o.some(e=>p.some(a=>e.startsWith(a)||e.includes("/"+a))),s=o.some(e=>e.startsWith("inferno/")),c=o.filter(e=>p.some(a=>e.startsWith(a))).slice(0,5);if(t){const e={ok:!(r&&!s),changedFiles:o.length,changedCode:r,changedInferno:s,sampleCodeFiles:c,hint:r&&!s?"Update at least one file in inferno/ before committing":null};console.log(JSON.stringify(e,null,2)),e.ok||process.exit(1);return}if(r&&!s){if(i||(k("Code changed but inferno/ was NOT updated","Update at least one file in inferno/ before committing"),c.length&&(console.log(),c.forEach(e=>console.log(" "+f("\u2022 "+e))))),h)throw new Error("doc-gate failed");process.exit(1)}i||d("doc-gate: docs are up to date")}export{O as docGateCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as
|
|
1
|
+
import*as s from"node:fs";import*as c from"node:path";import*as y from"node:os";import*as A from"node:http";import{execSync as O,spawnSync as v}from"node:child_process";import{fileURLToPath as P}from"node:url";import{bold as C,cyan as b,gray as S,green as w,yellow as k,red as $}from"../ui/output.mjs";import{detectAvailableProviders as _}from"../ai/providerRouter.mjs";import{readEntries as I}from"../amp/io.mjs";function f(e,n){try{const o=n();return{label:e,...o}}catch(o){return{label:e,status:"error",message:o.message,fix:null}}}function a(e,n){return{status:"pass",message:e,detail:n||null,fix:null}}function l(e,n){return{status:"warn",message:e,detail:null,fix:n||null}}function h(e,n){return{status:"fail",message:e,detail:null,fix:n||null}}function M(){const e=process.version,n=parseInt(e.slice(1).split(".")[0],10);return n>=20?a(`Node.js ${e}`,"Node 20+ recommended"):n>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function E(){try{const e=v("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return e.status===0?a(`infernoflow v${e.stdout.trim()} installed`):a("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return a("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function T(e){try{return O("git rev-parse --git-dir",{cwd:e,stdio:"ignore"}),a("Git repository detected")}catch{return h("Not a git repository","git init && git add . && git commit -m 'init'")}}function D(e){const n=c.join(e,".ai-memory"),o=c.join(e,"inferno");return s.existsSync(n)&&s.existsSync(o)?a(".ai-memory/ + inferno/ both present"):s.existsSync(n)?a(".ai-memory/ directory exists (memory mode)"):s.existsSync(o)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function L(e){if(x(e)){let o=0;try{o=I(e).length}catch{}return a(o===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${o} session entr${o===1?"y":"ies"}`)}const n=c.join(e,"inferno");for(const o of["contract.json","capabilities.json"]){const t=c.join(n,o);if(s.existsSync(t))try{const i=(JSON.parse(s.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${o} valid \u2014 ${i} capabilities`)}catch{return h(`${o} contains invalid JSON`,`Fix the JSON syntax in inferno/${o}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function x(e){const n=c.join(e,".ai-memory"),o=c.join(e,"inferno","contract.json");if(s.existsSync(n)&&!s.existsSync(o))return!0;try{return JSON.parse(s.readFileSync(c.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function F(e){if(x(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=c.join(e,"inferno","scenarios");if(!s.existsSync(n))return l("No scenarios/ directory","infernoflow init");const o=s.readdirSync(n).filter(t=>t.endsWith(".json"));return o.length?a(`${o.length} scenario file${o.length!==1?"s":""} found`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function G(e){if(x(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=c.join(e,"inferno","CHANGELOG.md");return s.existsSync(n)?a("inferno/CHANGELOG.md exists"):l("No inferno/CHANGELOG.md","infernoflow init")}function R(e){if(x(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const n=c.join(e,"inferno","CONTEXT.md");if(!s.existsSync(n))return l("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(n).mtimeMs)/(1e3*60*60*24);return o>7?l(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(o)}d old)`)}function J(e){const n=c.join(e,".git","hooks"),o=c.join(n,"post-commit"),t=c.join(n,"pre-push"),r=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),i=s.existsSync(t)&&s.readFileSync(t,"utf8").includes("infernoflow");return r&&i?a("Git hooks installed (post-commit + pre-push)"):l(r||i?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function H(e){const n=[c.join(e,".cursor","mcp.json"),c.join(e,".mcp.json"),c.join(y.homedir(),".cursor","mcp.json"),c.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),c.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of n)if(s.existsSync(o))try{const t=JSON.parse(s.readFileSync(o,"utf8")),r=t.mcpServers||t.mcp_servers||{};if(Object.keys(r).some(i=>i.toLowerCase().includes("inferno")))return a(`MCP server configured in ${c.basename(o)}`)}catch{}return l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function U(e,n){const o=c.join(e,".ai-memory",".mcp-runtime.json");if(!s.existsSync(o))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(s.readFileSync(o,"utf8"))}catch{return l(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?l(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===n?a(`MCP runtime v${t.version} matches CLI`):n.startsWith("0.0.0")||t.version.startsWith("0.0.0")?a(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):l(`MCP server is running v${t.version} but CLI is v${n} \u2014 restart your AI tool to load the new code.`,"Quit and reopen Cursor / Claude Code / VS Code (the long-running MCP process keeps the old code in memory until restart)")}function W(e){const n=[".cursorrules","CLAUDE.md",c.join(".github","copilot-instructions.md")],o=[];for(const t of n)try{const r=s.readFileSync(c.join(e,t),"utf8");r.includes("amp_write")&&/Memory protocol/i.test(r)&&o.push(t)}catch{}return o.length?a(`Memory protocol in ${o.length}/${n.length} rule file(s) \u2014 AI knows when to auto-capture`):l("Memory protocol missing from rule files \u2014 the AI won't auto-capture gotchas","infernoflow refresh (writes the amp_write capture protocol into CLAUDE.md / .cursorrules)")}function X(e){const n=_(e),o=Object.entries(n).filter(([,t])=>t).map(([t])=>t);return o.length?a(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):l("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
|
|
2
2
|
Or install Ollama (ollama.com) for free local AI
|
|
3
|
-
Or use VS Code with GitHub Copilot (zero config)`)}async function K(){return new Promise(e=>{const n=
|
|
3
|
+
Or use VS Code with GitHub Copilot (zero config)`)}async function K(){return new Promise(e=>{const n=A.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{e(a("Ollama running on localhost:11434"))});n.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),n.on("timeout",()=>{n.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function V(){const e=c.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(e))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const n=JSON.parse(s.readFileSync(e,"utf8")),o=n.user?.login||n.user?.name||n.user?.email||"unknown";if(n.mode==="supabase"&&n.access_token){if(n.expires_at){const t=new Date(n.expires_at).getTime();if(Date.now()>t)return l(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return n.mode==="device-flow"&&n.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:n.access_token?l(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return l("Credentials file unreadable","infernoflow logout && infernoflow login")}}function Y(){try{const e=P(import.meta.url),n=c.resolve(c.dirname(e),"..","..","bin","infernoflow.mjs");if(!s.existsSync(n))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...s.readFileSync(n,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=c.resolve(c.dirname(n),"..");for(const d of t){const m=d[1],p=c.join(i,"lib",m);s.existsSync(p)||r.push(m)}return r.length?h(`${r.length} routed command(s) missing module files: ${r.slice(0,3).join(", ")}${r.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):a(`All ${t.length} routed commands resolve to real files`)}catch(e){return{status:"info",message:`Router integrity check skipped: ${e.message}`,fix:null,detail:null}}}function z(e){const n=c.join(e,"package.json");if(!s.existsSync(n))return a("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(n,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},r=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),i=[];for(const[m,p]of Object.entries(t)){const j=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!j)continue;const u=j[1];r.has(u)||i.push({scriptName:m,verb:u})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return l(`package.json references ${i.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function q(e){const n=c.join(e,".gitignore");if(!s.existsSync(n))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(n,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?a(".gitignore excludes node_modules"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function B(e,n){const o=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of o){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");v("infernoflow",d,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function Q(e){return e==="pass"?w("\u2714"):e==="warn"?k("\u26A0"):e==="fail"?$("\u2717"):S("\xB7")}function Z(e,n){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)o[i.status]=(o[i.status]||0)+1;console.log(),console.log(` ${C("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${Q(i.status)} ${C(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${S(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${b("fix:")} ${S(i.fix)}`);console.log();const r=o.fail>0?$("issues found"):o.warn>0?k("warnings"):w("all good");console.log(` ${r} \u2014 ${w(String(o.pass))} pass \xB7 ${k(String(o.warn))} warn \xB7 ${$(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${b("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function ee(e){const n=e.slice(1),o=n.includes("--json"),t=n.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:u}=await import("node:url"),g=c.dirname(u(import.meta.url)),N=c.join(g,"..","..","package.json");d=JSON.parse(s.readFileSync(N,"utf8")).version||d}catch{}const m=[f("Node.js version",()=>M()),f("infernoflow CLI",()=>E()),f("Git repository",()=>T(r)),f("inferno/ directory",()=>D(r)),f("Contract / mode",()=>L(r)),f("Scenarios",()=>F(r)),f("Changelog",()=>G(r)),f("CONTEXT.md",()=>R(r)),f("Git hooks",()=>J(r)),f("Auto-capture protocol",()=>W(r)),f("MCP server",()=>H(r)),f("MCP runtime version",()=>U(r,d)),f("AI providers",()=>X(r)),f("Cloud sync",()=>V()),f(".gitignore",()=>q(r)),f("Router integrity",()=>Y()),f("npm scripts",()=>z(r)),await K().then(u=>({label:"Ollama (local AI)",...u}))],p=Date.now()-i;if(t){const u=B(m,r);if(u.length)return o||(console.log(),u.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),ee(["doctor","--json"])}if(o){const u={pass:0,warn:0,fail:0,info:0};m.forEach(g=>u[g.status]=(u[g.status]||0)+1),console.log(JSON.stringify({ok:u.fail===0,counts:u,results:m,elapsed:p}));return}Z(m,p),m.some(u=>u.status==="fail")&&process.exit(1)}export{ee as doctorCommand};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{bold as y,cyan as g,gray as r,green as p,yellow as l,red as a}from"../ui/output.mjs";import{readEntries as x,deleteEntry as u}from"../amp/io.mjs";async function v(i=[]){const c=process.cwd(),f=i[0]==="forget"?i.slice(1):i,d=f.includes("--last"),n=f.find(o=>o&&!o.startsWith("-"));!d&&!n&&(console.error(r(`
|
|
2
|
+
Usage: `)+g("infernoflow forget <id|prefix>")+r(" or ")+g("infernoflow forget --last")+`
|
|
3
|
+
`),process.exit(1));const t=x(c);t.length===0&&(console.error(l(`
|
|
4
|
+
No memory entries to forget.
|
|
5
|
+
`)),process.exit(1));let e;if(d)e=t[t.length-1];else{const o=t.filter(s=>s.id&&(s.id===n||s.id.startsWith(n)));if(o.length===0&&(console.error(a(`
|
|
6
|
+
No entry matches id/prefix: ${n}
|
|
7
|
+
`)),process.exit(1)),o.length>1){console.error(l(`
|
|
8
|
+
Ambiguous \u2014 ${o.length} entries match "${n}". Be more specific:`));for(const s of o.slice(0,8))console.error(r(` ${s.id} `)+(s.msg||s.summary||"").slice(0,60));console.error(""),process.exit(1)}e=o[0]}(!e||!e.id)&&(console.error(a(`
|
|
9
|
+
That entry has no id (very old format) \u2014 edit .ai-memory/sessions.jsonl by hand.
|
|
10
|
+
`)),process.exit(1));const{removed:h,files:m}=u(c,e.id);console.log(),h>0?(console.log(" "+p("\u2714")+" Forgot "+y(e.type||"entry")+r(` ${e.id}`)),console.log(" "+r((e.msg||e.summary||"").slice(0,80))),console.log(" "+r(`Removed from ${m.length} file${m.length===1?"":"s"}.`))):console.log(" "+l("Nothing removed \u2014 the id wasn't found on disk.")),console.log()}export{v as forgetCommand};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as s from"node:path";import*as
|
|
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
|
|
1
|
+
import*as i from"node:fs";import*as s from"node:path";import*as H from"node:readline";import{fileURLToPath as de}from"node:url";import{header as ue,ok as g,warn as T,info as q,done as z,nextSteps as me,bold as J,cyan as f,yellow as G,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as M,reviewCapabilitiesInteractive as ye,writeAdoptionBaseline as ge,buildAdoptionReport as he,summarizeCapabilities as we,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 L(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 U(e,o,n,t=!1){return i.existsSync(o)&&!n?(t||T("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 xe(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()?xe(r,c,n):U(r,c,n)}}function Ie(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 Qe(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
3
|
`),c=t.length===0||t.endsWith(`
|
|
4
4
|
`)?"":`
|
|
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
|
|
6
|
-
`)}function
|
|
7
|
-
`)}function
|
|
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 K(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 Ee(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 Pe(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
8
|
`)}function oe(e,o){const n=`# Changelog \u2014 ${o}
|
|
9
9
|
|
|
10
10
|
## Unreleased
|
|
@@ -17,30 +17,30 @@ import*as i from"node:fs";import*as s from"node:path";import*as G from"node:read
|
|
|
17
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
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
|
|
20
|
+
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const h=K(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const E=H.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(x=>{E.question(r(" What does this project do? (one line, Enter to skip): "),I=>{E.close(),x(I.trim())})})}const O={policyId:h,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(O,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(" "+n("Ready. Start using it:")),console.log(" "+t("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+t("infernoflow
|
|
23
|
-
> `),c=m=>new Promise(j=>{const p=
|
|
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 context")+r(" \u2014 generate AI context to paste")),console.log(" "+t("infernoflow init --mode full")+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=H.createInterface({input:process.stdin,output:process.stdout});let h=!1;p.on("SIGINT",()=>{h=!0,p.close(),j(null)}),p.on("close",()=>{h&&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"),h=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl"),O=i.existsSync(S),E=i.existsSync(s.join(h,"sessions.jsonl"));if((O||E)&&!o){const w=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
|
|
24
24
|
`+t("\u{1F525} infernoflow")+c(` \u2014 already set up
|
|
25
|
-
`)),console.log(" "+m("\u2714")+" "+
|
|
25
|
+
`)),console.log(" "+m("\u2714")+" "+w+` found
|
|
26
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
|
-
`));return}const
|
|
29
|
+
`));return}const I=K(e);console.log(`
|
|
30
30
|
`+t("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
|
|
31
|
-
`)),console.log(" Detected: "+r(
|
|
32
|
-
`),Se(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),ve(e,{project:
|
|
31
|
+
`)),console.log(" Detected: "+r(I)+`
|
|
32
|
+
`),Se(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),ve(e,{project:I,config:{autoCapture:!0}}),X(e);const F=B(e);for(const w of F)"created"in w&&(w.created||w.updated)&&g((w.created?"Created: ":"Updated: ")+r(w.rel));try{Z(e)}catch(w){T("MCP auto-setup skipped: "+w.message)}const P=await De({yes:n});P?(V(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:P,source:"init"}),console.log(`
|
|
33
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
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
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
|
|
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:${
|
|
40
|
-
`);for(const d of k)i.writeFileSync(s.join(
|
|
41
|
-
`);oe(s.join(y,"CHANGELOG.md"),
|
|
38
|
+
`))}async function eo(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"),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(A=>A.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";T(`Unknown template: ${h}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),N=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(N)||i.mkdirSync(N,{recursive:!0});const D=K(o),k=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:D,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:${h}`}))},null,2)+`
|
|
40
|
+
`);for(const d of k)i.writeFileSync(s.join(N,`${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"),D),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${D} \u2014 infernoflow context
|
|
42
42
|
|
|
43
|
-
> Template: ${
|
|
43
|
+
> Template: ${h} \u2014 ${l.description}
|
|
44
44
|
|
|
45
45
|
## Hint
|
|
46
46
|
${l.contextHint}
|
|
@@ -48,7 +48,7 @@ ${l.contextHint}
|
|
|
48
48
|
## Capabilities (${k.length})
|
|
49
49
|
${k.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
|
|
50
50
|
`)}
|
|
51
|
-
`),l.scripts&&(q("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,
|
|
52
|
-
`));const ae=await
|
|
53
|
-
`)),
|
|
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(` ${
|
|
51
|
+
`),l.scripts&&(q("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,A])=>console.log(` ${J(d)}: ${v(A)}`)),console.log()),z(`Initialised from template ${J(f(h))} \u2014 ${J(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"),O=e.includes("--vscode-copilot-hooks"),E=e.includes("--report-json"),x=e.includes("--report-json-only"),I=e.includes("--report-human-only"),F=W(e,"--lang"),P=W(e,"--framework"),w=W(e,"--project-type"),u=x;x&&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)),T("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const R=K(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let _=R,C=Y.split(",").map(a=>a.trim());if(r||m){let l=M(o,{language:F||void 0,framework:P||void 0,projectType:w||void 0});if(!t&&!x){const k=H.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},A=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
|
|
52
|
+
`));const ae=await L(k,"Language",d.language||A.language||"unknown"),pe=await L(k,"Framework",d.framework||A.framework||"unknown"),fe=await L(k,"Project type",d.projectType||A.projectType||"unknown");k.close(),l=M(o,{language:ae,framework:pe,projectType:fe})}const y=l.capabilities,N=we(y);x?console.log(JSON.stringify({mode:"adopt",policyId:R,inferredCapabilities:N,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(he(y))),console.log(),console.log(v(ke(l))),console.log(),E&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:R,inferredCapabilities:N,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 D=await ye(y,t);_=R,C=D.map(k=>k.id),C.length===0&&(C=["ExampleCapability"],u||T("No capabilities detected from the code \u2014 wrote a placeholder. Edit "+f("inferno/capabilities.json")+" to describe what this project does."))}else if(!t){const a=H.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
|
|
53
|
+
`)),_=await L(a,"Project / policy name",R),C=(await L(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=C.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=M(o,{language:F||void 0,framework:P||void 0,projectType:w||void 0});ge(b,_,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"),_,C),u||g("Created: "+f("inferno/contract.json")),Ee(s.join(b,"capabilities.json"),C),u||g("Created: "+f("inferno/capabilities.json")),Pe(s.join(b,"scenarios"),C),u||g("Created: "+f("inferno/scenarios/happy_path.json")),oe(s.join(b,"CHANGELOG.md"),_),u||g("Created: "+f("inferno/CHANGELOG.md"));const $=Oe(),te=s.join($,"scripts","inferno-doc-gate.mjs"),ie=s.join(o,"scripts","inferno-doc-gate.mjs");U(te,ie,n,u);const se=s.join($,"scripts","inferno-install-hooks.mjs"),re=s.join(o,"scripts","inferno-install-hooks.mjs");U(se,re,n,u);const le=s.join($,"ci","github-inferno-check.yml"),ce=s.join(ne,"infernoflow-check.yml");if(U(le,ce,n,u),Ie(o,u),S&&je({cwd:o,templatesRoot:$,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||T(a)}}),O&&be({cwd:o,templatesRoot:$,force:n,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||T(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=M(o,{language:F||void 0,framework:P||void 0,projectType:w||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(` ${G("\u{1F4A1}")} ${J("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),me([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+G("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+G("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+G("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...O?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+G("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!O?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Qe as ensureGitignoreEntries,eo as initCommand};
|
package/dist/lib/ruleFiles.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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(`
|
|
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`, `retry` / `retrying`, 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
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
|
|
3
|
+
`)}function x(e){const s=e.indexOf("<!-- AMP:START -->"),o=e.indexOf("<!-- AMP:END -->");if(s===-1||o===-1||o<=s)return e;const t=e.slice(0,s).replace(/\s+$/,""),i=e.slice(o+16).replace(/^\s+/,"");return(t?t+(i?`
|
|
4
4
|
|
|
5
|
-
`:""):"")+i}function g(e,
|
|
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=
|
|
5
|
+
`:""):"")+i}function g(e,s){const o=p.dirname(e);if(a.existsSync(o)||a.mkdirSync(o,{recursive:!0}),!a.existsSync(e))return a.writeFileSync(e,s+`
|
|
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=s+`
|
|
7
7
|
|
|
8
|
-
`+t;return a.writeFileSync(e,u,"utf8"),{created:!1,updated:!0}}const l=t.slice(0,i),
|
|
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,
|
|
10
|
-
`)}function R(e){const
|
|
8
|
+
`+t;return a.writeFileSync(e,u,"utf8"),{created:!1,updated:!0}}const l=t.slice(0,i),r=t.slice(c+h.length),n=l+s+r;return n===t?{created:!1,updated:!1}:(a.writeFileSync(e,n,"utf8"),{created:!1,updated:!0})}function D(e){const s=A(),o=[];for(const t of d){const i=p.join(e,t);try{const c=g(i,s);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,s=10){try{return w(`git log --pretty=format:"%h%x09%ad%x09%s" --date=short -n ${s}`,{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,s=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,s),l={gotcha:"\u26A0",decision:"\u2713",attempt:"\u2717",note:"\xB7",detection:"\u25CB",pattern:"\u25C7"},r=[];if(r.push(m),r.push("<!-- Auto-managed by infernoflow. Don't edit between these markers. -->"),r.push("## Project memory (infernoflow)"),r.push(""),r.push(y()),r.push(""),i.length>0){r.push("### Recent commits");for(const n of i)r.push(`- \`${n.hash}\` _${n.date}_ ${n.subject}`);r.push("")}if(c.length>0){r.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," ");r.push(`- \u{1F525} ${l[n.type]||"\xB7"} **${n.type||"note"}**${u}: ${f}`)}r.push("")}return t.length===0&&i.length===0&&r.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._'),r.push(h),r.join(`
|
|
10
|
+
`)}function R(e){const s=k(e),o=[];for(const t of d){const i=p.join(e,t);try{const c=g(i,s);o.push({rel:t,...c})}catch(c){o.push({rel:t,error:c.message})}}return o}export{R as refreshRuleFilesFromMemory,D as writeInitRuleFiles};
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import * as fs from "node:fs";
|
|
18
18
|
import * as path from "node:path";
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
19
20
|
|
|
20
21
|
/** Keep in sync with templates/scripts/inferno-promote-draft.mjs */
|
|
21
22
|
const DRAFT_HEADER = `# CONTEXT draft (gitignored)
|
|
@@ -132,6 +133,83 @@ async function readStdin() {
|
|
|
132
133
|
return Buffer.concat(chunks).toString("utf8");
|
|
133
134
|
}
|
|
134
135
|
|
|
136
|
+
// ── Deterministic trigger capture (beforeSubmitPrompt) ──────────────────────
|
|
137
|
+
// Cursor's beforeSubmitPrompt hook hands us the USER's prompt text before it
|
|
138
|
+
// goes to the model. The Memory-protocol block already asks the AI to log on
|
|
139
|
+
// these signals, but the AI doesn't always obey — so this is a deterministic
|
|
140
|
+
// backstop: if the prompt itself contains a trouble signal (!!, retry, "not
|
|
141
|
+
// working", …) we write an `attempt` entry ourselves. Bounded hard against
|
|
142
|
+
// noise: a 90s cooldown + identical-prompt dedupe, so a frustrated burst of
|
|
143
|
+
// "still broken!! retry!!" produces ONE entry, not ten.
|
|
144
|
+
const TRIGGER_RES = [
|
|
145
|
+
/(?:^|\s)!!+/,
|
|
146
|
+
/\bretry(?:ing)?\b/i,
|
|
147
|
+
/\bnot working\b/i,
|
|
148
|
+
/\bstill (?:broken|failing|not working|doesn['’]?t)\b/i,
|
|
149
|
+
/\bsame (?:error|issue|problem)\b/i,
|
|
150
|
+
/\bno change\b/i,
|
|
151
|
+
/\bdoesn['’]?t work\b/i,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
function memoryRootExists() {
|
|
155
|
+
return fs.existsSync(path.join(projectRoot(), ".ai-memory")) ||
|
|
156
|
+
fs.existsSync(path.join(projectRoot(), "inferno"));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function triggerStatePath() {
|
|
160
|
+
return path.join(projectRoot(), ".ai-memory", ".trigger-state.json");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function cheapHash(s) {
|
|
164
|
+
let h = 0;
|
|
165
|
+
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
|
|
166
|
+
return String(h);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleUserPrompt(text) {
|
|
170
|
+
const trimmed = (text || "").trim();
|
|
171
|
+
if (!trimmed) return;
|
|
172
|
+
if (!memoryRootExists()) return; // only inside infernoflow projects
|
|
173
|
+
if (!TRIGGER_RES.some((re) => re.test(trimmed))) return;
|
|
174
|
+
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
const stateFile = triggerStatePath();
|
|
177
|
+
let state = {};
|
|
178
|
+
try { state = JSON.parse(fs.readFileSync(stateFile, "utf8")); } catch {}
|
|
179
|
+
const h = cheapHash(trimmed.slice(0, 200));
|
|
180
|
+
const COOLDOWN_MS = 90_000;
|
|
181
|
+
if (state.lastHash === h) return; // exact same prompt — skip
|
|
182
|
+
if (state.lastTs && now - state.lastTs < COOLDOWN_MS) return; // rate-limit
|
|
183
|
+
|
|
184
|
+
const msg = "Auto-trigger — user signalled trouble: " +
|
|
185
|
+
trimmed.replace(/\s+/g, " ").slice(0, 180);
|
|
186
|
+
|
|
187
|
+
// Prefer the CLI (correct id / branch routing / AMP shape); fall back to a
|
|
188
|
+
// direct sessions.jsonl append so capture still works without a global CLI.
|
|
189
|
+
let wrote = false;
|
|
190
|
+
try {
|
|
191
|
+
const bin = process.platform === "win32" ? "infernoflow.cmd" : "infernoflow";
|
|
192
|
+
const r = spawnSync(bin, ["log", msg, "--type", "attempt", "--source", "cursor-trigger", "--tags", "auto-trigger"], {
|
|
193
|
+
cwd: projectRoot(), encoding: "utf8", timeout: 8000, shell: process.platform === "win32",
|
|
194
|
+
});
|
|
195
|
+
wrote = r.status === 0;
|
|
196
|
+
} catch { /* fall through to direct write */ }
|
|
197
|
+
if (!wrote) {
|
|
198
|
+
try {
|
|
199
|
+
const sess = path.join(projectRoot(), ".ai-memory", "sessions.jsonl");
|
|
200
|
+
fs.mkdirSync(path.dirname(sess), { recursive: true });
|
|
201
|
+
const entry = { type: "attempt", msg, ts: now, id: "amp_hook_" + now.toString(36), source: "cursor-trigger", tags: ["auto-trigger"], meta: { agent: "cursor-hook" } };
|
|
202
|
+
fs.appendFileSync(sess, JSON.stringify(entry) + "\n", "utf8");
|
|
203
|
+
wrote = true;
|
|
204
|
+
} catch { /* best effort */ }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (wrote) {
|
|
208
|
+
try { fs.writeFileSync(stateFile, JSON.stringify({ lastTs: now, lastHash: h }), "utf8"); } catch {}
|
|
209
|
+
process.stderr.write("[inferno-session-draft] auto-captured trigger to memory\n");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
135
213
|
// ── main ───────────────────────────────────────────────────────────────────
|
|
136
214
|
|
|
137
215
|
function main() {
|
|
@@ -149,6 +227,16 @@ function main() {
|
|
|
149
227
|
return;
|
|
150
228
|
}
|
|
151
229
|
|
|
230
|
+
// beforeSubmitPrompt: deterministic trigger capture on the USER's prompt.
|
|
231
|
+
if (process.argv.includes("--user-prompt")) {
|
|
232
|
+
const t = typeof data.prompt === "string" ? data.prompt
|
|
233
|
+
: typeof data.text === "string" ? data.text : "";
|
|
234
|
+
try { handleUserPrompt(t); } catch (e) { console.error("[inferno-session-draft] trigger:", e?.message); }
|
|
235
|
+
console.log("{}");
|
|
236
|
+
process.exit(0);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
152
240
|
const file = draftPath();
|
|
153
241
|
|
|
154
242
|
if (agentStop) {
|
package/package.json
CHANGED