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,204 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
* infernoflow watch
|
|
3
|
-
|
|
4
|
-
* File-system watcher that runs `infernoflow suggest` automatically whenever
|
|
5
|
-
* source files are saved. Zero manual steps — just code, save, and the
|
|
6
|
-
* contract stays in sync.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* infernoflow watch Watch src/ (or auto-detected root)
|
|
10
|
-
* infernoflow watch src lib Watch specific directories
|
|
11
|
-
* infernoflow watch --interval 5 Debounce interval in seconds (default: 3)
|
|
12
|
-
* infernoflow watch --dry-run Print what would run, don't actually run
|
|
13
|
-
* infernoflow watch --silent No output (git-hook friendly)
|
|
14
|
-
*
|
|
15
|
-
* What it does on each save:
|
|
16
|
-
* 1. Debounce (3 s default) — batches rapid multi-file saves
|
|
17
|
-
* 2. Diff changed files against capability-map.json
|
|
18
|
-
* 3. If relevant capabilities may be affected → run suggest
|
|
19
|
-
* 4. Run check silently — log issues to inferno/WATCH.log
|
|
20
|
-
*/
|
|
1
|
+
import*as f from"node:fs";import*as e from"node:path";import{fileURLToPath as $}from"node:url";import{spawnSync as b}from"node:child_process";import{warn as m,bold as x,cyan as D,gray as d,green as I,yellow as P}from"../ui/output.mjs";const R=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".go",".java",".cs",".rb",".swift"]),M=new Set(["node_modules",".git","dist","build","out",".next",".angular","vendor","coverage","__pycache__"]);function W(t){const i=["src","lib","app","pages","components","server","api"].filter(o=>f.existsSync(e.join(t,o)));return i.length?i.map(o=>e.join(t,o)):[t]}function k(t){return R.has(e.extname(t).toLowerCase())}function A(t,r){const i=e.join(r,"capability-map.json");if(!f.existsSync(i))return{relevant:!0,reason:"no cap-map \u2014 suggesting broadly"};let o;try{o=JSON.parse(f.readFileSync(i,"utf8"))}catch{return{relevant:!0,reason:"cap-map unreadable"}}const s=[];for(const g of t){const c=e.relative(process.cwd(),g).replace(/\\/g,"/");for(const[a,u]of Object.entries(o))c.startsWith(a.replace(/\\/g,"/"))&&s.push(...u)}return s.length>0?{relevant:!0,reason:`touches: ${[...new Set(s)].slice(0,3).join(", ")}`}:{relevant:!1,reason:"no mapped capabilities affected"}}function N(t,r,i,o,s){const c=`code changes in ${t.map(a=>e.basename(a,e.extname(a))).slice(0,3).join(", ")}`;if(s||process.stdout.write(` ${P("\u27F3")} suggesting from ${x(String(t.length))} changed file${t.length!==1?"s":""}\u2026 `),o){s||console.log(d("(dry run)"));return}try{b(process.execPath,[e.join(e.dirname(e.dirname(e.dirname($(import.meta.url)))),"bin","infernoflow.mjs"),"suggest",c,"--json"],{cwd:r,encoding:"utf8",timeout:3e4,stdio:"ignore"}),s||console.log(I("done"))}catch{s||console.log(d("skipped (no changes)"))}try{const u=b(process.execPath,[e.join(e.dirname(e.dirname(e.dirname($(import.meta.url)))),"bin","infernoflow.mjs"),"check","--json"],{cwd:r,encoding:"utf8",timeout:15e3}).stdout?.trim();if(u){const w=JSON.parse(u);if(w.status==="error"||w.status==="warning")f.writeFileSync(e.join(i,"WATCH.log"),u+`
|
|
2
|
+
`),s||m("Contract issues detected \u2014 see inferno/WATCH.log");else{const p=e.join(i,"WATCH.log");f.existsSync(p)&&f.unlinkSync(p)}}}catch{}}async function U(t){const r=t.slice(1),i=r.includes("--dry-run"),o=r.includes("--silent"),s=r.indexOf("--interval"),g=((s!==-1?parseFloat(r[s+1]):3)||3)*1e3,c=process.cwd(),a=e.join(c,"inferno");f.existsSync(a)||(m("inferno/ not found. Run: infernoflow init"),process.exit(1));const u=r.filter(n=>!n.startsWith("-")&&n!==String(r[s+1])),p=(u.length?u.map(n=>e.resolve(c,n)):W(c)).filter(n=>f.existsSync(n));p.length||(m("No valid directories to watch."),process.exit(1)),o||(console.log(),console.log(` ${x("\u{1F525} infernoflow watch")} ${d("(Ctrl+C to stop)")}`),console.log(),p.forEach(n=>console.log(` ${D("watching")} ${d(e.relative(c,n)||".")}`)),console.log(` ${d(`debounce: ${g/1e3}s`)}`),console.log());let y=null;const S=new Set,C=n=>{k(n)&&(S.add(n),y&&clearTimeout(y),y=setTimeout(()=>{const l=Array.from(S);if(S.clear(),!o){const T=l.map(_=>e.relative(c,_)).slice(0,3).join(", ");process.stdout.write(`
|
|
3
|
+
${d(new Date().toLocaleTimeString())} ${x(T)}${l.length>3?` +${l.length-3} more`:""} `)}const{relevant:v,reason:h}=A(l,a);if(!v){o||console.log(d(`skip (${h})`));return}N(l,c,a,i,o)},g))},j=[];for(const n of p)try{const l=f.watch(n,{recursive:!0},(v,h)=>{h&&C(e.join(n,h))});j.push(l)}catch(l){o||m(`Cannot watch ${n}: ${l.message}`)}j.length||(m("No directories could be watched."),process.exit(1)),process.on("SIGINT",()=>{j.forEach(n=>n.close()),o||(console.log(`
|
|
21
4
|
|
|
22
|
-
|
|
23
|
-
import * as path from "node:path";
|
|
24
|
-
import { fileURLToPath } from "node:url";
|
|
25
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
26
|
-
import { ok, warn, info, bold, cyan, gray, green, yellow } from "../ui/output.mjs";
|
|
27
|
-
|
|
28
|
-
// ── Source file detection ─────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".java", ".cs", ".rb", ".swift"]);
|
|
31
|
-
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".angular", "vendor", "coverage", "__pycache__"]);
|
|
32
|
-
|
|
33
|
-
function defaultWatchDirs(cwd) {
|
|
34
|
-
const candidates = ["src", "lib", "app", "pages", "components", "server", "api"];
|
|
35
|
-
const found = candidates.filter(d => fs.existsSync(path.join(cwd, d)));
|
|
36
|
-
return found.length ? found.map(d => path.join(cwd, d)) : [cwd];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isSourceFile(filePath) {
|
|
40
|
-
return SOURCE_EXTS.has(path.extname(filePath).toLowerCase());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── Capability relevance check ────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
function capabilityRelevance(changedFiles, infernoDir) {
|
|
46
|
-
const mapPath = path.join(infernoDir, "capability-map.json");
|
|
47
|
-
if (!fs.existsSync(mapPath)) return { relevant: true, reason: "no cap-map — suggesting broadly" };
|
|
48
|
-
|
|
49
|
-
let capMap;
|
|
50
|
-
try { capMap = JSON.parse(fs.readFileSync(mapPath, "utf8")); } catch { return { relevant: true, reason: "cap-map unreadable" }; }
|
|
51
|
-
|
|
52
|
-
const hits = [];
|
|
53
|
-
for (const file of changedFiles) {
|
|
54
|
-
const rel = path.relative(process.cwd(), file).replace(/\\/g, "/");
|
|
55
|
-
for (const [prefix, capIds] of Object.entries(capMap)) {
|
|
56
|
-
if (rel.startsWith(prefix.replace(/\\/g, "/"))) {
|
|
57
|
-
hits.push(...capIds);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (hits.length > 0) return { relevant: true, reason: `touches: ${[...new Set(hits)].slice(0,3).join(", ")}` };
|
|
63
|
-
return { relevant: false, reason: "no mapped capabilities affected" };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ── Run suggest + check ───────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
function runSuggest(changedFiles, cwd, infernoDir, dryRun, silent) {
|
|
69
|
-
const names = changedFiles.map(f => path.basename(f, path.extname(f))).slice(0, 3).join(", ");
|
|
70
|
-
const desc = `code changes in ${names}`;
|
|
71
|
-
|
|
72
|
-
if (!silent) {
|
|
73
|
-
process.stdout.write(` ${yellow("⟳")} suggesting from ${bold(String(changedFiles.length))} changed file${changedFiles.length !== 1 ? "s" : ""}… `);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (dryRun) {
|
|
77
|
-
if (!silent) console.log(gray("(dry run)"));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
spawnSync(process.execPath, [
|
|
83
|
-
path.join(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))), "bin", "infernoflow.mjs"),
|
|
84
|
-
"suggest", desc, "--json"
|
|
85
|
-
], { cwd, encoding: "utf8", timeout: 30_000, stdio: "ignore" });
|
|
86
|
-
|
|
87
|
-
if (!silent) console.log(green("done"));
|
|
88
|
-
} catch {
|
|
89
|
-
if (!silent) console.log(gray("skipped (no changes)"));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Silent check — write issues to WATCH.log
|
|
93
|
-
try {
|
|
94
|
-
const result = spawnSync(process.execPath, [
|
|
95
|
-
path.join(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))), "bin", "infernoflow.mjs"),
|
|
96
|
-
"check", "--json"
|
|
97
|
-
], { cwd, encoding: "utf8", timeout: 15_000 });
|
|
98
|
-
|
|
99
|
-
const out = result.stdout?.trim();
|
|
100
|
-
if (out) {
|
|
101
|
-
const data = JSON.parse(out);
|
|
102
|
-
if (data.status === "error" || data.status === "warning") {
|
|
103
|
-
fs.writeFileSync(path.join(infernoDir, "WATCH.log"), out + "\n");
|
|
104
|
-
if (!silent) warn(`Contract issues detected — see inferno/WATCH.log`);
|
|
105
|
-
} else {
|
|
106
|
-
const logPath = path.join(infernoDir, "WATCH.log");
|
|
107
|
-
if (fs.existsSync(logPath)) fs.unlinkSync(logPath);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
} catch {}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Watcher ───────────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
export async function watchCommand(rawArgs) {
|
|
116
|
-
const args = rawArgs.slice(1);
|
|
117
|
-
const dryRun = args.includes("--dry-run");
|
|
118
|
-
const silent = args.includes("--silent");
|
|
119
|
-
const intervalIdx = args.indexOf("--interval");
|
|
120
|
-
const debounceMs = ((intervalIdx !== -1 ? parseFloat(args[intervalIdx + 1]) : 3) || 3) * 1000;
|
|
121
|
-
const cwd = process.cwd();
|
|
122
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
123
|
-
|
|
124
|
-
if (!fs.existsSync(infernoDir)) {
|
|
125
|
-
warn("inferno/ not found. Run: infernoflow init");
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Collect directories to watch
|
|
130
|
-
const dirArgs = args.filter(a => !a.startsWith("-") && a !== String(args[intervalIdx + 1]));
|
|
131
|
-
const watchDirs = dirArgs.length
|
|
132
|
-
? dirArgs.map(d => path.resolve(cwd, d))
|
|
133
|
-
: defaultWatchDirs(cwd);
|
|
134
|
-
|
|
135
|
-
const validDirs = watchDirs.filter(d => fs.existsSync(d));
|
|
136
|
-
if (!validDirs.length) {
|
|
137
|
-
warn("No valid directories to watch.");
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!silent) {
|
|
142
|
-
console.log();
|
|
143
|
-
console.log(` ${bold("🔥 infernoflow watch")} ${gray("(Ctrl+C to stop)")}`);
|
|
144
|
-
console.log();
|
|
145
|
-
validDirs.forEach(d => console.log(` ${cyan("watching")} ${gray(path.relative(cwd, d) || ".")}`));
|
|
146
|
-
console.log(` ${gray(`debounce: ${debounceMs / 1000}s`)}`);
|
|
147
|
-
console.log();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let debounceTimer = null;
|
|
151
|
-
const pendingFiles = new Set();
|
|
152
|
-
|
|
153
|
-
const handleChange = (filePath) => {
|
|
154
|
-
if (!isSourceFile(filePath)) return;
|
|
155
|
-
pendingFiles.add(filePath);
|
|
156
|
-
|
|
157
|
-
if (debounceTimer) clearTimeout(debounceTimer);
|
|
158
|
-
debounceTimer = setTimeout(() => {
|
|
159
|
-
const changed = Array.from(pendingFiles);
|
|
160
|
-
pendingFiles.clear();
|
|
161
|
-
|
|
162
|
-
if (!silent) {
|
|
163
|
-
const names = changed.map(f => path.relative(cwd, f)).slice(0, 3).join(", ");
|
|
164
|
-
process.stdout.write(`\n ${gray(new Date().toLocaleTimeString())} ${bold(names)}${changed.length > 3 ? ` +${changed.length - 3} more` : ""} `);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const { relevant, reason } = capabilityRelevance(changed, infernoDir);
|
|
168
|
-
if (!relevant) {
|
|
169
|
-
if (!silent) console.log(gray(`skip (${reason})`));
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
runSuggest(changed, cwd, infernoDir, dryRun, silent);
|
|
174
|
-
}, debounceMs);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Start watchers on each directory
|
|
178
|
-
const watchers = [];
|
|
179
|
-
for (const dir of validDirs) {
|
|
180
|
-
try {
|
|
181
|
-
const watcher = fs.watch(dir, { recursive: true }, (event, filename) => {
|
|
182
|
-
if (filename) handleChange(path.join(dir, filename));
|
|
183
|
-
});
|
|
184
|
-
watchers.push(watcher);
|
|
185
|
-
} catch (err) {
|
|
186
|
-
if (!silent) warn(`Cannot watch ${dir}: ${err.message}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!watchers.length) {
|
|
191
|
-
warn("No directories could be watched.");
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Keep alive
|
|
196
|
-
process.on("SIGINT", () => {
|
|
197
|
-
watchers.forEach(w => w.close());
|
|
198
|
-
if (!silent) { console.log("\n\n Stopped."); console.log(); }
|
|
199
|
-
process.exit(0);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Block forever
|
|
203
|
-
await new Promise(() => {});
|
|
204
|
-
}
|
|
5
|
+
Stopped.`),console.log()),process.exit(0)}),await new Promise(()=>{})}export{U as watchCommand};
|
|
@@ -1,358 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* • Which capability does this serve?
|
|
6
|
-
* • What is its stability level?
|
|
7
|
-
* • What scenarios cover it?
|
|
8
|
-
* • Who introduced it and when?
|
|
9
|
-
* • What does it call / what calls it?
|
|
10
|
-
*
|
|
11
|
-
* Pure correlation — no AI needed. Uses scan.json + graph.json + scenarios/ + git log.
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* infernoflow why src/auth.ts
|
|
15
|
-
* infernoflow why loginUser
|
|
16
|
-
* infernoflow why src/auth.ts --function loginUser
|
|
17
|
-
* infernoflow why --json src/auth.ts
|
|
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 { bold, cyan, gray, green, yellow, red } from "../ui/output.mjs";
|
|
24
|
-
|
|
25
|
-
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
function loadJson(p) {
|
|
28
|
-
try { return JSON.parse(fs.readFileSync(p, "utf8")); }
|
|
29
|
-
catch { return null; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function runGit(cmd, cwd) {
|
|
33
|
-
try {
|
|
34
|
-
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
35
|
-
} catch { return ""; }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const LEVEL_ICON = { frozen: "🧊", stable: "〰️ ", experimental: "🌊" };
|
|
39
|
-
const LEVEL_COLOR = { frozen: red, stable: yellow, experimental: green };
|
|
40
|
-
|
|
41
|
-
function stability(cap) {
|
|
42
|
-
return cap?.stability || "experimental";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ── matchers ─────────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Given a target (file path or function name), find all matching
|
|
49
|
-
* capabilities from scan.json.
|
|
50
|
-
* Returns: [{ capId, capEntry, matchedVia, score }]
|
|
51
|
-
*/
|
|
52
|
-
function findCapabilities(target, scanCaps, allCaps, cwd) {
|
|
53
|
-
const results = [];
|
|
54
|
-
const isFile = target.includes("/") || target.includes("\\") || target.includes(".");
|
|
55
|
-
const relTarget = isFile ? path.relative(cwd, path.resolve(cwd, target)) : null;
|
|
56
|
-
|
|
57
|
-
for (const entry of scanCaps) {
|
|
58
|
-
const analysis = entry.codeAnalysis;
|
|
59
|
-
if (!analysis) continue;
|
|
60
|
-
|
|
61
|
-
const capFull = allCaps.find(c => c.id === entry.id) || {};
|
|
62
|
-
|
|
63
|
-
if (isFile) {
|
|
64
|
-
// Match by source file
|
|
65
|
-
const fileMatch = (analysis.sourceFiles || []).some(f =>
|
|
66
|
-
f === relTarget || f.endsWith(relTarget) || relTarget?.endsWith(f)
|
|
67
|
-
);
|
|
68
|
-
if (fileMatch) {
|
|
69
|
-
results.push({ capId: entry.id, capEntry: entry, capFull, matchedVia: "file", score: 1.0 });
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!isFile) {
|
|
75
|
-
// Match by function name (exact or contains)
|
|
76
|
-
const fnMatch = (analysis.functions || []).some(fn =>
|
|
77
|
-
fn.toLowerCase() === target.toLowerCase() ||
|
|
78
|
-
fn.toLowerCase().includes(target.toLowerCase()) ||
|
|
79
|
-
target.toLowerCase().includes(fn.toLowerCase())
|
|
80
|
-
);
|
|
81
|
-
if (fnMatch) {
|
|
82
|
-
results.push({ capId: entry.id, capEntry: entry, capFull, matchedVia: "function", score: 1.0 });
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return results;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ── scenario finder ───────────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
function findScenarios(capId, infernoDir) {
|
|
94
|
-
const scenariosDir = path.join(infernoDir, "scenarios");
|
|
95
|
-
if (!fs.existsSync(scenariosDir)) return [];
|
|
96
|
-
|
|
97
|
-
const found = [];
|
|
98
|
-
for (const f of fs.readdirSync(scenariosDir)) {
|
|
99
|
-
if (!f.endsWith(".json")) continue;
|
|
100
|
-
try {
|
|
101
|
-
const s = JSON.parse(fs.readFileSync(path.join(scenariosDir, f), "utf8"));
|
|
102
|
-
const covered = s.capabilitiesCovered || s.capabilities || [];
|
|
103
|
-
if (covered.includes(capId) || covered.some(c =>
|
|
104
|
-
c.toLowerCase() === capId.toLowerCase()
|
|
105
|
-
)) {
|
|
106
|
-
found.push({ file: f, scenario: s });
|
|
107
|
-
}
|
|
108
|
-
} catch {}
|
|
109
|
-
}
|
|
110
|
-
return found;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── git history ───────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
function getGitHistory(filePath, cwd, limit = 5) {
|
|
116
|
-
if (!filePath) return [];
|
|
117
|
-
const rel = path.relative(cwd, path.resolve(cwd, filePath));
|
|
118
|
-
const log = runGit(
|
|
119
|
-
`git log --follow --format="%h|%aI|%ae|%s" -${limit} -- ${JSON.stringify(rel)}`,
|
|
120
|
-
cwd
|
|
121
|
-
);
|
|
122
|
-
if (!log) return [];
|
|
123
|
-
|
|
124
|
-
return log.split("\n").filter(Boolean).map(line => {
|
|
125
|
-
const [hash, date, author, ...subjectParts] = line.split("|");
|
|
126
|
-
return {
|
|
127
|
-
hash: hash?.trim(),
|
|
128
|
-
date: date?.trim() ? new Date(date.trim()).toLocaleDateString() : "",
|
|
129
|
-
author: author?.trim(),
|
|
130
|
-
subject: subjectParts.join("|").trim(),
|
|
131
|
-
};
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function getFirstCommit(filePath, cwd) {
|
|
136
|
-
if (!filePath) return null;
|
|
137
|
-
const rel = path.relative(cwd, path.resolve(cwd, filePath));
|
|
138
|
-
const log = runGit(
|
|
139
|
-
`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(rel)}`,
|
|
140
|
-
cwd
|
|
141
|
-
);
|
|
142
|
-
if (!log) return null;
|
|
143
|
-
const lines = log.split("\n").filter(Boolean);
|
|
144
|
-
if (!lines.length) return null;
|
|
145
|
-
const [hash, date, author, ...subjectParts] = lines[lines.length - 1].split("|");
|
|
146
|
-
return {
|
|
147
|
-
hash: hash?.trim(),
|
|
148
|
-
date: date?.trim() ? new Date(date.trim()).toLocaleDateString() : "",
|
|
149
|
-
author: author?.trim(),
|
|
150
|
-
subject: subjectParts.join("|").trim(),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ── printer ───────────────────────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
function printResult(result, scenarios, history, firstCommit, graph, allCaps, target) {
|
|
157
|
-
const { capId, capEntry, capFull, matchedVia } = result;
|
|
158
|
-
const level = stability(capFull);
|
|
159
|
-
const icon = LEVEL_ICON[level] || "🌊";
|
|
160
|
-
const color = LEVEL_COLOR[level] || green;
|
|
161
|
-
|
|
162
|
-
console.log();
|
|
163
|
-
console.log(bold(` ${icon} ${color(capId)}`));
|
|
164
|
-
if (capFull.name || capFull.title) {
|
|
165
|
-
console.log(gray(` ${capFull.name || capFull.title}`));
|
|
166
|
-
}
|
|
167
|
-
if (capFull.description) {
|
|
168
|
-
console.log(gray(` ${capFull.description}`));
|
|
169
|
-
}
|
|
170
|
-
console.log();
|
|
171
|
-
|
|
172
|
-
// Matched via
|
|
173
|
-
console.log(gray(` matched via: `) + matchedVia + gray(` → `) + cyan(target));
|
|
174
|
-
|
|
175
|
-
// Stability
|
|
176
|
-
console.log(gray(` stability: `) + color(level));
|
|
177
|
-
|
|
178
|
-
// Source files
|
|
179
|
-
const files = capEntry.codeAnalysis?.sourceFiles || [];
|
|
180
|
-
if (files.length) {
|
|
181
|
-
console.log(gray(` source files: `) + files.join(", "));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Functions
|
|
185
|
-
const fns = capEntry.codeAnalysis?.functions || [];
|
|
186
|
-
if (fns.length) {
|
|
187
|
-
console.log(gray(` functions: `) + fns.join(", "));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// External services
|
|
191
|
-
const services = capEntry.codeAnalysis?.services || [];
|
|
192
|
-
if (services.length) {
|
|
193
|
-
console.log(gray(` uses: `) + cyan(services.join(", ")));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Throws
|
|
197
|
-
const throws = capEntry.codeAnalysis?.throws || [];
|
|
198
|
-
if (throws.length) {
|
|
199
|
-
console.log(gray(` throws: `) + yellow(throws.join(", ")));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Dependencies from graph
|
|
203
|
-
if (graph) {
|
|
204
|
-
const deps = graph.deps?.[capId] || [];
|
|
205
|
-
const dependents = graph.dependents?.[capId] || [];
|
|
206
|
-
if (deps.length) {
|
|
207
|
-
console.log(gray(` calls: `) + deps.map(d => {
|
|
208
|
-
const dCap = allCaps.find(c => c.id === d);
|
|
209
|
-
const dIcon = LEVEL_ICON[stability(dCap)] || "🌊";
|
|
210
|
-
return `${dIcon} ${d}`;
|
|
211
|
-
}).join(" "));
|
|
212
|
-
}
|
|
213
|
-
if (dependents.length) {
|
|
214
|
-
console.log(gray(` called by: `) + dependents.map(d => {
|
|
215
|
-
const dCap = allCaps.find(c => c.id === d);
|
|
216
|
-
const dIcon = LEVEL_ICON[stability(dCap)] || "🌊";
|
|
217
|
-
return `${dIcon} ${d}`;
|
|
218
|
-
}).join(" "));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
console.log();
|
|
223
|
-
|
|
224
|
-
// Scenarios
|
|
225
|
-
if (scenarios.length > 0) {
|
|
226
|
-
console.log(bold(" Scenarios that cover this capability:"));
|
|
227
|
-
for (const { scenario } of scenarios) {
|
|
228
|
-
const steps = scenario.steps?.length || 0;
|
|
229
|
-
console.log(` ${green("✔")} ${scenario.scenarioId || scenario.description || scenario.file}`);
|
|
230
|
-
if (scenario.description) console.log(gray(` ${scenario.description}`));
|
|
231
|
-
if (steps) console.log(gray(` ${steps} step(s)`));
|
|
232
|
-
}
|
|
233
|
-
console.log();
|
|
234
|
-
} else {
|
|
235
|
-
console.log(yellow(" ⚠ No scenarios found for this capability."));
|
|
236
|
-
console.log(gray(` Run: infernoflow suggest "add scenario for ${capId}"`));
|
|
237
|
-
console.log();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Git history
|
|
241
|
-
if (firstCommit) {
|
|
242
|
-
console.log(bold(" Origin:"));
|
|
243
|
-
console.log(` ${gray("first commit:")} ${firstCommit.hash} · ${firstCommit.date} · ${firstCommit.author}`);
|
|
244
|
-
console.log(` ${gray("subject:")} ${firstCommit.subject}`);
|
|
245
|
-
console.log();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (history.length > 0) {
|
|
249
|
-
console.log(bold(" Recent changes:"));
|
|
250
|
-
for (const h of history.slice(0, 4)) {
|
|
251
|
-
console.log(` ${gray(h.hash)} ${gray(h.date.padEnd(12))} ${h.subject}`);
|
|
252
|
-
}
|
|
253
|
-
console.log();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Frozen warning
|
|
257
|
-
if (level === "frozen") {
|
|
258
|
-
console.log(red(" 🧊 This capability is FROZEN — do not modify without explicit instruction."));
|
|
259
|
-
console.log();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ── entry point ───────────────────────────────────────────────────────────────
|
|
264
|
-
|
|
265
|
-
export async function whyCommand(rawArgs) {
|
|
266
|
-
const args = (rawArgs || []).slice(1); // skip command name
|
|
267
|
-
const jsonMode = args.includes("--json");
|
|
268
|
-
const fnFlag = args.indexOf("--function");
|
|
269
|
-
const fnFilter = fnFlag !== -1 ? args[fnFlag + 1] : null;
|
|
270
|
-
|
|
271
|
-
// Target: first non-flag argument (skip the value after --function if present)
|
|
272
|
-
const target = args.find((a, i) => !a.startsWith("--") && (fnFlag === -1 || i !== fnFlag + 1));
|
|
273
|
-
|
|
274
|
-
if (!target) {
|
|
275
|
-
console.error(red("✗ Usage: infernoflow why <file-or-function> [--function <name>] [--json]"));
|
|
276
|
-
console.error(gray(" Examples:"));
|
|
277
|
-
console.error(gray(" infernoflow why src/auth.ts"));
|
|
278
|
-
console.error(gray(" infernoflow why loginUser"));
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const cwd = process.cwd();
|
|
283
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
284
|
-
|
|
285
|
-
// Load scan.json
|
|
286
|
-
const scan = loadJson(path.join(infernoDir, "scan.json"));
|
|
287
|
-
if (!scan) {
|
|
288
|
-
console.error(red("✗ inferno/scan.json not found — run `infernoflow scan` first."));
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Load capabilities.json for stability + metadata
|
|
293
|
-
let allCaps = [];
|
|
294
|
-
const rawCaps = loadJson(path.join(infernoDir, "capabilities.json"));
|
|
295
|
-
if (rawCaps) allCaps = Array.isArray(rawCaps) ? rawCaps : (rawCaps.capabilities || []);
|
|
296
|
-
|
|
297
|
-
// Load graph.json
|
|
298
|
-
const graph = loadJson(path.join(infernoDir, "graph.json"));
|
|
299
|
-
|
|
300
|
-
// Find capabilities
|
|
301
|
-
const scanCaps = scan.capabilities || [];
|
|
302
|
-
let results = findCapabilities(target, scanCaps, allCaps, cwd);
|
|
303
|
-
|
|
304
|
-
// Apply --function filter
|
|
305
|
-
if (fnFilter && results.length > 1) {
|
|
306
|
-
results = results.filter(r =>
|
|
307
|
-
(r.capEntry.codeAnalysis?.functions || []).some(fn =>
|
|
308
|
-
fn.toLowerCase().includes(fnFilter.toLowerCase())
|
|
309
|
-
)
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (results.length === 0) {
|
|
314
|
-
console.log();
|
|
315
|
-
console.log(yellow(` No capability found matching: ${bold(target)}`));
|
|
316
|
-
console.log(gray(" Tip: run `infernoflow scan` to update code analysis, then try again."));
|
|
317
|
-
console.log(gray(" Tip: use a function name or relative file path."));
|
|
318
|
-
console.log();
|
|
319
|
-
process.exit(0);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (jsonMode) {
|
|
323
|
-
const out = results.map(r => {
|
|
324
|
-
const scenarios = findScenarios(r.capId, infernoDir);
|
|
325
|
-
const files = r.capEntry.codeAnalysis?.sourceFiles || [];
|
|
326
|
-
const history = getGitHistory(files[0], cwd);
|
|
327
|
-
const first = getFirstCommit(files[0], cwd);
|
|
328
|
-
return {
|
|
329
|
-
capId: r.capId,
|
|
330
|
-
name: r.capFull.name || r.capFull.title,
|
|
331
|
-
stability: stability(r.capFull),
|
|
332
|
-
matchedVia: r.matchedVia,
|
|
333
|
-
sourceFiles: files,
|
|
334
|
-
functions: r.capEntry.codeAnalysis?.functions || [],
|
|
335
|
-
services: r.capEntry.codeAnalysis?.services || [],
|
|
336
|
-
throws: r.capEntry.codeAnalysis?.throws || [],
|
|
337
|
-
deps: graph?.deps?.[r.capId] || [],
|
|
338
|
-
dependents: graph?.dependents?.[r.capId] || [],
|
|
339
|
-
scenarios: scenarios.map(s => s.scenario?.scenarioId || s.file),
|
|
340
|
-
firstCommit: first,
|
|
341
|
-
recentHistory: history,
|
|
342
|
-
};
|
|
343
|
-
});
|
|
344
|
-
console.log(JSON.stringify(out, null, 2));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
console.log(gray(`\n infernoflow why → ${bold(target)}`));
|
|
349
|
-
console.log(gray(" ──────────────────────────────────────────────────────────────"));
|
|
350
|
-
|
|
351
|
-
for (const result of results) {
|
|
352
|
-
const files = result.capEntry.codeAnalysis?.sourceFiles || [];
|
|
353
|
-
const scenarios = findScenarios(result.capId, infernoDir);
|
|
354
|
-
const history = getGitHistory(files[0], cwd);
|
|
355
|
-
const first = getFirstCommit(files[0], cwd);
|
|
356
|
-
printResult(result, scenarios, history, first, graph, allCaps, target);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
1
|
+
import*as F from"node:fs";import*as h from"node:path";import{execSync as W}from"node:child_process";import{bold as C,cyan as V,gray as n,green as x,yellow as I,red as L}from"../ui/output.mjs";function O(o){try{return JSON.parse(F.readFileSync(o,"utf8"))}catch{return null}}function D(o,t){try{return W(o,{cwd:t,encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}}const N={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},z={frozen:L,stable:I,experimental:x};function v(o){return o?.stability||"experimental"}function B(o,t,p,i){const c=[],l=o.includes("/")||o.includes("\\")||o.includes("."),r=l?h.relative(i,h.resolve(i,o)):null;for(const e of t){const d=e.codeAnalysis;if(!d)continue;const f=p.find(y=>y.id===e.id)||{};if(l&&(d.sourceFiles||[]).some(u=>u===r||u.endsWith(r)||r?.endsWith(u))){c.push({capId:e.id,capEntry:e,capFull:f,matchedVia:"file",score:1});continue}if(!l&&(d.functions||[]).some(u=>u.toLowerCase()===o.toLowerCase()||u.toLowerCase().includes(o.toLowerCase())||o.toLowerCase().includes(u.toLowerCase()))){c.push({capId:e.id,capEntry:e,capFull:f,matchedVia:"function",score:1});continue}}return c}function J(o,t){const p=h.join(t,"scenarios");if(!F.existsSync(p))return[];const i=[];for(const c of F.readdirSync(p))if(c.endsWith(".json"))try{const l=JSON.parse(F.readFileSync(h.join(p,c),"utf8")),r=l.capabilitiesCovered||l.capabilities||[];(r.includes(o)||r.some(e=>e.toLowerCase()===o.toLowerCase()))&&i.push({file:c,scenario:l})}catch{}return i}function M(o,t,p=5){if(!o)return[];const i=h.relative(t,h.resolve(t,o)),c=D(`git log --follow --format="%h|%aI|%ae|%s" -${p} -- ${JSON.stringify(i)}`,t);return c?c.split(`
|
|
2
|
+
`).filter(Boolean).map(l=>{const[r,e,d,...f]=l.split("|");return{hash:r?.trim(),date:e?.trim()?new Date(e.trim()).toLocaleDateString():"",author:d?.trim(),subject:f.join("|").trim()}}):[]}function R(o,t){if(!o)return null;const p=h.relative(t,h.resolve(t,o)),i=D(`git log --follow --format="%h|%aI|%ae|%s" -- ${JSON.stringify(p)}`,t);if(!i)return null;const c=i.split(`
|
|
3
|
+
`).filter(Boolean);if(!c.length)return null;const[l,r,e,...d]=c[c.length-1].split("|");return{hash:l?.trim(),date:r?.trim()?new Date(r.trim()).toLocaleDateString():"",author:e?.trim(),subject:d.join("|").trim()}}function G(o,t,p,i,c,l,r){const{capId:e,capEntry:d,capFull:f,matchedVia:y}=o,u=v(f),E=N[u]||"\u{1F30A}",m=z[u]||x;console.log(),console.log(C(` ${E} ${m(e)}`)),(f.name||f.title)&&console.log(n(` ${f.name||f.title}`)),f.description&&console.log(n(` ${f.description}`)),console.log(),console.log(n(" matched via: ")+y+n(" \u2192 ")+V(r)),console.log(n(" stability: ")+m(u));const g=d.codeAnalysis?.sourceFiles||[];g.length&&console.log(n(" source files: ")+g.join(", "));const s=d.codeAnalysis?.functions||[];s.length&&console.log(n(" functions: ")+s.join(", "));const b=d.codeAnalysis?.services||[];b.length&&console.log(n(" uses: ")+V(b.join(", ")));const w=d.codeAnalysis?.throws||[];if(w.length&&console.log(n(" throws: ")+I(w.join(", "))),c){const a=c.deps?.[e]||[],$=c.dependents?.[e]||[];a.length&&console.log(n(" calls: ")+a.map(j=>{const S=l.find(A=>A.id===j);return`${N[v(S)]||"\u{1F30A}"} ${j}`}).join(" ")),$.length&&console.log(n(" called by: ")+$.map(j=>{const S=l.find(A=>A.id===j);return`${N[v(S)]||"\u{1F30A}"} ${j}`}).join(" "))}if(console.log(),t.length>0){console.log(C(" Scenarios that cover this capability:"));for(const{scenario:a}of t){const $=a.steps?.length||0;console.log(` ${x("\u2714")} ${a.scenarioId||a.description||a.file}`),a.description&&console.log(n(` ${a.description}`)),$&&console.log(n(` ${$} step(s)`))}console.log()}else console.log(I(" \u26A0 No scenarios found for this capability.")),console.log(n(` Run: infernoflow suggest "add scenario for ${e}"`)),console.log();if(i&&(console.log(C(" Origin:")),console.log(` ${n("first commit:")} ${i.hash} \xB7 ${i.date} \xB7 ${i.author}`),console.log(` ${n("subject:")} ${i.subject}`),console.log()),p.length>0){console.log(C(" Recent changes:"));for(const a of p.slice(0,4))console.log(` ${n(a.hash)} ${n(a.date.padEnd(12))} ${a.subject}`);console.log()}u==="frozen"&&(console.log(L(" \u{1F9CA} This capability is FROZEN \u2014 do not modify without explicit instruction.")),console.log())}async function _(o){const t=(o||[]).slice(1),p=t.includes("--json"),i=t.indexOf("--function"),c=i!==-1?t[i+1]:null,l=t.find((g,s)=>!g.startsWith("--")&&(i===-1||s!==i+1));l||(console.error(L("\u2717 Usage: infernoflow why <file-or-function> [--function <name>] [--json]")),console.error(n(" Examples:")),console.error(n(" infernoflow why src/auth.ts")),console.error(n(" infernoflow why loginUser")),process.exit(1));const r=process.cwd(),e=h.join(r,"inferno"),d=O(h.join(e,"scan.json"));d||(console.error(L("\u2717 inferno/scan.json not found \u2014 run `infernoflow scan` first.")),process.exit(1));let f=[];const y=O(h.join(e,"capabilities.json"));y&&(f=Array.isArray(y)?y:y.capabilities||[]);const u=O(h.join(e,"graph.json")),E=d.capabilities||[];let m=B(l,E,f,r);if(c&&m.length>1&&(m=m.filter(g=>(g.capEntry.codeAnalysis?.functions||[]).some(s=>s.toLowerCase().includes(c.toLowerCase())))),m.length===0&&(console.log(),console.log(I(` No capability found matching: ${C(l)}`)),console.log(n(" Tip: run `infernoflow scan` to update code analysis, then try again.")),console.log(n(" Tip: use a function name or relative file path.")),console.log(),process.exit(0)),p){const g=m.map(s=>{const b=J(s.capId,e),w=s.capEntry.codeAnalysis?.sourceFiles||[],a=M(w[0],r),$=R(w[0],r);return{capId:s.capId,name:s.capFull.name||s.capFull.title,stability:v(s.capFull),matchedVia:s.matchedVia,sourceFiles:w,functions:s.capEntry.codeAnalysis?.functions||[],services:s.capEntry.codeAnalysis?.services||[],throws:s.capEntry.codeAnalysis?.throws||[],deps:u?.deps?.[s.capId]||[],dependents:u?.dependents?.[s.capId]||[],scenarios:b.map(j=>j.scenario?.scenarioId||j.file),firstCommit:$,recentHistory:a}});console.log(JSON.stringify(g,null,2));return}console.log(n(`
|
|
4
|
+
infernoflow why \u2192 ${C(l)}`)),console.log(n(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));for(const g of m){const s=g.capEntry.codeAnalysis?.sourceFiles||[],b=J(g.capId,e),w=M(s[0],r),a=R(s[0],r);G(g,b,w,a,u,f,l)}}export{_ as whyCommand};
|
|
@@ -1,60 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { installInfernoDraftTooling } from "./draftToolingInstall.mjs";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {object} opts
|
|
7
|
-
* @param {string} opts.cwd
|
|
8
|
-
* @param {string} opts.templatesRoot
|
|
9
|
-
* @param {boolean} opts.force
|
|
10
|
-
* @param {boolean} opts.silent
|
|
11
|
-
* @param {(msg: string) => void} [opts.logOk]
|
|
12
|
-
* @param {(msg: string) => void} [opts.logWarn]
|
|
13
|
-
*/
|
|
14
|
-
export function installCursorHooksArtifacts(opts) {
|
|
15
|
-
const { cwd, templatesRoot, force, silent } = opts;
|
|
16
|
-
const logOk = opts.logOk || (() => {});
|
|
17
|
-
const logWarn = opts.logWarn || (() => {});
|
|
18
|
-
|
|
19
|
-
function copyFile(src, dst) {
|
|
20
|
-
if (fs.existsSync(dst) && !force) {
|
|
21
|
-
if (!silent) logWarn("Skipped (exists): " + path.relative(cwd, dst));
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
25
|
-
fs.copyFileSync(src, dst);
|
|
26
|
-
if (!silent) logOk("Created: " + path.relative(cwd, dst));
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
installInfernoDraftTooling({ cwd, templatesRoot, force, silent, logOk, logWarn });
|
|
31
|
-
|
|
32
|
-
const srcHooksJson = path.join(templatesRoot, "cursor", "hooks.json");
|
|
33
|
-
const dstHooksJson = path.join(cwd, ".cursor", "hooks.json");
|
|
34
|
-
const srcHook = path.join(templatesRoot, "cursor", "hooks", "inferno-session-draft.mjs");
|
|
35
|
-
const dstHook = path.join(cwd, ".cursor", "hooks", "inferno-session-draft.mjs");
|
|
36
|
-
|
|
37
|
-
copyFile(srcHooksJson, dstHooksJson);
|
|
38
|
-
copyFile(srcHook, dstHook);
|
|
39
|
-
|
|
40
|
-
// MCP server
|
|
41
|
-
const srcMcp = path.join(templatesRoot, "cursor", "inferno-mcp-server.mjs");
|
|
42
|
-
const dstMcp = path.join(cwd, "inferno-mcp-server.mjs");
|
|
43
|
-
copyFile(srcMcp, dstMcp);
|
|
44
|
-
|
|
45
|
-
// .cursor/mcp.json
|
|
46
|
-
const mcpJsonPath = path.join(cwd, ".cursor", "mcp.json");
|
|
47
|
-
if (!fs.existsSync(mcpJsonPath) || force) {
|
|
48
|
-
let existing = {};
|
|
49
|
-
if (fs.existsSync(mcpJsonPath)) {
|
|
50
|
-
try { existing = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8")); } catch {}
|
|
51
|
-
}
|
|
52
|
-
if (!existing.mcpServers) existing.mcpServers = {};
|
|
53
|
-
existing.mcpServers.infernoflow = { command: "node", args: ["./inferno-mcp-server.mjs"], env: {} };
|
|
54
|
-
fs.mkdirSync(path.dirname(mcpJsonPath), { recursive: true });
|
|
55
|
-
fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2), "utf8");
|
|
56
|
-
if (!silent) logOk("Created: .cursor/mcp.json");
|
|
57
|
-
} else {
|
|
58
|
-
if (!silent) logWarn("Skipped (exists): .cursor/mcp.json");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
import*as r from"node:fs";import*as o from"node:path";import{installInfernoDraftTooling as v}from"./draftToolingInstall.mjs";function h(f){const{cwd:s,templatesRoot:t,force:m,silent:e}=f,a=f.logOk||(()=>{}),l=f.logWarn||(()=>{});function p(n,c){return r.existsSync(c)&&!m?(e||l("Skipped (exists): "+o.relative(s,c)),!1):(r.mkdirSync(o.dirname(c),{recursive:!0}),r.copyFileSync(n,c),e||a("Created: "+o.relative(s,c)),!0)}v({cwd:s,templatesRoot:t,force:m,silent:e,logOk:a,logWarn:l});const u=o.join(t,"cursor","hooks.json"),j=o.join(s,".cursor","hooks.json"),k=o.join(t,"cursor","hooks","inferno-session-draft.mjs"),S=o.join(s,".cursor","hooks","inferno-session-draft.mjs");p(u,j),p(k,S);const d=o.join(t,"cursor","inferno-mcp-server.mjs"),y=o.join(s,"inferno-mcp-server.mjs");p(d,y);const i=o.join(s,".cursor","mcp.json");if(!r.existsSync(i)||m){let n={};if(r.existsSync(i))try{n=JSON.parse(r.readFileSync(i,"utf8"))}catch{}n.mcpServers||(n.mcpServers={}),n.mcpServers.infernoflow={command:"node",args:["./inferno-mcp-server.mjs"],env:{}},r.mkdirSync(o.dirname(i),{recursive:!0}),r.writeFileSync(i,JSON.stringify(n,null,2),"utf8"),e||a("Created: .cursor/mcp.json")}else e||l("Skipped (exists): .cursor/mcp.json")}export{h as installCursorHooksArtifacts};
|