infernoflow 0.37.1 → 0.37.4
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 +71 -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,361 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Posts a capability drift analysis as a GitHub PR comment.
|
|
5
|
-
* Designed to run in CI (GitHub Actions) on pull_request events.
|
|
6
|
-
*
|
|
7
|
-
* Auto-reads context from GitHub Actions environment variables:
|
|
8
|
-
* GITHUB_TOKEN — required for posting comments
|
|
9
|
-
* GITHUB_REPOSITORY — e.g. "owner/repo"
|
|
10
|
-
* GITHUB_EVENT_PATH — path to the event JSON (contains PR number)
|
|
11
|
-
* GITHUB_SHA — current commit SHA
|
|
12
|
-
* GITHUB_BASE_REF — base branch name (e.g. "main")
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* infernoflow pr-comment # auto-detect from CI env
|
|
16
|
-
* infernoflow pr-comment --pr 42 # explicit PR number
|
|
17
|
-
* infernoflow pr-comment --repo owner/r # explicit repo
|
|
18
|
-
* infernoflow pr-comment --token ghp_... # explicit token
|
|
19
|
-
* infernoflow pr-comment --dry-run # print comment without posting
|
|
20
|
-
* infernoflow pr-comment --json # machine-readable result
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import * as path from "node:path";
|
|
25
|
-
import * as https from "node:https";
|
|
26
|
-
import { execSync } from "node:child_process";
|
|
27
|
-
import { header, ok, warn, info, done, bold, cyan, gray, green, red, yellow } from "../ui/output.mjs";
|
|
28
|
-
|
|
29
|
-
// ── git helpers ───────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
function capture(cmd, cwd) {
|
|
32
|
-
try {
|
|
33
|
-
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
34
|
-
} catch { return null; }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function lastTag(cwd) {
|
|
38
|
-
return capture("git describe --tags --abbrev=0", cwd) || null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function fileAtRef(ref, relPath, cwd) {
|
|
42
|
-
return capture(`git show "${ref}:${relPath}"`, cwd);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ── capability helpers ────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
function parseCaps(jsonText) {
|
|
48
|
-
if (!jsonText) return null;
|
|
49
|
-
try {
|
|
50
|
-
const obj = JSON.parse(jsonText);
|
|
51
|
-
const raw = obj.capabilities || [];
|
|
52
|
-
return raw.map(c => {
|
|
53
|
-
if (typeof c === "string") return { id: c, title: c };
|
|
54
|
-
return { id: c.id || c, title: c.title || c.id || String(c), status: c.status };
|
|
55
|
-
});
|
|
56
|
-
} catch { return null; }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function loadCapsFromDisk(infernoDir) {
|
|
60
|
-
for (const name of ["capabilities.json", "contract.json"]) {
|
|
61
|
-
const p = path.join(infernoDir, name);
|
|
62
|
-
if (fs.existsSync(p)) return parseCaps(fs.readFileSync(p, "utf8"));
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function loadCapsAtRef(ref, cwd) {
|
|
68
|
-
for (const name of ["capabilities.json", "contract.json"]) {
|
|
69
|
-
const content = fileAtRef(ref, `inferno/${name}`, cwd);
|
|
70
|
-
if (content) return parseCaps(content);
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function diffCaps(before, after) {
|
|
76
|
-
const beforeMap = new Map(before.map(c => [c.id, c]));
|
|
77
|
-
const afterMap = new Map(after.map(c => [c.id, c]));
|
|
78
|
-
const added = after.filter(c => !beforeMap.has(c.id));
|
|
79
|
-
const removed = before.filter(c => !afterMap.has(c.id));
|
|
80
|
-
const changed = [];
|
|
81
|
-
for (const c of after) {
|
|
82
|
-
const old = beforeMap.get(c.id);
|
|
83
|
-
if (!old) continue;
|
|
84
|
-
const changes = [];
|
|
85
|
-
if (old.title !== c.title) changes.push({ field: "title", from: old.title, to: c.title });
|
|
86
|
-
if ((old.status || "") !== (c.status || "")) changes.push({ field: "status", from: old.status || "—", to: c.status || "—" });
|
|
87
|
-
if (changes.length) changed.push({ id: c.id, changes });
|
|
88
|
-
}
|
|
89
|
-
return { added, removed, changed };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function classifyBump(diff) {
|
|
93
|
-
if (diff.removed.length > 0) return "major";
|
|
94
|
-
if (diff.added.length > 0) return "minor";
|
|
95
|
-
if (diff.changed.length > 0) return "patch";
|
|
96
|
-
return "none";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ── comment builder ───────────────────────────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
function buildComment(diff, bump, ref, currentVersion, nextVersion) {
|
|
102
|
-
const lines = [];
|
|
103
|
-
|
|
104
|
-
// Header
|
|
105
|
-
const bumpEmoji = bump === "major" ? "🔴" : bump === "minor" ? "🟡" : bump === "patch" ? "🟢" : "✅";
|
|
106
|
-
const bumpLabel = bump === "none" ? "No capability changes" : `${bump.toUpperCase()} bump recommended`;
|
|
107
|
-
lines.push(`## 🔥 infernoflow — Capability Analysis`);
|
|
108
|
-
lines.push(``);
|
|
109
|
-
lines.push(`${bumpEmoji} **${bumpLabel}**${bump !== "none" ? ` · \`${currentVersion}\` → \`${nextVersion}\`` : ""}`);
|
|
110
|
-
lines.push(``);
|
|
111
|
-
|
|
112
|
-
// Summary table
|
|
113
|
-
const hasChanges = diff.added.length || diff.removed.length || diff.changed.length;
|
|
114
|
-
if (!hasChanges) {
|
|
115
|
-
lines.push(`> No capability changes detected since \`${ref}\`. Contract is in sync.`);
|
|
116
|
-
} else {
|
|
117
|
-
lines.push(`| Change | Count |`);
|
|
118
|
-
lines.push(`|--------|-------|`);
|
|
119
|
-
if (diff.added.length) lines.push(`| ➕ Added | ${diff.added.length} |`);
|
|
120
|
-
if (diff.removed.length) lines.push(`| ❌ Removed | ${diff.removed.length} |`);
|
|
121
|
-
if (diff.changed.length) lines.push(`| ✏️ Modified | ${diff.changed.length} |`);
|
|
122
|
-
lines.push(``);
|
|
123
|
-
|
|
124
|
-
// Detail sections
|
|
125
|
-
if (diff.added.length) {
|
|
126
|
-
lines.push(`<details><summary>➕ Added capabilities (${diff.added.length})</summary>`);
|
|
127
|
-
lines.push(``);
|
|
128
|
-
for (const c of diff.added) lines.push(`- \`${c.id}\` — ${c.title}`);
|
|
129
|
-
lines.push(``);
|
|
130
|
-
lines.push(`</details>`);
|
|
131
|
-
lines.push(``);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (diff.removed.length) {
|
|
135
|
-
lines.push(`<details><summary>❌ Removed capabilities (${diff.removed.length}) — breaking change</summary>`);
|
|
136
|
-
lines.push(``);
|
|
137
|
-
for (const c of diff.removed) lines.push(`- \`${c.id}\` — ${c.title}`);
|
|
138
|
-
lines.push(``);
|
|
139
|
-
lines.push(`</details>`);
|
|
140
|
-
lines.push(``);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (diff.changed.length) {
|
|
144
|
-
lines.push(`<details><summary>✏️ Modified capabilities (${diff.changed.length})</summary>`);
|
|
145
|
-
lines.push(``);
|
|
146
|
-
for (const item of diff.changed) {
|
|
147
|
-
lines.push(`- \`${item.id}\``);
|
|
148
|
-
for (const ch of item.changes) lines.push(` - ${ch.field}: \`${ch.from}\` → \`${ch.to}\``);
|
|
149
|
-
}
|
|
150
|
-
lines.push(``);
|
|
151
|
-
lines.push(`</details>`);
|
|
152
|
-
lines.push(``);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Bump recommendation
|
|
157
|
-
if (bump === "major") {
|
|
158
|
-
lines.push(`> ⚠️ **Breaking change detected.** Capabilities were removed. Consider a major version bump.`);
|
|
159
|
-
lines.push(`> Run \`infernoflow version --apply\` to update \`package.json\`.`);
|
|
160
|
-
} else if (bump === "minor") {
|
|
161
|
-
lines.push(`> ℹ️ New capabilities added. A minor version bump is recommended.`);
|
|
162
|
-
lines.push(`> Run \`infernoflow version --apply\` to update \`package.json\`.`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
lines.push(``);
|
|
166
|
-
lines.push(`---`);
|
|
167
|
-
lines.push(`<sub>Generated by [infernoflow](https://github.com/ronmiz/infernoflow) · compared against \`${ref}\`</sub>`);
|
|
168
|
-
|
|
169
|
-
return lines.join("\n");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ── GitHub API ────────────────────────────────────────────────────────────────
|
|
173
|
-
|
|
174
|
-
function githubRequest(method, pathname, body, token) {
|
|
175
|
-
return new Promise((resolve, reject) => {
|
|
176
|
-
const data = body ? JSON.stringify(body) : null;
|
|
177
|
-
const req = https.request({
|
|
178
|
-
hostname: "api.github.com",
|
|
179
|
-
path: pathname,
|
|
180
|
-
method,
|
|
181
|
-
headers: {
|
|
182
|
-
"Authorization": `Bearer ${token}`,
|
|
183
|
-
"Accept": "application/vnd.github+json",
|
|
184
|
-
"Content-Type": "application/json",
|
|
185
|
-
"User-Agent": "infernoflow-cli",
|
|
186
|
-
...(data ? { "Content-Length": Buffer.byteLength(data) } : {}),
|
|
187
|
-
},
|
|
188
|
-
}, (res) => {
|
|
189
|
-
let raw = "";
|
|
190
|
-
res.on("data", chunk => raw += chunk);
|
|
191
|
-
res.on("end", () => {
|
|
192
|
-
try { resolve({ status: res.statusCode, body: JSON.parse(raw) }); }
|
|
193
|
-
catch { resolve({ status: res.statusCode, body: raw }); }
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
req.on("error", reject);
|
|
197
|
-
if (data) req.write(data);
|
|
198
|
-
req.end();
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function findExistingComment(repo, prNumber, token) {
|
|
203
|
-
// Look for a previous infernoflow comment to update instead of creating a new one
|
|
204
|
-
const res = await githubRequest("GET", `/repos/${repo}/issues/${prNumber}/comments?per_page=100`, null, token);
|
|
205
|
-
if (res.status !== 200 || !Array.isArray(res.body)) return null;
|
|
206
|
-
return res.body.find(c => c.body && c.body.includes("🔥 infernoflow — Capability Analysis")) || null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function postComment(repo, prNumber, body, token) {
|
|
210
|
-
// Update existing comment if found (avoids spam on multiple pushes)
|
|
211
|
-
const existing = await findExistingComment(repo, prNumber, token);
|
|
212
|
-
if (existing) {
|
|
213
|
-
return githubRequest("PATCH", `/repos/${repo}/issues/comments/${existing.id}`, { body }, token);
|
|
214
|
-
}
|
|
215
|
-
return githubRequest("POST", `/repos/${repo}/issues/${prNumber}/comments`, { body }, token);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── env helpers ───────────────────────────────────────────────────────────────
|
|
219
|
-
|
|
220
|
-
function readGithubEventPr() {
|
|
221
|
-
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
222
|
-
if (!eventPath || !fs.existsSync(eventPath)) return null;
|
|
223
|
-
try {
|
|
224
|
-
const event = JSON.parse(fs.readFileSync(eventPath, "utf8"));
|
|
225
|
-
return event.pull_request?.number || event.number || null;
|
|
226
|
-
} catch { return null; }
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function applyBump(version, type) {
|
|
230
|
-
const parts = (version || "0.0.0").split(".").map(Number);
|
|
231
|
-
if (type === "major") { parts[0]++; parts[1] = 0; parts[2] = 0; }
|
|
232
|
-
else if (type === "minor") { parts[1]++; parts[2] = 0; }
|
|
233
|
-
else if (type === "patch") { parts[2]++; }
|
|
234
|
-
return parts.join(".");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function readPackageVersion(cwd) {
|
|
238
|
-
const p = path.join(cwd, "package.json");
|
|
239
|
-
if (!fs.existsSync(p)) return "0.0.0";
|
|
240
|
-
try { return JSON.parse(fs.readFileSync(p, "utf8")).version || "0.0.0"; } catch { return "0.0.0"; }
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// ── main ──────────────────────────────────────────────────────────────────────
|
|
244
|
-
|
|
245
|
-
export async function prCommentCommand(rawArgs) {
|
|
246
|
-
const args = rawArgs.slice(1);
|
|
247
|
-
const dryRun = args.includes("--dry-run");
|
|
248
|
-
const asJson = args.includes("--json");
|
|
249
|
-
|
|
250
|
-
const prIdx = args.indexOf("--pr");
|
|
251
|
-
const repoIdx = args.indexOf("--repo");
|
|
252
|
-
const tokenIdx = args.indexOf("--token");
|
|
253
|
-
const refIdx = args.indexOf("--ref");
|
|
254
|
-
|
|
255
|
-
const cwd = process.cwd();
|
|
256
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
257
|
-
|
|
258
|
-
if (!asJson) header("infernoflow pr-comment");
|
|
259
|
-
|
|
260
|
-
// ── Resolve inputs ────────────────────────────────────────────────────────
|
|
261
|
-
const token = tokenIdx !== -1 ? args[tokenIdx + 1] : process.env.GITHUB_TOKEN;
|
|
262
|
-
const repo = repoIdx !== -1 ? args[repoIdx + 1] : process.env.GITHUB_REPOSITORY;
|
|
263
|
-
const prNumber = prIdx !== -1 ? parseInt(args[prIdx + 1], 10)
|
|
264
|
-
: readGithubEventPr();
|
|
265
|
-
|
|
266
|
-
let ref = refIdx !== -1 ? args[refIdx + 1] : null;
|
|
267
|
-
if (!ref) ref = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : null;
|
|
268
|
-
if (!ref) ref = lastTag(cwd);
|
|
269
|
-
if (!ref) {
|
|
270
|
-
const parentExists = capture("git rev-parse HEAD~1", cwd);
|
|
271
|
-
ref = parentExists ? "HEAD~1" : null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ── Validate ──────────────────────────────────────────────────────────────
|
|
275
|
-
if (!fs.existsSync(infernoDir)) {
|
|
276
|
-
const msg = "inferno/ not found — run: infernoflow init";
|
|
277
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
278
|
-
warn(msg); process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!dryRun && !token) {
|
|
282
|
-
const msg = "No GitHub token found. Set GITHUB_TOKEN env var or use --token";
|
|
283
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
284
|
-
warn(msg); process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (!dryRun && !repo) {
|
|
288
|
-
const msg = "No repository found. Set GITHUB_REPOSITORY env var or use --repo owner/repo";
|
|
289
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
290
|
-
warn(msg); process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (!dryRun && !prNumber) {
|
|
294
|
-
const msg = "No PR number found. Use --pr <number> or run in GitHub Actions on pull_request event";
|
|
295
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
296
|
-
warn(msg); process.exit(1);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── Load capabilities and compute diff ───────────────────────────────────
|
|
300
|
-
const current = loadCapsFromDisk(infernoDir);
|
|
301
|
-
const previous = ref ? loadCapsAtRef(ref, cwd) : null;
|
|
302
|
-
|
|
303
|
-
if (!current) {
|
|
304
|
-
const msg = "No capabilities.json or contract.json found in inferno/";
|
|
305
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); process.exit(1); }
|
|
306
|
-
warn(msg); process.exit(1);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const diff = diffCaps(previous || [], current);
|
|
310
|
-
const bump = classifyBump(diff);
|
|
311
|
-
const currentVersion = readPackageVersion(cwd);
|
|
312
|
-
const nextVersion = bump !== "none" ? applyBump(currentVersion, bump) : currentVersion;
|
|
313
|
-
|
|
314
|
-
// ── Build comment ─────────────────────────────────────────────────────────
|
|
315
|
-
const commentBody = buildComment(diff, bump, ref || "HEAD", currentVersion, nextVersion);
|
|
316
|
-
|
|
317
|
-
// ── Dry run ───────────────────────────────────────────────────────────────
|
|
318
|
-
if (dryRun) {
|
|
319
|
-
if (asJson) {
|
|
320
|
-
console.log(JSON.stringify({ ok: true, dryRun: true, bump, currentVersion, nextVersion, comment: commentBody }));
|
|
321
|
-
} else {
|
|
322
|
-
console.log();
|
|
323
|
-
info("DRY RUN — comment that would be posted:");
|
|
324
|
-
console.log();
|
|
325
|
-
console.log(commentBody);
|
|
326
|
-
console.log();
|
|
327
|
-
}
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// ── Post comment ──────────────────────────────────────────────────────────
|
|
332
|
-
if (!asJson) info(`Posting to ${bold(repo)} PR #${prNumber}...`);
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
const result = await postComment(repo, prNumber, commentBody, token);
|
|
336
|
-
|
|
337
|
-
if (result.status === 200 || result.status === 201) {
|
|
338
|
-
const commentUrl = result.body?.html_url || "";
|
|
339
|
-
if (asJson) {
|
|
340
|
-
console.log(JSON.stringify({ ok: true, bump, currentVersion, nextVersion, prNumber, repo, commentUrl }));
|
|
341
|
-
} else {
|
|
342
|
-
ok(`Comment posted → ${cyan(commentUrl || `PR #${prNumber}`)}`);
|
|
343
|
-
if (bump !== "none") {
|
|
344
|
-
console.log();
|
|
345
|
-
info(`Recommended bump: ${bold(bump.toUpperCase())} ${currentVersion} → ${nextVersion}`);
|
|
346
|
-
}
|
|
347
|
-
console.log();
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
const msg = `GitHub API error ${result.status}: ${JSON.stringify(result.body)}`;
|
|
351
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); }
|
|
352
|
-
else { warn(msg); }
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
} catch (err) {
|
|
356
|
-
const msg = `Failed to post comment: ${err.message}`;
|
|
357
|
-
if (asJson) { console.log(JSON.stringify({ ok: false, error: msg })); }
|
|
358
|
-
else { warn(msg); }
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
1
|
+
import*as h from"node:fs";import*as x from"node:path";import*as E from"node:https";import{execSync as T}from"node:child_process";import{header as J,ok as B,warn as g,info as w,bold as A,cyan as H}from"../ui/output.mjs";function N(e,n){try{return T(e,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch{return null}}function I(e){return N("git describe --tags --abbrev=0",e)||null}function P(e,n,s){return N(`git show "${e}:${n}"`,s)}function R(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){for(const n of["capabilities.json","contract.json"]){const s=x.join(e,n);if(h.existsSync(s))return R(h.readFileSync(s,"utf8"))}return null}function G(e,n){for(const s of["capabilities.json","contract.json"]){const o=P(e,`inferno/${s}`,n);if(o)return R(o)}return null}function U(e,n){const s=new Map(e.map(r=>[r.id,r])),o=new Map(n.map(r=>[r.id,r])),u=n.filter(r=>!s.has(r.id)),t=e.filter(r=>!o.has(r.id)),l=[];for(const r of n){const c=s.get(r.id);if(!c)continue;const a=[];c.title!==r.title&&a.push({field:"title",from:c.title,to:r.title}),(c.status||"")!==(r.status||"")&&a.push({field:"status",from:c.status||"\u2014",to:r.status||"\u2014"}),a.length&&l.push({id:r.id,changes:a})}return{added:u,removed:t,changed:l}}function F(e){return e.removed.length>0?"major":e.added.length>0?"minor":e.changed.length>0?"patch":"none"}function D(e,n,s,o,u){const t=[],l=n==="major"?"\u{1F534}":n==="minor"?"\u{1F7E1}":n==="patch"?"\u{1F7E2}":"\u2705",r=n==="none"?"No capability changes":`${n.toUpperCase()} bump recommended`;if(t.push("## \u{1F525} infernoflow \u2014 Capability Analysis"),t.push(""),t.push(`${l} **${r}**${n!=="none"?` \xB7 \`${o}\` \u2192 \`${u}\``:""}`),t.push(""),!(e.added.length||e.removed.length||e.changed.length))t.push(`> No capability changes detected since \`${s}\`. Contract is in sync.`);else{if(t.push("| Change | Count |"),t.push("|--------|-------|"),e.added.length&&t.push(`| \u2795 Added | ${e.added.length} |`),e.removed.length&&t.push(`| \u274C Removed | ${e.removed.length} |`),e.changed.length&&t.push(`| \u270F\uFE0F Modified | ${e.changed.length} |`),t.push(""),e.added.length){t.push(`<details><summary>\u2795 Added capabilities (${e.added.length})</summary>`),t.push("");for(const a of e.added)t.push(`- \`${a.id}\` \u2014 ${a.title}`);t.push(""),t.push("</details>"),t.push("")}if(e.removed.length){t.push(`<details><summary>\u274C Removed capabilities (${e.removed.length}) \u2014 breaking change</summary>`),t.push("");for(const a of e.removed)t.push(`- \`${a.id}\` \u2014 ${a.title}`);t.push(""),t.push("</details>"),t.push("")}if(e.changed.length){t.push(`<details><summary>\u270F\uFE0F Modified capabilities (${e.changed.length})</summary>`),t.push("");for(const a of e.changed){t.push(`- \`${a.id}\``);for(const d of a.changes)t.push(` - ${d.field}: \`${d.from}\` \u2192 \`${d.to}\``)}t.push(""),t.push("</details>"),t.push("")}}return n==="major"?(t.push("> \u26A0\uFE0F **Breaking change detected.** Capabilities were removed. Consider a major version bump."),t.push("> Run `infernoflow version --apply` to update `package.json`.")):n==="minor"&&(t.push("> \u2139\uFE0F New capabilities added. A minor version bump is recommended."),t.push("> Run `infernoflow version --apply` to update `package.json`.")),t.push(""),t.push("---"),t.push(`<sub>Generated by [infernoflow](https://github.com/ronmiz/infernoflow) \xB7 compared against \`${s}\`</sub>`),t.join(`
|
|
2
|
+
`)}function C(e,n,s,o){return new Promise((u,t)=>{const l=s?JSON.stringify(s):null,r=E.request({hostname:"api.github.com",path:n,method:e,headers:{Authorization:`Bearer ${o}`,Accept:"application/vnd.github+json","Content-Type":"application/json","User-Agent":"infernoflow-cli",...l?{"Content-Length":Buffer.byteLength(l)}:{}}},c=>{let a="";c.on("data",d=>a+=d),c.on("end",()=>{try{u({status:c.statusCode,body:JSON.parse(a)})}catch{u({status:c.statusCode,body:a})}})});r.on("error",t),l&&r.write(l),r.end()})}async function M(e,n,s){const o=await C("GET",`/repos/${e}/issues/${n}/comments?per_page=100`,null,s);return o.status!==200||!Array.isArray(o.body)?null:o.body.find(u=>u.body&&u.body.includes("\u{1F525} infernoflow \u2014 Capability Analysis"))||null}async function q(e,n,s,o){const u=await M(e,n,o);return u?C("PATCH",`/repos/${e}/issues/comments/${u.id}`,{body:s},o):C("POST",`/repos/${e}/issues/${n}/comments`,{body:s},o)}function V(){const e=process.env.GITHUB_EVENT_PATH;if(!e||!h.existsSync(e))return null;try{const n=JSON.parse(h.readFileSync(e,"utf8"));return n.pull_request?.number||n.number||null}catch{return null}}function L(e,n){const s=(e||"0.0.0").split(".").map(Number);return n==="major"?(s[0]++,s[1]=0,s[2]=0):n==="minor"?(s[1]++,s[2]=0):n==="patch"&&s[2]++,s.join(".")}function Y(e){const n=x.join(e,"package.json");if(!h.existsSync(n))return"0.0.0";try{return JSON.parse(h.readFileSync(n,"utf8")).version||"0.0.0"}catch{return"0.0.0"}}async function ne(e){const n=e.slice(1),s=n.includes("--dry-run"),o=n.includes("--json"),u=n.indexOf("--pr"),t=n.indexOf("--repo"),l=n.indexOf("--token"),r=n.indexOf("--ref"),c=process.cwd(),a=x.join(c,"inferno");o||J("infernoflow pr-comment");const d=l!==-1?n[l+1]:process.env.GITHUB_TOKEN,$=t!==-1?n[t+1]:process.env.GITHUB_REPOSITORY,b=u!==-1?parseInt(n[u+1],10):V();let p=r!==-1?n[r+1]:null;if(p||(p=process.env.GITHUB_BASE_REF?`origin/${process.env.GITHUB_BASE_REF}`:null),p||(p=I(c)),p||(p=N("git rev-parse HEAD~1",c)?"HEAD~1":null),!h.existsSync(a)){const i="inferno/ not found \u2014 run: infernoflow init";o&&(console.log(JSON.stringify({ok:!1,error:i})),process.exit(1)),g(i),process.exit(1)}if(!s&&!d){const i="No GitHub token found. Set GITHUB_TOKEN env var or use --token";o&&(console.log(JSON.stringify({ok:!1,error:i})),process.exit(1)),g(i),process.exit(1)}if(!s&&!$){const i="No repository found. Set GITHUB_REPOSITORY env var or use --repo owner/repo";o&&(console.log(JSON.stringify({ok:!1,error:i})),process.exit(1)),g(i),process.exit(1)}if(!s&&!b){const i="No PR number found. Use --pr <number> or run in GitHub Actions on pull_request event";o&&(console.log(JSON.stringify({ok:!1,error:i})),process.exit(1)),g(i),process.exit(1)}const O=_(a),k=p?G(p,c):null;if(!O){const i="No capabilities.json or contract.json found in inferno/";o&&(console.log(JSON.stringify({ok:!1,error:i})),process.exit(1)),g(i),process.exit(1)}const j=U(k||[],O),m=F(j),y=Y(c),v=m!=="none"?L(y,m):y,S=D(j,m,p||"HEAD",y,v);if(s){o?console.log(JSON.stringify({ok:!0,dryRun:!0,bump:m,currentVersion:y,nextVersion:v,comment:S})):(console.log(),w("DRY RUN \u2014 comment that would be posted:"),console.log(),console.log(S),console.log());return}o||w(`Posting to ${A($)} PR #${b}...`);try{const i=await q($,b,S,d);if(i.status===200||i.status===201){const f=i.body?.html_url||"";o?console.log(JSON.stringify({ok:!0,bump:m,currentVersion:y,nextVersion:v,prNumber:b,repo:$,commentUrl:f})):(B(`Comment posted \u2192 ${H(f||`PR #${b}`)}`),m!=="none"&&(console.log(),w(`Recommended bump: ${A(m.toUpperCase())} ${y} \u2192 ${v}`)),console.log())}else{const f=`GitHub API error ${i.status}: ${JSON.stringify(i.body)}`;o?console.log(JSON.stringify({ok:!1,error:f})):g(f),process.exit(1)}}catch(i){const f=`Failed to post comment: ${i.message}`;o?console.log(JSON.stringify({ok:!1,error:f})):g(f),process.exit(1)}}export{ne as prCommentCommand};
|
|
@@ -1,157 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { header, section, ok, warn, fail, gray, cyan, yellow } from "../ui/output.mjs";
|
|
5
|
-
|
|
6
|
-
const CODE_PREFIXES = ["src/", "frontend/", "backend/", "app/", "pages/", "components/", "lib/", "api/", "server/", "Controllers/"];
|
|
7
|
-
|
|
8
|
-
function sh(cmd) {
|
|
9
|
-
return execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] }).toString("utf8").trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readJson(filePath, fallback = null) {
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
15
|
-
} catch {
|
|
16
|
-
return fallback;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function readFile(filePath, fallback = "") {
|
|
21
|
-
try {
|
|
22
|
-
return fs.readFileSync(filePath, "utf8");
|
|
23
|
-
} catch {
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getChangedFiles(base, head) {
|
|
29
|
-
const out = base && head
|
|
30
|
-
? sh(`git diff --name-only ${base}..${head}`)
|
|
31
|
-
: sh("git diff --name-only HEAD");
|
|
32
|
-
return out ? out.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function buildCapabilityHints(cwd) {
|
|
36
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
37
|
-
const contract = readJson(path.join(infernoDir, "contract.json"), { capabilities: [] });
|
|
38
|
-
const registry = readJson(path.join(infernoDir, "capabilities.json"), { capabilities: [] });
|
|
39
|
-
const titleById = new Map((registry.capabilities || []).map((c) => [c.id, c.title || c.id]));
|
|
40
|
-
return (contract.capabilities || []).map((id) => {
|
|
41
|
-
const title = titleById.get(id) || id;
|
|
42
|
-
const keywords = new Set(
|
|
43
|
-
`${id} ${title}`
|
|
44
|
-
.replace(/([A-Z])/g, " $1")
|
|
45
|
-
.toLowerCase()
|
|
46
|
-
.split(/[^a-z0-9]+/)
|
|
47
|
-
.filter((k) => k.length >= 4)
|
|
48
|
-
);
|
|
49
|
-
return { id, title, keywords: Array.from(keywords) };
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function inferImpactedCapabilities(cwd, changedCodeFiles) {
|
|
54
|
-
const hints = buildCapabilityHints(cwd);
|
|
55
|
-
const impacted = [];
|
|
56
|
-
for (const hint of hints) {
|
|
57
|
-
const matched = [];
|
|
58
|
-
for (const rel of changedCodeFiles) {
|
|
59
|
-
const abs = path.join(cwd, rel);
|
|
60
|
-
const text = readFile(abs, "").toLowerCase();
|
|
61
|
-
if (!text) continue;
|
|
62
|
-
if (hint.keywords.some((k) => text.includes(k))) {
|
|
63
|
-
matched.push(rel);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (matched.length) {
|
|
67
|
-
impacted.push({ id: hint.id, title: hint.title, matchedFiles: matched.slice(0, 5) });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return impacted;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function prImpactCommand(args = []) {
|
|
74
|
-
const asJson = args.includes("--json");
|
|
75
|
-
const cwd = process.cwd();
|
|
76
|
-
const base = process.env.BASE_SHA || null;
|
|
77
|
-
const head = process.env.HEAD_SHA || null;
|
|
78
|
-
|
|
79
|
-
let changedFiles = [];
|
|
80
|
-
try {
|
|
81
|
-
changedFiles = getChangedFiles(base, head);
|
|
82
|
-
} catch {
|
|
83
|
-
const payload = { ok: true, skipped: true, reason: "no_git_available" };
|
|
84
|
-
if (asJson) {
|
|
85
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
header("pr-impact");
|
|
89
|
-
warn("git not available; cannot compute PR impact");
|
|
90
|
-
console.log();
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const changedCodeFiles = changedFiles.filter((f) => CODE_PREFIXES.some((p) => f.startsWith(p)));
|
|
95
|
-
const changedInfernoFiles = changedFiles.filter((f) => f.startsWith("inferno/"));
|
|
96
|
-
const impactedCapabilities = inferImpactedCapabilities(cwd, changedCodeFiles);
|
|
97
|
-
const inferredBehaviorChange = changedCodeFiles.length > 0;
|
|
98
|
-
const missingInfernoUpdate = inferredBehaviorChange && changedInfernoFiles.length === 0;
|
|
99
|
-
const confidence = impactedCapabilities.length > 0 ? "high" : inferredBehaviorChange ? "medium" : "low";
|
|
100
|
-
const reasonCodes = [];
|
|
101
|
-
if (inferredBehaviorChange) reasonCodes.push("CODE_CHANGED");
|
|
102
|
-
if (missingInfernoUpdate) reasonCodes.push("INFERNO_NOT_UPDATED");
|
|
103
|
-
if (impactedCapabilities.length > 0) reasonCodes.push("CAPABILITY_HINT_MATCH");
|
|
104
|
-
if (!reasonCodes.length) reasonCodes.push("NO_BEHAVIOR_SIGNAL");
|
|
105
|
-
|
|
106
|
-
const payload = {
|
|
107
|
-
ok: !missingInfernoUpdate,
|
|
108
|
-
base: base || "HEAD",
|
|
109
|
-
head: head || "WORKTREE",
|
|
110
|
-
changedFiles,
|
|
111
|
-
changedCodeFiles,
|
|
112
|
-
changedInfernoFiles,
|
|
113
|
-
inferredBehaviorChange,
|
|
114
|
-
impactedCapabilities,
|
|
115
|
-
confidence,
|
|
116
|
-
reasonCodes,
|
|
117
|
-
recommendations: missingInfernoUpdate
|
|
118
|
-
? ["Run infernoflow suggest \"describe behavior change\" and update inferno/", "Run infernoflow check --json"]
|
|
119
|
-
: ["Run infernoflow check --json to validate final state"],
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
if (asJson) {
|
|
123
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
124
|
-
process.exit(payload.ok ? 0 : 1);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
header("pr-impact");
|
|
128
|
-
|
|
129
|
-
section("Diff Scope");
|
|
130
|
-
ok(`Changed files: ${cyan(String(changedFiles.length))}`);
|
|
131
|
-
ok(`Code files: ${cyan(String(changedCodeFiles.length))}`);
|
|
132
|
-
ok(`Inferno files: ${cyan(String(changedInfernoFiles.length))}`);
|
|
133
|
-
|
|
134
|
-
section("Capability Impact");
|
|
135
|
-
if (impactedCapabilities.length === 0) {
|
|
136
|
-
warn("No capability hints matched changed code files");
|
|
137
|
-
} else {
|
|
138
|
-
impactedCapabilities.forEach((c) => {
|
|
139
|
-
console.log(` ${cyan("•")} ${c.id} ${gray(`(${c.title})`)}`);
|
|
140
|
-
c.matchedFiles.slice(0, 3).forEach((f) => console.log(` ${gray("- " + f)}`));
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
section("Doc Sync");
|
|
145
|
-
if (missingInfernoUpdate) {
|
|
146
|
-
fail("Code changed but inferno/ was not updated", "Run infernoflow suggest and then infernoflow check");
|
|
147
|
-
} else {
|
|
148
|
-
ok("No immediate inferno drift signal from changed files");
|
|
149
|
-
}
|
|
150
|
-
ok(`Confidence: ${cyan(confidence)}`);
|
|
151
|
-
|
|
152
|
-
section("Suggested Next");
|
|
153
|
-
payload.recommendations.forEach((r) => console.log(` ${yellow("→")} ${r}`));
|
|
154
|
-
console.log();
|
|
155
|
-
process.exit(payload.ok ? 0 : 1);
|
|
156
|
-
}
|
|
157
|
-
|
|
1
|
+
import{execSync as k}from"node:child_process";import*as S from"node:fs";import*as m from"node:path";import{header as w,section as y,ok as d,warn as E,fail as N,gray as $,cyan as g,yellow as _}from"../ui/output.mjs";const D=["src/","frontend/","backend/","app/","pages/","components/","lib/","api/","server/","Controllers/"];function A(n){return k(n,{stdio:["ignore","pipe","pipe"]}).toString("utf8").trim()}function I(n,t=null){try{return JSON.parse(S.readFileSync(n,"utf8"))}catch{return t}}function F(n,t=""){try{return S.readFileSync(n,"utf8")}catch{return t}}function H(n,t){const a=A(n&&t?`git diff --name-only ${n}..${t}`:"git diff --name-only HEAD");return a?a.split(`
|
|
2
|
+
`).map(r=>r.trim()).filter(Boolean):[]}function O(n){const t=m.join(n,"inferno"),a=I(m.join(t,"contract.json"),{capabilities:[]}),r=I(m.join(t,"capabilities.json"),{capabilities:[]}),c=new Map((r.capabilities||[]).map(e=>[e.id,e.title||e.id]));return(a.capabilities||[]).map(e=>{const i=c.get(e)||e,l=new Set(`${e} ${i}`.replace(/([A-Z])/g," $1").toLowerCase().split(/[^a-z0-9]+/).filter(s=>s.length>=4));return{id:e,title:i,keywords:Array.from(l)}})}function R(n,t){const a=O(n),r=[];for(const c of a){const e=[];for(const i of t){const l=m.join(n,i),s=F(l,"").toLowerCase();s&&c.keywords.some(f=>s.includes(f))&&e.push(i)}e.length&&r.push({id:c.id,title:c.title,matchedFiles:e.slice(0,5)})}return r}async function x(n=[]){const t=n.includes("--json"),a=process.cwd(),r=process.env.BASE_SHA||null,c=process.env.HEAD_SHA||null;let e=[];try{e=H(r,c)}catch{const o={ok:!0,skipped:!0,reason:"no_git_available"};if(t){console.log(JSON.stringify(o,null,2));return}w("pr-impact"),E("git not available; cannot compute PR impact"),console.log();return}const i=e.filter(o=>D.some(C=>o.startsWith(C))),l=e.filter(o=>o.startsWith("inferno/")),s=R(a,i),f=i.length>0,h=f&&l.length===0,b=s.length>0?"high":f?"medium":"low",p=[];f&&p.push("CODE_CHANGED"),h&&p.push("INFERNO_NOT_UPDATED"),s.length>0&&p.push("CAPABILITY_HINT_MATCH"),p.length||p.push("NO_BEHAVIOR_SIGNAL");const u={ok:!h,base:r||"HEAD",head:c||"WORKTREE",changedFiles:e,changedCodeFiles:i,changedInfernoFiles:l,inferredBehaviorChange:f,impactedCapabilities:s,confidence:b,reasonCodes:p,recommendations:h?['Run infernoflow suggest "describe behavior change" and update inferno/',"Run infernoflow check --json"]:["Run infernoflow check --json to validate final state"]};t&&(console.log(JSON.stringify(u,null,2)),process.exit(u.ok?0:1)),w("pr-impact"),y("Diff Scope"),d(`Changed files: ${g(String(e.length))}`),d(`Code files: ${g(String(i.length))}`),d(`Inferno files: ${g(String(l.length))}`),y("Capability Impact"),s.length===0?E("No capability hints matched changed code files"):s.forEach(o=>{console.log(` ${g("\u2022")} ${o.id} ${$(`(${o.title})`)}`),o.matchedFiles.slice(0,3).forEach(C=>console.log(` ${$("- "+C)}`))}),y("Doc Sync"),h?N("Code changed but inferno/ was not updated","Run infernoflow suggest and then infernoflow check"):d("No immediate inferno drift signal from changed files"),d(`Confidence: ${g(b)}`),y("Suggested Next"),u.recommendations.forEach(o=>console.log(` ${_("\u2192")} ${o}`)),console.log(),process.exit(u.ok?0:1)}export{x as prImpactCommand};
|