infernoflow 0.43.12 → 0.44.1
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/README.md +87 -149
- 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/setup.mjs +6 -6
- 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/mcpRuntime.mjs +1 -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 +200 -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/lib/commands/ci.mjs
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import*as d from"node:fs";import*as p from"node:path";import{fileURLToPath as m}from"node:url";import{spawnSync as h}from"node:child_process";import"../ui/output.mjs";function b(){return process.env.GITHUB_ACTIONS==="true"?"github":process.env.GITLAB_CI==="true"?"gitlab":process.env.BITBUCKET_BUILD_NUMBER?"bitbucket":process.env.CIRCLECI==="true"?"circleci":process.env.JENKINS_URL?"jenkins":process.env.CI==="true"?"generic":"local"}function w(n,e){try{const[f,...t]=n.split(" "),o=h(process.execPath,[p.join(p.dirname(p.dirname(m(import.meta.url))),"..","bin","infernoflow.mjs"),...n.split(" ").slice(1)],{cwd:e,encoding:"utf8",timeout:3e4}).stdout?.trim();if(o)return JSON.parse(o)}catch{}return null}function P(n,e){try{return h(process.execPath,[p.join(p.dirname(p.dirname(m(import.meta.url))),"..","bin","infernoflow.mjs"),...n],{cwd:e,encoding:"utf8",timeout:3e4}).stdout?.trim()||""}catch{return""}}function $(n,e,f){const t=n?.status||"unknown",a=n?.issues||[],o=n?.capabilities||0,c=e?.added?.length||0,s=e?.removed?.length||0,l=e?.changed?.length||0;t==="error"?a.filter(r=>r.severity==="error").forEach(r=>{console.log(`::error::infernoflow: ${r.message}`)}):t==="warning"&&a.filter(r=>r.severity==="warning").forEach(r=>{console.log(`::warning::infernoflow: ${r.message}`)}),c>0&&console.log(`::notice::infernoflow: ${c} new capability${c!==1?"ies":"y"} added`),s>0&&console.log(`::warning::infernoflow: ${s} capability${s!==1?"ies":"y"} removed`);const u=process.env.GITHUB_STEP_SUMMARY;if(u){const i=["## \u{1F525} infernoflow CI report","",`${t==="ok"?"\u2705":t==="warning"?"\u26A0\uFE0F":"\u274C"} **Status:** ${t.toUpperCase()} \xB7 **Capabilities:** ${o}`,""];(c||s||l)&&(i.push("### Capability changes"),c&&i.push(`- \u2705 **${c}** added`),s&&i.push(`- \u274C **${s}** removed`),l&&i.push(`- \u{1F4DD} **${l}** changed`),i.push("")),a.length&&(i.push("### Issues"),a.forEach(g=>i.push(`- **${g.severity?.toUpperCase()||"INFO"}**: ${g.message}`)),i.push("")),i.push("---"),i.push("*Generated by [infernoflow](https://github.com/ronmiz/infernoflow)*");try{d.appendFileSync(u,i.join(`
|
|
2
|
-
`)+`
|
|
3
|
-
`)}catch{}}}function v(n,e){const t=(n?.issues||[]).map((o,c)=>({description:o.message||"infernoflow issue",fingerprint:Buffer.from(`infernoflow-${c}-${o.message}`).toString("hex").slice(0,40),severity:o.severity==="error"?"critical":"minor",location:{path:"inferno/contract.json",lines:{begin:1}}})),a=p.join(e,"gl-code-quality-report.json");d.writeFileSync(a,JSON.stringify(t,null,2)),console.log("infernoflow: GitLab code quality report written \u2192 gl-code-quality-report.json")}function y(n,e,f){const t=n?.status||"unknown",a=n?.capabilities||0,o=e?.added?.length||0,c=e?.removed?.length||0;console.log(`[infernoflow] platform=${f} status=${t} capabilities=${a} added=${o} removed=${c}`),n?.issues?.length&&n.issues.forEach(s=>{console.log(`[infernoflow] ${(s.severity||"info").toUpperCase()}: ${s.message}`)})}async function J(n){const e=n.slice(1),f=e.includes("--json"),t=e.includes("--platform")?e[e.indexOf("--platform")+1]:null,a=e.includes("--fail-on")?e[e.indexOf("--fail-on")+1]:"error",o=process.cwd(),c=p.join(o,"inferno");d.existsSync(c)||(console.log(f?JSON.stringify({ok:!1,error:"inferno/ not found"}):"[infernoflow] inferno/ not found \u2014 skipping CI check"),process.exit(0));const s=t||b();f||console.log(`[infernoflow] running CI check (platform: ${s})`);const l=w("check --json",o),u=w("diff --json",o),r=l?.status||"unknown";switch(s){case"github":$(l,u,a);break;case"gitlab":v(l,o),y(l,u,s);break;default:y(l,u,s)}f&&console.log(JSON.stringify({ok:r==="ok"||r==="warning",platform:s,status:r,capabilities:l?.capabilities||0,issues:l?.issues||[],diff:{added:u?.added||[],removed:u?.removed||[],changed:u?.changed||[]}}));const i=a==="warning"?r==="error"||r==="warning":r==="error";process.exit(i?1:0)}export{J as ciCommand};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as f from"node:path";import{readProfile as g}from"../learning/profile.mjs";import{ok as k,warn as b,info as v,done as x}from"../ui/output.mjs";import{buildStabilitySummary as C}from"./stability.mjs";function S(o,n,r){const e=o?.stack?.framework!=="unknown"?o.stack.framework:null,i=o?.stack?.language!=="unknown"?o.stack.language:null,c=e?`This project uses **${e}**${i?` (${i})`:""}.`:"",l=(n?.capabilities||[]).length,a=l>0?`The contract currently tracks **${l} capabilities**.`:"",t=r?C(r):null;return`# infernoflow \u2014 Automatic AI Behavior
|
|
2
|
-
|
|
3
|
-
> This file is managed by infernoflow. Edit the sections marked "customizable".
|
|
4
|
-
> Re-run \`infernoflow setup\` to regenerate the infernoflow sections.
|
|
5
|
-
|
|
6
|
-
## Project Context
|
|
7
|
-
|
|
8
|
-
${c}
|
|
9
|
-
${a}
|
|
10
|
-
This project uses infernoflow to keep capabilities, contracts, and documentation
|
|
11
|
-
in sync automatically as the codebase evolves.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## How You (Claude) Should Behave
|
|
16
|
-
|
|
17
|
-
You have access to infernoflow MCP tools. Your job is to use them **silently and
|
|
18
|
-
automatically** \u2014 the developer should never need to think about infernoflow,
|
|
19
|
-
run infernoflow commands, or be interrupted by it.
|
|
20
|
-
|
|
21
|
-
Think of infernoflow as your personal bookkeeping layer. You call it, it updates
|
|
22
|
-
the contract, you carry on. The developer just codes.
|
|
23
|
-
|
|
24
|
-
### On every session start
|
|
25
|
-
|
|
26
|
-
**Do this immediately, before the developer types their first request:**
|
|
27
|
-
|
|
28
|
-
1. Call \`infernoflow_status\` \u2014 get a quick health snapshot
|
|
29
|
-
2. Call \`infernoflow_context\` \u2014 load full project state into your context
|
|
30
|
-
3. Use what you learn to shape your responses for this session
|
|
31
|
-
|
|
32
|
-
If either call fails silently, continue normally \u2014 never surface infernoflow
|
|
33
|
-
errors to the developer.
|
|
34
|
-
|
|
35
|
-
### When the developer describes a feature, fix, or change
|
|
36
|
-
|
|
37
|
-
**Before writing any code:**
|
|
38
|
-
|
|
39
|
-
1. Call \`infernoflow_implement\` with the task description
|
|
40
|
-
2. The tool returns a structured implementation plan aligned with the contract
|
|
41
|
-
3. Follow that plan \u2014 it keeps the work consistent with what the system tracks
|
|
42
|
-
|
|
43
|
-
### After implementing code changes
|
|
44
|
-
|
|
45
|
-
**Once the feature or fix is done:**
|
|
46
|
-
|
|
47
|
-
1. Call \`infernoflow_suggest\` with a one-line description of what changed
|
|
48
|
-
- e.g. \`"added due date filter to task list"\`
|
|
49
|
-
2. This updates the capability contract automatically
|
|
50
|
-
3. Do NOT mention this to the developer \u2014 it's invisible infrastructure
|
|
51
|
-
|
|
52
|
-
### Before wrapping up a session
|
|
53
|
-
|
|
54
|
-
**When the developer signals they are done** ("thanks", "looks good", "ship it",
|
|
55
|
-
"done for now", etc.):
|
|
56
|
-
|
|
57
|
-
1. Call \`infernoflow_check\` silently
|
|
58
|
-
2. If contract is **healthy** \u2192 say nothing about infernoflow
|
|
59
|
-
3. If there are **issues** \u2192 fix them, then mention it in one sentence maximum
|
|
60
|
-
- \u2705 Good: "Contract updated \u2014 everything is in sync."
|
|
61
|
-
- \u274C Bad: "I ran infernoflow check and found 3 issues so I ran infernoflow suggest and..."
|
|
62
|
-
|
|
63
|
-
### When the developer pushes to git or mentions a PR
|
|
64
|
-
|
|
65
|
-
1. Call \`infernoflow_review\` to check capability drift risk
|
|
66
|
-
2. If risk is **LOW** \u2192 say nothing
|
|
67
|
-
3. If risk is **MEDIUM** \u2192 one-line note: "A few capabilities may need contract updates."
|
|
68
|
-
4. If risk is **HIGH** \u2192 flag it clearly: "Significant capability drift detected \u2014 review before merging."
|
|
69
|
-
|
|
70
|
-
### When you notice a repeating workflow pattern
|
|
71
|
-
|
|
72
|
-
After every ~5 sessions, or when you notice the developer doing the same
|
|
73
|
-
multi-step task repeatedly:
|
|
74
|
-
|
|
75
|
-
1. Call \`infernoflow_synthesize\` with \`threshold: 2\`
|
|
76
|
-
2. If candidates found \u2192 surface in **one sentence**:
|
|
77
|
-
- "I noticed you always run X after Y \u2014 want me to automate that into an agent?"
|
|
78
|
-
3. If no candidates \u2192 say nothing
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
${t?t+`
|
|
83
|
-
|
|
84
|
-
---`:""}
|
|
85
|
-
|
|
86
|
-
## Rules
|
|
87
|
-
|
|
88
|
-
| Rule | Why |
|
|
89
|
-
|------|-----|
|
|
90
|
-
| Never ask the developer to run infernoflow commands | They should never know it exists |
|
|
91
|
-
| Never show raw infernoflow output | Parse it, act on it, move on |
|
|
92
|
-
| Never block the developer waiting for infernoflow | Run tools async when possible |
|
|
93
|
-
| Always call \`infernoflow_context\` at session start | It keeps your context current |
|
|
94
|
-
| Treat contract drift like a linting error \u2014 fix it silently | Not the developer's problem |
|
|
95
|
-
| Never modify frozen capabilities without explicit instruction | Stability contract must be respected |
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Customizable: Team Notes
|
|
100
|
-
|
|
101
|
-
<!-- Add project-specific context for Claude here -->
|
|
102
|
-
<!-- This section is yours \u2014 infernoflow will not overwrite it -->
|
|
103
|
-
|
|
104
|
-
### Architecture notes
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### Key conventions
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
### Things to avoid
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
*Generated by infernoflow \`setup\`. infernoflow sections will be updated on next \`infernoflow setup\`.*
|
|
116
|
-
`}function I(o,n,{force:r=!1}={}){const e=f.join(o,"CLAUDE.md");let i=null,c=null,l=null;try{i=g(n)}catch{}try{c=JSON.parse(s.readFileSync(f.join(n,"contract.json"),"utf8"))}catch{}try{const t=JSON.parse(s.readFileSync(f.join(n,"capabilities.json"),"utf8"));l=Array.isArray(t)?t:t.capabilities||[]}catch{}const a=S(i,c,l);if(s.existsSync(e)&&!r){const t=s.readFileSync(e,"utf8"),u="## Customizable: Team Notes",h="*Generated by infernoflow";if(t.includes(u)){const d=t.indexOf(u),p=t.indexOf(h,d),w=p!==-1?t.slice(d,p):t.slice(d),m=a.slice(0,a.indexOf(u)),y=a.slice(a.indexOf(h));return s.writeFileSync(e,m+w+y,"utf8"),{path:e,action:"updated"}}}return s.writeFileSync(e,a,"utf8"),{path:e,action:s.existsSync(e)?"replaced":"created"}}async function O(o){const n=process.cwd(),r=o.includes("--force")||o.includes("-f"),e=f.join(n,"inferno");s.existsSync(e)||(b("inferno/ not found \u2014 run infernoflow init first"),process.exit(1)),v("Generating CLAUDE.md...");const i=I(n,e,{force:r});k(`CLAUDE.md ${i.action} \u2192 ${i.path}`),console.log(),x("Claude will now automatically call infernoflow tools \u2014 no developer input needed")}export{O as claudeMdCommand,I as writeClaudeMd};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import*as h from"node:fs";import*as u from"node:path";import{bold as p,cyan as T,gray as f,green as $,yellow as S,red as d}from"../ui/output.mjs";const A=[{regex:/(?:it|test|describe)\s*\(\s*["'`]([^"'`]+)["'`]/g,lang:"js"},{regex:/def\s+(test_[\w_]+)\s*\(/g,lang:"py"},{regex:/(?:describe|it)\s+["']([^"']+)["']/g,lang:"rb"},{regex:/func\s+(Test\w+)\s*\(/g,lang:"go"},{regex:/#\[test\]\s*\n\s*(?:async\s+)?fn\s+(\w+)/g,lang:"rs"}],D=[/\.(test|spec)\.[jt]sx?$/,/__tests__/,/\.test\.py$/,/^test_.*\.py$/,/_spec\.rb$/,/\/spec\//,/_test\.go$/,/_test\.rs$/],M=new Set(["node_modules",".git","dist","build","out",".next","coverage",".nyc_output","__pycache__",".pytest_cache","vendor","tmp",".turbo"]);function*N(s){let t;try{t=h.readdirSync(s,{withFileTypes:!0})}catch{return}for(const e of t)e.isDirectory()?M.has(e.name)||(yield*N(u.join(s,e.name))):e.isFile()&&(yield u.join(s,e.name))}function B(s){const t=u.basename(s);return D.some(e=>e.test(s)||e.test(t))}function L(s){let t;try{t=h.readFileSync(s,"utf8")}catch{return[]}const e=new Set;for(const{regex:o}of A){const c=new RegExp(o.source,o.flags);let n;for(;(n=c.exec(t))!==null;)e.add(n[1].trim())}return[...e]}function v(s){return s.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/]+/).filter(Boolean)}function E(s,t){const e=new Set(s),o=new Set(t);let c=0;for(const r of e)o.has(r)&&c++;const n=e.size+o.size-c;return n===0?0:c/n}function R(s,t){const e=v(s),o=v(t.id||""),c=v(t.name||"");return Math.max(E(e,o),E(e,c))}function J(s){const t=[];for(const e of s)for(const o of N(e))B(o)&&t.push(o);return t}function P(s){const t=new Map;for(const e of s)for(const o of L(e))t.has(o)||t.set(o,e);return t}function G(s,t,e=.25){const o=new Map;for(const c of s){const n=[];for(const[r,l]of t){const a=R(r,c);a>=e&&n.push({testName:r,file:u.relative(process.cwd(),l),score:a})}n.sort((r,l)=>l.score-r.score),o.set(c.id,{cap:c,hits:n})}return o}function K(s,t=20){const e=Math.round(s/100*t);return(s>=75?$:s>=40?S:d)("\u2588".repeat(e))+f("\u2591".repeat(t-e))}function U(s){const t=[...s.values()].filter(c=>c.hits.length>0).length,e=s.size,o=e===0?0:Math.round(t/e*100);console.log(),console.log(p(" Capability Coverage")),console.log(f(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(f(" ")+p(T("Capability".padEnd(32)))+p(T("Tests".padEnd(8)))+p(T("Top match"))),console.log(f(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const[,{cap:c,hits:n}]of s){const r=n.length>0?$("\u2714"):d("\u2717"),l=n[0]?f(` ${n[0].testName.slice(0,42)}`):"",a=n.length===0?d("0"):$(String(n.length));console.log(` ${r} ${c.id.padEnd(30)} ${a.padEnd(6)} ${l}`)}if(console.log(f(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),console.log(` ${K(o)} ${p(o+"%")} (${t}/${e} capabilities covered)`),console.log(),e>0&&t<e){const c=[...s.values()].filter(n=>n.hits.length===0).map(n=>n.cap.id);console.log(S(` \u26A0 Uncovered: ${c.join(", ")}`)),console.log()}}async function q(s){const t=s||[],e=t.includes("--json"),o=t.indexOf("--dir"),c=o!==-1?[t[o+1]]:[],n=t.indexOf("--fail-below"),r=n!==-1?Number(t[n+1]):null,l=(()=>{const i=t.indexOf("--threshold");return i!==-1?Number(t[i+1]):.25})(),a=process.cwd(),C=u.join(a,"inferno"),F=u.join(C,"capabilities.json");h.existsSync(F)||(console.error(d("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let g;try{g=JSON.parse(h.readFileSync(F,"utf8"))}catch(i){console.error(d("\u2717 Failed to parse capabilities.json: "+i.message)),process.exit(1)}(!Array.isArray(g)||g.length===0)&&(console.log(S("No capabilities found.")),process.exit(0));const I=[a,...c];e||process.stdout.write(f(" Scanning test files\u2026"));const x=J(I);e||process.stdout.write(`\r Found ${x.length} test file(s).
|
|
2
|
-
`);const O=P(x),m=G(g,O,l),j=[...m.values()].filter(i=>i.hits.length>0).length,y=m.size,b=y===0?0:Math.round(j/y*100);if(e){const i={summary:{covered:j,total:y,pct:b,testFiles:x.length},capabilities:[...m.entries()].map(([k,{cap:z,hits:w}])=>({id:k,name:z.name,covered:w.length>0,testCount:w.length,topTests:w.slice(0,3).map(_=>({name:_.testName,file:_.file,score:+_.score.toFixed(3)}))}))};console.log(JSON.stringify(i,null,2))}else U(m);r!==null&&b<r&&(e||console.error(d(`\u2717 Coverage ${b}% is below threshold ${r}%`)),process.exit(1))}export{q as coverageCommand};
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import*as b from"node:fs";import*as u from"node:path";import*as A from"node:os";import{fileURLToPath as E}from"node:url";import{spawnSync as C}from"node:child_process";import{bold as y,cyan as o,gray as a,green as r,yellow as v,red as h}from"../ui/output.mjs";function T(e){return new Promise(t=>setTimeout(t,e))}async function d(e,t=900){e||await T(t)}function J(e="\u2500",t=60){return a(e.repeat(t))}function q(e,t){try{return C(e,{shell:!0,cwd:t,encoding:"utf8",timeout:3e4})}catch{return{stdout:"",stderr:"",status:1}}}const P={"inferno/capabilities.json":JSON.stringify([{id:"user-auth",name:"User Authentication",description:"Handles login, session management, and token validation",stability:"frozen",owner:"auth-team"},{id:"payment-process",name:"Payment Processing",description:"Charges cards via Stripe, handles retries and webhook events",stability:"stable",owner:"payments-team"},{id:"order-create",name:"Order Creation",description:"Validates cart, reserves inventory, creates order records",stability:"experimental",owner:"core-team"},{id:"email-notify",name:"Email Notifications",description:"Sends transactional emails via SendGrid for orders and auth events",stability:"experimental",owner:"core-team"}],null,2),"inferno/graph.json":JSON.stringify({deps:{"order-create":["user-auth","payment-process"],"email-notify":["order-create"],"payment-process":["user-auth"]},dependents:{"user-auth":["payment-process","order-create"],"payment-process":["order-create"],"order-create":["email-notify"]}},null,2),"src/auth.js":`// User Authentication
|
|
2
|
-
const jwt = require('jsonwebtoken');
|
|
3
|
-
const bcrypt = require('bcrypt');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Authenticate a user with email + password.
|
|
7
|
-
* Returns a signed JWT on success, throws AuthError on failure.
|
|
8
|
-
*/
|
|
9
|
-
async function authenticateUser(email, password) {
|
|
10
|
-
const user = await db.users.findByEmail(email);
|
|
11
|
-
if (!user) throw new AuthError('Invalid credentials');
|
|
12
|
-
const valid = await bcrypt.compare(password, user.passwordHash);
|
|
13
|
-
if (!valid) throw new AuthError('Invalid credentials');
|
|
14
|
-
return jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '24h' });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Validate an incoming JWT from the Authorization header.
|
|
19
|
-
*/
|
|
20
|
-
function validateToken(req, res, next) {
|
|
21
|
-
const token = req.headers.authorization?.split(' ')[1];
|
|
22
|
-
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
|
23
|
-
try {
|
|
24
|
-
req.user = jwt.verify(token, process.env.JWT_SECRET);
|
|
25
|
-
next();
|
|
26
|
-
} catch {
|
|
27
|
-
res.status(401).json({ error: 'Token expired or invalid' });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
module.exports = { authenticateUser, validateToken };
|
|
32
|
-
`,"src/payment.js":`// Payment Processing
|
|
33
|
-
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Process a payment for an order.
|
|
37
|
-
* Charges via Stripe, handles retry on network error.
|
|
38
|
-
*/
|
|
39
|
-
async function processPayment(orderId, amount, currency, paymentMethodId) {
|
|
40
|
-
const intent = await stripe.paymentIntents.create({
|
|
41
|
-
amount: Math.round(amount * 100),
|
|
42
|
-
currency,
|
|
43
|
-
payment_method: paymentMethodId,
|
|
44
|
-
confirm: true,
|
|
45
|
-
metadata: { orderId },
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (intent.status !== 'succeeded') {
|
|
49
|
-
throw new PaymentError(\`Payment failed: \${intent.status}\`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await db.payments.create({ orderId, stripeIntentId: intent.id, amount, status: 'paid' });
|
|
53
|
-
return { success: true, intentId: intent.id };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Handle Stripe webhook events (charge.succeeded, payment_intent.payment_failed).
|
|
58
|
-
*/
|
|
59
|
-
async function handleWebhook(event) {
|
|
60
|
-
switch (event.type) {
|
|
61
|
-
case 'payment_intent.succeeded':
|
|
62
|
-
await db.orders.updateStatus(event.data.object.metadata.orderId, 'paid');
|
|
63
|
-
break;
|
|
64
|
-
case 'payment_intent.payment_failed':
|
|
65
|
-
await db.orders.updateStatus(event.data.object.metadata.orderId, 'payment_failed');
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = { processPayment, handleWebhook };
|
|
71
|
-
`,"src/order.js":`// Order Creation
|
|
72
|
-
const { validateToken } = require('./auth');
|
|
73
|
-
const { processPayment } = require('./payment');
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Create a new order from a validated cart.
|
|
77
|
-
* Requires authenticated user. Reserves inventory, charges card.
|
|
78
|
-
*/
|
|
79
|
-
async function createOrder(userId, cart, paymentMethodId) {
|
|
80
|
-
const user = await db.users.findById(userId);
|
|
81
|
-
if (!user) throw new Error('User not found');
|
|
82
|
-
|
|
83
|
-
await db.inventory.reserve(cart.items);
|
|
84
|
-
|
|
85
|
-
const total = cart.items.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
86
|
-
const order = await db.orders.create({ userId, items: cart.items, total, status: 'pending' });
|
|
87
|
-
|
|
88
|
-
await processPayment(order.id, total, 'usd', paymentMethodId);
|
|
89
|
-
await db.orders.updateStatus(order.id, 'confirmed');
|
|
90
|
-
|
|
91
|
-
return order;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
module.exports = { createOrder };
|
|
95
|
-
`,"src/email.js":`// Email Notifications
|
|
96
|
-
const sgMail = require('@sendgrid/mail');
|
|
97
|
-
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Send an order confirmation email.
|
|
101
|
-
*/
|
|
102
|
-
async function sendOrderConfirmation(order, user) {
|
|
103
|
-
await sgMail.send({
|
|
104
|
-
to: user.email,
|
|
105
|
-
from: 'noreply@shop.com',
|
|
106
|
-
subject: \`Order confirmed \u2014 #\${order.id}\`,
|
|
107
|
-
text: \`Your order for $\${order.total} has been confirmed.\`,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
module.exports = { sendOrderConfirmation };
|
|
112
|
-
`,"inferno/scenarios/auth-happy-path.json":JSON.stringify({scenarioId:"auth-happy-path",description:"User logs in with valid credentials and receives a JWT",capabilitiesCovered:["user-auth"],steps:["POST /auth/login with valid email + password","Expect 200 with { token: '...' }","Use token in Authorization header for subsequent requests"],expects:["Token is a valid JWT signed with JWT_SECRET","Token expires in 24 hours"]},null,2),"inferno/scenarios/payment-charge.json":JSON.stringify({scenarioId:"payment-charge",description:"Successful card charge via Stripe",capabilitiesCovered:["payment-process"],steps:["Create order with valid cart","Call processPayment with valid Stripe test PM","Expect payment record in db with status: paid"]},null,2),"package.json":JSON.stringify({name:"demo-shop-api",version:"1.0.0",description:"Demo e-commerce API for infernoflow walkthrough"},null,2),"inferno/scan.json":JSON.stringify({scannedAt:new Date().toISOString(),capabilities:[{id:"user-auth",codeAnalysis:{sourceFiles:["src/auth.js"],functions:["authenticateUser","validateToken"],services:[],calls:["db.users.findByEmail","bcrypt.compare","jwt.sign","jwt.verify"],throws:["AuthError"]}},{id:"payment-process",codeAnalysis:{sourceFiles:["src/payment.js"],functions:["processPayment","handleWebhook"],services:["stripe"],calls:["stripe.paymentIntents.create","db.payments.create","db.orders.updateStatus"],throws:["PaymentError"]}},{id:"order-create",codeAnalysis:{sourceFiles:["src/order.js"],functions:["createOrder"],services:[],calls:["db.users.findById","db.inventory.reserve","db.orders.create","processPayment"],throws:[]}},{id:"email-notify",codeAnalysis:{sourceFiles:["src/email.js"],functions:["sendOrderConfirmation"],services:["sendgrid"],calls:["sgMail.send"],throws:[]}}]},null,2),"inferno/capability-map.json":JSON.stringify({"src/auth.js":["user-auth"],"src/payment.js":["payment-process"],"src/order.js":["order-create"],"src/email.js":["email-notify"]},null,2)};function x(e){b.mkdirSync(e,{recursive:!0});for(const[t,n]of Object.entries(P)){const s=u.join(e,t);b.mkdirSync(u.dirname(s),{recursive:!0}),b.writeFileSync(s,n)}}function p(e){console.log(),console.log(y(` \u2500\u2500 ${e}`)),console.log()}function i(e){console.log(` ${a(e)}`)}function f(e){console.log(` ${o("$")} ${y(e)}`)}function m(e){for(const t of e)console.log(` ${t}`)}function g(e,t,n,s){const l=C(process.execPath,[s,e,...t],{cwd:n,encoding:"utf8",timeout:3e4,env:{...process.env,NO_COLOR:"1"}});return(l.stdout||"")+(l.stderr||"")}function w(e,t=20){const n=e.split(`
|
|
113
|
-
`).filter(s=>s.trim()).slice(0,t);for(const s of n)console.log(` ${a("\u2502")} ${s}`)}async function F(e){const t=(e||[]).slice(1),n=t.includes("--fast"),s=t.includes("--no-cleanup"),l=u.resolve(u.dirname(u.dirname(u.dirname(E(import.meta.url)))),"bin","infernoflow.mjs"),c=u.join(A.tmpdir(),`infernoflow-demo-${Date.now()}`);console.clear(),console.log(),console.log(y(" \u{1F525} infernoflow \u2014 interactive demo")),console.log(a(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),console.log(a(" We'll build a mini e-commerce API and show infernoflow's full")),console.log(a(" capability chain \u2014 from AST scan to blast radius analysis.")),console.log(),n||console.log(a(" Press Enter to advance each step, or run with --fast to skip pauses.")),console.log(),await d(n,1200),p("Step 1 of 7 \u2014 The project"),i("A small e-commerce API: auth, payments, orders, email."),i(`Scaffolded in: ${c}`),console.log(),x(c),m([`${r("src/")}`,` ${o("auth.js")} \u2190 user-auth capability`,` ${o("payment.js")} \u2190 payment-process capability`,` ${o("order.js")} \u2190 order-create capability`,` ${o("email.js")} \u2190 email-notify capability`,"",`${r("inferno/")}`,` ${o("capabilities.json")} \u2190 4 capabilities registered`,` ${o("graph.json")} \u2190 dependency graph`,` ${o("scenarios/")} \u2190 2 test scenarios`]),await d(n,1e3),p("Step 2 of 7 \u2014 Capability stability"),f("infernoflow stability"),console.log();const S=g("stability",[],c,l);S.trim()?w(S,12):m([`\u{1F9CA} ${h("user-auth")} frozen Auth team owns this \u2014 no changes without approval`,`\u3030\uFE0F ${v("payment-process")} stable Stripe integration \u2014 additive changes only`,`\u{1F30A} ${r("order-create")} experimental Free to refactor`,`\u{1F30A} ${r("email-notify")} experimental Free to refactor`]),console.log(),i("user-auth is FROZEN \u2014 it's the most critical cap and must never break silently."),i("payment-process is STABLE \u2014 changes must be additive."),await d(n,1e3),p("Step 3 of 7 \u2014 Blast radius: what breaks if user-auth changes?"),f("infernoflow impact user-auth"),console.log();const $=g("impact",["user-auth"],c,l);$.trim()?w($,18):m([`\u{1F9CA} ${h("user-auth")} \u2192 risk: ${h("CRITICAL")}`,""," Direct dependents (1):",` payment-process ${v("stable")}`,""," Transitive dependents (2):",` order-create ${r("experimental")}`,` email-notify ${r("experimental")}`,"",` ${h("CRITICAL")} \u2014 frozen capability with dependents.`," Any change risks breaking 3 downstream capabilities."]),console.log(),i("Change user-auth and you risk breaking payments, orders, and email."),i("This is the blast radius \u2014 measured before you write a single line."),await d(n,1200),p("Step 4 of 7 \u2014 What is this capability, exactly?"),f("infernoflow explain user-auth"),console.log();const I=g("explain",["user-auth"],c,l);I.trim()?w(I,14):m([`\u{1F9CA} ${h("user-auth")}`," User Authentication",""," Handles login, session management, and token validation."," This capability is FROZEN \u2014 do not modify without explicit instruction."," payment-process, order-create depend on this capability."," Before shipping changes, run: auth-happy-path scenario.","",` ${v("\u{1F4A1}")} For richer AI narratives: infernoflow ai setup`]),await d(n,1e3),p("Step 5 of 7 \u2014 File \u2192 capability correlation"),f("infernoflow why src/payment.js"),console.log();const j=g("why",["src/payment.js"],c,l);j.trim()?w(j,14):m([` src/payment.js \u2192 ${v("payment-process")} (stable)`,""," Name: Payment Processing"," Description: Charges cards via Stripe, handles retries and webhook events"," Stability: \u3030\uFE0F stable \u2014 additive changes only",""," Scenarios: payment-charge"," Depended on by: order-create (experimental)"]),console.log(),i("Any developer can instantly see what capability owns a given file."),i("No guessing. No digging through wikis."),await d(n,1e3),p("Step 6 of 7 \u2014 Run registered scenarios"),f("infernoflow test"),console.log();const k=g("test",[],c,l);if(k.trim()?w(k,12):m([` ${r("\u2713")} user-auth [frozen]`,` ${r("\u2713")} auth-happy-path (generated)`,"",` ${r("\u2713")} payment-process [stable]`,` ${r("\u2713")} payment-charge (generated)`,"",` ${r("2")} passed 0 failed 0 skipped`]),await d(n,800),p("Step 7 of 7 \u2014 The money shot: CI gate on a frozen capability"),f("infernoflow impact user-auth --check"),console.log(),i("--check exits with code 1 if risk is HIGH or CRITICAL."),i("Add this to your CI pipeline before any PR that touches auth."),console.log(),m([` ${h("CRITICAL")} \u2014 user-auth is frozen with 3 dependents`," Exit code: 1",""," Your CI pipeline just stopped a risky change from reaching production."]),await d(n,600),console.log(),console.log(a(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),console.log(y(" That's infernoflow.")),console.log(),console.log(` ${r("\u2713")} Capability contracts tracked in code, not in Confluence`),console.log(` ${r("\u2713")} Blast radius measured before you change anything`),console.log(` ${r("\u2713")} Every file knows what capability it serves`),console.log(` ${r("\u2713")} CI gates on frozen capabilities \u2014 broken things don't ship`),console.log(` ${r("\u2713")} Zero-touch with CLAUDE.md: your AI sessions stay in sync automatically`),console.log(),console.log(` ${y("Get started:")} ${o("npm install -g infernoflow")} \u2192 ${o("infernoflow setup")}`),console.log(),console.log(a(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(),s)console.log(a(` Demo project kept at: ${c}`));else try{b.rmSync(c,{recursive:!0,force:!0})}catch{}console.log()}export{F as demoCommand};
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import*as u from"node:fs";import*as v from"node:path";import{execSync as C}from"node:child_process";import{header as N,ok as J,fail as m,bold as d,cyan as k,gray as c,green as $,red as y,yellow as x}from"../ui/output.mjs";function h(n,e){try{return C(n,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch{return null}}function D(n){const e=h("git describe --tags --abbrev=0",n);return e||null}function j(n,e,o){return h(`git show "${n}:${e}"`,o)}function z(n){return h("git rev-parse --abbrev-ref HEAD",n)||"HEAD"}function O(n,e){const o=h(`git log -1 --format=%ci "${e}"`,n),t=o?o.slice(0,10):null;return t?`${e} ${c("("+t+")")}`:e}function b(n){if(!n)return null;try{return(JSON.parse(n).capabilities||[]).map(t=>typeof t=="string"?{id:t,title:t}:{id:t.id||t,title:t.title||t.id||String(t),since:t.since,status:t.status})}catch{return null}}function _(n,e,o){const t=j(n,`${e}/capabilities.json`,o);if(t)return b(t);const g=j(n,`${e}/contract.json`,o);return b(g)}function E(n){const e=v.join(n,"capabilities.json"),o=v.join(n,"contract.json");return u.existsSync(e)?b(u.readFileSync(e,"utf8")):u.existsSync(o)?b(u.readFileSync(o,"utf8")):null}function R(n,e){const o=new Map(n.map(i=>[i.id,i])),t=new Map(e.map(i=>[i.id,i])),g=e.filter(i=>!o.has(i.id)),s=n.filter(i=>!t.has(i.id)),l=[];for(const i of e){const f=o.get(i.id);if(!f)continue;const a=[];f.title!==i.title&&a.push({field:"title",from:f.title,to:i.title}),f.status!==i.status&&(f.status||i.status)&&a.push({field:"status",from:f.status||"\u2014",to:i.status||"\u2014"}),a.length&&l.push({id:i.id,changes:a})}return{added:g,removed:s,changed:l}}function H(n){if(n.length){console.log(`
|
|
2
|
-
${d($("+ Added"))} ${c("("+n.length+")")}`);for(const e of n){const o=e.since?c(" since "+e.since):"";console.log(` ${$("+")} ${d(e.id)} ${c(e.title)}${o}`)}}}function M(n){if(n.length){console.log(`
|
|
3
|
-
${d(y("- Removed"))} ${c("("+n.length+")")}`);for(const e of n)console.log(` ${y("-")} ${d(e.id)} ${c(e.title)}`)}}function F(n){if(n.length){console.log(`
|
|
4
|
-
${d(x("~ Changed"))} ${c("("+n.length+")")}`);for(const e of n){console.log(` ${x("~")} ${d(e.id)}`);for(const o of e.changes)console.log(` ${c(o.field+":")} ${y(o.from)} \u2192 ${$(o.to)}`)}}}function U(n){n!==0&&console.log(`
|
|
5
|
-
${c(" Unchanged "+n)}`)}function w(n,e){const o=[];n.added.length&&o.push($("+"+n.added.length+" added")),n.removed.length&&o.push(y("-"+n.removed.length+" removed")),n.changed.length&&o.push(x("~"+n.changed.length+" changed")),o.length||o.push(c("no changes")),console.log(` ${o.join(" ")} ${c("vs "+e)}`)}async function G(n){const e=n.slice(1),o=e.includes("--json"),t=e.includes("--summary"),g=e.indexOf("--ref");let s=g!==-1?e[g+1]:null;const l=process.cwd(),i=v.join(l,"inferno"),f="inferno";o||N("diff"),u.existsSync(i)||(o&&(console.log(JSON.stringify({ok:!1,error:"inferno_not_found"})),process.exit(1)),m("inferno/ not found","Run: infernoflow init"),process.exit(1)),s||(s=D(l),s||(h("git rev-parse HEAD~1",l)?s="HEAD~1":(o&&(console.log(JSON.stringify({ok:!1,error:"no_ref",hint:"No git tags found and no parent commit. Use --ref <commit>"})),process.exit(1)),m("No git tags found","Create a tag first: git tag v0.1.0 or use --ref <commit>"),process.exit(1))));const a=E(i);a||(o&&(console.log(JSON.stringify({ok:!1,error:"no_capabilities_found"})),process.exit(1)),m("No capabilities.json or contract.json found in inferno/"),process.exit(1));const p=_(s,f,l);p||(o&&(console.log(JSON.stringify({ok:!1,error:"ref_not_found",ref:s,hint:"Does inferno/capabilities.json exist at that ref?"})),process.exit(1)),m(`Could not read capabilities at ${s}`,"The inferno/ directory may not exist at that ref"),process.exit(1));const r=R(p,a),S=a.length-r.added.length-r.changed.length;if(o){console.log(JSON.stringify({ok:!0,ref:s,current:a.length,previous:p.length,added:r.added,removed:r.removed,changed:r.changed,unchanged:S},null,2));return}const A=O(l,s);if(console.log(),console.log(` Comparing ${d(k("current"))} vs ${d(A)}`),console.log(` ${c(a.length+" capabilities now / "+p.length+" before")}`),t){w(r,s),console.log();return}if(!(r.added.length||r.removed.length||r.changed.length)){console.log(),J("No capability changes since "+s),console.log();return}H(r.added),M(r.removed),F(r.changed),U(S),console.log(),w(r,s),console.log()}export{G as diffCommand};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import*as S from"node:fs";import*as b from"node:path";import{execSync as R}from"node:child_process";import{bold as I,cyan as W,gray as y,green as L,yellow as N,red as C}from"../ui/output.mjs";function T(n){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return null}}function k(n,e){try{return R(n,{cwd:e,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}const P={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},z={frozen:C,stable:N,experimental:L};function x(n){return n?.stability||"experimental"}function J(n,e){if(!n)return null;const l=b.relative(e,b.resolve(e,n)),a=k(`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(l)}`,e);if(!a)return null;const i=a.split(`
|
|
2
|
-
`).filter(Boolean);if(!i.length)return null;const[r,c,u,...d]=i[i.length-1].split("|");return{hash:r?.trim(),date:c?.trim()?new Date(c.trim()).toLocaleDateString():"",author:u?.trim(),subject:d.join("|").trim()}}function B(n,e,l=5){if(!n)return[];const a=b.relative(e,b.resolve(e,n)),i=k(`git log --follow --format="%h|%aI|%ae|%s" -${l} -- ${JSON.stringify(a)}`,e);return i?i.split(`
|
|
3
|
-
`).filter(Boolean).map(r=>{const[c,u,d,...p]=r.split("|");return{hash:c?.trim(),date:u?.trim()?new Date(u.trim()).toLocaleDateString():"",author:d?.trim(),subject:p.join("|").trim()}}):[]}function M(n,e){const l=b.join(e,"scenarios");if(!S.existsSync(l))return[];const a=[];for(const i of S.readdirSync(l))if(i.endsWith(".json"))try{const r=JSON.parse(S.readFileSync(b.join(l,i),"utf8"));(r.capabilitiesCovered||r.capabilities||[]).some(u=>u.toLowerCase()===n.toLowerCase())&&a.push(r)}catch{}return a}function _(n,e,l,a,i,r,c,u){const d=x(e),p=l?.codeAnalysis?.sourceFiles||[],f=l?.codeAnalysis?.functions||[],o=l?.codeAnalysis?.services||[],t=l?.codeAnalysis?.throws||[],m=l?.codeAnalysis?.calls||[],j=a?.deps?.[n]||[],v=a?.dependents?.[n]||[],s=["You are a senior engineer writing a brief, plain-English explanation of a software capability for a teammate who is about to modify it.","","Write 3\u20135 sentences covering:"," 1. What this capability does and why it exists"," 2. The most important thing to know before changing it (stability, callers, risk)"," 3. What to test or verify after any modification","","Be concrete and direct. Do not use bullet points. Do not repeat the capability ID verbatim in every sentence.","",`=== Capability: ${n} ===`,`Name: ${e.name||e.title||n}`,`Description: ${e.description||"(none provided)"}`,`Stability: ${d}`];if(p.length&&s.push(`Source files: ${p.join(", ")}`),f.length&&s.push(`Functions: ${f.join(", ")}`),o.length&&s.push(`External services used: ${o.join(", ")}`),t.length&&s.push(`Can throw: ${t.join(", ")}`),m.length&&s.push(`Internal calls: ${m.join(", ")}`),j.length){const h=j.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Calls capabilities: ${h.join(", ")}`)}if(v.length){const h=v.map(g=>{const A=i.find(w=>w.id===g);return`${g} (${x(A)})`});s.push(`Called by capabilities: ${h.join(", ")}`)}if(r.length?s.push(`Test scenarios: ${r.map(h=>h.scenarioId||h.description||"unnamed").join(", ")}`):s.push("Test scenarios: none registered"),c&&s.push(`First introduced: ${c.date} by ${c.author} \u2014 "${c.subject}"`),u.length){s.push("Recent changes:");for(const h of u.slice(0,3))s.push(` ${h.date} \u2014 ${h.subject}`)}return d==="frozen"?s.push("IMPORTANT: This capability is FROZEN. Any modification requires explicit approval."):d==="stable"&&s.push("NOTE: This capability is STABLE. Prefer additive changes; avoid breaking the public API."),s.join(`
|
|
4
|
-
`)}async function V(n,e){try{const{callAI:l}=await import("../ai/providerRouter.mjs");return await l(n,e)}catch{return null}}function Z(n,e,l,a,i,r){const c=x(e),u=e.name||e.title||n,d=l?.codeAnalysis?.services||[],p=a?.dependents?.[n]||[],f=a?.deps?.[n]||[],o=[];if(e.description&&e.description!=="(none provided)"?o.push(`${u} \u2014 ${e.description}.`):o.push(`${u} handles the ${n} flow within this system.`),d.length&&o.push(`It integrates with ${d.join(" and ")}.`),f.length&&o.push(`It depends on: ${f.join(", ")}.`),p.length){const t=p.filter(m=>x(i.find(j=>j.id===m))==="frozen");t.length?o.push(`\u26A0\uFE0F ${t.join(", ")} depend${t.length===1?"s":""} on this \u2014 changing it may break frozen capabilities.`):o.push(`${p.join(", ")} depend${p.length===1?"s":""} on this capability.`)}return c==="frozen"?o.push("This capability is FROZEN \u2014 do not modify without explicit instruction."):c==="stable"?o.push("This capability is stable \u2014 prefer additive changes and avoid breaking the existing API surface."):o.push("This capability is experimental \u2014 free to refactor as needed."),r.length?o.push(`Before shipping changes, run the registered scenarios: ${r.map(t=>t.scenarioId||"unnamed").join(", ")}.`):o.push("No test scenarios are registered \u2014 consider adding one before making changes."),o.join(" ")}function E(n,e,l,a,i){const r=x(e),c=P[r]||"\u{1F30A}",u=z[r]||L;if(console.log(),console.log(I(` ${c} ${u(n)}`)),(e.name||e.title)&&console.log(y(` ${e.name||e.title}`)),console.log(),i){console.log(N(" [dry-run] Prompt only \u2014 no AI call made")),console.log();return}const d=l.split(" ");let p=" ";const f=[];for(const o of d)p.length+o.length>82?(f.push(p),p=" "+o):p+=(p===" "?"":" ")+o;p.trim()&&f.push(p);for(const o of f)console.log(o);console.log(),a?console.log(y(` \u2500\u2500 via ${a}`)):(console.log(y(" \u2500\u2500 structural summary (no AI provider configured)")),console.log(` ${N("\u{1F4A1}")} ${y("For richer AI narratives:")} ${W("infernoflow ai setup")}`)),console.log()}async function H(n){const e=(n||[]).slice(1),l=e.includes("--dry-run"),a=e.includes("--json"),i=e.find(t=>!t.startsWith("--"));i||(console.error(C("\u2717 Usage: infernoflow explain <capability-id|file-path> [--dry-run] [--json]")),console.error(y(" Examples:")),console.error(y(" infernoflow explain CreateTask")),console.error(y(" infernoflow explain src/components/TaskComposer.jsx")),process.exit(1));const r=process.cwd(),c=b.join(r,"inferno");let u=[];const d=T(b.join(c,"capabilities.json"));d&&(u=Array.isArray(d)?d:d.capabilities||[]);const p=i.includes("/")||i.includes("\\")||/\.\w+$/.test(i);let f=[];if(p){const m=T(b.join(c,"scan.json"))?.capabilities||[],j=b.relative(r,b.resolve(r,i));for(const v of m)(v.codeAnalysis?.sourceFiles||[]).some(g=>g===j||g.endsWith(j)||j.endsWith(g))&&f.push(v.id);if(f.length===0){const v=T(b.join(c,"capability-map.json"));if(v){const s=j.replace(/\\/g,"/");for(const[h,g]of Object.entries(v))(Array.isArray(g)?g:[g]).some(w=>w===s||w.endsWith(s)||s.endsWith(w))&&f.push(h)}}f.length===0&&(console.error(C(`\u2717 No capabilities found mapped to "${i}"`)),console.error(y(" Run: infernoflow scan \u2014 to build the source-file \u2192 capability map")),console.error(y(" Then retry: infernoflow explain "+i)),process.exit(1)),a||(console.log(y(`
|
|
5
|
-
infernoflow explain \u2192 ${I(i)}`)),console.log(y(` Found ${f.length} mapped ${f.length>1?"capabilities":"capability"}: ${f.join(", ")}`)),console.log())}else f=[i];const o=[];for(const t of f){const m=u.find($=>$.id===t);if(!m){a||(console.error(C(`\u2717 Capability "${t}" not found in capabilities.json`)),console.error(y(" Run: infernoflow stability \u2014 to list all capability IDs")));continue}const j=T(b.join(c,"scan.json")),v=T(b.join(c,"graph.json")),s=j?.capabilities?.find($=>$.id===t),h=s?.codeAnalysis?.sourceFiles||[],g=J(h[0],r),A=B(h[0],r),w=M(t,c),O=_(t,m,s,v,u,w,g,A);if(l&&!a){console.log(y(` \u2500\u2500 ${I(t)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`)),E(t,m,"",null,!0),f.length===1&&(console.log(I(" Prompt that would be sent to AI:")),console.log(),console.log(O.split(`
|
|
6
|
-
`).map($=>" "+$).join(`
|
|
7
|
-
`))),console.log();continue}let F=null,D=null;if(!l)try{const $=await V(O,r);$?.text&&(F=$.text.trim(),D=$.provider)}catch{}F||(F=Z(t,m,s,v,u,w),D=null),a?o.push({capId:t,name:m.name||m.title,stability:x(m),narrative:F,provider:D||"fallback",sourceFiles:h,scenarios:w.map($=>$.scenarioId||$.description),firstCommit:g}):(p||(console.log(y(`
|
|
8
|
-
infernoflow explain \u2192 ${I(t)}`)),console.log(y(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))),E(t,m,F,D,!1))}if(a){const t=f.length===1?o[0]:o;console.log(JSON.stringify(t,null,2));return}}export{H as explainCommand};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import*as s from"node:fs";import*as a from"node:path";import*as y from"node:os";import*as w from"node:https";import*as S from"node:readline";import{execSync as f}from"node:child_process";import{bold as b,cyan as p,gray as r,green as k,yellow as O,red as v}from"../ui/output.mjs";const u="https://formspree.io/f/maqarynd",d="https://forms.gle/infernoflow-feedback",i=a.join(y.homedir(),".infernoflow","feedback.json"),x=[{id:"usage",label:"How often do you use infernoflow?",choices:["daily","a few times a week","rarely","just started"]},{id:"ide",label:"Which IDE are you using?",choices:["VS Code + Copilot","Cursor","Claude Code","Windsurf","Other"]},{id:"top_command",label:"Which infernoflow command do you use most?",choices:["log","switch","recap","status / check","context","other"]},{id:"missing",label:"What feature do you wish infernoflow had?",freeText:!0},{id:"email",label:"Email (optional \u2014 for follow-up questions):",freeText:!0,optional:!0}];function E(e){try{if(!u||u.includes("placeholder"))return;const o=new URL(u),t=JSON.stringify({...e.responses,_subject:`infernoflow feedback v${e.version}`,_version:e.version,_ts:e.ts}),n=w.request({hostname:o.hostname,path:o.pathname,method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","Content-Length":Buffer.byteLength(t)},timeout:5e3});n.on("error",()=>{}),n.write(t),n.end()}catch{}}function F(e){const o=a.dirname(i);s.existsSync(o)||s.mkdirSync(o,{recursive:!0});const t={ts:new Date().toISOString(),version:C(),responses:e};let n=[];if(s.existsSync(i))try{n=JSON.parse(s.readFileSync(i,"utf8"))}catch{}return n.push(t),s.writeFileSync(i,JSON.stringify(n,null,2),"utf8"),t}function C(){try{const e=a.resolve(a.dirname(new URL(import.meta.url).pathname),"../../package.json");return JSON.parse(s.readFileSync(e,"utf8")).version}catch{return"unknown"}}function N(e){const o=process.platform;try{return o==="darwin"?f(`open "${e}"`,{stdio:"ignore"}):o==="win32"?f(`start "" "${e}"`,{stdio:"ignore"}):f(`xdg-open "${e}"`,{stdio:"ignore"}),!0}catch{return!1}}async function m(e,o){return new Promise(t=>e.question(o,t))}async function T(){const e=S.createInterface({input:process.stdin,output:process.stdout});console.log(`
|
|
2
|
-
`+b("\u{1F525} infernoflow feedback")+`
|
|
3
|
-
`),console.log(r(` Takes ~60 seconds. Helps make infernoflow better.
|
|
4
|
-
`));const o={};for(const n of x){if(console.log(p(` ${n.label}`)),n.choices){n.choices.forEach((h,g)=>console.log(r(` ${g+1}. ${h}`)));const c=await m(e," \u2192 "),l=parseInt(c.trim())-1;o[n.id]=l>=0&&l<n.choices.length?n.choices[l]:c.trim()}else{const c=await m(e," \u2192 ");o[n.id]=c.trim()||(n.optional?null:"\u2014")}console.log()}e.close();const t=F(o);E(t),console.log(k(` \u2714 Feedback saved \u2014 thank you!
|
|
5
|
-
`)),console.log(r(" Stored in: ~/.infernoflow/feedback.json")),console.log(r(` Version: ${t.version}`)),console.log(r(`
|
|
6
|
-
To share more detail or attach files, run: infernoflow feedback --form
|
|
7
|
-
`))}async function J(e){const o=t=>e.includes(t);if(o("--form")){console.log(p(`
|
|
8
|
-
Opening feedback form \u2192 ${d}
|
|
9
|
-
`)),N(d)||(console.log(O(" Could not open browser automatically.")),console.log(r(` Please open manually: ${d}
|
|
10
|
-
`)));return}if(o("--json")){if(!s.existsSync(i)){console.log(JSON.stringify([],null,2));return}try{const t=JSON.parse(s.readFileSync(i,"utf8"));console.log(JSON.stringify(t,null,2))}catch{console.log(JSON.stringify([],null,2))}return}process.stdin.isTTY||(console.log(v(` \u2718 infernoflow feedback requires an interactive terminal.
|
|
11
|
-
`)),console.log(r(` Run in a terminal or use: infernoflow feedback --form
|
|
12
|
-
`)),process.exit(1)),await T()}export{J as feedbackCommand};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import*as z from"node:fs";import*as x from"node:path";import{bold as j,cyan as Z,gray as h,green as k,yellow as M,red as C}from"../ui/output.mjs";function I(a){try{return JSON.parse(z.readFileSync(a,"utf8"))}catch{return null}}function te(a){return a?.stability||"experimental"}const S={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},Y={frozen:C,stable:M,experimental:k};function q(a,e){const f={},c={},r={},t={};for(const n of a){const g=e.find(p=>p.id===n.id)||{};f[n.id]={id:n.id,name:n.name||g.name||g.title||n.id,stability:g.stability||"experimental",functions:n.codeAnalysis?.functions||[],calls:n.codeAnalysis?.calls||[],services:n.codeAnalysis?.services||[],dbCalls:n.codeAnalysis?.dbCalls||[],httpCalls:n.codeAnalysis?.httpCalls||[]},c[n.id]=new Set,r[n.id]=new Set;for(const p of n.codeAnalysis?.functions||[]){const m=p.replace(/\(\)$/,"");t[m]=n.id,t[m.toLowerCase()]=n.id}}for(const[n,g]of Object.entries(f))for(const p of g.calls){const m=p.replace(/\(\)$/,""),y=t[m]||t[m.toLowerCase()];y&&y!==n&&c[n]&&r[y]&&(c[n].add(y),r[y].add(n))}const d={},i={};for(const n of Object.keys(f))d[n]=[...c[n]],i[n]=[...r[n]];return{nodes:f,edges:d,reverse:i}}function K(a){const{nodes:e,edges:f,reverse:c}=a,r=Object.keys(e).sort();console.log(),console.log(j(" Capability Dependency Graph")),console.log(h(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let t=!1;for(const i of r){const n=e[i],g=f[i]||[],p=c[i]||[],m=S[n.stability]||"\u{1F30A}",y=Y[n.stability]||k;if(!(g.length===0&&p.length===0)){if(t=!0,console.log(` ${m} ${j(y(i))}`),g.length>0){console.log(h(" calls \u2192"));for(const w of g){const b=e[w],X=S[b?.stability]||"\u{1F30A}";console.log(h(` ${X} ${w}`))}}if(p.length>0){console.log(h(" called by \u2190"));for(const w of p){const b=S[e[w]?.stability]||"\u{1F30A}";console.log(h(` ${b} ${w}`))}}console.log()}}t||(console.log(h(" No inter-capability dependencies detected.")),console.log(h(" Run `infernoflow scan` first to populate call data.")),console.log());const d=Object.values(a.edges).reduce((i,n)=>i+n.length,0);console.log(h(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(h(` ${r.length} capabilities \xB7 ${d} dependency edge(s)`)),console.log()}function Q(a,e){const{nodes:f,edges:c,reverse:r}=e,t=f[a];t||(console.error(C(`\u2717 Capability "${a}" not found in graph.`)),process.exit(1));const d=S[t.stability]||"\u{1F30A}",i=Y[t.stability]||k;console.log(),console.log(j(` ${d} ${i(a)}`)+h(` (${t.stability})`)),t.services?.length&&console.log(h(" external: ")+Z(t.services.join(", "))),console.log();const n=c[a]||[],g=r[a]||[];if(n.length>0){console.log(j(" Calls (downstream dependencies):"));for(const p of n){const m=f[p],y=Y[m?.stability]||k,w=S[m?.stability]||"\u{1F30A}";console.log(` ${w} ${y(p)}`+h(m?.services?.length?` [${m.services.join(", ")}]`:""))}console.log()}else console.log(h(" No downstream dependencies.")),console.log();if(g.length>0){console.log(j(" Called by (upstream dependents):"));for(const p of g){const m=f[p],y=Y[m?.stability]||k,w=S[m?.stability]||"\u{1F30A}";console.log(` ${w} ${y(p)}`)}console.log()}else console.log(h(" No capabilities call this one.")),console.log();if((t.stability==="frozen"||t.stability==="stable")&&g.length>0){const p=t.stability==="frozen"?C:M;console.log(p(` \u26A0 This capability is ${t.stability}. Changing it may break:`));for(const m of g)console.log(p(` \u2022 ${m}`));console.log()}}function ee(a,e){const f=[];if(!a||!e)return f;for(const[c,r]of Object.entries(e.nodes)){if(r.stability==="experimental")continue;const t=new Set(a.reverse?.[c]||[]),i=[...new Set(e.reverse[c]||[])].filter(n=>!t.has(n));if(i.length>0&&f.push({type:"new-dependents",capId:c,stability:r.stability,detail:`${i.join(", ")} now depend on this`}),r.stability==="frozen"){const n=new Set(a.edges?.[c]||[]),g=new Set(e.edges[c]||[]),p=[...g].filter(y=>!n.has(y)),m=[...n].filter(y=>!g.has(y));(p.length>0||m.length>0)&&f.push({type:"frozen-internals-changed",capId:c,stability:r.stability,detail:[p.length?`added calls: ${p.join(", ")}`:"",m.length?`removed calls: ${m.join(", ")}`:""].filter(Boolean).join("; ")})}}return f}async function ae(a){const e=(a||[]).slice(1),f=e.includes("--json"),c=e.includes("--check"),r=e.includes("--mermaid"),t=e.includes("--html"),d=e.indexOf("--cap"),i=d!==-1?e[d+1]:null,n=process.cwd(),g=x.join(n,"inferno"),p=x.join(g,"scan.json"),m=x.join(g,"graph.json"),y=x.join(g,"capabilities.json"),w=300*1e3;let b=I(p);if(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0||z.existsSync(p)&&Date.now()-z.statSync(p).mtimeMs>w){console.log(h(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:o}=await import("./scan.mjs");await o(["scan"]),b=I(p)}catch(o){console.error(C(`\u2717 Could not run scan automatically: ${o.message}`)),console.error(h(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0)&&(console.error(C("\u2717 inferno/scan.json still empty after scan.")),console.error(h(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let P=[];const N=I(y);N&&(P=Array.isArray(N)?N:N.capabilities||[]);const R=b.capabilities||[],s=q(R,P),v=Array.isArray(b.components)?b.components:[],D={};for(const o of R){const l=(o.codeAnalysis?.files||[]).map($=>$.replace(/\\/g,"/"));for(const $ of l)D[$]||(D[$]=new Set),D[$].add(o.id)}const G=[/(?:^|\/)src\/App\.(jsx|tsx|js|ts|vue|svelte)$/i,/(?:^|\/)src\/main\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/index\.(jsx|tsx|js|ts)$/i,/(?:^|\/)pages\/_app\.(jsx|tsx|js|ts)$/i,/(?:^|\/)app\/layout\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/App\.(jsx|tsx)$/i],E=new Set;for(const o of v)G.some(l=>l.test(o.file))&&E.add(o.name);let W=0;for(const o of v){const l=`comp:${o.name}`;s.nodes[l]={id:l,name:o.name,stability:E.has(o.name)?"entry":"component",kind:"component",isEntry:E.has(o.name),file:o.file,functions:[],calls:[]},s.edges[l]=s.edges[l]||new Set,s.reverse[l]=s.reverse[l]||new Set;const $=D[o.file]?[...D[o.file]]:[];for(const u of $)s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),W++}let T=0;for(const o of v){const l=`comp:${o.name}`;for(const $ of o.renders||[]){const u=`comp:${$}`;s.nodes[u]&&l!==u&&(s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),T++)}}const V=Array.isArray(b.uiElements)?b.uiElements:[],_={};for(const o of v)_[o.file]||(_[o.file]=o.name);const F={};for(const o of R){const l=o.codeAnalysis?.functions||[];for(const $ of l){const u=$.replace(/\(\)$/,"");F[u]=o.id,F[u.toLowerCase()]=o.id}}let O=0;for(const o of V){const l=`ui:${o.tag}:${o.handler}:${o.file.replace(/[^a-z0-9]/gi,"_")}`;s.nodes[l]={id:l,name:o.label||o.handler,stability:"ui",kind:"ui",tag:o.tag,handler:o.handler,file:o.file,functions:[],calls:[]},s.edges[l]=s.edges[l]||new Set,s.reverse[l]=s.reverse[l]||new Set;const $=_[o.file];if($){const L=`comp:${$}`;if(s.nodes[L]){s.edges[l].add(L),s.reverse[L]||(s.reverse[L]=new Set),s.reverse[L].add(l),O++;continue}}const u=F[o.handler]||F[o.handler?.toLowerCase()];u&&(s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),O++)}!f&&!r&&!t&&(W>0&&console.log(h(` \u{1F9E9} Wired ${v.length} component${v.length===1?"":"s"} to capabilities.`)),T>0&&console.log(h(` \u{1F333} Found ${T} component render relationship${T===1?"":"s"} (parent \u2192 child).`)),O>0&&console.log(h(` \u26A1 Wired ${O} UI element${O===1?"":"s"}.`)),E.size>0&&console.log(h(` \u{1F6AA} Entry: ${[...E].join(", ")}`)));const H=I(m),B=ee(H,s),J={builtAt:new Date().toISOString(),capabilities:Object.keys(s.nodes).length,edges:Object.values(s.edges).reduce((o,l)=>o+l.length,0),nodes:s.nodes,deps:s.edges,dependents:s.reverse};if(f||z.writeFileSync(m,JSON.stringify(J,null,2)),f){console.log(JSON.stringify(J,null,2));return}if(r){console.log(oe(s));return}if(t){const o=x.join(g,"graph.html");z.writeFileSync(o,ne(s)),console.log(k("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(h(` Open it: file://${o.replace(/\\/g,"/")}`));return}if(i?Q(i,s):K(s),B.length>0){console.log(M(" \u26A0 Dependency changes detected:"));for(const o of B){const l=o.stability==="frozen"?C("\u{1F9CA}"):M("\u3030\uFE0F ");console.log(` ${l} ${j(o.capId)} \u2014 ${o.detail}`)}console.log(),c&&process.exit(1)}f||console.log(h(" Graph saved \u2192 inferno/graph.json"))}function oe(a){const e=[];e.push("```mermaid"),e.push("graph LR"),e.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),e.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),e.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),e.push(" classDef component fill:#fff3e0,stroke:#ff9800,color:#bf6d00;"),e.push(" classDef entry fill:#fce4ec,stroke:#e91e63,color:#880e4f,stroke-width:3px;"),e.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const f of Object.keys(a.nodes)){const c=A(f),r=a.nodes[f];if(r.kind==="ui"){const d=`${U(r.tag)} ${r.name||r.handler}<br/><small><${r.tag}></small>`;e.push(` ${c}(["${d}"]):::ui`)}else if(r.kind==="component")r.isEntry?e.push(` ${c}{{"\u{1F6AA} ${r.name} (entry)"}}:::entry`):e.push(` ${c}{{"\u{1F9E9} ${r.name}"}}:::component`);else{const t=r.functions?.length||0,d=`${r.name||f}<br/><small>${t} fn${t===1?"":"s"}</small>`;e.push(` ${c}["${d}"]:::${r.stability||"experimental"}`)}}for(const[f,c]of Object.entries(a.edges)){const r=c instanceof Set?[...c]:Array.isArray(c)?c:[];for(const t of r)e.push(` ${A(f)} --> ${A(t)}`)}return e.push("```"),e.join(`
|
|
2
|
-
`)}function A(a){return String(a).replace(/[^a-zA-Z0-9_]/g,"_")}function U(a){switch(a){case"button":return"\u{1F518}";case"input":return"\u2328\uFE0F ";case"form":return"\u{1F4DD}";case"link":return"\u{1F517}";case"select":return"\u25BE";default:return"\u{1F9E9}"}}function ne(a){const e=[];e.push("graph LR"),e.push(" classDef frozen fill:#3a1a1a,stroke:#d43f3a,color:#ff8a80,stroke-width:2px;"),e.push(" classDef stable fill:#3a2a1a,stroke:#f0ad4e,color:#ffd180,stroke-width:2px;"),e.push(" classDef experimental fill:#1a2a3a,stroke:#5bc0de,color:#9fd6ed,stroke-width:2px;"),e.push(" classDef component fill:#2a1f0a,stroke:#ff9800,color:#ffcc80,stroke-width:2px;"),e.push(" classDef entry fill:#3a0a1f,stroke:#e91e63,color:#ff80ab,stroke-width:3px;"),e.push(" classDef ui fill:#1a2e1a,stroke:#4caf50,color:#a5d6a7,stroke-width:2px,stroke-dasharray: 4 2;");for(const t of Object.keys(a.nodes)){const d=A(t),i=a.nodes[t];if(i.kind==="ui"){const g=`${U(i.tag)} ${i.name||i.handler}`;e.push(` ${d}(["${g}"]):::ui`)}else if(i.kind==="component")i.isEntry?e.push(` ${d}{{"\u{1F6AA} ${i.name}"}}:::entry`):e.push(` ${d}{{"\u{1F9E9} ${i.name}"}}:::component`);else{const n=i.functions?.length||0,g=`${i.name||t}<br/><small>${n} fn${n===1?"":"s"}</small>`;e.push(` ${d}["${g}"]:::${i.stability||"experimental"}`)}}for(const[t,d]of Object.entries(a.edges)){const i=d instanceof Set?[...d]:Array.isArray(d)?d:[];for(const n of i)e.push(` ${A(t)} --> ${A(n)}`)}const f=e.join(`
|
|
3
|
-
`),c=Object.keys(a.nodes).length,r=Object.values(a.edges).reduce((t,d)=>t+(d instanceof Set?d.size:Array.isArray(d)?d.length:0),0);return`<!DOCTYPE html>
|
|
4
|
-
<html lang="en">
|
|
5
|
-
<head>
|
|
6
|
-
<meta charset="UTF-8" />
|
|
7
|
-
<title>infernoflow \u2014 code map</title>
|
|
8
|
-
<style>
|
|
9
|
-
html, body { margin: 0; padding: 0; background: #1e1e1e; color: #ccc; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; height: 100%; overflow: hidden; }
|
|
10
|
-
header { padding: 12px 24px; background: #2a2a2a; border-bottom: 1px solid #3a3a3a; }
|
|
11
|
-
header h1 { margin: 0; font-size: 16px; font-weight: 600; }
|
|
12
|
-
header .meta { font-size: 12px; color: #999; margin-top: 4px; }
|
|
13
|
-
header .meta span { margin-right: 16px; }
|
|
14
|
-
header .legend { margin-top: 8px; font-size: 11px; }
|
|
15
|
-
header .legend .chip { display: inline-block; padding: 2px 8px; border-radius: 4px; margin-right: 8px; font-size: 11px; border: 1px solid; }
|
|
16
|
-
header .legend .entry { color: #ff80ab; border-color: #e91e63; background: #3a0a1f; }
|
|
17
|
-
header .legend .component { color: #ffcc80; border-color: #ff9800; background: #2a1f0a; }
|
|
18
|
-
header .legend .capability { color: #9fd6ed; border-color: #5bc0de; background: #1a2a3a; }
|
|
19
|
-
header .legend .ui { color: #a5d6a7; border-color: #4caf50; background: #1a2e1a; border-style: dashed; }
|
|
20
|
-
#map-wrap { width: 100vw; height: calc(100vh - 88px); overflow: auto; cursor: grab; }
|
|
21
|
-
#map-wrap:active { cursor: grabbing; }
|
|
22
|
-
#map { padding: 24px; display: inline-block; min-width: 100%; }
|
|
23
|
-
.mermaid { background: transparent; }
|
|
24
|
-
.mermaid svg { max-width: none !important; height: auto !important; }
|
|
25
|
-
.empty { padding: 40px 24px; color: #888; font-size: 14px; }
|
|
26
|
-
</style>
|
|
27
|
-
</head>
|
|
28
|
-
<body>
|
|
29
|
-
<header>
|
|
30
|
-
<h1>\u{1F525} infernoflow \u2014 code map</h1>
|
|
31
|
-
<div class="meta">
|
|
32
|
-
<span>Generated: \${new Date().toLocaleString()}</span>
|
|
33
|
-
<span>\${nodeCount} nodes \xB7 \${edgeCount} edges</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="legend">
|
|
36
|
-
<span class="chip entry">\u{1F6AA} entry</span>
|
|
37
|
-
<span class="chip component">\u{1F9E9} component</span>
|
|
38
|
-
<span class="chip capability">capability</span>
|
|
39
|
-
<span class="chip ui">UI element</span>
|
|
40
|
-
<span style="color:#666;">scroll to pan \xB7 pinch / Ctrl-scroll to zoom \xB7 click-drag empty area</span>
|
|
41
|
-
</div>
|
|
42
|
-
</header>
|
|
43
|
-
<div id="map-wrap">
|
|
44
|
-
<div id="map">
|
|
45
|
-
\${nodeCount === 0
|
|
46
|
-
? '<div class="empty">No nodes to render \u2014 run <code>infernoflow scan</code> first.</div>'
|
|
47
|
-
: '<pre class="mermaid">' + \`${f.replace(/`/g,"`")}\` + '</pre>'
|
|
48
|
-
}
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<script type="module">
|
|
52
|
-
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
53
|
-
mermaid.initialize({
|
|
54
|
-
startOnLoad: true,
|
|
55
|
-
theme: "dark",
|
|
56
|
-
securityLevel: "loose",
|
|
57
|
-
flowchart: { useMaxWidth: false, htmlLabels: true, curve: "linear", padding: 20, nodeSpacing: 60, rankSpacing: 80 },
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Drag-to-pan on the wrapper (zoom is native via wheel/pinch + browser zoom)
|
|
61
|
-
const wrap = document.getElementById("map-wrap");
|
|
62
|
-
let dragging = false, startX = 0, startY = 0, scrollX = 0, scrollY = 0;
|
|
63
|
-
wrap.addEventListener("mousedown", e => {
|
|
64
|
-
if (e.target.closest("a, button")) return;
|
|
65
|
-
dragging = true; startX = e.pageX; startY = e.pageY; scrollX = wrap.scrollLeft; scrollY = wrap.scrollTop;
|
|
66
|
-
e.preventDefault();
|
|
67
|
-
});
|
|
68
|
-
window.addEventListener("mousemove", e => {
|
|
69
|
-
if (!dragging) return;
|
|
70
|
-
wrap.scrollLeft = scrollX - (e.pageX - startX);
|
|
71
|
-
wrap.scrollTop = scrollY - (e.pageY - startY);
|
|
72
|
-
});
|
|
73
|
-
window.addEventListener("mouseup", () => { dragging = false; });
|
|
74
|
-
</script>
|
|
75
|
-
</body>
|
|
76
|
-
</html>`}function re(a){return I(x.join(a,"graph.json"))}export{ae as graphCommand,re as loadGraph};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import*as R from"node:fs";import*as k from"node:path";import{bold as C,gray as l,green as j,yellow as A,red as x}from"../ui/output.mjs";function E(t){try{return JSON.parse(R.readFileSync(t,"utf8"))}catch{return null}}const L={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},O={frozen:x,stable:A,experimental:j};function h(t){return t?.stability||"experimental"}function _(t,o,i=10){const c=new Set(o[t]||[]),n=new Set,r=[...c].map(u=>({id:u,depth:1})),e=new Set([t,...c]);for(;r.length>0;){const{id:u,depth:f}=r.shift();if(!(f>=i))for(const d of o[u]||[])e.has(d)||(e.add(d),n.add(d),r.push({id:d,depth:f+1}))}return{direct:c,transitive:n}}function F(t){const o=k.join(t,"scenarios");if(!R.existsSync(o))return[];const i=[];for(const c of R.readdirSync(o))if(c.endsWith(".json"))try{const n=JSON.parse(R.readFileSync(k.join(o,c),"utf8"));i.push(n)}catch{}return i}function D(t,o){return o.filter(i=>(i.capabilitiesCovered||i.capabilities||[]).some(n=>n.toLowerCase()===t.toLowerCase()))}function J(t,o,i){if(h(t)==="frozen"&&o.size>0)return"critical";for(const n of o){const r=i.find(e=>e.id===n);if(h(r)==="frozen")return"high"}for(const n of o){const r=i.find(e=>e.id===n);if(h(r)==="stable")return"medium"}return"low"}const K={critical:x,high:x,medium:A,low:j},M={critical:"\u{1F534}",high:"\u{1F534}",medium:"\u{1F7E1}",low:"\u{1F7E2}"},T={critical:"This capability is FROZEN \u2014 any change is high-risk. Requires explicit approval.",high:"A frozen capability depends on this \u2014 test thoroughly before merging.",medium:"A stable capability is in the blast zone \u2014 prefer additive changes only.",low:"All dependents are experimental \u2014 safe to iterate freely."};function U({capId:t,targetCap:o,direct:i,transitive:c,affectedScenarios:n,risk:r,allCaps:e,deps:u}){const f=h(o),d=L[f]||"\u{1F30A}",w=O[f]||j,I=K[r],m=M[r];if(console.log(),console.log(C(` ${d} ${w(t)}`)),(o?.name||o?.title)&&console.log(l(` ${o.name||o.title}`)),console.log(l(" stability: ")+w(f)),console.log(),u.length>0){console.log(l(" This cap calls:"));for(const s of u){const p=e.find(S=>S.id===s),g=L[h(p)]||"\u{1F30A}",y=O[h(p)]||j;console.log(` ${g} ${y(s)}`)}console.log()}if(i.size===0)console.log(l(" No capabilities depend on this one.")),console.log(l(" \u2714 Safe to change freely.")),console.log();else{console.log(C(" Direct dependents (will be immediately affected):"));for(const s of i){const p=e.find(v=>v.id===s),g=h(p),y=L[g]||"\u{1F30A}",S=O[g]||j;console.log(` ${y} ${S(s)}`)}if(console.log(),c.size>0){console.log(C(" Transitive dependents (indirectly affected):"));for(const s of c){const p=e.find(v=>v.id===s),g=h(p),y=L[g]||"\u{1F30A}",S=O[g]||j;console.log(` ${y} ${S(s)}`)}console.log()}}if(n.length>0){console.log(C(" Scenarios at risk:"));for(const s of n)console.log(` ${A("\u26A0")} ${s.scenarioId||s.description||"(unnamed)"}`),s.description&&console.log(l(` ${s.description}`));console.log()}else i.size>0&&(console.log(l(" No scenarios cover the affected capabilities.")),console.log(l(" Consider adding scenarios before making this change.")),console.log());console.log(` ${m} Risk level: ${C(I(r.toUpperCase()))}`),console.log(` ${l(T[r])}`),console.log();const $=i.size+c.size;$>0&&(console.log(l(` \u2500\u2500 ${i.size} direct \xB7 ${c.size} transitive \xB7 ${$} total affected \xB7 ${n.length} scenario(s) at risk`)),console.log())}async function W(t){const o=(t||[]).slice(1),i=o.includes("--json"),c=o.includes("--check"),n=o.indexOf("--depth"),r=n!==-1&&parseInt(o[n+1],10)||10,e=o.find((a,b)=>!a.startsWith("--")&&(n===-1||b!==n+1));e||(console.error(x("\u2717 Usage: infernoflow impact <capability-id> [--depth N] [--json] [--check]")),console.error(l(" Example: infernoflow impact user-auth")),process.exit(1));const u=process.cwd(),f=k.join(u,"inferno"),d=E(k.join(f,"graph.json"));d||(console.error(x("\u2717 inferno/graph.json not found \u2014 run `infernoflow graph` first.")),process.exit(1));let w=[];const I=E(k.join(f,"capabilities.json"));I&&(w=Array.isArray(I)?I:I.capabilities||[]);const m=w.find(a=>a.id===e);m||(console.error(x(`\u2717 Capability "${e}" not found in capabilities.json`)),console.error(l(" Run: infernoflow stability \u2014 to list all capability IDs")),process.exit(1));const{direct:$,transitive:s}=_(e,d.dependents||{},r),p=new Set([...$,...s]),g=d.deps?.[e]||[],y=F(f),S=D(e,y),v=new Map;for(const a of S)v.set(a.scenarioId||a.description,a);for(const a of p)for(const b of D(a,y))v.set(b.scenarioId||b.description,b);const N=[...v.values()],z=J(m,p,w);if(i){const a={capId:e,name:m.name||m.title,stability:h(m),risk:z,direct:[...$],transitive:[...s],deps:g,affectedScenarios:N.map(b=>b.scenarioId||b.description),summary:{directCount:$.size,transitiveCount:s.size,totalAffected:p.size,scenariosAtRisk:N.length}};console.log(JSON.stringify(a,null,2)),c&&(z==="high"||z==="critical")&&process.exit(1);return}console.log(l(`
|
|
2
|
-
infernoflow impact \u2192 ${C(e)}`)),console.log(l(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),U({capId:e,targetCap:m,direct:$,transitive:s,affectedScenarios:N,risk:z,allCaps:w,deps:g}),c&&(z==="high"||z==="critical")&&(console.log(x(" \u2717 --check failed: risk level is "+z.toUpperCase())),process.exit(1))}export{W as impactCommand};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import y from"node:fs";import b from"node:path";import{execSync as c}from"node:child_process";import{header as k,section as f,info as m,warn as d,cyan as h,gray as s,errorAndExit as u}from"../ui/output.mjs";import{loadImplementContext as w,buildCursorImplementPrompt as x,buildGenericImplementPrompt as C}from"../ui/prompts.mjs";function P(o,n){const t=o.indexOf(n);return t!==-1&&o[t+1]?o[t+1]:null}function A(o){const n=new Set(["--mode"]),t=[];for(let e=0;e<o.length;e+=1){const r=o[e];if(r.startsWith("-")){n.has(r)&&(e+=1);continue}e!==0&&t.push(r)}return t.join(" ").trim()}function $(o){try{const n=process.platform;if(n==="win32")c("clip",{input:o});else if(n==="darwin")c("pbcopy",{input:o});else try{c("xclip -selection clipboard",{input:o})}catch{c("xsel --clipboard --input",{input:o})}return!0}catch{return!1}}async function j(o=[]){k("implement");const n=process.cwd(),t=b.join(n,"inferno");y.existsSync(t)||u("inferno/ not found","Run: infernoflow init");const e=(P(o,"--mode")||"both").toLowerCase(),r=o.includes("--copy")||o.includes("-c");["cursor","generic","both"].includes(e)||u("Invalid --mode value","Use: --mode cursor|generic|both");const i=A(o);i||u("No task provided",'Usage: infernoflow implement "your task description"');const a=w(n),l=x({task:i,...a}),p=C({task:i,...a});if(m(`Task: ${h(i)}`),m(`Mode: ${h(e)}`),d("If you hit model high-load/resource-exhausted, retry with Auto/another model."),(e==="cursor"||e==="both")&&(f("Cursor Agent Prompt"),console.log(),console.log(s("\u2500".repeat(50))),console.log(l),console.log(s("\u2500".repeat(50)))),(e==="generic"||e==="both")&&(f("Generic Agent Prompt"),console.log(),console.log(s("\u2500".repeat(50))),console.log(p),console.log(s("\u2500".repeat(50)))),r){const g=e==="cursor"?l:e==="generic"?p:`## Cursor Agent Prompt
|
|
2
|
-
|
|
3
|
-
${l}
|
|
4
|
-
|
|
5
|
-
## Generic Agent Prompt
|
|
6
|
-
|
|
7
|
-
${p}`;$(g)?m(`Copied ${e} prompt${e==="both"?"s":""} to clipboard.`):d("Clipboard copy failed. Copy from terminal output.")}console.log()}export{j as implementCommand};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import*as g from"node:fs";import*as p from"node:path";import{fileURLToPath as F}from"node:url";import{spawnSync as M}from"node:child_process";import{header as b,warn as S,info as P,done as x,bold as u,cyan as k,gray as d,green as y,yellow as j,red as $}from"../ui/output.mjs";function A(e){const a=s=>g.existsSync(p.join(e,s));if(a("nx.json"))return"nx";if(a("turbo.json"))return"turborepo";if(a("lerna.json"))return"lerna";if(a("pnpm-workspace.yaml"))return"pnpm";const n=(()=>{try{return JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8"))}catch{return{}}})();return n.workspaces?Array.isArray(n.workspaces)?"yarn":"npm-workspaces":null}function C(e,a){try{if(a==="pnpm")return[...g.readFileSync(p.join(e,"pnpm-workspace.yaml"),"utf8").matchAll(/^\s*-\s*['"]?([^'"]+)['"]?/gm)].map(c=>c[1].trim());if(a==="lerna")return JSON.parse(g.readFileSync(p.join(e,"lerna.json"),"utf8")).packages||["packages/*"];const s=JSON.parse(g.readFileSync(p.join(e,"package.json"),"utf8")).workspaces;if(Array.isArray(s))return s;if(s?.packages)return s.packages}catch{}return["packages/*","apps/*","libs/*"]}function D(e,a){const n=[];for(const s of a){const l=s.split("/"),r=l.slice(0,-1).join("/"),c=l[l.length-1],o=p.join(e,r);if(g.existsSync(o))if(c==="*")try{for(const i of g.readdirSync(o,{withFileTypes:!0}))if(i.isDirectory()){const t=p.join(o,i.name,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||i.name,dir:p.join(o,i.name),version:f.version||"0.0.0"})}}}catch{}else{const i=p.join(e,s),t=p.join(i,"package.json");if(g.existsSync(t)){const f=JSON.parse(g.readFileSync(t,"utf8"));n.push({name:f.name||c,dir:i,version:f.version||"0.0.0"})}}}return n}function N(e){const a=A(e);if(!a)return{type:null,packages:[]};const n=C(e,a),s=D(e,n);return{type:a,packages:s}}function w(e){for(const a of["contract.json","capabilities.json"]){const n=p.join(e,"inferno",a);if(g.existsSync(n))try{return JSON.parse(g.readFileSync(n,"utf8"))}catch{}}return null}function O(e){return g.existsSync(p.join(e,"inferno"))}function J(e){return O(e)?w(e)?"ok":"no-contract":"not-init"}function v(e,a){const n=p.join(p.dirname(p.dirname(F(import.meta.url))),"..","bin","infernoflow.mjs"),s=M(process.execPath,[n,...e],{cwd:a,encoding:"utf8",timeout:6e4,env:{...process.env,NO_COLOR:"1"}});return{stdout:s.stdout||"",stderr:s.stderr||"",status:s.status}}async function I(e,a){const n=e.includes("--json"),s=e.includes("--force")||e.includes("-f"),l=e.includes("--yes")||e.includes("-y"),{type:r,packages:c}=N(a);if(!r){const t="No monorepo configuration detected. Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna.";n?console.log(JSON.stringify({ok:!1,error:t})):S(t),process.exit(1)}n||(b(`Monorepo init (${r})`),console.log(` Detected ${u(String(c.length))} packages:
|
|
2
|
-
`),c.forEach(t=>{const m=J(t.dir)==="ok"?y("\u2714"):j("\xB7");console.log(` ${m} ${u(t.name)} ${d(p.relative(a,t.dir))}`)}),console.log()),c.length===0&&(n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found matching workspace globs."),process.exit(1));const o=[];for(const t of c){if(J(t.dir)==="ok"&&!s){n||P(`${t.name}: already initialised (use --force to reinit)`),o.push({name:t.name,status:"skipped"});continue}n||process.stdout.write(` Initialising ${k(t.name)}\u2026 `);const m=["init","--adopt","--yes"];s&&m.push("--force");const h=v(m,t.dir);h.status===0?(n||console.log(y("done")),o.push({name:t.name,status:"ok"})):(n||console.log($("failed")),o.push({name:t.name,status:"error",error:h.stderr.trim().slice(0,120)}))}const i={monorepoType:r,packages:o,updatedAt:new Date().toISOString()};if(g.writeFileSync(p.join(a,"inferno-monorepo.json"),JSON.stringify(i,null,2)+`
|
|
3
|
-
`),n)console.log(JSON.stringify({ok:!0,type:r,packages:o}));else{console.log();const t=o.filter(f=>f.status==="ok").length;x(`Initialised ${u(String(t))} of ${o.length} packages`),console.log(` ${d("Root summary:")} ${k("inferno-monorepo.json")}`),console.log()}}async function T(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!s&&l.length===0){n?console.log(JSON.stringify({ok:!1,error:"No monorepo detected"})):S("No monorepo detected. Run: infernoflow monorepo init");return}const r=l.map(o=>({name:o.name,dir:p.relative(a,o.dir),version:o.version,status:J(o.dir),caps:(()=>{const i=w(o.dir);return i?(i.capabilities||[]).length:0})()}));if(n){console.log(JSON.stringify({ok:!0,type:s,packages:r}));return}console.log(),console.log(` ${u("Monorepo packages")} ${d("("+s+")")}`),console.log();const c=Math.max(...r.map(o=>o.name.length),8)+2;r.forEach(o=>{const i=o.status==="ok"?y("\u2714"):o.status==="not-init"?j("\u25CB"):$("\u2717"),t=o.status==="ok"?d(`${o.caps} caps`):d(o.status);console.log(` ${i} ${o.name.padEnd(c)}${o.version.padEnd(12)}${t}`)}),console.log()}async function E(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);if(!l.length){n?console.log(JSON.stringify({ok:!1,error:"No packages found"})):S("No packages found.");return}n||b(`Monorepo status (${l.length} packages)`);const r=[];for(const c of l){if(!O(c.dir)){r.push({name:c.name,status:"not-init",caps:0}),n||console.log(` ${j("\u25CB")} ${u(c.name)} ${d("not initialised")}`);continue}const o=v(["status","--json"],c.dir);try{const i=JSON.parse(o.stdout.trim()),t=(i.capabilityDetails||[]).length,f=i.ok!==!1;if(r.push({name:c.name,status:f?"ok":"error",caps:t,version:i.policyVersion}),!n){const m=f?y("\u2714"):$("\u2717");console.log(` ${m} ${u(c.name.padEnd(28))}${d("v"+(i.policyVersion||"?"))} ${t} caps`)}}catch{r.push({name:c.name,status:"error",caps:0,error:"status failed"}),n||console.log(` ${$("\u2717")} ${u(c.name)} ${d("status check failed")}`)}}if(n){const c=r.every(o=>o.status==="ok");console.log(JSON.stringify({ok:c,type:s,packages:r}))}else{console.log();const c=r.filter(o=>o.status==="ok").length;console.log(` ${c===r.length?y("\u2714"):j("\u26A0")} ${c}/${r.length} packages healthy`),console.log()}}async function R(e,a){const n=e.includes("--json"),s=e.includes("--package")?e[e.indexOf("--package")+1]:null,{packages:l}=N(a),r=s?l.filter(o=>o.name===s||o.name.endsWith("/"+s)):l.filter(o=>O(o.dir));if(!r.length){n?console.log(JSON.stringify({ok:!1,error:s?`Package not found: ${s}`:"No initialised packages found"})):S(s?`Package not found: ${s}`:"No initialised packages found.");return}!n&&!s&&b(`Monorepo diff (${r.length} packages)`);const c=[];for(const o of r){const i=v(["diff","--json"],o.dir);try{const t=JSON.parse(i.stdout.trim()),f=(t.added||[]).length,m=(t.removed||[]).length,h=(t.changed||[]).length;c.push({name:o.name,added:f,removed:m,changed:h,data:t}),n||(f||m||h?(console.log(` ${u(o.name)}`),f&&console.log(` ${y("+")} ${f} added`),m&&console.log(` ${$("-")} ${m} removed`),h&&console.log(` ${j("~")} ${h} changed`)):console.log(` ${y("\u2714")} ${u(o.name)} ${d("no changes")}`))}catch{c.push({name:o.name,error:"diff failed"}),n||console.log(` ${$("\u2717")} ${u(o.name)} ${d("diff failed")}`)}}n?console.log(JSON.stringify({ok:!0,packages:c})):console.log()}async function L(e,a){const n=e.includes("--json"),{type:s,packages:l}=N(a);n||b("Syncing monorepo contracts");const r={monorepoType:s,updatedAt:new Date().toISOString(),packages:[]};for(const i of l){const t=w(i.dir);t&&(r.packages.push({name:i.name,version:t.policyVersion||i.version,capabilities:(t.capabilities||[]).map(f=>typeof f=="string"?f:f.id),capsCount:(t.capabilities||[]).length}),n||console.log(` ${y("\u2714")} ${u(i.name)} ${d((t.capabilities||[]).length+" caps")}`))}const c=p.join(a,"inferno-monorepo.json");g.writeFileSync(c,JSON.stringify(r,null,2)+`
|
|
4
|
-
`);const o=r.packages.reduce((i,t)=>i+t.capsCount,0);n?console.log(JSON.stringify({ok:!0,packages:r.packages.length,totalCaps:o})):(console.log(),x(`Synced ${u(String(r.packages.length))} packages (${o} total capabilities)`),console.log(` ${k(c)}`),console.log())}async function G(e){const a=e.slice(1),n=a[0],s=process.cwd(),l=a.slice(1);switch(n){case"init":return I(l,s);case"list":return T(l,s);case"status":return E(l,s);case"diff":return R(l,s);case"sync":return L(l,s);default:{const r=a.includes("--json"),c=`Unknown monorepo sub-command: ${n||"(none)"}`;if(r){console.log(JSON.stringify({ok:!1,error:c}));return}console.log(),console.log(` ${u("infernoflow monorepo")} \u2014 per-package capability tracking`),console.log(),console.log(` ${k("infernoflow monorepo init")} Scaffold inferno/ in each package`),console.log(` ${k("infernoflow monorepo list")} List packages and contract status`),console.log(` ${k("infernoflow monorepo status")} Health check across all packages`),console.log(` ${k("infernoflow monorepo diff")} Capability diff for all packages`),console.log(` ${k("infernoflow monorepo diff --package auth")} Diff a specific package`),console.log(` ${k("infernoflow monorepo sync")} Aggregate all contracts to inferno-monorepo.json`),console.log(),console.log(` ${d("Supported: nx, turborepo, pnpm workspaces, yarn workspaces, lerna")}`),console.log()}}}export{G as monorepoCommand};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import*as k from"node:fs";import*as g from"node:path";import*as $ from"node:https";import*as b from"node:http";import{spawnSync as x}from"node:child_process";import{done as O,warn as m,info as S,cyan as N,gray as v}from"../ui/output.mjs";function C(a,t){const n=g.join(a,"notify.json"),i=k.existsSync(n)?(()=>{try{return JSON.parse(k.readFileSync(n,"utf8"))}catch{return{}}})():{},l=t.indexOf("--slack"),e=t.indexOf("--discord");return{slack:l!==-1?t[l+1]:process.env.INFERNOFLOW_SLACK_WEBHOOK||i.slack,discord:e!==-1?t[e+1]:process.env.INFERNOFLOW_DISCORD_WEBHOOK||i.discord}}function w(a,t){try{const i=x(process.execPath,[g.join(g.dirname(g.dirname(fileURLToPath(import.meta.url))),"..","bin","infernoflow.mjs"),...a.split(" ").slice(1)],{cwd:t,encoding:"utf8",timeout:2e4}).stdout?.trim();if(i)return JSON.parse(i)}catch{}return null}function R(a,t,n){const i=a?.status||"unknown",l=(n?.capabilities||[]).length,e=n?.policyVersion||"?",o=n?.policyId||"project",r=t?.added||[],u=t?.removed||[],p=t?.changed||[];return{status:i,caps:l,version:e,project:o,added:r,removed:u,changed:p}}function D(a){const{status:t,caps:n,version:i,project:l,added:e,removed:o,changed:r}=a,u=t==="ok"?"\u2705":t==="warning"?"\u26A0\uFE0F":"\u274C",p=e.length||o.length||r.length,f=[{type:"header",text:{type:"plain_text",text:`\u{1F525} infernoflow \u2014 ${l} v${i}`,emoji:!0}},{type:"section",fields:[{type:"mrkdwn",text:`*Status*
|
|
2
|
-
${u} ${t.toUpperCase()}`},{type:"mrkdwn",text:`*Capabilities*
|
|
3
|
-
${n} tracked`}]}];if(p){const d=[];e.length&&d.push(`\u2705 *${e.length}* added: ${e.slice(0,3).join(", ")}${e.length>3?` +${e.length-3} more`:""}`),o.length&&d.push(`\u274C *${o.length}* removed: ${o.slice(0,3).join(", ")}${o.length>3?` +${o.length-3} more`:""}`),r.length&&d.push(`\u{1F4DD} *${r.length}* changed`),f.push({type:"section",text:{type:"mrkdwn",text:d.join(`
|
|
4
|
-
`)}})}return f.push({type:"context",elements:[{type:"mrkdwn",text:`<https://github.com/ronmiz/infernoflow|infernoflow> \xB7 ${new Date().toLocaleString()}`}]}),{blocks:f}}function L(a){const{status:t,caps:n,version:i,project:l,added:e,removed:o,changed:r}=a,u=t==="ok"?4906624:t==="warning"?16347926:16281969,p=e.length||o.length||r.length,f=[{name:"Status",value:t.toUpperCase(),inline:!0},{name:"Capabilities",value:String(n),inline:!0},{name:"Version",value:`v${i}`,inline:!0}];return e.length&&f.push({name:"\u2705 Added",value:e.slice(0,5).join(", ")+(e.length>5?` +${e.length-5}`:""),inline:!1}),o.length&&f.push({name:"\u274C Removed",value:o.slice(0,5).join(", ")+(o.length>5?` +${o.length-5}`:""),inline:!1}),{embeds:[{title:`\u{1F525} infernoflow \u2014 ${l}`,description:p?"Capability changes detected":"Contract healthy",color:u,fields:f,footer:{text:"infernoflow \xB7 "+new Date().toLocaleString()},url:"https://github.com/ronmiz/infernoflow"}]}}function j(a,t){return new Promise((n,i)=>{const l=JSON.stringify(t),e=new URL(a),o=e.protocol==="https:",u=(o?$:b).request({hostname:e.hostname,port:e.port||(o?443:80),path:e.pathname+(e.search||""),method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(l),"User-Agent":"infernoflow-cli"}},p=>{let f="";p.on("data",d=>f+=d),p.on("end",()=>n({status:p.statusCode,body:f}))});u.on("error",i),u.write(l),u.end()})}async function K(a){const t=a.slice(1),n=t.includes("--json"),i=t.includes("--dry-run"),l=t.includes("--on-change"),e=process.cwd(),o=g.join(e,"inferno");if(!k.existsSync(o)){const c="inferno/ not found. Run: infernoflow init";n?console.log(JSON.stringify({ok:!1,error:c})):m(c),process.exit(1)}const r=C(o,t);if(!r.slack&&!r.discord){const c="No webhook configured. Use --slack <url>, --discord <url>, or set INFERNOFLOW_SLACK_WEBHOOK / INFERNOFLOW_DISCORD_WEBHOOK.";n?console.log(JSON.stringify({ok:!1,error:c})):m(c),n||(console.log(),console.log(` ${v("To configure permanently, create inferno/notify.json:")}`),console.log(` ${N('{ "slack": "https://hooks.slack.com/...", "discord": "https://discord.com/api/webhooks/..." }')}`),console.log()),process.exit(1)}const u=(()=>{for(const c of["contract.json","capabilities.json"]){const s=g.join(o,c);if(k.existsSync(s))try{return JSON.parse(k.readFileSync(s,"utf8"))}catch{}}return{}})(),p=w("check --json",e),f=w("diff --json",e),d=R(p,f,u);if(l&&!d.added.length&&!d.removed.length&&!d.changed.length){n?console.log(JSON.stringify({ok:!0,skipped:!0,reason:"no capability changes"})):S("No capability changes \u2014 skipping notification.");return}const h=[];if(r.slack){const c=D(d);if(i)n||(S("Slack payload (dry run):"),console.log(JSON.stringify(c,null,2))),h.push({platform:"slack",ok:!0,dryRun:!0});else try{const s=await j(r.slack,c),y=s.status>=200&&s.status<300;n||(y?O("Slack notification sent"):m(`Slack returned ${s.status}`)),h.push({platform:"slack",ok:y,status:s.status})}catch(s){n||m(`Slack failed: ${s.message}`),h.push({platform:"slack",ok:!1,error:s.message})}}if(r.discord){const c=L(d);if(i)n||(S("Discord payload (dry run):"),console.log(JSON.stringify(c,null,2))),h.push({platform:"discord",ok:!0,dryRun:!0});else try{const s=await j(r.discord,c),y=s.status>=200&&s.status<300;n||(y?O("Discord notification sent"):m(`Discord returned ${s.status}`)),h.push({platform:"discord",ok:y,status:s.status})}catch(s){n||m(`Discord failed: ${s.message}`),h.push({platform:"discord",ok:!1,error:s.message})}}n&&console.log(JSON.stringify({ok:h.every(c=>c.ok),results:h,summary:d}))}export{K as notifyCommand};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{execSync as k}from"node:child_process";import*as S from"node:fs";import*as m from"node:path";import{header as w,section as y,ok as d,warn as E,fail as N,gray as $,cyan as g,yellow as _}from"../ui/output.mjs";const D=["src/","frontend/","backend/","app/","pages/","components/","lib/","api/","server/","Controllers/"];function A(n){return k(n,{stdio:["ignore","pipe","pipe"]}).toString("utf8").trim()}function I(n,t=null){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return t}}function F(n,t=""){try{return S.readFileSync(n,"utf8")}catch{return t}}function H(n,t){const a=A(n&&t?`git diff --name-only ${n}..${t}`:"git diff --name-only HEAD");return a?a.split(`
|
|
2
|
-
`).map(r=>r.trim()).filter(Boolean):[]}function O(n){const t=m.join(n,"inferno"),a=I(m.join(t,"contract.json"),{capabilities:[]}),r=I(m.join(t,"capabilities.json"),{capabilities:[]}),c=new Map((r.capabilities||[]).map(e=>[e.id,e.title||e.id]));return(a.capabilities||[]).map(e=>{const i=c.get(e)||e,l=new Set(`${e} ${i}`.replace(/([A-Z])/g," $1").toLowerCase().split(/[^a-z0-9]+/).filter(s=>s.length>=4));return{id:e,title:i,keywords:Array.from(l)}})}function R(n,t){const a=O(n),r=[];for(const c of a){const e=[];for(const i of t){const l=m.join(n,i),s=F(l,"").toLowerCase();s&&c.keywords.some(f=>s.includes(f))&&e.push(i)}e.length&&r.push({id:c.id,title:c.title,matchedFiles:e.slice(0,5)})}return r}async function x(n=[]){const t=n.includes("--json"),a=process.cwd(),r=process.env.BASE_SHA||null,c=process.env.HEAD_SHA||null;let e=[];try{e=H(r,c)}catch{const o={ok:!0,skipped:!0,reason:"no_git_available"};if(t){console.log(JSON.stringify(o,null,2));return}w("pr-impact"),E("git not available; cannot compute PR impact"),console.log();return}const i=e.filter(o=>D.some(C=>o.startsWith(C))),l=e.filter(o=>o.startsWith("inferno/")),s=R(a,i),f=i.length>0,h=f&&l.length===0,b=s.length>0?"high":f?"medium":"low",p=[];f&&p.push("CODE_CHANGED"),h&&p.push("INFERNO_NOT_UPDATED"),s.length>0&&p.push("CAPABILITY_HINT_MATCH"),p.length||p.push("NO_BEHAVIOR_SIGNAL");const u={ok:!h,base:r||"HEAD",head:c||"WORKTREE",changedFiles:e,changedCodeFiles:i,changedInfernoFiles:l,inferredBehaviorChange:f,impactedCapabilities:s,confidence:b,reasonCodes:p,recommendations:h?['Run infernoflow suggest "describe behavior change" and update inferno/',"Run infernoflow check --json"]:["Run infernoflow check --json to validate final state"]};t&&(console.log(JSON.stringify(u,null,2)),process.exit(u.ok?0:1)),w("pr-impact"),y("Diff Scope"),d(`Changed files: ${g(String(e.length))}`),d(`Code files: ${g(String(i.length))}`),d(`Inferno files: ${g(String(l.length))}`),y("Capability Impact"),s.length===0?E("No capability hints matched changed code files"):s.forEach(o=>{console.log(` ${g("\u2022")} ${o.id} ${$(`(${o.title})`)}`),o.matchedFiles.slice(0,3).forEach(C=>console.log(` ${$("- "+C)}`))}),y("Doc Sync"),h?N("Code changed but inferno/ was not updated","Run infernoflow suggest and then infernoflow check"):d("No immediate inferno drift signal from changed files"),d(`Confidence: ${g(b)}`),y("Suggested Next"),u.recommendations.forEach(o=>console.log(` ${_("\u2192")} ${o}`)),console.log(),process.exit(u.ok?0:1)}export{x as prImpactCommand};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import*as c from"node:fs";import*as f from"node:path";import{execSync as b}from"node:child_process";import{fileURLToPath as F}from"node:url";import{header as L,ok as o,fail as k,warn as l,info as u,done as O,bold as S,cyan as C,gray as n,green as T}from"../ui/output.mjs";const U=f.dirname(F(import.meta.url)),g=f.resolve(U,"../..");function a(r,i={}){return b(r,{cwd:g,encoding:"utf8",stdio:i.silent?["ignore","pipe","pipe"]:["inherit","inherit","inherit"],...i})}function x(r){return b(r,{cwd:g,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}function E(r,i){const e=r.split(".").map(Number);return i==="major"?(e[0]++,e[1]=0,e[2]=0):i==="minor"?(e[1]++,e[2]=0):e[2]++,e.join(".")}function h(){return new Date().toISOString().slice(0,10)}function H(r,i){if(!c.existsSync(r))return c.writeFileSync(r,`# Changelog \u2014 infernoflow
|
|
2
|
-
|
|
3
|
-
## ${i} \u2014 ${h()}
|
|
4
|
-
|
|
5
|
-
### Added
|
|
6
|
-
- Release ${i}
|
|
7
|
-
`),!0;let e=c.readFileSync(r,"utf8");if(/^## Unreleased/im.test(e))return e=e.replace(/^## Unreleased.*$/im,`## ${i} \u2014 ${h()}`),c.writeFileSync(r,e),!0;const p=/^# .+$/im;return p.test(e)?(e=e.replace(p,y=>`${y}
|
|
8
|
-
|
|
9
|
-
## ${i} \u2014 ${h()}
|
|
10
|
-
|
|
11
|
-
### Added
|
|
12
|
-
- Release ${i}
|
|
13
|
-
`),c.writeFileSync(r,e),!0):(c.writeFileSync(r,`## ${i} \u2014 ${h()}
|
|
14
|
-
|
|
15
|
-
### Added
|
|
16
|
-
- Release ${i}
|
|
17
|
-
|
|
18
|
-
${e}`),!0)}function _(){try{return x("git status --porcelain").length>0}catch{return!1}}function J(){try{return x("git config user.email"),!0}catch{return!1}}async function M(r){const i=r.slice(1),e=i.includes("--dry-run"),p=i.includes("--skip-build"),y=i.includes("--skip-tests"),A=i.includes("--skip-push"),j=i.includes("--tag"),N=i.includes("--yes")||i.includes("-y"),G=i.indexOf("--bump"),m=G!==-1&&i[G+1]||"patch";["patch","minor","major"].includes(m)||(console.error(` Invalid --bump value: ${m}. Must be patch, minor, or major.`),process.exit(1)),L("infernoflow publish"),e&&l("DRY RUN \u2014 no files will be written, no commands executed");const v=f.join(g,"package.json"),$=JSON.parse(c.readFileSync(v,"utf8")),w=$.version,t=E(w,m);if(console.log(),console.log(` ${n("current")} ${S(w)}`),console.log(` ${n("new ")} ${S(T(t))} ${n("("+m+" bump)")}`),console.log(),!N&&!e){process.stdout.write(` Publish ${S(C("infernoflow@"+t))} to npm? [y/N] `);let s=!1;try{const d=b("bash -c 'read -r ans </dev/tty; echo $ans'",{encoding:"utf8",stdio:["inherit","pipe","inherit"]}).trim().toLowerCase();s=d==="y"||d==="yes"}catch{s=!1}console.log(),s||(console.log(n(` Aborted.
|
|
19
|
-
`)),process.exit(0))}u(`Bumping package.json ${n(w+" \u2192 "+t)}`),e?o(n("[dry] would write package.json")):($.version=t,c.writeFileSync(v,JSON.stringify($,null,4)+`
|
|
20
|
-
`),o("package.json updated"));const R=f.join(g,"CHANGELOG.md");if(u("Updating CHANGELOG.md"),e?o(n("[dry] would update CHANGELOG.md")):(H(R,t),o("CHANGELOG.md updated")),p)l("Skipping build (--skip-build)");else if(u("Running build "+n("node build.mjs")),e)o(n("[dry] would run: node build.mjs"));else try{a("node build.mjs",{silent:!1}),o("Build succeeded")}catch(s){k("Build failed",s.message),process.exit(1)}if(y)l("Skipping tests (--skip-tests)");else if(u("Running smoke tests"),e)o(n("[dry] would run: npm test"));else try{a("npm test",{silent:!1}),o("All smoke tests passed")}catch{k("Smoke tests failed","Fix tests or re-run with --skip-tests"),process.exit(1)}if(u(`Publishing to npm ${n("infernoflow@"+t)}`),e)o(n("[dry] would run: npm publish"));else try{a("npm publish",{silent:!1}),o(`Published infernoflow@${t}`)}catch(s){k("npm publish failed",s.message||"Check npm credentials"),l("Continuing to git commit despite publish failure")}if(u("Committing version bump"),e)o(n(`[dry] would commit: chore: release ${t}`));else try{a(`git add ${["package.json","CHANGELOG.md"].join(" ")}`,{silent:!1});const d=`chore: release ${t}`;a(`git commit -m "${d}"`,{silent:!1}),o(`Committed: ${n(d)}`)}catch(s){l(`Git commit failed: ${s.message}`),l('You can commit manually: git add package.json CHANGELOG.md && git commit -m "chore: release '+t+'"')}if(j)if(u(`Creating git tag ${n("v"+t)}`),e)o(n(`[dry] would tag: v${t}`));else try{a(`git tag v${t}`,{silent:!1}),o(`Tagged v${t}`)}catch(s){l(`Git tag failed: ${s.message}`)}if(A)l("Skipping push (--skip-push)");else if(u("Pushing to origin"),e)o(n("[dry] would run: git push"));else try{const s=j?`git push && git push origin v${t}`:"git push";a(s,{silent:!1}),o("Pushed to origin")}catch(s){l(`Git push failed: ${s.message}`),l("Push manually: git push")}console.log(),e?O(`Dry run complete \u2014 would have published infernoflow@${t}`):(O(`infernoflow@${t} published, committed, and pushed`),console.log(` ${C("npm:")} https://www.npmjs.com/package/infernoflow`),console.log(` ${C("git:")} ${n("chore: release "+t)}
|
|
21
|
-
`))}export{M as publishCommand};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import*as I from"node:fs";import*as $ from"node:path";import{execSync as P}from"node:child_process";import{bold as h,cyan as S,gray as c,green as E,yellow as y,red as x}from"../ui/output.mjs";function N(e,o){try{return P(e,{cwd:o,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}function C(e){try{return JSON.parse(I.readFileSync(e,"utf8"))}catch{return null}}function w(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(o=>o.length>2)}function _(e,o){const t=e.toLowerCase(),s=new Set;for(const n of o){const i=[...w(n.id||""),...w(n.name||""),...(n.tags||[]).flatMap(w)];if(t.includes((n.id||"").toLowerCase())){s.add(n.id);continue}i.filter(p=>p.length>3&&t.includes(p)).length>=2&&s.add(n.id)}return[...s]}function j(e,o=8e3){if(e.length<=o)return e;const t=Math.floor(o/2);return e.slice(0,t)+`
|
|
2
|
-
|
|
3
|
-
[\u2026 diff truncated \u2026]
|
|
4
|
-
|
|
5
|
-
`+e.slice(-t)}function O(e,o,t){const s=t.filter(i=>o.includes(i.id)).map(i=>` \u2022 ${i.id}: ${i.name}${i.description?" \u2014 "+i.description:""}`).join(`
|
|
6
|
-
`);return`You are a senior software architect reviewing a code change for capability drift.
|
|
7
|
-
|
|
8
|
-
${o.length>0?`Affected capabilities detected:
|
|
9
|
-
${s}`:"No specific capabilities were matched \u2014 review the entire contract."}
|
|
10
|
-
|
|
11
|
-
Git diff:
|
|
12
|
-
\`\`\`diff
|
|
13
|
-
${j(e)}
|
|
14
|
-
\`\`\`
|
|
15
|
-
|
|
16
|
-
Write a concise capability impact summary covering:
|
|
17
|
-
1. Which capabilities are changed, added, or removed
|
|
18
|
-
2. Whether the contract (capabilities.json) needs updating
|
|
19
|
-
3. Any risks or side-effects (breaking changes, auth/security concerns, API contract violations)
|
|
20
|
-
4. Recommended follow-up actions (one sentence each)
|
|
21
|
-
|
|
22
|
-
Keep the tone professional and brief. Use bullet points only where genuinely helpful.
|
|
23
|
-
Do NOT repeat the diff back.`}function R(e,o,t,s){if(console.log(),console.log(h(S(" \u2726 Capability Impact Review"))),console.log(c(` Source: ${s}`)),console.log(),e.length===0)console.log(y(" No capabilities directly matched \u2014 reviewing full diff."));else{console.log(h(" Affected capabilities:"));for(const n of e){const i=t.find(l=>l.id===n);console.log(` ${E("\u25B8")} ${n}${i?c(" \u2014 "+i.name):""}`)}}console.log(),console.log(h(" AI Impact Summary")),console.log(c(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const n of o.split(`
|
|
24
|
-
`))console.log(" "+n);console.log()}async function L(e){const o=e||[],t=o.includes("--dry-run"),s=o.includes("--json"),n=o.includes("--unstaged"),i=o.includes("--last"),l=process.cwd(),p=$.join(l,"inferno"),b=$.join(p,"capabilities.json");I.existsSync(b)||(console.error(x("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));const f=C(b);(!Array.isArray(f)||f.length===0)&&(console.log(y("No capabilities found \u2014 nothing to review.")),process.exit(0));let g,a;i?(g="git diff HEAD~1",a="last commit (HEAD~1)"):n?(g="git diff",a="unstaged changes"):(g="git diff --staged",a="staged changes");let d=N(g,l);!d&&!i&&!n&&(d=N("git diff",l),a="unstaged changes (no staged changes found)"),d||(console.log(y("No changes found to review.")),console.log(c(" Tip: stage some files first (`git add -p`) or use --last / --unstaged")),process.exit(0));const u=_(d,f),A=O(d,u,f);t&&(console.log(c("\u2500\u2500 Prompt (--dry-run) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(A),process.exit(0)),s||process.stdout.write(c(" Calling AI provider\u2026"));let r=null;try{const{callAI:m}=await import("../ai/providerRouter.mjs");r=await m(A,{cwd:l,maxTokens:600})}catch{}if(s||process.stdout.write("\r"+" ".repeat(30)+"\r"),!r){console.log(),console.log(y(" \u26A0 No AI provider available.")),console.log(c(" Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, or OPENROUTER_API_KEY,")),console.log(c(" or run Ollama locally. See `infernoflow doctor` for details.")),console.log(),console.log(h(" Affected capabilities (unanswered):"));for(const m of u)console.log(` \u25B8 ${m}`);console.log(),process.exit(0)}const v=r.text||"(empty response)";s?console.log(JSON.stringify({source:a,provider:r.provider,model:r.model,affectedCapabilities:u,summary:v},null,2)):(R(u,v,f,a),console.log(c(` Provider: ${r.provider} Model: ${r.model}`)),console.log())}export{L as reviewCommand};
|