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.
- package/lib/commands/context.mjs +230 -170
- package/package.json +1 -1
package/lib/commands/context.mjs
CHANGED
|
@@ -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(
|
|
10
|
-
try {
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
state.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
: "
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
##
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
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
|
+
}
|