infernoflow 0.43.12 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/bin/infernoflow.mjs +30 -33
  2. package/dist/lib/amp/io.mjs +8 -8
  3. package/dist/lib/cleanTree.mjs +12 -0
  4. package/dist/lib/commands/ai.mjs +2 -2
  5. package/dist/lib/commands/amp.mjs +4 -4
  6. package/dist/lib/commands/ask.mjs +2 -2
  7. package/dist/lib/commands/context.mjs +18 -18
  8. package/dist/lib/commands/doctor.mjs +2 -3
  9. package/dist/lib/commands/init.mjs +31 -32
  10. package/dist/lib/commands/log.mjs +13 -19
  11. package/dist/lib/commands/recap.mjs +3 -3
  12. package/dist/lib/commands/refresh.mjs +5 -0
  13. package/dist/lib/commands/status.mjs +6 -7
  14. package/dist/lib/commands/switch.mjs +5 -5
  15. package/dist/lib/commands/sync.mjs +41 -0
  16. package/dist/lib/git/branch.mjs +2 -0
  17. package/dist/lib/projectRoot.mjs +1 -0
  18. package/dist/lib/ruleFiles.mjs +9 -8
  19. package/dist/lib/upgradeCheck.mjs +1 -1
  20. package/dist/templates/cursor/inferno-mcp-server.mjs +170 -325
  21. package/package.json +13 -5
  22. package/dist/lib/commands/changelog.mjs +0 -21
  23. package/dist/lib/commands/ci.mjs +0 -3
  24. package/dist/lib/commands/claudeMd.mjs +0 -116
  25. package/dist/lib/commands/coverage.mjs +0 -2
  26. package/dist/lib/commands/demo.mjs +0 -113
  27. package/dist/lib/commands/diff.mjs +0 -5
  28. package/dist/lib/commands/explain.mjs +0 -8
  29. package/dist/lib/commands/feedback.mjs +0 -12
  30. package/dist/lib/commands/graph.mjs +0 -76
  31. package/dist/lib/commands/impact.mjs +0 -2
  32. package/dist/lib/commands/implement.mjs +0 -7
  33. package/dist/lib/commands/monorepo.mjs +0 -4
  34. package/dist/lib/commands/notify.mjs +0 -4
  35. package/dist/lib/commands/prImpact.mjs +0 -2
  36. package/dist/lib/commands/publish.mjs +0 -21
  37. package/dist/lib/commands/review.mjs +0 -24
  38. package/dist/lib/commands/run.mjs +0 -10
  39. package/dist/lib/commands/scaffold.mjs +0 -124
  40. package/dist/lib/commands/scan.mjs +0 -42
  41. package/dist/lib/commands/stability.mjs +0 -2
  42. package/dist/lib/commands/stats.mjs +0 -4
  43. package/dist/lib/commands/suggest.mjs +0 -62
  44. package/dist/lib/commands/syncAuto.mjs +0 -1
  45. package/dist/lib/commands/test.mjs +0 -6
  46. package/dist/lib/commands/theme.mjs +0 -18
  47. package/dist/lib/commands/upgrade.mjs +0 -20
  48. package/dist/lib/commands/watch.mjs +0 -7
  49. package/dist/lib/commands/why.mjs +0 -4
@@ -1,10 +0,0 @@
1
- import*as a from"node:fs";import*as p from"node:path";import{execFileSync as T}from"node:child_process";import{fileURLToPath as L}from"node:url";import{generateWithLocalModel as $}from"../ai/localProvider.mjs";import{resolveProvider as q}from"../ai/providerRouter.mjs";import{buildPrompt as W,loadSuggestContext as G,parseSuggestionJson as O,validateSuggestion as M,detectSuggestionConflicts as V,applyChanges as K}from"./suggest.mjs";import{header as U,section as B,ok as x,warn as F,fail as v,info as h,gray as j}from"../ui/output.mjs";const z=L(import.meta.url),H=p.dirname(z),Q=p.resolve(H,"..","..","bin","infernoflow.mjs");function D(t){try{const e=T(process.execPath,[Q,...t],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return{ok:!0,data:JSON.parse(e)}}catch(e){const n=e?.stdout?.toString?.()||"";try{return{ok:!1,data:JSON.parse(n)}}catch{return{ok:!1,data:{ok:!1,errors:["command_failed"]}}}}}function m(t,e,n,o,s={}){const r={ts:new Date().toISOString(),stage:n,status:o,...s};if(e.push(r),t)return;const c=`${n}: ${o}`;o==="ok"?x(c):o==="warn"?F(c):o==="fail"?v(c):h(c)}function X(t){const e=p.join(t,"inferno"),n=[],o=r=>{for(const c of a.readdirSync(r,{withFileTypes:!0})){const f=p.join(r,c.name);c.isDirectory()?o(f):n.push(f)}};a.existsSync(e)&&o(e);const s=new Map;return n.forEach(r=>s.set(r,a.readFileSync(r,"utf8"))),s}function Y(t,e){const n=p.join(t,"inferno");if(a.existsSync(n)){const o=[],s=r=>{for(const c of a.readdirSync(r,{withFileTypes:!0})){const f=p.join(r,c.name);c.isDirectory()?s(f):o.push(f)}};s(n),o.forEach(r=>{e.has(r)||a.unlinkSync(r)})}for(const[o,s]of e.entries())a.mkdirSync(p.dirname(o),{recursive:!0}),a.writeFileSync(o,s,"utf8")}function Z(t,e){const n=p.join(t,"inferno","runs");a.mkdirSync(n,{recursive:!0});const o=p.join(n,`${Date.now()}.json`);return a.writeFileSync(o,JSON.stringify(e,null,2)+`
2
- `,"utf8"),p.relative(t,o)}function A(t,e,n=null){const o=t.indexOf(e);return o!==-1&&t[o+1]&&!t[o+1].startsWith("-")?t[o+1]:n}function ee(t){const e=new Set(["--provider","--ide"]),n=[];for(let o=1;o<t.length;o++){const s=t[o];if(s.startsWith("-")){e.has(s)&&(o+=1);continue}n.push(s)}return n.join(" ").trim()}function oe(t,e){return{summary:`Prompt fallback only: ${t}`,newCapabilities:[],removedCapabilities:[],updatedScenarios:[],changelogEntry:`- Prompt fallback mode for task: ${t} (no automatic contract mutation).`,_meta:{actionRequired:!0,nextStep:"Run infernoflow suggest or provide an agent bridge for automatic apply.",capabilitiesCount:(e?.capabilities||[]).length}}}async function te(t){if(process.env.INFERNO_AGENT_MOCK_RESPONSE)return process.env.INFERNO_AGENT_MOCK_RESPONSE;const e=process.env.INFERNO_AGENT_RESPONSE_FILE?process.env.INFERNO_AGENT_RESPONSE_FILE:p.join(process.cwd(),"inferno","agent-response.json");if(a.existsSync(e)){const r=a.readFileSync(e,"utf8");return a.unlinkSync(e),r}const n=p.join(process.cwd(),"inferno"),o=p.join(n,"agent-prompt.md");a.existsSync(o)&&a.unlinkSync(o),a.writeFileSync(o,t,"utf8"),process.stderr.write(`
3
- \u2139 Prompt written to inferno/agent-prompt.md
4
- `),process.stderr.write(` \u2192 Open it, paste into Cursor or Claude
5
- `),process.stderr.write(` \u2192 Save the JSON reply to: inferno/agent-response.json
6
- `),process.stderr.write(` Waiting up to 5 minutes...
7
-
8
- `);const s=Date.now()+3e5;for(;Date.now()<s;)if(await new Promise(r=>setTimeout(r,1e3)),a.existsSync(e)){const r=a.readFileSync(e,"utf8");return a.unlinkSync(e),process.stderr.write(` \u2714 Response received
9
-
10
- `),r}throw new Error("ide_agent_bridge_timeout")}async function le(t=[]){const e=t.includes("--json"),n=t.includes("--dry-run"),o=t.includes("--no-rollback"),s=(A(t,"--provider","auto")||"auto").toLowerCase(),r=(A(t,"--ide","auto")||"auto").toLowerCase(),c=ee(t)||"sync check",f=process.cwd(),l=[],y=[];e||U("run"),m(e,l,"init","info",{task:c,dryRun:n,noRollback:o});const b=D(["pr-impact","--json"]);m(e,l,"detect",b.data?.ok?"ok":"warn",{confidence:b.data?.confidence||"low"});const d=await q(s,r);if(y.push(...d.reasonCodes||[]),d.error==="agent_unavailable"){const i={ok:!1,error:"agent_unavailable",providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable,reasonCodes:y,events:l};e?console.log(JSON.stringify(i,null,2)):v("provider agent unavailable","Use --provider auto|local|prompt"),process.exit(1)}m(e,l,"route","ok",{providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable});const g=G(f);g?.contract||(e?console.log(JSON.stringify({ok:!1,error:"inferno_missing",events:l},null,2)):v("inferno/ missing or invalid"),process.exit(1));const _=W({description:c,contract:g.contract,capabilities:g.capabilities,scenarios:g.scenarios});let u;try{if(d.providerResolved==="local"){const i=await $(_);u=O(i)}else if(d.providerResolved==="agent"){const i=await te(_);u=O(i)}else u=oe(c,g.contract)}catch(i){const J={ok:!1,error:"proposal_failed",reason:String(i.message||i),reasonCodes:y,events:l};e?console.log(JSON.stringify(J,null,2)):v("proposal generation failed",i.message),process.exit(1)}m(e,l,"propose","ok",{newCapabilities:(u.newCapabilities||[]).length,removedCapabilities:(u.removedCapabilities||[]).length});const R=d.providerResolved==="prompt"?[]:M(u),E=d.providerResolved==="prompt"?[]:V(g.contract,u);if(R.length||E.length){const i={ok:!1,error:"invalid_suggestion",issues:[...R,...E],events:l};e?console.log(JSON.stringify(i,null,2)):v("suggestion invalid",i.issues[0]),process.exit(1)}const P=X(f);let w=!1,S=!1,N=!1;try{n?m(e,l,"apply","info",{dryRun:!0}):d.providerResolved==="prompt"?m(e,l,"apply","warn",{skipped:!0,reason:"prompt_fallback_requires_manual_step"}):(S=K({cwd:f,contract:g.contract,capabilities:g.capabilities,suggestion:u,version:g.version,quiet:e}),m(e,l,"apply","ok",{changed:S}));let i=D(["check","--json"]);if(process.env.INFERNO_TEST_FORCE_VALIDATE_FAIL==="1"&&(i={ok:!1,data:{ok:!1,errors:["forced_validation_failure"]}}),!i.ok||!i.data?.ok)throw new Error(`validation_failed:${(i.data?.errors||[]).join(",")}`);N=!0,m(e,l,"validate","ok")}catch(i){m(e,l,"validate","fail",{reason:String(i.message||i)}),!n&&!o&&(Y(f,P),w=!0,m(e,l,"rollback","ok"))}const I={task:c,dryRun:n,noRollback:o,rolledBack:w,applyChanged:S,suggestionSummary:u.summary||"",touchedCapabilities:[...(u.newCapabilities||[]).map(i=>i.id),...u.removedCapabilities||[]],events:l},C=Z(f,I),k={ok:N,mode:"run",task:c,dryRun:n,providerRequested:s,providerResolved:d.providerResolved,ideDetected:d.ideDetected,agentAvailable:d.agentAvailable,reasonCodes:Array.from(new Set(y)),rolledBack:w,applyChanged:S,artifactPath:C,events:l};e&&(console.log(JSON.stringify(k,null,2)),process.exit(k.ok?0:1)),B("Result"),h(`task: ${j(c)}`),h(`artifact: ${j(C)}`),k.ok?x("run completed"):F("run rolled back after failed validation"),console.log(),process.exit(k.ok?0:1)}export{le as runCommand};
@@ -1,124 +0,0 @@
1
- import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as T,gray as l,green as O,yellow as Q,red as b}from"../ui/output.mjs";function P(t){try{return JSON.parse(p.readFileSync(t,"utf8"))}catch{return null}}function z(t,e){p.writeFileSync(t,JSON.stringify(e,null,2)+`
2
- `)}function d(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}function fe(t){return t.replace(/([A-Z]+)([A-Z][a-z])/g,"$1-$2").replace(/([a-z\d])([A-Z])/g,"$1-$2").toLowerCase().replace(/[_\s]+/g,"-").replace(/^-+|-+$/g,"")}function X(t){if(!t||typeof t!="string")return null;const e=t.trim();if(!/^[A-Za-z][A-Za-z0-9 _-]*$/.test(e))return null;const n=e.replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/([a-z\d])([A-Z])/g,"$1 $2").split(/[-_\s]+/).filter(Boolean);return n.length?n.map(r=>r.charAt(0).toUpperCase()+r.slice(1).toLowerCase()).join(""):null}function R(t){const e=d(t);return e.charAt(0).toLowerCase()+e.slice(1)}function Y(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function ee(t){const e=t.split(/[-_]/);if(e.length===1)return R(t);const n=["auth","login","logout","register","refresh","validate","verify","process","refund","charge","send","fetch","create","update","delete","get","list","search","sync","import","export","scan","check","notify"],r=e[e.length-1],i=e[0];if(n.includes(i)){const o={auth:"authenticate",get:"get",list:"list",send:"send",check:"check",notify:"notify"}[i]||i,u=e.slice(1).map((g,v)=>v===0?g.charAt(0).toUpperCase()+g.slice(1):g).join("");return o+u.charAt(0).toUpperCase()+u.slice(1)}if(n.includes(r)){const s=e.slice(0,-1).map((o,u)=>u===0?o.charAt(0).toUpperCase()+o.slice(1):o).join("");return r+s}return R(t)}function te(t,e,n){if(t?.capabilities?.length){const s=t.capabilities.flatMap(o=>o.codeAnalysis?.sourceFiles||[]).map(o=>a.extname(o));if(s.filter(o=>o===".ts").length>s.filter(o=>o===".js").length)return"ts";if(s.includes(".py"))return"py";if(s.includes(".go"))return"go";if(s.some(o=>o===".js"||o===".mjs"))return"js"}const r=e?.language||e?.lang;return r?r.toLowerCase().replace("javascript","js").replace("typescript","ts"):p.existsSync(a.join(n,"tsconfig.json"))?"ts":p.existsSync(a.join(n,"pyproject.toml"))?"py":p.existsSync(a.join(n,"go.mod"))?"go":"js"}function ne(t,e){if(!t?.capabilities?.length)return null;const n=t.capabilities.flatMap(s=>s.codeAnalysis?.sourceFiles||[]);if(!n.length)return null;const r={};for(const s of n){const o=a.dirname(s).split("/")[0];r[o]=(r[o]||0)+1}const i=Object.entries(r).sort((s,o)=>o[1]-s[1])[0];return i?i[0]:null}function oe(t){if(!t?.capabilities?.length)return[];const e=t.capabilities.flatMap(n=>n.codeAnalysis?.services||[]);return[...new Set(e)]}function re(t,e,n,r,i){const s=d(t),o=`${s}Error`,u=J("ts",i);return`/**
3
- * ${e}
4
- *
5
- * ${n}
6
- *
7
- * @capability ${t}
8
- * @stability experimental
9
- */
10
- ${u}
11
-
12
- // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
13
-
14
- export class ${o} extends Error {
15
- constructor(message: string, public readonly code?: string) {
16
- super(message);
17
- this.name = "${o}";
18
- }
19
- }
20
-
21
- // \u2500\u2500 types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
22
-
23
- export interface ${s}Input {
24
- // TODO: define input fields
25
- }
26
-
27
- export interface ${s}Result {
28
- // TODO: define result fields
29
- success: boolean;
30
- }
31
-
32
- // \u2500\u2500 implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
33
-
34
- /**
35
- * ${r} \u2014 primary entry point for ${e}.
36
- * TODO: implement this function.
37
- */
38
- export async function ${r}(input: ${s}Input): Promise<${s}Result> {
39
- // TODO: implement
40
- throw new ${o}("Not implemented yet");
41
- }
42
- `}function se(t,e,n,r,i){const o=`${d(t)}Error`,u=J("js",i);return`/**
43
- * ${e}
44
- *
45
- * ${n}
46
- *
47
- * @capability ${t}
48
- * @stability experimental
49
- */
50
- ${u}
51
-
52
- // \u2500\u2500 errors \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
53
-
54
- export class ${o} extends Error {
55
- constructor(message, code) {
56
- super(message);
57
- this.name = "${o}";
58
- this.code = code;
59
- }
60
- }
61
-
62
- // \u2500\u2500 implementation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
63
-
64
- /**
65
- * ${r} \u2014 primary entry point for ${e}.
66
- * TODO: implement this function.
67
- *
68
- * @param {object} input
69
- * @returns {Promise<object>}
70
- */
71
- export async function ${r}(input = {}) {
72
- // TODO: implement
73
- throw new ${o}("Not implemented yet");
74
- }
75
- `}function ie(t,e,n,r){const i=d(t);return`"""
76
- ${e}
77
-
78
- ${n}
79
-
80
- capability: ${t}
81
- stability: experimental
82
- """
83
-
84
- from typing import Any
85
-
86
-
87
- class ${i}Error(Exception):
88
- """Raised when ${e} operations fail."""
89
- def __init__(self, message: str, code: str | None = None):
90
- super().__init__(message)
91
- self.code = code
92
-
93
-
94
- async def ${r.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"")}(input: dict[str, Any]) -> dict[str, Any]:
95
- """Primary entry point for ${e}.
96
-
97
- TODO: implement this function.
98
- """
99
- raise ${i}Error("Not implemented yet")
100
- `}function ce(t,e,n,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
101
- //
102
- // ${n}
103
- //
104
- // capability: ${t}
105
- // stability: experimental
106
- package ${i}
107
-
108
- import "errors"
109
-
110
- // Err${d(t)} is returned when ${e} operations fail.
111
- var Err${d(t)} = errors.New("${t}: operation failed")
112
-
113
- // ${d(r)} is the primary entry point for ${e}.
114
- // TODO: implement this function.
115
- func ${d(r)}(input map[string]any) (map[string]any, error) {
116
- return nil, Err${d(t)}
117
- }
118
- `}function J(t,e){if(!e.length)return"";const n=[];if(t==="ts"||t==="js"){const r={stripe:"// import Stripe from 'stripe';",postgres:"// import { Pool } from 'pg';",mysql:"// import mysql from 'mysql2/promise';",redis:"// import { createClient } from 'redis';",s3:"// import { S3Client } from '@aws-sdk/client-s3';",sendgrid:"// import sgMail from '@sendgrid/mail';",twilio:"// import twilio from 'twilio';",openai:"// import OpenAI from 'openai';"};for(const i of e){const s=r[i.toLowerCase()];s&&n.push(s)}}return n.length?n.join(`
119
- `)+`
120
- `:""}function le(t,e,n){return{scenarioId:`${t}-happy-path`,description:`Happy path for ${e}`,capabilitiesCovered:[t],createdAt:new Date().toISOString(),steps:[{step:1,action:`Call ${n} with valid input`,expected:"Returns success result"},{step:2,action:`Call ${n} with invalid input`,expected:"Throws appropriate error"}]}}function ae({id:t,filePath:e,scenarioPath:n,lang:r,fn:i,dryRun:s}){console.log(),console.log(U(` \u{1F30A} ${O(t)}`)),console.log(l(" stability: experimental \u2014 free to evolve")),console.log(),console.log(l(" Generated:")),console.log(` ${O("+")} ${T(e)} ${l(`(${r} source skeleton)`)}`),console.log(` ${O("+")} ${T("inferno/capabilities.json")} ${l("(capability registered)")}`),console.log(` ${O("+")} ${T(n)} ${l("(placeholder scenario)")}`),console.log(),s?console.log(Q(" [dry-run] \u2014 no files were written")):(console.log(l(" Next steps:")),console.log(l(` 1. Implement ${i}() in ${e}`)),console.log(l(" 2. Run: infernoflow scan \u2014 to extract call graph")),console.log(l(" 3. Run: infernoflow graph \u2014 to see dependencies")),console.log(l(" 4. Run: infernoflow check \u2014 to validate contract"))),console.log()}async function ue(t){const e=(t||[]).slice(1),n=e.includes("--dry-run"),r=e.includes("--json"),i=e.indexOf("--lang"),s=i!==-1?e[i+1]:null,o=e.indexOf("--dir"),u=o!==-1?e[o+1]:null,g=e.indexOf("--description"),v=g!==-1?e[g+1]:null,M=new Set([i+1,o+1,g+1].filter(f=>f>0));let c=e.find((f,A)=>!f.startsWith("--")&&!M.has(A));c||(console.error(b("\u2717 Usage: infernoflow scaffold <capability-id> [--dir <src>] [--lang ts|js|py|go] [--dry-run] [--json]")),console.error(l(" Example: infernoflow scaffold CreateItem (or payment-refund \u2014 both work)")),process.exit(1));const I=c,F=X(I);F||(console.error(b(`\u2717 Invalid capability ID: "${I}"`)),console.error(l(' Try: CreateItem, payment-refund, user_auth, or "Send Email".')),process.exit(1)),c=F;const h=process.cwd(),D=a.join(h,"inferno"),_=a.join(D,"capabilities.json");p.existsSync(_)||(console.error(b("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let w=[];const C=P(_);C&&(w=Array.isArray(C)?C:C.capabilities||[]),w.some(f=>f.id===c||f.id===I)&&(console.error(b(`\u2717 Capability "${c}" already exists in capabilities.json`)),console.error(l(" Use a different ID, or run: infernoflow why "+c)),process.exit(1));const E=P(a.join(D,"scan.json")),q=P(a.join(D,"developer-profile.json")),x=s||te(E,q,h),B=u||ne(E,h)||"src",G={ts:".ts",js:".js",py:".py",go:".go"}[x]||".js",m=Y(c),j=v||`TODO: describe ${m}`,y=ee(c),Z=oe(E);let $;x==="ts"?$=re(c,m,j,y,Z):x==="py"?$=ie(c,m,j,y):x==="go"?$=ce(c,m,j,y):$=se(c,m,j,y,Z);const H=R(c)+G,S=a.join(B,H),N=a.join(h,S),k=a.join("inferno","scenarios",`${c}.json`),L=a.join(h,k),K=le(c,m,y),V={id:c,name:m,description:j,stability:"experimental",since:new Date().toISOString().slice(0,10)};if(r){console.log(JSON.stringify({capId:c,name:m,stability:"experimental",lang:x,filePath:S,scenarioPath:k,primaryFn:y,dryRun:n,code:$},null,2));return}if(console.log(l(`
121
- infernoflow scaffold \u2192 ${U(c)}`)),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")),!n){const f=a.dirname(N);p.existsSync(f)||p.mkdirSync(f,{recursive:!0}),p.existsSync(N)&&(console.error(b(` \u2717 File already exists: ${S}`)),console.error(l(" Delete it first or choose a different --dir")),process.exit(1)),p.writeFileSync(N,$,"utf8"),w.push(V),z(_,w);const A=a.join(h,"inferno","scenarios");p.existsSync(A)||p.mkdirSync(A,{recursive:!0}),p.existsSync(L)||z(L,K)}const W=$.split(`
122
- `).slice(0,12).map(f=>" "+f).join(`
123
- `);console.log(l(`
124
- Preview:`)),console.log(l(W)),console.log(l(" ...")),ae({id:c,filePath:S,scenarioPath:k,lang:x,fn:y,dryRun:n})}export{ue as scaffoldCommand};
@@ -1,42 +0,0 @@
1
- import*as S from"node:fs";import*as d from"node:path";import{createRequire as V}from"node:module";import{execSync as W}from"node:child_process";import{bold as P,cyan as G,gray as u,green as k,yellow as A,red as T}from"../ui/output.mjs";const z=V(import.meta.url),K=["/usr/local/lib/node_modules_global/lib/node_modules/typescript","/usr/lib/node_modules/typescript",d.join(process.env.HOME||"",".npm-global/lib/node_modules/typescript")];function Q(){for(const e of K)try{return z(d.join(e,"lib/typescript.js"))}catch{}try{return z("typescript")}catch{}return null}const a=Q(),X=[{service:"stripe",patterns:["stripe","Stripe","createPaymentIntent","charges.create"]},{service:"sendgrid",patterns:["sendgrid","@sendgrid","sgMail","sendgrid.send"]},{service:"ses",patterns:["SES","ses.sendEmail","aws-sdk/ses","nodemailer"]},{service:"s3",patterns:["S3","s3.upload","s3.getObject","PutObjectCommand","@aws-sdk/s3"]},{service:"redis",patterns:["redis","Redis","ioredis","createClient"]},{service:"jwt",patterns:["jwt","jsonwebtoken","sign(","verify(","decode("]},{service:"bcrypt",patterns:["bcrypt","argon2","scrypt","hashSync","compare("]},{service:"prisma",patterns:["prisma.","PrismaClient","@prisma/client"]},{service:"mongoose",patterns:["mongoose",".save()",".findOne(",".aggregate("]},{service:"postgres",patterns:["pg","Pool(","Client(","query(","postgres("]},{service:"mysql",patterns:["mysql","mysql2","createConnection"]},{service:"graphql",patterns:["graphql","gql`","ApolloServer","GraphQLSchema"]},{service:"firebase",patterns:["firebase","firestore","initializeApp"]},{service:"twilio",patterns:["twilio","Twilio(","messages.create"]},{service:"openai",patterns:["openai","OpenAI(","createCompletion","chat.completions"]}];function _(e){const t=new Set;for(const{service:n,patterns:s}of X)s.some(o=>e.includes(o))&&t.add(n);return[...t]}const Y=[/\.(find|findOne|findMany|findById|findAll)\s*\(/g,/\.(create|insert|insertOne|insertMany|save)\s*\(/g,/\.(update|updateOne|updateMany|updateById|upsert)\s*\(/g,/\.(delete|deleteOne|deleteMany|remove|destroy)\s*\(/g,/\.(query|execute|raw)\s*\(/g,/\.(aggregate|groupBy|count|sum)\s*\(/g,/db\.\w+\s*\(/g,/prisma\.\w+\.\w+\s*\(/g];function F(e){const t=new Set;for(const n of Y){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,10)}const ee=[/fetch\s*\(/g,/axios\.(get|post|put|patch|delete)\s*\(/g,/http\.(get|post|request)\s*\(/g,/got\.(get|post|put|delete)\s*\(/g,/request\.(get|post|put|delete)\s*\(/g,/\$http\.(get|post|put|delete)\s*\(/g];function E(e){const t=new Set;for(const n of ee){const s=new RegExp(n.source,"g");let o;for(;(o=s.exec(e))!==null;)t.add(o[0].replace(/\s*\($/,"()"))}return[...t].slice(0,8)}const se=[{tag:"button",re:/<button[\s\S]*?on(?:Click|Press|Submit)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/button>/gi},{tag:"input",re:/<input[\s\S]*?on(?:Change|Input|Blur)\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?\/?>/gi},{tag:"form",re:/<form[\s\S]*?onSubmit\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi},{tag:"link",re:/<a[\s\S]*?onClick\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>([\s\S]*?)<\/a>/gi},{tag:"select",re:/<select[\s\S]*?onChange\s*=\s*\{?([A-Za-z_$][\w$]*)\}?[\s\S]*?>/gi}];function te(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=[];for(const{tag:s,re:o}of se){const i=new RegExp(o.source,o.flags);let r;for(;(r=i.exec(e))!==null;){const l=r[1],f=(r[2]||"").replace(/\{[^}]*\}/g,"").replace(/\s+/g," ").trim().slice(0,40);if(n.push({tag:s,handler:l,label:f||l,file:t}),n.length>=50)return n}}return n}const ne=[/export\s+default\s+function\s+([A-Z][\w$]*)/g,/export\s+function\s+([A-Z][\w$]*)/g,/^function\s+([A-Z][\w$]*)\s*\([\s\S]*?\)\s*\{[\s\S]*?return\s*\(/gm,/(?:export\s+)?const\s+([A-Z][\w$]*)\s*=\s*(?:\([\s\S]*?\)|[\w$]+)\s*=>\s*[({<]/g];function oe(e,t){if(!/\.(jsx|tsx|vue|svelte)$/i.test(t))return[];const n=new Set;for(const r of ne){const l=new RegExp(r.source,r.flags);let f;for(;(f=l.exec(e))!==null;)f[1]&&n.add(f[1])}const s=new Set,o=/<([A-Z][\w$]*)/g;let i;for(;(i=o.exec(e))!==null;){const r=i[1];r&&!n.has(r)&&s.add(r)}return[...n].map(r=>({name:r,file:t,renders:[...s]}))}function O(e){return a&&e.name&&a.isIdentifier(e.name)?e.name.text:null}function ie(e){const t=[],n=[];function s(o){if(a.isCallExpression(o)){const i=o.expression;a.isIdentifier(i)?t.push({pos:o.pos,end:o.end,name:i.text+"()"}):a.isPropertyAccessExpression(i)&&t.push({pos:o.pos,end:o.end,name:i.name.text+"()"})}a.isThrowStatement(o)&&o.expression&&a.isNewExpression(o.expression)&&a.isIdentifier(o.expression.expression)&&n.push({pos:o.pos,end:o.end,name:o.expression.expression.text}),o.forEachChild?.(s)}return s(e),{calls:t,throws:n}}function re(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))].slice(0,20)}function ce(e,t,n){return[...new Set(e.filter(s=>s.pos>=t&&s.end<=n).map(s=>s.name))]}function le(e){return a?a.isFunctionDeclaration(e)||a.isFunctionExpression(e)||a.isArrowFunction(e)||a.isMethodDeclaration(e):!1}function ae(e){return a&&(e.parent&&a.isVariableDeclaration(e.parent)||e.parent&&a.isPropertyAssignment(e.parent))?O(e.parent):null}function pe(e,t){if(!a)return null;let n;try{n=a.createSourceFile(e,t,a.ScriptTarget.Latest,!0)}catch{return null}const{calls:s,throws:o}=ie(n),i=[];function r(l){if(le(l)){const f=O(l)||ae(l)||"<anonymous>",g=t.slice(l.pos,l.end),h=re(s,l.pos,l.end),x=ce(o,l.pos,l.end);i.push({name:f,calls:h,throws:x,services:_(g),dbCalls:F(g),httpCalls:E(g),loc:n.getLineAndCharacterOfPosition(l.pos).line+1})}l.forEachChild?.(r)}return r(n),i}const ue=`
2
- import ast, json, sys
3
-
4
- def get_calls(node):
5
- calls = []
6
- for n in ast.walk(node):
7
- if isinstance(n, ast.Call):
8
- if isinstance(n.func, ast.Name):
9
- calls.append(n.func.id + "()")
10
- elif isinstance(n.func, ast.Attribute):
11
- calls.append(n.func.attr + "()")
12
- return list(set(calls))[:20]
13
-
14
- def get_raises(node):
15
- raises = []
16
- for n in ast.walk(node):
17
- if isinstance(n, ast.Raise) and n.exc:
18
- if isinstance(n.exc, ast.Call) and isinstance(n.exc.func, ast.Name):
19
- raises.append(n.exc.func.id)
20
- elif isinstance(n.exc, ast.Name):
21
- raises.append(n.exc.id)
22
- return list(set(raises))
23
-
24
- try:
25
- code = open(sys.argv[1], encoding="utf-8", errors="ignore").read()
26
- tree = ast.parse(code)
27
- functions = []
28
- for node in ast.walk(tree):
29
- if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
30
- functions.append({
31
- "name": node.name,
32
- "calls": get_calls(node),
33
- "throws": get_raises(node),
34
- "loc": node.lineno,
35
- })
36
- print(json.dumps(functions))
37
- except Exception as e:
38
- print(json.dumps([]))
39
- `;function fe(e){try{const t=W(`python3 -c ${JSON.stringify(ue)} ${JSON.stringify(e)}`,{timeout:8e3,encoding:"utf8",stdio:["pipe","pipe","pipe"]}),n=JSON.parse(t.trim()||"[]"),s=S.readFileSync(e,"utf8");return n.map(o=>({...o,services:_(s),dbCalls:F(s),httpCalls:E(s)}))}catch{return null}}const de=[{re:/^func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(/gm,lang:"go"},{re:/^\s*(?:def|async def)\s+(\w+)\s*\(/gm,lang:"py"},{re:/^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:\w+\s+)?(\w+)\s*\(/gm,lang:"java"},{re:/^\s*def\s+(\w+)\s*[\(\|]/gm,lang:"rb"}];function N(e,t){const n=d.extname(e).slice(1),s=de.find(l=>l.lang===n);if(!s)return null;const o=[],i=new RegExp(s.re.source,"gm");let r;for(;(r=i.exec(t))!==null;){const l=r.index,f=Math.min(l+2e3,t.length),g=t.slice(l,f);o.push({name:r[1],calls:[],throws:[],services:_(g),dbCalls:F(g),httpCalls:E(g),loc:t.slice(0,l).split(`
40
- `).length})}return o.length>0?o:null}const ge=new Set(["node_modules",".git","dist","build","out",".next",".nuxt","coverage","__pycache__",".pytest_cache","vendor","tmp",".turbo","target",".gradle","public","static","assets"]),me=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".rb",".java"]),he=/\.(test|spec)\.[jt]sx?$|_test\.(go|py|rb)|spec\.(rb|js|ts)$/;function*D(e){let t;try{t=S.readdirSync(e,{withFileTypes:!0})}catch{return}for(const n of t)if(n.isDirectory())ge.has(n.name)||(yield*D(d.join(e,n.name)));else if(n.isFile()){const s=d.extname(n.name);me.has(s)&&!he.test(n.name)&&(yield d.join(e,n.name))}}function ye(e){let t;try{t=S.readFileSync(e,"utf8")}catch{return[]}const n=d.extname(e);return[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(n)?pe(e,t)||N(e,t)||[]:n===".py"?fe(e)||N(e,t)||[]:N(e,t)||[]}function R(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").toLowerCase().split(/[\s_\-/.]+/).filter(t=>t.length>1)}function q(e,t){const n=new Set(e),s=new Set(t);let o=0;for(const r of n)s.has(r)&&o++;const i=n.size+s.size-o;return i===0?0:o/i}function we(e,t){const n=R(e.name);let s=null,o=0;for(const i of t){const r=Math.max(q(n,R(i.id||"")),q(n,R(i.name||i.title||"")));r>o&&(o=r,s=i)}return o>=.2?{cap:s,score:o}:null}function Se(e={},t,n,s){const o=d.relative(s,n),i=(r=[],l=[])=>[...new Set([...r,...l])];return{functions:i(e.functions,[t.name]),sourceFiles:i(e.sourceFiles,[o]),calls:i(e.calls,t.calls),throws:i(e.throws,t.throws),services:i(e.services,t.services),dbCalls:i(e.dbCalls,t.dbCalls),httpCalls:i(e.httpCalls,t.httpCalls),scannedAt:new Date().toISOString()}}function xe(e){console.log(),console.log(P(" Scan Results")),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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[t,n]of Object.entries(e)){const{codeAnalysis:s}=n;s&&(console.log(),console.log(` ${k("\u25CF")} ${P(t)}`),s.sourceFiles?.length&&console.log(u(" files: ")+s.sourceFiles.join(", ")),s.functions?.length&&console.log(u(" funcs: ")+s.functions.join(", ")),s.services?.length&&console.log(u(" services: ")+G(s.services.join(", "))),s.dbCalls?.length&&console.log(u(" db: ")+s.dbCalls.slice(0,4).join(", ")),s.httpCalls?.length&&console.log(u(" http: ")+s.httpCalls.slice(0,4).join(", ")),s.throws?.length&&console.log(u(" throws: ")+A(s.throws.join(", "))))}console.log(),console.log(u(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"))}async function Ae(e){const t=e||[],n=t.includes("--dry-run"),s=t.includes("--json"),o=t.indexOf("--dir"),i=o!==-1?[t[o+1]]:[],r=(()=>{const c=t.indexOf("--capability");return c!==-1?t[c+1]:null})(),l=process.cwd(),f=d.join(l,"inferno"),g=d.join(f,"capabilities.json");S.existsSync(g)||(console.error(T("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let h;try{h=JSON.parse(S.readFileSync(g,"utf8"))}catch(c){console.error(T("\u2717 Failed to parse capabilities.json: "+c.message)),process.exit(1)}Array.isArray(h)||(h.capabilities?h=h.capabilities:(console.error(T("\u2717 Unexpected capabilities.json format.")),process.exit(1)));const x=r?h.filter(c=>c.id===r||(c.name||"").toLowerCase()===r.toLowerCase()):h;x.length===0&&(console.log(A(r?`No capability matched: ${r}`:"No capabilities found.")),process.exit(0));const M=[l,...i];s||process.stdout.write(u(" Walking source files\u2026"));const w=[];for(const c of M)for(const p of D(c))w.push(p);s||process.stdout.write(`\r Found ${w.length} source files.
41
- `),s||process.stdout.write(u(" Analyzing\u2026"));const b=[];let C=0;for(const c of w){const p=ye(c);for(const m of p)b.push({fn:m,filePath:c});C++,!s&&C%20===0&&process.stdout.write(`\r Analyzed ${C}/${w.length} files\u2026`)}s||process.stdout.write(`\r Analyzed ${w.length} files, found ${b.length} functions.
42
- `);const j=[],v=[];for(const c of w)if(/\.(jsx|tsx|vue|svelte)$/i.test(c))try{const p=S.readFileSync(c,"utf8"),m=d.relative(l,c).replace(/\\/g,"/");j.push(...te(p,m)),v.push(...oe(p,m))}catch{}s||(v.length>0&&console.log(` Found ${v.length} components (React/Vue/Svelte).`),j.length>0&&console.log(` Found ${j.length} UI elements (buttons, inputs, forms, links).`));const y={};for(const c of x)y[c.id]={...c,codeAnalysis:null};for(const{fn:c,filePath:p}of b){const m=we(c,x);if(!m)continue;const{cap:I}=m,H=y[I.id]?.codeAnalysis||{};y[I.id].codeAnalysis=Se(H,c,p,l)}const Z=Object.keys(y).length,J=Object.values(y).filter(c=>c.codeAnalysis).length;if(s){const c={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(y).map(([p,m])=>({id:p,name:m.name||m.title,codeAnalysis:m.codeAnalysis}))};console.log(JSON.stringify(c,null,2));return}if(xe(y),console.log(` ${k("\u2714")} Matched ${J}/${Z} capabilities to source functions`),console.log(),n){console.log(A(" --dry-run: no files written."));return}const L={scannedAt:new Date().toISOString(),files:w.length,functions:b.length,capabilities:Object.entries(y).map(([c,p])=>({id:c,name:p.name||p.title,codeAnalysis:p.codeAnalysis})),uiElements:j,components:v},U=d.join(f,"scan.json");S.writeFileSync(U,JSON.stringify(L,null,2)),console.log(u(" Saved \u2192 inferno/scan.json"));let $=0;const B=h.map(c=>{const p=y[c.id]?.codeAnalysis;return p?($++,{...c,codeAnalysis:p}):c});$>0&&(S.writeFileSync(g,JSON.stringify(B,null,2)),console.log(u(` Updated ${$} capability entries in capabilities.json`))),console.log(),a||(console.log(A(" \u26A0 TypeScript compiler not found \u2014 JS/TS analyzed with regex fallback.")),console.log(u(" For deeper analysis: npm install -g typescript")),console.log())}export{Ae as scanCommand};
@@ -1,2 +0,0 @@
1
- import*as p from"node:fs";import*as d from"node:path";import{bold as b,cyan as x,gray as l,green as w,yellow as $,red as r}from"../ui/output.mjs";const m=["experimental","stable","frozen"],j={experimental:"\u{1F30A}",stable:"\u3030\uFE0F",frozen:"\u{1F9CA}"},S={experimental:w,stable:$,frozen:r};function h(n){try{const s=JSON.parse(p.readFileSync(n,"utf8"));return Array.isArray(s)?s:s.capabilities||[]}catch(s){console.error(r("\u2717 Failed to read capabilities.json: "+s.message)),process.exit(1)}}function z(n,s){p.writeFileSync(n,JSON.stringify(s,null,2))}function u(n){return n.stability||"experimental"}function L(n){const s=m.indexOf(n),i=S[n]||l,t="\u2588".repeat(s+1),e="\u2591".repeat(m.length-s-1);return i(t)+l(e)}function C(n,s){if(s){const e=n.map(o=>({id:o.id,name:o.name||o.title,stability:u(o)}));console.log(JSON.stringify(e,null,2));return}const i={frozen:[],stable:[],experimental:[]};for(const e of n)i[u(e)].push(e);console.log(),console.log(b(" Capability Stability")),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")),console.log(l(" ")+b(x("Capability".padEnd(32)))+b(x("Level".padEnd(16)))+b(x("Solid/Liquid"))),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"));for(const e of n){const o=u(e),a=j[o],c=S[o]||l;console.log(` ${a} ${e.id.padEnd(30)} ${c(o.padEnd(14))} ${L(o)}`)}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")),console.log();const t={frozen:i.frozen.length,stable:i.stable.length,experimental:i.experimental.length};console.log(` ${r("\u{1F9CA} Frozen:")} ${t.frozen} ${$("\u3030\uFE0F Stable:")} ${t.stable} ${w("\u{1F30A} Experimental:")} ${t.experimental}`),console.log(),console.log(l(" Tip: infernoflow freeze <cap-id> \u2014 infernoflow thaw <cap-id>")),console.log()}function F(n,s,i,t){m.includes(t)||(console.error(r(`\u2717 Invalid level "${t}". Must be: ${m.join(", ")}`)),process.exit(1));const e=n.findIndex(f=>f.id===i);e===-1&&(console.error(r(`\u2717 Capability "${i}" not found in capabilities.json`)),process.exit(1));const o=u(n[e]);n[e]={...n[e],stability:t,stabilitySetAt:new Date().toISOString()},z(s,n);const a=j[t],c=S[t];console.log(),console.log(` ${a} ${b(i)} ${l(o)} \u2192 ${c(t)}`),t==="frozen"&&(console.log(l(" AI assistants will be instructed not to modify this capability.")),console.log(l(" Run `infernoflow setup` to update CLAUDE.md with this change."))),console.log()}function A(n,s,i){const t=n.findIndex(o=>o.id===i);t===-1&&(console.error(r(`\u2717 Capability "${i}" not found in capabilities.json`)),process.exit(1));const e=u(n[t]);n[t]={...n[t],stability:"experimental",stabilitySetAt:new Date().toISOString()},z(s,n),console.log(),console.log(` \u{1F30A} ${b(i)} ${l(e)} \u2192 ${w("experimental")}`),console.log(l(" This capability is now liquid \u2014 free to evolve.")),console.log()}function O(n,s){const i=d.join(n,"capabilities.json"),t=d.join(n,"scan.json");if(!p.existsSync(i)||!p.existsSync(t))return[];const e=h(i),o=JSON.parse(p.readFileSync(t,"utf8")),a=new Date(o.scannedAt),c=[];for(const f of e){if(u(f)!=="frozen")continue;const g=o.capabilities?.find(y=>y.id===f.id);if(g?.codeAnalysis?.sourceFiles)for(const y of g.codeAnalysis.sourceFiles){const E=d.join(s,y);try{p.statSync(E).mtimeMs>a.getTime()&&c.push({capId:f.id,file:y})}catch{}}}return c}function D(n){const s=n.filter(o=>u(o)==="frozen").map(o=>o.id),i=n.filter(o=>u(o)==="stable").map(o=>o.id),t=n.filter(o=>u(o)==="experimental").map(o=>o.id);if(s.length===0&&i.length===0)return null;const e=["### Capability Stability (Solid/Liquid Layer)",""];if(s.length>0){e.push("**\u{1F9CA} Frozen \u2014 NEVER modify without explicit instruction:**");for(const o of s)e.push(`- \`${o}\``);e.push("")}if(i.length>0){e.push("**\u3030\uFE0F Stable \u2014 prefer additive changes, avoid breaking API:**");for(const o of i)e.push(`- \`${o}\``);e.push("")}return t.length>0&&(e.push(`**\u{1F30A} Experimental \u2014 free to refactor:** ${t.map(o=>`\`${o}\``).join(", ")}`),e.push("")),e.push("> Run `infernoflow stability` to see the full liquid/solid map."),e.join(`
2
- `)}async function N(n){const i=(n||[]).slice(1).includes("--json"),t=process.cwd(),e=d.join(t,"inferno"),o=d.join(e,"capabilities.json");p.existsSync(o)||(console.error(r("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));const a=h(o);C(a,i);const c=O(e,t);if(c.length>0){console.log(r(" \u26A0 Frozen capability drift detected!"));for(const f of c)console.log(r(` ${f.capId}: ${f.file} was modified since last scan`));console.log(l(" Run `infernoflow scan` to update the baseline.")),console.log()}}async function P(n){const s=(n||[]).slice(1),i=s.find(g=>!g.startsWith("--")),e=s.includes("--stable")?"stable":"frozen";i||(console.error(r("\u2717 Usage: infernoflow freeze <capability-id> [--stable]")),process.exit(1));const o=process.cwd(),a=d.join(o,"inferno"),c=d.join(a,"capabilities.json");p.existsSync(c)||(console.error(r("\u2717 inferno/capabilities.json not found.")),process.exit(1));const f=h(c);F(f,c,i,e)}async function R(n){const i=(n||[]).slice(1).find(c=>!c.startsWith("--"));i||(console.error(r("\u2717 Usage: infernoflow thaw <capability-id>")),process.exit(1));const t=process.cwd(),e=d.join(t,"inferno"),o=d.join(e,"capabilities.json");p.existsSync(o)||(console.error(r("\u2717 inferno/capabilities.json not found.")),process.exit(1));const a=h(o);A(a,o,i)}export{m as LEVELS,D as buildStabilitySummary,O as checkFrozenDrift,P as freezeCommand,N as stabilityCommand,R as thawCommand};
@@ -1,4 +0,0 @@
1
- import*as d from"node:fs";import*as a from"node:path";import{bold as g,cyan as b,gray as t,green as u,yellow as E,red as j}from"../ui/output.mjs";import{ampPaths as I,readEntries as N}from"../amp/io.mjs";const y="inferno";function $(){return I(process.cwd()).sessions}const O=a.join(y,"CONTEXT.md"),A=a.join(y,"theme.json"),C=a.join(y,"scan.json"),M=a.join(y,"contract.json"),P=a.join(y,"capabilities.json");function v(o){try{return JSON.parse(d.readFileSync(o,"utf8"))}catch{return null}}function L(o){return Math.ceil((o||"").length/4)}const x={gotcha:400,decision:200,attempt:250,preference:150,note:100,theme:300,handoff:500,error:200};function F(o){const e={ok:!1,memory:{total:0,byType:{},oldestEntry:null,newestEntry:null,sessionsTracked:0},context:{sizeBytes:0,estimatedTokens:0,hasIntent:!1,hasWorking:!1},theme:{captured:!1,fonts:0,colors:0,cssVars:0,framework:null},coverage:{total:0,withAnalysis:0,pct:0},chains:{total:0,resolved:0},contract:{policyId:null,capabilities:0,isLite:!1},savings:{estimatedTokens:0,breakdown:{}}},i=v(a.join(o,M));i&&(e.contract.policyId=i.policyId,e.contract.capabilities=(i.capabilities||[]).length,e.contract.isLite=!!i.lite,e.ok=!0);const c=v(a.join(o,P));if(c){const s=Array.isArray(c)?c:c.capabilities||[];e.coverage.total=s.length,e.coverage.withAnalysis=s.filter(m=>m.codeAnalysis).length,e.coverage.pct=e.coverage.total?Math.round(e.coverage.withAnalysis/e.coverage.total*100):0}const n=$();if(d.existsSync(n)){const s=N(process.cwd());e.memory.total=s.length;for(const k of s){const h=k.type||"note";e.memory.byType[h]=(e.memory.byType[h]||0)+1;const T=x[h]||100;e.savings.estimatedTokens+=T,e.savings.breakdown[h]=(e.savings.breakdown[h]||0)+T}s.length&&(e.memory.oldestEntry=s[0].ts,e.memory.newestEntry=s[s.length-1].ts);const m=new Set(s.map(k=>{const h=k.ts;return typeof h=="number"?new Date(h).toISOString().slice(0,10):(h||"").slice(0,10)}));e.memory.sessionsTracked=m.size}const l=a.join(o,O);if(d.existsSync(l)){const s=d.readFileSync(l,"utf8");e.context.sizeBytes=Buffer.byteLength(s,"utf8"),e.context.estimatedTokens=L(s),e.context.hasIntent=s.includes("## Intent"),e.context.hasWorking=s.includes("## Working on")}const r=v(a.join(o,A));r&&(e.theme.captured=!0,e.theme.fonts=Object.keys(r.fonts||{}).filter(s=>r.fonts[s]).length,e.theme.colors=Object.keys(r.colors?.palette||{}).length,e.theme.cssVars=Object.keys(r.cssVars||{}).length,e.theme.framework=r.framework||null);const f=v(a.join(o,C));if(f?.httpChains){const s=Object.values(f.httpChains).flat();e.chains.total=s.length,e.chains.resolved=s.filter(m=>m.resolved).length}return e}function w(o,e,i=20){const c=e>0?Math.round(o/e*i):0;return"\u2588".repeat(c)+"\u2591".repeat(i-c)}function S(o){return o>=80?u:o>=40?E:j}function B(o){if(!o)return"never";const e=new Date(o),c=Date.now()-e.getTime(),n=Math.floor(c/864e5);return n===0?"today":n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function p(o){return o>=1e3?`~${Math.round(o/100)/10}k`:`~${o}`}function D(o){const e=t(" "+"\u2500".repeat(52));console.log(),console.log(" "+g("\u{1F525} infernoflow stats")),o.contract.policyId&&console.log(t(` Project: ${o.contract.policyId}${o.contract.isLite?" (lite)":""}`)),console.log(e),console.log(),console.log(" "+g("Session memory")+t(" ("+a.relative(process.cwd(),$())+")")),console.log();const i=o.memory.total;if(i===0)console.log(t(' No entries yet \u2014 run: infernoflow log "<what happened>" --type gotcha'));else{const n=["gotcha","decision","attempt","preference","theme","note","handoff","error"],l=Math.max(...Object.values(o.memory.byType));for(const r of n){const f=o.memory.byType[r]||0;if(f===0)continue;const s=w(f,l,16),m=r.padEnd(12);console.log(` ${t(m)} ${b(s)} ${f}`)}console.log(),console.log(t(" Total entries: ")+g(i)),console.log(t(" Sessions tracked: ")+o.memory.sessionsTracked),o.memory.newestEntry&&console.log(t(" Last entry: ")+B(o.memory.newestEntry))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Context injection")+t(" (per session start)")),console.log(),o.context.sizeBytes===0?console.log(t(" No CONTEXT.md yet \u2014 run: infernoflow context")):(console.log(t(" Size: ")+`${Math.round(o.context.sizeBytes/1024*10)/10} KB`),console.log(t(" Tokens: ")+g(p(o.context.estimatedTokens))+t(" injected into every session")),o.context.hasIntent&&console.log(t(" ")+u("\u2714")+t(" Intent captured")),o.context.hasWorking&&console.log(t(" ")+u("\u2714")+t(" Working state captured"))),console.log(),console.log(e),console.log(),console.log(" "+g("Capability coverage")+t(" (code analysis via infernoflow scan)")),console.log(),o.coverage.total===0)console.log(t(" No capabilities yet \u2014 run: infernoflow init"));else{const n=S(o.coverage.pct),l=w(o.coverage.withAnalysis,o.coverage.total,24);if(console.log(` ${n(l)} ${g(o.coverage.pct+"%")} (${o.coverage.withAnalysis}/${o.coverage.total})`),o.coverage.pct<100){const r=o.coverage.total-o.coverage.withAnalysis;console.log(t(`
2
- ${r} capabilities without code analysis`)),console.log(t(" Run: infernoflow scan to enrich them"))}}if(o.chains.total>0){console.log(),console.log(e),console.log(),console.log(" "+g("HTTP call chains")+t(" (end-to-end resolution)")),console.log();const n=Math.round(o.chains.resolved/o.chains.total*100),l=S(n),r=w(o.chains.resolved,o.chains.total,20);console.log(` ${l(r)} ${g(n+"%")} resolved (${o.chains.resolved}/${o.chains.total} call chains)`),o.chains.resolved<o.chains.total&&console.log(t(`
3
- Unresolved calls may be to external services or missing route files`))}if(console.log(),console.log(e),console.log(),console.log(" "+g("Design system")+t(" (inferno/theme.json)")),console.log(),!o.theme.captured)console.log(t(" Not captured yet \u2014 run: infernoflow theme"));else{const n=[];o.theme.fonts&&n.push(`${o.theme.fonts} font${o.theme.fonts!==1?"s":""}`),o.theme.colors&&n.push(`${o.theme.colors} colors`),o.theme.cssVars&&n.push(`${o.theme.cssVars} CSS vars`),o.theme.framework&&n.push(`${o.theme.framework}`),console.log(t(" ")+u("\u2714")+" "+n.join(" \xB7 ")),console.log(t(" AI agents always use the correct fonts and colors for this project"))}console.log(),console.log(e),console.log(),console.log(" "+g("Estimated token savings")+t(" (vs re-discovering from scratch)")),console.log();const c=o.savings.estimatedTokens;if(c===0)console.log(t(" No session entries yet \u2014 start logging to track savings"));else{const n=Math.max(o.memory.sessionsTracked,1),l=Math.round(c/n);console.log(" Total saved: "+g(u(p(c)+" tokens"))),console.log(" Per session: "+g(p(l)+" tokens")),console.log(),console.log(t(" Breakdown:"));const r=["gotcha","handoff","attempt","decision","theme","preference","note","error"];for(const f of r){const s=o.savings.breakdown[f];if(!s)continue;const m=o.memory.byType[f]||0;console.log(t(` ${f.padEnd(12)} ${m}\xD7 \xD7 ${x[f]||100} = `)+b(p(s)))}console.log(),console.log(t(" * Estimates based on typical back-and-forth cost per entry type.")),console.log(t(" Actual savings vary with model, project complexity, and session length."))}console.log(),console.log(e),console.log()}async function R(o=[]){const e=o.includes("--json"),i=o.includes("--brief"),c=process.cwd();!d.existsSync(a.join(c,y))&&!d.existsSync(a.join(c,".ai-memory"))&&(console.error(j(` \u2718 not initialized \u2014 run: infernoflow init
4
- `)),process.exit(1));const n=F(c);if(e){console.log(JSON.stringify(n,null,2));return}if(i){const l=[];n.memory.total&&l.push(`${n.memory.total} memory entries`),n.context.estimatedTokens&&l.push(`${p(n.context.estimatedTokens)} tokens/session`),n.coverage.total&&l.push(`${n.coverage.pct}% capability coverage`),n.savings.estimatedTokens&&l.push(`${p(n.savings.estimatedTokens)} tokens saved`),console.log(l.join(" \xB7 ")||"No data yet \u2014 run infernoflow init + infernoflow log");return}D(n)}export{R as statsCommand};
@@ -1,62 +0,0 @@
1
- import*as p from"node:fs";import*as f from"node:path";import*as R from"node:readline";import{header as ee,ok as O,warn as M,info as ie,done as te,section as U,nextSteps as ne,bold as oe,cyan as S,gray as E,yellow as se,green as W,red as Z,errorAndExit as _}from"../ui/output.mjs";import{personalisePrompt as ae}from"../learning/adapt.mjs";function j(i){try{return JSON.parse(p.readFileSync(i,"utf8"))}catch{return null}}function K(i,e){return new Promise(t=>{i.question(e,r=>t(r.trim()))})}function fe(i){return i.replace(/[-_]+/g," ").split(" ").map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}function re({description:i,contract:e,capabilities:t,scenarios:r}){const b=e.capabilities||[],u=(t?.capabilities||[]).map(h=>` - ${h.id}: ${h.title||h.id}`).join(`
2
- `),s=r.map(h=>{const v=(h.capabilitiesCovered||[]).join(", "),m=(h.steps||[]).map(c=>` {action: "${c.action}", expect: "${c.expect}"}`).join(`
3
- `);return` File: ${h._file}
4
- capabilitiesCovered: [${v}]
5
- steps:
6
- ${m}`}).join(`
7
-
8
- `);return`You are a developer assistant for the infernoflow CLI tool.
9
-
10
- Your job is to analyze a code change description and suggest updates to the infernoflow contract files.
11
-
12
- ## Current contract state
13
-
14
- policyId: ${e.policyId}
15
- policyVersion: ${e.policyVersion}
16
- capabilities: [${b.join(", ")}]
17
-
18
- ## Current capabilities registry
19
- ${u||" (none)"}
20
-
21
- ## Current scenarios
22
- ${s||" (none)"}
23
-
24
- ## Developer's description of what changed
25
- "${i}"
26
-
27
- ## Your task
28
-
29
- Respond with ONLY a valid JSON object (no markdown, no explanation) in this exact format:
30
-
31
- {
32
- "summary": "one-line summary of what changed",
33
- "newCapabilities": [
34
- { "id": "CapabilityName", "title": "Human readable title", "reason": "why this is a new capability" }
35
- ],
36
- "removedCapabilities": ["CapabilityId"],
37
- "updatedScenarios": [
38
- {
39
- "file": "existing_scenario_filename.json or new_scenario_name.json",
40
- "isNew": false,
41
- "capabilitiesCovered": ["CapabilityId1", "CapabilityId2"],
42
- "stepsToAdd": [
43
- { "action": "CapabilityId", "expect": "what should happen" }
44
- ]
45
- }
46
- ],
47
- "changelogEntry": "- Short description of the change for CHANGELOG.md"
48
- }
49
-
50
- Rules:
51
- - Only suggest capabilities that are genuinely new behaviors the system gains
52
- - Capability IDs must be PascalCase (e.g. SendEmail, not send_email)
53
- - If nothing changed capability-wise, return empty arrays
54
- - changelogEntry should start with "- "
55
- - Keep it minimal and accurate`}function B(i){const e=[];if(!i||typeof i!="object")return["AI response must be a JSON object."];i.summary!=null&&typeof i.summary!="string"&&e.push('"summary" must be a string.'),Array.isArray(i.newCapabilities)||e.push('"newCapabilities" must be an array.'),Array.isArray(i.removedCapabilities)||e.push('"removedCapabilities" must be an array.'),Array.isArray(i.updatedScenarios)||e.push('"updatedScenarios" must be an array.'),i.changelogEntry!=null&&typeof i.changelogEntry!="string"&&e.push('"changelogEntry" must be a string.');for(const t of i.newCapabilities||[]){if(!t||typeof t!="object"){e.push('Each item in "newCapabilities" must be an object.');continue}(typeof t.id!="string"||!/^[A-Z][A-Za-z0-9]*$/.test(t.id))&&e.push("newCapabilities[].id must be PascalCase (example: SendEmail)."),(typeof t.title!="string"||!t.title.trim())&&e.push("newCapabilities[].title must be a non-empty string.")}for(const t of i.removedCapabilities||[])(typeof t!="string"||!t.trim())&&e.push("removedCapabilities[] must contain non-empty strings.");for(const t of i.updatedScenarios||[]){if(!t||typeof t!="object"){e.push('Each item in "updatedScenarios" must be an object.');continue}(typeof t.file!="string"||!t.file.endsWith(".json"))&&e.push("updatedScenarios[].file must be a .json filename."),typeof t.isNew!="boolean"&&e.push("updatedScenarios[].isNew must be boolean."),(!Array.isArray(t.capabilitiesCovered)||!Array.isArray(t.stepsToAdd))&&e.push("updatedScenarios[].capabilitiesCovered and stepsToAdd must be arrays.")}return e}function Q(i,e){const t=[],r=new Set(i.capabilities||[]),b=new Set((e.newCapabilities||[]).map(s=>s.id)),u=new Set(e.removedCapabilities||[]);for(const s of b)u.has(s)&&t.push(`Capability "${s}" appears in both newCapabilities and removedCapabilities.`),r.has(s)&&t.push(`Capability "${s}" already exists in contract capabilities.`);for(const s of u)r.has(s)||t.push(`Capability "${s}" cannot be removed because it does not exist in contract.`);return t}function X({cwd:i,contract:e,capabilities:t,suggestion:r,version:b,quiet:u=!1}){const s=f.join(i,"inferno"),h=f.join(s,"contract.json"),v=f.join(s,"capabilities.json"),m=f.join(s,"CHANGELOG.md"),c=f.join(s,"scenarios"),y=r.newCapabilities||[],$=r.removedCapabilities||[],P=r.updatedScenarios||[],k=r.changelogEntry||"";let J=!1;const w=[],A=(n,a)=>w.push({filePath:n,content:a});if(y.length>0||$.length>0){const n=[...e.capabilities.filter(d=>!$.includes(d)),...y.map(d=>d.id)],a=Number(e.policyVersion||1)+1,l={...e,capabilities:n,policyVersion:a};A(h,JSON.stringify(l,null,2)+`
56
- `),u||O(`contract.json updated \u2192 policyVersion: v${a}`),J=!0}if(y.length>0||$.length>0){const n=t?{...t}:{schemaVersion:1,capabilities:[]};n.capabilities=(n.capabilities||[]).filter(a=>!$.includes(a.id));for(const a of y)n.capabilities.find(l=>l.id===a.id)||n.capabilities.push({id:a.id,title:a.title,since:b});A(v,JSON.stringify(n,null,2)+`
57
- `),u||O("capabilities.json updated")}for(const n of P){const a=f.join(c,n.file);let l;if(n.isNew||!p.existsSync(a))l={scenarioId:n.file.replace(".json",""),description:r.summary||"",capabilitiesCovered:n.capabilitiesCovered||[],steps:n.stepsToAdd||[]},A(a,JSON.stringify(l,null,2)+`
58
- `),u||O(`Created scenario: ${S(n.file)}`);else{l=j(a);const d=new Set(l.capabilitiesCovered||[]);(n.capabilitiesCovered||[]).forEach(N=>d.add(N)),l.capabilitiesCovered=[...d],l.steps=[...l.steps||[],...n.stepsToAdd||[]],A(a,JSON.stringify(l,null,2)+`
59
- `),u||O(`Updated scenario: ${S(n.file)}`)}J=!0}if(k&&p.existsSync(m)){let n=p.readFileSync(m,"utf8");/##\s+Unreleased/i.test(n)&&(n=n.replace(/(##\s+Unreleased[^\n]*\n)/i,`$1
60
- ${k}
61
- `),A(m,n),u||O("CHANGELOG.md updated"),J=!0)}const I=new Map;try{for(const n of w){p.existsSync(n.filePath)?I.set(n.filePath,p.readFileSync(n.filePath,"utf8")):I.set(n.filePath,null);const a=`${n.filePath}.tmp`;p.writeFileSync(a,n.content),p.renameSync(a,n.filePath)}}catch(n){for(const[a,l]of I.entries())l===null?p.existsSync(a)&&p.unlinkSync(a):p.writeFileSync(a,l);throw new Error(`Failed applying changes. Rolled back. Details: ${n.message}`)}return J}function q(i){const e=String(i||"").trim().replace(/^```json?\n?/,"").replace(/\n?```$/,"");return JSON.parse(e)}function ue(i){const e=f.join(i,"inferno"),t=f.join(e,"contract.json"),r=f.join(e,"capabilities.json"),b=f.join(e,"scenarios"),u=j(t),s=j(r),h=[];if(p.existsSync(b))for(const c of p.readdirSync(b).filter(y=>y.endsWith(".json"))){const y=j(f.join(b,c));y&&h.push({...y,_file:c})}let v="0.1.0";const m=f.join(i,"package.json");if(p.existsSync(m)){const c=j(m);c?.version&&(v=c.version)}return{contract:u,capabilities:s,scenarios:h,version:v}}function F(i){console.log(JSON.stringify(i,null,2))}function x(i,e,t){F({ok:!1,error:i,message:e,hint:t}),process.exit(1)}async function ce(){return new Promise(i=>{let e="";process.stdin.setEncoding("utf8"),process.stdin.on("data",t=>{e+=t}),process.stdin.on("end",()=>i(e.trim())),setTimeout(()=>i(""),100)})}async function he(i){const e=process.cwd(),t=f.join(e,"inferno"),r=i.includes("--json"),b=i.includes("--apply"),u=i.indexOf("--response");let s=u!==-1?i[u+1]:null;r||ee("suggest"),p.existsSync(t)||(r&&x("inferno_not_found","inferno/ not found","Run: infernoflow init"),_("inferno/ not found","Run: infernoflow init"));const h=f.join(t,"contract.json"),v=f.join(t,"capabilities.json"),m=f.join(t,"scenarios"),c=j(h);c||(r&&x("contract_not_found","contract.json not found or invalid"),_("contract.json not found or invalid"));const y=j(v),$=[];if(p.existsSync(m))for(const o of p.readdirSync(m).filter(g=>g.endsWith(".json"))){const g=j(f.join(m,o));g&&$.push({...g,_file:o})}let P="0.1.0";const k=f.join(e,"package.json");if(p.existsSync(k)){const o=j(k);o?.version&&(P=o.version)}let w=i.filter(o=>!o.startsWith("-")).slice(1).join(" ");if(!w&&!r){const o=R.createInterface({input:process.stdin,output:process.stdout});console.log(E(" Describe what changed in your code (e.g. 'added email notifications'):")),w=await K(o,` ${S(">")} `),o.close(),console.log()}w||(r&&x("no_description","No description provided",'Usage: infernoflow suggest "what changed" --json'),_("No description provided",'Usage: infernoflow suggest "what changed"'));const A=re({description:w,contract:c,capabilities:y,scenarios:$}),I=ae(A,t);if(r){if(!s){const C=await ce();C&&(s=C)}if(!s){F({ok:!0,mode:"prompt",description:w,prompt:I,context:{policyId:c.policyId,policyVersion:c.policyVersion,capabilities:c.capabilities||[],scenarios:$.map(C=>C._file),version:P}});return}if(s.startsWith("@")){const C=s.slice(1);try{s=p.readFileSync(C,"utf8")}catch{x("file_not_found",`Cannot read response file: ${C}`)}}let o;try{o=q(s)}catch(C){x("parse_error","Could not parse AI response as JSON",C.message)}const g=B(o);g.length>0&&x("validation_error",g[0],g.join("; "));const T=Q(c,o);T.length>0&&x("conflict_error",T[0],T.join("; "));const z={summary:o.summary||"",newCapabilities:o.newCapabilities||[],removedCapabilities:o.removedCapabilities||[],updatedScenarios:o.updatedScenarios||[],changelogEntry:o.changelogEntry||""};if(!b){F({ok:!0,mode:"validate",description:w,changes:z,applied:!1});return}try{X({cwd:e,contract:c,capabilities:y,suggestion:o,version:P,quiet:!0}),F({ok:!0,mode:"apply",description:w,changes:z,applied:!0})}catch(C){x("apply_error",C.message)}return}U("Generated Prompt"),console.log(),console.log(E("\u2500".repeat(50))),console.log(I),console.log(E("\u2500".repeat(50))),console.log(),ie("Copy the prompt above and paste it into:"),console.log(` ${S("\u2022")} Claude \u2192 https://claude.ai`),console.log(` ${S("\u2022")} ChatGPT \u2192 https://chatgpt.com`),console.log(` ${S("\u2022")} Copilot, Cursor, or any AI you use`),console.log(),M("The AI will respond with a JSON object."),console.log();const n=R.createInterface({input:process.stdin,output:process.stdout});console.log(E(" Paste the AI's JSON response below, then press Enter twice:")),console.log();let a="",l=0;await new Promise(o=>{n.on("line",g=>{g.trim()===""?(l++,l>=2&&a.trim()&&o()):(l=0,a+=g+`
62
- `)}),n.on("close",o)}),n.close();let d;try{d=q(a)}catch{_("Could not parse the AI response as JSON","Make sure you copied the full JSON response from the AI")}const N=B(d);N.length>0&&_("AI response schema is invalid",N[0]+(N.length>1?` (+${N.length-1} more)`:""));const D=Q(c,d);D.length>0&&_("AI response contains conflicting capability operations",D[0]+(D.length>1?` (+${D.length-1} more)`:"")),U("Proposed Changes"),console.log(),d.summary&&(console.log(` ${oe("Summary:")} ${d.summary}`),console.log());const L=d.newCapabilities||[],V=d.removedCapabilities||[],G=d.updatedScenarios||[];L.length===0&&V.length===0&&G.length===0&&(O("No capability changes detected \u2014 nothing to apply."),console.log(),process.exit(0)),L.length>0&&(console.log(` ${W("+")} New capabilities:`),L.forEach(o=>console.log(` ${W(o.id)} \u2014 ${E(o.title)}`)),console.log()),V.length>0&&(console.log(` ${Z("-")} Removed capabilities:`),V.forEach(o=>console.log(` ${Z(o)}`)),console.log()),G.length>0&&(console.log(` ${S("~")} Scenario updates:`),G.forEach(o=>{const g=o.isNew?W("[new]"):S("[update]");console.log(` ${g} ${o.file}`)}),console.log()),d.changelogEntry&&(console.log(` ${se("\u{1F4DD}")} Changelog: ${E(d.changelogEntry)}`),console.log());const H=R.createInterface({input:process.stdin,output:process.stdout}),Y=await K(H,` Apply these changes? ${E("(y/n)")} `);H.close(),console.log(),Y.toLowerCase()!=="y"&&Y.toLowerCase()!=="yes"&&(M("Cancelled \u2014 no changes made."),console.log(),process.exit(0)),U("Applying Changes"),console.log(),X({cwd:e,contract:c,capabilities:y,suggestion:d,version:P}),te("suggest complete!"),ne([S("infernoflow status")+" \u2014 verify the updated contract",S("infernoflow check")+" \u2014 validate everything"])}export{X as applyChanges,re as buildPrompt,Q as detectSuggestionConflicts,ue as loadSuggestContext,q as parseSuggestionJson,j as readJson,he as suggestCommand,B as validateSuggestion};
@@ -1 +0,0 @@
1
- import{execFileSync as O}from"node:child_process";import*as u from"node:fs";import*as m from"node:path";import{fileURLToPath as S}from"node:url";import{header as C,section as g,ok as l,warn as y,yellow as x,gray as k}from"../ui/output.mjs";function _(t){const i=m.join(t,"inferno","contract.json"),r=m.join(t,"inferno","CONTEXT.md");if(!u.existsSync(i))return null;if(!u.existsSync(r))return["CONTEXT.md is missing"];let e;try{e=JSON.parse(u.readFileSync(i,"utf8"))}catch(o){return[`contract.json unreadable: ${o.message}`]}const c=u.readFileSync(r,"utf8"),s=[];if(e.policyId){const o=c.match(/^#\s+([^\n]+)/m),n=o?o[1]:"";n.includes(e.policyId)||s.push(`H1 "${n}" doesn't match policyId "${e.policyId}"`)}if(typeof e.policyVersion=="number"){const o=c.match(/\bv(\d+(?:\.\d+)?)\b/i);if(o){const n=parseFloat(o[1]);n!==e.policyVersion&&s.push(`CONTEXT.md references v${n} but contract is v${e.policyVersion}`)}}const a=(Array.isArray(e.capabilities)?e.capabilities.map(o=>typeof o=="string"?o:o.id).filter(Boolean):[]).filter(o=>!c.includes(o));return a.length>0&&s.push(`CONTEXT.md is missing capabilities: ${a.join(", ")}`),s.length>0?s:[]}const E=S(import.meta.url),D=m.dirname(E),$=m.resolve(D,"..","..","bin","infernoflow.mjs");function I(t){const i=O(process.execPath,[$,...t],{encoding:"utf8",stdio:["ignore","pipe","pipe"]});return JSON.parse(i)}function N(t){try{return{ok:!0,data:I(t)}}catch(i){const r=i?.stdout?.toString?.()||"";try{return{ok:!1,data:JSON.parse(r)}}catch{return{ok:!1,data:{ok:!1,errors:["command_failed"]}}}}}async function P(t=[]){const i=t.includes("--auto"),r=t.includes("--json"),e=t.includes("--dry-run");i||(r&&(console.log(JSON.stringify({ok:!1,error:"missing_required_flag",hint:"Use: infernoflow sync --auto"},null,2)),process.exit(1)),C("sync"),y("missing --auto flag"),console.log(` ${x("\u2192")} infernoflow sync --auto`),console.log(),process.exit(1));const c=N(["pr-impact","--json"]),s=_(process.cwd())||[],d=s.length>0,a=!c.data?.ok||d,o=c.data?.confidence||"low",n=o==="high"?"auto":o==="medium"?"ask":"block",T=a?[...d?["Regenerate CONTEXT.md via `infernoflow context`"]:[],"Generate inferno update proposal (suggest)","Review changes","Validate with check --json"]:["No inferno drift detected","Validate with check --json"],f=N(["check","--json"]),h={ok:c.ok&&f.ok&&!!f.data?.ok,mode:"auto-skeleton",dryRun:e,needsSync:a,didApply:!1,confidence:o,policyDecision:n,actions:T,prImpact:c.data,postCheck:f.data,contextDrift:s,reasonCodes:[...a?["DRIFT_DETECTED"]:["NO_DRIFT"],...d?["CONTEXT_MD_STALE"]:[],`POLICY_${n.toUpperCase()}`,...n==="auto"?["AUTO_APPLY_DISABLED_IN_SKELETON"]:[]]};if(r&&(console.log(JSON.stringify(h,null,2)),process.exit(h.ok?0:1)),C("sync --auto"),g("State"),a?y("Inferno drift detected"):l("No inferno drift detected"),d)for(const p of s)y(`CONTEXT.md drift: ${p}`);l(`Confidence: ${k(o)}`),l(`Policy decision: ${k(n)}`),l(`Apply mode: ${k("skeleton (no file writes)")}`),e&&l("Dry run enabled"),g("Plan"),T.forEach(p=>console.log(` ${x("\u2192")} ${p}`)),g("Validation"),f.ok&&f.data?.ok?l("Post-check passed"):y("Post-check failed; see infernoflow check --json"),console.log(),process.exit(h.ok?0:1)}export{P as syncCommand};
@@ -1,6 +0,0 @@
1
- import*as y from"node:fs";import*as $ from"node:path";import*as E from"node:os";import{spawnSync as w}from"node:child_process";import{bold as A,gray as f,green as F,yellow as I,red as b}from"../ui/output.mjs";function N(i){try{return JSON.parse(y.readFileSync(i,"utf8"))}catch{return null}}function J(i){return i?.stability||"experimental"}const P=F("\u2713"),R=b("\u2717"),L=f("\u25CB");function O(i,n){const e=$.join(n,"scenarios");if(!y.existsSync(e))return[];const g=[];for(const c of y.readdirSync(e))if(c.endsWith(".json"))try{const p=JSON.parse(y.readFileSync($.join(e,c),"utf8"));(p.capabilitiesCovered||p.capabilities||[]).some(a=>a.toLowerCase()===i.toLowerCase())&&g.push({...p,_file:c})}catch{}return g}function z(i){const n=N($.join(i,"package.json"));if(!n)return null;const e={...n.dependencies,...n.devDependencies};return e?.vitest?"vitest":e?.jest?"jest":e?.mocha?"mocha":n.scripts?.test&&!n.scripts.test.includes("no test")?{custom:n.scripts.test}:null}function D(i,n,e,g){const c=n.name||n.title||i,p=n.description||"(no description)",r=g?.codeAnalysis?.sourceFiles||[],a=g?.codeAnalysis?.functions||[],m=e?.steps||e?.actions||[],u=e?.expects||e?.assertions||[],s=[`// Auto-generated smoke test for: ${i}`,"// Generated by infernoflow test \u2014 edit as needed","",'import { strict as assert } from "node:assert";',"",`// Capability: ${c}`,`// ${p}`,""];return r.length&&(s.push(`// Source: ${r[0]}`),s.push(`// import { ${a[0]||i} } from "./${$.basename(r[0])}";`),s.push("")),s.push("async function run() {"),s.push(" const results = [];"),s.push(""),m.length?m.forEach((t,h)=>{const d=typeof t=="string"?t:t.action||t.description||`step ${h+1}`;s.push(` // Step ${h+1}: ${d}`),s.push(` results.push({ step: ${JSON.stringify(d)}, status: "manual" });`),s.push("")}):(s.push(" // No explicit steps \u2014 running basic smoke test"),s.push(' results.push({ step: "capability exists", status: "pass" });'),s.push("")),u.length&&(u.forEach((t,h)=>{const d=typeof t=="string"?t:t.condition||t.description||`assertion ${h+1}`;s.push(` // Assert: ${d}`),s.push(` // assert(condition, ${JSON.stringify(d)});`)}),s.push("")),s.push(" return results;"),s.push("}"),s.push(""),s.push("run().then(results => {"),s.push(' const failed = results.filter(r => r.status === "fail");'),s.push(" results.forEach(r => {"),s.push(' const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "\u25CB";'),s.push(" console.log(` ${icon} ${r.step}`);"),s.push(" });"),s.push(" if (failed.length) { console.error(`\\n ${failed.length} failed`); process.exit(1); }"),s.push(" else console.log(`\\n All steps passed`);"),s.push("}).catch(err => { console.error(err); process.exit(1); });"),s.join(`
2
- `)}function M(i,n,e,g,c){const p=e?.scenarioId||e?.id||e?._file?.replace(".json","")||"unnamed",r=z(c),a=e?.testFiles||e?.testFile?[e.testFile].flat().filter(Boolean):[];if(a.length){for(const s of a){const t=$.resolve(c,s);if(!y.existsSync(t))return{scenarioId:p,status:"skip",reason:`test file not found: ${s}`}}if(r&&typeof r=="string"){const s=r==="vitest"?`npx vitest run ${a.join(" ")} --reporter verbose`:r==="jest"?`npx jest ${a.join(" ")} --no-coverage`:r==="mocha"?`npx mocha ${a.join(" ")}`:null;if(s){const t=w(s,{shell:!0,cwd:c,encoding:"utf8",timeout:6e4}),h=t.status===0;return{scenarioId:p,status:h?"pass":"fail",output:(t.stdout||"")+(t.stderr||""),runner:r}}}if(r?.custom){const s=w(r.custom,{shell:!0,cwd:c,encoding:"utf8",timeout:6e4});return{scenarioId:p,status:s.status===0?"pass":"fail",output:(s.stdout||"")+(s.stderr||""),runner:"npm test"}}}const m=D(i,n,e,g),u=$.join(E.tmpdir(),`infernoflow-test-${i}-${Date.now()}.mjs`);try{y.writeFileSync(u,m);const s=w(process.execPath,[u],{cwd:c,encoding:"utf8",timeout:3e4}),t=s.status===0;return{scenarioId:p,status:t?"pass":"fail",output:(s.stdout||"")+(s.stderr||""),runner:"ad-hoc",generated:!0}}finally{try{y.unlinkSync(u)}catch{}}}function T(i,n,e,g){const c=J(n),p=c==="frozen"?b("frozen"):c==="stable"?I("stable"):f("experimental"),r=e.length,a=e.filter(t=>t.status==="pass").length,m=e.filter(t=>t.status==="fail").length,u=e.filter(t=>t.status==="skip").length,s=m>0?b("\u2717"):a>0?F("\u2713"):f("\u25CB");console.log(` ${s} ${A(i)} ${f(`[${p}]`)}`);for(const t of e){const h=t.status==="pass"?P:t.status==="fail"?R:L,d=t.generated?f(" (generated)"):t.runner?f(` (${t.runner})`):"";if(console.log(` ${h} ${f(t.scenarioId)}${d}`),t.reason&&console.log(` ${f(t.reason)}`),g&&t.output){const l=t.output.trim().split(`
3
- `).slice(0,10).join(`
4
- `);console.log(l.split(`
5
- `).map(k=>` ${f(k)}`).join(`
6
- `))}}return{total:r,passed:a,failed:m,skipped:u}}async function K(i){const n=(i||[]).slice(1),e=n.includes("--json"),g=n.includes("--bail"),c=n.includes("--generate"),p=n.includes("--all"),r=n.includes("--verbose")||n.includes("-v"),a=n.filter(o=>!o.startsWith("--")&&o!=="-v"),m=process.cwd(),u=$.join(m,"inferno");y.existsSync(u)||(e||console.error(b("\u2717 inferno/ not found. Run: infernoflow init")),process.exit(1));let s=[];const t=N($.join(u,"capabilities.json"));t&&(s=Array.isArray(t)?t:t.capabilities||[]);const h=N($.join(u,"scan.json"));let d;if(a.length?d=a.map(o=>{const j=s.find(v=>v.id===o);return j||(e||console.error(b(`\u2717 Capability "${o}" not found in capabilities.json`)),process.exit(1)),j}):p?d=s:(d=s.filter(o=>O(o.id,u).length>0),d.length||(e||(console.log(),console.log(` ${f("No scenarios registered. Use --all to test all capabilities, or")} `),console.log(` ${f("add scenarios to inferno/scenarios/ first.")}`),console.log()),process.exit(0))),e||(console.log(),console.log(` ${A("\u{1F9EA} infernoflow test")}`),console.log()),c){const o=d[0],v=O(o.id,u)[0]||{},S=h?.capabilities?.find(C=>C.id===o.id),x=D(o.id,o,v,S);console.log(x);return}const l={total:0,passed:0,failed:0,skipped:0,caps:[]};let k=!1;for(const o of d){const j=O(o.id,u),v=h?.capabilities?.find(C=>C.id===o.id);let S=[];if(!j.length)S=[{scenarioId:"(no scenarios)",status:"skip",reason:"register scenarios in inferno/scenarios/"}];else for(const C of j){const _=M(o.id,o,C,v,m);if(S.push(_),g&&_.status==="fail"){k=!0;break}}const x=T(o.id,o,S,r);if(l.total+=x.total,l.passed+=x.passed,l.failed+=x.failed,l.skipped+=x.skipped,l.caps.push({id:o.id,stability:J(o),results:S}),k)break}if(!e){console.log();const o=l.failed>0?b:l.passed>0?F:f;console.log(` ${o(A(String(l.passed)))} passed ${l.failed>0?b(A(String(l.failed))):f("0")} failed ${f(String(l.skipped))} skipped`),k&&console.log(` ${I("(bailed on first failure)")}`),console.log(),l.failed||console.log(f(" \u2500\u2500 infernoflow test complete")),console.log()}e&&console.log(JSON.stringify(l,null,2)),process.exit(l.failed>0?1:0)}export{K as testCommand};
@@ -1,18 +0,0 @@
1
- import*as u from"node:fs";import*as y from"node:path";import{scanTheme as F}from"../theme/scanner.mjs";import{bold as $,cyan as h,gray as i,green as k,yellow as d,red as I}from"../ui/output.mjs";const p="inferno",S=y.join(p,"theme.json"),w=y.join(p,"sessions.jsonl");function j(e){try{return JSON.parse(u.readFileSync(e,"utf8"))}catch{return null}}function N(e){u.existsSync(w)&&u.appendFileSync(w,JSON.stringify(e)+`
2
- `,"utf8")}function C(e,o){const c=[];e?.fonts?.primary!==o?.fonts?.primary&&c.push(`primary font: ${e?.fonts?.primary||"none"} \u2192 ${o?.fonts?.primary||"none"}`),e?.fonts?.mono!==o?.fonts?.mono&&c.push(`mono font: ${e?.fonts?.mono||"none"} \u2192 ${o?.fonts?.mono||"none"}`),e?.colors?.mode!==o?.colors?.mode&&c.push(`color mode: ${e?.colors?.mode||"unknown"} \u2192 ${o?.colors?.mode}`);const l=e?.colors?.palette||{},f=o?.colors?.palette||{};for(const t of new Set([...Object.keys(l),...Object.keys(f)]))l[t]!==f[t]&&c.push(`${t} color: ${l[t]||"none"} \u2192 ${f[t]||"none"}`);const a=e?.cssVars||{},r=o?.cssVars||{},n=Object.keys(r).filter(t=>!a[t]),s=Object.keys(r).filter(t=>a[t]&&a[t]!==r[t]);return n.length&&c.push(`new CSS vars: ${n.slice(0,5).join(", ")}`),s.length&&c.push(`changed CSS vars: ${s.slice(0,5).join(", ")}`),c}function O(e){const{fonts:o,colors:c,cssVars:l,framework:f,stats:a}=e;console.log(`
3
- `+$("\u{1F3A8} Design System")),console.log(" "+"\u2500".repeat(50)),console.log(h(`
4
- Fonts`)),o.primary&&console.log(` Primary : ${o.primary}`),o.mono&&console.log(` Mono : ${o.mono}`),o.all?.length>2&&console.log(i(` All : ${o.all.join(", ")}`)),o.sources?.length&&console.log(i(` Sources : ${o.sources.join(", ")}`)),console.log(h(`
5
- Colors`)+i(` (${c.mode} mode)`));for(const[r,n]of Object.entries(c.palette)){const s=`\x1B[48;2;${parseInt(n.slice(1,3),16)};${parseInt(n.slice(3,5),16)};${parseInt(n.slice(5,7),16)}m \x1B[0m`;console.log(` ${r.padEnd(14)} ${s} ${n}`)}if(Object.keys(l).length){console.log(h(`
6
- CSS Variables`)+i(` (${Object.keys(l).length} found)`));const r=Object.entries(l).slice(0,12);for(const[n,s]of r)console.log(` ${n.padEnd(24)} ${i(s)}`);Object.keys(l).length>12&&console.log(i(` \u2026 and ${Object.keys(l).length-12} more`))}console.log(h(`
7
- Framework`)+` ${f}`),console.log(i(`
8
- Scanned: ${a.styleFiles} style files \xB7 ${a.colorsFound} colors \xB7 ${a.varsFound} CSS vars
9
- `))}async function V(e){const o=n=>e.includes(n),c=o("--dry-run"),l=o("--show")||o("-s"),f=o("--json"),a=o("--watch");if(console.log(`
10
- `+$("\u{1F525} infernoflow \u2014 theme")),console.log(" "+"\u2500".repeat(50)+`
11
- `),u.existsSync(p)||(console.error(I(` \u2718 inferno/ not found \u2014 run: infernoflow init
12
- `)),process.exit(1)),l){const n=j(S);if(!n){console.log(d(` \u26A0 No theme.json yet \u2014 run: infernoflow theme
13
- `));return}if(f){console.log(JSON.stringify(n,null,2));return}O(n);return}const r=()=>{console.log(i(" Scanning style files\u2026"));const n=process.cwd(),s=F(n);if(f){console.log(JSON.stringify(s,null,2));return}if(O(s),c){console.log(d(` \u2691 Dry run \u2014 theme.json not written
14
- `));return}const t=j(S),g={...s,scannedAt:new Date().toISOString()};if(u.writeFileSync(S,JSON.stringify(g,null,2)+`
15
- `,"utf8"),console.log(k(` \u2714 Written \u2192 inferno/theme.json
16
- `)),t){const m=C(t,s);if(m.length){N({ts:new Date().toISOString(),agent:"infernoflow",type:"theme",summary:"Theme changed: "+m.join("; ")}),console.log(d(" \u26A1 Theme changes logged to sessions.jsonl"));for(const b of m)console.log(i(` \u2022 ${b}`));console.log()}}};if(r(),a){console.log(h(` Watching style files for changes\u2026 (Ctrl+C to stop)
17
- `));const{watch:n}=await import("node:fs");let s=null;n(process.cwd(),{recursive:!0},(t,g)=>{if(!g)return;const m=y.extname(g);[".css",".scss",".sass",".less",".styl"].includes(m)&&(s&&clearTimeout(s),s=setTimeout(()=>{console.log(i(`
18
- Change detected: ${g}`)),r()},1e3))}),await new Promise(()=>{})}}export{V as themeCommand};
@@ -1,20 +0,0 @@
1
- import*as n from"node:fs";import*as o from"node:path";import{bold as k,cyan as r,gray as c,green as g,yellow as x,red as O}from"../ui/output.mjs";const m="inferno";function N(l){try{return JSON.parse(n.readFileSync(l,"utf8"))}catch{return null}}async function F(l){const f=process.cwd(),i=l.includes("--dry-run"),v=l.includes("--yes")||l.includes("-y");console.log(`
2
- `+k("\u{1F525} infernoflow upgrade")),console.log(" "+"\u2500".repeat(50)+`
3
- `);const a=o.join(f,m);n.existsSync(a)||(console.error(O(` \u2718 inferno/ not found \u2014 run: infernoflow init --lite first
4
- `)),process.exit(1));const y=o.join(a,".lite"),b=n.existsSync(y),s=N(o.join(a,"contract.json")),j=s?.policyId||o.basename(f),d=s?.capabilities||[];if(!b){console.log(x(` \u26A0 This project is already on the full setup \u2014 nothing to upgrade.
5
- `));return}console.log(c(` Project: ${j}`)),console.log(c(` Capabilities: ${d.length||0}`)),console.log(c(` Mode: lite \u2192 full
6
- `));const u=[],w=(e,t)=>{const p=o.join(f,e);if(n.existsSync(p)){console.log(c(` skipped (exists): ${e}`));return}if(i){console.log(r(` would create: ${e}`));return}n.mkdirSync(o.dirname(p),{recursive:!0}),n.writeFileSync(p,t,"utf8"),console.log(g(` \u2714 Created: ${e}`)),u.push(e)};if(d.length){const e={scenarioId:"happy_path",description:"Basic happy-path covering all capabilities",capabilitiesCovered:d,steps:d.map(t=>({action:t,expect:`${t} works as expected`}))};w(o.join(m,"scenarios","happy_path.json"),JSON.stringify(e,null,2)+`
7
- `)}else i||n.mkdirSync(o.join(a,"scenarios"),{recursive:!0}),console.log(c(" created: inferno/scenarios/ (empty \u2014 add scenarios as you define capabilities)"));w(o.join(m,"CHANGELOG.md"),`# Changelog \u2014 ${j}
8
-
9
- ## Unreleased
10
-
11
- - Upgraded from lite setup
12
-
13
- ## 0.1.0 \u2014 Initial release
14
-
15
- - Project initialized with infernoflow
16
- `),!i&&s&&(s.rules={docsRequiredOnCapabilityChange:!0,requireScenarioForEachCapability:!1,requireChangelogOnCapabilityChange:!0},delete s.lite,n.writeFileSync(o.join(a,"contract.json"),JSON.stringify(s,null,2)+`
17
- `),console.log(g(" \u2714 Updated: inferno/contract.json (added rules)")),u.push("inferno/contract.json"));const h=o.join(f,"package.json");if(n.existsSync(h)&&!i){const e=JSON.parse(n.readFileSync(h,"utf8"));e.scripts=e.scripts||{};let t=!1;const p={"inferno:check":"infernoflow check","inferno:context":"infernoflow context","inferno:theme":"infernoflow theme"};for(const[S,C]of Object.entries(p))e.scripts[S]||(e.scripts[S]=C,t=!0);t&&(n.writeFileSync(h,JSON.stringify(e,null,2)+`
18
- `),console.log(g(" \u2714 Updated: package.json scripts (inferno:check, inferno:context, inferno:theme)")),u.push("package.json"))}else i&&console.log(r(" would update: package.json scripts"));if(!i&&n.existsSync(y)&&(n.unlinkSync(y),console.log(g(" \u2714 Removed .lite marker \u2014 now on full setup"))),console.log(),i){console.log(x(` \u2691 Dry run \u2014 nothing written. Remove --dry-run to apply.
19
- `));return}if(!u.length){console.log(c(` Nothing new to create \u2014 already fully set up.
20
- `));return}console.log(" "+k("Upgrade complete!")),console.log(" "+r("\u2192")+" Run "+r("infernoflow check")+" to validate the contract"),console.log(" "+r("\u2192")+" Run "+r("infernoflow vibe")+" to start auto-sync mode"),console.log()}export{F as upgradeCommand};
@@ -1,7 +0,0 @@
1
- import*as p from"node:fs";import*as t from"node:path";import{fileURLToPath as D}from"node:url";import{spawnSync as I}from"node:child_process";import{warn as x,bold as y,cyan as N,gray as d,green as L,yellow as O}from"../ui/output.mjs";const H=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".java",".cs",".rb",".swift"]),ie=new Set(["node_modules",".git","dist","build","out",".next",".angular","vendor","coverage","__pycache__"]);function M(e){const i=["src","lib","app","pages","components","server","api","tests","test","__tests__","spec"].filter(o=>p.existsSync(t.join(e,o)));return i.length?i.map(o=>t.join(e,o)):[e]}function U(e){return H.has(t.extname(e).toLowerCase())}const G=new Set(["package.json","package-lock.json","yarn.lock","pnpm-lock.yaml","bun.lockb","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","poetry.lock","Cargo.toml","Cargo.lock","go.mod","go.sum","Gemfile","Gemfile.lock","composer.json","composer.lock"]),J=/(?:^|[\\/])(?:test|tests|spec|__tests__)[\\/].+\.(?:mjs|cjs|js|jsx|ts|tsx|py|go|rb|java|cs)$|\.(?:test|spec)\.(?:mjs|cjs|js|jsx|ts|tsx|py|go|rb|java|cs)$/i;function X(e){return G.has(t.basename(e))}function Y(e){return J.test(e)}function k(e,n){return`
2
- ${O("\u26A0")} ${e}
3
- ${d("\u2192")} ${N(n)}`}const R=new Map,j=new Map,C=250,q=[5,12,25];function B(e){const n=Date.now(),i=j.get(e)||0;if(n-i<C)return null;j.set(e,n);const o=(R.get(e)||0)+1;return R.set(e,o),o}function K(e,n,i){if(!q.includes(n))return null;const o=t.relative(i,e);return k(`${y(o)} edited ${n}\xD7 this session \u2014 stuck on something?`,`infernoflow log "<what's tripping you up>" --type gotcha`)}function Z(e,n){const i=t.relative(n,e);return k(`dependency manifest changed: ${y(i)}`,'infernoflow log "<switched from X to Y because Z>" --type decision')}function z(e,n){const i=t.relative(n,e);return k(`test file removed: ${y(i)} \u2014 was it failing?`,'infernoflow log "<why the test was removed>" --type attempt --result failed')}function Q(e,n){const i=t.join(n,"capability-map.json");if(!p.existsSync(i))return{relevant:!0,reason:"no cap-map \u2014 suggesting broadly"};let o;try{o=JSON.parse(p.readFileSync(i,"utf8"))}catch{return{relevant:!0,reason:"cap-map unreadable"}}const u=[];for(const w of e){const g=t.relative(process.cwd(),w).replace(/\\/g,"/");for(const[f,a]of Object.entries(o))g.startsWith(f.replace(/\\/g,"/"))&&u.push(...a)}return u.length>0?{relevant:!0,reason:`touches: ${[...new Set(u)].slice(0,3).join(", ")}`}:{relevant:!1,reason:"no mapped capabilities affected"}}function V(e,n,i,o,u){const g=`code changes in ${e.map(f=>t.basename(f,t.extname(f))).slice(0,3).join(", ")}`;if(u||process.stdout.write(` ${O("\u27F3")} suggesting from ${y(String(e.length))} changed file${e.length!==1?"s":""}\u2026 `),o){u||console.log(d("(dry run)"));return}try{I(process.execPath,[t.join(t.dirname(t.dirname(t.dirname(D(import.meta.url)))),"bin","infernoflow.mjs"),"suggest",g,"--json"],{cwd:n,encoding:"utf8",timeout:3e4,stdio:"ignore"}),u||console.log(L("done"))}catch{u||console.log(d("skipped (no changes)"))}try{const a=I(process.execPath,[t.join(t.dirname(t.dirname(t.dirname(D(import.meta.url)))),"bin","infernoflow.mjs"),"check","--json"],{cwd:n,encoding:"utf8",timeout:15e3}).stdout?.trim();if(a){const h=JSON.parse(a);if(h.status==="error"||h.status==="warning")p.writeFileSync(t.join(i,"WATCH.log"),a+`
4
- `),u||x("Contract issues detected \u2014 see inferno/WATCH.log");else{const S=t.join(i,"WATCH.log");p.existsSync(S)&&p.unlinkSync(S)}}}catch{}}async function re(e){const n=e.slice(1),i=n.includes("--dry-run"),o=n.includes("--silent"),u=n.includes("--no-tips"),w=!o&&!u,g=n.indexOf("--interval"),f=((g!==-1?parseFloat(n[g+1]):3)||3)*1e3,a=process.cwd(),h=t.join(a,"inferno");p.existsSync(h)||(x("inferno/ not found. Run: infernoflow init"),process.exit(1));const S=n.filter(r=>!r.startsWith("-")&&r!==String(n[g+1])),$=(S.length?S.map(r=>t.resolve(a,r)):M(a)).filter(r=>p.existsSync(r));$.length||(x("No valid directories to watch."),process.exit(1)),o||(console.log(),console.log(` ${y("\u{1F525} infernoflow watch")} ${d("(Ctrl+C to stop)")}`),console.log(),$.forEach(r=>console.log(` ${N("watching")} ${d(t.relative(a,r)||".")}`)),console.log(` ${d(`debounce: ${f/1e3}s`)}`),console.log());let b=null;const T=new Set,F=new Set,m=[],E=(r,c)=>{if(w&&X(c)&&p.existsSync(c)){const s="dep:"+c,l=Date.now();l-(j.get(s)||0)>=C&&(j.set(s,l),m.push(Z(c,a)))}if(w&&Y(c))if(r==="rename"&&!p.existsSync(c)){const s="del:"+c,l=Date.now();l-(j.get(s)||0)>=C&&(j.set(s,l),m.push(z(c,a)))}else p.existsSync(c)&&F.add(c);if(!U(c)){if(m.length&&!o)for(const s of m.splice(0))console.log(s);return}if(T.add(c),w){const s=B(c);if(s!==null){const l=K(c,s,a);l&&m.push(l)}}b&&clearTimeout(b),b=setTimeout(()=>{const s=Array.from(T);if(T.clear(),!o){const _=s.map(A=>t.relative(a,A)).slice(0,3).join(", ");process.stdout.write(`
5
- ${d(new Date().toLocaleTimeString())} ${y(_)}${s.length>3?` +${s.length-3} more`:""} `)}const{relevant:l,reason:W}=Q(s,h);if(l?V(s,a,h,i,o):o||console.log(d(`skip (${W})`)),m.length&&!o)for(const _ of m.splice(0))console.log(_)},f)},v=[];for(const r of $)try{const c=p.watch(r,{recursive:!0},(s,l)=>{l&&E(s,t.join(r,l))});v.push(c)}catch(c){o||x(`Cannot watch ${r}: ${c.message}`)}try{const r=p.watch(a,{recursive:!1},(c,s)=>{s&&E(c,t.join(a,s))});v.push(r)}catch{}v.length||(x("No directories could be watched."),process.exit(1)),process.on("SIGINT",()=>{v.forEach(r=>r.close()),o||(console.log(`
6
-
7
- Stopped.`),console.log()),process.exit(0)}),await new Promise(()=>{})}export{re as watchCommand};
@@ -1,4 +0,0 @@
1
- import*as F from"node:fs";import*as h from"node:path";import{execSync as W}from"node:child_process";import{bold as C,cyan as V,gray as n,green as x,yellow as I,red as L}from"../ui/output.mjs";function O(o){try{return JSON.parse(F.readFileSync(o,"utf8"))}catch{return null}}function D(o,t){try{return W(o,{cwd:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}const N={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},z={frozen:L,stable:I,experimental:x};function v(o){return o?.stability||"experimental"}function B(o,t,p,i){const c=[],l=o.includes("/")||o.includes("\\")||o.includes("."),r=l?h.relative(i,h.resolve(i,o)):null;for(const e of t){const d=e.codeAnalysis;if(!d)continue;const f=p.find(y=>y.id===e.id)||{};if(l&&(d.sourceFiles||[]).some(u=>u===r||u.endsWith(r)||r?.endsWith(u))){c.push({capId:e.id,capEntry:e,capFull:f,matchedVia:"file",score:1});continue}if(!l&&(d.functions||[]).some(u=>u.toLowerCase()===o.toLowerCase()||u.toLowerCase().includes(o.toLowerCase())||o.toLowerCase().includes(u.toLowerCase()))){c.push({capId:e.id,capEntry:e,capFull:f,matchedVia:"function",score:1});continue}}return c}function J(o,t){const p=h.join(t,"scenarios");if(!F.existsSync(p))return[];const i=[];for(const c of F.readdirSync(p))if(c.endsWith(".json"))try{const l=JSON.parse(F.readFileSync(h.join(p,c),"utf8")),r=l.capabilitiesCovered||l.capabilities||[];(r.includes(o)||r.some(e=>e.toLowerCase()===o.toLowerCase()))&&i.push({file:c,scenario:l})}catch{}return i}function M(o,t,p=5){if(!o)return[];const i=h.relative(t,h.resolve(t,o)),c=D(`git log --follow --format="%h|%aI|%ae|%s" -${p} -- ${JSON.stringify(i)}`,t);return c?c.split(`
2
- `).filter(Boolean).map(l=>{const[r,e,d,...f]=l.split("|");return{hash:r?.trim(),date:e?.trim()?new Date(e.trim()).toLocaleDateString():"",author:d?.trim(),subject:f.join("|").trim()}}):[]}function R(o,t){if(!o)return null;const p=h.relative(t,h.resolve(t,o)),i=D(`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(p)}`,t);if(!i)return null;const c=i.split(`
3
- `).filter(Boolean);if(!c.length)return null;const[l,r,e,...d]=c[c.length-1].split("|");return{hash:l?.trim(),date:r?.trim()?new Date(r.trim()).toLocaleDateString():"",author:e?.trim(),subject:d.join("|").trim()}}function G(o,t,p,i,c,l,r){const{capId:e,capEntry:d,capFull:f,matchedVia:y}=o,u=v(f),E=N[u]||"\u{1F30A}",m=z[u]||x;console.log(),console.log(C(` ${E} ${m(e)}`)),(f.name||f.title)&&console.log(n(` ${f.name||f.title}`)),f.description&&console.log(n(` ${f.description}`)),console.log(),console.log(n(" matched via: ")+y+n(" \u2192 ")+V(r)),console.log(n(" stability: ")+m(u));const g=d.codeAnalysis?.sourceFiles||[];g.length&&console.log(n(" source files: ")+g.join(", "));const s=d.codeAnalysis?.functions||[];s.length&&console.log(n(" functions: ")+s.join(", "));const b=d.codeAnalysis?.services||[];b.length&&console.log(n(" uses: ")+V(b.join(", ")));const w=d.codeAnalysis?.throws||[];if(w.length&&console.log(n(" throws: ")+I(w.join(", "))),c){const a=c.deps?.[e]||[],$=c.dependents?.[e]||[];a.length&&console.log(n(" calls: ")+a.map(j=>{const S=l.find(A=>A.id===j);return`${N[v(S)]||"\u{1F30A}"} ${j}`}).join(" ")),$.length&&console.log(n(" called by: ")+$.map(j=>{const S=l.find(A=>A.id===j);return`${N[v(S)]||"\u{1F30A}"} ${j}`}).join(" "))}if(console.log(),t.length>0){console.log(C(" Scenarios that cover this capability:"));for(const{scenario:a}of t){const $=a.steps?.length||0;console.log(` ${x("\u2714")} ${a.scenarioId||a.description||a.file}`),a.description&&console.log(n(` ${a.description}`)),$&&console.log(n(` ${$} step(s)`))}console.log()}else console.log(I(" \u26A0 No scenarios found for this capability.")),console.log(n(` Run: infernoflow suggest "add scenario for ${e}"`)),console.log();if(i&&(console.log(C(" Origin:")),console.log(` ${n("first commit:")} ${i.hash} \xB7 ${i.date} \xB7 ${i.author}`),console.log(` ${n("subject:")} ${i.subject}`),console.log()),p.length>0){console.log(C(" Recent changes:"));for(const a of p.slice(0,4))console.log(` ${n(a.hash)} ${n(a.date.padEnd(12))} ${a.subject}`);console.log()}u==="frozen"&&(console.log(L(" \u{1F9CA} This capability is FROZEN \u2014 do not modify without explicit instruction.")),console.log())}async function _(o){const t=(o||[]).slice(1),p=t.includes("--json"),i=t.indexOf("--function"),c=i!==-1?t[i+1]:null,l=t.find((g,s)=>!g.startsWith("--")&&(i===-1||s!==i+1));l||(console.error(L("\u2717 Usage: infernoflow why <file-or-function> [--function <name>] [--json]")),console.error(n(" Examples:")),console.error(n(" infernoflow why src/auth.ts")),console.error(n(" infernoflow why loginUser")),process.exit(1));const r=process.cwd(),e=h.join(r,"inferno"),d=O(h.join(e,"scan.json"));d||(console.error(L("\u2717 inferno/scan.json not found \u2014 run `infernoflow scan` first.")),process.exit(1));let f=[];const y=O(h.join(e,"capabilities.json"));y&&(f=Array.isArray(y)?y:y.capabilities||[]);const u=O(h.join(e,"graph.json")),E=d.capabilities||[];let m=B(l,E,f,r);if(c&&m.length>1&&(m=m.filter(g=>(g.capEntry.codeAnalysis?.functions||[]).some(s=>s.toLowerCase().includes(c.toLowerCase())))),m.length===0&&(console.log(),console.log(I(` No capability found matching: ${C(l)}`)),console.log(n(" Tip: run `infernoflow scan` to update code analysis, then try again.")),console.log(n(" Tip: use a function name or relative file path.")),console.log(),process.exit(0)),p){const g=m.map(s=>{const b=J(s.capId,e),w=s.capEntry.codeAnalysis?.sourceFiles||[],a=M(w[0],r),$=R(w[0],r);return{capId:s.capId,name:s.capFull.name||s.capFull.title,stability:v(s.capFull),matchedVia:s.matchedVia,sourceFiles:w,functions:s.capEntry.codeAnalysis?.functions||[],services:s.capEntry.codeAnalysis?.services||[],throws:s.capEntry.codeAnalysis?.throws||[],deps:u?.deps?.[s.capId]||[],dependents:u?.dependents?.[s.capId]||[],scenarios:b.map(j=>j.scenario?.scenarioId||j.file),firstCommit:$,recentHistory:a}});console.log(JSON.stringify(g,null,2));return}console.log(n(`
4
- infernoflow why \u2192 ${C(l)}`)),console.log(n(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 g of m){const s=g.capEntry.codeAnalysis?.sourceFiles||[],b=J(g.capId,e),w=M(s[0],r),a=R(s[0],r);G(g,b,w,a,u,f,l)}}export{_ as whyCommand};