infernoflow 0.6.0 → 0.7.1

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.
@@ -23,6 +23,7 @@ const HELP = `
23
23
  --working "..." What you are building right now
24
24
  --decision "..." Record a decision or note
25
25
  --show Print context without writing file
26
+ --copy, -c Copy context to clipboard instantly
26
27
  --reset Clear all stored state
27
28
 
28
29
  ${bold("Typical workflow:")}
@@ -1,170 +1,128 @@
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 { execSync } from "node:child_process";
4
+ import { bold, gray, cyan, red, green, yellow } from "../ui/output.mjs";
5
+
6
+ function copyToClipboard(text) {
7
+ try {
8
+ const p = process.platform;
9
+ if (p === "win32") execSync("clip", { input: text });
10
+ else if (p === "darwin") execSync("pbcopy", { input: text });
11
+ else { try { execSync("xclip -selection clipboard", { input: text }); } catch { execSync("xsel --clipboard --input", { input: text }); } }
12
+ return true;
13
+ } catch { return false; }
14
+ }
15
+
16
+ const INFERNO_DIR = "inferno";
17
+ const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
18
+ const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
19
+
20
+ function readJSON(f) { try { return JSON.parse(fs.readFileSync(f,"utf8")); } catch { return null; } }
21
+ function readFile(f) { try { return fs.readFileSync(f,"utf8"); } catch { return null; } }
22
+ function loadState() { const r=readFile(STATE_FILE); if(!r) return {}; try { return JSON.parse(r); } catch { return {}; } }
23
+ function saveState(s) { fs.writeFileSync(STATE_FILE,JSON.stringify(s,null,2),"utf8"); }
24
+ function fmtDate(iso) { if(!iso) return "unknown"; return new Date(iso).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}); }
25
+ function parseChangelog(txt,max) {
26
+ if(!txt) return [];
27
+ const entries=[]; let cur=null;
28
+ for(const line of txt.split("\n")) {
29
+ if(line.startsWith("## ")) { if(cur&&entries.length<max) entries.push(cur); if(entries.length>=max) break; cur={title:line.replace("## ","").trim(),items:[]}; }
30
+ else if(cur&&line.startsWith("- ")) cur.items.push(line.replace("- ","").trim());
31
+ }
32
+ if(cur&&entries.length<max) entries.push(cur);
33
+ return entries.filter(e=>e.items.length>0);
34
+ }
35
+
36
+ export async function contextCommand(args) {
37
+ const has = (f) => args.includes(f);
38
+ const flag = (f) => { const i=args.indexOf(f); return i!==-1&&args[i+1]?args[i+1]:null; };
39
+
40
+ const intent = flag("--intent") || flag("-i");
41
+ const working = flag("--working") || flag("-w");
42
+ const decision = flag("--decision") || flag("-d");
43
+ const showOnly = has("--show") || has("-s");
44
+ const copyFlag = has("--copy") || has("-c");
45
+ const resetFlag= has("--reset");
46
+
47
+ console.log("\n "+bold("��� infernoflow — context"));
48
+ console.log(" "+"─".repeat(50)+"\n");
49
+
50
+ if(!fs.existsSync(INFERNO_DIR)){
51
+ console.error(red(" inferno/ not found"));
52
+ console.error(gray(" → Run: infernoflow init\n"));
53
+ process.exit(1);
54
+ }
55
+
56
+ const contract = readJSON(path.join(INFERNO_DIR,"contract.json"));
57
+ const capabilities = readJSON(path.join(INFERNO_DIR,"capabilities.json"));
58
+ const changelog = readFile(path.join(INFERNO_DIR,"CHANGELOG.md"));
59
+
60
+ if(!contract||!capabilities){
61
+ console.error(red(" ✘ Missing contract.json or capabilities.json\n"));
62
+ process.exit(1);
63
+ }
64
+
65
+ let state = loadState();
66
+ if(resetFlag){ state={}; console.log(yellow(" ⚠ State reset\n")); }
67
+ if(intent) { state.intent=intent; state.intentUpdated=new Date().toISOString(); console.log(green(' ✔ Intent saved: "'+intent+'"')); }
68
+ if(working) { state.working=working; state.workingUpdated=new Date().toISOString(); console.log(green(' Working on: "'+working+'"')); }
69
+ if(decision) { if(!state.decisions) state.decisions=[]; state.decisions.push({text:decision,date:new Date().toISOString()}); console.log(green(' ✔ Decision recorded: "'+decision+'"')); }
70
+ if(intent||working||decision) saveState(state);
71
+
72
+ const capList = capabilities.capabilities||[];
73
+ const allInSync = capList.length===(contract.capabilities||[]).length;
74
+ const recent = parseChangelog(changelog,3);
75
+ const version = String(contract.policyVersion).replace(/^v/i,"");
76
+ const now = new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"});
77
+ const syncBadge = allInSync?"✓ validated":"⚠ out of sync";
78
+
79
+ const capLines = capList.map(c=>"- **"+c.id+"** — "+c.title).join("\n");
80
+ const chgLines = recent.length>0 ? recent.map(e=>"### "+e.title+"\n"+e.items.map(i=>" - "+i).join("\n")).join("\n\n") : "_No recent changes_";
81
+ const intentLine = state.intent ? state.intent+" _("+fmtDate(state.intentUpdated)+")_" : "_Not set — run: infernoflow context --intent \"...\"_";
82
+ const workingLine = state.working ? state.working+" _("+fmtDate(state.workingUpdated)+")_" : "_Not set — run: infernoflow context --working \"...\"_";
83
+ const decLines = state.decisions&&state.decisions.length>0 ? state.decisions.slice(-5).map(d=>"- "+d.text+" _("+fmtDate(d.date)+")_").join("\n") : "_No decisions recorded_";
84
+
85
+ const md = [
86
+ "# Project Context — "+contract.policyId+" v"+version,
87
+ "> Generated by infernoflow | "+now+" | "+syncBadge,
88
+ "","---","",
89
+ "## What this system does","",capLines,"","---","",
90
+ "## Recent changes","",chgLines,"","---","",
91
+ "## Current state","",
92
+ "- **Capabilities:** "+capList.length,
93
+ "- **Version:** v"+version,
94
+ "- **Sync:** "+syncBadge,
95
+ "","---","",
96
+ "## What I am working on right now","",workingLine,"","---","",
97
+ "## Intent — what I want to build next","",intentLine,"","---","",
98
+ "## Decisions & notes","",decLines,"","---",
99
+ "_Paste this block at the start of any new AI session._"
100
+ ].join("\n");
101
+
102
+ if(!showOnly){ fs.writeFileSync(CONTEXT_FILE,md,"utf8"); console.log(green("\n ✔ Context written → "+CONTEXT_FILE)); }
103
+
104
+ if(copyFlag){
105
+ const ok=copyToClipboard(md);
106
+ console.log(ok ? green(" ✔ Copied to clipboard — paste with Ctrl+V") : yellow(" ⚠ Clipboard copy failed — open inferno/CONTEXT.md manually"));
107
+ }
108
+
109
+ console.log("\n "+bold("Context Summary"));
110
+ console.log(" "+"─".repeat(50));
111
+ console.log(" Project "+contract.policyId+" v"+version);
112
+ console.log(" Capabilities "+capList.length+" registered");
113
+ console.log(" Sync "+(allInSync?green("✓ in sync"):yellow("⚠ check needed")));
114
+ console.log(" Working on "+(state.working?cyan(state.working):gray("not set")));
115
+ console.log(" Intent "+(state.intent ?cyan(state.intent) :gray("not set")));
116
+ console.log(" Decisions "+(state.decisions?state.decisions.length:0)+" recorded\n");
117
+
118
+ if(copyFlag){
119
+ console.log(" "+bold("Ready to use:"));
120
+ console.log(" "+cyan("→")+" Paste into Claude / Cursor / Copilot with "+cyan("Ctrl+V")+"\n");
121
+ } else {
122
+ console.log(" "+bold("Ready to use:"));
123
+ console.log(" "+cyan("1.")+" Open "+cyan("inferno/CONTEXT.md"));
124
+ console.log(" "+cyan("2.")+" Copy everything");
125
+ console.log(" "+cyan("3.")+" Paste at the start of your next AI session");
126
+ console.log(" "+gray(" tip: use --copy to skip steps 1-2 automatically")+"\n");
127
+ }
128
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "The forge for liquid code — keep capabilities, contracts, and docs in sync.",
5
5
  "type": "module",
6
6
  "bin": {