infernoflow 0.5.0 → 0.7.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.
@@ -1,170 +1,230 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { bold, gray, cyan, red, green, yellow } from "../ui/output.mjs";
4
-
5
- const INFERNO_DIR = "inferno";
6
- const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
7
- const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
8
-
9
- function readJSON(filePath) {
10
- try { return JSON.parse(fs.readFileSync(filePath, "utf8")); }
11
- catch { return null; }
12
- }
13
-
14
- function readFile(filePath) {
15
- try { return fs.readFileSync(filePath, "utf8"); }
16
- catch { return null; }
17
- }
18
-
19
- function parseRecentChangelog(changelogContent, maxEntries = 3) {
20
- if (!changelogContent) return [];
21
- const lines = changelogContent.split("\n");
22
- const entries = [];
23
- let current = null;
24
-
25
- for (const line of lines) {
26
- if (line.startsWith("## ")) {
27
- if (current && entries.length < maxEntries) entries.push(current);
28
- if (entries.length >= maxEntries) break;
29
- current = { title: line.replace("## ", "").trim(), items: [] };
30
- } else if (current && line.startsWith("- ")) {
31
- current.items.push(line.replace("- ", "").trim());
32
- }
33
- }
34
- if (current && entries.length < maxEntries) entries.push(current);
35
- return entries.filter(e => e.items.length > 0);
36
- }
37
-
38
- function loadState() {
39
- const raw = readFile(STATE_FILE);
40
- if (!raw) return {};
41
- try { return JSON.parse(raw); } catch { return {}; }
42
- }
43
-
44
- function saveState(state) {
45
- fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf8");
46
- }
47
-
48
- function formatDate(iso) {
49
- if (!iso) return "unknown";
50
- return new Date(iso).toLocaleDateString("en-GB", {
51
- day: "2-digit", month: "short", year: "numeric"
52
- });
53
- }
54
-
55
- export async function contextCommand(args) {
56
- const getFlagValue = (f) => {
57
- const i = args.indexOf(f);
58
- return i !== -1 && args[i + 1] ? args[i + 1] : null;
59
- };
60
- const hasFlag = (f) => args.includes(f);
61
-
62
- const intent = getFlagValue("--intent") || getFlagValue("-i");
63
- const working = getFlagValue("--working") || getFlagValue("-w");
64
- const decision = getFlagValue("--decision") || getFlagValue("-d");
65
- const showOnly = hasFlag("--show") || hasFlag("-s");
66
- const resetState = hasFlag("--reset");
67
-
68
- console.log(`\n ${bold("🔥 infernoflow — context")}`);
69
- console.log(` ${"".repeat(50)}\n`);
70
-
71
- if (!fs.existsSync(INFERNO_DIR)) {
72
- console.error(red(" inferno/ not found"));
73
- console.error(gray(" Run: infernoflow init\n"));
74
- process.exit(1);
75
- }
76
-
77
- const contract = readJSON(path.join(INFERNO_DIR, "contract.json"));
78
- const capabilities = readJSON(path.join(INFERNO_DIR, "capabilities.json"));
79
- const changelog = readFile(path.join(INFERNO_DIR, "CHANGELOG.md"));
80
-
81
- if (!contract || !capabilities) {
82
- console.error(red(" Missing contract.json or capabilities.json"));
83
- process.exit(1);
84
- }
85
-
86
- let state = loadState();
87
- if (resetState) { state = {}; console.log(yellow(" ⚠ State reset\n")); }
88
-
89
- if (intent) { state.intent = intent; state.intentUpdated = new Date().toISOString(); console.log(green(` ✔ Intent saved: "${intent}"`)); }
90
- if (working) { state.working = working; state.workingUpdated = new Date().toISOString(); console.log(green(` ✔ Working on: "${working}"`)); }
91
- if (decision) {
92
- if (!state.decisions) state.decisions = [];
93
- state.decisions.push({ text: decision, date: new Date().toISOString() });
94
- console.log(green(` ✔ Decision recorded: "${decision}"`));
95
- }
96
- if (intent || working || decision) saveState(state);
97
-
98
- const capList = capabilities.capabilities || [];
99
- const contractCaps = contract.capabilities || [];
100
- const allInSync = capList.length === contractCaps.length;
101
- const recentChanges = parseRecentChangelog(changelog, 3);
102
-
103
- const now = new Date().toLocaleDateString("en-GB", {
104
- day: "2-digit", month: "short", year: "numeric"
105
- });
106
-
107
- const capLines = capList.map(c => `- **${c.id}** ${c.title}`).join("\n");
108
-
109
- const changelogLines = recentChanges.length > 0
110
- ? recentChanges.map(e => `### ${e.title}\n${e.items.map(i => ` - ${i}`).join("\n")}`).join("\n\n")
111
- : "_No recent changes recorded_";
112
-
113
- const intentLine = state.intent
114
- ? `${state.intent} _(updated: ${formatDate(state.intentUpdated)})_`
115
- : "_Not set — run: `infernoflow context --intent \"what I want to do next\"`_";
116
-
117
- const workingLine = state.working
118
- ? `${state.working} _(updated: ${formatDate(state.workingUpdated)})_`
119
- : "_Not set — run: `infernoflow context --working \"what I am building now\"`_";
120
-
121
- const decisionsLines = state.decisions?.length > 0
122
- ? state.decisions.slice(-5).map(d => `- ${d.text} _(${formatDate(d.date)})_`).join("\n")
123
- : "_No decisions recorded — run: `infernoflow context --decision \"why I chose X\"`_";
124
-
125
- const syncBadge = allInSync ? "✓ validated" : "⚠ out of sync — run infernoflow check";
126
- const version = String(contract.policyVersion).replace(/^v/i, "");
127
-
128
- const contextMd = `# Project Context — ${contract.policyId} v${version}
129
- > Generated by infernoflow | ${now} | ${syncBadge}
130
-
131
- ---
132
-
133
- ## What this system does
134
-
135
- ${capLines}
136
-
137
- ---
138
-
139
- ## Recent changes
140
-
141
- ${changelogLines}
142
-
143
- ---
144
-
145
- ## Current state
146
-
147
- - **Capabilities declared:** ${capList.length}
148
- - **Contract version:** v${version}
149
- - **Sync status:** ${syncBadge}
150
-
151
- ---
152
-
153
- ## What I am working on right now
154
-
155
- ${workingLine}
156
-
157
- ---
158
-
159
- ## Intent what I want to build next
160
-
161
- ${intentLine}
162
-
163
- ---
164
-
165
- ## Decisions & notes
166
-
167
- ${decisionsLines}
168
-
169
- ---
170
- _Paste this block at the start of any new AI session
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { bold, gray, cyan, red, green, yellow } from "../ui/output.mjs";
4
+
5
+ const INFERNO_DIR = "inferno";
6
+ const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
7
+ const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
8
+
9
+ function readJSON(f) {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(f, "utf8"));
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ function readFile(f) {
17
+ try {
18
+ return fs.readFileSync(f, "utf8");
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function loadState() {
24
+ const r = readFile(STATE_FILE);
25
+ if (!r) return {};
26
+ try {
27
+ return JSON.parse(r);
28
+ } catch {
29
+ return {};
30
+ }
31
+ }
32
+ function saveState(s) {
33
+ fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2), "utf8");
34
+ }
35
+ function formatDate(iso) {
36
+ if (!iso) return "unknown";
37
+ return new Date(iso).toLocaleDateString("en-GB", {
38
+ day: "2-digit",
39
+ month: "short",
40
+ year: "numeric",
41
+ });
42
+ }
43
+ function parseChangelog(txt, max) {
44
+ if (!txt) return [];
45
+ const entries = [];
46
+ let cur = null;
47
+ for (const line of txt.split("\n")) {
48
+ if (line.startsWith("## ")) {
49
+ if (cur && entries.length < max) entries.push(cur);
50
+ if (entries.length >= max) break;
51
+ cur = { title: line.replace("## ", "").trim(), items: [] };
52
+ } else if (cur && line.startsWith("- ")) {
53
+ cur.items.push(line.replace("- ", "").trim());
54
+ }
55
+ }
56
+ if (cur && entries.length < max) entries.push(cur);
57
+ return entries.filter((e) => e.items.length > 0);
58
+ }
59
+
60
+ export async function contextCommand(args) {
61
+ const flag = (f) => {
62
+ const i = args.indexOf(f);
63
+ return i !== -1 && args[i + 1] ? args[i + 1] : null;
64
+ };
65
+ const has = (f) => args.includes(f);
66
+
67
+ const intent = flag("--intent") || flag("-i");
68
+ const working = flag("--working") || flag("-w");
69
+ const decision = flag("--decision") || flag("-d");
70
+ const showOnly = has("--show") || has("-s");
71
+
72
+ console.log("\n " + bold("🔥 infernoflow — context"));
73
+ console.log(" " + "─".repeat(50) + "\n");
74
+
75
+ if (!fs.existsSync(INFERNO_DIR)) {
76
+ console.error(red(" ✘ inferno/ not found"));
77
+ console.error(gray(" Run: infernoflow init\n"));
78
+ process.exit(1);
79
+ }
80
+
81
+ const contract = readJSON(path.join(INFERNO_DIR, "contract.json"));
82
+ const capabilities = readJSON(path.join(INFERNO_DIR, "capabilities.json"));
83
+ const changelog = readFile(path.join(INFERNO_DIR, "CHANGELOG.md"));
84
+
85
+ if (!contract || !capabilities) {
86
+ console.error(red(" Missing contract.json or capabilities.json\n"));
87
+ process.exit(1);
88
+ }
89
+
90
+ let state = loadState();
91
+
92
+ if (intent) {
93
+ state.intent = intent;
94
+ state.intentUpdated = new Date().toISOString();
95
+ console.log(green(' ✔ Intent saved: "' + intent + '"'));
96
+ }
97
+ if (working) {
98
+ state.working = working;
99
+ state.workingUpdated = new Date().toISOString();
100
+ console.log(green(' Working on: "' + working + '"'));
101
+ }
102
+ if (decision) {
103
+ if (!state.decisions) state.decisions = [];
104
+ state.decisions.push({ text: decision, date: new Date().toISOString() });
105
+ console.log(green(' ✔ Decision recorded: "' + decision + '"'));
106
+ }
107
+ if (intent || working || decision) saveState(state);
108
+
109
+ const capList = capabilities.capabilities || [];
110
+ const allInSync = capList.length === (contract.capabilities || []).length;
111
+ const recent = parseChangelog(changelog, 3);
112
+ const version = String(contract.policyVersion).replace(/^v/i, "");
113
+ const now = new Date().toLocaleDateString("en-GB", {
114
+ day: "2-digit",
115
+ month: "short",
116
+ year: "numeric",
117
+ });
118
+ const syncBadge = allInSync ? "✓ validated" : "⚠ out of sync";
119
+
120
+ const capLines = capList
121
+ .map((c) => "- **" + c.id + "** — " + c.title)
122
+ .join("\n");
123
+
124
+ const changelogLines =
125
+ recent.length > 0
126
+ ? recent
127
+ .map(
128
+ (e) =>
129
+ "### " +
130
+ e.title +
131
+ "\n" +
132
+ e.items.map((i) => " - " + i).join("\n"),
133
+ )
134
+ .join("\n\n")
135
+ : "_No recent changes_";
136
+
137
+ const intentLine = state.intent
138
+ ? state.intent + " _(" + formatDate(state.intentUpdated) + ")_"
139
+ : '_Not set — run: infernoflow context --intent "..._"';
140
+
141
+ const workingLine = state.working
142
+ ? state.working + " _(" + formatDate(state.workingUpdated) + ")_"
143
+ : '_Not set — run: infernoflow context --working "..._"';
144
+
145
+ const decLines =
146
+ state.decisions && state.decisions.length > 0
147
+ ? state.decisions
148
+ .slice(-5)
149
+ .map((d) => "- " + d.text + " _(" + formatDate(d.date) + ")_")
150
+ .join("\n")
151
+ : "_No decisions recorded_";
152
+
153
+ const md = [
154
+ "# Project Context — " + contract.policyId + " v" + version,
155
+ "> Generated by infernoflow | " + now + " | " + syncBadge,
156
+ "",
157
+ "---",
158
+ "",
159
+ "## What this system does",
160
+ "",
161
+ capLines,
162
+ "",
163
+ "---",
164
+ "",
165
+ "## Recent changes",
166
+ "",
167
+ changelogLines,
168
+ "",
169
+ "---",
170
+ "",
171
+ "## Current state",
172
+ "",
173
+ "- **Capabilities:** " + capList.length,
174
+ "- **Version:** v" + version,
175
+ "- **Sync:** " + syncBadge,
176
+ "",
177
+ "---",
178
+ "",
179
+ "## What I am working on right now",
180
+ "",
181
+ workingLine,
182
+ "",
183
+ "---",
184
+ "",
185
+ "## Intent — what I want to build next",
186
+ "",
187
+ intentLine,
188
+ "",
189
+ "---",
190
+ "",
191
+ "## Decisions & notes",
192
+ "",
193
+ decLines,
194
+ "",
195
+ "---",
196
+ "_Paste this block at the start of any new AI session._",
197
+ ].join("\n");
198
+
199
+ if (!showOnly) {
200
+ fs.writeFileSync(CONTEXT_FILE, md, "utf8");
201
+ console.log(green("\n ✔ Context written → " + CONTEXT_FILE));
202
+ }
203
+
204
+ console.log("\n " + bold("Context Summary"));
205
+ console.log(" " + "─".repeat(50));
206
+ console.log(" Project " + contract.policyId + " — v" + version);
207
+ console.log(" Capabilities " + capList.length + " registered");
208
+ console.log(
209
+ " Sync " +
210
+ (allInSync ? green("✓ in sync") : yellow("⚠ check needed")),
211
+ );
212
+ console.log(
213
+ " Working on " + (state.working ? cyan(state.working) : gray("not set")),
214
+ );
215
+ console.log(
216
+ " Intent " + (state.intent ? cyan(state.intent) : gray("not set")),
217
+ );
218
+ console.log(
219
+ " Decisions " +
220
+ (state.decisions ? state.decisions.length : 0) +
221
+ " recorded\n",
222
+ );
223
+
224
+ console.log(" " + bold("Ready to use:"));
225
+ console.log(" " + cyan("1.") + " Open " + cyan("inferno/CONTEXT.md"));
226
+ console.log(" " + cyan("2.") + " Copy everything");
227
+ console.log(
228
+ " " + cyan("3.") + " Paste at the start of your next AI session\n",
229
+ );
230
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
5
5
  "type": "module",
6
6
  "bin": {