infernoflow 0.43.12 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/infernoflow.mjs +30 -33
- package/dist/lib/amp/io.mjs +8 -8
- package/dist/lib/cleanTree.mjs +12 -0
- package/dist/lib/commands/ai.mjs +2 -2
- package/dist/lib/commands/amp.mjs +4 -4
- package/dist/lib/commands/ask.mjs +2 -2
- package/dist/lib/commands/context.mjs +18 -18
- package/dist/lib/commands/doctor.mjs +2 -3
- package/dist/lib/commands/init.mjs +31 -32
- package/dist/lib/commands/log.mjs +13 -19
- package/dist/lib/commands/recap.mjs +3 -3
- package/dist/lib/commands/refresh.mjs +5 -0
- package/dist/lib/commands/status.mjs +6 -7
- package/dist/lib/commands/switch.mjs +5 -5
- package/dist/lib/commands/sync.mjs +41 -0
- package/dist/lib/git/branch.mjs +2 -0
- package/dist/lib/projectRoot.mjs +1 -0
- package/dist/lib/ruleFiles.mjs +9 -8
- package/dist/lib/upgradeCheck.mjs +1 -1
- package/dist/templates/cursor/inferno-mcp-server.mjs +170 -325
- package/package.json +13 -5
- package/dist/lib/commands/changelog.mjs +0 -21
- package/dist/lib/commands/ci.mjs +0 -3
- package/dist/lib/commands/claudeMd.mjs +0 -116
- package/dist/lib/commands/coverage.mjs +0 -2
- package/dist/lib/commands/demo.mjs +0 -113
- package/dist/lib/commands/diff.mjs +0 -5
- package/dist/lib/commands/explain.mjs +0 -8
- package/dist/lib/commands/feedback.mjs +0 -12
- package/dist/lib/commands/graph.mjs +0 -76
- package/dist/lib/commands/impact.mjs +0 -2
- package/dist/lib/commands/implement.mjs +0 -7
- package/dist/lib/commands/monorepo.mjs +0 -4
- package/dist/lib/commands/notify.mjs +0 -4
- package/dist/lib/commands/prImpact.mjs +0 -2
- package/dist/lib/commands/publish.mjs +0 -21
- package/dist/lib/commands/review.mjs +0 -24
- package/dist/lib/commands/run.mjs +0 -10
- package/dist/lib/commands/scaffold.mjs +0 -124
- package/dist/lib/commands/scan.mjs +0 -42
- package/dist/lib/commands/stability.mjs +0 -2
- package/dist/lib/commands/stats.mjs +0 -4
- package/dist/lib/commands/suggest.mjs +0 -62
- package/dist/lib/commands/syncAuto.mjs +0 -1
- package/dist/lib/commands/test.mjs +0 -6
- package/dist/lib/commands/theme.mjs +0 -18
- package/dist/lib/commands/upgrade.mjs +0 -20
- package/dist/lib/commands/watch.mjs +0 -7
- package/dist/lib/commands/why.mjs +0 -4
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,41 +1,38 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
`)}const v={scan:"scan",check:"check",status:"status",freeze:"freeze",thaw:"thaw",why:"why",impact:"impact",graph:"graph",stability:"stability",scaffold:"scaffold",explain:"explain",test:"test",coverage:"coverage",suggest:"suggest",run:"run",implement:"implement","doc-gate":"doc-gate","pr-impact":"pr-impact",review:"review",demo:"demo",upgrade:"upgrade",context:"context",sync:"sync"},b={publish:"publish",changelog:"changelog",diff:"diff",monorepo:"monorepo",ci:"ci",ai:"ai",theme:"theme",stats:"stats",feedback:"feedback",telemetry:"telemetry",uninstall:"uninstall","generate-skills":"generate-skills","install-cursor-hooks":"install-cursor-hooks","install-vscode-copilot-hooks":"install-vscode-copilot-hooks",setup:"setup"},M={"Memory (top-level)":["log","ask","switch","recap","status"],"Watch (top-level)":["watch"],"Setup (top-level)":["init","doctor"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"],"Contract (use: infernoflow contract <verb>)":Object.keys(v),"Dev (use: infernoflow dev <verb>)":Object.keys(b)};function R(){return Object.entries(M).map(([t,n])=>` ${m(t+":")}
|
|
6
|
-
${n.join(" ")}`).join(`
|
|
2
|
+
(function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const s={"\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(s).join("|"),"g");function c(l){const h=l.write.bind(l);l.write=function(a,...w){if(typeof a=="string")a=a.replace(r,p=>s[p]);else if(Buffer.isBuffer(a)){const p=a.toString("utf8").replace(r,$=>s[$]);a=Buffer.from(p,"utf8")}return h(a,...w)}}c(process.stdout),c(process.stderr)})();import{readFileSync as C}from"node:fs";import{dirname as k,join as f}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as u}from"../lib/ui/output.mjs";const A=k(v(import.meta.url));function I(o){for(const s of[f(o,"..","..","package.json"),f(o,"..","package.json")])try{return JSON.parse(C(s,"utf8"))}catch{}return{version:"0.0.0-source"}}const M=I(A),m=M.version||"0.0.0",y={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",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),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 U(){const o=Object.keys(y),s=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(y).map(([r,c])=>` ${r.padEnd(s," ")}${c}`).join(`
|
|
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 S(){return Object.entries(O).map(([o,s])=>` ${i(o+":")}
|
|
4
|
+
${s.join(" ")}`).join(`
|
|
7
5
|
|
|
8
|
-
`)}const
|
|
9
|
-
${
|
|
10
|
-
${
|
|
6
|
+
`)}const g=Object.keys(d).length,R=`
|
|
7
|
+
${i("\u{1F525} infernoflow")} ${e("v"+m)}
|
|
8
|
+
${e("Persistent memory for AI coding sessions")}
|
|
11
9
|
|
|
12
|
-
${
|
|
10
|
+
${i("Usage:")}
|
|
13
11
|
infernoflow [command] [options]
|
|
14
12
|
|
|
15
|
-
${
|
|
16
|
-
${
|
|
17
|
-
${
|
|
18
|
-
${
|
|
19
|
-
${
|
|
20
|
-
${
|
|
13
|
+
${i("Memory")} ${e("\u2014 the 5-command core")}
|
|
14
|
+
${t("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
|
|
15
|
+
${t("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
|
|
16
|
+
${t("switch")} Generate handoff for next AI agent
|
|
17
|
+
${t("recap")} End-of-session health score + unlogged changes
|
|
18
|
+
${t("status")} Quick health check
|
|
21
19
|
|
|
22
|
-
${
|
|
23
|
-
${
|
|
24
|
-
${
|
|
25
|
-
${
|
|
20
|
+
${i("Setup")}
|
|
21
|
+
${t("init")} 60-second setup ${e("(memory mode by default)")}
|
|
22
|
+
${t("setup")} Re-run IDE wiring ${e("(idempotent \u2014 MCP + hooks)")}
|
|
23
|
+
${t("doctor")} Diagnose your setup
|
|
24
|
+
${t("context")} Generate AI-ready context for new sessions
|
|
26
25
|
|
|
27
|
-
${
|
|
28
|
-
${
|
|
29
|
-
${a("contract")} Capability contracts ${o("(scan, freeze, impact, scaffold, \u2026)")}
|
|
30
|
-
${a("dev")} Publishing, AI providers, hooks ${o("(publish, ai, ci, \u2026)")}
|
|
26
|
+
${i("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
|
|
27
|
+
${t("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
|
|
31
28
|
|
|
32
|
-
${
|
|
33
|
-
${
|
|
34
|
-
`;import*as
|
|
35
|
-
${
|
|
36
|
-
`),console.log(
|
|
37
|
-
${
|
|
38
|
-
`),process.exit(0));const
|
|
39
|
-
Unknown command: ${
|
|
40
|
-
`)),process.exit(1));const
|
|
41
|
-
Error: `)+
|
|
29
|
+
${e("Run")} ${t("infernoflow commands")} ${e("to see all "+g+" commands grouped.")}
|
|
30
|
+
${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
|
|
31
|
+
`;import*as b from"node:fs";import*as E from"node:path";try{const o=E.join(process.cwd(),"inferno");if(b.existsSync(o)){const{observeCommandStart:s}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&s(o,r)}}catch{}const[,,n,...x]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(R),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(m),process.exit(0)),n==="commands"&&(console.log(`
|
|
32
|
+
${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+g+" commands")}
|
|
33
|
+
`),console.log(S()),console.log(`
|
|
34
|
+
${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
|
|
35
|
+
`),process.exit(0));const P=Object.keys(d);P.includes(n)||(console.error(u(`
|
|
36
|
+
Unknown command: ${n}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
|
|
37
|
+
`)),process.exit(1));const j=[n,...x];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(m,n)}catch{}d[n](j).catch(o=>{console.error(u(`
|
|
38
|
+
Error: `)+o.message),process.exit(1)});
|
package/dist/lib/amp/io.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as
|
|
2
|
-
`).filter(Boolean).map(
|
|
3
|
-
|
|
4
|
-
`,"utf8"),!0}function
|
|
5
|
-
`).filter(Boolean);let
|
|
6
|
-
`,"utf8"),
|
|
1
|
+
import*as i from"node:fs";import*as F from"node:os";import*as r from"node:path";import{findProjectRoot as m}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 u=JSON.parse(i.readFileSync(r.join(t,"amp.json"),"utf8"));u&&typeof u.globalDir=="string"&&u.globalDir.trim()&&(o=u.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 g(e,t={}){const o=t.literal?r.resolve(e):m(e),n=r.join(o,".ai-memory"),f=r.join(o,"inferno"),u=t.forWrite||i.existsSync(n)||!i.existsSync(f),c=u?n:f,s=u,l=r.join(c,"branches"),a=b(o),S=r.join(l,`${a.currentSlug}.jsonl`),j=a.defaultSlug?r.join(l,`${a.defaultSlug}.jsonl`):null,h=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:h,branchesDir:l,currentBranchFile:S,defaultBranchFile:j,branch:a}}function A(e,t){try{return N(e,t)}catch{return r.join(t,"global.jsonl")}}function y(e){const t=m(e),o=r.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const d="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function k(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=d[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=d[Math.floor(Math.random()*32)];return t+o}function p(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 u=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:u,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 J(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:u,result:c,...s}=t;return Object.keys(s).length&&(n.meta=s),n}function M(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(J)}catch{return[]}}function P(e){const t=g(e),o=new Set,n=[],f=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],u=[...new Set(f.filter(Boolean))];for(const c of u)for(const s of M(c)){const l=s.id||`${s.ts}|${s.summary}`;o.has(l)||(o.add(l),n.push(s))}return n.sort((c,s)=>{const l=typeof c.ts=="number"?c.ts:Date.parse(c.ts||0),a=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0);return l-a})}function w(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){y(e);const o=g(e,{forWrite:!0}),n=w(o,t),f=p(t),u=JSON.stringify(f)+`
|
|
3
|
+
`;if(i.mkdirSync(r.dirname(n),{recursive:!0}),i.appendFileSync(n,u,"utf8"),n!==o.sessions)try{i.mkdirSync(r.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,u,"utf8")}catch{}return f}function C(e){const{config:t}=g(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function L(e,t={}){y(e);const{config:o}=g(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)+`
|
|
4
|
+
`,"utf8"),!0}function _(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"};y(e);const u=i.readFileSync(o,"utf8").split(`
|
|
5
|
+
`).filter(Boolean);let c=0;for(const s of u)try{const l=JSON.parse(s),a=p(l);i.appendFileSync(f,JSON.stringify(a)+`
|
|
6
|
+
`,"utf8"),c++}catch{}return i.writeFileSync(r.join(n,"MIGRATED.md"),`# Migrated from inferno/
|
|
7
7
|
|
|
8
|
-
Copied ${
|
|
8
|
+
Copied ${c} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
|
|
9
9
|
|
|
10
10
|
The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
|
|
11
|
-
`,"utf8"),{migrated:
|
|
11
|
+
`,"utf8"),{migrated:c,reason:"ok"}}export{x as AMP_VERSION,g as ampPaths,$ as appendEntry,y as ensureAmpDir,J as fromAmp,k as generateULID,_ as migrateLegacy,B as projectSlug,C as readConfig,P as readEntries,N as resolveGlobalFile,p as toAmp,L as writeDefaultConfig};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import*as m from"node:fs";import*as p from"node:path";const d="# >>> infernoflow:start",s="# <<< infernoflow:end",g=["",d,"# Personal memory (per-developer, per-machine). Sync via cloud folder","# or `infernoflow sync`, not git.",".ai-memory/global.jsonl",".ai-memory/sessions.jsonl","# Regenerated artifacts \u2014 never commit these.",".ai-memory/handoff.md",".ai-memory/CONTEXT.draft.md",".ai-memory/HANDOFF.md",".ai-memory/.last-cli-version","# Build/publish hygiene \u2014 don't ship memory in published .NET / monorepo bundles.","**/publish/.ai-memory/","**/publish/inferno/","**/dist/.ai-memory/","**/dist/inferno/",s,""].join(`
|
|
2
|
+
`),I=["",d,"# Branch-local memory: append-only JSONL files. Auto-merge concurrent","# additions from different machines/branches as union of lines so","# `home \u2192 work \u2192 home` syncs don't produce conflicts.",".ai-memory/branches/*.jsonl merge=union",s,""].join(`
|
|
3
|
+
`),T="# --- infernoflow (developer-local AI memory; do not commit) ---",y="# --- /infernoflow ---";function A(n){const r=n.indexOf(T),i=n.indexOf(y);if(r===-1||i===-1||i<=r)return n;const o=n.slice(0,r).replace(/\s+$/,""),e=n.slice(i+y.length).replace(/^\s+/,"");return(o?o+`
|
|
4
|
+
`:"")+(e||"")}function h(n,r,i){const o=p.join(n,r);let e="";try{e=m.readFileSync(o,"utf8")}catch{}e=A(e);const c=e.indexOf(d),a=e.indexOf(s),l=i.trim();let t;if(c!==-1&&a!==-1&&a>c){const f=e.slice(0,c).replace(/\s+$/,""),u=e.slice(a+s.length).replace(/^\s+/,"");t=(f?f+`
|
|
5
|
+
|
|
6
|
+
`:"")+l+`
|
|
7
|
+
`+(u?`
|
|
8
|
+
`+u:"")}else e?t=e.replace(/\s+$/,"")+`
|
|
9
|
+
|
|
10
|
+
`+l+`
|
|
11
|
+
`:t=l+`
|
|
12
|
+
`;return t===e?"unchanged":(m.mkdirSync(p.dirname(o),{recursive:!0}),m.writeFileSync(o,t,"utf8"),e?"updated":"created")}function E(n){return{gitignore:h(n,".gitignore",g),gitattributes:h(n,".gitattributes",I)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
|
package/dist/lib/commands/ai.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as g from"node:fs";import*as
|
|
2
|
-
`)}const m=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function _(n){return new Promise(o=>{const t=new URL(n),e=(t.protocol==="https:"?x:C).request({hostname:t.hostname,port:t.port||(t.protocol==="https:"?443:80),path:t.pathname+(t.search||""),method:"GET",timeout:5e3},s=>{let a="";s.on("data",c=>a+=c),s.on("end",()=>{try{o({status:s.statusCode,body:JSON.parse(a)})}catch{o({status:s.statusCode,body:a})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function b(n,o){const l={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[n],e=o[n]?.apiKey,s=l||e,a=l?"env":e?"integrations.json":null,c=o[n]?.model||m.find(u=>u.id===n)?.default;if(n==="ollama"){const u=await _("http://localhost:11434/api/tags").catch(()=>null);if(u?.status===200){const p=u.body?.models?.map(f=>f.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:p}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!s,source:a,model:c,available:null,masked:s?s.slice(0,8)+"\u2026":null}}async function N(n,o,t){try{const
|
|
1
|
+
import*as g from"node:fs";import*as A from"node:path";import*as x from"node:https";import*as C from"node:http";import*as R from"node:readline";import{bold as d,cyan as v,gray as i,green as r,yellow as y,red as h}from"../ui/output.mjs";function O(n){return A.join(n,"inferno")}function k(n){const o=A.join(O(n),"integrations.json");if(!g.existsSync(o))return{};try{return JSON.parse(g.readFileSync(o,"utf8"))}catch{return{}}}function E(n,o){const t=O(n);g.existsSync(t)||g.mkdirSync(t,{recursive:!0}),g.writeFileSync(A.join(t,"integrations.json"),JSON.stringify(o,null,2)+`
|
|
2
|
+
`)}const m=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function _(n){return new Promise(o=>{const t=new URL(n),e=(t.protocol==="https:"?x:C).request({hostname:t.hostname,port:t.port||(t.protocol==="https:"?443:80),path:t.pathname+(t.search||""),method:"GET",timeout:5e3},s=>{let a="";s.on("data",c=>a+=c),s.on("end",()=>{try{o({status:s.statusCode,body:JSON.parse(a)})}catch{o({status:s.statusCode,body:a})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function b(n,o){const l={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[n],e=o[n]?.apiKey,s=l||e,a=l?"env":e?"integrations.json":null,c=o[n]?.model||m.find(u=>u.id===n)?.default;if(n==="ollama"){const u=await _("http://localhost:11434/api/tags").catch(()=>null);if(u?.status===200){const p=u.body?.models?.map(f=>f.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:p}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!s,source:a,model:c,available:null,masked:s?s.slice(0,8)+"\u2026":null}}async function N(n,o,t){try{const l=await import("../ai/providerRouter.mjs");if(typeof l.callAI!="function")return null;const e=`Reply with exactly: "infernoflow AI test OK \u2014 ${n}"`;return await l.callAI(e,t,n)}catch{return null}}function $(n,o){return new Promise(t=>n.question(o,t))}async function U(n){const o=k(n);console.log(),console.log(` ${d("infernoflow ai")} ${i("\u2014 provider status")}`),console.log();let t=!1;for(const l of m){const e=await b(l.id,o);e.configured&&(t=!0);const s=e.configured?r("\u2713"):i("\u25CB"),a=d(l.name.padEnd(22)),c=e.configured?`${r("configured")} ${i(e.source)} ${i("model: "+e.model)}${e.masked?" "+i(e.masked):""}`:i("not configured");console.log(` ${s} ${a} ${c}`)}console.log(),t?console.log(` ${i("Run")} ${v("infernoflow ai test")} ${i("to verify the active provider.")}`):(console.log(` ${y("No AI providers configured.")} Run: ${v("infernoflow ai setup")}`),console.log(` ${i("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function G(n){const o=k(n),t={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY};console.log(),console.log(` ${d("\u{1F525} infernoflow ai setup")}`),console.log(` ${i("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),m.forEach((e,s)=>{const a=t[e.id],c=o[e.id]?.apiKey,u=a?r(" \u2713 key detected in environment"):c?r(" \u2713 key already saved"):"",p=d(String(s+1)),f=e.id==="ollama"?i(" (local, no key needed)"):"";console.log(` ${p}) ${d(e.name.padEnd(22))}${f}${u}`)}),console.log();const l=R.createInterface({input:process.stdin,output:process.stdout});try{const e=await $(l," Select provider [1]: "),s=(parseInt(e.trim())||1)-1;if(s<0||s>=m.length){console.log(h(` Invalid choice. Enter a number 1\u2013${m.length}.`));return}const a=m[s],c=a.id;if(console.log(),console.log(` ${d(a.name)}`),c==="ollama"){const p=await $(l," Ollama host [http://localhost:11434]: "),f=await $(l,` Model [${a.default}]: `);o.ollama={host:p.trim()||"http://localhost:11434",model:f.trim()||a.default},E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await _(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(r("OK")):(console.log(y("not reachable")),console.log(` ${y("\u26A0")} Start Ollama first: ${v("ollama serve")}`))}else{const p=t[c],f=o[c]?.apiKey,I=p||f;if(I){const w=p?"environment variable":"saved config";if(console.log(` ${r("\u2713")} API key detected from ${w}: ${i(I.slice(0,12)+"\u2026")}`),(await $(l," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),a.docsUrl&&console.log(` ${i("Get a key at:")} ${v(a.docsUrl)}`);const K=await $(l," Paste new API key: ");if(!K.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:K.trim(),model:o[c]?.model||a.default}}else o[c]={apiKey:I,model:o[c]?.model||a.default}}else{console.log(` ${i("Get your API key at:")} ${v(a.docsUrl)}`),console.log(` ${i("Tip: paste the key below \u2014 it starts with")} ${i(a.keyHint)}`),console.log();const w=await $(l," Paste API key: ");if(!w.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:w.trim(),model:a.default}}const P=o[c].model;console.log(),console.log(` ${i("Available models:")} ${a.models.join(" ")}`);const S=await $(l,` Model [${P}]: `);o[c].model=S.trim()||P,E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await N(c,o,n))?.text?console.log(r("OK")+i(` (${o[c].model})`)):(console.log(y("no response")),console.log(` ${y("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${r("\u2713")} ${d(a.name)} is ready.`),console.log(` ${i("AI-powered commands:")} explain why review changelog`),console.log();const u=A.join(n,".gitignore");g.existsSync(u)&&(g.readFileSync(u,"utf8").includes("integrations.json")||(console.log(` ${y("\u26A0")} Add ${v("inferno/integrations.json")} to your .gitignore to avoid committing your API key.`),console.log()))}finally{l.close()}}async function Y(n,o){const t=k(o),l=n.find(s=>!s.startsWith("--"))||null;console.log(),console.log(` ${d("infernoflow ai test")}`),console.log();const e=l?m.filter(s=>s.id===l):m;for(const s of e){if(!(await b(s.id,t)).configured){console.log(` ${i("\u25CB")} ${d(s.name.padEnd(22))} ${i("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${y("\u2026")} ${d(s.name.padEnd(22))} testing\u2026 `);const c=await N(s.id,t,o);c?.text?(console.log(r("OK")+i(` (${c.model||s.id})`)),console.log(` ${i(c.text.trim().slice(0,80))}`)):(console.log(h("FAIL")),console.log(` ${h("No response \u2014 check API key or model name")}`))}console.log()}async function T(n,o){const t=k(o),l=n.find(e=>!e.startsWith("--"));if(l||(console.error(h("\u2717 Usage: infernoflow ai clear <provider>")),console.error(i(" Example: infernoflow ai clear openai")),process.exit(1)),!t[l]){console.log(i(` No config found for "${l}"`));return}delete t[l],E(o,t),console.log(r(` \u2713 Cleared config for ${l}`))}async function L(n){const o=(n||[]).slice(1),t=o.find(s=>!s.startsWith("--"))||"status",l=o.filter(s=>s!==t),e=process.cwd();switch(t){case"setup":return G(e);case"status":return U(e);case"test":return Y(l,e);case"clear":return T(l,e);default:console.error(h(`\u2717 Unknown subcommand: "${t}"`)),console.error(i(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{L as aiCommand};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as m from"node:fs";import*as
|
|
2
|
-
`).filter(Boolean)
|
|
3
|
-
Unknown amp verb: ${
|
|
4
|
-
`)),process.exit(1)}export{
|
|
1
|
+
import*as m from"node:fs";import*as p from"node:path";import{bold as r,cyan as d,gray as s,green as h,yellow as S,red as y}from"../ui/output.mjs";import{AMP_VERSION as v,ampPaths as b,readEntries as P,migrateLegacy as w}from"../amp/io.mjs";const A=new Set(["gotcha","decision","attempt","note","detection","pattern"]),x=new Set(["copilot","cursor","claude","windsurf","other"]);function E(o,e){const n=[];return(!o||typeof o!="object")&&n.push("not an object"),o.type?A.has(o.type)||n.push(`type "${o.type}" not in AMP enum`):n.push("missing required field: type"),o.msg?typeof o.msg!="string"?n.push("msg must be a string"):o.msg.length>500&&n.push(`msg too long (${o.msg.length} > 500)`):n.push("missing required field: msg"),o.ts==null?n.push("missing required field: ts"):(typeof o.ts!="number"||!Number.isFinite(o.ts))&&n.push("ts must be a Unix-ms integer"),o.id&&!/^amp_[0-9A-Z]{26}$/.test(o.id)&&n.push(`id "${o.id}" not in amp_ULID format`),o.tool&&!x.has(o.tool)&&n.push(`tool "${o.tool}" not in AMP enum`),o.confidence!=null&&(o.confidence<0||o.confidence>1)&&n.push("confidence out of range [0,1]"),n}function k(o){const e=b(o),n=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile],c=[...new Set(n.filter(Boolean))];try{if(m.existsSync(e.branchesDir))for(const t of m.readdirSync(e.branchesDir)){if(!t.endsWith(".jsonl"))continue;const l=p.join(e.branchesDir,t);c.includes(l)||c.push(l)}}catch{}const a=[];for(const t of c){if(!m.existsSync(t))continue;let l;try{l=m.readFileSync(t,"utf8").split(`
|
|
2
|
+
`).filter(Boolean)}catch{continue}l.forEach((g,f)=>{try{a.push({ok:!0,file:t,line:f+1,value:JSON.parse(g)})}catch(i){a.push({ok:!1,file:t,line:f+1,value:null,error:i.message})}})}return{sessions:e.sessions,entries:a,lines:[],files:c}}function j(o){const{sessions:e,isAmp:n,root:c}=b(o),a=p.join(o,".ai-memory"),t=p.join(o,"inferno"),l=m.existsSync(a),g=m.existsSync(t)&&m.existsSync(p.join(t,"sessions.jsonl"));if(console.log(),console.log(` ${r("\u{1F525} infernoflow amp")} ${s("\u2014 AI Memory Protocol status")}`),console.log(),console.log(` Spec version ${r(v)}`),console.log(` Conformance level ${r("AMP Full")} ${s("(read + write + handoff + injection)")}`),console.log(),!l&&!g){console.log(` ${S("\u26A0")} ${s("Project not initialised \u2014 run:")} ${d("infernoflow init")}`),console.log();return}if(console.log(` ${r("Layout:")}`),console.log(` .ai-memory/ ${l?h("\u2714 present")+(n?s(" (active)"):""):s("\u2014")}`),console.log(` inferno/ ${g?S("\u26A0 legacy")+(n?"":s(" (active)")):s("\u2014")}`),console.log(),m.existsSync(e)){const f=P(o);if(console.log(` ${r("Memory:")}`),console.log(` file ${s(p.relative(o,e))}`),console.log(` entries ${r(String(f.length))}`),f.length){const i=new Map;for(const u of f)i.set(u.type,(i.get(u.type)||0)+1);const $=[...i.entries()].map(([u,M])=>`${u}:${M}`).join(" ");console.log(` breakdown ${s($)}`)}}g&&!l&&(console.log(),console.log(` ${s("Tip:")} ${d("infernoflow amp migrate")} ${s("copies legacy memory into the AMP layout.")}`)),console.log()}function D(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp migrate")}`),console.log();const e=w(o);e.migrated>0?(console.log(` ${h("\u2714")} Migrated ${r(String(e.migrated))} entr${e.migrated===1?"y":"ies"} \u2192 ${d(".ai-memory/sessions.jsonl")}`),console.log(` ${s("The original inferno/sessions.jsonl is untouched. See .ai-memory/MIGRATED.md for details.")}`)):console.log(` ${s("Nothing migrated \u2014 ")}${e.reason}${s(".")}`),console.log()}function F(o){console.log(),console.log(` ${r("\u{1F525} infernoflow amp validate")}`),console.log();const{sessions:e,entries:n}=k(o);if(!n.length){console.log(` ${s("No entries to validate at")} ${d(p.relative(o,e))}`),console.log();return}let c=0,a=0,t=0;const l=[];for(const i of n){const $=p.relative(o,i.file);if(!i.ok){a++,l.push({file:$,line:i.line,error:`parse error: ${i.error}`});continue}const u=E(i.value,i.line);u.length?(t++,l.push({file:$,line:i.line,error:u.join("; ")})):c++}for(const i of l.slice(0,10))console.log(` ${y("\u2718")} ${i.file}:${i.line} ${s(i.error)}`);l.length>10&&console.log(` ${s(`\u2026 ${l.length-10} more`)}`),l.length&&console.log();const g=n.length,f=a===0&&t===0;console.log(f?` ${h("\u2714")} ${r(`${c}/${g}`)} entries conform to AMP v${v}.`:` ${y("\u2718")} ${r(`${c}/${g}`)} entries OK \xB7 ${a} parse \xB7 ${t} schema`),console.log(),f||process.exit(1)}function N(){console.log(v)}async function L(o){const e=o[1],n=process.cwd();if(!e||e==="status")return j(n);if(e==="migrate")return D(n);if(e==="validate")return F(n);if(e==="version")return N();if(e==="--help"||e==="-h")return j(n);console.error(y(`
|
|
3
|
+
Unknown amp verb: ${e}`)),console.error(s(` Try: infernoflow amp (status | migrate | validate | version)
|
|
4
|
+
`)),process.exit(1)}export{L as ampCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"node:fs";import
|
|
1
|
+
import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as R,yellow as I,red as W}from"../ui/output.mjs";import{readEntries as j}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},D={gotcha:I,decision:R,attempt:T,preference:T,theme:T,note:s,error:W,handoff:s};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function q(o,t){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let l=0;for(const r of t)(o.summary||"").toLowerCase().includes(r)&&(l+=3),n.includes(r)&&(l+=1),n.some(e=>e.startsWith(r)||r.startsWith(e))&&(l+=.5);return l}function L(o){if(!o)return"";const t=new Date(o),i=Date.now()-t.getTime(),n=Math.floor(i/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function M(o,t){const i=o.type||"note",n=S[i]||"\xB7",l=D[i]||s,r=o.result?s(` [${o.result}]`):"",e=o.agent?s(` \u2014 ${o.agent}`):"",f=s(` (${L(o.ts)})`);let a=o.summary||"";if(t)for(const u of t){const p=new RegExp(`(${u})`,"gi");a=a.replace(p,$=>O($))}console.log(` ${l(n+" "+i.padEnd(11))}${r}${e}${f}`),console.log(` ${a}`)}function P(o){return j(o)}function Y(o,t,i,n){let l=o;i&&(l=l.filter(e=>(e.type||"note")===i));let r;return t.length>0?r=l.map(e=>({entry:e,score:q(e,t)})).filter(({score:e})=>e>0).sort((e,f)=>{if(f.score!==e.score)return f.score-e.score;const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):r=l.map(e=>({entry:e,score:1})).sort((e,f)=>{const a=d[e.entry.type]??9,u=d[f.entry.type]??9;return a!==u?a-u:new Date(f.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),r.slice(0,n||20)}async function U(o=[]){const t=o,i=t.indexOf("--type"),n=i!==-1?t[i+1]:null,l=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),r=l!==-1?parseInt(t[l+1]||"20",10):15,e=t.includes("--json"),f=t.includes("--recent")||t.includes("-r"),a=t.slice(1),p=a.filter((c,g)=>!(c.startsWith("--")||g>0&&a[g-1].startsWith("--"))).join(" ").trim(),$=f?[]:E(p),k=process.cwd(),m=P(k);if(m.length===0){if(e){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
|
|
2
2
|
No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
|
|
3
|
-
`));return}const
|
|
3
|
+
`));return}const y=f?m.slice(-r).reverse().map(c=>({entry:c,score:1})):Y(m,$,n,r);if(e){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:c,score:g})=>({...c,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${O("\u{1F525} infernoflow ask")} ${T(`"${p}"`)}${n?s(` [${n}]`):""}`:f?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${n?s(` [${n}]`):""}`),console.log(s(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(s(` No entries found for "${p}"`)),n&&console.log(s(` Try removing --type ${n} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:c,score:g}of y){const h=c.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:c,score:g})}const C=Object.keys(d).sort((c,g)=>d[c]-d[g]);let x=0;for(const c of C){const g=w.get(c);if(!g?.length)continue;console.log();const h=D[c]||s;console.log(h(` ${S[c]} ${c.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:N}of g)console.log(),M(N,$),x++}console.log(),console.log(s(` ${x} result${x!==1?"s":""} from ${m.length} total entries`)),y.length===r&&m.length>r&&console.log(s(" Use --limit N to see more")),console.log()}export{U as askCommand};
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
`))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w")
|
|
1
|
+
import p from"node:fs";import b from"node:path";import{execSync as h}from"node:child_process";import{bold as I,gray as d,cyan as l,red as H,green as a,yellow as F}from"../ui/output.mjs";import{buildCursorImplementPrompt as wt,buildGenericImplementPrompt as yt}from"../ui/prompts.mjs";import{detectDrift as Ct}from"../git/detect-drift.mjs";function kt(e){try{const n=process.platform;if(n==="win32")h("clip",{input:e});else if(n==="darwin")h("pbcopy",{input:e});else try{h("xclip -selection clipboard",{input:e})}catch{h("xsel --clipboard --input",{input:e})}return!0}catch{return!1}}const w="inferno",O=b.join(w,"CONTEXT.md"),K=b.join(w,"context-state.json");function M(e){try{return JSON.parse(p.readFileSync(e,"utf8"))}catch{return null}}function W(e){try{return p.readFileSync(e,"utf8")}catch{return null}}function q(){const e=W(K);if(!e)return{};try{return JSON.parse(e)}catch{return{}}}function z(e){p.writeFileSync(K,JSON.stringify(e,null,2),"utf8")}function E(e){return e?new Date(e).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function St(e,n){if(!e)return[];const i=[];let c=null;for(const r of e.split(`
|
|
2
|
+
`))if(r.startsWith("## ")){if(c&&i.length<n&&i.push(c),i.length>=n)break;c={title:r.replace("## ","").trim(),items:[]}}else c&&r.startsWith("- ")&&c.items.push(r.replace("- ","").trim());return c&&i.length<n&&i.push(c),i.filter(r=>r.items.length>0)}async function bt(e){const n=o=>e.includes(o),i=o=>{const u=e.indexOf(o);return u!==-1&&e[u+1]?e[u+1]:null},c=i("--intent")||i("-i"),r=i("--working")||i("-w"),x=i("--decision")||i("-d"),Q=n("--show")||n("-s"),G=n("--copy")||n("-c"),Y=n("--cursor"),Z=n("--copilot"),tt=n("--reset"),et=n("--watch"),P=n("--auto-commit")||n("--auto-push"),N=n("--auto-push"),R=parseInt(i("--interval")||"30",10)*1e3;console.log(`
|
|
3
3
|
`+I("\uFFFD\uFFFD\uFFFD infernoflow \u2014 context")),console.log(" "+"\u2500".repeat(50)+`
|
|
4
|
-
`),
|
|
5
|
-
`)),process.exit(1));const f=
|
|
6
|
-
`)),process.exit(1));let t=
|
|
7
|
-
`))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"')))
|
|
8
|
-
`),
|
|
4
|
+
`),p.existsSync(w)||(console.error(H(" \u2718 inferno/ not found")),console.error(d(` \u2192 Run: infernoflow init
|
|
5
|
+
`)),process.exit(1));const f=M(b.join(w,"contract.json")),v=M(b.join(w,"capabilities.json")),nt=W(b.join(w,"CHANGELOG.md"));(!f||!v)&&(console.error(H(` \u2718 Missing contract.json or capabilities.json
|
|
6
|
+
`)),process.exit(1));let t=q();tt&&(t={},console.log(F(` \u26A0 State reset
|
|
7
|
+
`))),c&&(t.intent=c,t.intentUpdated=new Date().toISOString(),console.log(a(' \u2714 Intent saved: "'+c+'"'))),r&&(t.working=r,t.workingUpdated=new Date().toISOString(),console.log(a(' \u2714 Working on: "'+r+'"'))),x&&(t.decisions||(t.decisions=[]),t.decisions.push({text:x,date:new Date().toISOString()}),console.log(a(' \u2714 Decision recorded: "'+x+'"'))),(c||r||x)&&z(t);const $=v.capabilities||[],A=$.length===(f.capabilities||[]).length,U=St(nt,3),D=String(f.policyVersion).replace(/^v/i,""),ot=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),B=A?"\u2713 validated":"\u26A0 out of sync",T=t.intent||"describe the exact task to implement",J={task:T,contract:f,caps:v,scenarios:[],state:t},it=wt(J),st=yt(J),ct=$.map(o=>"- **"+o.id+"** \u2014 "+o.title).join(`
|
|
8
|
+
`),rt=U.length>0?U.map(o=>"### "+o.title+`
|
|
9
9
|
`+o.items.map(u=>" - "+u).join(`
|
|
10
10
|
`)).join(`
|
|
11
11
|
|
|
12
|
-
`):"_No recent changes_",
|
|
13
|
-
`):"_No decisions recorded_",
|
|
14
|
-
`);if(
|
|
15
|
-
\u2714 Context written \u2192 `+
|
|
16
|
-
`+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+
|
|
17
|
-
`),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${
|
|
12
|
+
`):"_No recent changes_",lt=t.intent?t.intent+" _("+E(t.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',at=t.working?t.working+" _("+E(t.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',dt=t.decisions&&t.decisions.length>0?t.decisions.slice(-5).map(o=>"- "+o.text+" _("+E(o.date)+")_").join(`
|
|
13
|
+
`):"_No decisions recorded_",j=["# Project Context \u2014 "+f.policyId+" v"+D,"> Generated by infernoflow | "+ot+" | "+B,"","---","","## What this system does","",ct,"","---","","## Recent changes","",rt,"","---","","## Current state","","- **Capabilities:** "+$.length,"- **Version:** v"+D,"- **Sync:** "+B,"","---","","## What I am working on right now","",at,"","---","","## Intent \u2014 what I want to build next","",lt,"","---","","## Decisions & notes","",dt,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${T}" --mode both`,"```","","### Cursor Agent Prompt","","```text",it,"```","","### Generic Agent Prompt","","```text",st,"```","","---","_Paste this block at the start of any new AI session._"].join(`
|
|
14
|
+
`);if(Q||(p.writeFileSync(O,j,"utf8"),console.log(a(`
|
|
15
|
+
\u2714 Context written \u2192 `+O))),G){const o=kt(j);console.log(o?a(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):F(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(Y&&(p.writeFileSync(".cursorrules",j,"utf8"),console.log(a(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),Z&&(p.existsSync(".github")||p.mkdirSync(".github"),p.writeFileSync(".github/copilot-instructions.md",j,"utf8"),console.log(a(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
|
|
16
|
+
`+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+f.policyId+" \u2014 v"+D),console.log(" Capabilities "+$.length+" registered"),console.log(" Sync "+(A?a("\u2713 in sync"):F("\u26A0 check needed"))),console.log(" Working on "+(t.working?l(t.working):d("not set"))),console.log(" Intent "+(t.intent?l(t.intent):d("not set"))),console.log(" Decisions "+(t.decisions?t.decisions.length:0)+` recorded
|
|
17
|
+
`),console.log(" "+I("Implementation Prompt")),console.log(" "+l("\u2192")+" Run "+l(`infernoflow implement "${T}" --mode both`)+`
|
|
18
18
|
`),G?(console.log(" "+I("Ready to use:")),console.log(" "+l("\u2192")+" Paste into Claude / Cursor / Copilot with "+l("Ctrl+V")+`
|
|
19
19
|
`)):(console.log(" "+I("Ready to use:")),console.log(" "+l("1.")+" Open "+l("inferno/CONTEXT.md")),console.log(" "+l("2.")+" Copy everything"),console.log(" "+l("3.")+" Paste at the start of your next AI session"),console.log(" "+d(" tip: use --copy to skip steps 1-2 automatically")+`
|
|
20
|
-
`)),
|
|
21
|
-
`));let u="",V=null;const
|
|
20
|
+
`)),et){let L=function(g){try{return h(g,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},gt=function(g){try{return h(`git status --porcelain "${g}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},pt=function(g,s,_){const C=`chore: update context [${s.length>0?s.slice(0,3).join(", "):`${_} files`}]`;return L(`git add "${g}"`)?gt(g)?{ok:!1,reason:"nothing to commit"}:L(`git commit -m "${C}"`)?{ok:!0,msg:C}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},ut=function(){return L("git push")};const o=N?"auto-push":P?"auto-commit":"watch";console.log(" "+l("\u{1F441} Watch mode active")+d(` \u2014 polling every ${R/1e3}s`+(N?" \xB7 will commit + push on change":P?" \xB7 will commit on change":""))),console.log(" "+d(`Press Ctrl+C to stop
|
|
21
|
+
`));let u="",V=null;const X=async()=>{try{const g=process.cwd(),s=Ct(g,{sinceCommits:1}),_=s.changedFiles.sort().join("|");if(_===u||(u=_,s.changedFiles.length===0))return;const y=s.affectedCapabilities.map(S=>S.id),C=y.length>0?`Working on: ${y.join(", ")} (${s.changedFiles.length} files changed)`:`${s.changedFiles.length} files changed \u2014 no capability match yet`,k=q();if(k.working!==C){k.working=C,k.workingUpdated=new Date().toISOString(),z(k),await bt(e.filter(m=>m!=="--watch"&&m!=="--auto-commit"&&m!=="--auto-push"));const S=W(O),ft=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
|
|
22
22
|
${a("\u2714")} [${ft}] Context updated \u2014 ${y.length} capabilities affected
|
|
23
23
|
${d(s.changedFiles.slice(0,3).join(", ")+(s.changedFiles.length>3?` +${s.changedFiles.length-3} more`:""))}
|
|
24
|
-
`),
|
|
25
|
-
`),
|
|
24
|
+
`),P&&S!==V){V=S;const m=pt(O,y,s.changedFiles.length);if(m.ok){if(process.stderr.write(` ${a("\u2714")} Committed: ${d(m.msg)}
|
|
25
|
+
`),N){const ht=ut();process.stderr.write(ht?` ${a("\u2714")} Pushed to origin
|
|
26
26
|
`:` ${F("\u26A0")} Push failed \u2014 will retry next change
|
|
27
27
|
`)}}else process.stderr.write(` ${F("\u26A0")} Commit skipped: ${d(m.reason)}
|
|
28
|
-
`)}}}catch{}};await
|
|
28
|
+
`)}}}catch{}};await X();const mt=setInterval(X,R);process.on("SIGINT",()=>{clearInterval(mt),process.stderr.write(`
|
|
29
29
|
`+d(`Watch stopped.
|
|
30
30
|
|
|
31
31
|
`)),process.exit(0)}),await new Promise(()=>{})}}export{bt as contextCommand};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as
|
|
2
|
-
`).filter(Boolean).length}catch{}return c(`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const i of["contract.json","capabilities.json"]){const t=l.join(e,i);if(s.existsSync(t))try{const d=(JSON.parse(s.readFileSync(t,"utf8")).capabilities||[]).length;return c(`${i} valid \u2014 ${d} capabilities`)}catch{return p(`${i} contains invalid JSON`,`Fix the JSON syntax in inferno/${i}`)}}return p("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function k(n){try{return JSON.parse(s.readFileSync(l.join(n,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function G(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","scenarios");if(!s.existsSync(e))return u("No scenarios/ directory","infernoflow init");const o=s.readdirSync(e).filter(i=>i.endsWith(".json"));return o.length?c(`${o.length} scenario file${o.length!==1?"s":""} found`):u("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function F(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=l.join(n,"inferno","CHANGELOG.md");return s.existsSync(e)?c("inferno/CHANGELOG.md exists"):u("No inferno/CHANGELOG.md","infernoflow init")}function M(n){if(k(n))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const e=l.join(n,"inferno","CONTEXT.md");if(!s.existsSync(e))return u("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(e).mtimeMs)/(1e3*60*60*24);return o>7?u(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):c(`CONTEXT.md present (${Math.round(o)}d old)`)}function D(n){const e=l.join(n,".git","hooks"),o=l.join(e,"post-commit"),i=l.join(e,"pre-push"),t=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),r=s.existsSync(i)&&s.readFileSync(i,"utf8").includes("infernoflow");return t&&r?c("Git hooks installed (post-commit + pre-push)"):u(t||r?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function L(n){const e=[l.join(n,".cursor","mcp.json"),l.join(n,".mcp.json"),l.join(y.homedir(),".cursor","mcp.json"),l.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),l.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of e)if(s.existsSync(o))try{const i=JSON.parse(s.readFileSync(o,"utf8")),t=i.mcpServers||i.mcp_servers||{};if(Object.keys(t).some(r=>r.toLowerCase().includes("inferno")))return c(`MCP server configured in ${l.basename(o)}`)}catch{}return u("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function R(n){const e=A(n),o=Object.entries(e).filter(([,i])=>i).map(([i])=>i);return o.length?c(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):u("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
|
|
1
|
+
import*as s from"node:fs";import*as a from"node:path";import*as y from"node:os";import*as v from"node:http";import{execSync as C,spawnSync as $}from"node:child_process";import{fileURLToPath as O}from"node:url";import{bold as b,cyan as N,gray as x,green as w,yellow as j,red as S}from"../ui/output.mjs";import{detectAvailableProviders as A}from"../ai/providerRouter.mjs";import{readEntries as _}from"../amp/io.mjs";function c(n,e){try{const o=e();return{label:n,...o}}catch(o){return{label:n,status:"error",message:o.message,fix:null}}}function l(n,e){return{status:"pass",message:n,detail:e||null,fix:null}}function u(n,e){return{status:"warn",message:n,detail:null,fix:e||null}}function p(n,e){return{status:"fail",message:n,detail:null,fix:e||null}}function P(){const n=process.version,e=parseInt(n.slice(1).split(".")[0],10);return e>=20?l(`Node.js ${n}`,"Node 20+ recommended"):e>=18?l(`Node.js ${n}`):p(`Node.js ${n} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function E(){try{const n=$("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return n.status===0?l(`infernoflow v${n.stdout.trim()} installed`):l("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return l("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function I(n){try{return C("git rev-parse --git-dir",{cwd:n,stdio:"ignore"}),l("Git repository detected")}catch{return p("Not a git repository","git init && git add . && git commit -m 'init'")}}function T(n){const e=a.join(n,"inferno");return s.existsSync(e)?l("inferno/ directory exists"):p("inferno/ not found","infernoflow init")}function G(n){const e=a.join(n,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(a.join(e,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){let t=0;try{t=_(n).length}catch{}return l(t===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${t} session entr${t===1?"y":"ies"}`)}for(const t of["contract.json","capabilities.json"]){const i=a.join(e,t);if(s.existsSync(i))try{const d=(JSON.parse(s.readFileSync(i,"utf8")).capabilities||[]).length;return l(`${t} valid \u2014 ${d} capabilities`)}catch{return p(`${t} contains invalid JSON`,`Fix the JSON syntax in inferno/${t}`)}}return p("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function k(n){try{return JSON.parse(s.readFileSync(a.join(n,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function F(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=a.join(n,"inferno","scenarios");if(!s.existsSync(e))return u("No scenarios/ directory","infernoflow init");const o=s.readdirSync(e).filter(t=>t.endsWith(".json"));return o.length?l(`${o.length} scenario file${o.length!==1?"s":""} found`):u("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function M(n){if(k(n))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const e=a.join(n,"inferno","CHANGELOG.md");return s.existsSync(e)?l("inferno/CHANGELOG.md exists"):u("No inferno/CHANGELOG.md","infernoflow init")}function R(n){if(k(n))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const e=a.join(n,"inferno","CONTEXT.md");if(!s.existsSync(e))return u("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-s.statSync(e).mtimeMs)/(1e3*60*60*24);return o>7?u(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):l(`CONTEXT.md present (${Math.round(o)}d old)`)}function D(n){const e=a.join(n,".git","hooks"),o=a.join(e,"post-commit"),t=a.join(e,"pre-push"),i=s.existsSync(o)&&s.readFileSync(o,"utf8").includes("infernoflow"),r=s.existsSync(t)&&s.readFileSync(t,"utf8").includes("infernoflow");return i&&r?l("Git hooks installed (post-commit + pre-push)"):u(i||r?"Partial git hooks installed":"Git hooks not installed","infernoflow setup --yes")}function L(n){const e=[a.join(n,".cursor","mcp.json"),a.join(n,".mcp.json"),a.join(y.homedir(),".cursor","mcp.json"),a.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),a.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const o of e)if(s.existsSync(o))try{const t=JSON.parse(s.readFileSync(o,"utf8")),i=t.mcpServers||t.mcp_servers||{};if(Object.keys(i).some(r=>r.toLowerCase().includes("inferno")))return l(`MCP server configured in ${a.basename(o)}`)}catch{}return u("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude config)")}function J(n){const e=A(n),o=Object.entries(e).filter(([,t])=>t).map(([t])=>t);return o.length?l(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):u("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
|
|
3
2
|
Or install Ollama (ollama.com) for free local AI
|
|
4
|
-
Or use VS Code with GitHub Copilot (zero config)`)}async function
|
|
3
|
+
Or use VS Code with GitHub Copilot (zero config)`)}async function H(){return new Promise(n=>{const e=v.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{n(l("Ollama running on localhost:11434"))});e.on("error",()=>n({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),e.on("timeout",()=>{e.destroy(),n({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function X(){const n=a.join(y.homedir(),".infernoflow","credentials.json");if(!s.existsSync(n))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const e=JSON.parse(s.readFileSync(n,"utf8")),o=e.user?.login||e.user?.name||e.user?.email||"unknown";if(e.mode==="supabase"&&e.access_token){if(e.expires_at){const t=new Date(e.expires_at).getTime();if(Date.now()>t)return u(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return l(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return e.mode==="device-flow"&&e.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:e.access_token?u(`Legacy login for ${o} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return u("Credentials file unreadable","infernoflow logout && infernoflow login")}}function K(){try{const n=O(import.meta.url),e=a.resolve(a.dirname(n),"..","..","bin","infernoflow.mjs");if(!s.existsSync(e))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...s.readFileSync(e,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],i=[],r=a.resolve(a.dirname(e),"..");for(const d of t){const m=d[1],h=a.join(r,"lib",m);s.existsSync(h)||i.push(m)}return i.length?p(`${i.length} routed command(s) missing module files: ${i.slice(0,3).join(", ")}${i.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):l(`All ${t.length} routed commands resolve to real files`)}catch(n){return{status:"info",message:`Router integrity check skipped: ${n.message}`,fix:null,detail:null}}}function V(n){const e=a.join(n,"package.json");if(!s.existsSync(e))return l("No package.json to audit");let o;try{o=JSON.parse(s.readFileSync(e,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},i=new Set(["log","ask","switch","recap","status","init","doctor","graph","watch","amp","contract","dev","demo","setup","log-decision","log-attempt","context","stats","test"]),r=[];for(const[m,h]of Object.entries(t)){const f=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(h));if(!f)continue;const g=f[1];i.has(g)||r.push({scriptName:m,verb:g})}if(r.length===0)return l("npm scripts use current command surface");const d=r.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return u(`package.json references ${r.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function W(n){const e=a.join(n,".gitignore");if(!s.existsSync(e))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=s.readFileSync(e,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?l(".gitignore excludes node_modules"):u(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function Y(n,e){const o=n.filter(i=>i.status==="warn"&&i.fix),t=[];for(const i of o){const r=i.fix;if(r.startsWith("infernoflow ")){const d=r.slice(12).split(" ");$("infernoflow",d,{cwd:e,encoding:"utf8",timeout:3e4}).status===0&&t.push(i.label)}}return t}function z(n){return n==="pass"?w("\u2714"):n==="warn"?j("\u26A0"):n==="fail"?S("\u2717"):x("\xB7")}function U(n,e){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const r of n)o[r.status]=(o[r.status]||0)+1;console.log(),console.log(` ${b("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...n.map(r=>r.label.length))+2;for(const r of n)console.log(` ${z(r.status)} ${b(r.label.padEnd(t))} ${r.message}`),r.detail&&console.log(` ${" ".repeat(t)} ${x(r.detail)}`),r.fix&&(r.status==="warn"||r.status==="fail")&&console.log(` ${" ".repeat(t)} ${N("fix:")} ${x(r.fix)}`);console.log();const i=o.fail>0?S("issues found"):o.warn>0?j("warnings"):w("all good");console.log(` ${i} \u2014 ${w(String(o.pass))} pass \xB7 ${j(String(o.warn))} warn \xB7 ${S(String(o.fail))} fail (${e}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${N("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function q(n){const e=n.slice(1),o=e.includes("--json"),t=e.includes("--fix"),i=process.cwd(),r=Date.now(),d=[c("Node.js version",()=>P()),c("infernoflow CLI",()=>E()),c("Git repository",()=>I(i)),c("inferno/ directory",()=>T(i)),c("Contract / mode",()=>G(i)),c("Scenarios",()=>F(i)),c("Changelog",()=>M(i)),c("CONTEXT.md",()=>R(i)),c("Git hooks",()=>D(i)),c("MCP server",()=>L(i)),c("AI providers",()=>J(i)),c("Cloud sync",()=>X()),c(".gitignore",()=>W(i)),c("Router integrity",()=>K()),c("npm scripts",()=>V(i)),await H().then(f=>({label:"Ollama (local AI)",...f}))],m=Date.now()-r;if(t){const f=Y(d,i);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),q(["doctor","--json"])}if(o){const f={pass:0,warn:0,fail:0,info:0};d.forEach(g=>f[g.status]=(f[g.status]||0)+1),console.log(JSON.stringify({ok:f.fail===0,counts:f,results:d,elapsed:m}));return}U(d,m),d.some(f=>f.status==="fail")&&process.exit(1)}export{q as doctorCommand};
|