infernoflow 0.10.6 → 0.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -166,6 +166,9 @@ infernoflow doc-gate --json
166
166
  | `infernoflow status` | At-a-glance health of your contract |
167
167
  | `infernoflow suggest` | Generate an AI prompt, apply capability updates |
168
168
  | `infernoflow implement` | Generate implementation prompts for coding agents |
169
+ | `infernoflow run` | One-command local-model flow with validation and rollback |
170
+ | `infernoflow pr-impact` | Analyze changed files and infer capability/doc drift |
171
+ | `infernoflow sync --auto` | Deterministic sync flow for agents (skeleton) |
169
172
  | `infernoflow check` | Full validation: contract, capabilities, scenarios, changelog |
170
173
  | `infernoflow doc-gate` | Fails if code changed but docs weren't updated |
171
174
  | `infernoflow context` | Build/persist AI session context for this project |
@@ -183,6 +186,14 @@ infernoflow implement "..." --mode both
183
186
  infernoflow implement "..." --mode cursor
184
187
  infernoflow implement "..." --mode generic
185
188
  infernoflow implement "..." --mode both --copy
189
+ infernoflow run "add favorite badge to tasks"
190
+ infernoflow run "sync check" --dry-run
191
+ infernoflow run "sync check" --json
192
+ infernoflow pr-impact
193
+ infernoflow pr-impact --json
194
+ infernoflow sync --auto
195
+ infernoflow sync --auto --json
196
+ npm run inferno:hooks # install local git hooks (after init)
186
197
  infernoflow check --json # machine-readable output for CI
187
198
  infernoflow check --skip-doc-gate
188
199
  infernoflow status --json # machine-readable status summary
@@ -225,6 +236,43 @@ Proposed Changes
225
236
 
226
237
  Works with any AI — Claude, ChatGPT, GitHub Copilot, Cursor, or your own setup.
227
238
 
239
+ ## `infernoflow run` — zero copy/paste flow
240
+
241
+ Run one command and infernoflow will:
242
+ 1. Detect drift (`pr-impact`)
243
+ 2. Generate suggestion via local model
244
+ 3. Apply inferno updates
245
+ 4. Validate with `check`
246
+ 5. Roll back automatically if validation fails
247
+
248
+ ```bash
249
+ infernoflow run "add favorite badge to tasks and filter by favorite"
250
+ ```
251
+
252
+ Machine mode:
253
+
254
+ ```bash
255
+ infernoflow run "sync check" --json
256
+ ```
257
+
258
+ Local model configuration (required):
259
+
260
+ ```bash
261
+ # default provider: ollama
262
+ set INFERNO_LOCAL_PROVIDER=ollama
263
+ set INFERNO_LOCAL_ENDPOINT=http://127.0.0.1:11434/api/generate
264
+ set INFERNO_LOCAL_MODEL=llama3.1:8b
265
+ ```
266
+
267
+ Optional OpenAI-compatible local server:
268
+
269
+ ```bash
270
+ set INFERNO_LOCAL_PROVIDER=openai
271
+ set INFERNO_LOCAL_ENDPOINT=http://127.0.0.1:1234/v1/chat/completions
272
+ set INFERNO_LOCAL_MODEL=local-model
273
+ set INFERNO_LOCAL_API_KEY=local
274
+ ```
275
+
228
276
  ## `infernoflow implement` — code-agent execution prompts
229
277
 
230
278
  Generate coding prompts from your project context and inferno contract:
@@ -272,13 +320,26 @@ Recommended chain:
272
320
 
273
321
  ```yaml
274
322
  # .github/workflows/ci.yml
275
- - name: infernoflow check
276
- run: npx infernoflow check --json
323
+ - name: infernoflow run
324
+ run: npx infernoflow run "sync check" --json
277
325
  env:
326
+ INFERNO_LOCAL_PROVIDER: ollama
327
+ INFERNO_LOCAL_ENDPOINT: http://127.0.0.1:11434/api/generate
328
+ INFERNO_LOCAL_MODEL: llama3.1:8b
278
329
  BASE_SHA: ${{ github.event.pull_request.base.sha }}
279
330
  HEAD_SHA: ${{ github.event.pull_request.head.sha }}
280
331
  ```
281
332
 
333
+ When you run `infernoflow init`, it now scaffolds:
334
+ - `scripts/inferno-install-hooks.mjs`
335
+ - `.github/workflows/infernoflow-check.yml`
336
+
337
+ Install local hooks once per clone:
338
+
339
+ ```bash
340
+ npm run inferno:hooks
341
+ ```
342
+
282
343
  ## Release Checklist
283
344
 
284
345
  ```bash
@@ -11,6 +11,9 @@ const COMMAND_DESCRIPTIONS = {
11
11
  init: "Scaffold inferno/ in your project (or adopt existing project)",
12
12
  check: "Validate contract, capabilities, scenarios, changelog",
13
13
  status: "Show contract health at a glance",
14
+ "pr-impact": "Summarize PR impact on capabilities and docs",
15
+ sync: "Run deterministic inferno sync flow",
16
+ run: "One-command detect/propose/apply/validate flow",
14
17
  "doc-gate": "Fail if code changed but docs were not updated",
15
18
  suggest: "Generate AI prompt + apply capability updates",
16
19
  implement: "Generate code-agent implementation prompt(s)",
@@ -21,6 +24,9 @@ const COMMAND_HANDLERS = {
21
24
  init: async (args) => (await import("../lib/commands/init.mjs")).initCommand(args),
22
25
  check: async (args) => (await import("../lib/commands/check.mjs")).checkCommand(args),
23
26
  status: async (args) => (await import("../lib/commands/status.mjs")).statusCommand(args),
27
+ "pr-impact": async (args) => (await import("../lib/commands/prImpact.mjs")).prImpactCommand(args),
28
+ sync: async (args) => (await import("../lib/commands/syncAuto.mjs")).syncCommand(args),
29
+ run: async (args) => (await import("../lib/commands/run.mjs")).runCommand(args),
24
30
  suggest: async (args) => (await import("../lib/commands/suggest.mjs")).suggestCommand(args),
25
31
  implement: async (args) => (await import("../lib/commands/implement.mjs")).implementCommand(args),
26
32
  context: async (args) => (await import("../lib/commands/context.mjs")).contextCommand(args),
@@ -66,6 +72,11 @@ ${formatCommandsHelp()}
66
72
  --mode <type> cursor | generic | both (default: both)
67
73
  --copy, -c Copy generated prompt(s) to clipboard
68
74
 
75
+ ${bold("run options:")}
76
+ --dry-run Execute full flow without writing files
77
+ --json Emit machine-readable events and result payload
78
+ --no-rollback Keep changes even if validation fails
79
+
69
80
  ${bold("Typical workflow:")}
70
81
  ${gray('1. infernoflow context --intent "what I want to build"')}
71
82
  ${gray("2. [paste inferno/CONTEXT.md into Claude / Cursor / Copilot]")}
@@ -77,6 +88,9 @@ ${formatCommandsHelp()}
77
88
  ${gray("status --json")}
78
89
  ${gray("check --json")}
79
90
  ${gray("doc-gate --json")}
91
+ ${gray("pr-impact --json")}
92
+ ${gray("sync --auto --json")}
93
+ ${gray('run "task" --json')}
80
94
  `;
81
95
 
82
96
  const [, , cmd, ...rest] = process.argv;
@@ -0,0 +1,88 @@
1
+ const DEFAULT_TIMEOUT_MS = 45000;
2
+
3
+ function withTimeout(ms) {
4
+ const controller = new AbortController();
5
+ const timer = setTimeout(() => controller.abort(), ms);
6
+ return { controller, timer };
7
+ }
8
+
9
+ async function callOllama(prompt, timeoutMs) {
10
+ const endpoint = process.env.INFERNO_LOCAL_ENDPOINT || "http://127.0.0.1:11434/api/generate";
11
+ const model = process.env.INFERNO_LOCAL_MODEL || "llama3.1:8b";
12
+ const { controller, timer } = withTimeout(timeoutMs);
13
+ try {
14
+ const res = await fetch(endpoint, {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ signal: controller.signal,
18
+ body: JSON.stringify({
19
+ model,
20
+ prompt,
21
+ stream: false,
22
+ }),
23
+ });
24
+ if (!res.ok) {
25
+ const body = await res.text();
26
+ throw new Error(`local_model_http_${res.status}: ${body.slice(0, 240)}`);
27
+ }
28
+ const data = await res.json();
29
+ if (!data?.response || typeof data.response !== "string") {
30
+ throw new Error("local_model_invalid_response");
31
+ }
32
+ return data.response.trim();
33
+ } finally {
34
+ clearTimeout(timer);
35
+ }
36
+ }
37
+
38
+ async function callOpenAICompat(prompt, timeoutMs) {
39
+ const endpoint = process.env.INFERNO_LOCAL_ENDPOINT || "http://127.0.0.1:1234/v1/chat/completions";
40
+ const model = process.env.INFERNO_LOCAL_MODEL || "local-model";
41
+ const apiKey = process.env.INFERNO_LOCAL_API_KEY || "local";
42
+ const { controller, timer } = withTimeout(timeoutMs);
43
+ try {
44
+ const res = await fetch(endpoint, {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ Authorization: `Bearer ${apiKey}`,
49
+ },
50
+ signal: controller.signal,
51
+ body: JSON.stringify({
52
+ model,
53
+ temperature: 0.1,
54
+ messages: [
55
+ { role: "system", content: "Return JSON only." },
56
+ { role: "user", content: prompt },
57
+ ],
58
+ }),
59
+ });
60
+ if (!res.ok) {
61
+ const body = await res.text();
62
+ throw new Error(`local_model_http_${res.status}: ${body.slice(0, 240)}`);
63
+ }
64
+ const data = await res.json();
65
+ const text = data?.choices?.[0]?.message?.content;
66
+ if (!text || typeof text !== "string") {
67
+ throw new Error("local_model_invalid_response");
68
+ }
69
+ return text.trim();
70
+ } finally {
71
+ clearTimeout(timer);
72
+ }
73
+ }
74
+
75
+ export async function generateWithLocalModel(prompt, options = {}) {
76
+ if (process.env.INFERNO_LOCAL_MOCK_RESPONSE) {
77
+ return process.env.INFERNO_LOCAL_MOCK_RESPONSE;
78
+ }
79
+
80
+ const provider = (process.env.INFERNO_LOCAL_PROVIDER || "ollama").toLowerCase();
81
+ const timeoutMs = Number(options.timeoutMs || process.env.INFERNO_LOCAL_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
82
+
83
+ if (provider === "openai") {
84
+ return callOpenAICompat(prompt, timeoutMs);
85
+ }
86
+ return callOllama(prompt, timeoutMs);
87
+ }
88
+