@flumecode/runner 0.5.0 → 0.6.0
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/dist/cli.js +119 -36
- package/package.json +1 -1
- package/skills-plugin/skills/create-release/SKILL.md +176 -0
package/dist/cli.js
CHANGED
|
@@ -572,6 +572,33 @@ function buildRepairPrompt(ctx, hookLog) {
|
|
|
572
572
|
];
|
|
573
573
|
return lines.join("\n");
|
|
574
574
|
}
|
|
575
|
+
function buildReleasePrompt(ctx) {
|
|
576
|
+
const task = `Use the \`flumecode:create-release\` skill to handle this turn. You are driving a release: first analyse commits since the last tag, propose version bumps, and ask the user to confirm via widgets (Phase 1); once the user's widget answers appear in the thread, apply the bumps to package.json files and update CHANGELOG.md (Phase 2). Do NOT commit or push \u2014 the runner handles that and opens the bump PR.`;
|
|
577
|
+
const orient = `Before investigating raw source, check for a FlumeCode wiki at \`.flumecode/wiki/\`. If it exists, read \`.flumecode/wiki/README.md\` first \u2014 it is the index \u2014 and follow its links to the pages and source paths relevant to this release. If there is no wiki, work from the code directly.`;
|
|
578
|
+
const widgets = `When you need the user to choose, ask it as a widget rather than writing the options as prose: call \`single_select\` for a one-of-N choice (radio buttons) or \`multi_select\` for a "select all that apply" choice (checkboxes). Don't add your own "Other" option \u2014 the UI always provides one. After calling a widget tool, end your turn \u2014 the user's answer comes back as their next message and starts a fresh run.`;
|
|
579
|
+
const lines = [
|
|
580
|
+
`You are "${ctx.agentName}", an autonomous coding agent driving a FlumeCode release.`,
|
|
581
|
+
`The repository ${ctx.repo.fullName} is checked out in your current working directory on the release bump branch "${ctx.repo.checkoutBranch}".`,
|
|
582
|
+
task,
|
|
583
|
+
orient,
|
|
584
|
+
widgets,
|
|
585
|
+
"",
|
|
586
|
+
"These coding guidelines apply to all code produced in this run:",
|
|
587
|
+
"",
|
|
588
|
+
loadRule("coding-guideline"),
|
|
589
|
+
"",
|
|
590
|
+
`# Release: ${ctx.request?.title ?? ""}`
|
|
591
|
+
];
|
|
592
|
+
if (ctx.request?.body) {
|
|
593
|
+
lines.push("", ctx.request.body);
|
|
594
|
+
}
|
|
595
|
+
appendThread(lines, ctx);
|
|
596
|
+
lines.push(
|
|
597
|
+
"",
|
|
598
|
+
"Your final reply is posted verbatim as your comment in the release thread \u2014 if you called widgets (Phase 1), your reply text accompanies the questions; if you applied the bumps (Phase 2), make it the report the skill produced. The runner appends the pull-request link."
|
|
599
|
+
);
|
|
600
|
+
return lines.join("\n");
|
|
601
|
+
}
|
|
575
602
|
function buildInitPrompt(ctx) {
|
|
576
603
|
return [
|
|
577
604
|
`You are "${ctx.agentName}" initializing FlumeCode for the repository ${ctx.repo.fullName}, checked out in your current working directory.`,
|
|
@@ -761,14 +788,15 @@ async function rebaseOntoMergeBranch(ctx, dir) {
|
|
|
761
788
|
if (!mergeBranch) return;
|
|
762
789
|
await git(["-C", dir, "fetch", "--quiet", "origin", mergeBranch]);
|
|
763
790
|
try {
|
|
764
|
-
await git(["-C", dir, "rebase", "FETCH_HEAD"]);
|
|
765
|
-
} catch {
|
|
791
|
+
await git(["-C", dir, "rebase", "--empty=drop", "FETCH_HEAD"]);
|
|
792
|
+
} catch (err) {
|
|
766
793
|
const conflicted = await git(["-C", dir, "diff", "--name-only", "--diff-filter=U"]).catch(
|
|
767
794
|
() => ({ stdout: "" })
|
|
768
795
|
);
|
|
769
796
|
const files = conflicted.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
770
797
|
await git(["-C", dir, "rebase", "--abort"]).catch(() => {
|
|
771
798
|
});
|
|
799
|
+
if (files.length === 0) throw err;
|
|
772
800
|
throw new RebaseConflictError(mergeBranch, files);
|
|
773
801
|
}
|
|
774
802
|
}
|
|
@@ -854,11 +882,44 @@ var HEARTBEAT_MS = 5 * 6e4;
|
|
|
854
882
|
async function pushAndOpenPr(ctx, dir, abort, opts = { rebase: true }) {
|
|
855
883
|
if (abort.signal.aborted) throw new Error("Run canceled by user");
|
|
856
884
|
const committed = await commitWithRepair(ctx, dir, abort);
|
|
857
|
-
if (!committed) return { kind: "none" };
|
|
858
|
-
|
|
885
|
+
if (!committed) return { outcome: { kind: "none" }, autoMerged: false };
|
|
886
|
+
let autoMerged = false;
|
|
887
|
+
if (opts.rebase) {
|
|
888
|
+
try {
|
|
889
|
+
await rebaseOntoMergeBranch(ctx, dir);
|
|
890
|
+
} catch (err) {
|
|
891
|
+
if (!(err instanceof RebaseConflictError)) throw err;
|
|
892
|
+
if (abort.signal.aborted) throw new Error("Run canceled by user");
|
|
893
|
+
console.warn(
|
|
894
|
+
` rebase onto ${ctx.repo.mergeBranch} conflicted \u2014 merging it in and resolving with the agent\u2026`
|
|
895
|
+
);
|
|
896
|
+
await mergeAndResolveConflicts(ctx, dir, abort);
|
|
897
|
+
await commitWithRepair(ctx, dir, abort);
|
|
898
|
+
autoMerged = true;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
859
901
|
await pushBranch(ctx, dir);
|
|
860
902
|
const pr = await openPullRequest(ctx);
|
|
861
|
-
return pr ? { kind: "pr", pr } : { kind: "pushed" };
|
|
903
|
+
return { outcome: pr ? { kind: "pr", pr } : { kind: "pushed" }, autoMerged };
|
|
904
|
+
}
|
|
905
|
+
async function mergeAndResolveConflicts(ctx, dir, abort) {
|
|
906
|
+
const { conflicted } = await mergeInMergeBranch(ctx, dir);
|
|
907
|
+
if (!conflicted) return { resolved: false, text: null };
|
|
908
|
+
const result = await runClaudeCode({
|
|
909
|
+
cwd: dir,
|
|
910
|
+
prompt: buildResolvePrompt(ctx),
|
|
911
|
+
permissionMode: ctx.permissionMode,
|
|
912
|
+
model: ORCHESTRATOR_MODEL,
|
|
913
|
+
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
914
|
+
abortController: abort
|
|
915
|
+
});
|
|
916
|
+
const unresolved = await listUnmergedPaths(dir);
|
|
917
|
+
if (unresolved.length > 0) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
`Could not fully resolve the merge \u2014 ${unresolved.length} file(s) still conflict: ${unresolved.join(", ")}`
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return { resolved: true, text: result.text.trim() || null };
|
|
862
923
|
}
|
|
863
924
|
async function commitWithRepair(ctx, dir, abort) {
|
|
864
925
|
for (let attempt = 1; ; attempt++) {
|
|
@@ -882,17 +943,18 @@ async function commitWithRepair(ctx, dir, abort) {
|
|
|
882
943
|
}
|
|
883
944
|
function outcomeBanner(outcome, opts) {
|
|
884
945
|
const wikiNote = opts.documented ? " (with wiki updates)" : "";
|
|
946
|
+
const mergeNote = opts.autoMerged ? "\n\u{1F500} The base branch had advanced with conflicting changes \u2014 merged it in and resolved the conflicts automatically." : "";
|
|
885
947
|
switch (outcome.kind) {
|
|
886
948
|
case "pr":
|
|
887
949
|
return `
|
|
888
950
|
|
|
889
951
|
---
|
|
890
|
-
\u2705 Opened pull request from \`${opts.branch}\`${wikiNote} \xB7 [View pull request](${outcome.pr.url})`;
|
|
952
|
+
\u2705 Opened pull request from \`${opts.branch}\`${wikiNote} \xB7 [View pull request](${outcome.pr.url})${mergeNote}`;
|
|
891
953
|
case "pushed":
|
|
892
954
|
return `
|
|
893
955
|
|
|
894
956
|
---
|
|
895
|
-
\u26A0\uFE0F Pushed \`${opts.branch}\`${wikiNote}, but couldn't open a pull request (no diff against the base branch, or one is already open)
|
|
957
|
+
\u26A0\uFE0F Pushed \`${opts.branch}\`${wikiNote}, but couldn't open a pull request (no diff against the base branch, or one is already open).${mergeNote}`;
|
|
896
958
|
case "none":
|
|
897
959
|
return `
|
|
898
960
|
|
|
@@ -924,6 +986,11 @@ async function processJob(ctx, abort = new AbortController()) {
|
|
|
924
986
|
prepared = true;
|
|
925
987
|
return await processResolveJob(ctx, dir, abort);
|
|
926
988
|
}
|
|
989
|
+
if (ctx.kind === "release") {
|
|
990
|
+
const { resumed } = await prepareResumingBranch(ctx, dir, reused);
|
|
991
|
+
prepared = true;
|
|
992
|
+
return await processReleaseJob(ctx, dir, resumed, abort);
|
|
993
|
+
}
|
|
927
994
|
await prepareAtSha(ctx, dir, reused);
|
|
928
995
|
prepared = true;
|
|
929
996
|
return await processChatJob(ctx, dir, abort);
|
|
@@ -947,10 +1014,11 @@ async function processInitJob(ctx, dir, abort) {
|
|
|
947
1014
|
abortController: abort
|
|
948
1015
|
})).text.trim();
|
|
949
1016
|
let reply = summary || "(the agent produced no summary)";
|
|
950
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort);
|
|
1017
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort);
|
|
951
1018
|
reply += outcomeBanner(outcome, {
|
|
952
1019
|
branch: ctx.repo.checkoutBranch,
|
|
953
|
-
noChange: "no files were generated; the wiki may already exist."
|
|
1020
|
+
noChange: "no files were generated; the wiki may already exist.",
|
|
1021
|
+
autoMerged
|
|
954
1022
|
});
|
|
955
1023
|
return { text: reply, widgets: [] };
|
|
956
1024
|
}
|
|
@@ -996,8 +1064,8 @@ async function processChatJob(ctx, dir, abort) {
|
|
|
996
1064
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
997
1065
|
}
|
|
998
1066
|
}
|
|
999
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort);
|
|
1000
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1067
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort);
|
|
1068
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1001
1069
|
return { text: reply, widgets: [] };
|
|
1002
1070
|
}
|
|
1003
1071
|
async function processImplementJob(ctx, dir, resumed, abort) {
|
|
@@ -1034,8 +1102,8 @@ async function processImplementJob(ctx, dir, resumed, abort) {
|
|
|
1034
1102
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
1035
1103
|
}
|
|
1036
1104
|
}
|
|
1037
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1038
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1105
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1106
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1039
1107
|
return { text: reply, widgets: [], ...outcome.kind === "pr" ? { pr: outcome.pr } : {} };
|
|
1040
1108
|
}
|
|
1041
1109
|
async function processReviseJob(ctx, dir, resumed, abort) {
|
|
@@ -1078,9 +1146,9 @@ async function processReviseJob(ctx, dir, resumed, abort) {
|
|
|
1078
1146
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
1079
1147
|
}
|
|
1080
1148
|
}
|
|
1081
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1149
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1082
1150
|
if (outcome.kind !== "none") {
|
|
1083
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1151
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1084
1152
|
}
|
|
1085
1153
|
return {
|
|
1086
1154
|
text: reply,
|
|
@@ -1093,27 +1161,8 @@ async function processResolveJob(ctx, dir, abort) {
|
|
|
1093
1161
|
console.log(`
|
|
1094
1162
|
\u25B6 Resolve ${ctx.jobId} \u2014 ${ctx.repo.fullName}: "${jobTitle(ctx)}"`);
|
|
1095
1163
|
const installResult = await installDependencies(dir);
|
|
1096
|
-
const {
|
|
1097
|
-
let reply
|
|
1098
|
-
if (conflicted) {
|
|
1099
|
-
const result = await runClaudeCode({
|
|
1100
|
-
cwd: dir,
|
|
1101
|
-
prompt: buildResolvePrompt(ctx),
|
|
1102
|
-
permissionMode: ctx.permissionMode,
|
|
1103
|
-
model: ORCHESTRATOR_MODEL,
|
|
1104
|
-
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
1105
|
-
abortController: abort
|
|
1106
|
-
});
|
|
1107
|
-
reply = result.text.trim() || "(the agent produced no report)";
|
|
1108
|
-
const unresolved = await listUnmergedPaths(dir);
|
|
1109
|
-
if (unresolved.length > 0) {
|
|
1110
|
-
throw new Error(
|
|
1111
|
-
`Could not fully resolve the merge \u2014 ${unresolved.length} file(s) still conflict: ${unresolved.join(", ")}`
|
|
1112
|
-
);
|
|
1113
|
-
}
|
|
1114
|
-
} else {
|
|
1115
|
-
reply = `Merged \`${ctx.repo.mergeBranch ?? "the merge branch"}\` into \`${ctx.repo.checkoutBranch}\` cleanly \u2014 there were no conflicts to resolve.`;
|
|
1116
|
-
}
|
|
1164
|
+
const { resolved, text } = await mergeAndResolveConflicts(ctx, dir, abort);
|
|
1165
|
+
let reply = resolved ? text || "(the agent produced no report)" : `Merged \`${ctx.repo.mergeBranch ?? "the merge branch"}\` into \`${ctx.repo.checkoutBranch}\` cleanly \u2014 there were no conflicts to resolve.`;
|
|
1117
1166
|
if (installResult.status === "failed") {
|
|
1118
1167
|
reply += `
|
|
1119
1168
|
|
|
@@ -1127,6 +1176,40 @@ async function processResolveJob(ctx, dir, abort) {
|
|
|
1127
1176
|
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch });
|
|
1128
1177
|
return { text: reply, widgets: [], ...pr ? { pr } : {} };
|
|
1129
1178
|
}
|
|
1179
|
+
async function processReleaseJob(ctx, dir, resumed, abort) {
|
|
1180
|
+
console.log(`
|
|
1181
|
+
\u25B6 Release ${ctx.jobId} \u2014 ${ctx.repo.fullName}: "${jobTitle(ctx)}"`);
|
|
1182
|
+
const installResult = await installDependencies(dir);
|
|
1183
|
+
const result = await runClaudeCode({
|
|
1184
|
+
cwd: dir,
|
|
1185
|
+
prompt: buildReleasePrompt(ctx),
|
|
1186
|
+
permissionMode: ctx.permissionMode,
|
|
1187
|
+
model: ORCHESTRATOR_MODEL,
|
|
1188
|
+
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
1189
|
+
abortController: abort
|
|
1190
|
+
});
|
|
1191
|
+
let reply = result.text.trim() || "(the agent produced no reply)";
|
|
1192
|
+
if (installResult.status === "failed") {
|
|
1193
|
+
reply += `
|
|
1194
|
+
|
|
1195
|
+
> \u26A0\uFE0F Dependencies failed to install (\`${installResult.manager}\`); tests may not have run.`;
|
|
1196
|
+
}
|
|
1197
|
+
if (result.widgets.length > 0) {
|
|
1198
|
+
console.log(
|
|
1199
|
+
` \u2026release ${ctx.jobId} posted ${result.widgets.length} widget(s); awaiting reply`
|
|
1200
|
+
);
|
|
1201
|
+
return { text: reply, widgets: result.widgets };
|
|
1202
|
+
}
|
|
1203
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1204
|
+
if (outcome.kind !== "none") {
|
|
1205
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, autoMerged });
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
text: reply,
|
|
1209
|
+
widgets: [],
|
|
1210
|
+
...outcome.kind === "pr" ? { pr: outcome.pr } : {}
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1130
1213
|
async function heartbeat(config) {
|
|
1131
1214
|
const health = await checkClaudeCode();
|
|
1132
1215
|
if (health.ready) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-release
|
|
3
|
+
description: >-
|
|
4
|
+
Draft release notes and version suggestions for a release, then (after the
|
|
5
|
+
user confirms) open a bump PR that updates package.json version(s) and
|
|
6
|
+
CHANGELOG.md. Two-turn flow: first turn asks the user to confirm versions via
|
|
7
|
+
widgets; second turn (answers in thread) writes the bumps and opens the PR.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# create-release
|
|
11
|
+
|
|
12
|
+
You are driving a FlumeCode release. This skill has two distinct phases; detect
|
|
13
|
+
which one applies before acting.
|
|
14
|
+
|
|
15
|
+
## Phase detection
|
|
16
|
+
|
|
17
|
+
Check the thread (`# Conversation so far` in the prompt). If **no widget answers**
|
|
18
|
+
appear in any prior agent turn, this is **Phase 1** — propose versions and ask.
|
|
19
|
+
If a prior turn contains widget-answer selections (the user picked a version), this
|
|
20
|
+
is **Phase 2** — apply the bumps and report.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Phase 1 — Propose versions and ask
|
|
25
|
+
|
|
26
|
+
### 1. Find the last release tags
|
|
27
|
+
|
|
28
|
+
For `@flumecode/web`, find the most recent `v*` tag:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
git tag -l --sort=-version:refname 'v*' | head -1
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For `@flumecode/runner`, find the most recent `runner-v*` tag:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
git tag -l --sort=-version:refname 'runner-v*' | head -1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If no matching tag exists for a package, treat the current version in that
|
|
41
|
+
package's `package.json` as the baseline.
|
|
42
|
+
|
|
43
|
+
### 2. List commits since the last tag, split by package
|
|
44
|
+
|
|
45
|
+
Get all commits since the tag (or all commits if no tag):
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
git log <lastWebTag>..HEAD --oneline
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Then split by what they touch:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
# web + shared packages
|
|
55
|
+
git log <lastWebTag>..HEAD --oneline -- apps/web/ packages/
|
|
56
|
+
|
|
57
|
+
# runner only
|
|
58
|
+
git log <lastRunnerTag>..HEAD --oneline -- apps/runner/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If a package has no commits, mark it **unchanged** — do not bump it.
|
|
62
|
+
|
|
63
|
+
### 3. Infer bump level per package
|
|
64
|
+
|
|
65
|
+
For each package that has commits:
|
|
66
|
+
|
|
67
|
+
- `feat:` in a commit subject, or any breaking change → **minor** (or major if
|
|
68
|
+
explicitly breaking).
|
|
69
|
+
- `fix:`, `chore:`, `docs:`, `refactor:`, `test:` → **patch**.
|
|
70
|
+
- When in doubt, default to **patch**.
|
|
71
|
+
|
|
72
|
+
### 4. Compute suggested next versions
|
|
73
|
+
|
|
74
|
+
Read current versions from `apps/web/package.json` and `apps/runner/package.json`.
|
|
75
|
+
Apply the inferred bump (semver: patch = last digit +1, minor = middle digit +1 +
|
|
76
|
+
reset patch to 0). For unchanged packages, keep the current version.
|
|
77
|
+
|
|
78
|
+
### 5. Draft concise release notes
|
|
79
|
+
|
|
80
|
+
3–10 user-readable bullet points summarising what changed. Group by theme; skip
|
|
81
|
+
internal chore commits unless they affect the user.
|
|
82
|
+
|
|
83
|
+
### 6. Ask the user to confirm
|
|
84
|
+
|
|
85
|
+
For **each package that has commits** (skip unchanged ones), call
|
|
86
|
+
`mcp__flume_widgets__single_select` with:
|
|
87
|
+
|
|
88
|
+
- `question`: e.g. `@flumecode/web version for this release?`
|
|
89
|
+
- `options`: the suggested next version, plus the two sibling semver levels (e.g.
|
|
90
|
+
if patch suggested: also offer minor and major), plus the current version as
|
|
91
|
+
"No change (keep X.Y.Z)".
|
|
92
|
+
|
|
93
|
+
Also present the drafted release notes in your reply text so the user can read
|
|
94
|
+
them, then call `mcp__flume_widgets__single_select` for a confirmatory question:
|
|
95
|
+
`Do the release notes look correct?` with options `Yes, use these notes` and
|
|
96
|
+
`I'll edit them in the PR`.
|
|
97
|
+
|
|
98
|
+
**After calling widgets, end your turn.** Do NOT open a PR in Phase 1.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Phase 2 — Apply the bumps and report
|
|
103
|
+
|
|
104
|
+
### 1. Read the widget answers
|
|
105
|
+
|
|
106
|
+
The user's confirmed version selections appear in the `# Conversation so far`
|
|
107
|
+
thread as agent messages (the widget-answer turn). Extract the chosen version for
|
|
108
|
+
each package from those selections.
|
|
109
|
+
|
|
110
|
+
### 2. Update package.json files
|
|
111
|
+
|
|
112
|
+
For each package whose version changed, edit the `"version"` field in:
|
|
113
|
+
|
|
114
|
+
- `apps/web/package.json` — for `@flumecode/web`
|
|
115
|
+
- `apps/runner/package.json` — for `@flumecode/runner`
|
|
116
|
+
|
|
117
|
+
Change only the `"version"` line; do not reformat the file.
|
|
118
|
+
|
|
119
|
+
### 3. Update CHANGELOG.md
|
|
120
|
+
|
|
121
|
+
Edit (or create) `CHANGELOG.md` at the repo root. Insert a new section at the
|
|
122
|
+
top, below any existing `# Changelog` title line:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
## [X.Y.Z / runner-X.Y.Z] - YYYY-MM-DD
|
|
126
|
+
|
|
127
|
+
- Bullet point from release notes
|
|
128
|
+
- Another bullet point
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use the ISO date format (`YYYY-MM-DD`). Preserve all existing entries — do not
|
|
132
|
+
delete or rewrite prior sections.
|
|
133
|
+
|
|
134
|
+
If both packages are bumped, list both versions in the heading (e.g.
|
|
135
|
+
`## [0.9.0 / runner-0.5.0] - 2026-06-06`). If only one package is bumped, list
|
|
136
|
+
only that version in the heading.
|
|
137
|
+
|
|
138
|
+
### 4. Stop — do not commit or push
|
|
139
|
+
|
|
140
|
+
Leave the edited files in the working tree. The runner commits them and opens the
|
|
141
|
+
pull request.
|
|
142
|
+
|
|
143
|
+
### 5. End with this exact report format
|
|
144
|
+
|
|
145
|
+
Your final message must match this shape (adjust versions and files to match what
|
|
146
|
+
actually changed):
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
**Bumped versions:**
|
|
150
|
+
- `@flumecode/web`: `0.8.0` → `0.9.0`
|
|
151
|
+
- `@flumecode/runner`: `0.4.0` → `0.5.0`
|
|
152
|
+
|
|
153
|
+
**CHANGELOG updated** with release notes.
|
|
154
|
+
|
|
155
|
+
**Files changed:** `apps/web/package.json`, `apps/runner/package.json`, `CHANGELOG.md`
|
|
156
|
+
|
|
157
|
+
<!-- flumecode:versions {"@flumecode/web":"0.9.0","@flumecode/runner":"0.5.0"} -->
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The `<!-- flumecode:versions {...} -->` comment is machine-readable and **must be
|
|
161
|
+
the last line** of your message body (before any trailing newline). The runner
|
|
162
|
+
reads it to persist the confirmed versions on the release entity. Use the exact
|
|
163
|
+
JSON key names `@flumecode/web` and `@flumecode/runner`; omit a package if its
|
|
164
|
+
version did not change.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Notes
|
|
169
|
+
|
|
170
|
+
- **Runner-only bump:** if only `apps/runner/` has commits, bump only
|
|
171
|
+
`apps/runner/package.json`. Leave `apps/web/package.json` unchanged.
|
|
172
|
+
- **Clear Phase 1 text:** be explicit about what changed since the last tag so the
|
|
173
|
+
user can confidently confirm or override your suggestions.
|
|
174
|
+
- **Never edit** any file other than `apps/web/package.json`,
|
|
175
|
+
`apps/runner/package.json`, and `CHANGELOG.md`.
|
|
176
|
+
- **Never commit, push, or open a PR** — the runner does that.
|