automata-cli 0.9.0-develop.382 → 0.9.0-develop.388

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/README.md CHANGED
@@ -71,7 +71,7 @@ Checks that the PR exists and is merged, the working tree is clean, and the remo
71
71
 
72
72
  ### `automata git publish-release`
73
73
 
74
- Execute the full GitFlow release sequence and push to `origin`. Only requires `git`.
74
+ Execute the release sequence — GitFlow, or trunk-based for a repository without `develop` — and push to `origin`. Only requires `git`.
75
75
 
76
76
  ```bash
77
77
  automata git publish-release # auto-detect version from the trunk tag
@@ -40,6 +40,11 @@ var DUMP_ON_BLOCK_OPTIONS = [
40
40
  { label: "Yes \u2014 print the full health report when a tick does nothing", value: true },
41
41
  { label: "No \u2014 print only the one-line reason", value: false }
42
42
  ];
43
+ var RELEASE_FLOW_OPTIONS = [
44
+ { label: "Detect from origin (gitflow if origin/develop exists)", value: void 0 },
45
+ { label: "GitFlow", value: "gitflow" },
46
+ { label: "Trunk-based", value: "trunk" }
47
+ ];
43
48
  var MAIN_MENU_OPTIONS = ["Remote / Mode", "Implement-Next", "Prompts", "Issue Watch", "Do Work", "Git"];
44
49
  var PROMPTS_MENU_OPTIONS = [
45
50
  "Sonar",
@@ -186,6 +191,8 @@ function ConfigWizard() {
186
191
  existing.doWork?.prompts?.prOrphan ?? DEFAULT_DO_WORK_PR_ORPHAN_PROMPT
187
192
  );
188
193
  const [gitTrunkBranch, setGitTrunkBranch] = useState(existing.git?.trunkBranch ?? "");
194
+ const initialReleaseFlowIndex = RELEASE_FLOW_OPTIONS.findIndex((o) => o.value === existing.git?.releaseFlow);
195
+ const [releaseFlowIndex, setReleaseFlowIndex] = useState(Math.max(initialReleaseFlowIndex, 0));
189
196
  const [pendingRemote, setPendingRemote] = useState(existing.remoteType ?? "gh");
190
197
  const [pendingTechnique, setPendingTechnique] = useState(
191
198
  existing.issueDiscoveryTechnique ?? "label"
@@ -374,12 +381,7 @@ function ConfigWizard() {
374
381
  },
375
382
  "git-trunk-branch": {
376
383
  setValue: setGitTrunkBranch,
377
- onSubmit: () => {
378
- const branch = gitTrunkBranch.trim();
379
- const current = readRawConfig();
380
- writeConfig({ ...current, git: { ...current.git, trunkBranch: branch || void 0 } });
381
- exit();
382
- },
384
+ onSubmit: () => setScreen("git-release-flow"),
383
385
  onBack: () => setScreen("main")
384
386
  }
385
387
  };
@@ -464,6 +466,25 @@ function ConfigWizard() {
464
466
  exit();
465
467
  },
466
468
  onBack: () => setScreen("do-work-lock-stale")
469
+ },
470
+ "git-release-flow": {
471
+ index: releaseFlowIndex,
472
+ length: RELEASE_FLOW_OPTIONS.length,
473
+ setIndex: setReleaseFlowIndex,
474
+ onSelect: () => {
475
+ const branch = gitTrunkBranch.trim();
476
+ const current = readRawConfig();
477
+ writeConfig({
478
+ ...current,
479
+ git: {
480
+ ...current.git,
481
+ trunkBranch: branch || void 0,
482
+ releaseFlow: RELEASE_FLOW_OPTIONS[releaseFlowIndex].value
483
+ }
484
+ });
485
+ exit();
486
+ },
487
+ onBack: () => setScreen("git-trunk-branch")
467
488
  }
468
489
  };
469
490
  useInput((input, key) => {
@@ -591,7 +612,7 @@ function ConfigWizard() {
591
612
  title: "Git \u2014 Trunk Branch",
592
613
  label: "Branch publish-release releases to (blank = detect it from the remote):",
593
614
  value: gitTrunkBranch,
594
- hint: `Type branch \xB7 Enter to save \xB7 ${BACK}`
615
+ hint: `Type branch \xB7 Enter to continue \xB7 ${BACK}`
595
616
  }
596
617
  };
597
618
  const menuViews = {
@@ -630,6 +651,12 @@ function ConfigWizard() {
630
651
  options: DUMP_ON_BLOCK_OPTIONS.map((o) => o.label),
631
652
  index: doWorkDumpOnBlockIndex,
632
653
  hint: `\u2191/\u2193 to move \xB7 Enter to save and exit \xB7 ${BACK}`
654
+ },
655
+ "git-release-flow": {
656
+ title: "Git \u2014 Release Flow",
657
+ options: RELEASE_FLOW_OPTIONS.map((o) => o.label),
658
+ index: releaseFlowIndex,
659
+ hint: `\u2191/\u2193 to move \xB7 Enter to save \xB7 ${BACK}`
633
660
  }
634
661
  };
635
662
  const textView = textViews[screen];
package/dist/index.js CHANGED
@@ -26,6 +26,60 @@ var version = packageJson.version;
26
26
 
27
27
  // src/commands/config.ts
28
28
  import { Command } from "commander";
29
+
30
+ // src/git/releaseFlow.ts
31
+ var RELEASE_FLOWS = ["gitflow", "trunk"];
32
+ var RELEASE_FLOW_CONFIG_KEY = "git.releaseFlow";
33
+ function isReleaseFlow(value) {
34
+ return typeof value === "string" && RELEASE_FLOWS.includes(value);
35
+ }
36
+ function describeReleaseFlowSource(source) {
37
+ switch (source) {
38
+ case "config":
39
+ return `configured as ${RELEASE_FLOW_CONFIG_KEY}`;
40
+ case "develop-present":
41
+ return "detected: origin/develop exists";
42
+ case "develop-absent":
43
+ return "detected: origin/develop does not exist";
44
+ }
45
+ }
46
+ function invalidReleaseFlowMessage(value) {
47
+ return `Invalid ${RELEASE_FLOW_CONFIG_KEY} ${JSON.stringify(value)} in .automata/config.json; expected one of: ${RELEASE_FLOWS.join(", ")}.
48
+ Fix it with: automata config set git-release-flow <gitflow|trunk>`;
49
+ }
50
+ function planRelease(flow, version2, trunk, trunkIsLocal) {
51
+ if (flow === "trunk") {
52
+ const message = `chore(release): ${version2}`;
53
+ return [
54
+ { args: ["commit", "--allow-empty", "-m", message], desc: `git commit --allow-empty -m "${message}"` },
55
+ { args: ["tag", version2], desc: `git tag ${version2}` },
56
+ {
57
+ args: ["push", "--atomic", "origin", trunk, version2],
58
+ desc: `git push --atomic origin ${trunk} ${version2}`
59
+ }
60
+ ];
61
+ }
62
+ const releaseBranch = `release/${version2}`;
63
+ const checkoutTrunk = trunkIsLocal ? { args: ["checkout", trunk], desc: `git checkout ${trunk}` } : {
64
+ args: ["checkout", "-b", trunk, `origin/${trunk}`],
65
+ desc: `git checkout -b ${trunk} origin/${trunk}`
66
+ };
67
+ return [
68
+ { args: ["checkout", "-b", releaseBranch], desc: `git checkout -b ${releaseBranch}` },
69
+ checkoutTrunk,
70
+ { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
71
+ { args: ["tag", version2], desc: `git tag ${version2}` },
72
+ { args: ["checkout", "develop"], desc: `git checkout develop` },
73
+ { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
74
+ { args: ["branch", "-d", releaseBranch], desc: `git branch -d ${releaseBranch}` },
75
+ {
76
+ args: ["push", "origin", "develop", trunk, version2],
77
+ desc: `git push origin develop ${trunk} ${version2}`
78
+ }
79
+ ];
80
+ }
81
+
82
+ // src/commands/config.ts
29
83
  var VALID_TYPES = ["gh", "azdo"];
30
84
  var VALID_TECHNIQUES = ["label", "assignee", "title-contains"];
31
85
  var VALID_EXECUTORS = ["claude", "codex"];
@@ -239,12 +293,24 @@ var configSetGitTrunkBranch = new Command("git-trunk-branch").description("Pin t
239
293
  process.stdout.write(`git trunk branch set to: ${branch}
240
294
  `);
241
295
  });
242
- var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType).addCommand(configSetIssueDiscoveryTechnique).addCommand(configSetIssueDiscoveryValue).addCommand(configSetClaudeSystemPrompt).addCommand(configSetAllowedUsers).addCommand(configSetAgentUser).addCommand(configSetDoWorkBaseBranch).addCommand(configSetDoWorkProtectedBranches).addCommand(configSetDoWorkExecutor).addCommand(configSetDoWorkModel).addCommand(configSetDoWorkEffort).addCommand(configSetDoWorkMaxRuns).addCommand(configSetDoWorkLockStaleMinutes).addCommand(configSetDoWorkDumpOnBlock).addCommand(configSetDoWorkPrompt).addCommand(configSetGitTrunkBranch);
296
+ var configSetGitReleaseFlow = new Command("git-release-flow").description("Pin the release procedure `publish-release` runs, instead of detecting it").argument("<value>", `Release flow: ${RELEASE_FLOWS.join(" | ")} (when the key is absent, the flow is detected from origin/develop)`).action((value) => {
297
+ const flow = value.trim();
298
+ if (!isReleaseFlow(flow)) {
299
+ process.stderr.write(`Error: invalid release flow "${value}". Must be one of: ${RELEASE_FLOWS.join(", ")}
300
+ `);
301
+ process.exit(1);
302
+ }
303
+ const current = readRawConfig();
304
+ writeConfig({ ...current, git: { ...current.git, releaseFlow: flow } });
305
+ process.stdout.write(`git release flow set to: ${flow}
306
+ `);
307
+ });
308
+ var configSet = new Command("set").description("Set a configuration value").addCommand(configSetType).addCommand(configSetIssueDiscoveryTechnique).addCommand(configSetIssueDiscoveryValue).addCommand(configSetClaudeSystemPrompt).addCommand(configSetAllowedUsers).addCommand(configSetAgentUser).addCommand(configSetDoWorkBaseBranch).addCommand(configSetDoWorkProtectedBranches).addCommand(configSetDoWorkExecutor).addCommand(configSetDoWorkModel).addCommand(configSetDoWorkEffort).addCommand(configSetDoWorkMaxRuns).addCommand(configSetDoWorkLockStaleMinutes).addCommand(configSetDoWorkDumpOnBlock).addCommand(configSetDoWorkPrompt).addCommand(configSetGitTrunkBranch).addCommand(configSetGitReleaseFlow);
243
309
  var configCommand = new Command("config").description("Configure automata settings").addCommand(configSet).action(async () => {
244
310
  const [{ render }, React, { ConfigWizard }] = await Promise.all([
245
311
  import("ink"),
246
312
  import("react"),
247
- import("./ConfigWizard-T7N4CZFT.js")
313
+ import("./ConfigWizard-RGXIC7OS.js")
248
314
  ]);
249
315
  const { waitUntilExit } = render(React.createElement(ConfigWizard));
250
316
  await waitUntilExit();
@@ -1147,17 +1213,44 @@ ${stderr.trim()}`);
1147
1213
  }
1148
1214
  return stdout.trim().length > 0;
1149
1215
  }
1150
- function checkReleasePreconditions() {
1216
+ function probeRemoteBranch(branch) {
1217
+ const ref = `refs/heads/${branch}`;
1218
+ const { status, stderr } = run2("git", ["ls-remote", "--exit-code", "origin", ref]);
1219
+ if (status === 0) return { ok: true, exists: true };
1220
+ if (status === 2) return { ok: true, exists: false };
1221
+ return {
1222
+ ok: false,
1223
+ message: stderr.trim() || `git ls-remote --exit-code origin ${ref} failed.`
1224
+ };
1225
+ }
1226
+ function resolveReleaseFlow() {
1227
+ const configured = readRawConfig().git?.releaseFlow;
1228
+ if (configured !== void 0) {
1229
+ if (!isReleaseFlow(configured)) return { ok: false, message: invalidReleaseFlowMessage(configured) };
1230
+ return { ok: true, flow: configured, source: "config" };
1231
+ }
1232
+ const develop = probeRemoteBranch("develop");
1233
+ if (!develop.ok) {
1234
+ return {
1235
+ ok: false,
1236
+ message: `Could not check whether origin has a 'develop' branch to choose the release flow.
1237
+ ${develop.message}
1238
+ Set it explicitly with: automata config set git-release-flow <gitflow|trunk>`
1239
+ };
1240
+ }
1241
+ return develop.exists ? { ok: true, flow: "gitflow", source: "develop-present" } : { ok: true, flow: "trunk", source: "develop-absent" };
1242
+ }
1243
+ function checkReleasePreconditions(expectedBranch) {
1151
1244
  let branch;
1152
1245
  try {
1153
1246
  branch = getCurrentBranch();
1154
1247
  } catch (err) {
1155
1248
  return { ok: false, message: err.message };
1156
1249
  }
1157
- if (branch !== "develop") {
1250
+ if (branch !== expectedBranch) {
1158
1251
  return {
1159
1252
  ok: false,
1160
- message: `publish-release must be run from the 'develop' branch (currently on '${branch}').`
1253
+ message: `publish-release must be run from the '${expectedBranch}' branch (currently on '${branch}').`
1161
1254
  };
1162
1255
  }
1163
1256
  if (hasUncommittedChanges()) {
@@ -1168,25 +1261,8 @@ function checkReleasePreconditions() {
1168
1261
  }
1169
1262
  return { ok: true };
1170
1263
  }
1171
- function publishRelease(version2, dryRun, trunk) {
1172
- const releaseBranch = `release/${version2}`;
1173
- const checkoutTrunk = localBranchExists(trunk) ? { args: ["checkout", trunk], desc: `git checkout ${trunk}` } : {
1174
- args: ["checkout", "-b", trunk, `origin/${trunk}`],
1175
- desc: `git checkout -b ${trunk} origin/${trunk}`
1176
- };
1177
- const steps = [
1178
- { args: ["checkout", "-b", releaseBranch], desc: `git checkout -b ${releaseBranch}` },
1179
- checkoutTrunk,
1180
- { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
1181
- { args: ["tag", version2], desc: `git tag ${version2}` },
1182
- { args: ["checkout", "develop"], desc: `git checkout develop` },
1183
- { args: ["merge", "--no-ff", releaseBranch], desc: `git merge --no-ff ${releaseBranch}` },
1184
- { args: ["branch", "-d", releaseBranch], desc: `git branch -d ${releaseBranch}` },
1185
- {
1186
- args: ["push", "origin", "develop", trunk, version2],
1187
- desc: `git push origin develop ${trunk} ${version2}`
1188
- }
1189
- ];
1264
+ function publishRelease(version2, dryRun, trunk, flow) {
1265
+ const steps = planRelease(flow, version2, trunk, flow === "gitflow" && localBranchExists(trunk));
1190
1266
  for (const step of steps) {
1191
1267
  if (dryRun) {
1192
1268
  process.stdout.write(`[dry-run] ${step.desc}
@@ -1614,10 +1690,13 @@ var finishFeatureCmd = new Command2("finish-feature").description("Clean up a me
1614
1690
  process.exit(1);
1615
1691
  }
1616
1692
  });
1617
- var publishReleaseCmd = new Command2("publish-release").description("Execute the full GitFlow release sequence and push to origin").argument("[version]", "Release version in X.Y.Z format (auto-detected from the trunk tag if omitted)").option("--dry-run", "Print git commands without executing them").addHelpText(
1693
+ var publishReleaseCmd = new Command2("publish-release").description("Execute the release sequence (GitFlow or trunk-based) and push to origin").argument("[version]", "Release version in X.Y.Z format (auto-detected from the trunk tag if omitted)").option("--dry-run", "Print git commands without executing them").addHelpText(
1618
1694
  "after",
1619
1695
  `
1620
- Release sequence:
1696
+ Release flow: git.releaseFlow in .automata/config.json if set, else detected \u2014
1697
+ gitflow when origin has a 'develop' branch, trunk when it does not.
1698
+
1699
+ GitFlow sequence (run from develop):
1621
1700
  1. git checkout -b release/<version>
1622
1701
  2. git checkout <trunk> && git merge --no-ff release/<version>
1623
1702
  3. git tag <version>
@@ -1625,6 +1704,11 @@ Release sequence:
1625
1704
  5. git branch -d release/<version>
1626
1705
  6. git push origin develop <trunk> <version>
1627
1706
 
1707
+ Trunk sequence (run from <trunk>; it may be ahead of origin, not behind):
1708
+ 1. git commit --allow-empty -m "chore(release): <version>"
1709
+ 2. git tag <version>
1710
+ 3. git push --atomic origin <trunk> <version>
1711
+
1628
1712
  <trunk> is resolved from origin \u2014 git.trunkBranch in .automata/config.json if
1629
1713
  set, else origin/HEAD, the remote's advertised HEAD, or a probe of main/master.
1630
1714
  Tags are fetched from origin first, in --dry-run too, so the version a dry run
@@ -1635,16 +1719,10 @@ and the minor segment is incremented (e.g. 1.2.0 \u2192 1.3.0).
1635
1719
 
1636
1720
  CHANGELOG.md must already carry a '## [<version>] - YYYY-MM-DD' section for the
1637
1721
  version being released; the release is refused otherwise, in --dry-run too, and
1638
- before any branch, merge or tag is created. A repository with no CHANGELOG.md is
1639
- not subject to the check.`
1722
+ before any commit, branch, merge or tag is created. A repository with no
1723
+ CHANGELOG.md is not subject to the check.`
1640
1724
  ).action((version2, options) => {
1641
1725
  const dryRun = options.dryRun ?? false;
1642
- const preconditions = checkReleasePreconditions();
1643
- if (!preconditions.ok) {
1644
- process.stderr.write(`Error: ${preconditions.message}
1645
- `);
1646
- process.exit(1);
1647
- }
1648
1726
  const trunk = resolveTrunkBranch();
1649
1727
  if (!trunk.ok) {
1650
1728
  process.stderr.write(`Error: ${unresolvedTrunkMessage(trunk.attempted)}
@@ -1653,7 +1731,22 @@ not subject to the check.`
1653
1731
  }
1654
1732
  const trunkBranch = trunk.branch;
1655
1733
  const trunkRef = `origin/${trunkBranch}`;
1734
+ const flow = resolveReleaseFlow();
1735
+ if (!flow.ok) {
1736
+ process.stderr.write(`Error: ${flow.message}
1737
+ `);
1738
+ process.exit(1);
1739
+ }
1740
+ const releaseFlow = flow.flow;
1741
+ const preconditions = checkReleasePreconditions(releaseFlow === "gitflow" ? "develop" : trunkBranch);
1742
+ if (!preconditions.ok) {
1743
+ process.stderr.write(`Error: ${preconditions.message}
1744
+ `);
1745
+ process.exit(1);
1746
+ }
1656
1747
  process.stdout.write(`Trunk branch: ${trunkBranch} (${describeTrunkSource(trunk.source, trunkBranch)})
1748
+ `);
1749
+ process.stdout.write(`Release flow: ${releaseFlow} (${describeReleaseFlowSource(flow.source)})
1657
1750
  `);
1658
1751
  const fetched = fetchTrunkAndTags(trunkBranch);
1659
1752
  if (!fetched.ok) {
@@ -1700,7 +1793,7 @@ ${fetched.message}
1700
1793
  `);
1701
1794
  }
1702
1795
  try {
1703
- publishRelease(resolvedVersion, dryRun, trunkBranch);
1796
+ publishRelease(resolvedVersion, dryRun, trunkBranch, releaseFlow);
1704
1797
  } catch (err) {
1705
1798
  process.stderr.write(`Error: ${err.message}
1706
1799
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.9.0-develop.382",
3
+ "version": "0.9.0-develop.388",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {