infernoflow 0.40.3 → 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 +17 -0
- package/dist/lib/commands/scaffold.mjs +19 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
8
|
+
## 0.40.4 — 2026-05-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **GitHub Action PR-comment v2** — the action now shows the matched file inline next to high-relevance gotchas, surfaces failed-attempts on the same surface as a "don't repeat" section, and adds an `infernoflow impact <cap>` tip when frozen capabilities are touched. Comment header now shows total session-memory size + gotcha/decision counts.
|
|
12
|
+
- **Idempotent comment marker** — the action embeds `<!-- infernoflow-action:pr-memory-check -->` so repeated runs reliably edit the same comment instead of risking duplicates from string-match drift.
|
|
13
|
+
- **`min-type: always`** — opt-in mode that posts the comment even when nothing relevant is in the diff (useful for docs/landing screenshots).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **`action/dist/index.js` rebuilt** from the new src. Workflow template now references `ronmiz/infernoflow@action-v2`.
|
|
17
|
+
- **Comment marker recovery** — v2 still recognises v1 comments (matches by title), so existing PRs migrate cleanly without leaving orphaned comments.
|
|
18
|
+
- **Action runtime cleanup** — replaced dead `upsertComment` workaround with proper PATCH support; HTTP helpers consolidated into a single `httpsRequest(method, ...)` with `httpsGet/Post/Patch` thin wrappers.
|
|
19
|
+
|
|
3
20
|
## 0.40.3 — 2026-05-02
|
|
4
21
|
|
|
5
22
|
### Added
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import*as p from"node:fs";import*as a from"node:path";import{bold as U,cyan as
|
|
2
|
-
`)}function d(t){return t.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}function
|
|
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
|
-
* ${
|
|
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 ${
|
|
14
|
+
export class ${o} extends Error {
|
|
15
15
|
constructor(message: string, public readonly code?: string) {
|
|
16
16
|
super(message);
|
|
17
|
-
this.name = "${
|
|
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 ${
|
|
40
|
+
throw new ${o}("Not implemented yet");
|
|
41
41
|
}
|
|
42
|
-
`}function
|
|
42
|
+
`}function se(t,e,n,r,i){const o=`${d(t)}Error`,u=J("js",i);return`/**
|
|
43
43
|
* ${e}
|
|
44
44
|
*
|
|
45
|
-
* ${
|
|
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 ${
|
|
54
|
+
export class ${o} extends Error {
|
|
55
55
|
constructor(message, code) {
|
|
56
56
|
super(message);
|
|
57
|
-
this.name = "${
|
|
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 ${
|
|
73
|
+
throw new ${o}("Not implemented yet");
|
|
74
74
|
}
|
|
75
|
-
`}function
|
|
75
|
+
`}function ie(t,e,n,r){const i=d(t);return`"""
|
|
76
76
|
${e}
|
|
77
77
|
|
|
78
|
-
${
|
|
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
|
|
100
|
+
`}function ce(t,e,n,r){const i=t.split("-")[0];return`// Package ${i} implements ${e}.
|
|
101
101
|
//
|
|
102
|
-
// ${
|
|
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
|
|
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
|
|
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")),!
|
|
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(" ...")),
|
|
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.
|
|
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": {
|