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,282 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Smart semver bump recommendation based on capability changes since the last
|
|
5
|
-
* git tag (or a custom ref).
|
|
6
|
-
*
|
|
7
|
-
* Classification rules:
|
|
8
|
-
* MAJOR β any capability was REMOVED (breaking: callers lose functionality)
|
|
9
|
-
* MINOR β capabilities were ADDED (non-breaking: new surface area)
|
|
10
|
-
* PATCH β only metadata changed (title / description / status edits)
|
|
11
|
-
* NONE β no capability changes at all
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* infernoflow version # recommend bump type + show next version
|
|
15
|
-
* infernoflow version --apply # apply recommended bump to package.json
|
|
16
|
-
* infernoflow version --ref v1.2.3 # compare against a specific ref
|
|
17
|
-
* infernoflow version --json # machine-readable output
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as path from "node:path";
|
|
22
|
-
import { execSync } from "node:child_process";
|
|
23
|
-
import { header, ok, warn, info, bold, cyan, gray, green, red, yellow, done } from "../ui/output.mjs";
|
|
24
|
-
|
|
25
|
-
// ββ git helpers (shared with diff.mjs) βββββββββββββββββββββββββββββββββββββββ
|
|
26
|
-
|
|
27
|
-
function capture(cmd, cwd) {
|
|
28
|
-
try {
|
|
29
|
-
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function lastTag(cwd) {
|
|
36
|
-
return capture("git describe --tags --abbrev=0", cwd) || null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function fileAtRef(ref, relPath, cwd) {
|
|
40
|
-
return capture(`git show "${ref}:${relPath}"`, cwd);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ββ capability helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
44
|
-
|
|
45
|
-
function parseCaps(jsonText) {
|
|
46
|
-
if (!jsonText) return null;
|
|
47
|
-
try {
|
|
48
|
-
const obj = JSON.parse(jsonText);
|
|
49
|
-
const raw = obj.capabilities || [];
|
|
50
|
-
return raw.map(c => {
|
|
51
|
-
if (typeof c === "string") return { id: c, title: c };
|
|
52
|
-
return { id: c.id || c, title: c.title || c.id || String(c), status: c.status };
|
|
53
|
-
});
|
|
54
|
-
} catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function loadCapsFromDisk(infernoDir) {
|
|
60
|
-
const capsPath = path.join(infernoDir, "capabilities.json");
|
|
61
|
-
const contractPath = path.join(infernoDir, "contract.json");
|
|
62
|
-
if (fs.existsSync(capsPath)) return parseCaps(fs.readFileSync(capsPath, "utf8"));
|
|
63
|
-
if (fs.existsSync(contractPath)) return parseCaps(fs.readFileSync(contractPath, "utf8"));
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function loadCapsAtRef(ref, infernoRelDir, cwd) {
|
|
68
|
-
const capsJson = fileAtRef(ref, `${infernoRelDir}/capabilities.json`, cwd);
|
|
69
|
-
if (capsJson) return parseCaps(capsJson);
|
|
70
|
-
const contractJson = fileAtRef(ref, `${infernoRelDir}/contract.json`, cwd);
|
|
71
|
-
return parseCaps(contractJson);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function diffCaps(before, after) {
|
|
75
|
-
const beforeMap = new Map(before.map(c => [c.id, c]));
|
|
76
|
-
const afterMap = new Map(after.map(c => [c.id, c]));
|
|
77
|
-
|
|
78
|
-
const added = after.filter(c => !beforeMap.has(c.id));
|
|
79
|
-
const removed = before.filter(c => !afterMap.has(c.id));
|
|
80
|
-
|
|
81
|
-
const changed = [];
|
|
82
|
-
for (const c of after) {
|
|
83
|
-
const old = beforeMap.get(c.id);
|
|
84
|
-
if (!old) continue;
|
|
85
|
-
const changes = [];
|
|
86
|
-
if (old.title !== c.title) changes.push({ field: "title", from: old.title, to: c.title });
|
|
87
|
-
if ((old.status || "") !== (c.status || "")) changes.push({ field: "status", from: old.status || "β", to: c.status || "β" });
|
|
88
|
-
if (changes.length) changed.push({ id: c.id, changes });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { added, removed, changed };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ββ semver helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
95
|
-
|
|
96
|
-
function classifyBump(diff) {
|
|
97
|
-
if (diff.removed.length > 0) return "major";
|
|
98
|
-
if (diff.added.length > 0) return "minor";
|
|
99
|
-
if (diff.changed.length > 0) return "patch";
|
|
100
|
-
return "none";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function applyBump(version, type) {
|
|
104
|
-
const parts = (version || "0.0.0").split(".").map(Number);
|
|
105
|
-
if (type === "major") { parts[0]++; parts[1] = 0; parts[2] = 0; }
|
|
106
|
-
else if (type === "minor") { parts[1]++; parts[2] = 0; }
|
|
107
|
-
else if (type === "patch") { parts[2]++; }
|
|
108
|
-
return parts.join(".");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function readPackageVersion(cwd) {
|
|
112
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
113
|
-
if (!fs.existsSync(pkgPath)) return null;
|
|
114
|
-
try {
|
|
115
|
-
return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version || null;
|
|
116
|
-
} catch { return null; }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function writePackageVersion(cwd, newVersion) {
|
|
120
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
121
|
-
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
122
|
-
const data = JSON.parse(raw);
|
|
123
|
-
data.version = newVersion;
|
|
124
|
-
fs.writeFileSync(pkgPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ββ reason builder ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
128
|
-
|
|
129
|
-
function buildReason(type, diff, ref) {
|
|
130
|
-
const lines = [];
|
|
131
|
-
if (type === "major") {
|
|
132
|
-
lines.push(`${diff.removed.length} capability removed β breaking change`);
|
|
133
|
-
for (const c of diff.removed.slice(0, 3)) lines.push(` - ${c.id}: ${c.title}`);
|
|
134
|
-
if (diff.removed.length > 3) lines.push(` β¦ and ${diff.removed.length - 3} more`);
|
|
135
|
-
} else if (type === "minor") {
|
|
136
|
-
lines.push(`${diff.added.length} new capability added`);
|
|
137
|
-
for (const c of diff.added.slice(0, 3)) lines.push(` + ${c.id}: ${c.title}`);
|
|
138
|
-
if (diff.added.length > 3) lines.push(` β¦ and ${diff.added.length - 3} more`);
|
|
139
|
-
} else if (type === "patch") {
|
|
140
|
-
lines.push(`${diff.changed.length} capability metadata updated`);
|
|
141
|
-
} else {
|
|
142
|
-
lines.push(`No capability changes since ${ref}`);
|
|
143
|
-
}
|
|
144
|
-
return lines;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// ββ MCP-compatible JSON output ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
148
|
-
|
|
149
|
-
function emitJson(payload) {
|
|
150
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ββ main command ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
154
|
-
|
|
155
|
-
export async function versionCommand(rawArgs) {
|
|
156
|
-
const args = rawArgs.slice(1);
|
|
157
|
-
const asJson = args.includes("--json");
|
|
158
|
-
const apply = args.includes("--apply");
|
|
159
|
-
|
|
160
|
-
const refIdx = args.indexOf("--ref");
|
|
161
|
-
let ref = refIdx !== -1 ? args[refIdx + 1] : null;
|
|
162
|
-
|
|
163
|
-
const cwd = process.cwd();
|
|
164
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
165
|
-
|
|
166
|
-
if (!asJson) header("infernoflow version");
|
|
167
|
-
|
|
168
|
-
// ββ Validate βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
169
|
-
if (!fs.existsSync(infernoDir)) {
|
|
170
|
-
if (asJson) { emitJson({ ok: false, error: "inferno_not_found" }); process.exit(1); }
|
|
171
|
-
warn("inferno/ not found β run: infernoflow init");
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ββ Resolve ref ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
176
|
-
if (!ref) {
|
|
177
|
-
ref = lastTag(cwd);
|
|
178
|
-
if (!ref) {
|
|
179
|
-
const parentExists = capture("git rev-parse HEAD~1", cwd);
|
|
180
|
-
ref = parentExists ? "HEAD~1" : null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!ref) {
|
|
185
|
-
const currentVersion = readPackageVersion(cwd) || "0.0.0";
|
|
186
|
-
if (asJson) {
|
|
187
|
-
emitJson({ ok: true, bump: "minor", current: currentVersion, next: applyBump(currentVersion, "minor"), reason: ["No git history β defaulting to minor for first release"], ref: null });
|
|
188
|
-
} else {
|
|
189
|
-
info("No git history found β defaulting to minor for first release");
|
|
190
|
-
ok(`Recommended: ${bold(cyan("minor"))} β ${bold(applyBump(currentVersion, "minor"))}`);
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ββ Load capabilities ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
196
|
-
const current = loadCapsFromDisk(infernoDir);
|
|
197
|
-
const previous = loadCapsAtRef(ref, "inferno", cwd);
|
|
198
|
-
|
|
199
|
-
if (!current) {
|
|
200
|
-
if (asJson) { emitJson({ ok: false, error: "no_capabilities" }); process.exit(1); }
|
|
201
|
-
warn("No capabilities.json or contract.json found");
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// If no previous snapshot, treat all current caps as new β minor
|
|
206
|
-
const prevCaps = previous || [];
|
|
207
|
-
const diff = diffCaps(prevCaps, current);
|
|
208
|
-
const bump = classifyBump(diff);
|
|
209
|
-
|
|
210
|
-
const currentVersion = readPackageVersion(cwd) || "0.0.0";
|
|
211
|
-
const nextVersion = bump === "none" ? currentVersion : applyBump(currentVersion, bump);
|
|
212
|
-
const reason = buildReason(bump, diff, ref);
|
|
213
|
-
|
|
214
|
-
// ββ JSON output ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
215
|
-
if (asJson) {
|
|
216
|
-
emitJson({
|
|
217
|
-
ok: true,
|
|
218
|
-
bump,
|
|
219
|
-
current: currentVersion,
|
|
220
|
-
next: nextVersion,
|
|
221
|
-
ref,
|
|
222
|
-
reason,
|
|
223
|
-
diff: {
|
|
224
|
-
added: diff.added.length,
|
|
225
|
-
removed: diff.removed.length,
|
|
226
|
-
changed: diff.changed.length,
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ββ Human output ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
233
|
-
const bumpColor = bump === "major" ? red
|
|
234
|
-
: bump === "minor" ? green
|
|
235
|
-
: bump === "patch" ? yellow
|
|
236
|
-
: gray;
|
|
237
|
-
|
|
238
|
-
console.log();
|
|
239
|
-
console.log(` Current version ${bold(currentVersion)} ${gray("(" + ref + ")")}`);
|
|
240
|
-
console.log();
|
|
241
|
-
|
|
242
|
-
if (bump === "none") {
|
|
243
|
-
ok(`No capability changes β version stays at ${bold(currentVersion)}`);
|
|
244
|
-
} else {
|
|
245
|
-
console.log(` ${bold("Recommended bump:")} ${bumpColor(bold(bump.toUpperCase()))}`);
|
|
246
|
-
console.log();
|
|
247
|
-
for (const line of reason) {
|
|
248
|
-
const prefix = line.startsWith(" +") ? green(" +")
|
|
249
|
-
: line.startsWith(" -") ? red(" -")
|
|
250
|
-
: line.startsWith(" β¦") ? gray(" β¦")
|
|
251
|
-
: " ";
|
|
252
|
-
const text = line.replace(/^\s+[+\-β¦]\s?/, "");
|
|
253
|
-
if (line.startsWith(" ") && !line.startsWith(" β¦")) {
|
|
254
|
-
console.log(` ${line.trim()}`);
|
|
255
|
-
} else {
|
|
256
|
-
console.log(` ${gray(line)}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
console.log();
|
|
260
|
-
console.log(` ${bold(currentVersion)} β ${bumpColor(bold(nextVersion))}`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ββ Apply ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
264
|
-
if (apply && bump !== "none") {
|
|
265
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
266
|
-
if (!fs.existsSync(pkgPath)) {
|
|
267
|
-
warn("No package.json found β skipping --apply");
|
|
268
|
-
} else {
|
|
269
|
-
writePackageVersion(cwd, nextVersion);
|
|
270
|
-
console.log();
|
|
271
|
-
done(`package.json updated β ${bold(nextVersion)}`);
|
|
272
|
-
}
|
|
273
|
-
} else if (apply && bump === "none") {
|
|
274
|
-
console.log();
|
|
275
|
-
info("No changes to apply β version unchanged");
|
|
276
|
-
} else if (bump !== "none") {
|
|
277
|
-
console.log();
|
|
278
|
-
info(`Run ${cyan("infernoflow version --apply")} to write ${nextVersion} to package.json`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
console.log();
|
|
282
|
-
}
|
|
1
|
+
import*as u from"node:fs";import*as h from"node:path";import{execSync as V}from"node:child_process";import{header as W,ok as C,warn as v,info as k,bold as p,cyan as J,gray as $,green as P,red as F,yellow as A,done as E}from"../ui/output.mjs";function x(e,n){try{return V(e,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch{return null}}function M(e){return x("git describe --tags --abbrev=0",e)||null}function O(e,n,t){return x(`git show "${e}:${n}"`,t)}function j(e){if(!e)return null;try{return(JSON.parse(e).capabilities||[]).map(o=>typeof o=="string"?{id:o,title:o}:{id:o.id||o,title:o.title||o.id||String(o),status:o.status})}catch{return null}}function _(e){const n=h.join(e,"capabilities.json"),t=h.join(e,"contract.json");return u.existsSync(n)?j(u.readFileSync(n,"utf8")):u.existsSync(t)?j(u.readFileSync(t,"utf8")):null}function B(e,n,t){const o=O(e,`${n}/capabilities.json`,t);if(o)return j(o);const r=O(e,`${n}/contract.json`,t);return j(r)}function D(e,n){const t=new Map(e.map(s=>[s.id,s])),o=new Map(n.map(s=>[s.id,s])),r=n.filter(s=>!t.has(s.id)),c=e.filter(s=>!o.has(s.id)),l=[];for(const s of n){const f=t.get(s.id);if(!f)continue;const d=[];f.title!==s.title&&d.push({field:"title",from:f.title,to:s.title}),(f.status||"")!==(s.status||"")&&d.push({field:"status",from:f.status||"\u2014",to:s.status||"\u2014"}),d.length&&l.push({id:s.id,changes:d})}return{added:r,removed:c,changed:l}}function H(e){return e.removed.length>0?"major":e.added.length>0?"minor":e.changed.length>0?"patch":"none"}function S(e,n){const t=(e||"0.0.0").split(".").map(Number);return n==="major"?(t[0]++,t[1]=0,t[2]=0):n==="minor"?(t[1]++,t[2]=0):n==="patch"&&t[2]++,t.join(".")}function R(e){const n=h.join(e,"package.json");if(!u.existsSync(n))return null;try{return JSON.parse(u.readFileSync(n,"utf8")).version||null}catch{return null}}function I(e,n){const t=h.join(e,"package.json"),o=u.readFileSync(t,"utf8"),r=JSON.parse(o);r.version=n,u.writeFileSync(t,JSON.stringify(r,null,2)+`
|
|
2
|
+
`,"utf8")}function U(e,n,t){const o=[];if(e==="major"){o.push(`${n.removed.length} capability removed \u2014 breaking change`);for(const r of n.removed.slice(0,3))o.push(` - ${r.id}: ${r.title}`);n.removed.length>3&&o.push(` \u2026 and ${n.removed.length-3} more`)}else if(e==="minor"){o.push(`${n.added.length} new capability added`);for(const r of n.added.slice(0,3))o.push(` + ${r.id}: ${r.title}`);n.added.length>3&&o.push(` \u2026 and ${n.added.length-3} more`)}else e==="patch"?o.push(`${n.changed.length} capability metadata updated`):o.push(`No capability changes since ${t}`);return o}function b(e){console.log(JSON.stringify(e,null,2))}async function Q(e){const n=e.slice(1),t=n.includes("--json"),o=n.includes("--apply"),r=n.indexOf("--ref");let c=r!==-1?n[r+1]:null;const l=process.cwd(),s=h.join(l,"inferno");if(t||W("infernoflow version"),u.existsSync(s)||(t&&(b({ok:!1,error:"inferno_not_found"}),process.exit(1)),v("inferno/ not found \u2014 run: infernoflow init"),process.exit(1)),c||(c=M(l),c||(c=x("git rev-parse HEAD~1",l)?"HEAD~1":null)),!c){const i=R(l)||"0.0.0";t?b({ok:!0,bump:"minor",current:i,next:S(i,"minor"),reason:["No git history \u2014 defaulting to minor for first release"],ref:null}):(k("No git history found \u2014 defaulting to minor for first release"),C(`Recommended: ${p(J("minor"))} \u2192 ${p(S(i,"minor"))}`));return}const f=_(s),d=B(c,"inferno",l);f||(t&&(b({ok:!1,error:"no_capabilities"}),process.exit(1)),v("No capabilities.json or contract.json found"),process.exit(1));const m=D(d||[],f),a=H(m),g=R(l)||"0.0.0",y=a==="none"?g:S(g,a),w=U(a,m,c);if(t){b({ok:!0,bump:a,current:g,next:y,ref:c,reason:w,diff:{added:m.added.length,removed:m.removed.length,changed:m.changed.length}});return}const N=a==="major"?F:a==="minor"?P:a==="patch"?A:$;if(console.log(),console.log(` Current version ${p(g)} ${$("("+c+")")}`),console.log(),a==="none")C(`No capability changes \u2014 version stays at ${p(g)}`);else{console.log(` ${p("Recommended bump:")} ${N(p(a.toUpperCase()))}`),console.log();for(const i of w){const z=i.startsWith(" +")?P(" +"):i.startsWith(" -")?F(" -"):i.startsWith(" \u2026")?$(" \u2026"):" ",G=i.replace(/^\s+[+\-β¦]\s?/,"");i.startsWith(" ")&&!i.startsWith(" \u2026")?console.log(` ${i.trim()}`):console.log(` ${$(i)}`)}console.log(),console.log(` ${p(g)} \u2192 ${N(p(y))}`)}if(o&&a!=="none"){const i=h.join(l,"package.json");u.existsSync(i)?(I(l,y),console.log(),E(`package.json updated \u2192 ${p(y)}`)):v("No package.json found \u2014 skipping --apply")}else o&&a==="none"?(console.log(),k("No changes to apply \u2014 version unchanged")):a!=="none"&&(console.log(),k(`Run ${J("infernoflow version --apply")} to write ${y} to package.json`));console.log()}export{Q as versionCommand};
|
|
@@ -1,365 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Persistent background process for vibe coding sessions.
|
|
5
|
-
* Completely invisible to the developer β just leave it running in a terminal.
|
|
6
|
-
*
|
|
7
|
-
* What it does every cycle:
|
|
8
|
-
* 1. Watches source files for saves
|
|
9
|
-
* 2. On save: runs infernoflow suggest to sync the contract
|
|
10
|
-
* 3. Regenerates CONTEXT.md so the next AI prompt is always fresh
|
|
11
|
-
* 4. Watches inferno/ for contract changes β updates CONTEXT.md automatically
|
|
12
|
-
* 5. Prints a one-line status ticker β nothing noisy
|
|
13
|
-
*
|
|
14
|
-
* Also provides a session summary on exit (Ctrl+C):
|
|
15
|
-
* - Files changed
|
|
16
|
-
* - Capabilities added / updated
|
|
17
|
-
* - Health score delta
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* infernoflow vibe Start vibe mode (default dirs)
|
|
21
|
-
* infernoflow vibe --dir src,api Watch specific directories
|
|
22
|
-
* infernoflow vibe --no-suggest Watch + context only, skip suggest
|
|
23
|
-
* infernoflow vibe --no-context Skip CONTEXT.md regeneration
|
|
24
|
-
* infernoflow vibe --interval 5 Debounce seconds (default 4)
|
|
25
|
-
* infernoflow vibe --silent Suppress all output (pure background)
|
|
26
|
-
* infernoflow vibe --port 7337 Also start dashboard on this port
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import * as fs from "node:fs";
|
|
30
|
-
import * as path from "node:path";
|
|
31
|
-
import * as http from "node:http";
|
|
32
|
-
import * as os from "node:os";
|
|
33
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
34
|
-
import { bold, cyan, gray, green, yellow, red, info, warn } from "../ui/output.mjs";
|
|
35
|
-
|
|
36
|
-
// ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
37
|
-
|
|
38
|
-
const SOURCE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".py", ".rb", ".go", ".rs"]);
|
|
39
|
-
const IGNORE_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "__pycache__", "vendor", "coverage", "inferno"]);
|
|
40
|
-
|
|
41
|
-
function runCli(args, cwd, silent = false) {
|
|
42
|
-
const result = spawnSync("infernoflow", args, {
|
|
43
|
-
cwd,
|
|
44
|
-
encoding: "utf8",
|
|
45
|
-
env: { ...process.env, NO_COLOR: "1" },
|
|
46
|
-
timeout: 30_000,
|
|
47
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
48
|
-
});
|
|
49
|
-
if (!silent && result.error) return { ok: false, out: "", err: result.error.message };
|
|
50
|
-
return { ok: result.status === 0, out: result.stdout || "", err: result.stderr || "" };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function timestamp() {
|
|
54
|
-
return new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function readContractCapCount(infernoDir) {
|
|
58
|
-
for (const f of ["contract.json", "capabilities.json"]) {
|
|
59
|
-
const p = path.join(infernoDir, f);
|
|
60
|
-
if (!fs.existsSync(p)) continue;
|
|
61
|
-
try {
|
|
62
|
-
const data = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
63
|
-
return (data.capabilities || []).length;
|
|
64
|
-
} catch {}
|
|
65
|
-
}
|
|
66
|
-
return 0;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function readHealthScore(cwd) {
|
|
70
|
-
const result = spawnSync("infernoflow", ["health", "--json"], {
|
|
71
|
-
cwd, encoding: "utf8", timeout: 20_000, stdio: ["ignore", "pipe", "pipe"],
|
|
72
|
-
env: { ...process.env, NO_COLOR: "1" },
|
|
73
|
-
});
|
|
74
|
-
try { return JSON.parse(result.stdout || "{}").score ?? null; } catch { return null; }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// ββ Session state βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
78
|
-
|
|
79
|
-
class VibeSession {
|
|
80
|
-
constructor() {
|
|
81
|
-
this.startedAt = new Date();
|
|
82
|
-
this.filesChanged = new Set();
|
|
83
|
-
this.suggestRuns = 0;
|
|
84
|
-
this.contextUpdates = 0;
|
|
85
|
-
this.capsAtStart = 0;
|
|
86
|
-
this.capsNow = 0;
|
|
87
|
-
this.scoreAtStart = null;
|
|
88
|
-
this.scoreNow = null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
recordChange(filePath) { this.filesChanged.add(filePath); }
|
|
92
|
-
recordSuggest() { this.suggestRuns++; }
|
|
93
|
-
recordContext() { this.contextUpdates++; }
|
|
94
|
-
|
|
95
|
-
elapsed() {
|
|
96
|
-
const ms = Date.now() - this.startedAt.getTime();
|
|
97
|
-
const mins = Math.floor(ms / 60000);
|
|
98
|
-
const secs = Math.floor((ms % 60000) / 1000);
|
|
99
|
-
return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
printSummary() {
|
|
103
|
-
const capDelta = this.capsNow - this.capsAtStart;
|
|
104
|
-
const scoreDelta = this.scoreNow !== null && this.scoreAtStart !== null
|
|
105
|
-
? this.scoreNow - this.scoreAtStart : null;
|
|
106
|
-
|
|
107
|
-
console.log();
|
|
108
|
-
console.log(` ${bold("π₯ infernoflow vibe β session summary")}`);
|
|
109
|
-
console.log();
|
|
110
|
-
console.log(` ${bold("Duration:")} ${this.elapsed()}`);
|
|
111
|
-
console.log(` ${bold("Files watched:")} ${this.filesChanged.size}`);
|
|
112
|
-
console.log(` ${bold("Syncs run:")} ${this.suggestRuns}`);
|
|
113
|
-
console.log(` ${bold("Context refreshes:")} ${this.contextUpdates}`);
|
|
114
|
-
|
|
115
|
-
if (capDelta !== 0) {
|
|
116
|
-
const col = capDelta > 0 ? green : yellow;
|
|
117
|
-
console.log(` ${bold("Capabilities:")} ${col((capDelta > 0 ? "+" : "") + capDelta)} (${this.capsAtStart} β ${this.capsNow})`);
|
|
118
|
-
} else {
|
|
119
|
-
console.log(` ${bold("Capabilities:")} ${this.capsNow} (no change)`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (scoreDelta !== null) {
|
|
123
|
-
const col = scoreDelta > 0 ? green : scoreDelta < 0 ? yellow : gray;
|
|
124
|
-
console.log(` ${bold("Health score:")} ${col((scoreDelta > 0 ? "+" : "") + scoreDelta)} (${this.scoreAtStart} β ${this.scoreNow})`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
console.log();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ββ Status ticker βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
132
|
-
|
|
133
|
-
const SPINNERS = ["β ", "β ", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ", "β "];
|
|
134
|
-
let spinIdx = 0;
|
|
135
|
-
|
|
136
|
-
function tick(msg, col = gray) {
|
|
137
|
-
const spinner = SPINNERS[spinIdx++ % SPINNERS.length];
|
|
138
|
-
process.stdout.write(`\r ${col(spinner)} ${msg} `);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function tickDone(msg) {
|
|
142
|
-
process.stdout.write(`\r ${green("β")} ${msg} \n`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function tickWarn(msg) {
|
|
146
|
-
process.stdout.write(`\r ${yellow("β ")} ${msg} \n`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ββ Core cycle ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
150
|
-
|
|
151
|
-
async function runCycle(changedFile, cwd, infernoDir, opts, session) {
|
|
152
|
-
const rel = path.relative(cwd, changedFile);
|
|
153
|
-
session.recordChange(changedFile);
|
|
154
|
-
|
|
155
|
-
if (!opts.silent) tick(`${cyan(rel)} changed β syncingβ¦`, cyan);
|
|
156
|
-
|
|
157
|
-
// 1. Suggest (sync contract)
|
|
158
|
-
if (!opts.noSuggest) {
|
|
159
|
-
const desc = `updated ${path.basename(changedFile)}`;
|
|
160
|
-
const r = runCli(["suggest", desc, "--json"], cwd, true);
|
|
161
|
-
session.recordSuggest();
|
|
162
|
-
|
|
163
|
-
if (!opts.silent) {
|
|
164
|
-
if (r.ok) {
|
|
165
|
-
tickDone(`Synced: ${cyan(rel)}`);
|
|
166
|
-
} else {
|
|
167
|
-
tickWarn(`Suggest skipped (${gray("no AI provider")})`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 2. Regenerate CONTEXT.md
|
|
173
|
-
if (!opts.noContext) {
|
|
174
|
-
const r = runCli(["context"], cwd, true);
|
|
175
|
-
session.recordContext();
|
|
176
|
-
if (!opts.silent && r.ok) {
|
|
177
|
-
tick(`Context refreshed`, gray);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// 3. Update cap count
|
|
182
|
-
session.capsNow = readContractCapCount(infernoDir);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ββ File watcher ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
186
|
-
|
|
187
|
-
function watchDirs(dirs, cwd, infernoDir, opts, session) {
|
|
188
|
-
const watchers = [];
|
|
189
|
-
const debounceMap = new Map();
|
|
190
|
-
|
|
191
|
-
const onFileChange = (eventType, filePath) => {
|
|
192
|
-
if (!SOURCE_EXTENSIONS.has(path.extname(filePath))) return;
|
|
193
|
-
if ([...IGNORE_DIRS].some(d => filePath.includes(`${path.sep}${d}${path.sep}`))) return;
|
|
194
|
-
|
|
195
|
-
// Debounce per file
|
|
196
|
-
if (debounceMap.has(filePath)) clearTimeout(debounceMap.get(filePath));
|
|
197
|
-
debounceMap.set(filePath, setTimeout(async () => {
|
|
198
|
-
debounceMap.delete(filePath);
|
|
199
|
-
await runCycle(filePath, cwd, infernoDir, opts, session);
|
|
200
|
-
}, opts.interval * 1000));
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
for (const dir of dirs) {
|
|
204
|
-
if (!fs.existsSync(dir)) continue;
|
|
205
|
-
try {
|
|
206
|
-
const w = fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
207
|
-
if (!filename) return;
|
|
208
|
-
onFileChange(eventType, path.join(dir, filename));
|
|
209
|
-
});
|
|
210
|
-
watchers.push(w);
|
|
211
|
-
} catch {}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Also watch inferno/ for contract changes β regenerate CONTEXT.md
|
|
215
|
-
try {
|
|
216
|
-
const iw = fs.watch(infernoDir, { recursive: true }, (eventType, filename) => {
|
|
217
|
-
if (!filename) return;
|
|
218
|
-
if (!filename.endsWith(".json") && !filename.endsWith(".md")) return;
|
|
219
|
-
if (filename.includes("CONTEXT")) return; // avoid loop
|
|
220
|
-
|
|
221
|
-
if (debounceMap.has("__inferno__")) clearTimeout(debounceMap.get("__inferno__"));
|
|
222
|
-
debounceMap.set("__inferno__", setTimeout(async () => {
|
|
223
|
-
debounceMap.delete("__inferno__");
|
|
224
|
-
if (!opts.noContext) {
|
|
225
|
-
runCli(["context"], cwd, true);
|
|
226
|
-
session.recordContext();
|
|
227
|
-
if (!opts.silent) tick(`Contract updated β context refreshed`, gray);
|
|
228
|
-
}
|
|
229
|
-
session.capsNow = readContractCapCount(infernoDir);
|
|
230
|
-
}, 1000));
|
|
231
|
-
});
|
|
232
|
-
watchers.push(iw);
|
|
233
|
-
} catch {}
|
|
234
|
-
|
|
235
|
-
return watchers;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ββ Dashboard mini-server βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
239
|
-
|
|
240
|
-
function startMiniDashboard(port, cwd, infernoDir) {
|
|
241
|
-
const server = http.createServer((req, res) => {
|
|
242
|
-
if (req.url === "/status") {
|
|
243
|
-
const caps = readContractCapCount(infernoDir);
|
|
244
|
-
const health = readHealthScore(cwd);
|
|
245
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
246
|
-
res.end(JSON.stringify({ ok: true, capabilities: caps, health, ts: new Date().toISOString() }));
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
250
|
-
res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8">
|
|
1
|
+
import*as m from"node:fs";import*as p from"node:path";import*as R from"node:http";import"node:os";import{spawnSync as j}from"node:child_process";import{bold as d,cyan as w,gray as h,green as v,yellow as C,info as k,warn as A}from"../ui/output.mjs";const E=new Set([".js",".mjs",".cjs",".ts",".tsx",".jsx",".py",".rb",".go",".rs"]),M=new Set(["node_modules",".git","dist","build",".next","__pycache__","vendor","coverage","inferno"]);function N(s,t,o=!1){const e=j("infernoflow",s,{cwd:t,encoding:"utf8",env:{...process.env,NO_COLOR:"1"},timeout:3e4,stdio:["ignore","pipe","pipe"]});return!o&&e.error?{ok:!1,out:"",err:e.error.message}:{ok:e.status===0,out:e.stdout||"",err:e.stderr||""}}function B(){return new Date().toLocaleTimeString("en-US",{hour12:!1})}function y(s){for(const t of["contract.json","capabilities.json"]){const o=p.join(s,t);if(m.existsSync(o))try{return(JSON.parse(m.readFileSync(o,"utf8")).capabilities||[]).length}catch{}}return 0}function _(s){const t=j("infernoflow",["health","--json"],{cwd:s,encoding:"utf8",timeout:2e4,stdio:["ignore","pipe","pipe"],env:{...process.env,NO_COLOR:"1"}});try{return JSON.parse(t.stdout||"{}").score??null}catch{return null}}class H{constructor(){this.startedAt=new Date,this.filesChanged=new Set,this.suggestRuns=0,this.contextUpdates=0,this.capsAtStart=0,this.capsNow=0,this.scoreAtStart=null,this.scoreNow=null}recordChange(t){this.filesChanged.add(t)}recordSuggest(){this.suggestRuns++}recordContext(){this.contextUpdates++}elapsed(){const t=Date.now()-this.startedAt.getTime(),o=Math.floor(t/6e4),e=Math.floor(t%6e4/1e3);return o>0?`${o}m ${e}s`:`${e}s`}printSummary(){const t=this.capsNow-this.capsAtStart,o=this.scoreNow!==null&&this.scoreAtStart!==null?this.scoreNow-this.scoreAtStart:null;if(console.log(),console.log(` ${d("\u{1F525} infernoflow vibe \u2014 session summary")}`),console.log(),console.log(` ${d("Duration:")} ${this.elapsed()}`),console.log(` ${d("Files watched:")} ${this.filesChanged.size}`),console.log(` ${d("Syncs run:")} ${this.suggestRuns}`),console.log(` ${d("Context refreshes:")} ${this.contextUpdates}`),t!==0){const e=t>0?v:C;console.log(` ${d("Capabilities:")} ${e((t>0?"+":"")+t)} (${this.capsAtStart} \u2192 ${this.capsNow})`)}else console.log(` ${d("Capabilities:")} ${this.capsNow} (no change)`);if(o!==null){const e=o>0?v:o<0?C:h;console.log(` ${d("Health score:")} ${e((o>0?"+":"")+o)} (${this.scoreAtStart} \u2192 ${this.scoreNow})`)}console.log()}}const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];let U=0;function x(s,t=h){const o=I[U++%I.length];process.stdout.write(`\r ${t(o)} ${s} `)}function W(s){process.stdout.write(`\r ${v("\u2714")} ${s}
|
|
2
|
+
`)}function L(s){process.stdout.write(`\r ${C("\u26A0")} ${s}
|
|
3
|
+
`)}async function z(s,t,o,e,i){const n=p.relative(t,s);if(i.recordChange(s),e.silent||x(`${w(n)} changed \u2014 syncing\u2026`,w),!e.noSuggest){const r=`updated ${p.basename(s)}`,g=N(["suggest",r,"--json"],t,!0);i.recordSuggest(),e.silent||(g.ok?W(`Synced: ${w(n)}`):L(`Suggest skipped (${h("no AI provider")})`))}if(!e.noContext){const r=N(["context"],t,!0);i.recordContext(),!e.silent&&r.ok&&x("Context refreshed",h)}i.capsNow=y(o)}function G(s,t,o,e,i){const n=[],r=new Map,g=(f,c)=>{E.has(p.extname(c))&&([...M].some(a=>c.includes(`${p.sep}${a}${p.sep}`))||(r.has(c)&&clearTimeout(r.get(c)),r.set(c,setTimeout(async()=>{r.delete(c),await z(c,t,o,e,i)},e.interval*1e3))))};for(const f of s)if(m.existsSync(f))try{const c=m.watch(f,{recursive:!0},(a,S)=>{S&&g(a,p.join(f,S))});n.push(c)}catch{}try{const f=m.watch(o,{recursive:!0},(c,a)=>{a&&(!a.endsWith(".json")&&!a.endsWith(".md")||a.includes("CONTEXT")||(r.has("__inferno__")&&clearTimeout(r.get("__inferno__")),r.set("__inferno__",setTimeout(async()=>{r.delete("__inferno__"),e.noContext||(N(["context"],t,!0),i.recordContext(),e.silent||x("Contract updated \u2014 context refreshed",h)),i.capsNow=y(o)},1e3))))});n.push(f)}catch{}return n}function J(s,t,o){const e=R.createServer((i,n)=>{if(i.url==="/status"){const r=y(o),g=_(t);n.writeHead(200,{"Content-Type":"application/json"}),n.end(JSON.stringify({ok:!0,capabilities:r,health:g,ts:new Date().toISOString()}));return}n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(`<!DOCTYPE html><html><head><meta charset="UTF-8">
|
|
251
4
|
<meta http-equiv="refresh" content="10">
|
|
252
|
-
<title
|
|
5
|
+
<title>\u{1F525} vibe</title>
|
|
253
6
|
<style>body{font-family:system-ui;background:#0f1117;color:#e2e8f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;flex-direction:column;gap:16px}
|
|
254
7
|
h1{color:#f97316;margin:0}.sub{color:#64748b;font-size:14px}</style></head>
|
|
255
|
-
<body><h1
|
|
8
|
+
<body><h1>\u{1F525} infernoflow vibe</h1><div class="sub">Auto-refreshes every 10s</div>
|
|
256
9
|
<script>
|
|
257
10
|
fetch('/status').then(r=>r.json()).then(d=>{
|
|
258
|
-
document.body.innerHTML='<h1
|
|
11
|
+
document.body.innerHTML='<h1>\u{1F525} vibe</h1><p style="color:#22c55e">'+d.capabilities+' capabilities</p>'
|
|
259
12
|
+(d.health!==null?'<p>Health: '+d.health+'/100</p>':'')
|
|
260
13
|
+'<p style="color:#64748b;font-size:12px">'+new Date(d.ts).toLocaleTimeString()+'</p>';
|
|
261
14
|
});
|
|
262
|
-
</script></body></html>`);
|
|
263
|
-
});
|
|
264
|
-
server.listen(port, "127.0.0.1", () => {});
|
|
265
|
-
return server;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ββ Entry βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
269
|
-
|
|
270
|
-
export async function vibeCommand(rawArgs) {
|
|
271
|
-
const args = rawArgs.slice(1);
|
|
272
|
-
const silent = args.includes("--silent");
|
|
273
|
-
const noSuggest = args.includes("--no-suggest");
|
|
274
|
-
const noContext = args.includes("--no-context");
|
|
275
|
-
const cwd = process.cwd();
|
|
276
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
277
|
-
|
|
278
|
-
if (!fs.existsSync(infernoDir)) {
|
|
279
|
-
warn("inferno/ not found. Run: infernoflow init");
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Parse flags
|
|
284
|
-
const dirIdx = args.indexOf("--dir");
|
|
285
|
-
const intervalIdx = args.indexOf("--interval");
|
|
286
|
-
const portIdx = args.indexOf("--port");
|
|
287
|
-
|
|
288
|
-
const interval = intervalIdx !== -1 ? parseInt(args[intervalIdx + 1], 10) : 4;
|
|
289
|
-
const port = portIdx !== -1 ? parseInt(args[portIdx + 1], 10) : null;
|
|
290
|
-
|
|
291
|
-
const scanDirs = dirIdx !== -1
|
|
292
|
-
? args[dirIdx + 1].split(",").map(d => path.resolve(cwd, d.trim()))
|
|
293
|
-
: ["src", "lib", "app", "api", "pages", "routes", "controllers", "handlers", "services"]
|
|
294
|
-
.map(d => path.join(cwd, d))
|
|
295
|
-
.filter(d => fs.existsSync(d));
|
|
296
|
-
|
|
297
|
-
if (!scanDirs.length) {
|
|
298
|
-
// Watch the whole project root as fallback
|
|
299
|
-
scanDirs.push(cwd);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const opts = { silent, noSuggest, noContext, interval };
|
|
303
|
-
const session = new VibeSession();
|
|
304
|
-
|
|
305
|
-
// Snapshot initial state
|
|
306
|
-
session.capsAtStart = readContractCapCount(infernoDir);
|
|
307
|
-
session.capsNow = session.capsAtStart;
|
|
308
|
-
session.scoreAtStart = readHealthScore(cwd);
|
|
309
|
-
|
|
310
|
-
if (!silent) {
|
|
311
|
-
console.log();
|
|
312
|
-
console.log(` ${bold("π₯ infernoflow vibe")} ${gray("β vibe coding mode active")}`);
|
|
313
|
-
console.log();
|
|
314
|
-
console.log(` ${bold("Watching:")} ${scanDirs.map(d => path.relative(cwd, d) || ".").join(", ")}`);
|
|
315
|
-
console.log(` ${bold("Debounce:")} ${interval}s`);
|
|
316
|
-
if (noSuggest) console.log(` ${gray("--no-suggest: contract sync disabled")}`);
|
|
317
|
-
if (noContext) console.log(` ${gray("--no-context: CONTEXT.md refresh disabled")}`);
|
|
318
|
-
console.log();
|
|
319
|
-
console.log(` ${cyan(String(session.capsAtStart))} capabilities in contract`);
|
|
320
|
-
if (session.scoreAtStart !== null) console.log(` Health score: ${cyan(String(session.scoreAtStart))}/100`);
|
|
321
|
-
console.log();
|
|
322
|
-
console.log(` ${gray("Watching for file saves⦠Press Ctrl+C to stop and see session summary.")}`);
|
|
323
|
-
console.log();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Optional mini dashboard
|
|
327
|
-
if (port) {
|
|
328
|
-
startMiniDashboard(port, cwd, infernoDir);
|
|
329
|
-
if (!silent) info(`Mini dashboard β http://localhost:${port}`);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Start watching
|
|
333
|
-
const watchers = watchDirs(scanDirs, cwd, infernoDir, opts, session);
|
|
334
|
-
|
|
335
|
-
if (!watchers.length) {
|
|
336
|
-
warn("No directories to watch. Use --dir src,lib,api");
|
|
337
|
-
process.exit(1);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Spinner to show we're alive
|
|
341
|
-
let spinTimer = null;
|
|
342
|
-
if (!silent) {
|
|
343
|
-
let idleMsg = "Watchingβ¦";
|
|
344
|
-
spinTimer = setInterval(() => {
|
|
345
|
-
tick(idleMsg, gray);
|
|
346
|
-
}, 150);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Graceful shutdown
|
|
350
|
-
const shutdown = async () => {
|
|
351
|
-
if (spinTimer) clearInterval(spinTimer);
|
|
352
|
-
process.stdout.write("\r \r");
|
|
353
|
-
watchers.forEach(w => { try { w.close(); } catch {} });
|
|
354
|
-
session.scoreNow = readHealthScore(cwd);
|
|
355
|
-
session.capsNow = readContractCapCount(infernoDir);
|
|
356
|
-
if (!silent) session.printSummary();
|
|
357
|
-
process.exit(0);
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
process.on("SIGINT", shutdown);
|
|
361
|
-
process.on("SIGTERM", shutdown);
|
|
362
|
-
|
|
363
|
-
// Keep alive
|
|
364
|
-
await new Promise(() => {});
|
|
365
|
-
}
|
|
15
|
+
</script></body></html>`)});return e.listen(s,"127.0.0.1",()=>{}),e}async function K(s){const t=s.slice(1),o=t.includes("--silent"),e=t.includes("--no-suggest"),i=t.includes("--no-context"),n=process.cwd(),r=p.join(n,"inferno");m.existsSync(r)||(A("inferno/ not found. Run: infernoflow init"),process.exit(1));const g=t.indexOf("--dir"),f=t.indexOf("--interval"),c=t.indexOf("--port"),a=f!==-1?parseInt(t[f+1],10):4,S=c!==-1?parseInt(t[c+1],10):null,$=g!==-1?t[g+1].split(",").map(u=>p.resolve(n,u.trim())):["src","lib","app","api","pages","routes","controllers","handlers","services"].map(u=>p.join(n,u)).filter(u=>m.existsSync(u));$.length||$.push(n);const D={silent:o,noSuggest:e,noContext:i,interval:a},l=new H;l.capsAtStart=y(r),l.capsNow=l.capsAtStart,l.scoreAtStart=_(n),o||(console.log(),console.log(` ${d("\u{1F525} infernoflow vibe")} ${h("\u2014 vibe coding mode active")}`),console.log(),console.log(` ${d("Watching:")} ${$.map(u=>p.relative(n,u)||".").join(", ")}`),console.log(` ${d("Debounce:")} ${a}s`),e&&console.log(` ${h("--no-suggest: contract sync disabled")}`),i&&console.log(` ${h("--no-context: CONTEXT.md refresh disabled")}`),console.log(),console.log(` ${w(String(l.capsAtStart))} capabilities in contract`),l.scoreAtStart!==null&&console.log(` Health score: ${w(String(l.scoreAtStart))}/100`),console.log(),console.log(` ${h("Watching for file saves\u2026 Press Ctrl+C to stop and see session summary.")}`),console.log()),S&&(J(S,n,r),o||k(`Mini dashboard \u2192 http://localhost:${S}`));const T=G($,n,r,D,l);T.length||(A("No directories to watch. Use --dir src,lib,api"),process.exit(1));let b=null;if(!o){let u="Watching\u2026";b=setInterval(()=>{x(u,h)},150)}const O=async()=>{b&&clearInterval(b),process.stdout.write("\r \r"),T.forEach(u=>{try{u.close()}catch{}}),l.scoreNow=_(n),l.capsNow=y(r),o||l.printSummary(),process.exit(0)};process.on("SIGINT",O),process.on("SIGTERM",O),await new Promise(()=>{})}export{K as vibeCommand};
|