infernoflow 0.37.1 → 0.37.3
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 +64 -0
- package/dist/bin/infernoflow.mjs +29 -277
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/ask.mjs +4 -299
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +30 -135
- package/dist/lib/commands/cloud.mjs +10 -773
- package/dist/lib/commands/context.mjs +34 -346
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/feedback.mjs +12 -216
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +11 -378
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +45 -631
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +18 -248
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/recap.mjs +6 -380
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +11 -1118
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/stats.mjs +5 -402
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/switch.mjs +13 -520
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -195
- package/dist/lib/commands/uninstall.mjs +13 -406
- package/dist/lib/commands/upgrade.mjs +20 -153
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/telemetry.mjs +19 -269
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -343
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -95
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/package.json +2 -4
- package/scripts/postinstall.js +2 -2
|
@@ -1,346 +1,34 @@
|
|
|
1
|
-
import fs from "node:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return entries.filter(e=>e.items.length>0);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function contextCommand(args) {
|
|
39
|
-
const has = (f) => args.includes(f);
|
|
40
|
-
const flag = (f) => { const i=args.indexOf(f); return i!==-1&&args[i+1]?args[i+1]:null; };
|
|
41
|
-
|
|
42
|
-
const intent = flag("--intent") || flag("-i");
|
|
43
|
-
const working = flag("--working") || flag("-w");
|
|
44
|
-
const decision = flag("--decision") || flag("-d");
|
|
45
|
-
const showOnly = has("--show") || has("-s");
|
|
46
|
-
const copyFlag = has("--copy") || has("-c");
|
|
47
|
-
const cursorFlag = has("--cursor");
|
|
48
|
-
const copilotFlag = has("--copilot");
|
|
49
|
-
const resetFlag= has("--reset");
|
|
50
|
-
const watchFlag = has("--watch");
|
|
51
|
-
const autoCommit = has("--auto-commit") || has("--auto-push");
|
|
52
|
-
const autoPush = has("--auto-push");
|
|
53
|
-
const statsFlag = has("--stats");
|
|
54
|
-
const watchInterval = parseInt(flag("--interval") || "30", 10) * 1000;
|
|
55
|
-
|
|
56
|
-
console.log("\n "+bold("��� infernoflow — context"));
|
|
57
|
-
console.log(" "+"─".repeat(50)+"\n");
|
|
58
|
-
|
|
59
|
-
if(!fs.existsSync(INFERNO_DIR)){
|
|
60
|
-
console.error(red(" ✘ inferno/ not found"));
|
|
61
|
-
console.error(gray(" → Run: infernoflow init\n"));
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const contract = readJSON(path.join(INFERNO_DIR,"contract.json"));
|
|
66
|
-
const capabilities = readJSON(path.join(INFERNO_DIR,"capabilities.json"));
|
|
67
|
-
const changelog = readFile(path.join(INFERNO_DIR,"CHANGELOG.md"));
|
|
68
|
-
|
|
69
|
-
if(!contract||!capabilities){
|
|
70
|
-
console.error(red(" ✘ Missing contract.json or capabilities.json\n"));
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let state = loadState();
|
|
75
|
-
if(resetFlag){ state={}; console.log(yellow(" ⚠ State reset\n")); }
|
|
76
|
-
if(intent) { state.intent=intent; state.intentUpdated=new Date().toISOString(); console.log(green(' ✔ Intent saved: "'+intent+'"')); }
|
|
77
|
-
if(working) { state.working=working; state.workingUpdated=new Date().toISOString(); console.log(green(' ✔ Working on: "'+working+'"')); }
|
|
78
|
-
if(decision) { if(!state.decisions) state.decisions=[]; state.decisions.push({text:decision,date:new Date().toISOString()}); console.log(green(' ✔ Decision recorded: "'+decision+'"')); }
|
|
79
|
-
if(intent||working||decision) saveState(state);
|
|
80
|
-
|
|
81
|
-
const capList = capabilities.capabilities||[];
|
|
82
|
-
const allInSync = capList.length===(contract.capabilities||[]).length;
|
|
83
|
-
const recent = parseChangelog(changelog,3);
|
|
84
|
-
const version = String(contract.policyVersion).replace(/^v/i,"");
|
|
85
|
-
const now = new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"});
|
|
86
|
-
const syncBadge = allInSync?"✓ validated":"⚠ out of sync";
|
|
87
|
-
const implementTask = state.intent || "describe the exact task to implement";
|
|
88
|
-
const implementInput = { task: implementTask, contract, caps: capabilities, scenarios: [], state };
|
|
89
|
-
const cursorPrompt = buildCursorImplementPrompt(implementInput);
|
|
90
|
-
const genericPrompt = buildGenericImplementPrompt(implementInput);
|
|
91
|
-
|
|
92
|
-
const capLines = capList.map(c=>"- **"+c.id+"** — "+c.title).join("\n");
|
|
93
|
-
const chgLines = recent.length>0 ? recent.map(e=>"### "+e.title+"\n"+e.items.map(i=>" - "+i).join("\n")).join("\n\n") : "_No recent changes_";
|
|
94
|
-
const intentLine = state.intent ? state.intent+" _("+fmtDate(state.intentUpdated)+")_" : "_Not set — run: infernoflow context --intent \"...\"_";
|
|
95
|
-
const workingLine = state.working ? state.working+" _("+fmtDate(state.workingUpdated)+")_" : "_Not set — run: infernoflow context --working \"...\"_";
|
|
96
|
-
const decLines = state.decisions&&state.decisions.length>0 ? state.decisions.slice(-5).map(d=>"- "+d.text+" _("+fmtDate(d.date)+")_").join("\n") : "_No decisions recorded_";
|
|
97
|
-
|
|
98
|
-
// ── Session memory (what AI can't infer from code) ────────────────────────
|
|
99
|
-
const sessionsFile = path.join(INFERNO_DIR, "sessions.jsonl");
|
|
100
|
-
let sessionLines = "_No session history yet — use: infernoflow log \"<what happened>\"_";
|
|
101
|
-
if (fs.existsSync(sessionsFile)) {
|
|
102
|
-
const entries = fs.readFileSync(sessionsFile, "utf8")
|
|
103
|
-
.split("\n").filter(Boolean)
|
|
104
|
-
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
105
|
-
.filter(Boolean)
|
|
106
|
-
.slice(-10); // last 10 entries
|
|
107
|
-
if (entries.length) {
|
|
108
|
-
sessionLines = entries.map(e => {
|
|
109
|
-
const ts = new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});
|
|
110
|
-
const result = e.result ? ` [${e.result}]` : "";
|
|
111
|
-
return `- **${e.type||"note"}**${result} ${e.summary} _(${ts})_`;
|
|
112
|
-
}).join("\n");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── Design system (theme.json) ────────────────────────────────────────────
|
|
117
|
-
const themeFile = path.join(INFERNO_DIR, "theme.json");
|
|
118
|
-
let themeLines = "_Not scanned yet — run: infernoflow theme_";
|
|
119
|
-
if (fs.existsSync(themeFile)) {
|
|
120
|
-
try {
|
|
121
|
-
const theme = JSON.parse(fs.readFileSync(themeFile, "utf8"));
|
|
122
|
-
const lines = [];
|
|
123
|
-
if (theme.fonts?.primary) lines.push(`- **Font (primary):** ${theme.fonts.primary}`);
|
|
124
|
-
if (theme.fonts?.mono) lines.push(`- **Font (mono):** ${theme.fonts.mono}`);
|
|
125
|
-
if (theme.colors?.mode) lines.push(`- **Color mode:** ${theme.colors.mode}`);
|
|
126
|
-
if (theme.colors?.palette) {
|
|
127
|
-
const pal = Object.entries(theme.colors.palette).map(([k,v])=>`${k}=${v}`).join(" ");
|
|
128
|
-
lines.push(`- **Palette:** ${pal}`);
|
|
129
|
-
}
|
|
130
|
-
if (theme.cssVars && Object.keys(theme.cssVars).length) {
|
|
131
|
-
const vars = Object.entries(theme.cssVars).slice(0,8).map(([k,v])=>`${k}: ${v}`).join(" | ");
|
|
132
|
-
lines.push(`- **CSS vars:** ${vars}`);
|
|
133
|
-
}
|
|
134
|
-
if (theme.framework) lines.push(`- **Framework:** ${theme.framework}`);
|
|
135
|
-
lines.push("", "> ⚠ Always use these exact values. Do not introduce new colors or fonts.");
|
|
136
|
-
themeLines = lines.join("\n");
|
|
137
|
-
} catch {}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const md = [
|
|
141
|
-
"# Project Context — "+contract.policyId+" v"+version,
|
|
142
|
-
"> Generated by infernoflow | "+now+" | "+syncBadge,
|
|
143
|
-
"","---","",
|
|
144
|
-
"## Session Memory — what AI can't infer from code",
|
|
145
|
-
"_Decisions made, failed attempts, gotchas, preferences_","",
|
|
146
|
-
sessionLines,"","---","",
|
|
147
|
-
"## Design System",
|
|
148
|
-
"_Fonts, colors, CSS vars — always match these exactly_","",
|
|
149
|
-
themeLines,"","---","",
|
|
150
|
-
"## What this system does","",capLines,"","---","",
|
|
151
|
-
"## Recent changes","",chgLines,"","---","",
|
|
152
|
-
"## Current state","",
|
|
153
|
-
"- **Capabilities:** "+capList.length,
|
|
154
|
-
"- **Version:** v"+version,
|
|
155
|
-
"- **Sync:** "+syncBadge,
|
|
156
|
-
"","---","",
|
|
157
|
-
"## What I am working on right now","",workingLine,"","---","",
|
|
158
|
-
"## Intent — what I want to build next","",intentLine,"","---","",
|
|
159
|
-
"## Decisions & notes","",decLines,"","---",
|
|
160
|
-
"",
|
|
161
|
-
"## Implementation Prompt Seed","",
|
|
162
|
-
"Use this to start coding immediately with an agent:","",
|
|
163
|
-
"```bash",
|
|
164
|
-
`infernoflow implement "${implementTask}" --mode both`,
|
|
165
|
-
"```",
|
|
166
|
-
"",
|
|
167
|
-
"### Cursor Agent Prompt","",
|
|
168
|
-
"```text",
|
|
169
|
-
cursorPrompt,
|
|
170
|
-
"```",
|
|
171
|
-
"",
|
|
172
|
-
"### Generic Agent Prompt","",
|
|
173
|
-
"```text",
|
|
174
|
-
genericPrompt,
|
|
175
|
-
"```",
|
|
176
|
-
"",
|
|
177
|
-
"---",
|
|
178
|
-
"_Paste this block at the start of any new AI session._"
|
|
179
|
-
].join("\n");
|
|
180
|
-
|
|
181
|
-
if(!showOnly){ fs.writeFileSync(CONTEXT_FILE,md,"utf8"); console.log(green("\n ✔ Context written → "+CONTEXT_FILE)); }
|
|
182
|
-
|
|
183
|
-
if(copyFlag){
|
|
184
|
-
const ok=copyToClipboard(md);
|
|
185
|
-
console.log(ok ? green(" ✔ Copied to clipboard — paste with Ctrl+V") : yellow(" ⚠ Clipboard copy failed — open inferno/CONTEXT.md manually"));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (cursorFlag) {
|
|
189
|
-
fs.writeFileSync(".cursorrules", md, "utf8");
|
|
190
|
-
console.log(green(" ✔ Written to .cursorrules — Cursor loads this automatically"));
|
|
191
|
-
}
|
|
192
|
-
if (copilotFlag) {
|
|
193
|
-
if (!fs.existsSync(".github")) fs.mkdirSync(".github");
|
|
194
|
-
fs.writeFileSync(".github/copilot-instructions.md", md, "utf8");
|
|
195
|
-
console.log(green(" ✔ Written to .github/copilot-instructions.md — Copilot loads this automatically"));
|
|
196
|
-
}
|
|
197
|
-
console.log("\n "+bold("Context Summary"));
|
|
198
|
-
console.log(" "+"─".repeat(50));
|
|
199
|
-
console.log(" Project "+contract.policyId+" — v"+version);
|
|
200
|
-
console.log(" Capabilities "+capList.length+" registered");
|
|
201
|
-
console.log(" Sync "+(allInSync?green("✓ in sync"):yellow("⚠ check needed")));
|
|
202
|
-
console.log(" Working on "+(state.working?cyan(state.working):gray("not set")));
|
|
203
|
-
console.log(" Intent "+(state.intent ?cyan(state.intent) :gray("not set")));
|
|
204
|
-
console.log(" Decisions "+(state.decisions?state.decisions.length:0)+" recorded");
|
|
205
|
-
|
|
206
|
-
// Brief stats line when --stats flag is set
|
|
207
|
-
if (statsFlag) {
|
|
208
|
-
try {
|
|
209
|
-
const { statsCommand } = await import("./stats.mjs");
|
|
210
|
-
process.stdout.write("\n");
|
|
211
|
-
await statsCommand(["--brief"]);
|
|
212
|
-
} catch {}
|
|
213
|
-
}
|
|
214
|
-
console.log();
|
|
215
|
-
console.log(" "+bold("Implementation Prompt"));
|
|
216
|
-
console.log(" "+cyan("→")+" Run "+cyan(`infernoflow implement "${implementTask}" --mode both`)+"\n");
|
|
217
|
-
|
|
218
|
-
if(copyFlag){
|
|
219
|
-
console.log(" "+bold("Ready to use:"));
|
|
220
|
-
console.log(" "+cyan("→")+" Paste into Claude / Cursor / Copilot with "+cyan("Ctrl+V")+"\n");
|
|
221
|
-
} else {
|
|
222
|
-
console.log(" "+bold("Ready to use:"));
|
|
223
|
-
console.log(" "+cyan("1.")+" Open "+cyan("inferno/CONTEXT.md"));
|
|
224
|
-
console.log(" "+cyan("2.")+" Copy everything");
|
|
225
|
-
console.log(" "+cyan("3.")+" Paste at the start of your next AI session");
|
|
226
|
-
console.log(" "+gray(" tip: use --copy to skip steps 1-2 automatically")+"\n");
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ── Watch mode ────────────────────────────────────────────────────────────
|
|
230
|
-
if (watchFlag) {
|
|
231
|
-
const modeLabel = autoPush ? "auto-push" : autoCommit ? "auto-commit" : "watch";
|
|
232
|
-
console.log(" " + cyan("👁 Watch mode active") + gray(
|
|
233
|
-
` — polling every ${watchInterval / 1000}s` +
|
|
234
|
-
(autoPush ? " · will commit + push on change" : autoCommit ? " · will commit on change" : "")
|
|
235
|
-
));
|
|
236
|
-
console.log(" " + gray("Press Ctrl+C to stop\n"));
|
|
237
|
-
|
|
238
|
-
let lastChangedFiles = "";
|
|
239
|
-
let lastCommittedContent = null;
|
|
240
|
-
|
|
241
|
-
// ── git helpers ──────────────────────────────────────────────────────
|
|
242
|
-
function gitRun(cmd) {
|
|
243
|
-
try {
|
|
244
|
-
execSync(cmd, { cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
245
|
-
return true;
|
|
246
|
-
} catch { return false; }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function gitIsCleanFor(filePath) {
|
|
250
|
-
// Returns true if the file has no staged/unstaged changes (nothing to commit)
|
|
251
|
-
try {
|
|
252
|
-
const out = execSync(`git status --porcelain "${filePath}"`, {
|
|
253
|
-
cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]
|
|
254
|
-
}).trim();
|
|
255
|
-
return out === "";
|
|
256
|
-
} catch { return true; }
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function commitContext(contextPath, affectedCaps, changedCount) {
|
|
260
|
-
const caps = affectedCaps.length > 0 ? affectedCaps.slice(0, 3).join(", ") : `${changedCount} files`;
|
|
261
|
-
const msg = `chore: update context [${caps}]`;
|
|
262
|
-
const staged = gitRun(`git add "${contextPath}"`);
|
|
263
|
-
if (!staged) return { ok: false, reason: "git add failed" };
|
|
264
|
-
if (gitIsCleanFor(contextPath)) return { ok: false, reason: "nothing to commit" };
|
|
265
|
-
const committed = gitRun(`git commit -m "${msg}"`);
|
|
266
|
-
if (!committed) return { ok: false, reason: "git commit failed (lock?)" };
|
|
267
|
-
return { ok: true, msg };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function pushContext() {
|
|
271
|
-
const ok = gitRun("git push");
|
|
272
|
-
return ok;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// ── poll loop ────────────────────────────────────────────────────────
|
|
276
|
-
const poll = async () => {
|
|
277
|
-
try {
|
|
278
|
-
const cwd = process.cwd();
|
|
279
|
-
const drift = detectDrift(cwd, { sinceCommits: 1 });
|
|
280
|
-
const changedKey = drift.changedFiles.sort().join("|");
|
|
281
|
-
|
|
282
|
-
if (changedKey === lastChangedFiles) return;
|
|
283
|
-
lastChangedFiles = changedKey;
|
|
284
|
-
if (drift.changedFiles.length === 0) return;
|
|
285
|
-
|
|
286
|
-
// Update "working" field
|
|
287
|
-
const affected = drift.affectedCapabilities.map(c => c.id);
|
|
288
|
-
const newWorking = affected.length > 0
|
|
289
|
-
? `Working on: ${affected.join(", ")} (${drift.changedFiles.length} files changed)`
|
|
290
|
-
: `${drift.changedFiles.length} files changed — no capability match yet`;
|
|
291
|
-
|
|
292
|
-
const currentState = loadState();
|
|
293
|
-
if (currentState.working !== newWorking) {
|
|
294
|
-
currentState.working = newWorking;
|
|
295
|
-
currentState.workingUpdated = new Date().toISOString();
|
|
296
|
-
saveState(currentState);
|
|
297
|
-
|
|
298
|
-
// Regenerate CONTEXT.md silently
|
|
299
|
-
await contextCommand(args.filter(a => a !== "--watch" && a !== "--auto-commit" && a !== "--auto-push"));
|
|
300
|
-
|
|
301
|
-
const newContent = readFile(CONTEXT_FILE);
|
|
302
|
-
const ts = new Date().toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
303
|
-
|
|
304
|
-
process.stderr.write(
|
|
305
|
-
`\n ${green("✔")} [${ts}] Context updated — ${affected.length} capabilities affected\n` +
|
|
306
|
-
` ${gray(drift.changedFiles.slice(0, 3).join(", ") + (drift.changedFiles.length > 3 ? ` +${drift.changedFiles.length - 3} more` : ""))}\n`
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
// Auto-commit if enabled and content actually changed
|
|
310
|
-
if (autoCommit && newContent !== lastCommittedContent) {
|
|
311
|
-
lastCommittedContent = newContent;
|
|
312
|
-
const result = commitContext(CONTEXT_FILE, affected, drift.changedFiles.length);
|
|
313
|
-
if (result.ok) {
|
|
314
|
-
process.stderr.write(` ${green("✔")} Committed: ${gray(result.msg)}\n`);
|
|
315
|
-
if (autoPush) {
|
|
316
|
-
const pushed = pushContext();
|
|
317
|
-
process.stderr.write(
|
|
318
|
-
pushed
|
|
319
|
-
? ` ${green("✔")} Pushed to origin\n`
|
|
320
|
-
: ` ${yellow("⚠")} Push failed — will retry next change\n`
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
process.stderr.write(` ${yellow("⚠")} Commit skipped: ${gray(result.reason)}\n`);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
} catch {
|
|
329
|
-
// Silent — watch mode never crashes
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
// Poll immediately then on interval
|
|
334
|
-
await poll();
|
|
335
|
-
const timer = setInterval(poll, watchInterval);
|
|
336
|
-
|
|
337
|
-
process.on("SIGINT", () => {
|
|
338
|
-
clearInterval(timer);
|
|
339
|
-
process.stderr.write("\n " + gray("Watch stopped.\n\n"));
|
|
340
|
-
process.exit(0);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
// Prevent Node from exiting
|
|
344
|
-
await new Promise(() => {});
|
|
345
|
-
}
|
|
346
|
-
}
|
|
1
|
+
import c from"node:fs";import y from"node:path";import{execSync as C}from"node:child_process";import{bold as I,gray as m,cyan as p,red as Z,green as d,yellow as _}from"../ui/output.mjs";import{buildCursorImplementPrompt as $t,buildGenericImplementPrompt as bt}from"../ui/prompts.mjs";import{detectDrift as Ft}from"../git/detect-drift.mjs";function jt(n){try{const i=process.platform;if(i==="win32")C("clip",{input:n});else if(i==="darwin")C("pbcopy",{input:n});else try{C("xclip -selection clipboard",{input:n})}catch{C("xsel --clipboard --input",{input:n})}return!0}catch{return!1}}const h="inferno",D=y.join(h,"CONTEXT.md"),tt=y.join(h,"context-state.json");function et(n){try{return JSON.parse(c.readFileSync(n,"utf8"))}catch{return null}}function E(n){try{return c.readFileSync(n,"utf8")}catch{return null}}function nt(){const n=E(tt);if(!n)return{};try{return JSON.parse(n)}catch{return{}}}function ot(n){c.writeFileSync(tt,JSON.stringify(n,null,2),"utf8")}function A(n){return n?new Date(n).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}):"unknown"}function It(n,i){if(!n)return[];const s=[];let l=null;for(const a of n.split(`
|
|
2
|
+
`))if(a.startsWith("## ")){if(l&&s.length<i&&s.push(l),s.length>=i)break;l={title:a.replace("## ","").trim(),items:[]}}else l&&a.startsWith("- ")&&l.items.push(a.replace("- ","").trim());return l&&s.length<i&&s.push(l),s.filter(a=>a.items.length>0)}async function _t(n){const i=t=>n.includes(t),s=t=>{const o=n.indexOf(t);return o!==-1&&n[o+1]?n[o+1]:null},l=s("--intent")||s("-i"),a=s("--working")||s("-w"),x=s("--decision")||s("-d"),it=i("--show")||i("-s"),B=i("--copy")||i("-c"),st=i("--cursor"),rt=i("--copilot"),ct=i("--reset"),lt=i("--watch"),P=i("--auto-commit")||i("--auto-push"),L=i("--auto-push"),at=i("--stats"),R=parseInt(s("--interval")||"30",10)*1e3;console.log(`
|
|
3
|
+
`+I("\uFFFD\uFFFD\uFFFD infernoflow \u2014 context")),console.log(" "+"\u2500".repeat(50)+`
|
|
4
|
+
`),c.existsSync(h)||(console.error(Z(" \u2718 inferno/ not found")),console.error(m(` \u2192 Run: infernoflow init
|
|
5
|
+
`)),process.exit(1));const S=et(y.join(h,"contract.json")),T=et(y.join(h,"capabilities.json")),pt=E(y.join(h,"CHANGELOG.md"));(!S||!T)&&(console.error(Z(` \u2718 Missing contract.json or capabilities.json
|
|
6
|
+
`)),process.exit(1));let e=nt();ct&&(e={},console.log(_(` \u26A0 State reset
|
|
7
|
+
`))),l&&(e.intent=l,e.intentUpdated=new Date().toISOString(),console.log(d(' \u2714 Intent saved: "'+l+'"'))),a&&(e.working=a,e.workingUpdated=new Date().toISOString(),console.log(d(' \u2714 Working on: "'+a+'"'))),x&&(e.decisions||(e.decisions=[]),e.decisions.push({text:x,date:new Date().toISOString()}),console.log(d(' \u2714 Decision recorded: "'+x+'"'))),(l||a||x)&&ot(e);const O=T.capabilities||[],V=O.length===(S.capabilities||[]).length,J=It(pt,3),W=String(S.policyVersion).replace(/^v/i,""),dt=new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}),U=V?"\u2713 validated":"\u26A0 out of sync",G=e.intent||"describe the exact task to implement",X={task:G,contract:S,caps:T,scenarios:[],state:e},ut=$t(X),mt=bt(X),gt=O.map(t=>"- **"+t.id+"** \u2014 "+t.title).join(`
|
|
8
|
+
`),ft=J.length>0?J.map(t=>"### "+t.title+`
|
|
9
|
+
`+t.items.map(o=>" - "+o).join(`
|
|
10
|
+
`)).join(`
|
|
11
|
+
|
|
12
|
+
`):"_No recent changes_",ht=e.intent?e.intent+" _("+A(e.intentUpdated)+")_":'_Not set \u2014 run: infernoflow context --intent "..."_',wt=e.working?e.working+" _("+A(e.workingUpdated)+")_":'_Not set \u2014 run: infernoflow context --working "..."_',yt=e.decisions&&e.decisions.length>0?e.decisions.slice(-5).map(t=>"- "+t.text+" _("+A(t.date)+")_").join(`
|
|
13
|
+
`):"_No decisions recorded_",M=y.join(h,"sessions.jsonl");let H='_No session history yet \u2014 use: infernoflow log "<what happened>"_';if(c.existsSync(M)){const t=c.readFileSync(M,"utf8").split(`
|
|
14
|
+
`).filter(Boolean).map(o=>{try{return JSON.parse(o)}catch{return null}}).filter(Boolean).slice(-10);t.length&&(H=t.map(o=>{const f=new Date(o.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),u=o.result?` [${o.result}]`:"";return`- **${o.type||"note"}**${u} ${o.summary} _(${f})_`}).join(`
|
|
15
|
+
`))}const K=y.join(h,"theme.json");let q="_Not scanned yet \u2014 run: infernoflow theme_";if(c.existsSync(K))try{const t=JSON.parse(c.readFileSync(K,"utf8")),o=[];if(t.fonts?.primary&&o.push(`- **Font (primary):** ${t.fonts.primary}`),t.fonts?.mono&&o.push(`- **Font (mono):** ${t.fonts.mono}`),t.colors?.mode&&o.push(`- **Color mode:** ${t.colors.mode}`),t.colors?.palette){const f=Object.entries(t.colors.palette).map(([u,k])=>`${u}=${k}`).join(" ");o.push(`- **Palette:** ${f}`)}if(t.cssVars&&Object.keys(t.cssVars).length){const f=Object.entries(t.cssVars).slice(0,8).map(([u,k])=>`${u}: ${k}`).join(" | ");o.push(`- **CSS vars:** ${f}`)}t.framework&&o.push(`- **Framework:** ${t.framework}`),o.push("","> \u26A0 Always use these exact values. Do not introduce new colors or fonts."),q=o.join(`
|
|
16
|
+
`)}catch{}const N=["# Project Context \u2014 "+S.policyId+" v"+W,"> Generated by infernoflow | "+dt+" | "+U,"","---","","## Session Memory \u2014 what AI can't infer from code","_Decisions made, failed attempts, gotchas, preferences_","",H,"","---","","## Design System","_Fonts, colors, CSS vars \u2014 always match these exactly_","",q,"","---","","## What this system does","",gt,"","---","","## Recent changes","",ft,"","---","","## Current state","","- **Capabilities:** "+O.length,"- **Version:** v"+W,"- **Sync:** "+U,"","---","","## What I am working on right now","",wt,"","---","","## Intent \u2014 what I want to build next","",ht,"","---","","## Decisions & notes","",yt,"","---","","## Implementation Prompt Seed","","Use this to start coding immediately with an agent:","","```bash",`infernoflow implement "${G}" --mode both`,"```","","### Cursor Agent Prompt","","```text",ut,"```","","### Generic Agent Prompt","","```text",mt,"```","","---","_Paste this block at the start of any new AI session._"].join(`
|
|
17
|
+
`);if(it||(c.writeFileSync(D,N,"utf8"),console.log(d(`
|
|
18
|
+
\u2714 Context written \u2192 `+D))),B){const t=jt(N);console.log(t?d(" \u2714 Copied to clipboard \u2014 paste with Ctrl+V"):_(" \u26A0 Clipboard copy failed \u2014 open inferno/CONTEXT.md manually"))}if(st&&(c.writeFileSync(".cursorrules",N,"utf8"),console.log(d(" \u2714 Written to .cursorrules \u2014 Cursor loads this automatically"))),rt&&(c.existsSync(".github")||c.mkdirSync(".github"),c.writeFileSync(".github/copilot-instructions.md",N,"utf8"),console.log(d(" \u2714 Written to .github/copilot-instructions.md \u2014 Copilot loads this automatically"))),console.log(`
|
|
19
|
+
`+I("Context Summary")),console.log(" "+"\u2500".repeat(50)),console.log(" Project "+S.policyId+" \u2014 v"+W),console.log(" Capabilities "+O.length+" registered"),console.log(" Sync "+(V?d("\u2713 in sync"):_("\u26A0 check needed"))),console.log(" Working on "+(e.working?p(e.working):m("not set"))),console.log(" Intent "+(e.intent?p(e.intent):m("not set"))),console.log(" Decisions "+(e.decisions?e.decisions.length:0)+" recorded"),at)try{const{statsCommand:t}=await import("./stats.mjs");process.stdout.write(`
|
|
20
|
+
`),await t(["--brief"])}catch{}if(console.log(),console.log(" "+I("Implementation Prompt")),console.log(" "+p("\u2192")+" Run "+p(`infernoflow implement "${G}" --mode both`)+`
|
|
21
|
+
`),B?(console.log(" "+I("Ready to use:")),console.log(" "+p("\u2192")+" Paste into Claude / Cursor / Copilot with "+p("Ctrl+V")+`
|
|
22
|
+
`)):(console.log(" "+I("Ready to use:")),console.log(" "+p("1.")+" Open "+p("inferno/CONTEXT.md")),console.log(" "+p("2.")+" Copy everything"),console.log(" "+p("3.")+" Paste at the start of your next AI session"),console.log(" "+m(" tip: use --copy to skip steps 1-2 automatically")+`
|
|
23
|
+
`)),lt){let u=function(g){try{return C(g,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}),!0}catch{return!1}},k=function(g){try{return C(`git status --porcelain "${g}"`,{cwd:process.cwd(),encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()===""}catch{return!0}},z=function(g,r,v){const b=`chore: update context [${r.length>0?r.slice(0,3).join(", "):`${v} files`}]`;return u(`git add "${g}"`)?k(g)?{ok:!1,reason:"nothing to commit"}:u(`git commit -m "${b}"`)?{ok:!0,msg:b}:{ok:!1,reason:"git commit failed (lock?)"}:{ok:!1,reason:"git add failed"}},Q=function(){return u("git push")};var xt=u,Ot=k,Nt=z,vt=Q;const t=L?"auto-push":P?"auto-commit":"watch";console.log(" "+p("\u{1F441} Watch mode active")+m(` \u2014 polling every ${R/1e3}s`+(L?" \xB7 will commit + push on change":P?" \xB7 will commit on change":""))),console.log(" "+m(`Press Ctrl+C to stop
|
|
24
|
+
`));let o="",f=null;const Y=async()=>{try{const g=process.cwd(),r=Ft(g,{sinceCommits:1}),v=r.changedFiles.sort().join("|");if(v===o||(o=v,r.changedFiles.length===0))return;const $=r.affectedCapabilities.map(j=>j.id),b=$.length>0?`Working on: ${$.join(", ")} (${r.changedFiles.length} files changed)`:`${r.changedFiles.length} files changed \u2014 no capability match yet`,F=nt();if(F.working!==b){F.working=b,F.workingUpdated=new Date().toISOString(),ot(F),await _t(n.filter(w=>w!=="--watch"&&w!=="--auto-commit"&&w!=="--auto-push"));const j=E(D),kt=new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});if(process.stderr.write(`
|
|
25
|
+
${d("\u2714")} [${kt}] Context updated \u2014 ${$.length} capabilities affected
|
|
26
|
+
${m(r.changedFiles.slice(0,3).join(", ")+(r.changedFiles.length>3?` +${r.changedFiles.length-3} more`:""))}
|
|
27
|
+
`),P&&j!==f){f=j;const w=z(D,$,r.changedFiles.length);if(w.ok){if(process.stderr.write(` ${d("\u2714")} Committed: ${m(w.msg)}
|
|
28
|
+
`),L){const Ct=Q();process.stderr.write(Ct?` ${d("\u2714")} Pushed to origin
|
|
29
|
+
`:` ${_("\u26A0")} Push failed \u2014 will retry next change
|
|
30
|
+
`)}}else process.stderr.write(` ${_("\u26A0")} Commit skipped: ${m(w.reason)}
|
|
31
|
+
`)}}}catch{}};await Y();const St=setInterval(Y,R);process.on("SIGINT",()=>{clearInterval(St),process.stderr.write(`
|
|
32
|
+
`+m(`Watch stopped.
|
|
33
|
+
|
|
34
|
+
`)),process.exit(0)}),await new Promise(()=>{})}}export{_t as contextCommand};
|