@flumecode/runner 0.5.0 → 0.7.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
CHANGED
|
@@ -225,10 +225,16 @@ var SERVER_NAME2 = "flume_plan";
|
|
|
225
225
|
var SUBMIT_PLAN = "submit_plan";
|
|
226
226
|
var PLAN_TOOL_NAME = `mcp__${SERVER_NAME2}__${SUBMIT_PLAN}`;
|
|
227
227
|
var PLAN_MARKER = "<!-- flumecode:end-of-plan -->";
|
|
228
|
+
var pseudoCodeEntrySchema = z2.object({
|
|
229
|
+
file: z2.string().min(1),
|
|
230
|
+
pseudoCode: z2.string().min(1)
|
|
231
|
+
});
|
|
228
232
|
var stepSchema = z2.object({
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
title: z2.string().min(1).describe("A concise imperative title for this step."),
|
|
234
|
+
description: z2.string().min(1).describe("What changes and why \u2014 the rationale for this step."),
|
|
235
|
+
pseudoCode: z2.array(pseudoCodeEntrySchema).optional().describe(
|
|
236
|
+
"Per-file pseudo code. Provide an entry for every non-documentation file this step touches. Each entry contains the file path and pseudo code describing the changes to that file."
|
|
237
|
+
)
|
|
232
238
|
});
|
|
233
239
|
var planInputSchema = {
|
|
234
240
|
title: z2.string().min(1).max(120).describe(
|
|
@@ -260,12 +266,20 @@ function renderPlan(plan) {
|
|
|
260
266
|
}
|
|
261
267
|
}
|
|
262
268
|
lines.push("");
|
|
263
|
-
lines.push("
|
|
269
|
+
lines.push("## Steps");
|
|
264
270
|
for (const [i, step] of plan.steps.entries()) {
|
|
265
|
-
lines.push(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
lines.push("");
|
|
272
|
+
lines.push(`### ${i + 1}. ${step.title}`);
|
|
273
|
+
lines.push("");
|
|
274
|
+
lines.push(step.description);
|
|
275
|
+
if (step.pseudoCode && step.pseudoCode.length > 0) {
|
|
276
|
+
for (const entry of step.pseudoCode) {
|
|
277
|
+
lines.push("");
|
|
278
|
+
lines.push(`\`${entry.file}\``);
|
|
279
|
+
lines.push("");
|
|
280
|
+
lines.push("```");
|
|
281
|
+
lines.push(entry.pseudoCode);
|
|
282
|
+
lines.push("```");
|
|
269
283
|
}
|
|
270
284
|
}
|
|
271
285
|
}
|
|
@@ -572,6 +586,33 @@ function buildRepairPrompt(ctx, hookLog) {
|
|
|
572
586
|
];
|
|
573
587
|
return lines.join("\n");
|
|
574
588
|
}
|
|
589
|
+
function buildReleasePrompt(ctx) {
|
|
590
|
+
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.`;
|
|
591
|
+
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.`;
|
|
592
|
+
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.`;
|
|
593
|
+
const lines = [
|
|
594
|
+
`You are "${ctx.agentName}", an autonomous coding agent driving a FlumeCode release.`,
|
|
595
|
+
`The repository ${ctx.repo.fullName} is checked out in your current working directory on the release bump branch "${ctx.repo.checkoutBranch}".`,
|
|
596
|
+
task,
|
|
597
|
+
orient,
|
|
598
|
+
widgets,
|
|
599
|
+
"",
|
|
600
|
+
"These coding guidelines apply to all code produced in this run:",
|
|
601
|
+
"",
|
|
602
|
+
loadRule("coding-guideline"),
|
|
603
|
+
"",
|
|
604
|
+
`# Release: ${ctx.request?.title ?? ""}`
|
|
605
|
+
];
|
|
606
|
+
if (ctx.request?.body) {
|
|
607
|
+
lines.push("", ctx.request.body);
|
|
608
|
+
}
|
|
609
|
+
appendThread(lines, ctx);
|
|
610
|
+
lines.push(
|
|
611
|
+
"",
|
|
612
|
+
"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."
|
|
613
|
+
);
|
|
614
|
+
return lines.join("\n");
|
|
615
|
+
}
|
|
575
616
|
function buildInitPrompt(ctx) {
|
|
576
617
|
return [
|
|
577
618
|
`You are "${ctx.agentName}" initializing FlumeCode for the repository ${ctx.repo.fullName}, checked out in your current working directory.`,
|
|
@@ -761,14 +802,15 @@ async function rebaseOntoMergeBranch(ctx, dir) {
|
|
|
761
802
|
if (!mergeBranch) return;
|
|
762
803
|
await git(["-C", dir, "fetch", "--quiet", "origin", mergeBranch]);
|
|
763
804
|
try {
|
|
764
|
-
await git(["-C", dir, "rebase", "FETCH_HEAD"]);
|
|
765
|
-
} catch {
|
|
805
|
+
await git(["-C", dir, "rebase", "--empty=drop", "FETCH_HEAD"]);
|
|
806
|
+
} catch (err) {
|
|
766
807
|
const conflicted = await git(["-C", dir, "diff", "--name-only", "--diff-filter=U"]).catch(
|
|
767
808
|
() => ({ stdout: "" })
|
|
768
809
|
);
|
|
769
810
|
const files = conflicted.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
770
811
|
await git(["-C", dir, "rebase", "--abort"]).catch(() => {
|
|
771
812
|
});
|
|
813
|
+
if (files.length === 0) throw err;
|
|
772
814
|
throw new RebaseConflictError(mergeBranch, files);
|
|
773
815
|
}
|
|
774
816
|
}
|
|
@@ -854,11 +896,44 @@ var HEARTBEAT_MS = 5 * 6e4;
|
|
|
854
896
|
async function pushAndOpenPr(ctx, dir, abort, opts = { rebase: true }) {
|
|
855
897
|
if (abort.signal.aborted) throw new Error("Run canceled by user");
|
|
856
898
|
const committed = await commitWithRepair(ctx, dir, abort);
|
|
857
|
-
if (!committed) return { kind: "none" };
|
|
858
|
-
|
|
899
|
+
if (!committed) return { outcome: { kind: "none" }, autoMerged: false };
|
|
900
|
+
let autoMerged = false;
|
|
901
|
+
if (opts.rebase) {
|
|
902
|
+
try {
|
|
903
|
+
await rebaseOntoMergeBranch(ctx, dir);
|
|
904
|
+
} catch (err) {
|
|
905
|
+
if (!(err instanceof RebaseConflictError)) throw err;
|
|
906
|
+
if (abort.signal.aborted) throw new Error("Run canceled by user");
|
|
907
|
+
console.warn(
|
|
908
|
+
` rebase onto ${ctx.repo.mergeBranch} conflicted \u2014 merging it in and resolving with the agent\u2026`
|
|
909
|
+
);
|
|
910
|
+
await mergeAndResolveConflicts(ctx, dir, abort);
|
|
911
|
+
await commitWithRepair(ctx, dir, abort);
|
|
912
|
+
autoMerged = true;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
859
915
|
await pushBranch(ctx, dir);
|
|
860
916
|
const pr = await openPullRequest(ctx);
|
|
861
|
-
return pr ? { kind: "pr", pr } : { kind: "pushed" };
|
|
917
|
+
return { outcome: pr ? { kind: "pr", pr } : { kind: "pushed" }, autoMerged };
|
|
918
|
+
}
|
|
919
|
+
async function mergeAndResolveConflicts(ctx, dir, abort) {
|
|
920
|
+
const { conflicted } = await mergeInMergeBranch(ctx, dir);
|
|
921
|
+
if (!conflicted) return { resolved: false, text: null };
|
|
922
|
+
const result = await runClaudeCode({
|
|
923
|
+
cwd: dir,
|
|
924
|
+
prompt: buildResolvePrompt(ctx),
|
|
925
|
+
permissionMode: ctx.permissionMode,
|
|
926
|
+
model: ORCHESTRATOR_MODEL,
|
|
927
|
+
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
928
|
+
abortController: abort
|
|
929
|
+
});
|
|
930
|
+
const unresolved = await listUnmergedPaths(dir);
|
|
931
|
+
if (unresolved.length > 0) {
|
|
932
|
+
throw new Error(
|
|
933
|
+
`Could not fully resolve the merge \u2014 ${unresolved.length} file(s) still conflict: ${unresolved.join(", ")}`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return { resolved: true, text: result.text.trim() || null };
|
|
862
937
|
}
|
|
863
938
|
async function commitWithRepair(ctx, dir, abort) {
|
|
864
939
|
for (let attempt = 1; ; attempt++) {
|
|
@@ -882,17 +957,18 @@ async function commitWithRepair(ctx, dir, abort) {
|
|
|
882
957
|
}
|
|
883
958
|
function outcomeBanner(outcome, opts) {
|
|
884
959
|
const wikiNote = opts.documented ? " (with wiki updates)" : "";
|
|
960
|
+
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
961
|
switch (outcome.kind) {
|
|
886
962
|
case "pr":
|
|
887
963
|
return `
|
|
888
964
|
|
|
889
965
|
---
|
|
890
|
-
\u2705 Opened pull request from \`${opts.branch}\`${wikiNote} \xB7 [View pull request](${outcome.pr.url})`;
|
|
966
|
+
\u2705 Opened pull request from \`${opts.branch}\`${wikiNote} \xB7 [View pull request](${outcome.pr.url})${mergeNote}`;
|
|
891
967
|
case "pushed":
|
|
892
968
|
return `
|
|
893
969
|
|
|
894
970
|
---
|
|
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)
|
|
971
|
+
\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
972
|
case "none":
|
|
897
973
|
return `
|
|
898
974
|
|
|
@@ -924,6 +1000,11 @@ async function processJob(ctx, abort = new AbortController()) {
|
|
|
924
1000
|
prepared = true;
|
|
925
1001
|
return await processResolveJob(ctx, dir, abort);
|
|
926
1002
|
}
|
|
1003
|
+
if (ctx.kind === "release") {
|
|
1004
|
+
const { resumed } = await prepareResumingBranch(ctx, dir, reused);
|
|
1005
|
+
prepared = true;
|
|
1006
|
+
return await processReleaseJob(ctx, dir, resumed, abort);
|
|
1007
|
+
}
|
|
927
1008
|
await prepareAtSha(ctx, dir, reused);
|
|
928
1009
|
prepared = true;
|
|
929
1010
|
return await processChatJob(ctx, dir, abort);
|
|
@@ -947,10 +1028,11 @@ async function processInitJob(ctx, dir, abort) {
|
|
|
947
1028
|
abortController: abort
|
|
948
1029
|
})).text.trim();
|
|
949
1030
|
let reply = summary || "(the agent produced no summary)";
|
|
950
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort);
|
|
1031
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort);
|
|
951
1032
|
reply += outcomeBanner(outcome, {
|
|
952
1033
|
branch: ctx.repo.checkoutBranch,
|
|
953
|
-
noChange: "no files were generated; the wiki may already exist."
|
|
1034
|
+
noChange: "no files were generated; the wiki may already exist.",
|
|
1035
|
+
autoMerged
|
|
954
1036
|
});
|
|
955
1037
|
return { text: reply, widgets: [] };
|
|
956
1038
|
}
|
|
@@ -996,8 +1078,8 @@ async function processChatJob(ctx, dir, abort) {
|
|
|
996
1078
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
997
1079
|
}
|
|
998
1080
|
}
|
|
999
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort);
|
|
1000
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1081
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort);
|
|
1082
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1001
1083
|
return { text: reply, widgets: [] };
|
|
1002
1084
|
}
|
|
1003
1085
|
async function processImplementJob(ctx, dir, resumed, abort) {
|
|
@@ -1034,8 +1116,8 @@ async function processImplementJob(ctx, dir, resumed, abort) {
|
|
|
1034
1116
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
1035
1117
|
}
|
|
1036
1118
|
}
|
|
1037
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1038
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1119
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1120
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1039
1121
|
return { text: reply, widgets: [], ...outcome.kind === "pr" ? { pr: outcome.pr } : {} };
|
|
1040
1122
|
}
|
|
1041
1123
|
async function processReviseJob(ctx, dir, resumed, abort) {
|
|
@@ -1078,9 +1160,9 @@ async function processReviseJob(ctx, dir, resumed, abort) {
|
|
|
1078
1160
|
console.warn(` wiki update skipped: ${errorMessage2(err)}`);
|
|
1079
1161
|
}
|
|
1080
1162
|
}
|
|
1081
|
-
const outcome = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1163
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1082
1164
|
if (outcome.kind !== "none") {
|
|
1083
|
-
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented });
|
|
1165
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, documented, autoMerged });
|
|
1084
1166
|
}
|
|
1085
1167
|
return {
|
|
1086
1168
|
text: reply,
|
|
@@ -1093,27 +1175,8 @@ async function processResolveJob(ctx, dir, abort) {
|
|
|
1093
1175
|
console.log(`
|
|
1094
1176
|
\u25B6 Resolve ${ctx.jobId} \u2014 ${ctx.repo.fullName}: "${jobTitle(ctx)}"`);
|
|
1095
1177
|
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
|
-
}
|
|
1178
|
+
const { resolved, text } = await mergeAndResolveConflicts(ctx, dir, abort);
|
|
1179
|
+
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
1180
|
if (installResult.status === "failed") {
|
|
1118
1181
|
reply += `
|
|
1119
1182
|
|
|
@@ -1127,6 +1190,40 @@ async function processResolveJob(ctx, dir, abort) {
|
|
|
1127
1190
|
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch });
|
|
1128
1191
|
return { text: reply, widgets: [], ...pr ? { pr } : {} };
|
|
1129
1192
|
}
|
|
1193
|
+
async function processReleaseJob(ctx, dir, resumed, abort) {
|
|
1194
|
+
console.log(`
|
|
1195
|
+
\u25B6 Release ${ctx.jobId} \u2014 ${ctx.repo.fullName}: "${jobTitle(ctx)}"`);
|
|
1196
|
+
const installResult = await installDependencies(dir);
|
|
1197
|
+
const result = await runClaudeCode({
|
|
1198
|
+
cwd: dir,
|
|
1199
|
+
prompt: buildReleasePrompt(ctx),
|
|
1200
|
+
permissionMode: ctx.permissionMode,
|
|
1201
|
+
model: ORCHESTRATOR_MODEL,
|
|
1202
|
+
maxTurns: ORCHESTRATOR_MAX_TURNS,
|
|
1203
|
+
abortController: abort
|
|
1204
|
+
});
|
|
1205
|
+
let reply = result.text.trim() || "(the agent produced no reply)";
|
|
1206
|
+
if (installResult.status === "failed") {
|
|
1207
|
+
reply += `
|
|
1208
|
+
|
|
1209
|
+
> \u26A0\uFE0F Dependencies failed to install (\`${installResult.manager}\`); tests may not have run.`;
|
|
1210
|
+
}
|
|
1211
|
+
if (result.widgets.length > 0) {
|
|
1212
|
+
console.log(
|
|
1213
|
+
` \u2026release ${ctx.jobId} posted ${result.widgets.length} widget(s); awaiting reply`
|
|
1214
|
+
);
|
|
1215
|
+
return { text: reply, widgets: result.widgets };
|
|
1216
|
+
}
|
|
1217
|
+
const { outcome, autoMerged } = await pushAndOpenPr(ctx, dir, abort, { rebase: !resumed });
|
|
1218
|
+
if (outcome.kind !== "none") {
|
|
1219
|
+
reply += outcomeBanner(outcome, { branch: ctx.repo.checkoutBranch, autoMerged });
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
text: reply,
|
|
1223
|
+
widgets: [],
|
|
1224
|
+
...outcome.kind === "pr" ? { pr: outcome.pr } : {}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1130
1227
|
async function heartbeat(config) {
|
|
1131
1228
|
const health = await checkClaudeCode();
|
|
1132
1229
|
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.
|
|
@@ -66,9 +66,10 @@ Field-by-field guidance:
|
|
|
66
66
|
and nothing more.
|
|
67
67
|
- **`assumptions`** — anything you decided during investigation (including
|
|
68
68
|
unanswered defaults from Phase 1).
|
|
69
|
-
- **`steps`** — an ordered list. For each step:
|
|
70
|
-
|
|
71
|
-
references (`path/to/file.ts`) and name the functions/symbols involved.
|
|
69
|
+
- **`steps`** — an ordered list. For each step provide:
|
|
70
|
+
- **`title`** — a concise imperative phrase naming the step (e.g. "Add submit_plan schema to plan.ts").
|
|
71
|
+
- **`description`** — what changes and why: the concrete change being made and the rationale for it. Use concrete file references (`path/to/file.ts`) and name the functions/symbols involved.
|
|
72
|
+
- **`pseudoCode`** — an array of `{ file, pseudoCode }` entries. Provide an entry for every file the step touches **except** documentation files (SKILL.md, README.md, wiki pages, etc.). `pseudoCode` is optional in the schema but expected for all non-documentation files. Each entry names the file path and contains pseudo code that precisely describes the changes to make in that file.
|
|
72
73
|
- **`acceptanceCriteria`** — **required; at least 2 items.** Each criterion must
|
|
73
74
|
be an observable condition you could check after the work is done: a behavior,
|
|
74
75
|
a test result, or a verifiable state. Together they must fully define "done" —
|