infernoflow 0.43.9 → 0.43.11
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 +9 -9
- package/dist/lib/commands/adopt.mjs +1 -1
- package/dist/lib/commands/doctor.mjs +3 -3
- package/dist/lib/commands/init.mjs +37 -36
- package/dist/lib/commands/setup.mjs +5 -5
- package/dist/lib/commands/status.mjs +6 -6
- package/dist/lib/commands/syncAuto.mjs +1 -1
- package/dist/lib/upgradeCheck.mjs +3 -0
- package/package.json +1 -1
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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 t={"\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]"},n=new RegExp(Object.keys(t).join("|"),"g");function
|
|
3
|
-
Unknown ${e} verb: ${
|
|
4
|
-
`)),process.exit(1)),l[c]([c,...n.slice(2)])}function B(){const e=Object.keys(y),t=Math.max(...e.map(n=>n.length),8)+1;return Object.entries(y).map(([n,
|
|
2
|
+
(function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const t={"\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]"},n=new RegExp(Object.keys(t).join("|"),"g");function r(c){const d=c.write.bind(c);c.write=function(s,...f){if(typeof s=="string")s=s.replace(n,u=>t[u]);else if(Buffer.isBuffer(s)){const u=s.toString("utf8").replace(n,k=>t[k]);s=Buffer.from(u,"utf8")}return d(s,...f)}}r(process.stdout),r(process.stderr)})();import{readFileSync as $}from"node:fs";import{dirname as S,join as g}from"node:path";import{fileURLToPath as x}from"node:url";import{bold as m,gray as o,cyan as a,red as h}from"../lib/ui/output.mjs";const A=S(x(import.meta.url));function O(e){for(const t of[g(e,"..","..","package.json"),g(e,"..","package.json")])try{return JSON.parse($(t,"utf8"))}catch{}return{version:"0.0.0-source"}}const I=O(A),p=I.version||"0.0.0",y={publish:"Bump version, update changelog, build, npm publish, git commit + push in one shot",diff:"Show what capabilities changed since the last git tag (or any ref)",changelog:"Draft a changelog entry from commits since the last tag",setup:"One command to get fully operational \u2014 detects IDE, inits, installs hooks + MCP",init:"Scaffold inferno/ in your project (or adopt existing project)","install-cursor-hooks":"Install Cursor hooks: draft agent replies to inferno/CONTEXT.draft.md","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview): draft to inferno/CONTEXT.draft.md",check:"Validate contract, capabilities, scenarios, changelog",status:"Show contract health at a glance","pr-impact":"Summarize PR impact on capabilities and docs",sync:"Run deterministic inferno sync flow",run:"One-command detect/propose/apply/validate flow","doc-gate":"Fail if code changed but docs were not updated",suggest:"Generate AI prompt + apply capability updates",implement:"Generate code-agent implementation prompt(s)",context:"Generate AI-ready context for new sessions","generate-skills":"Generate personalised Cursor rules + skill files from your developer profile",watch:"Watch source files and run suggest automatically on save",ci:"CI-native check: GitHub Actions annotations, GitLab code quality, exit codes",notify:"Post capability drift summary to Slack or Discord",monorepo:"Manage infernoflow across monorepo packages (init | list | status | diff | sync)",doctor:"Diagnose your infernoflow setup \u2014 checks Node, git, contract, AI providers, MCP, hooks",coverage:"Map test files to capabilities \u2014 show which caps have test coverage and which don't",review:"AI-powered capability impact review for staged or recent git changes",scan:"Deep AST scan \u2014 route discovery, entry point detection, HTTP URL extraction, capability suggestions",graph:"Build capability dependency graph \u2014 shows which caps call which, detects breaking changes",stability:"Show solid/liquid stability level for every capability (frozen/stable/experimental)",freeze:"Mark a capability as frozen (solid) \u2014 AI will not modify it without explicit instruction",thaw:"Reset a capability to experimental (liquid) \u2014 free to evolve",why:"Given a file or function name \u2014 show which capability it serves, scenarios, stability, and git history",impact:"Blast radius analysis \u2014 see every cap, scenario, and risk level affected before you change anything",scaffold:"Generate a new capability \u2014 source skeleton, contract registration, and placeholder scenario in one command",explain:"AI narrative about a capability \u2014 what it does, why it exists, what's risky, and what to test",test:"Run registered scenarios for a capability \u2014 auto-generates a smoke harness if no test runner is configured",ai:"Manage AI providers \u2014 setup, status, test connection (subcommands: setup | status | test | clear)",demo:"Interactive walkthrough \u2014 scaffolds a sample project and runs the full capability chain end-to-end",feedback:"60-second CLI survey about how you use infernoflow (--form to open web form)",telemetry:"Manage anonymous usage telemetry (on | off | status) \u2014 opt-in, command names only",log:"Append to session memory (decisions, gotchas, failed attempts, theme changes) \u2014 what AI can't infer from code",theme:"Scan fonts, colors, and CSS variables \u2014 write inferno/theme.json so AI always matches the design system",switch:"Generate a handoff summary when switching AI agents \u2014 paste into the next session so nothing is lost",upgrade:"Upgrade a lite infernoflow setup to the full structure (scenarios, changelog, scripts)",stats:"Value dashboard \u2014 session memory, tokens injected per session, coverage %, estimated savings",ask:"Query session memory \u2014 search gotchas, decisions, and failed attempts by keyword or type",recap:"End-of-session summary \u2014 what was captured, what git changes weren't logged, session health score",uninstall:"Remove infernoflow from a project \u2014 inferno/, CLAUDE.md, MCP server, git hooks (--dry-run to preview)",contract:"Capability contracts \u2014 scan, freeze, impact, graph, scaffold, etc. (run: infernoflow contract)",dev:"Maintenance & integration \u2014 publish, changelog, dashboard, ai, ci, sync, etc. (run: infernoflow dev)",amp:"AI Memory Protocol \u2014 status, migrate from legacy, validate (run: infernoflow amp)"},l={publish:async e=>(await import("../lib/commands/publish.mjs")).publishCommand(e),diff:async e=>(await import("../lib/commands/diff.mjs")).diffCommand(e),changelog:async e=>(await import("../lib/commands/changelog.mjs")).changelogCommand(e),setup:async e=>(await import("../lib/commands/setup.mjs")).setupCommand(e),init:async e=>(await import("../lib/commands/init.mjs")).initCommand(e),"install-cursor-hooks":async e=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(e),"install-vscode-copilot-hooks":async e=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(e),check:async e=>(await import("../lib/commands/check.mjs")).checkCommand(e),status:async e=>(await import("../lib/commands/status.mjs")).statusCommand(e),"pr-impact":async e=>(await import("../lib/commands/prImpact.mjs")).prImpactCommand(e),sync:async e=>(await import("../lib/commands/syncAuto.mjs")).syncCommand(e),run:async e=>(await import("../lib/commands/run.mjs")).runCommand(e),suggest:async e=>(await import("../lib/commands/suggest.mjs")).suggestCommand(e),implement:async e=>(await import("../lib/commands/implement.mjs")).implementCommand(e),context:async e=>(await import("../lib/commands/context.mjs")).contextCommand(e),"doc-gate":async e=>(await import("../lib/commands/docGate.mjs")).docGateCommand(e),"generate-skills":async e=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(e),watch:async e=>(await import("../lib/commands/watch.mjs")).watchCommand(e),ci:async e=>(await import("../lib/commands/ci.mjs")).ciCommand(e),notify:async e=>(await import("../lib/commands/notify.mjs")).notifyCommand(e),monorepo:async e=>(await import("../lib/commands/monorepo.mjs")).monorepoCommand(e),doctor:async e=>(await import("../lib/commands/doctor.mjs")).doctorCommand(e),coverage:async e=>(await import("../lib/commands/coverage.mjs")).coverageCommand(e),review:async e=>(await import("../lib/commands/review.mjs")).reviewCommand(e),scan:async e=>(await import("../lib/commands/scan.mjs")).scanCommand(e),graph:async e=>(await import("../lib/commands/graph.mjs")).graphCommand(e),stability:async e=>(await import("../lib/commands/stability.mjs")).stabilityCommand(e),freeze:async e=>(await import("../lib/commands/stability.mjs")).freezeCommand(e),thaw:async e=>(await import("../lib/commands/stability.mjs")).thawCommand(e),why:async e=>(await import("../lib/commands/why.mjs")).whyCommand(e),impact:async e=>(await import("../lib/commands/impact.mjs")).impactCommand(e),scaffold:async e=>(await import("../lib/commands/scaffold.mjs")).scaffoldCommand(e),explain:async e=>(await import("../lib/commands/explain.mjs")).explainCommand(e),test:async e=>(await import("../lib/commands/test.mjs")).testCommand(e),ai:async e=>(await import("../lib/commands/ai.mjs")).aiCommand(e),demo:async e=>(await import("../lib/commands/demo.mjs")).demoCommand(e),log:async e=>(await import("../lib/commands/log.mjs")).logCommand(e),theme:async e=>(await import("../lib/commands/theme.mjs")).themeCommand(e),switch:async e=>(await import("../lib/commands/switch.mjs")).switchCommand(e),upgrade:async e=>(await import("../lib/commands/upgrade.mjs")).upgradeCommand(e),stats:async e=>(await import("../lib/commands/stats.mjs")).statsCommand(e),ask:async e=>(await import("../lib/commands/ask.mjs")).askCommand(e),recap:async e=>(await import("../lib/commands/recap.mjs")).recapCommand(e),uninstall:async e=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(e),feedback:async e=>(await import("../lib/commands/feedback.mjs")).feedbackCommand(e),telemetry:async e=>(await import("../lib/telemetry.mjs")).telemetryCommand(e),contract:async e=>w("contract",v,e),dev:async e=>w("dev",b,e),amp:async e=>(await import("../lib/commands/amp.mjs")).ampCommand(e)};async function w(e,t,n){const r=n[1];if(!r||r==="--help"||r==="-h"){console.log(),console.log(` ${m("\u{1F525} infernoflow "+e)} ${o("\u2014 available verbs:")}`),console.log();const d=Math.max(...Object.keys(t).map(s=>s.length))+2;for(const s of Object.keys(t)){const f=y[t[s]]||"";console.log(` ${a(s.padEnd(d))} ${o(f)}`)}console.log(),console.log(` ${o("Run")} ${a(`infernoflow ${e} <verb> --help`)} ${o("for verb-specific options.")}`),console.log();return}const c=t[r];return(!c||!l[c])&&(console.error(h(`
|
|
3
|
+
Unknown ${e} verb: ${r}`)),console.error(o(` Run: infernoflow ${e} (see all verbs)
|
|
4
|
+
`)),process.exit(1)),l[c]([c,...n.slice(2)])}function B(){const e=Object.keys(y),t=Math.max(...e.map(n=>n.length),8)+1;return Object.entries(y).map(([n,r])=>` ${n.padEnd(t," ")}${r}`).join(`
|
|
5
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
6
|
${n.join(" ")}`).join(`
|
|
7
7
|
|
|
8
8
|
`)}const C=Object.keys(l).length,j=`
|
|
9
|
-
${m("\u{1F525} infernoflow")} ${o("v"+
|
|
9
|
+
${m("\u{1F525} infernoflow")} ${o("v"+p)}
|
|
10
10
|
${o("Persistent memory for AI coding sessions")}
|
|
11
11
|
|
|
12
12
|
${m("Usage:")}
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
|
|
32
32
|
${o("Run")} ${a("infernoflow commands")} ${o("to see all "+C+" commands grouped.")}
|
|
33
33
|
${o("Run")} ${a("infernoflow <command> --help")} ${o("for command-specific options.")}
|
|
34
|
-
`;import*as E from"node:fs";import*as P from"node:path";try{const e=P.join(process.cwd(),"inferno");if(E.existsSync(e)){const{observeCommandStart:t}=await import("../lib/learning/observe.mjs"),n=process.argv[2];n&&!n.startsWith("-")&&t(e,n)}}catch{}const[,,
|
|
35
|
-
${m("\u{1F525} infernoflow")} ${o("v"+
|
|
34
|
+
`;import*as E from"node:fs";import*as P from"node:path";try{const e=P.join(process.cwd(),"inferno");if(E.existsSync(e)){const{observeCommandStart:t}=await import("../lib/learning/observe.mjs"),n=process.argv[2];n&&!n.startsWith("-")&&t(e,n)}}catch{}const[,,i,...D]=process.argv;(!i||i==="--help"||i==="-h")&&(console.log(j),process.exit(0)),(i==="--version"||i==="-v")&&(console.log(p),process.exit(0)),i==="commands"&&(console.log(`
|
|
35
|
+
${m("\u{1F525} infernoflow")} ${o("v"+p)} ${o("\u2014 all "+C+" commands")}
|
|
36
36
|
`),console.log(R()),console.log(`
|
|
37
37
|
${o("Run")} ${a("infernoflow <command> --help")} ${o("for options.")}
|
|
38
|
-
`),process.exit(0));const
|
|
39
|
-
Unknown command: ${
|
|
40
|
-
`)),process.exit(1));const
|
|
38
|
+
`),process.exit(0));const N=Object.keys(l);N.includes(i)||(console.error(h(`
|
|
39
|
+
Unknown command: ${i}`)),console.error(o("Run: infernoflow commands (see all commands)")),console.error(o(`Run: infernoflow --help (quick start)
|
|
40
|
+
`)),process.exit(1));const T=[i,...D];try{const{runUpgradeBackfillIfNeeded:e}=await import("../lib/upgradeCheck.mjs");await e(p,i)}catch{}l[i](T).catch(e=>{console.error(h(`
|
|
41
41
|
Error: `)+e.message),process.exit(1)});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as x from"node:fs";import*as A from"node:path";import"node:readline";import{seedProfileFromAdoption as L}from"../learning/profile.mjs";import{scanAngular as D}from"../adopters/angular.mjs";import{scanReact as R}from"../adopters/react.mjs";import{scanCSS as M}from"../adopters/css.mjs";function U(e){return e.replace(/[^a-zA-Z0-9]+/g," ").trim().split(/\s+/).filter(Boolean).map(r=>r[0].toUpperCase()+r.slice(1)).join("")}function O(e){return e.replace(/([A-Z])/g," $1").trim()}function k(e){try{return x.readFileSync(e,"utf8")}catch{return""}}const H=[{id:"CreateItem",title:"Create Item",regex:/\b(post|create|add)\b/i},{id:"ReadItems",title:"Read Items",regex:/\b(get|read|list|fetch)\b/i},{id:"UpdateItem",title:"Update Item",regex:/\b(put|patch|update|edit)\b/i},{id:"DeleteItem",title:"Delete Item",regex:/\b(delete|remove)\b/i},{id:"SearchItems",title:"Search Items",regex:/\bsearch\b/i},{id:"FilterItems",title:"Filter Items",regex:/\bfilter\b/i},{id:"SetDueDate",title:"Set Due Date",regex:/\bdueDate|deadline|due\b/i},{id:"SetPriority",title:"Set Priority",regex:/\bpriority\b/i},{id:"ToggleComplete",title:"Toggle Complete",regex:/\bcomplete|completed|toggle\b/i},{id:"ClearCompleted",title:"Clear Completed",regex:/\bclearCompleted|clear completed\b/i}];function ie(e){return Q(e).capabilities}function W(e){const r=[],i=["src","server","app","backend","frontend","api","Controllers"];for(const o of i){const n=A.join(e,o);if(!x.existsSync(n))continue;const s=[n];for(;s.length;){const f=s.pop();for(const t of x.readdirSync(f,{withFileTypes:!0})){const a=A.join(f,t.name);if(t.isDirectory()){if(new Set(["node_modules",".git","dist","build","out","www","tmp",".tmp","vendor","assets","public","static","coverage",".nyc_output",".angular",".vite",".cache",".parcel-cache",".next",".nuxt","__pycache__","e2e","test","tests","spec","__tests__","fixtures","mocks",".turbo","storybook-static"]).has(t.name))continue;s.push(a)}else if(/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm|cs|csproj)$/.test(t.name)){if(/\.(min|bundle)\.(js|css)$/.test(t.name)||/\.map$/.test(t.name)||/\.(spec|test)\.(ts|js|tsx|jsx)$/.test(t.name))continue;r.push(a)}}}}for(const o of x.readdirSync(e,{withFileTypes:!0}))o.isFile()&&/^(Program\.cs|.+\.csproj)$/i.test(o.name)&&r.push(A.join(e,o.name));return r}function G(e,r){const i=new Set;for(const o of e){const n=A.relative(r,o),s=k(o),f=s.matchAll(/\bclass\s+([A-Z][A-Za-z0-9_]*?(?:Component|Page|View|Widget|Card))\b/g);for(const m of f)i.add(m[1]);const t=s.matchAll(/\bselector\s*:\s*["']([^"']+)["']/g);for(const m of t)i.add(m[1]);const a=s.matchAll(/\bfunction\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);for(const m of a)/component|page|view|card|chart|dashboard/i.test(m[1])&&i.add(m[1]);const d=n.match(/([^/\\]+)\.(component|page|view|widget|card)\.(ts|tsx|js|jsx)$/i);d&&i.add(d[1])}return Array.from(i).sort()}function N(e){const r=new Set,i=new Set,o=new Set(["if","for","while","const","let","var","return","function","class","import","export","null","undefined","true","false","string","number","boolean","any","unknown","never","selector","templateUrl","styleUrl","standalone","imports","providers","providedIn","options","scales","responsive","display","title","type","label","component","service","routes","appConfig","ApplicationConfig"]),n=s=>{s&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&(s.length<=1||o.has(s)||/^[A-Z0-9_]+$/.test(s)||r.add(s))};for(const s of e){const f=k(s);if(/\.(html|htm)$/i.test(s)){const t=f.matchAll(/\{\{\s*(?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const c of t)n(c[1]);const a=f.matchAll(/\[\(ngModel\)\]\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const c of a)n(c[1]);const d=f.matchAll(/\[[a-zA-Z0-9_-]+\]\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const c of d)n(c[1]);const m=f.matchAll(/\*ngIf\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const c of m)n(c[1])}if(/\.(ts|tsx|js|jsx|mjs|cjs)$/i.test(s)){const t=f.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:async\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/g);for(const p of t)i.add(p[1]);const a=f.matchAll(/\bthis\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g);for(const p of a)n(p[1]);const d=f.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:readonly\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::|=)/g);for(const p of d)n(p[1]);const m=f.matchAll(/@Input\([^)]*\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*[:=]/g);for(const p of m)n(p[1]);const c=f.matchAll(/forEach\(\((\w+)\)\s*=>/g);for(const p of c){const C=p[1],S=new RegExp(`\\b${C}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\b`,"g");for(const y of f.matchAll(S))n(y[1])}}}return Array.from(r).filter(s=>!i.has(s)).sort().slice(0,80)}function B(e){const r=new Set,i=A.join(e,"package.json");if(!x.existsSync(i))return[];try{const o=JSON.parse(k(i)||"{}"),n={...o.dependencies||{},...o.devDependencies||{}};for(const s of Object.keys(n))r.add(s)}catch{}return Array.from(r).sort()}function V(e,r,i){const o=r.filter(t=>/\.(css|scss|sass|less|styl)$/i.test(t)).map(t=>A.relative(e,t)).sort(),n=[],s=t=>i.includes(t);s("tailwindcss")&&n.push("Tailwind CSS"),s("bootstrap")&&n.push("Bootstrap"),i.some(t=>t.startsWith("@angular/material"))&&n.push("Angular Material"),s("antd")&&n.push("Ant Design"),s("styled-components")&&n.push("styled-components"),(s("emotion")||s("@emotion/react"))&&n.push("Emotion");const f=new Set;for(const t of r){if(!/\.(css|scss|sass|less|styl|html|htm|ts|tsx|js|jsx|mjs|cjs)$/i.test(t))continue;const a=k(t);for(const d of a.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)/g))f.add(`--${d[1]}`)}return{cssFrameworks:n,styleFileCount:o.length,styleFilesSample:o.slice(0,12),designTokens:Array.from(f).sort().slice(0,24)}}function q(e){let r=!1,i=!1;const o=new Set;for(const s of e){if(!/\.(html|htm|tsx|jsx|ts|js|mjs|cjs)$/i.test(s))continue;const f=k(s);/\bgrid\b|grid-template|grid-cols-|display:\s*grid/i.test(f)&&(r=!0),/\bflex\b|display:\s*flex|flex-row|flex-col|justify-|items-/i.test(f)&&(i=!0);for(const t of f.matchAll(/<(main|header|footer|section|aside|nav)\b/gi))o.add(t[1].toLowerCase());for(const t of f.matchAll(/class(?:Name)?\s*=\s*["'`][^"'`]*(dashboard|chart|card|sidebar|content|toolbar|filter|panel|table)[^"'`]*["'`]/gi)){const a=t[1].toLowerCase();o.add(a==="filter"?"filters":a)}}return{layoutType:r&&i?"mixed":r?"grid":i?"flex":"unknown",usesGrid:r,usesFlex:i,sections:Array.from(o).sort()}}function J(e,r,i,o={}){const n={ts:0,js:0,py:0,java:0,go:0,rb:0,rs:0,cs:0,php:0};for(const y of r){const b=A.extname(y).toLowerCase();(b===".ts"||b===".tsx")&&(n.ts+=1),(b===".js"||b===".jsx"||b===".mjs"||b===".cjs")&&(n.js+=1),b===".py"&&(n.py+=1),b===".java"&&(n.java+=1),b===".go"&&(n.go+=1),b===".rb"&&(n.rb+=1),b===".rs"&&(n.rs+=1),b===".cs"&&(n.cs+=1),b===".php"&&(n.php+=1)}const s=Object.entries(n).sort((y,b)=>b[1]-y[1]),f=s[0]?.[1]>0?s[0][0]:"unknown";let t="unknown",a=!1,d=!1,m=!1;for(const y of r){const b=A.basename(y).toLowerCase();if(b.endsWith(".csproj")){const P=k(y);/Microsoft\.NET\.Sdk\.Web/i.test(P)&&(a=!0),(/Blazor/i.test(P)||/Microsoft\.AspNetCore\.Components/i.test(P))&&(m=!0)}if(b==="program.cs"){const P=k(y);/app\.Map(Get|Post|Put|Delete|Patch)\s*\(/i.test(P)&&(d=!0)}}const c=y=>i.includes(y);i.some(y=>y.startsWith("@angular/"))?t="angular":c("react")?t="react":c("vue")?t="vue":c("svelte")?t="svelte":c("next")?t="nextjs":c("nuxt")?t="nuxt":c("express")?t="express":c("@nestjs/core")?t="nestjs":c("fastify")?t="fastify":c("flask")?t="flask":c("django")?t="django":c("spring-boot")?t="spring":m?t="blazor":d?t="minimalapi":(a||n.cs>0)&&(t="aspnet");let p="fullstack";const C=["src","frontend","app"].some(y=>x.existsSync(A.join(e,y))),S=["server","backend","api"].some(y=>x.existsSync(A.join(e,y)));return["react","angular","vue","svelte","nextjs","nuxt"].includes(t)&&(p="frontend"),["express","nestjs","fastify","flask","django","spring","aspnet","minimalapi"].includes(t)&&(p="backend"),C&&S&&(p="fullstack"),!C&&!S&&(p="library"),t==="blazor"&&(p="frontend"),{language:o.language||f,framework:o.framework||t,projectType:o.projectType||p,detected:{language:f,framework:t,projectType:p}}}function K(e,r){const i=[],o=new Set,n=t=>{let a=String(t||"").trim();return a?(a=a.replace(/https?:\/\/[^/]+/gi,""),a=a.replace(/\$\{[^}]+\}/g,"{var}"),a=a.replace(/\{[A-Za-z_][A-Za-z0-9_]*\}/g,"{var}"),a=a.replace(/:[A-Za-z_][A-Za-z0-9_]*/g,"{var}"),a=a.replace(/\/\d+(?=\/|$)/g,"/{id}"),a=a.replace(/=[^&\s]+/g,"={value}"),a=a.replace(/\/+/g,"/"),a):""},s=t=>{const a=n(t.endpointPattern);if(!a)return;const d=`${t.method}|${a}|${t.sourceFile}|${t.style}`;o.has(d)||(o.add(d),i.push({...t,endpointPattern:a}))};for(const t of r){if(!/\.(ts|tsx|js|jsx|mjs|cjs|cs)$/i.test(t))continue;const a=A.relative(e,t),d=k(t);if(!(/service|api|client|controller|program\.cs/i.test(a)||/HttpClient|fetch\(|app\.Map(Get|Post|Put|Delete|Patch)\(/i.test(d)))continue;const c=d.replace(/\r\n/g,`
|
|
1
|
+
import*as x from"node:fs";import*as A from"node:path";import"node:readline";import{seedProfileFromAdoption as L}from"../learning/profile.mjs";import{scanAngular as D}from"../adopters/angular.mjs";import{scanReact as R}from"../adopters/react.mjs";import{scanCSS as M}from"../adopters/css.mjs";function U(e){return e.replace(/[^a-zA-Z0-9]+/g," ").trim().split(/\s+/).filter(Boolean).map(r=>r[0].toUpperCase()+r.slice(1)).join("")}function O(e){return e.replace(/([A-Z])/g," $1").trim()}function k(e){try{return x.readFileSync(e,"utf8")}catch{return""}}const H=[{id:"CreateItem",title:"Create Item",regex:/\b(post|create|add)\b/i},{id:"ReadItems",title:"Read Items",regex:/\b(get|read|list|fetch)\b/i},{id:"UpdateItem",title:"Update Item",regex:/\b(put|patch|update|edit)\b/i},{id:"DeleteItem",title:"Delete Item",regex:/\b(delete|remove)\b/i},{id:"SearchItems",title:"Search Items",regex:/\bsearch\b/i},{id:"FilterItems",title:"Filter Items",regex:/\bfilter\b/i},{id:"SetDueDate",title:"Set Due Date",regex:/\bdueDate|deadline|due\b/i},{id:"SetPriority",title:"Set Priority",regex:/\bpriority\b/i},{id:"ToggleComplete",title:"Toggle Complete",regex:/\bcomplete|completed|toggle\b/i},{id:"ClearCompleted",title:"Clear Completed",regex:/\bclearCompleted|clear completed\b/i}];function ie(e){return Q(e).capabilities}function W(e){const r=[],i=["src","server","app","backend","frontend","api","Controllers"];for(const o of i){const n=A.join(e,o);if(!x.existsSync(n))continue;const s=[n];for(;s.length;){const f=s.pop();for(const t of x.readdirSync(f,{withFileTypes:!0})){const a=A.join(f,t.name);if(t.isDirectory()){if(new Set(["node_modules",".git","dist","build","out","www","tmp",".tmp","vendor","assets","public","static","coverage",".nyc_output",".angular",".vite",".cache",".parcel-cache",".next",".nuxt","__pycache__","e2e","test","tests","spec","__tests__","fixtures","mocks",".turbo","storybook-static",".cursor",".vscode",".claude",".ai-memory","inferno","legacy"]).has(t.name))continue;s.push(a)}else if(/\.(js|jsx|ts|tsx|mjs|cjs|json|md|html|htm|cs|csproj)$/.test(t.name)){if(/\.(min|bundle)\.(js|css)$/.test(t.name)||/\.map$/.test(t.name)||/\.(spec|test)\.(ts|js|tsx|jsx)$/.test(t.name)||/^inferno-/.test(t.name))continue;r.push(a)}}}}for(const o of x.readdirSync(e,{withFileTypes:!0}))o.isFile()&&/^(Program\.cs|.+\.csproj)$/i.test(o.name)&&r.push(A.join(e,o.name));return r}function G(e,r){const i=new Set;for(const o of e){const n=A.relative(r,o),s=k(o),f=s.matchAll(/\bclass\s+([A-Z][A-Za-z0-9_]*?(?:Component|Page|View|Widget|Card))\b/g);for(const m of f)i.add(m[1]);const t=s.matchAll(/\bselector\s*:\s*["']([^"']+)["']/g);for(const m of t)i.add(m[1]);const a=s.matchAll(/\bfunction\s+([A-Z][A-Za-z0-9_]*)\s*\(/g);for(const m of a)/component|page|view|card|chart|dashboard/i.test(m[1])&&i.add(m[1]);const d=n.match(/([^/\\]+)\.(component|page|view|widget|card)\.(ts|tsx|js|jsx)$/i);d&&i.add(d[1])}return Array.from(i).sort()}function N(e){const r=new Set,i=new Set,o=new Set(["if","for","while","const","let","var","return","function","class","import","export","null","undefined","true","false","string","number","boolean","any","unknown","never","selector","templateUrl","styleUrl","standalone","imports","providers","providedIn","options","scales","responsive","display","title","type","label","component","service","routes","appConfig","ApplicationConfig"]),n=s=>{s&&/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)&&(s.length<=1||o.has(s)||/^[A-Z0-9_]+$/.test(s)||r.add(s))};for(const s of e){const f=k(s);if(/\.(html|htm)$/i.test(s)){const t=f.matchAll(/\{\{\s*(?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const c of t)n(c[1]);const a=f.matchAll(/\[\(ngModel\)\]\s*=\s*["']([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const c of a)n(c[1]);const d=f.matchAll(/\[[a-zA-Z0-9_-]+\]\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)["']/g);for(const c of d)n(c[1]);const m=f.matchAll(/\*ngIf\s*=\s*["'](?:this\.)?([a-zA-Z_][a-zA-Z0-9_]*)/g);for(const c of m)n(c[1])}if(/\.(ts|tsx|js|jsx|mjs|cjs)$/i.test(s)){const t=f.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:async\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\([^)]*\)\s*\{/g);for(const p of t)i.add(p[1]);const a=f.matchAll(/\bthis\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g);for(const p of a)n(p[1]);const d=f.matchAll(/(?:^|\n)\s*(?:public|private|protected)?\s*(?:readonly\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\s*(?::|=)/g);for(const p of d)n(p[1]);const m=f.matchAll(/@Input\([^)]*\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*[:=]/g);for(const p of m)n(p[1]);const c=f.matchAll(/forEach\(\((\w+)\)\s*=>/g);for(const p of c){const C=p[1],S=new RegExp(`\\b${C}\\.([a-zA-Z_][a-zA-Z0-9_]*)\\b`,"g");for(const y of f.matchAll(S))n(y[1])}}}return Array.from(r).filter(s=>!i.has(s)).sort().slice(0,80)}function B(e){const r=new Set,i=A.join(e,"package.json");if(!x.existsSync(i))return[];try{const o=JSON.parse(k(i)||"{}"),n={...o.dependencies||{},...o.devDependencies||{}};for(const s of Object.keys(n))r.add(s)}catch{}return Array.from(r).sort()}function V(e,r,i){const o=r.filter(t=>/\.(css|scss|sass|less|styl)$/i.test(t)).map(t=>A.relative(e,t)).sort(),n=[],s=t=>i.includes(t);s("tailwindcss")&&n.push("Tailwind CSS"),s("bootstrap")&&n.push("Bootstrap"),i.some(t=>t.startsWith("@angular/material"))&&n.push("Angular Material"),s("antd")&&n.push("Ant Design"),s("styled-components")&&n.push("styled-components"),(s("emotion")||s("@emotion/react"))&&n.push("Emotion");const f=new Set;for(const t of r){if(!/\.(css|scss|sass|less|styl|html|htm|ts|tsx|js|jsx|mjs|cjs)$/i.test(t))continue;const a=k(t);for(const d of a.matchAll(/--([a-zA-Z][a-zA-Z0-9_-]*)/g))f.add(`--${d[1]}`)}return{cssFrameworks:n,styleFileCount:o.length,styleFilesSample:o.slice(0,12),designTokens:Array.from(f).sort().slice(0,24)}}function q(e){let r=!1,i=!1;const o=new Set;for(const s of e){if(!/\.(html|htm|tsx|jsx|ts|js|mjs|cjs)$/i.test(s))continue;const f=k(s);/\bgrid\b|grid-template|grid-cols-|display:\s*grid/i.test(f)&&(r=!0),/\bflex\b|display:\s*flex|flex-row|flex-col|justify-|items-/i.test(f)&&(i=!0);for(const t of f.matchAll(/<(main|header|footer|section|aside|nav)\b/gi))o.add(t[1].toLowerCase());for(const t of f.matchAll(/class(?:Name)?\s*=\s*["'`][^"'`]*(dashboard|chart|card|sidebar|content|toolbar|filter|panel|table)[^"'`]*["'`]/gi)){const a=t[1].toLowerCase();o.add(a==="filter"?"filters":a)}}return{layoutType:r&&i?"mixed":r?"grid":i?"flex":"unknown",usesGrid:r,usesFlex:i,sections:Array.from(o).sort()}}function J(e,r,i,o={}){const n={ts:0,js:0,py:0,java:0,go:0,rb:0,rs:0,cs:0,php:0};for(const y of r){const b=A.extname(y).toLowerCase();(b===".ts"||b===".tsx")&&(n.ts+=1),(b===".js"||b===".jsx"||b===".mjs"||b===".cjs")&&(n.js+=1),b===".py"&&(n.py+=1),b===".java"&&(n.java+=1),b===".go"&&(n.go+=1),b===".rb"&&(n.rb+=1),b===".rs"&&(n.rs+=1),b===".cs"&&(n.cs+=1),b===".php"&&(n.php+=1)}const s=Object.entries(n).sort((y,b)=>b[1]-y[1]),f=s[0]?.[1]>0?s[0][0]:"unknown";let t="unknown",a=!1,d=!1,m=!1;for(const y of r){const b=A.basename(y).toLowerCase();if(b.endsWith(".csproj")){const P=k(y);/Microsoft\.NET\.Sdk\.Web/i.test(P)&&(a=!0),(/Blazor/i.test(P)||/Microsoft\.AspNetCore\.Components/i.test(P))&&(m=!0)}if(b==="program.cs"){const P=k(y);/app\.Map(Get|Post|Put|Delete|Patch)\s*\(/i.test(P)&&(d=!0)}}const c=y=>i.includes(y);i.some(y=>y.startsWith("@angular/"))?t="angular":c("react")?t="react":c("vue")?t="vue":c("svelte")?t="svelte":c("next")?t="nextjs":c("nuxt")?t="nuxt":c("express")?t="express":c("@nestjs/core")?t="nestjs":c("fastify")?t="fastify":c("flask")?t="flask":c("django")?t="django":c("spring-boot")?t="spring":m?t="blazor":d?t="minimalapi":(a||n.cs>0)&&(t="aspnet");let p="fullstack";const C=["src","frontend","app"].some(y=>x.existsSync(A.join(e,y))),S=["server","backend","api"].some(y=>x.existsSync(A.join(e,y)));return["react","angular","vue","svelte","nextjs","nuxt"].includes(t)&&(p="frontend"),["express","nestjs","fastify","flask","django","spring","aspnet","minimalapi"].includes(t)&&(p="backend"),C&&S&&(p="fullstack"),!C&&!S&&(p="library"),t==="blazor"&&(p="frontend"),{language:o.language||f,framework:o.framework||t,projectType:o.projectType||p,detected:{language:f,framework:t,projectType:p}}}function K(e,r){const i=[],o=new Set,n=t=>{let a=String(t||"").trim();return a?(a=a.replace(/https?:\/\/[^/]+/gi,""),a=a.replace(/\$\{[^}]+\}/g,"{var}"),a=a.replace(/\{[A-Za-z_][A-Za-z0-9_]*\}/g,"{var}"),a=a.replace(/:[A-Za-z_][A-Za-z0-9_]*/g,"{var}"),a=a.replace(/\/\d+(?=\/|$)/g,"/{id}"),a=a.replace(/=[^&\s]+/g,"={value}"),a=a.replace(/\/+/g,"/"),a):""},s=t=>{const a=n(t.endpointPattern);if(!a)return;const d=`${t.method}|${a}|${t.sourceFile}|${t.style}`;o.has(d)||(o.add(d),i.push({...t,endpointPattern:a}))};for(const t of r){if(!/\.(ts|tsx|js|jsx|mjs|cjs|cs)$/i.test(t))continue;const a=A.relative(e,t),d=k(t);if(!(/service|api|client|controller|program\.cs/i.test(a)||/HttpClient|fetch\(|app\.Map(Get|Post|Put|Delete|Patch)\(/i.test(d)))continue;const c=d.replace(/\r\n/g,`
|
|
2
2
|
`),p={},C=u=>{let l=String(u||"").trim();if(!l)return"";for(l=l.replace(/;+$/,"").trim(),l=l.replace(/\(\s*$/,"");l.startsWith("'")&&l.endsWith("'")||l.startsWith('"')&&l.endsWith('"')||l.startsWith("`")&&l.endsWith("`");)l=l.slice(1,-1).trim();return l},S=u=>{const l=String(u||"").trim();return l?!!(/^https?:\/\//i.test(l)||l.startsWith("/")||/\bapi\b/i.test(l)||/\$\{[^}]+\}/.test(l)||/\?[^=\s]+=?/.test(l)||/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./${}-]+$/.test(l)):!1},y=(u,l)=>{!u||!l||(p[u]=C(l))},b=/(?:const|let|var)\s+([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;for(const u of c.matchAll(b))y(u[1],u[2]);const P=/(?:public|private|protected)?\s*(?:readonly\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([\s\S]*?);/g;for(const u of c.matchAll(P))y(u[1],u[2]);const F=u=>{const l=C(u);if(!l)return"";if(/^['"`][\s\S]*['"`]$/.test(l))return l.replace(/^['"`]|['"`]$/g,"");if(p[l]&&S(p[l]))return p[l];const h=l.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);if(h&&p[h[1]]&&S(p[h[1]]))return p[h[1]];const w=l.split("+").map(g=>g.trim()).filter(Boolean);if(w.length>1){const g=w.map(j=>{if(/^['"`][\s\S]*['"`]$/.test(j))return j.replace(/^['"`]|['"`]$/g,"");if(p[j]&&S(p[j]))return p[j];const v=j.match(/^this\.([A-Za-z_][A-Za-z0-9_]*)$/);return v&&p[v[1]]&&S(p[v[1]])?p[v[1]]:`{${j}}`}).join("");if(g)return g}const $=l.match(/^(.+?)\?(.+?):(.+)$/s);if($){const g=F($[2]),j=F($[3]);if(g||j)return`${g||"{optionA}"} | ${j||"{optionB}"}`}return l},_=/\.\s*(get|post|put|patch|delete)\s*(?:<[\s\S]*?>)?\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of c.matchAll(_)){const l=u[1].toUpperCase(),h=F(u[2]);!h||!S(h)||s({method:l,endpointPattern:h,style:"httpClient",sourceFile:a})}const T=/\bfetch\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of c.matchAll(T)){const l=F(u[1]);if(!l||!S(l))continue;const h=u.index||0,w=c.slice(h,h+260),g=(/method\s*:\s*["'](GET|POST|PUT|PATCH|DELETE)["']/i.exec(w)?.[1]||"GET").toUpperCase();s({method:g,endpointPattern:l,style:"fetch",sourceFile:a})}const z=/\baxios\.(get|post|put|patch|delete)\s*\(\s*([\s\S]*?)(?:,|\))/gi;for(const u of c.matchAll(z)){const l=u[1].toUpperCase(),h=F(u[2]);!h||!S(h)||s({method:l,endpointPattern:h,style:"axios",sourceFile:a})}const I=/\baxios\s*\(\s*\{([\s\S]*?)\}\s*\)/gi;for(const u of c.matchAll(I)){const l=u[1],h=/\bmethod\s*:\s*["']?(get|post|put|patch|delete)["']?/i.exec(l),w=/\burl\s*:\s*([^,\n]+)/i.exec(l),$=(h?.[1]||"get").toUpperCase(),g=F(w?.[1]||"");!g||!S(g)||s({method:$,endpointPattern:g,style:"axios-config",sourceFile:a})}const Z=/\.\s*request\s*\(\s*["'](GET|POST|PUT|PATCH|DELETE)["']\s*,\s*([\s\S]*?)(?:,|\))/gi;for(const u of c.matchAll(Z)){const l=u[1].toUpperCase(),h=F(u[2]);!h||!S(h)||s({method:l,endpointPattern:h,style:"request",sourceFile:a})}if(/\.cs$/i.test(t)){const u=/\bapp\.Map(Get|Post|Put|Delete|Patch)\s*\(\s*"([^"]+)"/gi;for(const g of c.matchAll(u))s({method:g[1].toUpperCase(),endpointPattern:g[2],style:"csharp-map",sourceFile:a});const l=/\[Route\("([^"]+)"\)\][\s\S]*?class\s+\w+/i.exec(c),h=l?l[1]:"",w=/\[(HttpGet|HttpPost|HttpPut|HttpDelete|HttpPatch)(?:\("([^"]*)"\))?\]/gi;for(const g of c.matchAll(w)){const j=g[1].replace("Http","").toUpperCase(),v=g[2]||"",E=[h,v].filter(Boolean).join("/").replace(/\/+/g,"/").replace(/\[controller\]/gi,"{controller}");s({method:j,endpointPattern:E||h||"{controller-route}",style:"csharp-controller",sourceFile:a})}const $=/\b(GetAsync|PostAsync|PutAsync|DeleteAsync|SendAsync)\s*\(\s*"([^"]+)"/gi;for(const g of c.matchAll($)){const j=g[1].replace("Async","").replace("Send","SEND").toUpperCase();s({method:j,endpointPattern:g[2],style:"csharp-httpclient",sourceFile:a})}}}const f=i.reduce((t,a)=>(t[a.method]=(t[a.method]||0)+1,t),{});return{totalCalls:i.length,byMethod:f,calls:i.slice(0,80)}}function Q(e,r={}){const i=W(e),o=new Map,n=(c,p)=>{o.has(c.id)||o.set(c.id,{id:c.id,title:c.title,reason:"Detected from code signals",sourceFiles:new Set}),o.get(c.id).sourceFiles.add(A.relative(e,p))};for(const c of i){const p=k(c);for(const C of H)C.regex.test(p)&&n(C,c)}const s=A.join(e,"package.json");if(x.existsSync(s)){const c=JSON.parse(k(s)||"{}"),p=typeof c.name=="string"?c.name:A.basename(e);U(p)&&!o.size&&(o.set("ReadItems",{id:"ReadItems",title:"Read Items",reason:`Fallback default for ${p}`,sourceFiles:new Set}),o.set("CreateItem",{id:"CreateItem",title:"Create Item",reason:`Fallback default for ${p}`,sourceFiles:new Set}))}o.size||(o.set("CreateItem",{id:"CreateItem",title:"Create Item",reason:"Fallback default",sourceFiles:new Set}),o.set("ReadItems",{id:"ReadItems",title:"Read Items",reason:"Fallback default",sourceFiles:new Set}));const f=B(e),t=J(e,i,f,r);let a=[];try{if(t.framework==="angular"){const c=D(e,i);for(const p of c.capabilities)o.has(p.id)||o.set(p.id,{...p,sourceFiles:new Set(p.sourceFiles)})}else if(t.framework==="react"||t.framework==="nextjs"){const c=R(e,i);for(const p of c.capabilities)o.has(p.id)||o.set(p.id,{...p,sourceFiles:new Set(p.sourceFiles)})}}catch{}let d={designTokens:[],colorTokens:[],spacingTokens:[],componentClasses:[],themeVars:[]};try{d=M(e,i)}catch{}return{capabilities:Array.from(o.values()).map(c=>({...c,sourceFiles:Array.from(c.sourceFiles||[])})),components:G(i,e),displayFields:N(i),externalLibraries:f,uiLayout:q(i),styling:{...V(e,i,f),designTokens:d.designTokens.length>0?d.designTokens:void 0,colorTokens:d.colorTokens,spacingTokens:d.spacingTokens,componentClasses:d.componentClasses,themeVars:d.themeVars},developmentProfile:t,apiCalls:K(e,i)}}async function re(e,r=!1){if(e&&e.length>0){const i=e.map(o=>o.id).join(", ");console.log(` Inferred capabilities: ${i}`),console.log(" (edit inferno/capabilities.json later if you want to refine)")}return e}function ae(e){if(!e.length)return"No capabilities inferred.";const r=X(e),i=r.reduce((s,f)=>s+f.signalCount,0),o={high:r.filter(s=>s.confidence==="high").length,medium:r.filter(s=>s.confidence==="medium").length,low:r.filter(s=>s.confidence==="low").length},n=[];n.push("Adoption Analysis"),n.push("=".repeat(56)),n.push(`Capabilities detected : ${r.length}`),n.push(`Signal hits total : ${i}`),n.push(`Confidence mix : high=${o.high}, medium=${o.medium}, low=${o.low}`),n.push("-".repeat(56)),n.push("Capability Breakdown"),n.push("-".repeat(56)),n.push("Confidence Signals Capability"),n.push("-".repeat(56));for(const s of r){const f=s.confidence.toUpperCase().padEnd(10," "),t=String(s.signalCount).padEnd(7," ");if(n.push(`${f} ${t} ${s.id} (${s.title})`),s.signalCount>0){const a=s.sourceFiles.slice(0,3).join(", ");n.push(` sources: ${a}`),s.sourceFiles.length>3&&n.push(` more : +${s.sourceFiles.length-3} additional files`)}else n.push(" sources: inferred fallback (no strong code signal)")}return n.push("=".repeat(56)),n.join(`
|
|
3
3
|
`)}function ce(e){const r=(i,o,n=10)=>{const s=[`${i} (${o.length})`];if(s.push("-".repeat(56)),!o.length)return s.push(" - none"),s.join(`
|
|
4
4
|
`);for(const f of o.slice(0,n))s.push(` - ${f}`);return o.length>n&&s.push(` - ... +${o.length-n} more`),s.join(`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as l from"node:path";import*as
|
|
2
|
-
`).filter(Boolean).length}catch{}return
|
|
1
|
+
import*as s from"node:fs";import*as l 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";function a(n,e){try{const o=e();return{label:n,...o}}catch(o){return{label:n,status:"error",message:o.message,fix:null}}}function c(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 _(){const n=process.version,e=parseInt(n.slice(1).split(".")[0],10);return e>=20?c(`Node.js ${n}`,"Node 20+ recommended"):e>=18?c(`Node.js ${n}`):p(`Node.js ${n} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function P(){try{const n=$("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return n.status===0?c(`infernoflow v${n.stdout.trim()} installed`):c("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return c("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function E(n){try{return C("git rev-parse --git-dir",{cwd:n,stdio:"ignore"}),c("Git repository detected")}catch{return p("Not a git repository","git init && git add . && git commit -m 'init'")}}function I(n){const e=l.join(n,"inferno");return s.existsSync(e)?c("inferno/ directory exists"):p("inferno/ not found","infernoflow init")}function T(n){const e=l.join(n,"inferno");if((()=>{try{return JSON.parse(s.readFileSync(l.join(e,"config.json"),"utf8"))}catch{return{}}})().mode==="memory"){const i=l.join(e,"sessions.jsonl");if(!s.existsSync(i))return c("Memory mode \u2014 sessions.jsonl will be created on first log");let t=0;try{t=s.readFileSync(i,"utf8").split(`
|
|
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
|
|
3
3
|
Or install Ollama (ollama.com) for free local AI
|
|
4
|
-
Or use VS Code with GitHub Copilot (zero config)`)}async function J(){return new Promise(n=>{const
|
|
4
|
+
Or use VS Code with GitHub Copilot (zero config)`)}async function J(){return new Promise(n=>{const e=v.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{n(c("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 H(){const n=l.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 i=new Date(e.expires_at).getTime();if(Date.now()>i)return u(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return c(`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 X(){try{const n=O(import.meta.url),e=l.resolve(l.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 i=[...s.readFileSync(e,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],t=[],r=l.resolve(l.dirname(e),"..");for(const d of i){const m=d[1],h=l.join(r,"lib",m);s.existsSync(h)||t.push(m)}return t.length?p(`${t.length} routed command(s) missing module files: ${t.slice(0,3).join(", ")}${t.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):c(`All ${i.length} routed commands resolve to real files`)}catch(n){return{status:"info",message:`Router integrity check skipped: ${n.message}`,fix:null,detail:null}}}function K(n){const e=l.join(n,"package.json");if(!s.existsSync(e))return c("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 i=o.scripts||{},t=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(i)){const f=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(h));if(!f)continue;const g=f[1];t.has(g)||r.push({scriptName:m,verb:g})}if(r.length===0)return c("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 V(n){const e=l.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)?c(".gitignore excludes node_modules"):u(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function W(n,e){const o=n.filter(t=>t.status==="warn"&&t.fix),i=[];for(const t of o){const r=t.fix;if(r.startsWith("infernoflow ")){const d=r.slice(12).split(" ");$("infernoflow",d,{cwd:e,encoding:"utf8",timeout:3e4}).status===0&&i.push(t.label)}}return i}function Y(n){return n==="pass"?w("\u2714"):n==="warn"?j("\u26A0"):n==="fail"?S("\u2717"):x("\xB7")}function z(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 i=Math.max(...n.map(r=>r.label.length))+2;for(const r of n)console.log(` ${Y(r.status)} ${b(r.label.padEnd(i))} ${r.message}`),r.detail&&console.log(` ${" ".repeat(i)} ${x(r.detail)}`),r.fix&&(r.status==="warn"||r.status==="fail")&&console.log(` ${" ".repeat(i)} ${N("fix:")} ${x(r.fix)}`);console.log();const t=o.fail>0?S("issues found"):o.warn>0?j("warnings"):w("all good");console.log(` ${t} \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 U(n){const e=n.slice(1),o=e.includes("--json"),i=e.includes("--fix"),t=process.cwd(),r=Date.now(),d=[a("Node.js version",()=>_()),a("infernoflow CLI",()=>P()),a("Git repository",()=>E(t)),a("inferno/ directory",()=>I(t)),a("Contract / mode",()=>T(t)),a("Scenarios",()=>G(t)),a("Changelog",()=>F(t)),a("CONTEXT.md",()=>M(t)),a("Git hooks",()=>D(t)),a("MCP server",()=>L(t)),a("AI providers",()=>R(t)),a("Cloud sync",()=>H()),a(".gitignore",()=>V(t)),a("Router integrity",()=>X()),a("npm scripts",()=>K(t)),await J().then(f=>({label:"Ollama (local AI)",...f}))],m=Date.now()-r;if(i){const f=W(d,t);if(f.length)return o||(console.log(),f.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),U(["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}z(d,m),d.some(f=>f.status==="fail")&&process.exit(1)}export{U as doctorCommand};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import*as i from"node:fs";import*as s from"node:path";import*as
|
|
2
|
-
`,"utf8"),o||g("Updated "+
|
|
3
|
-
`),
|
|
1
|
+
import*as i from"node:fs";import*as s from"node:path";import*as G from"node:readline";import{fileURLToPath as pe}from"node:url";import{header as fe,ok as g,warn as P,done as q,nextSteps as de,bold as L,cyan as f,yellow as $,gray as v}from"../ui/output.mjs";import{discoverProjectSignals as H,reviewCapabilitiesInteractive as ue,writeAdoptionBaseline as me,buildAdoptionReport as ye,summarizeCapabilities as ge,buildSignalsReport as he}from"./adopt.mjs";import{installCursorHooksArtifacts as we}from"../cursorHooksInstall.mjs";import{ensureAmpDir as ke,appendEntry as je,writeDefaultConfig as Se}from"../amp/io.mjs";import{installVsCodeCopilotHooksArtifacts as ve}from"../vsCodeCopilotHooksInstall.mjs";import{writeInitRuleFiles as z}from"../ruleFiles.mjs";import{autoSetupMcp as V}from"./setup.mjs";const be=s.dirname(pe(import.meta.url));function Ce(){return s.resolve(be,"../../templates")}function D(e,o,t=""){return new Promise(n=>{const r=t?v(` (${t})`):"";e.question(` ${o}${r}: `,c=>{n(c.trim()||t)})})}function W(e,...o){for(const t of o){const n=e.indexOf(t);if(n!==-1&&e[n+1]&&!e[n+1].startsWith("-"))return e[n+1]}return null}function J(e,o,t,n=!1){return i.existsSync(o)&&!t?(n||P("Skipped (exists): "+s.relative(process.cwd(),o)),!1):(i.mkdirSync(s.dirname(o),{recursive:!0}),i.copyFileSync(e,o),n||g("Created: "+f(s.relative(process.cwd(),o))),!0)}function Oe(e,o,t){i.mkdirSync(o,{recursive:!0});for(const n of i.readdirSync(e,{withFileTypes:!0})){const r=s.join(e,n.name),c=s.join(o,n.name);n.isDirectory()?Oe(r,c,t):J(r,c,t)}}function Ie(e,o=!1){const t=s.join(e,"package.json");if(!i.existsSync(t))return;const n=JSON.parse(i.readFileSync(t,"utf8"));n.scripts=n.scripts||{};let r=!1;const c={"inferno:check":"infernoflow check","inferno:status":"infernoflow status","inferno:gate":"infernoflow doc-gate","inferno:impact":"infernoflow pr-impact --json","inferno:sync":"infernoflow sync --auto --json","inferno:run":'infernoflow run "sync check" --provider auto --json',"inferno:hooks":"node scripts/inferno-install-hooks.mjs"};for(const[m,k]of Object.entries(c))n.scripts[m]||(n.scripts[m]=k,r=!0);r&&(i.writeFileSync(t,JSON.stringify(n,null,2)+`
|
|
2
|
+
`,"utf8"),o||g("Updated "+f("package.json")+" scripts"))}const X="# --- infernoflow (developer-local AI memory; do not commit) ---",xe="# --- /infernoflow ---",B=[".ai-memory/",".cursorrules","CLAUDE.md",".github/copilot-instructions.md"];function Z(e,{silent:o=!1}={}){const t=s.join(e,".gitignore");let n="";if(i.existsSync(t)&&(n=i.readFileSync(t,"utf8")),n.includes(X))return!1;const r=["",X,"# Memory is per-developer, not per-branch. These files travel with you, not with git.",...B,xe,""].join(`
|
|
3
|
+
`),c=n.length===0||n.endsWith(`
|
|
4
4
|
`)?"":`
|
|
5
|
-
`;
|
|
6
|
-
`)}function
|
|
5
|
+
`;if(i.writeFileSync(t,n+c+r,"utf8"),!o){g("Updated "+f(".gitignore")+v(" \u2014 added entries:"));for(const m of B)console.log(v(" + ")+f(m));console.log(v(" Memory + AI rule files are now per-developer (won't move with branches).")),console.log(v(" To commit them instead: delete the `")+f("# --- infernoflow ---")+v("` block in .gitignore."))}return!0}function M(e){const o=s.join(e,"package.json");if(i.existsSync(o))try{const t=JSON.parse(i.readFileSync(o,"utf8"));if(t.name)return t.name.replace(/[^a-z0-9_-]/gi,"_")}catch{}return s.basename(e)}function Ae(e,o,t){const n={policyId:o,policyVersion:1,capabilities:t,rules:{docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!0,requireChangelogOnCapabilityChange:!0}};i.writeFileSync(e,JSON.stringify(n,null,2)+`
|
|
6
|
+
`)}function Te(e,o){const t={schemaVersion:1,capabilities:o.map(n=>({id:n,title:n.replace(/([A-Z])/g," $1").trim(),since:"0.1.0"}))};i.writeFileSync(e,JSON.stringify(t,null,2)+`
|
|
7
7
|
`)}function Ee(e,o){i.mkdirSync(e,{recursive:!0});const t={scenarioId:"happy_path",description:"Basic happy-path flow covering all capabilities",capabilitiesCovered:o,steps:o.map(n=>({action:n,expect:`${n} works as expected`}))};i.writeFileSync(s.join(e,"happy_path.json"),JSON.stringify(t,null,2)+`
|
|
8
|
-
`)}function
|
|
8
|
+
`)}function Q(e,o){const t=`# Changelog \u2014 ${o}
|
|
9
9
|
|
|
10
10
|
## Unreleased
|
|
11
11
|
|
|
@@ -14,41 +14,42 @@ import*as i from"node:fs";import*as s from"node:path";import*as L from"node:read
|
|
|
14
14
|
## 0.1.0 \u2014 Initial release
|
|
15
15
|
|
|
16
16
|
- Project initialized with infernoflow
|
|
17
|
-
`;i.writeFileSync(e,t)}async function Pe(e,o){const{bold:t,cyan:n,gray:r,green:
|
|
17
|
+
`;i.writeFileSync(e,t)}async function Pe(e,o){const{bold:t,cyan:n,gray:r,green:c,yellow:m,red:k}=await import("../ui/output.mjs");console.log(`
|
|
18
18
|
`+t("\u{1F525} infernoflow init --lite")),console.log(" "+"\u2500".repeat(50)+`
|
|
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(
|
|
20
|
-
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const h=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const
|
|
19
|
+
`),console.log(r(" Lite mode: 3 files, no scripts, no workflows, no hooks.")),console.log(r(" Use `infernoflow upgrade` later to expand to the full setup.\n"));const p=s.join(e,"inferno");i.existsSync(p)&&!o&&(console.log(m(` \u26A0 inferno/ already exists. Use --force to overwrite.
|
|
20
|
+
`)),process.exit(0)),i.mkdirSync(p,{recursive:!0});const h=M(e);let S="";if(!process.argv.includes("--yes")&&!process.argv.includes("-y")&&process.stdin.isTTY){const O=G.createInterface({input:process.stdin,output:process.stdout});S=await new Promise(C=>{O.question(r(" What does this project do? (one line, Enter to skip): "),I=>{O.close(),C(I.trim())})})}const A={policyId:h,policyVersion:1,lite:!0,capabilities:[],intent:S||void 0};i.writeFileSync(s.join(p,"contract.json"),JSON.stringify(A,null,2)+`
|
|
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(
|
|
23
|
-
> `),
|
|
24
|
-
`+n("\u{1F525} infernoflow")+
|
|
25
|
-
`)),console.log(" "+
|
|
26
|
-
`),
|
|
27
|
-
`)),i.existsSync(p)||console.log(
|
|
28
|
-
`)),console.log(
|
|
29
|
-
`));return}const
|
|
30
|
-
`+n("\u{1F525} infernoflow")+
|
|
31
|
-
`)),console.log(" Detected: "+r(
|
|
32
|
-
`),
|
|
33
|
-
`+
|
|
34
|
-
`+
|
|
35
|
-
`),console.log(" "+r('infernoflow log "..."')+
|
|
36
|
-
`)),console.log(
|
|
37
|
-
`)),console.log(
|
|
38
|
-
`))}
|
|
39
|
-
`),
|
|
40
|
-
`)
|
|
41
|
-
`);
|
|
22
|
+
`),i.writeFileSync(s.join(p,"sessions.jsonl"),"","utf8"),i.writeFileSync(s.join(p,".lite"),"1","utf8"),console.log(c(" \u2714 Created inferno/contract.json")),console.log(c(" \u2714 Created inferno/capabilities.json")),console.log(c(" \u2714 Created inferno/sessions.jsonl")),console.log(),console.log(" "+t("Ready. Start using it:")),console.log(" "+n("infernoflow log")+r(` "what you're building" --type note`)),console.log(" "+n("infernoflow theme")+r(" \u2014 scan your fonts + colors")),console.log(" "+n("infernoflow context")+r(" \u2014 generate AI context to paste")),console.log(" "+n("infernoflow upgrade")+r(" \u2014 expand to full setup when you need it")),console.log()}const Ne=/^(?:node|npm|npx|yarn|pnpm|bun|git|cd|mkdir|rm|ls|cat|echo|type|dir|copy|del|move|python|python3|pip|go|cargo|java|gradle|mvn|docker|kubectl|curl|wget|ssh|scp|chmod|chown|sudo|brew|apt|yum)\b/i,Fe=/\s(?:&&|\|\||>>|>|<<|<|\|)\s/,Re=/(?:^|\s)[A-Za-z]:\\|\.\.[\\\/]|[\\\/]bin[\\\/]/;function _e(e){if(!e)return{kind:"empty"};const o=e.replace(/^\s*[>$#]\s+/,"").trim();return o?/[\r\n]/.test(o)?{kind:"multiline",value:o}:Ne.test(o)?{kind:"command",value:o}:Fe.test(o)?{kind:"command",value:o}:Re.test(o)?{kind:"command",value:o}:o.length<3?{kind:"tooShort",value:o}:{kind:"ok",value:o}:{kind:"empty"}}async function $e({yes:e}){if(e||!process.stdin.isTTY)return"";const{gray:o,yellow:t,cyan:n}=await import("../ui/output.mjs"),r=" "+o(`What should the next AI agent know about this project?
|
|
23
|
+
> `),c=m=>new Promise(k=>{const p=G.createInterface({input:process.stdin,output:process.stdout});let h=!1;p.on("SIGINT",()=>{h=!0,p.close(),k(null)}),p.on("close",()=>{h&&k(null)}),p.question(m,S=>{p.close(),k(S)})});for(let m=0;m<2;m++){const k=await c(m===0?r:" "+o("> "));if(k===null)return console.log(),"";const p=_e(k);if(p.kind==="ok")return p.value;if(p.kind==="empty")return"";if(p.kind==="command"){console.log(" "+t("\u26A0")+" That looks like a shell command, not a memory."),console.log(" "+o(" Try a short note like: ")+n('"API returns 202 not 200 on async upload"'));continue}if(p.kind==="multiline"){console.log(" "+t("\u26A0")+" Multi-line paste detected \u2014 log a single gotcha at a time."),console.log(" "+o(" Try one short sentence:"));continue}if(p.kind==="tooShort"){console.log(" "+t("\u26A0")+" Too short to be useful as a memory. Skip with Enter, or try again:");continue}}return""}async function De(e,o,t){const{bold:n,cyan:r,gray:c,green:m,yellow:k}=await import("../ui/output.mjs"),p=s.join(e,".ai-memory"),h=s.join(e,"inferno"),S=s.join(p,"sessions.jsonl");if((i.existsSync(p)||i.existsSync(h))&&!o){const j=i.existsSync(p)?".ai-memory/":"inferno/ (legacy)";console.log(`
|
|
24
|
+
`+n("\u{1F525} infernoflow")+c(` \u2014 already set up
|
|
25
|
+
`)),console.log(" "+m("\u2714")+" "+j+` found
|
|
26
|
+
`),Z(e),z(e);try{V(e,{silent:!0})}catch{}console.log(" Quick commands:"),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 handoff to next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
|
|
27
|
+
`)),i.existsSync(p)||console.log(c(" Tip: run ")+r("infernoflow amp migrate")+c(` to move legacy memory into .ai-memory/
|
|
28
|
+
`)),console.log(c(` For contracts & CI gates: infernoflow init --mode full
|
|
29
|
+
`));return}const O=M(e);console.log(`
|
|
30
|
+
`+n("\u{1F525} infernoflow")+c(` \u2014 let's get you set up (30 seconds)
|
|
31
|
+
`)),console.log(" Detected: "+r(O)+`
|
|
32
|
+
`),ke(e),i.existsSync(S)||i.writeFileSync(S,"","utf8"),Se(e,{project:O,config:{autoCapture:!0}}),Z(e);const C=z(e);for(const j of C)(j.created||j.updated)&&g((j.created?"Created: ":"Updated: ")+r(j.rel));try{V(e)}catch(j){P("MCP auto-setup skipped: "+j.message)}const I=await $e({yes:t});I&&(je(e,{ts:new Date().toISOString(),agent:"user",type:"gotcha",summary:I,source:"init"}),console.log(`
|
|
33
|
+
`+m("\u2714")+" First gotcha logged!")),console.log(`
|
|
34
|
+
`+m("\u2714")+` You're set up. Quick commands:
|
|
35
|
+
`),console.log(" "+r('infernoflow log "..."')+c(" \u2014 remember something")),console.log(" "+r("infernoflow switch")+c(" \u2014 generate handoff for next AI")),console.log(" "+r("infernoflow recap")+c(` \u2014 session summary
|
|
36
|
+
`)),console.log(c(` Tip: infernoflow switch --copy puts the handoff on your clipboard.
|
|
37
|
+
`)),console.log(c(` Want contracts & CI gates? Run: infernoflow init --mode full
|
|
38
|
+
`))}function Ge(){console.log(["infernoflow init \u2014 set up persistent AI memory in this project","","Usage:"," infernoflow init Memory-first setup (default, 60 sec)"," infernoflow init --lite Minimal: 3 files, no scripts, no workflows"," infernoflow init --mode full Contracts + CI gates (advanced)"," infernoflow init --adopt Detect existing capabilities from code"," infernoflow init --template <name> Start from a project template","","Options:"," -y, --yes Non-interactive; accept all defaults"," -f, --force Overwrite existing inferno/ or .ai-memory/"," --cursor-hooks Also install Cursor IDE hooks (advanced)"," --vscode-copilot-hooks Also install VS Code Copilot hooks (advanced)"," --lang <lang> Override language detection (with --adopt)"," --framework <name> Override framework detection (with --adopt)"," --project-type <type> Override project-type detection (with --adopt)"," -h, --help Show this help","","Default flow writes .ai-memory/ + AI rule files + MCP server config, then asks","for one first gotcha. Press Enter to skip \u2014 never blocks."].join(`
|
|
39
|
+
`))}async function Xe(e){if(e.includes("--help")||e.includes("-h")){Ge();return}const o=process.cwd(),t=e.includes("--force")||e.includes("-f"),n=e.includes("--yes")||e.includes("-y"),r=e.includes("--adopt"),c=e.find(a=>a.startsWith("--mode="))?.split("=")[1]||(e.indexOf("--mode")!==-1?e[e.indexOf("--mode")+1]:null),m=c==="full"||c==="contract",k=r||e.includes("--template")||e.includes("--cursor-hooks")||e.includes("--vscode-copilot-hooks")||e.includes("--lite");if(!m&&!k){await De(o,t,n);return}if(e.includes("--lite")){await Pe(o,t);return}const p=e.indexOf("--template"),h=p!==-1?e[p+1]:null;if(h){let a;try{a=await import("../templates/index.mjs")}catch{}const l=a?.getTemplate(h);if(!l){const d=a?a.listTemplates().map(x=>x.name).join(", "):"rest-api, nextjs, cli, graphql, monorepo";P(`Unknown template: ${h}. Available: ${d}`),process.exit(1)}const y=s.join(o,"inferno"),E=s.join(y,"scenarios");i.existsSync(y)||i.mkdirSync(y,{recursive:!0}),i.existsSync(E)||i.mkdirSync(E,{recursive:!0});const _=M(o),w=l.capabilities;i.writeFileSync(s.join(y,"contract.json"),JSON.stringify({policyId:_,policyVersion:1,capabilities:w.map(d=>d.id)},null,2)+`
|
|
40
|
+
`),i.writeFileSync(s.join(y,"capabilities.json"),JSON.stringify({capabilities:w.map(d=>({id:d.id,description:d.description,since:new Date().toISOString().slice(0,10),source:`template:${h}`}))},null,2)+`
|
|
41
|
+
`);for(const d of w)i.writeFileSync(s.join(E,`${d.id}.json`),JSON.stringify({id:`${d.id}-happy-path`,capability:d.id,description:`Happy path for ${d.description||d.id}`,steps:[{action:"invoke",target:d.id,input:{}},{action:"assert",field:"status",value:"success"}],capabilitiesCovered:[d.id]},null,2)+`
|
|
42
|
+
`);Q(s.join(y,"CHANGELOG.md"),_),i.writeFileSync(s.join(y,"CONTEXT.md"),`# ${_} \u2014 infernoflow context
|
|
42
43
|
|
|
43
|
-
> Template: ${h} \u2014 ${
|
|
44
|
+
> Template: ${h} \u2014 ${l.description}
|
|
44
45
|
|
|
45
46
|
## Hint
|
|
46
|
-
${
|
|
47
|
+
${l.contextHint}
|
|
47
48
|
|
|
48
49
|
## Capabilities (${w.length})
|
|
49
|
-
${w.map(
|
|
50
|
+
${w.map(d=>`- \`${d.id}\`: ${d.description}`).join(`
|
|
50
51
|
`)}
|
|
51
|
-
`),
|
|
52
|
-
`));const
|
|
53
|
-
`)),
|
|
54
|
-
`,"utf8"),u||g("Created: "+
|
|
52
|
+
`),l.scripts&&(info("Suggested package.json scripts for this template:"),Object.entries(l.scripts).forEach(([d,x])=>console.log(` ${L(d)}: ${v(x)}`)),console.log()),q(`Initialised from template ${L(f(h))} \u2014 ${L(String(w.length))} capabilities`),console.log(),info(`Run ${f("infernoflow vibe")} to start vibe coding mode`),console.log();return}const S=e.includes("--cursor-hooks"),A=e.includes("--vscode-copilot-hooks"),O=e.includes("--report-json"),C=e.includes("--report-json-only"),I=e.includes("--report-human-only"),j=W(e,"--lang"),U=W(e,"--framework"),K=W(e,"--project-type"),u=C;C&&I&&(console.error("Error: --report-json-only and --report-human-only cannot be used together."),process.exit(1)),u||fe("init");const b=s.join(o,"inferno"),ee=s.join(o,".github","workflows");i.existsSync(b)&&!t&&(u&&(console.log(JSON.stringify({ok:!1,error:"inferno_exists",hint:"Use --force to overwrite"},null,2)),process.exit(1)),P("inferno/ already exists. Use --force to overwrite."),console.log(),process.exit(0));const N=M(o),Y="CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";let F=N,T=Y.split(",").map(a=>a.trim());if(r){let l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});if(!n&&!C){const w=G.createInterface({input:process.stdin,output:process.stdout}),d=l.developmentProfile||{},x=d.detected||{};console.log(v(` Review inferred development stack (press Enter to accept detected values)
|
|
53
|
+
`));const le=await D(w,"Language",d.language||x.language||"unknown"),ce=await D(w,"Framework",d.framework||x.framework||"unknown"),ae=await D(w,"Project type",d.projectType||x.projectType||"unknown");w.close(),l=H(o,{language:le,framework:ce,projectType:ae})}const y=l.capabilities,E=ge(y);C?console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)):(console.log(),console.log(v(ye(y))),console.log(),console.log(v(he(l))),console.log(),O&&!I&&(console.log(JSON.stringify({mode:"adopt",policyId:N,inferredCapabilities:E,components:l.components,displayFields:l.displayFields,externalLibraries:l.externalLibraries,uiLayout:l.uiLayout,styling:l.styling,developmentProfile:l.developmentProfile,apiCalls:l.apiCalls},null,2)),console.log()));const _=await ue(y,n);F=N,T=_.map(w=>w.id)}else if(!n){const a=G.createInterface({input:process.stdin,output:process.stdout});console.log(v(` Press Enter to accept defaults
|
|
54
|
+
`)),F=await D(a,"Project / policy name",N),T=(await D(a,"Capabilities (comma-separated)",Y)).split(",").map(y=>y.trim()).filter(Boolean),a.close(),console.log()}if(i.mkdirSync(b,{recursive:!0}),r){const a=T.map(y=>({id:y,title:y.replace(/([A-Z])/g," $1").trim()})),l=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});me(b,F,a,l),u||(g("Created: "+f("inferno/contract.json")),g("Created: "+f("inferno/capabilities.json")),g("Created: "+f("inferno/scenarios/adoption_baseline.json")),g("Created: "+f("inferno/adoption_profile.json")),g("Created: "+f("inferno/CHANGELOG.md")))}else Ae(s.join(b,"contract.json"),F,T),u||g("Created: "+f("inferno/contract.json")),Te(s.join(b,"capabilities.json"),T),u||g("Created: "+f("inferno/capabilities.json")),Ee(s.join(b,"scenarios"),T),u||g("Created: "+f("inferno/scenarios/happy_path.json")),Q(s.join(b,"CHANGELOG.md"),F),u||g("Created: "+f("inferno/CHANGELOG.md"));const R=Ce(),oe=s.join(R,"scripts","inferno-doc-gate.mjs"),ne=s.join(o,"scripts","inferno-doc-gate.mjs");J(oe,ne,t,u);const te=s.join(R,"scripts","inferno-install-hooks.mjs"),ie=s.join(o,"scripts","inferno-install-hooks.mjs");J(te,ie,t,u);const se=s.join(R,"ci","github-inferno-check.yml"),re=s.join(ee,"infernoflow-check.yml");if(J(se,re,t,u),Ie(o,u),S&&we({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),A&&ve({cwd:o,templatesRoot:R,force:t,silent:u,logOk:a=>{u||g(a)},logWarn:a=>{u||P(a)}}),r){const a=s.join(b,"context-state.json");let l={};try{l=JSON.parse(i.readFileSync(a,"utf8"))}catch{}const y=H(o,{language:j||void 0,framework:U||void 0,projectType:K||void 0});l.stack=y.developmentProfile,i.writeFileSync(a,JSON.stringify(l,null,2)+`
|
|
55
|
+
`,"utf8"),u||g("Created: "+f("inferno/context-state.json"))}if(!u){q("infernoflow initialized!");const a=s.join(b,"integrations.json");!(process.env.ANTHROPIC_API_KEY||process.env.OPENAI_API_KEY||process.env.GOOGLE_AI_API_KEY||process.env.OPENROUTER_API_KEY||process.env.GEMINI_API_KEY)&&!i.existsSync(a)&&(console.log(),console.log(` ${$("\u{1F4A1}")} ${L("Tip:")} connect an AI provider for explain, why, review, and changelog AI.`),console.log(` ${f("infernoflow ai setup")} \u2014 takes 60 seconds`)),de([f("infernoflow status")+" \u2014 see your contract at a glance",f("infernoflow check")+" \u2014 validate everything",(r?"Review inferred baseline in ":"Edit ")+$("inferno/capabilities.json")+(r?" and refine IDs/titles":" to describe each capability in detail"),"Add more "+$("inferno/scenarios/*.json")+" files for edge cases","Add "+f("inferno:check")+" to your CI pipeline",...S?["Restart Cursor \u2014 hooks write assistant text to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...A?["Restart VS Code \u2014 Copilot hooks append prompts + assistant (from transcript) to "+$("inferno/CONTEXT.draft.md"),"Promote when ready: "+f("npm run inferno:promote-draft -- --append-notes")]:[],...!S&&!A?["Optional: "+f("infernoflow install-cursor-hooks")+" or "+f("infernoflow install-vscode-copilot-hooks")]:[]])}}export{Z as ensureGitignoreEntries,Xe as initCommand};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import*as n from"node:fs";import*as l from"node:path";import*as w from"node:os";import{fileURLToPath as C}from"node:url";import{execSync as j}from"node:child_process";import{detectIdeContext as h}from"../ai/ideDetection.mjs";import{header as _,ok as y,warn as x,info as S,done as M,cyan as f,yellow as $,bold as g,green as d}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";const
|
|
2
|
-
`,"utf8"),{updated:!0,path:o})}function
|
|
3
|
-
`,"utf8"),i.cursorMcp=!0,o("Registered MCP server in "+f(".cursor/mcp.json")))}catch(e){r(".cursor/mcp.json update skipped: "+e.message)}try{
|
|
1
|
+
import*as n from"node:fs";import*as l from"node:path";import*as w from"node:os";import{fileURLToPath as C}from"node:url";import{execSync as j}from"node:child_process";import{detectIdeContext as h}from"../ai/ideDetection.mjs";import{header as _,ok as y,warn as x,info as S,done as M,cyan as f,yellow as $,bold as g,green as d,gray as k}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import"../vsCodeCopilotHooksInstall.mjs";const J=l.dirname(C(import.meta.url));function v(){return l.resolve(J,"../../templates")}function P(s){try{return j(`npx infernoflow ${s}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(t){return t.stdout||t.stderr||t.message}}const O=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function F(s){const t=l.join(w.homedir(),".claude.json");let c={};if(n.existsSync(t))try{c=JSON.parse(n.readFileSync(t,"utf8"))}catch{c={}}c.mcpServers||(c.mcpServers={});const o=c.mcpServers.infernoflow;if(o&&o.args&&o.args[0]===s)return{updated:!1};c.mcpServers.infernoflow={command:"node",args:[s]};const r=JSON.stringify(c,null,2).replace(/\u0000+/g,"");return n.writeFileSync(t,r,"utf8"),{updated:!0,path:t}}function N(s,t){const c=l.join(s,".vscode"),o=l.join(c,"mcp.json");let r={};if(n.existsSync(o))try{r=JSON.parse(n.readFileSync(o,"utf8"))}catch{r={}}r.servers||(r.servers={});const i=r.servers.infernoflow;return i&&i.args&&i.args[0]===t?{updated:!1}:(r.servers.infernoflow={type:"stdio",command:"node",args:[t]},n.mkdirSync(c,{recursive:!0}),n.writeFileSync(o,JSON.stringify(r,null,2)+`
|
|
2
|
+
`,"utf8"),{updated:!0,path:o})}function R(s,t){const c=l.join(s,".claude"),o=l.join(c,"settings.json");let r={};if(n.existsSync(o))try{r=JSON.parse(n.readFileSync(o,"utf8"))}catch{r={}}const i=new Set(r.allowedTools||[]);for(const a of O)i.add(`mcp__infernoflow__${a}`);const p={...r,allowedTools:[...i]};return n.mkdirSync(c,{recursive:!0}),n.writeFileSync(o,JSON.stringify(p,null,2),"utf8"),o}function T(s,{silent:t=!1}={}){const c=v(),o=t?()=>{}:e=>y(e),r=t?()=>{}:e=>x(e),i={mcpServer:!1,claudeJson:!1,claudeSettings:!1},p=l.join(c,"cursor","inferno-mcp-server.mjs"),a=l.join(s,".cursor","inferno-mcp-server.mjs");try{n.existsSync(a)||(n.mkdirSync(l.dirname(a),{recursive:!0}),n.copyFileSync(p,a),i.mcpServer=!0,o("Copied MCP server \u2192 "+f(".cursor/inferno-mcp-server.mjs")))}catch(e){r("MCP server copy skipped: "+e.message)}try{F(a).updated&&(i.claudeJson=!0,o("Registered MCP server in "+f("~/.claude.json")))}catch(e){r("~/.claude.json update skipped: "+e.message)}try{N(s,a).updated&&(i.vscodeMcp=!0,o("Registered MCP server in "+f(".vscode/mcp.json")+k(" (Copilot Chat)")))}catch(e){r(".vscode/mcp.json update skipped: "+e.message)}try{const e=l.join(s,".cursor","mcp.json");let u={};if(n.existsSync(e))try{u=JSON.parse(n.readFileSync(e,"utf8"))}catch{u={}}u.mcpServers||(u.mcpServers={});const m=u.mcpServers.infernoflow;(!m||!m.args||m.args[0]!==a)&&(u.mcpServers.infernoflow={command:"node",args:[a],env:{}},n.mkdirSync(l.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(u,null,2)+`
|
|
3
|
+
`,"utf8"),i.cursorMcp=!0,o("Registered MCP server in "+f(".cursor/mcp.json")))}catch(e){r(".cursor/mcp.json update skipped: "+e.message)}try{R(s,!1),i.claudeSettings=!0,o("Pre-approved infernoflow tools in "+f(".claude/settings.json"))}catch(e){r(".claude/settings.json skipped: "+e.message)}return i}async function E(s){const t=process.cwd(),c=s.includes("--force")||s.includes("-f"),o=s.includes("--yes")||s.includes("-y"),r=v();_("infernoflow setup");const{ideDetected:i}=h("auto");S(`IDE detected: ${g(i==="cursor"?"Cursor":i==="vscode"?"VS Code":i==="windsurf"?"Windsurf":"unknown")}`);const a=l.join(t,".ai-memory");n.existsSync(a)?y(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
|
|
4
4
|
${$(".ai-memory/")} not found \u2014 running init ...
|
|
5
|
-
`),
|
|
6
|
-
${g("What was set up:")}`),console.log(` ${d("\u2714")} MCP server installed \u2192 ${f(".cursor/inferno-mcp-server.mjs")}`),e.cursorMcp&&console.log(` ${d("\u2714")} Cursor MCP config \u2192 ${f(".cursor/mcp.json")}`),e.vscodeMcp&&console.log(` ${d("\u2714")} VS Code Copilot MCP config \u2192 ${f(".vscode/mcp.json")}`),e.claudeJson&&console.log(` ${d("\u2714")} Claude Code MCP config \u2192 ${f("~/.claude.json")}`),e.claudeSettings&&console.log(` ${d("\u2714")} Auto-approved tools \u2192 ${f(".claude/settings.json")}`),console.log(),console.log(` ${g("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${f('"call the amp_write tool with a test note"')}`),console.log()}export{
|
|
5
|
+
`),P(o?"init --yes":"init")),console.log(),S("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const e=T(t,{silent:!1});console.log(),M("infernoflow ready"),console.log(`
|
|
6
|
+
${g("What was set up:")}`),console.log(` ${d("\u2714")} MCP server installed \u2192 ${f(".cursor/inferno-mcp-server.mjs")}`),e.cursorMcp&&console.log(` ${d("\u2714")} Cursor MCP config \u2192 ${f(".cursor/mcp.json")}`),e.vscodeMcp&&console.log(` ${d("\u2714")} VS Code Copilot MCP config \u2192 ${f(".vscode/mcp.json")}`),e.claudeJson&&console.log(` ${d("\u2714")} Claude Code MCP config \u2192 ${f("~/.claude.json")}`),e.claudeSettings&&console.log(` ${d("\u2714")} Auto-approved tools \u2192 ${f(".claude/settings.json")}`),console.log(),console.log(` ${g("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${f('"call the amp_write tool with a test note"')}`),console.log()}export{O as MCP_TOOLS,T as autoSetupMcp,E as setupCommand,F as updateClaudeJson,N as updateVscodeMcpJson,R as writeClaudeSettings};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import*as o from"node:fs";import*as l from"node:path";import{header as L,ok as V,fail as J,warn as U,section as $,bold as r,cyan as
|
|
2
|
-
`).filter(Boolean).map(g=>{try{return JSON.parse(g)}catch{return null}}).filter(Boolean):[],c=e.filter(g=>g.type==="gotcha").length,
|
|
3
|
-
${n("Want capability contracts + CI gates? Run:")} ${
|
|
4
|
-
`);return}o.existsSync(
|
|
5
|
-
${m("\u26A0")} ${
|
|
6
|
-
${d("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),o.existsSync(N)){const t=o.readdirSync(N).filter(e=>e.endsWith(".json"));t.length===0?U("No scenario files \u2014 add .json files to inferno/scenarios/"):t.forEach(e=>{try{const c=JSON.parse(o.readFileSync(l.join(N,e),"utf8")),
|
|
1
|
+
import*as o from"node:fs";import*as l from"node:path";import{header as L,ok as V,fail as J,warn as U,section as $,bold as r,cyan as w,yellow as m,gray as n,green as d,red as R,white as W}from"../ui/output.mjs";import{ampPaths as _}from"../amp/io.mjs";function E(a){const s=Math.floor((Date.now()-a)/1e3);return s<60?"just now":s<3600?`${Math.floor(s/60)}m ago`:s<86400?`${Math.floor(s/3600)}h ago`:`${Math.floor(s/86400)}d ago`}function z(a,s){const f=new Set;if(o.existsSync(a))for(const i of o.readdirSync(a).filter(y=>y.endsWith(".json")))try{(JSON.parse(o.readFileSync(l.join(a,i),"utf8")).capabilitiesCovered||[]).forEach(M=>f.add(M))}catch{}return{covered:s.filter(i=>f.has(i)),uncovered:s.filter(i=>!f.has(i))}}async function K(a=[]){const s=a.includes("--json"),f=process.cwd(),i=l.join(f,"inferno"),y=l.join(f,".ai-memory");s||L("status"),!o.existsSync(i)&&!o.existsSync(y)&&(s&&(console.log(JSON.stringify({ok:!1,error:"not_initialized",hint:"Run: infernoflow init"},null,2)),process.exit(1)),J("not initialized \u2014 neither .ai-memory/ nor inferno/ found","Run: infernoflow init"),console.log(),process.exit(1));const M=l.join(y,"amp.json"),k=l.join(i,"config.json"),S=l.join(i,"contract.json"),D=o.existsSync(M)||o.existsSync(y),H=(()=>{try{return JSON.parse(o.readFileSync(k,"utf8")).mode||null}catch{return null}})();if(D||H==="memory"||!o.existsSync(S)&&o.existsSync(k)){const t=_(f).sessions,e=o.existsSync(t)?o.readFileSync(t,"utf8").split(`
|
|
2
|
+
`).filter(Boolean).map(g=>{try{return JSON.parse(g)}catch{return null}}).filter(Boolean):[],c=e.filter(g=>g.type==="gotcha").length,h=e.filter(g=>g.type==="decision").length,u=e.filter(g=>g.type==="attempt").length,p=e[e.length-1];if(s){console.log(JSON.stringify({ok:!0,mode:"memory",entries:e.length,gotchas:c,decisions:h,attempts:u,lastEntry:p?p.ts:null},null,2));return}$("Session memory"),console.log(` ${n("entries")} ${r(String(e.length))}`),console.log(` ${n("gotchas")} ${r(String(c))}`),console.log(` ${n("decisions")} ${r(String(h))}`),console.log(` ${n("attempts")} ${r(String(u))}`),p&&console.log(` ${n("last entry")} ${n(E(new Date(p.ts).getTime()))}`),console.log(),e.length===0?console.log(` ${m("\u25CF")} ${r(m("empty"))} ${n("\u2014 log your first gotcha:")} ${w('infernoflow log "..." --type gotcha')}`):console.log(` ${d("\u25CF")} ${r(d("ready"))} ${n("\u2014 run")} ${w("infernoflow recap")} ${n("for the full session summary")}`),o.existsSync(S)?console.log():console.log(`
|
|
3
|
+
${n("Want capability contracts + CI gates? Run:")} ${w("infernoflow init --mode full")}
|
|
4
|
+
`);return}o.existsSync(S)||(s&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),J("contract.json not found"),console.log(),process.exit(1));const j=JSON.parse(o.readFileSync(S,"utf8")),C=j.capabilities||[],F=o.statSync(S),N=l.join(i,"scenarios"),O=l.join(i,"CHANGELOG.md"),P=l.join(i,"capabilities.json"),{covered:I,uncovered:x}=z(N,C),G=o.existsSync(O)&&/##\s+Unreleased/i.test(o.readFileSync(O,"utf8")),v=[];x.length>0&&v.push(`${x.length} capabilities without scenario coverage`),G||v.push("CHANGELOG missing ## Unreleased section");const b=v.length===0;if(s){const t={ok:b,driftReasons:v,project:{policyId:j.policyId||null,policyVersion:j.policyVersion||null,lastChange:E(F.mtimeMs)},capabilities:{total:C.length,uncovered:x},changelog:{hasUnreleased:G}};console.log(JSON.stringify(t,null,2)),process.exit(b?0:1)}b||($("Drift"),v.forEach(t=>console.log(` ${m("\u26A0")} ${t}`))),$("Project"),console.log(` ${n("policy")} ${r(j.policyId||"\u2014")}`),console.log(` ${n("version")} ${r("v"+(j.policyVersion||"?"))}`),console.log(` ${n("last change")} ${n(E(F.mtimeMs))}`),$(`Capabilities ${n("("+C.length+")")}`);let A={};if(o.existsSync(P))try{(JSON.parse(o.readFileSync(P,"utf8")).capabilities||[]).forEach(e=>{A[e.id]=e})}catch{}if(C.forEach(t=>{const e=A[t],h=I.includes(t)?d("\u2714"):R("\u2718"),u=e?.title?n(` \u2014 ${e.title}`):"",p=e?.since?n(` [${e.since}]`):"";console.log(` ${h} ${W(t)}${u}${p}`)}),x.length>0?console.log(`
|
|
5
|
+
${m("\u26A0")} ${x.length} capability(ies) lack scenario coverage`):console.log(`
|
|
6
|
+
${d("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),o.existsSync(N)){const t=o.readdirSync(N).filter(e=>e.endsWith(".json"));t.length===0?U("No scenario files \u2014 add .json files to inferno/scenarios/"):t.forEach(e=>{try{const c=JSON.parse(o.readFileSync(l.join(N,e),"utf8")),h=c.steps?.length||0,u=(c.capabilitiesCovered||[]).length;console.log(` ${d("\u2714")} ${w(e)} ${n(`\u2014 ${h} steps, ${u} caps covered`)}`)}catch{console.log(` ${R("\u2718")} ${w(e)} ${n("\u2014 invalid JSON")}`)}})}else U("scenarios/ directory not found");if($("Changelog"),o.existsSync(O)){const t=o.readFileSync(O,"utf8");/##\s+Unreleased/i.test(t)?V("Has ## Unreleased section"):J("Missing ## Unreleased section"),t.split(`
|
|
7
7
|
`).filter(c=>/^##\s/.test(c)).slice(0,3).forEach(c=>console.log(` ${n(c)}`))}else J("inferno/CHANGELOG.md not found");console.log(),console.log(b?` ${d("\u25CF")} ${r(d("ready"))} ${n("\u2014 run infernoflow check for full validation")}`:` ${m("\u25CF")} ${r(m("needs attention"))} ${n("\u2014 run infernoflow check for details")}`),console.log()}export{K as statusCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as
|
|
1
|
+
import{execFileSync as O}from"node:child_process";import*as u from"node:fs";import*as m from"node:path";import{fileURLToPath as S}from"node:url";import{header as C,section as g,ok as l,warn as y,yellow as x,gray as k}from"../ui/output.mjs";function _(t){const i=m.join(t,"inferno","contract.json"),r=m.join(t,"inferno","CONTEXT.md");if(!u.existsSync(i))return null;if(!u.existsSync(r))return["CONTEXT.md is missing"];let e;try{e=JSON.parse(u.readFileSync(i,"utf8"))}catch(o){return[`contract.json unreadable: ${o.message}`]}const c=u.readFileSync(r,"utf8"),s=[];if(e.policyId){const o=c.match(/^#\s+([^\n]+)/m),n=o?o[1]:"";n.includes(e.policyId)||s.push(`H1 "${n}" doesn't match policyId "${e.policyId}"`)}if(typeof e.policyVersion=="number"){const o=c.match(/\bv(\d+(?:\.\d+)?)\b/i);if(o){const n=parseFloat(o[1]);n!==e.policyVersion&&s.push(`CONTEXT.md references v${n} but contract is v${e.policyVersion}`)}}const a=(Array.isArray(e.capabilities)?e.capabilities.map(o=>typeof o=="string"?o:o.id).filter(Boolean):[]).filter(o=>!c.includes(o));return a.length>0&&s.push(`CONTEXT.md is missing capabilities: ${a.join(", ")}`),s.length>0?s:[]}const E=S(import.meta.url),D=m.dirname(E),$=m.resolve(D,"..","..","bin","infernoflow.mjs");function I(t){const i=O(process.execPath,[$,...t],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return JSON.parse(i)}function N(t){try{return{ok:!0,data:I(t)}}catch(i){const r=i?.stdout?.toString?.()||"";try{return{ok:!1,data:JSON.parse(r)}}catch{return{ok:!1,data:{ok:!1,errors:["command_failed"]}}}}}async function P(t=[]){const i=t.includes("--auto"),r=t.includes("--json"),e=t.includes("--dry-run");i||(r&&(console.log(JSON.stringify({ok:!1,error:"missing_required_flag",hint:"Use: infernoflow sync --auto"},null,2)),process.exit(1)),C("sync"),y("missing --auto flag"),console.log(` ${x("\u2192")} infernoflow sync --auto`),console.log(),process.exit(1));const c=N(["pr-impact","--json"]),s=_(process.cwd())||[],d=s.length>0,a=!c.data?.ok||d,o=c.data?.confidence||"low",n=o==="high"?"auto":o==="medium"?"ask":"block",T=a?[...d?["Regenerate CONTEXT.md via `infernoflow context`"]:[],"Generate inferno update proposal (suggest)","Review changes","Validate with check --json"]:["No inferno drift detected","Validate with check --json"],f=N(["check","--json"]),h={ok:c.ok&&f.ok&&!!f.data?.ok,mode:"auto-skeleton",dryRun:e,needsSync:a,didApply:!1,confidence:o,policyDecision:n,actions:T,prImpact:c.data,postCheck:f.data,contextDrift:s,reasonCodes:[...a?["DRIFT_DETECTED"]:["NO_DRIFT"],...d?["CONTEXT_MD_STALE"]:[],`POLICY_${n.toUpperCase()}`,...n==="auto"?["AUTO_APPLY_DISABLED_IN_SKELETON"]:[]]};if(r&&(console.log(JSON.stringify(h,null,2)),process.exit(h.ok?0:1)),C("sync --auto"),g("State"),a?y("Inferno drift detected"):l("No inferno drift detected"),d)for(const p of s)y(`CONTEXT.md drift: ${p}`);l(`Confidence: ${k(o)}`),l(`Policy decision: ${k(n)}`),l(`Apply mode: ${k("skeleton (no file writes)")}`),e&&l("Dry run enabled"),g("Plan"),T.forEach(p=>console.log(` ${x("\u2192")} ${p}`)),g("Validation"),f.ok&&f.data?.ok?l("Post-check passed"):y("Post-check failed; see infernoflow check --json"),console.log(),process.exit(h.ok?0:1)}export{P as syncCommand};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import*as o from"node:fs";import*as a from"node:path";const u=new Set(["--help","-h","--version","-v","commands","doctor","setup","init","uninstall"]);function p(t){const n=a.join(t,".ai-memory"),e=a.join(t,"inferno");return o.existsSync(n)?a.join(n,".last-cli-version"):o.existsSync(e)?a.join(e,".last-cli-version"):null}function d(t){try{return o.readFileSync(t,"utf8").trim()}catch{return""}}async function m(t,n){if(!t||n&&u.has(n))return;const e=process.cwd(),c=p(e);if(!c)return;const s=d(c);if(s===t)return;let f=!1,l=s==="";try{const{writeInitRuleFiles:i}=await import("./ruleFiles.mjs");i(e)}catch{}try{const{ensureGitignoreEntries:i}=await import("./commands/init.mjs");i(e,{silent:!0})}catch{}try{const{autoSetupMcp:i}=await import("./commands/setup.mjs"),r=i(e,{silent:!0});f=!!(r&&(r.mcpServer||r.claudeJson||r.cursorMcp||r.vscodeMcp||r.claudeSettings))}catch{}try{o.writeFileSync(c,t,"utf8")}catch{}if(f||l)try{const{gray:i}=await import("./ui/output.mjs"),r=s?`upgraded ${s} \u2192 ${t}`:`initialized for ${t}`;process.stderr.write(i(` infernoflow: ${r}, wired MCP servers + rule files
|
|
2
|
+
`))}catch{process.stderr.write(` infernoflow: refreshed for v${t}
|
|
3
|
+
`)}}export{m as runUpgradeBackfillIfNeeded};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infernoflow",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.11",
|
|
4
4
|
"description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|