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,594 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
function
|
|
27
|
-
try {
|
|
28
|
-
return execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function lastTag(cwd) {
|
|
35
|
-
return capture("git describe --tags --abbrev=0", cwd);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function commitsSince(ref, cwd) {
|
|
39
|
-
// Returns array of { hash, subject, body }
|
|
40
|
-
// Use NUL-delimited output to avoid shell escaping issues with separators
|
|
41
|
-
const range = ref ? `${ref}..HEAD` : "";
|
|
42
|
-
const raw = capture(`git log ${range} --format=%H%x1f%s%x1f%b%x1e`, cwd);
|
|
43
|
-
if (!raw) return [];
|
|
44
|
-
|
|
45
|
-
return raw
|
|
46
|
-
.split("\x1e")
|
|
47
|
-
.map(s => s.trim())
|
|
48
|
-
.filter(Boolean)
|
|
49
|
-
.map(block => {
|
|
50
|
-
const parts = block.split("\x1f");
|
|
51
|
-
return {
|
|
52
|
-
hash: (parts[0] || "").trim().slice(0, 8),
|
|
53
|
-
subject: (parts[1] || "").trim(),
|
|
54
|
-
body: (parts[2] || "").trim(),
|
|
55
|
-
};
|
|
56
|
-
})
|
|
57
|
-
.filter(c => c.subject);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function refDate(ref, cwd) {
|
|
61
|
-
return capture(`git log -1 --format=%ci "${ref}"`, cwd)?.slice(0, 10) || null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ── commit classification ────────────────────────────────────────────────────
|
|
65
|
-
|
|
66
|
-
const TYPE_MAP = {
|
|
67
|
-
feat: "Added",
|
|
68
|
-
feature: "Added",
|
|
69
|
-
add: "Added",
|
|
70
|
-
fix: "Fixed",
|
|
71
|
-
bugfix: "Fixed",
|
|
72
|
-
hotfix: "Fixed",
|
|
73
|
-
perf: "Changed",
|
|
74
|
-
refactor: "Changed",
|
|
75
|
-
change: "Changed",
|
|
76
|
-
chore: "Changed",
|
|
77
|
-
docs: "Changed",
|
|
78
|
-
style: "Changed",
|
|
79
|
-
test: "Changed",
|
|
80
|
-
ci: "Changed",
|
|
81
|
-
remove: "Removed",
|
|
82
|
-
revert: "Removed",
|
|
83
|
-
deprecate:"Removed",
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
function classifyCommit(subject) {
|
|
87
|
-
// Conventional commits: "feat: ..." or "feat(scope): ..."
|
|
88
|
-
const match = subject.match(/^(\w+)(?:\([^)]+\))?[!]?:\s*(.+)/);
|
|
89
|
-
if (match) {
|
|
90
|
-
const type = match[1].toLowerCase();
|
|
91
|
-
return {
|
|
92
|
-
section: TYPE_MAP[type] || "Changed",
|
|
93
|
-
message: match[2].trim(),
|
|
94
|
-
breaking: subject.includes("!"),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Heuristic: starts with a keyword
|
|
99
|
-
const lower = subject.toLowerCase();
|
|
100
|
-
for (const [keyword, section] of Object.entries(TYPE_MAP)) {
|
|
101
|
-
if (lower.startsWith(keyword + " ") || lower.startsWith(keyword + ":")) {
|
|
102
|
-
return { section, message: subject, breaking: false };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { section: "Changed", message: subject, breaking: false };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function groupCommits(commits) {
|
|
110
|
-
const sections = { Added: [], Fixed: [], Changed: [], Removed: [], Breaking: [] };
|
|
111
|
-
|
|
112
|
-
for (const c of commits) {
|
|
113
|
-
const { section, message, breaking } = classifyCommit(c.subject);
|
|
114
|
-
if (breaking) sections.Breaking.push(message);
|
|
115
|
-
sections[section].push(message);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Remove duplicates (some commits end up in Breaking AND their category)
|
|
119
|
-
for (const key of Object.keys(sections)) {
|
|
120
|
-
sections[key] = [...new Set(sections[key])];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return sections;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ── markdown rendering ───────────────────────────────────────────────────────
|
|
127
|
-
|
|
128
|
-
function renderUnreleased(sections, ref) {
|
|
129
|
-
const lines = ["## Unreleased", ""];
|
|
130
|
-
|
|
131
|
-
if (ref) {
|
|
132
|
-
lines.push(`> Changes since ${ref}`, "");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const ORDER = ["Breaking", "Added", "Fixed", "Changed", "Removed"];
|
|
136
|
-
let hasContent = false;
|
|
137
|
-
|
|
138
|
-
for (const heading of ORDER) {
|
|
139
|
-
const items = sections[heading];
|
|
140
|
-
if (!items || !items.length) continue;
|
|
141
|
-
hasContent = true;
|
|
142
|
-
lines.push(`### ${heading}`);
|
|
143
|
-
for (const item of items) {
|
|
144
|
-
lines.push(`- ${item}`);
|
|
145
|
-
}
|
|
146
|
-
lines.push("");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (!hasContent) {
|
|
150
|
-
lines.push("- No significant changes", "");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return lines.join("\n");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ── CHANGELOG file operations ────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
function readChangelog(changelogPath) {
|
|
159
|
-
if (!fs.existsSync(changelogPath)) return null;
|
|
160
|
-
return fs.readFileSync(changelogPath, "utf8");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function extractUnreleased(text) {
|
|
164
|
-
// Returns the content of the ## Unreleased block, or null
|
|
165
|
-
const match = text.match(/^## Unreleased[\s\S]*?(?=\n## |\n---|\z)/im);
|
|
166
|
-
return match ? match[0].trim() : null;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function injectUnreleased(text, newBlock) {
|
|
170
|
-
// Replace existing ## Unreleased block
|
|
171
|
-
if (/^## Unreleased/im.test(text)) {
|
|
172
|
-
return text.replace(
|
|
173
|
-
/^## Unreleased[\s\S]*?(?=\n## |\n---)/im,
|
|
174
|
-
newBlock + "\n\n"
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// No existing Unreleased block — insert after the first # heading
|
|
179
|
-
if (/^# .+/im.test(text)) {
|
|
180
|
-
return text.replace(
|
|
181
|
-
/^(# .+\n)/im,
|
|
182
|
-
`$1\n${newBlock}\n\n`
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Prepend
|
|
187
|
-
return `${newBlock}\n\n${text}`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function appendToUnreleased(text, newBlock) {
|
|
191
|
-
// Extract just the bullet lines from newBlock and append to existing Unreleased
|
|
192
|
-
const newLines = newBlock.split("\n").filter(l => l.startsWith("- ")).join("\n");
|
|
193
|
-
if (!newLines) return text;
|
|
194
|
-
|
|
195
|
-
if (/^## Unreleased/im.test(text)) {
|
|
196
|
-
// Find end of Unreleased and insert before next section
|
|
197
|
-
return text.replace(
|
|
198
|
-
/(^## Unreleased[\s\S]*?)(\n## )/im,
|
|
199
|
-
`$1\n${newLines}\n$2`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return injectUnreleased(text, newBlock);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ── sub-commands ─────────────────────────────────────────────────────────────
|
|
207
|
-
|
|
208
|
-
function subcmdList(cwd, ref) {
|
|
209
|
-
const tag = ref || lastTag(cwd);
|
|
210
|
-
const commits = commitsSince(tag, cwd);
|
|
211
|
-
|
|
212
|
-
if (!commits.length) {
|
|
213
|
-
info(`No commits since ${tag || "beginning"}`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
console.log(`\n ${bold("Commits since")} ${cyan(tag || "beginning")} ${gray("(" + commits.length + ")")}\n`);
|
|
218
|
-
for (const c of commits) {
|
|
219
|
-
const { section } = classifyCommit(c.subject);
|
|
220
|
-
const color = section === "Added" ? green : section === "Fixed" ? yellow : gray;
|
|
221
|
-
console.log(` ${gray(c.hash)} ${color(c.subject)}`);
|
|
222
|
-
}
|
|
223
|
-
console.log();
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function subcmdShow(changelogPath) {
|
|
227
|
-
const text = readChangelog(changelogPath);
|
|
228
|
-
if (!text) {
|
|
229
|
-
fail("CHANGELOG.md not found");
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const block = extractUnreleased(text);
|
|
233
|
-
if (!block) {
|
|
234
|
-
warn("No ## Unreleased section found in CHANGELOG.md");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
console.log("\n" + block + "\n");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function subcmdUpdate(cwd, changelogPath, opts) {
|
|
241
|
-
const { ref, dryRun, append, asJson } = opts;
|
|
242
|
-
|
|
243
|
-
const tag = ref || lastTag(cwd);
|
|
244
|
-
const commits = commitsSince(tag, cwd);
|
|
245
|
-
|
|
246
|
-
if (!commits.length) {
|
|
247
|
-
if (asJson) {
|
|
248
|
-
console.log(JSON.stringify({ ok: true, ref: tag, commits: 0, message: "No new commits" }));
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
warn(`No commits found since ${tag || "beginning of repo"}`);
|
|
252
|
-
console.log();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const sections = groupCommits(commits);
|
|
257
|
-
const newBlock = renderUnreleased(sections, tag);
|
|
258
|
-
|
|
259
|
-
if (asJson) {
|
|
260
|
-
console.log(JSON.stringify({
|
|
261
|
-
ok: true,
|
|
262
|
-
ref: tag,
|
|
263
|
-
commits: commits.length,
|
|
264
|
-
sections: {
|
|
265
|
-
breaking: sections.Breaking,
|
|
266
|
-
added: sections.Added,
|
|
267
|
-
fixed: sections.Fixed,
|
|
268
|
-
changed: sections.Changed,
|
|
269
|
-
removed: sections.Removed,
|
|
270
|
-
},
|
|
271
|
-
markdown: newBlock,
|
|
272
|
-
}, null, 2));
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Print the drafted block
|
|
277
|
-
console.log();
|
|
278
|
-
console.log(gray(" ─── Drafted entry ─────────────────────────────────"));
|
|
279
|
-
newBlock.split("\n").forEach(l => console.log(" " + l));
|
|
280
|
-
console.log(gray(" ────────────────────────────────────────────────────"));
|
|
281
|
-
console.log();
|
|
282
|
-
info(`${commits.length} commit${commits.length > 1 ? "s" : ""} since ${cyan(tag || "beginning")}`);
|
|
283
|
-
|
|
284
|
-
if (dryRun) {
|
|
285
|
-
warn("Dry run — CHANGELOG.md not modified");
|
|
286
|
-
console.log();
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Write to CHANGELOG.md
|
|
291
|
-
let text = readChangelog(changelogPath);
|
|
292
|
-
if (!text) {
|
|
293
|
-
// Create a fresh changelog
|
|
294
|
-
text = `# Changelog\n\n`;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const updated = append ? appendToUnreleased(text, newBlock) : injectUnreleased(text, newBlock);
|
|
298
|
-
fs.writeFileSync(changelogPath, updated);
|
|
299
|
-
|
|
300
|
-
ok(`CHANGELOG.md updated ${gray("(" + (append ? "appended" : "replaced") + " ## Unreleased)")}`);
|
|
301
|
-
console.log();
|
|
302
|
-
done("Changelog drafted — review and edit before your next release");
|
|
303
|
-
console.log(` Run ${cyan("infernoflow publish")} when ready to cut the release\n`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// ── AI changelog writer ───────────────────────────────────────────────────────
|
|
307
|
-
|
|
308
|
-
function parseCaps(jsonText) {
|
|
309
|
-
if (!jsonText) return [];
|
|
310
|
-
try {
|
|
311
|
-
const obj = JSON.parse(jsonText);
|
|
312
|
-
const raw = obj.capabilities || [];
|
|
313
|
-
return raw.map(c => typeof c === "string" ? { id: c, title: c } : c);
|
|
314
|
-
} catch { return []; }
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function loadCapsFromDisk(infernoDir) {
|
|
318
|
-
for (const name of ["capabilities.json", "contract.json"]) {
|
|
319
|
-
const p = path.join(infernoDir, name);
|
|
320
|
-
if (fs.existsSync(p)) return parseCaps(fs.readFileSync(p, "utf8"));
|
|
321
|
-
}
|
|
322
|
-
return [];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function loadCapsAtRef(ref, cwd) {
|
|
326
|
-
try {
|
|
327
|
-
for (const name of ["capabilities.json", "contract.json"]) {
|
|
328
|
-
try {
|
|
329
|
-
const content = execSync(`git show "${ref}:inferno/${name}"`, {
|
|
330
|
-
cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]
|
|
331
|
-
}).trim();
|
|
332
|
-
if (content) return parseCaps(content);
|
|
333
|
-
} catch {}
|
|
334
|
-
}
|
|
335
|
-
} catch {}
|
|
336
|
-
return [];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function diffCapsSimple(before, after) {
|
|
340
|
-
const beforeMap = new Map(before.map(c => [c.id, c]));
|
|
341
|
-
const afterMap = new Map(after.map(c => [c.id, c]));
|
|
342
|
-
return {
|
|
343
|
-
added: after.filter(c => !beforeMap.has(c.id)),
|
|
344
|
-
removed: before.filter(c => !afterMap.has(c.id)),
|
|
345
|
-
changed: after.filter(c => {
|
|
346
|
-
const old = beforeMap.get(c.id);
|
|
347
|
-
return old && old.title !== c.title;
|
|
348
|
-
}),
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function buildAiPrompt(commits, diff, ref, version) {
|
|
353
|
-
const lines = [];
|
|
354
|
-
lines.push(`You are writing a changelog entry for a software release.`);
|
|
355
|
-
lines.push(`Write it in a friendly, clear, developer-facing tone.`);
|
|
356
|
-
lines.push(`Use Markdown. Start with ## ${version || "Unreleased"}`);
|
|
357
|
-
lines.push(`Include sections: ### Added, ### Fixed, ### Changed, ### Removed (only if non-empty).`);
|
|
358
|
-
lines.push(`Be concise — one line per item. Do not include commit hashes.`);
|
|
359
|
-
lines.push(`Do not invent features that are not listed below.`);
|
|
360
|
-
lines.push(``);
|
|
361
|
-
lines.push(`## Capability changes since ${ref || "last release"}:`);
|
|
362
|
-
if (diff.added.length) lines.push(`Added: ${diff.added.map(c => c.title || c.id).join(", ")}`);
|
|
363
|
-
if (diff.removed.length) lines.push(`Removed: ${diff.removed.map(c => c.title || c.id).join(", ")}`);
|
|
364
|
-
if (diff.changed.length) lines.push(`Changed: ${diff.changed.map(c => c.title || c.id).join(", ")}`);
|
|
365
|
-
if (!diff.added.length && !diff.removed.length && !diff.changed.length) lines.push(`(no capability changes)`);
|
|
366
|
-
lines.push(``);
|
|
367
|
-
lines.push(`## Git commits since ${ref || "last release"}:`);
|
|
368
|
-
for (const c of commits.slice(0, 40)) lines.push(`- ${c.subject}`);
|
|
369
|
-
lines.push(``);
|
|
370
|
-
lines.push(`Write the changelog entry now:`);
|
|
371
|
-
return lines.join("\n");
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function buildStructuredFallback(commits, diff, version) {
|
|
375
|
-
// High-quality template fallback when no AI provider is available
|
|
376
|
-
const sections = groupCommits(commits);
|
|
377
|
-
|
|
378
|
-
// Merge capability changes into sections
|
|
379
|
-
for (const c of diff.added) sections.Added.unshift(`${c.title || c.id} capability`);
|
|
380
|
-
for (const c of diff.removed) sections.Removed.unshift(`${c.title || c.id} capability`);
|
|
381
|
-
for (const c of diff.changed) sections.Changed.unshift(`Updated ${c.title || c.id} capability`);
|
|
382
|
-
|
|
383
|
-
// Deduplicate
|
|
384
|
-
for (const key of Object.keys(sections)) sections[key] = [...new Set(sections[key])];
|
|
385
|
-
|
|
386
|
-
const tag = version || "Unreleased";
|
|
387
|
-
const date = new Date().toISOString().slice(0, 10);
|
|
388
|
-
const lines = [`## ${tag} — ${date}`, ""];
|
|
389
|
-
|
|
390
|
-
const ORDER = ["Breaking", "Added", "Fixed", "Changed", "Removed"];
|
|
391
|
-
for (const heading of ORDER) {
|
|
392
|
-
const items = sections[heading];
|
|
393
|
-
if (!items?.length) continue;
|
|
394
|
-
lines.push(`### ${heading}`);
|
|
395
|
-
for (const item of items) lines.push(`- ${item}`);
|
|
396
|
-
lines.push("");
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return lines.join("\n");
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async function callLocalAi(prompt) {
|
|
403
|
-
// Try Ollama (localhost:11434) — most common local AI setup
|
|
404
|
-
try {
|
|
405
|
-
const { default: http } = await import("node:http");
|
|
406
|
-
return new Promise((resolve, reject) => {
|
|
407
|
-
const body = JSON.stringify({ model: "llama3", prompt, stream: false });
|
|
408
|
-
const req = http.request({
|
|
409
|
-
hostname: "localhost", port: 11434,
|
|
410
|
-
path: "/api/generate", method: "POST",
|
|
411
|
-
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
412
|
-
}, (res) => {
|
|
413
|
-
let raw = "";
|
|
414
|
-
res.on("data", d => raw += d);
|
|
415
|
-
res.on("end", () => {
|
|
416
|
-
try { resolve(JSON.parse(raw).response || null); } catch { resolve(null); }
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
req.setTimeout(30_000, () => { req.destroy(); reject(new Error("timeout")); });
|
|
420
|
-
req.on("error", reject);
|
|
421
|
-
req.write(body);
|
|
422
|
-
req.end();
|
|
423
|
-
});
|
|
424
|
-
} catch { return null; }
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async function callAnthropicAi(prompt, apiKey) {
|
|
428
|
-
const { default: https } = await import("node:https");
|
|
429
|
-
return new Promise((resolve, reject) => {
|
|
430
|
-
const body = JSON.stringify({
|
|
431
|
-
model: "claude-haiku-4-5-20251001",
|
|
432
|
-
max_tokens: 1024,
|
|
433
|
-
messages: [{ role: "user", content: prompt }],
|
|
434
|
-
});
|
|
435
|
-
const req = https.request({
|
|
436
|
-
hostname: "api.anthropic.com",
|
|
437
|
-
path: "/v1/messages", method: "POST",
|
|
438
|
-
headers: {
|
|
439
|
-
"x-api-key": apiKey,
|
|
440
|
-
"anthropic-version": "2023-06-01",
|
|
441
|
-
"Content-Type": "application/json",
|
|
442
|
-
"Content-Length": Buffer.byteLength(body),
|
|
443
|
-
},
|
|
444
|
-
}, (res) => {
|
|
445
|
-
let raw = "";
|
|
446
|
-
res.on("data", d => raw += d);
|
|
447
|
-
res.on("end", () => {
|
|
448
|
-
try {
|
|
449
|
-
const data = JSON.parse(raw);
|
|
450
|
-
resolve(data.content?.[0]?.text || null);
|
|
451
|
-
} catch { resolve(null); }
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
req.setTimeout(30_000, () => { req.destroy(); reject(new Error("timeout")); });
|
|
455
|
-
req.on("error", reject);
|
|
456
|
-
req.write(body);
|
|
457
|
-
req.end();
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async function subcmdAi(cwd, changelogPath, opts) {
|
|
462
|
-
const { ref, dryRun, asJson, version } = opts;
|
|
463
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
464
|
-
|
|
465
|
-
const tag = ref || lastTag(cwd);
|
|
466
|
-
const commits = commitsSince(tag, cwd);
|
|
467
|
-
|
|
468
|
-
// Load capability diff
|
|
469
|
-
const currentCaps = fs.existsSync(infernoDir) ? loadCapsFromDisk(infernoDir) : [];
|
|
470
|
-
const prevCaps = tag ? (() => {
|
|
471
|
-
try {
|
|
472
|
-
const ex = require("child_process").execSync;
|
|
473
|
-
for (const name of ["capabilities.json", "contract.json"]) {
|
|
474
|
-
try {
|
|
475
|
-
const c = ex(`git show "${tag}:inferno/${name}"`, { cwd, encoding: "utf8", stdio: ["ignore","pipe","pipe"] }).trim();
|
|
476
|
-
if (c) return parseCaps(c);
|
|
477
|
-
} catch {}
|
|
478
|
-
}
|
|
479
|
-
return [];
|
|
480
|
-
} catch { return []; }
|
|
481
|
-
})() : [];
|
|
482
|
-
|
|
483
|
-
const diff = diffCapsSimple(prevCaps, currentCaps);
|
|
484
|
-
|
|
485
|
-
if (!asJson) {
|
|
486
|
-
info(`Generating AI changelog since ${bold(tag || "beginning")}...`);
|
|
487
|
-
info(`${commits.length} commits · ${diff.added.length} added · ${diff.removed.length} removed · ${diff.changed.length} changed`);
|
|
488
|
-
console.log();
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const prompt = buildAiPrompt(commits, diff, tag, version);
|
|
492
|
-
let aiText = null;
|
|
493
|
-
let provider = "template";
|
|
494
|
-
|
|
495
|
-
// Try AI providers in order: Anthropic API → Ollama → structured template
|
|
496
|
-
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
497
|
-
if (anthropicKey) {
|
|
498
|
-
try {
|
|
499
|
-
aiText = await callAnthropicAi(prompt, anthropicKey);
|
|
500
|
-
if (aiText) provider = "anthropic";
|
|
501
|
-
} catch {}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (!aiText) {
|
|
505
|
-
try {
|
|
506
|
-
aiText = await callLocalAi(prompt);
|
|
507
|
-
if (aiText) provider = "ollama";
|
|
508
|
-
} catch {}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Fallback: structured template
|
|
512
|
-
if (!aiText) {
|
|
513
|
-
aiText = buildStructuredFallback(commits, diff, version);
|
|
514
|
-
provider = "template";
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (asJson) {
|
|
518
|
-
console.log(JSON.stringify({ ok: true, provider, ref: tag, commits: commits.length, markdown: aiText }));
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
console.log(gray(" ─── Generated changelog ──────────────────────────────"));
|
|
523
|
-
aiText.split("\n").forEach(l => console.log(" " + l));
|
|
524
|
-
console.log(gray(" ──────────────────────────────────────────────────────"));
|
|
525
|
-
console.log();
|
|
526
|
-
if (provider === "template") {
|
|
527
|
-
console.log(` ${yellow("💡")} ${gray("For AI-written changelogs:")} ${cyan("infernoflow ai setup")}`);
|
|
528
|
-
} else {
|
|
529
|
-
info(`Generated via: ${bold(provider)}`);
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
if (dryRun) {
|
|
533
|
-
warn("Dry run — CHANGELOG.md not modified");
|
|
534
|
-
console.log();
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// Write to CHANGELOG.md
|
|
539
|
-
let text = fs.existsSync(changelogPath) ? fs.readFileSync(changelogPath, "utf8") : "# Changelog\n\n";
|
|
540
|
-
const updated = injectUnreleased(text, aiText);
|
|
541
|
-
fs.writeFileSync(changelogPath, updated);
|
|
542
|
-
|
|
543
|
-
ok(`CHANGELOG.md updated with AI-generated entry`);
|
|
544
|
-
if (provider === "template") {
|
|
545
|
-
info(`Tip: set ANTHROPIC_API_KEY for richer AI-written changelogs`);
|
|
546
|
-
}
|
|
547
|
-
console.log();
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// ── main ─────────────────────────────────────────────────────────────────────
|
|
551
|
-
|
|
552
|
-
export async function changelogCommand(rawArgs) {
|
|
553
|
-
const args = rawArgs.slice(1); // drop "changelog"
|
|
554
|
-
|
|
555
|
-
// Sub-command: first non-flag arg
|
|
556
|
-
const sub = args.find(a => !a.startsWith("-")) || "update";
|
|
557
|
-
|
|
558
|
-
const dryRun = args.includes("--dry-run");
|
|
559
|
-
const append = args.includes("--append");
|
|
560
|
-
const asJson = args.includes("--json");
|
|
561
|
-
|
|
562
|
-
const refIdx = args.indexOf("--ref");
|
|
563
|
-
const versionIdx = args.indexOf("--version");
|
|
564
|
-
const ref = refIdx !== -1 ? args[refIdx + 1] : null;
|
|
565
|
-
const version = versionIdx !== -1 ? args[versionIdx + 1] : null;
|
|
566
|
-
|
|
567
|
-
const cwd = process.cwd();
|
|
568
|
-
const changelogPath = path.join(cwd, "CHANGELOG.md");
|
|
569
|
-
|
|
570
|
-
if (!asJson) header("changelog " + sub);
|
|
571
|
-
|
|
572
|
-
if (sub === "list") {
|
|
573
|
-
subcmdList(cwd, ref);
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (sub === "show") {
|
|
578
|
-
subcmdShow(changelogPath);
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (sub === "update") {
|
|
583
|
-
await subcmdUpdate(cwd, changelogPath, { ref, dryRun, append, asJson });
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (sub === "ai") {
|
|
588
|
-
await subcmdAi(cwd, changelogPath, { ref, dryRun, asJson, version });
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
fail(`Unknown sub-command: ${sub}`, "Use: update | show | list | ai");
|
|
593
|
-
process.exit(1);
|
|
594
|
-
}
|
|
1
|
+
import*as h from"node:fs";import*as A from"node:path";import{execSync as U}from"node:child_process";import{header as _,ok as G,fail as E,warn as b,info as y,done as B,bold as S,cyan as w,gray as p,green as M,yellow as F}from"../ui/output.mjs";function v(n,e){try{return U(n,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim()}catch{return null}}function x(n){return v("git describe --tags --abbrev=0",n)}function k(n,e){const o=n?`${n}..HEAD`:"",i=v(`git log ${o} --format=%H%x1f%s%x1f%b%x1e`,e);return i?i.split("").map(t=>t.trim()).filter(Boolean).map(t=>{const s=t.split("");return{hash:(s[0]||"").trim().slice(0,8),subject:(s[1]||"").trim(),body:(s[2]||"").trim()}}).filter(t=>t.subject):[]}function re(n,e){return v(`git log -1 --format=%ci "${n}"`,e)?.slice(0,10)||null}const L={feat:"Added",feature:"Added",add:"Added",fix:"Fixed",bugfix:"Fixed",hotfix:"Fixed",perf:"Changed",refactor:"Changed",change:"Changed",chore:"Changed",docs:"Changed",style:"Changed",test:"Changed",ci:"Changed",remove:"Removed",revert:"Removed",deprecate:"Removed"};function D(n){const e=n.match(/^(\w+)(?:\([^)]+\))?[!]?:\s*(.+)/);if(e){const i=e[1].toLowerCase();return{section:L[i]||"Changed",message:e[2].trim(),breaking:n.includes("!")}}const o=n.toLowerCase();for(const[i,t]of Object.entries(L))if(o.startsWith(i+" ")||o.startsWith(i+":"))return{section:t,message:n,breaking:!1};return{section:"Changed",message:n,breaking:!1}}function I(n){const e={Added:[],Fixed:[],Changed:[],Removed:[],Breaking:[]};for(const o of n){const{section:i,message:t,breaking:s}=D(o.subject);s&&e.Breaking.push(t),e[i].push(t)}for(const o of Object.keys(e))e[o]=[...new Set(e[o])];return e}function W(n,e){const o=["## Unreleased",""];e&&o.push(`> Changes since ${e}`,"");const i=["Breaking","Added","Fixed","Changed","Removed"];let t=!1;for(const s of i){const a=n[s];if(!(!a||!a.length)){t=!0,o.push(`### ${s}`);for(const c of a)o.push(`- ${c}`);o.push("")}}return t||o.push("- No significant changes",""),o.join(`
|
|
2
|
+
`)}function T(n){return h.existsSync(n)?h.readFileSync(n,"utf8"):null}function q(n){const e=n.match(/^## Unreleased[\s\S]*?(?=\n## |\n---|\z)/im);return e?e[0].trim():null}function O(n,e){return/^## Unreleased/im.test(n)?n.replace(/^## Unreleased[\s\S]*?(?=\n## |\n---)/im,e+`
|
|
3
|
+
|
|
4
|
+
`):/^# .+/im.test(n)?n.replace(/^(# .+\n)/im,`$1
|
|
5
|
+
${e}
|
|
6
|
+
|
|
7
|
+
`):`${e}
|
|
8
|
+
|
|
9
|
+
${n}`}function Y(n,e){const o=e.split(`
|
|
10
|
+
`).filter(i=>i.startsWith("- ")).join(`
|
|
11
|
+
`);return o?/^## Unreleased/im.test(n)?n.replace(/(^## Unreleased[\s\S]*?)(\n## )/im,`$1
|
|
12
|
+
${o}
|
|
13
|
+
$2`):O(n,e):n}function K(n,e){const o=e||x(n),i=k(o,n);if(!i.length){y(`No commits since ${o||"beginning"}`);return}console.log(`
|
|
14
|
+
${S("Commits since")} ${w(o||"beginning")} ${p("("+i.length+")")}
|
|
15
|
+
`);for(const t of i){const{section:s}=D(t.subject),a=s==="Added"?M:s==="Fixed"?F:p;console.log(` ${p(t.hash)} ${a(t.subject)}`)}console.log()}function z(n){const e=T(n);if(!e){E("CHANGELOG.md not found");return}const o=q(e);if(!o){b("No ## Unreleased section found in CHANGELOG.md");return}console.log(`
|
|
16
|
+
`+o+`
|
|
17
|
+
`)}async function Q(n,e,o){const{ref:i,dryRun:t,append:s,asJson:a}=o,c=i||x(n),r=k(c,n);if(!r.length){if(a){console.log(JSON.stringify({ok:!0,ref:c,commits:0,message:"No new commits"}));return}b(`No commits found since ${c||"beginning of repo"}`),console.log();return}const d=I(r),l=W(d,c);if(a){console.log(JSON.stringify({ok:!0,ref:c,commits:r.length,sections:{breaking:d.Breaking,added:d.Added,fixed:d.Fixed,changed:d.Changed,removed:d.Removed},markdown:l},null,2));return}if(console.log(),console.log(p(" \u2500\u2500\u2500 Drafted entry \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")),l.split(`
|
|
18
|
+
`).forEach($=>console.log(" "+$)),console.log(p(" \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")),console.log(),y(`${r.length} commit${r.length>1?"s":""} since ${w(c||"beginning")}`),t){b("Dry run \u2014 CHANGELOG.md not modified"),console.log();return}let u=T(e);u||(u=`# Changelog
|
|
19
|
+
|
|
20
|
+
`);const g=s?Y(u,l):O(u,l);h.writeFileSync(e,g),G(`CHANGELOG.md updated ${p("("+(s?"appended":"replaced")+" ## Unreleased)")}`),console.log(),B("Changelog drafted \u2014 review and edit before your next release"),console.log(` Run ${w("infernoflow publish")} when ready to cut the release
|
|
21
|
+
`)}function j(n){if(!n)return[];try{return(JSON.parse(n).capabilities||[]).map(i=>typeof i=="string"?{id:i,title:i}:i)}catch{return[]}}function V(n){for(const e of["capabilities.json","contract.json"]){const o=A.join(n,e);if(h.existsSync(o))return j(h.readFileSync(o,"utf8"))}return[]}function ae(n,e){try{for(const o of["capabilities.json","contract.json"])try{const i=U(`git show "${n}:inferno/${o}"`,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();if(i)return j(i)}catch{}}catch{}return[]}function X(n,e){const o=new Map(n.map(t=>[t.id,t])),i=new Map(e.map(t=>[t.id,t]));return{added:e.filter(t=>!o.has(t.id)),removed:n.filter(t=>!i.has(t.id)),changed:e.filter(t=>{const s=o.get(t.id);return s&&s.title!==t.title})}}function Z(n,e,o,i){const t=[];t.push("You are writing a changelog entry for a software release."),t.push("Write it in a friendly, clear, developer-facing tone."),t.push(`Use Markdown. Start with ## ${i||"Unreleased"}`),t.push("Include sections: ### Added, ### Fixed, ### Changed, ### Removed (only if non-empty)."),t.push("Be concise \u2014 one line per item. Do not include commit hashes."),t.push("Do not invent features that are not listed below."),t.push(""),t.push(`## Capability changes since ${o||"last release"}:`),e.added.length&&t.push(`Added: ${e.added.map(s=>s.title||s.id).join(", ")}`),e.removed.length&&t.push(`Removed: ${e.removed.map(s=>s.title||s.id).join(", ")}`),e.changed.length&&t.push(`Changed: ${e.changed.map(s=>s.title||s.id).join(", ")}`),!e.added.length&&!e.removed.length&&!e.changed.length&&t.push("(no capability changes)"),t.push(""),t.push(`## Git commits since ${o||"last release"}:`);for(const s of n.slice(0,40))t.push(`- ${s.subject}`);return t.push(""),t.push("Write the changelog entry now:"),t.join(`
|
|
22
|
+
`)}function ee(n,e,o){const i=I(n);for(const r of e.added)i.Added.unshift(`${r.title||r.id} capability`);for(const r of e.removed)i.Removed.unshift(`${r.title||r.id} capability`);for(const r of e.changed)i.Changed.unshift(`Updated ${r.title||r.id} capability`);for(const r of Object.keys(i))i[r]=[...new Set(i[r])];const t=o||"Unreleased",s=new Date().toISOString().slice(0,10),a=[`## ${t} \u2014 ${s}`,""],c=["Breaking","Added","Fixed","Changed","Removed"];for(const r of c){const d=i[r];if(d?.length){a.push(`### ${r}`);for(const l of d)a.push(`- ${l}`);a.push("")}}return a.join(`
|
|
23
|
+
`)}async function ne(n){try{const{default:e}=await import("node:http");return new Promise((o,i)=>{const t=JSON.stringify({model:"llama3",prompt:n,stream:!1}),s=e.request({hostname:"localhost",port:11434,path:"/api/generate",method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(t)}},a=>{let c="";a.on("data",r=>c+=r),a.on("end",()=>{try{o(JSON.parse(c).response||null)}catch{o(null)}})});s.setTimeout(3e4,()=>{s.destroy(),i(new Error("timeout"))}),s.on("error",i),s.write(t),s.end()})}catch{return null}}async function te(n,e){const{default:o}=await import("node:https");return new Promise((i,t)=>{const s=JSON.stringify({model:"claude-haiku-4-5-20251001",max_tokens:1024,messages:[{role:"user",content:n}]}),a=o.request({hostname:"api.anthropic.com",path:"/v1/messages",method:"POST",headers:{"x-api-key":e,"anthropic-version":"2023-06-01","Content-Type":"application/json","Content-Length":Buffer.byteLength(s)}},c=>{let r="";c.on("data",d=>r+=d),c.on("end",()=>{try{const d=JSON.parse(r);i(d.content?.[0]?.text||null)}catch{i(null)}})});a.setTimeout(3e4,()=>{a.destroy(),t(new Error("timeout"))}),a.on("error",t),a.write(s),a.end()})}async function oe(n,e,o){const{ref:i,dryRun:t,asJson:s,version:a}=o,c=A.join(n,"inferno"),r=i||x(n),d=k(r,n),l=h.existsSync(c)?V(c):[],u=r?(()=>{try{const C=require("child_process").execSync;for(const P of["capabilities.json","contract.json"])try{const R=C(`git show "${r}:inferno/${P}"`,{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();if(R)return j(R)}catch{}return[]}catch{return[]}})():[],g=X(u,l);s||(y(`Generating AI changelog since ${S(r||"beginning")}...`),y(`${d.length} commits \xB7 ${g.added.length} added \xB7 ${g.removed.length} removed \xB7 ${g.changed.length} changed`),console.log());const $=Z(d,g,r,a);let f=null,m="template";const N=process.env.ANTHROPIC_API_KEY;if(N)try{f=await te($,N),f&&(m="anthropic")}catch{}if(!f)try{f=await ne($),f&&(m="ollama")}catch{}if(f||(f=ee(d,g,a),m="template"),s){console.log(JSON.stringify({ok:!0,provider:m,ref:r,commits:d.length,markdown:f}));return}if(console.log(p(" \u2500\u2500\u2500 Generated changelog \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")),f.split(`
|
|
24
|
+
`).forEach(C=>console.log(" "+C)),console.log(p(" \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")),console.log(),m==="template"?console.log(` ${F("\u{1F4A1}")} ${p("For AI-written changelogs:")} ${w("infernoflow ai setup")}`):y(`Generated via: ${S(m)}`),t){b("Dry run \u2014 CHANGELOG.md not modified"),console.log();return}let H=h.existsSync(e)?h.readFileSync(e,"utf8"):`# Changelog
|
|
25
|
+
|
|
26
|
+
`;const J=O(H,f);h.writeFileSync(e,J),G("CHANGELOG.md updated with AI-generated entry"),m==="template"&&y("Tip: set ANTHROPIC_API_KEY for richer AI-written changelogs"),console.log()}async function ce(n){const e=n.slice(1),o=e.find(g=>!g.startsWith("-"))||"update",i=e.includes("--dry-run"),t=e.includes("--append"),s=e.includes("--json"),a=e.indexOf("--ref"),c=e.indexOf("--version"),r=a!==-1?e[a+1]:null,d=c!==-1?e[c+1]:null,l=process.cwd(),u=A.join(l,"CHANGELOG.md");if(s||_("changelog "+o),o==="list"){K(l,r);return}if(o==="show"){z(u);return}if(o==="update"){await Q(l,u,{ref:r,dryRun:i,append:t,asJson:s});return}if(o==="ai"){await oe(l,u,{ref:r,dryRun:i,asJson:s,version:d});return}E(`Unknown sub-command: ${o}`,"Use: update | show | list | ai"),process.exit(1)}export{ce as changelogCommand};
|