infernoflow 0.40.4 → 0.40.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog — infernoflow
2
2
 
3
+ ## 0.40.5 — 2026-05-02
4
+
5
+ ### Fixed
6
+ - **`infernoflow scaffold` ID consistency** — scaffold previously rejected `UserSearch` with "Invalid capability ID — use lowercase kebab-case", but every other contracts command (scan, adopt, freeze, contract.json itself) uses PascalCase IDs like `CreateItem`. Anyone seeing the existing capabilities and trying to scaffold a similar one was blocked. Now scaffold accepts kebab-case, snake_case, camelCase, PascalCase, or space-separated input and normalizes to the canonical PascalCase. Duplicate-detection matches either the canonical or the user-typed form.
7
+
3
8
  ## 0.40.4 — 2026-05-02
4
9
 
5
10
  ### Added
@@ -1,8 +1,8 @@
1
- import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as _,gray as l,green as C,yellow as Z,red as b}from"../ui/output.mjs";function T(t){try{return JSON.parse(p.readFileSync(t,"utf8"))}catch{return null}}function J(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 P(t){const e=d(t);return e.charAt(0).toLowerCase()+e.slice(1)}function K(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(" ")}function Q(t){const e=t.split(/[-_]/);if(e.length===1)return P(t);const o=["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(o.includes(i)){const n={auth:"authenticate",get:"get",list:"list",send:"send",check:"check",notify:"notify"}[i]||i,u=e.slice(1).map((g,A)=>A===0?g.charAt(0).toUpperCase()+g.slice(1):g).join("");return n+u.charAt(0).toUpperCase()+u.slice(1)}if(o.includes(r)){const s=e.slice(0,-1).map((n,u)=>u===0?n.charAt(0).toUpperCase()+n.slice(1):n).join("");return r+s}return P(t)}function X(t,e,o){if(t?.capabilities?.length){const s=t.capabilities.flatMap(n=>n.codeAnalysis?.sourceFiles||[]).map(n=>a.extname(n));if(s.filter(n=>n===".ts").length>s.filter(n=>n===".js").length)return"ts";if(s.includes(".py"))return"py";if(s.includes(".go"))return"go";if(s.some(n=>n===".js"||n===".mjs"))return"js"}const r=e?.language||e?.lang;return r?r.toLowerCase().replace("javascript","js").replace("typescript","ts"):p.existsSync(a.join(o,"tsconfig.json"))?"ts":p.existsSync(a.join(o,"pyproject.toml"))?"py":p.existsSync(a.join(o,"go.mod"))?"go":"js"}function Y(t,e){if(!t?.capabilities?.length)return null;const o=t.capabilities.flatMap(s=>s.codeAnalysis?.sourceFiles||[]);if(!o.length)return null;const r={};for(const s of o){const n=a.dirname(s).split("/")[0];r[n]=(r[n]||0)+1}const i=Object.entries(r).sort((s,n)=>n[1]-s[1])[0];return i?i[0]:null}function ee(t){if(!t?.capabilities?.length)return[];const e=t.capabilities.flatMap(o=>o.codeAnalysis?.services||[]);return[...new Set(e)]}function te(t,e,o,r,i){const s=d(t),n=`${s}Error`,u=L("ts",i);return`/**
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
3
  * ${e}
4
4
  *
5
- * ${o}
5
+ * ${n}
6
6
  *
7
7
  * @capability ${t}
8
8
  * @stability experimental
@@ -11,10 +11,10 @@ ${u}
11
11
 
12
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
13
 
14
- export class ${n} extends Error {
14
+ export class ${o} extends Error {
15
15
  constructor(message: string, public readonly code?: string) {
16
16
  super(message);
17
- this.name = "${n}";
17
+ this.name = "${o}";
18
18
  }
19
19
  }
20
20
 
@@ -37,12 +37,12 @@ export interface ${s}Result {
37
37
  */
38
38
  export async function ${r}(input: ${s}Input): Promise<${s}Result> {
39
39
  // TODO: implement
40
- throw new ${n}("Not implemented yet");
40
+ throw new ${o}("Not implemented yet");
41
41
  }
42
- `}function ne(t,e,o,r,i){const n=`${d(t)}Error`,u=L("js",i);return`/**
42
+ `}function se(t,e,n,r,i){const o=`${d(t)}Error`,u=J("js",i);return`/**
43
43
  * ${e}
44
44
  *
45
- * ${o}
45
+ * ${n}
46
46
  *
47
47
  * @capability ${t}
48
48
  * @stability experimental
@@ -51,10 +51,10 @@ ${u}
51
51
 
52
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
53
 
54
- export class ${n} extends Error {
54
+ export class ${o} extends Error {
55
55
  constructor(message, code) {
56
56
  super(message);
57
- this.name = "${n}";
57
+ this.name = "${o}";
58
58
  this.code = code;
59
59
  }
60
60
  }
@@ -70,12 +70,12 @@ export class ${n} extends Error {
70
70
  */
71
71
  export async function ${r}(input = {}) {
72
72
  // TODO: implement
73
- throw new ${n}("Not implemented yet");
73
+ throw new ${o}("Not implemented yet");
74
74
  }
75
- `}function oe(t,e,o,r){const i=d(t);return`"""
75
+ `}function ie(t,e,n,r){const i=d(t);return`"""
76
76
  ${e}
77
77
 
78
- ${o}
78
+ ${n}
79
79
 
80
80
  capability: ${t}
81
81
  stability: experimental
@@ -97,9 +97,9 @@ async def ${r.replace(/([A-Z])/g,"_$1").toLowerCase().replace(/^_/,"")}(input: d
97
97
  TODO: implement this function.
98
98
  """
99
99
  raise ${i}Error("Not implemented yet")
100
- `}function se(t,e,o,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
100
+ `}function ce(t,e,n,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
101
101
  //
102
- // ${o}
102
+ // ${n}
103
103
  //
104
104
  // capability: ${t}
105
105
  // stability: experimental
@@ -115,10 +115,10 @@ var Err${d(t)} = errors.New("${t}: operation failed")
115
115
  func ${d(r)}(input map[string]any) (map[string]any, error) {
116
116
  return nil, Err${d(t)}
117
117
  }
118
- `}function L(t,e){if(!e.length)return"";const o=[];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&&o.push(s)}}return o.length?o.join(`
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
119
  `)+`
120
- `:""}function re(t,e,o){return{scenarioId:`${t}-happy-path`,description:`Happy path for ${e}`,capabilitiesCovered:[t],createdAt:new Date().toISOString(),steps:[{step:1,action:`Call ${o} with valid input`,expected:"Returns success result"},{step:2,action:`Call ${o} with invalid input`,expected:"Throws appropriate error"}]}}function ie({id:t,filePath:e,scenarioPath:o,lang:r,fn:i,dryRun:s}){console.log(),console.log(U(` \u{1F30A} ${C(t)}`)),console.log(l(" stability: experimental \u2014 free to evolve")),console.log(),console.log(l(" Generated:")),console.log(` ${C("+")} ${_(e)} ${l(`(${r} source skeleton)`)}`),console.log(` ${C("+")} ${_("inferno/capabilities.json")} ${l("(capability registered)")}`),console.log(` ${C("+")} ${_(o)} ${l("(placeholder scenario)")}`),console.log(),s?console.log(Z(" [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 le(t){const e=(t||[]).slice(1),o=e.includes("--dry-run"),r=e.includes("--json"),i=e.indexOf("--lang"),s=i!==-1?e[i+1]:null,n=e.indexOf("--dir"),u=n!==-1?e[n+1]:null,g=e.indexOf("--description"),A=g!==-1?e[g+1]:null,M=new Set([i+1,n+1,g+1].filter(f=>f>0)),c=e.find((f,v)=>!f.startsWith("--")&&!M.has(v));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 payment-refund")),process.exit(1)),/^[a-z][a-z0-9-]*$/.test(c)||(console.error(b(`\u2717 Invalid capability ID: "${c}"`)),console.error(l(" Use lowercase kebab-case: payment-refund, user-auth, etc.")),process.exit(1));const h=process.cwd(),D=a.join(h,"inferno"),I=a.join(D,"capabilities.json");p.existsSync(I)||(console.error(b("\u2717 inferno/capabilities.json not found \u2014 run `infernoflow init` first.")),process.exit(1));let w=[];const S=T(I);S&&(w=Array.isArray(S)?S:S.capabilities||[]),w.some(f=>f.id===c)&&(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=T(a.join(D,"scan.json")),q=T(a.join(D,"developer-profile.json")),x=s||X(E,q,h),z=u||Y(E,h)||"src",G={ts:".ts",js:".js",py:".py",go:".go"}[x]||".js",m=K(c),j=A||`TODO: describe ${m}`,y=Q(c),R=ee(E);let $;x==="ts"?$=te(c,m,j,y,R):x==="py"?$=oe(c,m,j,y):x==="go"?$=se(c,m,j,y):$=ne(c,m,j,y,R);const B=P(c)+G,O=a.join(z,B),N=a.join(h,O),k=a.join("inferno","scenarios",`${c}.json`),F=a.join(h,k),H=re(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:O,scenarioPath:k,primaryFn:y,dryRun:o,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")),!o){const f=a.dirname(N);p.existsSync(f)||p.mkdirSync(f,{recursive:!0}),p.existsSync(N)&&(console.error(b(` \u2717 File already exists: ${O}`)),console.error(l(" Delete it first or choose a different --dir")),process.exit(1)),p.writeFileSync(N,$,"utf8"),w.push(V),J(I,w);const v=a.join(h,"inferno","scenarios");p.existsSync(v)||p.mkdirSync(v,{recursive:!0}),p.existsSync(F)||J(F,H)}const W=$.split(`
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
122
  `).slice(0,12).map(f=>" "+f).join(`
123
123
  `);console.log(l(`
124
- Preview:`)),console.log(l(W)),console.log(l(" ...")),ie({id:c,filePath:O,scenarioPath:k,lang:x,fn:y,dryRun:o})}export{le as scaffoldCommand};
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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.40.4",
3
+ "version": "0.40.5",
4
4
  "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {