infernoflow 0.36.0 → 0.37.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.
- package/CHANGELOG.md +71 -0
- package/dist/bin/infernoflow.mjs +35 -0
- package/dist/lib/commands/init.mjs +86 -0
- package/dist/lib/commands/log.mjs +64 -0
- package/dist/lib/commands/recap.mjs +26 -5
- package/dist/lib/commands/switch.mjs +39 -0
- package/dist/lib/ui/output.mjs +29 -6
- package/package.json +4 -3
- package/scripts/postinstall.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog — infernoflow
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
> Changes since v0.10.17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Scarf analytics + telemetry UUID/timezone + feedback Formspree (v0.35.6)
|
|
9
|
+
- cloud session memory sync (v0.35.4)
|
|
10
|
+
- opt-in telemetry system (v0.35.3)
|
|
11
|
+
- stunning switch output (v0.35.2)
|
|
12
|
+
- infernoflow feedback command (v0.35.1)
|
|
13
|
+
- auto-capture git hooks + log --auto/--quiet/--source (v0.35.0)
|
|
14
|
+
- progressive disclosure — 5 core commands in --help, infernoflow commands shows full grouped list (v0.34.4)
|
|
15
|
+
- npm metadata — AI-optimized description + 20 keywords for AI-native discovery (v0.34.3)
|
|
16
|
+
- infernoflow uninstall — remove all infernoflow artifacts from a project (v0.34.1)
|
|
17
|
+
- v0.34.0 — persistent memory layer: switch session-boundary-aware, README rewrite, build fix (Sprint 8+9+10)
|
|
18
|
+
- infernoflow recap — end-of-session summary with session health score, unlogged git topics, and nudges (v0.33.7)
|
|
19
|
+
- infernoflow ask — query session memory by keyword/type, gotchas surface first (v0.33.6)
|
|
20
|
+
- infernoflow stats — value dashboard (memory, tokens injected, coverage, savings estimate) (v0.33.5)
|
|
21
|
+
- end-to-end HTTP chain tracing — resolves outbound calls to route handlers (v0.33.4)
|
|
22
|
+
- Sprint 4 — route discovery, HTTP URL extraction, entry point detection, --suggest (v0.33.3)
|
|
23
|
+
- infernoflow init --lite (3 files) + infernoflow upgrade (lite → full) (v0.33.2)
|
|
24
|
+
- infernoflow switch — AI agent handoff summary (v0.33.1)
|
|
25
|
+
- session memory (infernoflow log) + design system tracker (infernoflow theme) — capture what AI can't infer from code (v0.33.0)
|
|
26
|
+
- graph --html interactive SVG output; VS Code extension v0.5.0 with Capability Graph panel (v0.32.9)
|
|
27
|
+
- VS Code extension v0.4.0 — save-triggered contract sync
|
|
28
|
+
- ai setup numbered menu with env-var auto-detection (v0.32.3)
|
|
29
|
+
- Sprint 18C/D — dogfood capabilities.json, doctor action list, init AI nudge (v0.32.0)
|
|
30
|
+
- Sprint 18 — demo command, AI fallback nudges, test fix (v0.31.0)
|
|
31
|
+
- Sprint 17 — infernoflow test + ai commands (v0.30.0)
|
|
32
|
+
- Sprint 16C — infernoflow explain command (v0.29.0)
|
|
33
|
+
- Sprint 16B — infernoflow scaffold command (v0.28.0)
|
|
34
|
+
- Sprint 15 Liquid Layer + Sprint 16A/16D (v0.26-0.27)
|
|
35
|
+
- auto-configure Claude Code MCP + allowedTools (v0.10.25)
|
|
36
|
+
- add changelog and diff commands for enhanced version tracking
|
|
37
|
+
- add VS Code + Cursor extension
|
|
38
|
+
- add infernoflow publish command
|
|
39
|
+
- bump to 0.10.18, fix duplicate infernoDir in suggest
|
|
40
|
+
- add React-specific scanner for --adopt
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- uninstall now removes all infernoflow artifacts completely (v0.34.2)
|
|
44
|
+
- extension sidebar icon badge — switch to createTreeView so uncovered count shows on activity bar icon
|
|
45
|
+
- graph crashes on toString() — use Map instead of plain object for funcIndex (v0.32.8)
|
|
46
|
+
- check handles bare-array capabilities.json; scenario coverage is a warning not error (v0.32.7)
|
|
47
|
+
- typo 'capabilityy' in explain file-path output
|
|
48
|
+
- explain command now accepts file paths in addition to capability IDs (v0.32.6)
|
|
49
|
+
- await import in non-async loadCapsAtRef causes syntax error (v0.32.5)
|
|
50
|
+
- add missing resolveProvider export to providerRouter.mjs (v0.32.4)
|
|
51
|
+
- Windows path bug in demo/watch/ci/monorepo/notify (v0.32.2)
|
|
52
|
+
- doctor CLI check false positive on Windows (.cmd PATH resolution)
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- v0.37.0 — Windows unicode fix, memory-first init, hot files in switch, CLAUDE.md auto-update, health score tips, icon fix, cross-platform postinstall
|
|
56
|
+
- wire Polar.sh Pro checkout — real payments live
|
|
57
|
+
- point homepage to infernoflow.dev (v0.35.9)
|
|
58
|
+
- activate Formspree feedback endpoint (v0.35.8)
|
|
59
|
+
- activate PostHog EU telemetry (v0.35.7)
|
|
60
|
+
- bump to 0.35.5 (0.35.4 already published)
|
|
61
|
+
- rewrite README to reflect all capabilities (v0.32.5)
|
|
62
|
+
- v0.32.1
|
|
63
|
+
- bump to v0.32.0
|
|
64
|
+
- bump version to 0.31.0
|
|
65
|
+
- release 0.10.24
|
|
66
|
+
- release 0.10.23
|
|
67
|
+
- release 0.10.22
|
|
68
|
+
- release 0.10.21
|
|
69
|
+
- release 0.10.20
|
|
70
|
+
- release 0.10.19
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
3
74
|
## 0.10.25 — 2026-04-22
|
|
4
75
|
|
|
5
76
|
### Added
|
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ── Windows PowerShell unicode fix ───────────────────────────────────────
|
|
4
|
+
// Default PowerShell can't render box-drawing chars — patch stdout/stderr
|
|
5
|
+
// to replace them with ASCII equivalents before any output happens.
|
|
6
|
+
(function patchUnicodeForWindows() {
|
|
7
|
+
if (process.platform !== "win32") return;
|
|
8
|
+
if (process.env.WT_SESSION) return; // Windows Terminal — supports unicode
|
|
9
|
+
if (process.env.ConEmuPID) return; // ConEmu/Cmder
|
|
10
|
+
if (process.env.TERM_PROGRAM === "vscode") return; // VS Code terminal
|
|
11
|
+
|
|
12
|
+
const MAP = {
|
|
13
|
+
"─": "-", "━": "-", "═": "=",
|
|
14
|
+
"│": "|", "┃": "|", "║": "|",
|
|
15
|
+
"┌": "+", "┐": "+", "└": "+", "┘": "+",
|
|
16
|
+
"├": "+", "┤": "+", "┬": "+", "┴": "+", "┼": "+",
|
|
17
|
+
"·": "*", "→": "->", "←": "<-", "✔": "[OK]", "✓": "[OK]",
|
|
18
|
+
"✘": "[X]", "✗": "[X]", "⚠": "[!]", "ℹ": "[i]",
|
|
19
|
+
};
|
|
20
|
+
const RE = new RegExp(Object.keys(MAP).join("|"), "g");
|
|
21
|
+
|
|
22
|
+
function patch(stream) {
|
|
23
|
+
const orig = stream.write.bind(stream);
|
|
24
|
+
stream.write = function(chunk, ...args) {
|
|
25
|
+
if (typeof chunk === "string") chunk = chunk.replace(RE, c => MAP[c]);
|
|
26
|
+
else if (Buffer.isBuffer(chunk)) {
|
|
27
|
+
const s = chunk.toString("utf8").replace(RE, c => MAP[c]);
|
|
28
|
+
chunk = Buffer.from(s, "utf8");
|
|
29
|
+
}
|
|
30
|
+
return orig(chunk, ...args);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
patch(process.stdout);
|
|
34
|
+
patch(process.stderr);
|
|
35
|
+
})();
|
|
36
|
+
|
|
2
37
|
import { readFileSync } from "node:fs";
|
|
3
38
|
import { dirname, join } from "node:path";
|
|
4
39
|
import { fileURLToPath } from "node:url";
|
|
@@ -214,12 +214,98 @@ async function initLite(cwd, force) {
|
|
|
214
214
|
console.log();
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
/** Default init: memory-only, asks for first gotcha, 60 seconds */
|
|
218
|
+
async function initMemory(cwd, force, yes) {
|
|
219
|
+
const { bold, cyan, gray, green, yellow } = await import("../ui/output.mjs");
|
|
220
|
+
|
|
221
|
+
const infernoDir = path.join(cwd, "inferno");
|
|
222
|
+
const sessionsFile = path.join(infernoDir, "sessions.jsonl");
|
|
223
|
+
const configFile = path.join(infernoDir, "config.json");
|
|
224
|
+
|
|
225
|
+
if (fs.existsSync(infernoDir) && !force) {
|
|
226
|
+
// Already initialized — just confirm and show commands
|
|
227
|
+
console.log("\n " + bold("🔥 infernoflow") + gray(" — already set up\n"));
|
|
228
|
+
console.log(" " + green("✔") + " inferno/ found\n");
|
|
229
|
+
console.log(" Quick commands:");
|
|
230
|
+
console.log(" " + cyan("infernoflow log \"...\"") + gray(" — remember something"));
|
|
231
|
+
console.log(" " + cyan("infernoflow switch") + gray(" — handoff to next AI"));
|
|
232
|
+
console.log(" " + cyan("infernoflow recap") + gray(" — session summary\n"));
|
|
233
|
+
console.log(gray(" For contracts & CI gates: infernoflow init --mode full\n"));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const projectName = detectProjectName(cwd);
|
|
238
|
+
|
|
239
|
+
console.log("\n " + bold("🔥 infernoflow") + gray(" — let's get you set up (30 seconds)\n"));
|
|
240
|
+
console.log(" Detected: " + cyan(projectName) + "\n");
|
|
241
|
+
|
|
242
|
+
// Create inferno/ directory
|
|
243
|
+
fs.mkdirSync(infernoDir, { recursive: true });
|
|
244
|
+
|
|
245
|
+
// Write minimal config
|
|
246
|
+
fs.writeFileSync(configFile, JSON.stringify({
|
|
247
|
+
project: projectName,
|
|
248
|
+
version: "1",
|
|
249
|
+
mode: "memory",
|
|
250
|
+
created: new Date().toISOString(),
|
|
251
|
+
}, null, 2) + "\n", "utf8");
|
|
252
|
+
|
|
253
|
+
// Create empty sessions.jsonl
|
|
254
|
+
if (!fs.existsSync(sessionsFile)) {
|
|
255
|
+
fs.writeFileSync(sessionsFile, "", "utf8");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Ask for first gotcha (skip if --yes or not TTY)
|
|
259
|
+
let gotcha = "";
|
|
260
|
+
if (!yes && process.stdin.isTTY) {
|
|
261
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
262
|
+
gotcha = await new Promise(resolve => {
|
|
263
|
+
rl.question(
|
|
264
|
+
" " + gray("What should the next AI agent know about this project?\n > "),
|
|
265
|
+
ans => { rl.close(); resolve(ans.trim()); }
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (gotcha) {
|
|
271
|
+
const entry = {
|
|
272
|
+
ts: new Date().toISOString(),
|
|
273
|
+
agent: "user",
|
|
274
|
+
type: "gotcha",
|
|
275
|
+
summary: gotcha,
|
|
276
|
+
source: "init",
|
|
277
|
+
};
|
|
278
|
+
fs.appendFileSync(sessionsFile, JSON.stringify(entry) + "\n", "utf8");
|
|
279
|
+
console.log("\n " + green("✔") + " First gotcha logged!");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log("\n " + green("✔") + " You're set up. Quick commands:\n");
|
|
283
|
+
console.log(" " + cyan("infernoflow log \"...\"") + gray(" — remember something"));
|
|
284
|
+
console.log(" " + cyan("infernoflow switch") + gray(" — generate handoff for next AI"));
|
|
285
|
+
console.log(" " + cyan("infernoflow recap") + gray(" — session summary\n"));
|
|
286
|
+
console.log(gray(" Tip: infernoflow switch --copy puts the handoff on your clipboard.\n"));
|
|
287
|
+
console.log(gray(" Want contracts & CI gates? Run: infernoflow init --mode full\n"));
|
|
288
|
+
}
|
|
289
|
+
|
|
217
290
|
export async function initCommand(args) {
|
|
218
291
|
const cwd = process.cwd();
|
|
219
292
|
const force = args.includes("--force") || args.includes("-f");
|
|
220
293
|
const yes = args.includes("--yes") || args.includes("-y");
|
|
221
294
|
const adopt = args.includes("--adopt");
|
|
222
295
|
|
|
296
|
+
// ── Memory-first default (no flags) — 60-second onboarding ───────────────
|
|
297
|
+
const modeArg = args.find(a => a.startsWith("--mode="))?.split("=")[1]
|
|
298
|
+
|| (args.indexOf("--mode") !== -1 ? args[args.indexOf("--mode") + 1] : null);
|
|
299
|
+
|
|
300
|
+
const isFullMode = modeArg === "full" || modeArg === "contract";
|
|
301
|
+
const hasAdvancedFlag = adopt || args.includes("--template") || args.includes("--cursor-hooks")
|
|
302
|
+
|| args.includes("--vscode-copilot-hooks") || args.includes("--lite");
|
|
303
|
+
|
|
304
|
+
if (!isFullMode && !hasAdvancedFlag) {
|
|
305
|
+
await initMemory(cwd, force, yes);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
223
309
|
// ── Lite mode — tiny project, single directory, 3 files only ──────────────
|
|
224
310
|
if (args.includes("--lite")) {
|
|
225
311
|
await initLite(cwd, force);
|
|
@@ -30,6 +30,67 @@ import { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
|
30
30
|
const INFERNO_DIR = "inferno";
|
|
31
31
|
const SESSIONS_FILE = path.join(INFERNO_DIR, "sessions.jsonl");
|
|
32
32
|
|
|
33
|
+
/** Silently regenerate CLAUDE.md, .cursorrules, and copilot-instructions.md
|
|
34
|
+
* after every log entry so AI agents always have fresh context. */
|
|
35
|
+
function autoUpdateContextFiles() {
|
|
36
|
+
try {
|
|
37
|
+
const sessionsRaw = fs.existsSync(SESSIONS_FILE)
|
|
38
|
+
? fs.readFileSync(SESSIONS_FILE, "utf8").split("\n").filter(Boolean)
|
|
39
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean)
|
|
40
|
+
: [];
|
|
41
|
+
|
|
42
|
+
const gotchas = sessionsRaw.filter(e => e.type === "gotcha");
|
|
43
|
+
const decisions = sessionsRaw.filter(e => e.type === "decision");
|
|
44
|
+
const attempts = sessionsRaw.filter(e => e.type === "attempt" && (e.result === "failed" || e.result === "partial"));
|
|
45
|
+
|
|
46
|
+
const lines = [
|
|
47
|
+
`# Project Context (auto-generated by infernoflow)`,
|
|
48
|
+
``,
|
|
49
|
+
`> Last updated: ${new Date().toISOString()}`,
|
|
50
|
+
``,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (gotchas.length) {
|
|
54
|
+
lines.push(`## ⚠️ Known Gotchas (Read These First)`, ``);
|
|
55
|
+
for (const g of gotchas) lines.push(`- ${g.summary}`);
|
|
56
|
+
lines.push(``);
|
|
57
|
+
}
|
|
58
|
+
if (decisions.length) {
|
|
59
|
+
lines.push(`## ✓ Decisions In Effect`, ``);
|
|
60
|
+
for (const d of decisions) lines.push(`- ${d.summary}`);
|
|
61
|
+
lines.push(``);
|
|
62
|
+
}
|
|
63
|
+
if (attempts.length) {
|
|
64
|
+
lines.push(`## ❌ Things That Don't Work (Don't Try These)`, ``);
|
|
65
|
+
for (const a of attempts) lines.push(`- ${a.summary}`);
|
|
66
|
+
lines.push(``);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = lines.join("\n");
|
|
70
|
+
const cwd = process.cwd();
|
|
71
|
+
|
|
72
|
+
// Update CLAUDE.md if it already exists (don't create unsolicited)
|
|
73
|
+
const claudeMd = path.join(cwd, "CLAUDE.md");
|
|
74
|
+
if (fs.existsSync(claudeMd)) {
|
|
75
|
+
fs.writeFileSync(claudeMd, content, "utf8");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Update .cursorrules if Cursor is detected
|
|
79
|
+
const cursorrules = path.join(cwd, ".cursorrules");
|
|
80
|
+
if (fs.existsSync(cursorrules) || fs.existsSync(path.join(cwd, ".cursor"))) {
|
|
81
|
+
fs.writeFileSync(cursorrules, content, "utf8");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update copilot-instructions.md if .github exists
|
|
85
|
+
const copilotInstructions = path.join(cwd, ".github", "copilot-instructions.md");
|
|
86
|
+
if (fs.existsSync(path.join(cwd, ".github"))) {
|
|
87
|
+
fs.writeFileSync(copilotInstructions, content, "utf8");
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Never surface errors to the user — this is background infrastructure
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
33
94
|
const VALID_TYPES = ["note","attempt","decision","gotcha","preference","theme","handoff","error"];
|
|
34
95
|
const VALID_RESULTS = ["worked","failed","partial","unknown"];
|
|
35
96
|
|
|
@@ -175,6 +236,9 @@ export async function logCommand(args) {
|
|
|
175
236
|
const written = appendEntry(entry, { auto: autoFlag, quiet: quietFlag });
|
|
176
237
|
if (!written) return; // auto mode, no inferno/ — skip silently
|
|
177
238
|
|
|
239
|
+
// Silently regenerate CLAUDE.md / .cursorrules / copilot-instructions.md
|
|
240
|
+
autoUpdateContextFiles();
|
|
241
|
+
|
|
178
242
|
if (!quietFlag) {
|
|
179
243
|
const typeLabel = type !== "note" ? cyan(` [${type}]`) : "";
|
|
180
244
|
const resultLabel = result ? gray(` → ${result}`) : "";
|
|
@@ -338,11 +338,32 @@ export async function recapCommand(rawArgs = []) {
|
|
|
338
338
|
console.log(`${icon} ${ok ? label : gray(label)}`);
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
341
|
+
// Actionable improvement tips
|
|
342
|
+
{
|
|
343
|
+
const gotchaCount = sessionEntries.filter(e => e.type === "gotcha").length;
|
|
344
|
+
const decisionCount = sessionEntries.filter(e => e.type === "decision").length;
|
|
345
|
+
const tips = [];
|
|
346
|
+
|
|
347
|
+
if (gotchaCount === 0) {
|
|
348
|
+
tips.push(cyan("infernoflow log \"...\" --type gotcha") + gray(" — adds 35 pts"));
|
|
349
|
+
} else if (gotchaCount < 3 && score < 80) {
|
|
350
|
+
tips.push(gray(` ${3 - gotchaCount} more gotcha(s) would push you higher`));
|
|
351
|
+
}
|
|
352
|
+
if (decisionCount === 0) {
|
|
353
|
+
tips.push(cyan("infernoflow log \"...\" --type decision") + gray(" — adds 25 pts"));
|
|
354
|
+
}
|
|
355
|
+
if (score >= 60 && score < 80) {
|
|
356
|
+
tips.push(gray(" Almost B! One more entry gets you there."));
|
|
357
|
+
}
|
|
358
|
+
if (score >= 80) {
|
|
359
|
+
tips.push(green(" Great session — your handoff will be excellent."));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (tips.length) {
|
|
363
|
+
console.log();
|
|
364
|
+
console.log(gray(" How to improve:"));
|
|
365
|
+
for (const t of tips) console.log(" " + t);
|
|
366
|
+
}
|
|
346
367
|
}
|
|
347
368
|
|
|
348
369
|
// ── Next session tip ──────────────────────────────────────────────────────
|
|
@@ -136,6 +136,31 @@ function getGitDiffStat() {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/** Get most-edited files from git this session */
|
|
140
|
+
function getHotFiles(since) {
|
|
141
|
+
try {
|
|
142
|
+
const sinceArg = since && since.getTime() > 0
|
|
143
|
+
? `--after="${since.toISOString()}"`
|
|
144
|
+
: "-10";
|
|
145
|
+
const raw = execSync(
|
|
146
|
+
`git log ${sinceArg} --name-only --pretty=format: 2>/dev/null`,
|
|
147
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
148
|
+
).trim();
|
|
149
|
+
if (!raw) return [];
|
|
150
|
+
const counts = {};
|
|
151
|
+
for (const line of raw.split("\n")) {
|
|
152
|
+
const f = line.trim();
|
|
153
|
+
if (f) counts[f] = (counts[f] || 0) + 1;
|
|
154
|
+
}
|
|
155
|
+
return Object.entries(counts)
|
|
156
|
+
.sort((a, b) => b[1] - a[1])
|
|
157
|
+
.slice(0, 5)
|
|
158
|
+
.map(([file, edits]) => ({ file, edits }));
|
|
159
|
+
} catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
139
164
|
/** Get recent git commits in this session */
|
|
140
165
|
function getGitCommits(since) {
|
|
141
166
|
try {
|
|
@@ -208,6 +233,7 @@ function buildHandoff(toAgent, sinceArg, allFlag) {
|
|
|
208
233
|
// Git data
|
|
209
234
|
const commits = getGitCommits(sessionStart.getTime() > 0 ? sessionStart : null);
|
|
210
235
|
const diffStat = getGitDiffStat();
|
|
236
|
+
const hotFiles = getHotFiles(sessionStart.getTime() > 0 ? sessionStart : null);
|
|
211
237
|
|
|
212
238
|
// Memory pool
|
|
213
239
|
const pool = sessions.length > 0 ? sessions : recentFallback;
|
|
@@ -289,6 +315,15 @@ function buildHandoff(toAgent, sinceArg, allFlag) {
|
|
|
289
315
|
lines.push("");
|
|
290
316
|
}
|
|
291
317
|
|
|
318
|
+
// ── Hot files — auto-detected from git ────────────────────────────────────
|
|
319
|
+
if (hotFiles.length) {
|
|
320
|
+
lines.push("## 📁 Hot Files This Session", "");
|
|
321
|
+
for (const { file, edits } of hotFiles) {
|
|
322
|
+
lines.push(`- \`${file}\` — ${edits} edit${edits !== 1 ? "s" : ""}`);
|
|
323
|
+
}
|
|
324
|
+
lines.push("");
|
|
325
|
+
}
|
|
326
|
+
|
|
292
327
|
// ── Preferences ───────────────────────────────────────────────────────────
|
|
293
328
|
if (prefs.length) {
|
|
294
329
|
lines.push("## Developer preferences", "");
|
|
@@ -437,6 +472,7 @@ export async function switchCommand(args) {
|
|
|
437
472
|
const theme = readJSON(THEME_FILE);
|
|
438
473
|
const contract = readJSON(CONTRACT_FILE) || {};
|
|
439
474
|
const commits = getGitCommits(sessionStartNow.getTime() > 0 ? sessionStartNow : null);
|
|
475
|
+
const hotFilesNow = getHotFiles(sessionStartNow.getTime() > 0 ? sessionStartNow : null);
|
|
440
476
|
const ide = detectIde();
|
|
441
477
|
const pool = sessionEntries.length > 0 ? sessionEntries : allEntriesNow.slice(-5);
|
|
442
478
|
const openThreads = findOpenThreads(pool);
|
|
@@ -454,6 +490,9 @@ export async function switchCommand(args) {
|
|
|
454
490
|
console.log(" Memory " + sessionEntries.length + " entries this session (total: " + allEntriesNow.length + ")");
|
|
455
491
|
if (openThreads.length) console.log(" Open threads " + yellow(openThreads.length + " unresolved"));
|
|
456
492
|
if (commits.length) console.log(" Git commits " + commits.length + " this session");
|
|
493
|
+
if (hotFilesNow.length) {
|
|
494
|
+
console.log(" Hot files " + hotFilesNow.map(f => cyan(f.file)).join(", "));
|
|
495
|
+
}
|
|
457
496
|
console.log(" Capabilities " + (contract.capabilities || []).length + " registered");
|
|
458
497
|
if (theme?.fonts?.primary) console.log(" Font " + theme.fonts.primary);
|
|
459
498
|
if (theme?.colors?.mode) console.log(" Color mode " + theme.colors.mode);
|
package/dist/lib/ui/output.mjs
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
// Zero-dependency color/output utilities using ANSI codes
|
|
2
2
|
|
|
3
|
+
// ── Terminal capability detection ─────────────────────────────────────────
|
|
4
|
+
function supportsUnicode() {
|
|
5
|
+
if (process.platform === "win32") {
|
|
6
|
+
// Windows Terminal sets WT_SESSION
|
|
7
|
+
if (process.env.WT_SESSION) return true;
|
|
8
|
+
// ConEmu / Cmder
|
|
9
|
+
if (process.env.ConEmuPID) return true;
|
|
10
|
+
// VS Code integrated terminal
|
|
11
|
+
if (process.env.TERM_PROGRAM === "vscode") return true;
|
|
12
|
+
// Default cmd.exe / PowerShell — no unicode box drawing
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return true; // Mac/Linux always support it
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const CHARS = supportsUnicode()
|
|
19
|
+
? { h: "─", v: "│", tl: "┌", tr: "┐", bl: "└", br: "┘",
|
|
20
|
+
dot: "·", arrow: "→", check: "✔", cross: "✘", warn: "⚠", fire: "🔥" }
|
|
21
|
+
: { h: "-", v: "|", tl: "+", tr: "+", bl: "+", br: "+",
|
|
22
|
+
dot: "*", arrow: "->", check: "[OK]", cross: "[X]", warn: "[!]", fire: "**" };
|
|
23
|
+
|
|
24
|
+
export const HR = CHARS.h.repeat(50);
|
|
25
|
+
|
|
3
26
|
const c = {
|
|
4
27
|
reset: "\x1b[0m",
|
|
5
28
|
bold: "\x1b[1m",
|
|
@@ -38,17 +61,17 @@ function strip(str) {
|
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
export function header(text) {
|
|
41
|
-
const title = boldOrange("
|
|
64
|
+
const title = boldOrange(CHARS.fire + " infernoflow") + gray(" — " + text);
|
|
42
65
|
console.log("\n" + title);
|
|
43
|
-
console.log(gray(
|
|
66
|
+
console.log(gray(HR));
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
export function ok(msg) { console.log(" " + green(
|
|
69
|
+
export function ok(msg) { console.log(" " + green(CHARS.check) + " " + msg); }
|
|
47
70
|
export function fail(msg, hint) {
|
|
48
|
-
console.log(" " + red(
|
|
49
|
-
if (hint) console.log(" " + gray("
|
|
71
|
+
console.log(" " + red(CHARS.cross) + " " + red(msg));
|
|
72
|
+
if (hint) console.log(" " + gray(CHARS.arrow + " " + hint));
|
|
50
73
|
}
|
|
51
|
-
export function warn(msg) { console.log(" " + yellow(
|
|
74
|
+
export function warn(msg) { console.log(" " + yellow(CHARS.warn) + " " + yellow(msg)); }
|
|
52
75
|
export function info(msg) { console.log(" " + cyan("ℹ") + " " + msg); }
|
|
53
76
|
export function section(title) { console.log("\n" + bold(white(title))); }
|
|
54
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infernoflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Persistent memory for AI coding sessions — captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"dist/templates",
|
|
16
16
|
"README.md",
|
|
17
17
|
"CHANGELOG.md",
|
|
18
|
-
"dist/lib/templates"
|
|
18
|
+
"dist/lib/templates",
|
|
19
|
+
"scripts/postinstall.js"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
21
22
|
"test": "node scripts/smoke.mjs && node scripts/json-smoke.mjs && node scripts/json-negative-smoke.mjs && node scripts/implement-smoke.mjs && node scripts/adopt-smoke.mjs && node scripts/pr-impact-smoke.mjs && node scripts/sync-smoke.mjs && node scripts/run-smoke.mjs",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"build": "node build.mjs",
|
|
24
25
|
"prepublishOnly": "node build.mjs",
|
|
25
26
|
"inferno:promote-draft": "node scripts/inferno-promote-draft.mjs",
|
|
26
|
-
"postinstall": "node
|
|
27
|
+
"postinstall": "node scripts/postinstall.js"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
29
30
|
"@scarf/scarf": "^1.3.0"
|