@inixiative/config 0.5.201 → 0.5.203

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 @@ TypeScript 6.0 notes, discovered by this repo's fixture suite:
71
71
  bunx @inixiative/config check [dir] [--preset=base|node|react]
72
72
  bunx @inixiative/config sync [dir] [--preset=...] [--force] [--no-install]
73
73
  bunx @inixiative/config scan [root] [--lane=primitives|agentic]
74
- bunx @inixiative/config train [root] [--lane=primitives|agentic] [--push] [--dry-run]
74
+ bunx @inixiative/config train [root] [--lane=primitives|agentic] [--push] [--dry-run] [--adopt-check-timeout=<seconds>]
75
75
  bunx @inixiative/config ports [project]
76
76
  bunx @inixiative/config terms [dir] [--write-baseline]
77
77
  ```
@@ -110,10 +110,10 @@ Lanes stand alone. A lane with a package missing a checkout, or claimed by ambig
110
110
  Blessing is two-phase, so a dependent that needs work never deadlocks a release:
111
111
 
112
112
  1. **Candidate.** A published version is recorded under `candidates`, and the config release that ends the train carries it, so `check` in any repo's CI accepts it from then on.
113
- 2. **Adopt.** A repo (lane checkout or consumer) whose check fails against the candidates it was moved onto doesn't stop the train. Its checkout is put back as it was, and the train opens `train/adopt-<pkg>-<version>` in a worktree (the repo's own `worktree:create <base> <branch>` when it declares one, so a template-based repo gets its slot's databases, released with its `worktree:destroy`; otherwise a temporary linked worktree; either way with the repo's ignored `.env*` files copied in where missing): the same bump and re-lock, then the repo's `train:adopt` script if its package.json declares one (Foundry's re-records cassettes with `bun run test:live`), all committed, and re-locked onto the new config release so its CI judges it against the candidates. `--push` pushes the branch; the train opens no PR. A lane checkout that adopts publishes nothing this run. A consumer that passes against candidates commits on its default branch, like any other passing repo. If the adoption's commit is refused (its hooks typecheck), the branch is deleted and the summary says to adopt by hand.
114
- 3. **Promote.** At the end of each run a candidate is blessed (moved into its lane's section) unless something holds it: its lane stopped; a repo has an adoption branch for it that is not yet on its default branch (opened this run, unmerged, or committed but never pushed); or a repo that declares it was not walked to an outcome this run (another lane's checkout, a skipped or failed one, a consumer without a checkout). A later train finds a waiting adoption branch and opens nothing new; once it merges, the repo is on the candidate and the train promotes it. A candidate every dependent already accepts is blessed in the run that published it. An adoption branch abandoned without merging holds its candidates until it is deleted; the next train that moves that repo deletes it.
113
+ 2. **Adopt.** A repo (lane checkout or consumer) whose check fails against the candidates it was moved onto doesn't stop the train. Its checkout is put back as it was, and the train opens `train/adopt-<pkg>-<version>` in a worktree (the repo's own `worktree:create <base> <branch>` when it declares one, so a template-based repo gets its slot's databases, released with its `worktree:destroy`; otherwise a temporary linked worktree; either way with the repo's ignored `.env*` files copied in where missing): the same bump and re-lock, then the repo's `train:adopt` script if its package.json declares one (Foundry's re-records cassettes with `bun run test:live`), and a commit. The train stages its own writes (the package.json files and `bun.lock`) before `train:adopt` runs and captures what the script wrote as it exits. That output joins the commit only if the script passed and the repo's `check` then passes with it in place, within `--adopt-check-timeout` seconds (default 1800; a timeout is a failure). That check runs against the config release the train started from, before the branch is re-locked onto the new one. Anything the check itself writes is never committed. Otherwise the adoption is **incomplete**: the commit is the re-lock alone (`chore: adopt <label> (incomplete)`, with a `Train-Adopt: incomplete (<reason>)` trailer), and the script's output, edits to owned files included, is saved on `<branch>-output`, one commit on top of it, which `--push` pushes beside the branch. Later trains read the trailer, keep reporting the branch as incomplete, and print what to run to finish it: `bun run train:adopt && bun run check` on the branch, then a commit with the trailer `Train-Adopt: complete`. A repo that declares no `train:adopt` gets the re-lock alone. `--dry-run` never fails a check, so for each repo moving onto candidates it prints how an adoption would be decided. The branch is then re-locked onto the new config release so its CI judges it against the candidates. `--push` pushes the branch; the train opens no PR. A lane checkout that adopts publishes nothing this run. A consumer that passes against candidates commits on its default branch, like any other passing repo. If the adoption's commit is refused (its hooks typecheck), the branch is deleted and the summary says to adopt by hand.
114
+ 3. **Promote.** At the end of each run a candidate is blessed (moved into its lane's section) unless something holds it: its lane stopped; a repo has an adoption branch for it that is not yet on its default branch (opened this run, incomplete, unmerged, or committed but never pushed); or a repo that declares it was not walked to an outcome this run (another lane's checkout, a skipped or failed one, a consumer without a checkout). A later train finds a waiting adoption branch and opens nothing new; once it merges, the repo is on the candidate and the train promotes it. A candidate every dependent already accepts is blessed in the run that published it. An adoption branch abandoned without merging holds its candidates until it is deleted; the next train that moves that repo deletes it.
115
115
 
116
- The summary lists what was blessed, every candidate with what holds it, every hold with its reason, and each pending adoption branch.
116
+ The summary lists what was blessed, every candidate with what holds it (an incomplete adoption is marked so), every hold with its reason, and each pending adoption branch.
117
117
 
118
118
  **Holds.** A hold keeps a package's breaking line out of the blessed set while its dependents can't take it yet (signet 0.12 waiting on Kingdom's grants cutover), without stopping the package's own releases or anyone else's:
119
119
 
package/dist/cli.js CHANGED
@@ -1008,12 +1008,13 @@ var OPTIONS = {
1008
1008
  check: { preset },
1009
1009
  sync: { preset, force: flag, "no-install": flag },
1010
1010
  scan: { lane },
1011
- train: { lane, push: flag, "dry-run": flag },
1011
+ train: { lane, push: flag, "dry-run": flag, "adopt-check-timeout": { type: "string" } },
1012
1012
  ports: {},
1013
1013
  terms: { "write-baseline": flag }
1014
1014
  };
1015
1015
  var UsageError = class extends Error {
1016
1016
  };
1017
+ var DEFAULT_ADOPT_CHECK_TIMEOUT = 30 * 60;
1017
1018
  var isCommand = (value) => COMMANDS.includes(value);
1018
1019
  var parseCommandLine = (argv) => {
1019
1020
  const command2 = argv[0];
@@ -1050,6 +1051,11 @@ var parseCommandLine = (argv) => {
1050
1051
  if (typeof laneValue === "string" && !isLane(laneValue)) {
1051
1052
  throw new UsageError(`unknown lane: ${laneValue}`);
1052
1053
  }
1054
+ const timeoutValue = values["adopt-check-timeout"];
1055
+ const adoptCheckTimeout2 = typeof timeoutValue === "string" ? Number(timeoutValue) : DEFAULT_ADOPT_CHECK_TIMEOUT;
1056
+ if (!Number.isInteger(adoptCheckTimeout2) || adoptCheckTimeout2 <= 0) {
1057
+ throw new UsageError(`--adopt-check-timeout takes whole seconds, got ${timeoutValue}`);
1058
+ }
1053
1059
  return {
1054
1060
  command: command2,
1055
1061
  positionals: positionals2,
@@ -1059,6 +1065,7 @@ var parseCommandLine = (argv) => {
1059
1065
  noInstall: values["no-install"] === true,
1060
1066
  push: values.push === true,
1061
1067
  dryRun: values["dry-run"] === true,
1068
+ adoptCheckTimeout: adoptCheckTimeout2,
1062
1069
  writeBaseline: values["write-baseline"] === true
1063
1070
  };
1064
1071
  };
@@ -1349,7 +1356,7 @@ try {
1349
1356
  );
1350
1357
  process.exit(2);
1351
1358
  }
1352
- var { command, positionals, preset: presetFlag, dryRun } = commandLine;
1359
+ var { command, positionals, preset: presetFlag, dryRun, adoptCheckTimeout } = commandLine;
1353
1360
  var dir = resolve2(positionals[0] ?? ".");
1354
1361
  if (command === "ports") {
1355
1362
  const project = positionals[0];
@@ -1664,18 +1671,52 @@ if (command === "train") {
1664
1671
  if (git(cwd, "rev-parse", "--verify", "--quiet", ref).status !== 0) return null;
1665
1672
  return Number(git(cwd, "rev-list", "--count", `HEAD..${ref}`).stdout.trim());
1666
1673
  };
1674
+ const outputBranch = (branch) => `${branch}-output`;
1675
+ const incompleteReason = (cwd, ref) => {
1676
+ const newest = git(
1677
+ cwd,
1678
+ "log",
1679
+ "--format=%(trailers:key=Train-Adopt,valueonly,separator=%x2C)",
1680
+ `HEAD..${ref}`
1681
+ ).stdout.split("\n").map((value) => value.trim()).find(Boolean);
1682
+ if (!newest?.startsWith("incomplete")) return null;
1683
+ return /\((.*)\)/.exec(newest)?.[1] ?? "no reason recorded";
1684
+ };
1667
1685
  const pendingAdoption = (checkout, moving) => {
1668
1686
  if (moving.length === 0) return false;
1669
1687
  const branch = adoptionBranch(moving);
1670
1688
  const remote = ahead(checkout.dir, `refs/remotes/origin/${branch}`);
1671
1689
  const local = ahead(checkout.dir, `refs/heads/${branch}`);
1672
1690
  if (!remote && !local) return false;
1691
+ walked.add(checkout.dir);
1692
+ const reason = incompleteReason(
1693
+ checkout.dir,
1694
+ remote ? `refs/remotes/origin/${branch}` : `refs/heads/${branch}`
1695
+ );
1696
+ if (reason) {
1697
+ const output = outputBranch(branch);
1698
+ const saved = [`refs/heads/${output}`, `refs/remotes/origin/${output}`].some(
1699
+ (ref) => git(checkout.dir, "rev-parse", "--verify", "--quiet", ref).status === 0
1700
+ );
1701
+ console.log(`\u23F3 adopting ${moving.join(", ")} on ${branch} \u2014 incomplete (${reason})`);
1702
+ console.log(
1703
+ ` finish it: on ${branch}, run \`bun run train:adopt && bun run check\`, commit what it wrote with \`git commit --trailer 'Train-Adopt: complete'\`, and push${saved ? `; the last train's output is on ${output}` : ""}`
1704
+ );
1705
+ waiting.push({
1706
+ name: checkout.name,
1707
+ dir: checkout.dir,
1708
+ moving,
1709
+ state: "incomplete",
1710
+ branch,
1711
+ reason
1712
+ });
1713
+ return true;
1714
+ }
1673
1715
  const state = remote ? "unmerged" : "unpushed";
1674
1716
  console.log(
1675
1717
  `\u23F3 adopting ${moving.join(", ")} on ${branch} \u2014 ${state === "unmerged" ? "waiting for it to merge" : "never pushed: push it"}`
1676
1718
  );
1677
1719
  waiting.push({ name: checkout.name, dir: checkout.dir, moving, state, branch });
1678
- walked.add(checkout.dir);
1679
1720
  return true;
1680
1721
  };
1681
1722
  const worktree = (checkout, branch) => {
@@ -1716,29 +1757,93 @@ if (command === "train") {
1716
1757
  git(checkout.dir, "branch", "-D", branch);
1717
1758
  };
1718
1759
  const defaultBranch = (cwd) => offDefaultBranch(cwd)?.main ?? currentBranch(cwd);
1760
+ const captureOutput = (cwd) => {
1761
+ const changed = [
1762
+ ...git(cwd, "diff", "--name-only", "-z").stdout.split("\0"),
1763
+ ...git(cwd, "ls-files", "--others", "--exclude-standard", "-z").stdout.split("\0")
1764
+ ].filter(Boolean);
1765
+ if (changed.length === 0) return null;
1766
+ const scratch = mkdtempSync(join3(tmpdir(), "inixiative-train-"));
1767
+ try {
1768
+ const env = { ...process.env, GIT_INDEX_FILE: join3(scratch, "index") };
1769
+ const step = (...args) => {
1770
+ const result = spawnSync3("git", args, { cwd, env, encoding: "utf8" });
1771
+ if (result.status !== 0) {
1772
+ throw new Abort(`git ${args[0]} failed capturing train:adopt's output`);
1773
+ }
1774
+ return result.stdout.trim();
1775
+ };
1776
+ step("read-tree", git(cwd, "write-tree").stdout.trim());
1777
+ step("add", "-A", "--", ...changed);
1778
+ return step("write-tree");
1779
+ } finally {
1780
+ rmSync3(scratch, { recursive: true, force: true });
1781
+ }
1782
+ };
1783
+ const adoptionPlan = (script, output) => script ? `re-lock, run train:adopt, then the check \u2014 complete if both pass, else incomplete with the re-lock only and train:adopt's output saved on ${output}` : "re-lock and commit";
1784
+ const previewAdoption = (checkout, moving) => {
1785
+ if (!dryRun || moving.length === 0) return;
1786
+ const branch = adoptionBranch(moving);
1787
+ console.log(
1788
+ ` [dry-run] would, if its check fails against the candidates, open ${branch} in ${checkout.name}: ${adoptionPlan(checkout.pkg.scripts?.["train:adopt"], outputBranch(branch))}`
1789
+ );
1790
+ };
1719
1791
  const adopt = (checkout, moving) => {
1720
1792
  const branch = adoptionBranch(moving);
1793
+ const output = outputBranch(branch);
1721
1794
  const label = moving.map((name) => `${name}@${manifest.candidates?.[name]}`).join(", ");
1722
1795
  const script = checkout.pkg.scripts?.["train:adopt"];
1723
1796
  try {
1724
- effect(
1725
- `open ${branch} in ${checkout.name}: re-lock onto ${label}${script ? ", run train:adopt" : ""}, commit`,
1726
- void 0,
1797
+ const outcome = effect(
1798
+ `open ${branch} in ${checkout.name}: ${adoptionPlan(script, output)}`,
1799
+ { reason: null, saved: false },
1727
1800
  () => {
1728
1801
  const branchCheckout = worktree(checkout, branch);
1802
+ let reason = null;
1803
+ let saved = false;
1729
1804
  try {
1730
- bumpRanges(branchCheckout);
1805
+ const { inspection } = bumpRanges(branchCheckout);
1731
1806
  relockAndCheck(branchCheckout, false);
1732
- if (script && spawnSync3("bun", ["run", "train:adopt"], {
1733
- cwd: branchCheckout.dir,
1734
- stdio: "inherit"
1735
- }).status !== 0) {
1736
- console.log(
1737
- `\u26A0 train:adopt failed in ${checkout.name} \u2014 the branch needs work before it merges`
1738
- );
1807
+ const owned = [...inspection.packagePaths, "bun.lock"].filter(
1808
+ (path) => existsSync3(join3(branchCheckout.dir, path))
1809
+ );
1810
+ if (git(branchCheckout.dir, "add", "--", ...owned).status !== 0) {
1811
+ throw new Abort(`git add failed on ${branch} in ${checkout.name}`);
1812
+ }
1813
+ let produced = null;
1814
+ if (script) {
1815
+ const adopted = spawnSync3("bun", ["run", "train:adopt"], {
1816
+ cwd: branchCheckout.dir,
1817
+ stdio: "inherit"
1818
+ }).status === 0;
1819
+ produced = captureOutput(branchCheckout.dir);
1820
+ const leftOut = `its output is left out; ${branch} carries the re-lock only and needs work before it merges`;
1821
+ if (!adopted) {
1822
+ reason = "train:adopt failed";
1823
+ console.log(`\u26A0 train:adopt failed in ${checkout.name} \u2014 ${leftOut}`);
1824
+ } else {
1825
+ const check = spawnSync3("bun", ["run", "check"], {
1826
+ cwd: branchCheckout.dir,
1827
+ stdio: "inherit",
1828
+ timeout: adoptCheckTimeout * 1e3,
1829
+ killSignal: "SIGKILL"
1830
+ });
1831
+ if (check.status !== 0) {
1832
+ reason = "check failed after train:adopt";
1833
+ console.log(
1834
+ check.error || check.signal ? `\u26A0 check timed out after ${adoptCheckTimeout}s in ${checkout.name} after train:adopt \u2014 ${leftOut}` : `\u26A0 check still fails in ${checkout.name} after train:adopt \u2014 ${leftOut}`
1835
+ );
1836
+ }
1837
+ }
1838
+ if (!reason && produced) git(branchCheckout.dir, "read-tree", produced);
1739
1839
  }
1740
- git(branchCheckout.dir, "add", "-A");
1741
- const commit = spawnSync3("git", ["commit", "-m", `chore: adopt ${label}`], {
1840
+ const message = reason ? [
1841
+ "-m",
1842
+ `chore: adopt ${label} (incomplete)`,
1843
+ "-m",
1844
+ `Train-Adopt: incomplete (${reason})`
1845
+ ] : ["-m", `chore: adopt ${label}`];
1846
+ const commit = spawnSync3("git", ["commit", ...message], {
1742
1847
  cwd: branchCheckout.dir,
1743
1848
  stdio: "inherit"
1744
1849
  });
@@ -1747,15 +1852,44 @@ if (command === "train") {
1747
1852
  `commit failed on ${branch} in ${checkout.name} (its hooks reject the adoption)`
1748
1853
  );
1749
1854
  }
1855
+ if (reason && produced) {
1856
+ const saving = spawnSync3(
1857
+ "git",
1858
+ [
1859
+ "commit-tree",
1860
+ produced,
1861
+ "-p",
1862
+ "HEAD",
1863
+ "-m",
1864
+ `train:adopt output for ${branch} (${reason}), not adopted`
1865
+ ],
1866
+ { cwd: branchCheckout.dir, encoding: "utf8" }
1867
+ );
1868
+ saved = saving.status === 0 && git(branchCheckout.dir, "branch", "-f", output, saving.stdout.trim()).status === 0;
1869
+ console.log(
1870
+ saved ? ` its output is saved on ${output} (git -C ${checkout.dir} diff ${branch} ${output})` : `\u2717 could not save train:adopt's output on ${output} in ${checkout.name}`
1871
+ );
1872
+ }
1873
+ git(branchCheckout.dir, "reset", "--quiet", "--hard", "HEAD");
1874
+ git(branchCheckout.dir, "clean", "--quiet", "-fd");
1750
1875
  } catch (error) {
1751
1876
  dropBranch(checkout, branch);
1877
+ git(checkout.dir, "branch", "-D", output);
1752
1878
  throw error;
1753
1879
  }
1754
1880
  followers.push({ checkout: branchCheckout, onBranch: true });
1881
+ return { reason, saved };
1755
1882
  }
1756
1883
  );
1757
- adoptionBranches.push({ dir: checkout.dir, branch });
1758
- waiting.push({ name: checkout.name, dir: checkout.dir, moving, state: "opened", branch });
1884
+ adoptionBranches.push({ dir: checkout.dir, branch, ...outcome.saved ? { output } : {} });
1885
+ waiting.push({
1886
+ name: checkout.name,
1887
+ dir: checkout.dir,
1888
+ moving,
1889
+ state: outcome.reason ? "incomplete" : "opened",
1890
+ branch,
1891
+ ...outcome.reason ? { reason: outcome.reason } : {}
1892
+ });
1759
1893
  } catch (error) {
1760
1894
  if (!(error instanceof Abort)) throw error;
1761
1895
  failed.push(checkout.name);
@@ -1856,6 +1990,7 @@ if (command === "train") {
1856
1990
  if (pendingAdoption(checkout, moving)) continue;
1857
1991
  const { inspection, bumped, staleLock } = bumpRanges(checkout);
1858
1992
  const relocked = bumped || staleLock || releases.some((release) => release.ahead);
1993
+ if (relocked) previewAdoption(checkout, moving);
1859
1994
  try {
1860
1995
  if (relocked) relockAndCheck(checkout);
1861
1996
  if (commitOwned(checkout, inspection.packagePaths, relocked)) committed.add(checkout.dir);
@@ -1934,6 +2069,7 @@ if (command === "train") {
1934
2069
  console.log("\u2713 already on the blessed set");
1935
2070
  continue;
1936
2071
  }
2072
+ previewAdoption(checkout, moving);
1937
2073
  try {
1938
2074
  relockAndCheck(checkout);
1939
2075
  const message = moving.length > 0 ? `chore: adopt ${moving.map((name) => `${name}@${manifest.candidates?.[name]}`).join(", ")}` : void 0;
@@ -1996,6 +2132,7 @@ if (command === "train") {
1996
2132
  }
1997
2133
  const why = {
1998
2134
  opened: "opened this run",
2135
+ incomplete: "incomplete",
1999
2136
  unmerged: "waiting for its branch to merge",
2000
2137
  unpushed: "committed but never pushed",
2001
2138
  failed: "the train could not commit it \u2014 adopt by hand",
@@ -2007,7 +2144,9 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
2007
2144
  if (candidates.length > 0) {
2008
2145
  console.log("candidates (check accepts them; not yet blessed):");
2009
2146
  for (const [name, version] of candidates) {
2010
- const on = waiting.filter((entry) => entry.moving.includes(name)).map((entry) => `${entry.name} (${entry.branch ?? why[entry.state]})`);
2147
+ const on = waiting.filter((entry) => entry.moving.includes(name)).map(
2148
+ (entry) => `${entry.name} (${entry.branch ?? why[entry.state]}${entry.state === "incomplete" ? ", incomplete" : ""})`
2149
+ );
2011
2150
  console.log(` ${name}@${version}${on.length === 0 ? "" : ` \u2014 waiting on ${on.join(", ")}`}`);
2012
2151
  }
2013
2152
  }
@@ -2016,7 +2155,7 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
2016
2155
  console.log("pending (merge each adoption branch, then run the train again to bless):");
2017
2156
  for (const entry of waiting) {
2018
2157
  console.log(
2019
- ` ${entry.name}: ${entry.branch ? `${entry.branch} \u2014 ` : ""}${why[entry.state]}`
2158
+ ` ${entry.name}: ${entry.branch ? `${entry.branch} \u2014 ` : ""}${entry.state === "incomplete" ? `incomplete (${entry.reason}) \u2014 finish it on the branch` : why[entry.state]}`
2020
2159
  );
2021
2160
  }
2022
2161
  }
@@ -2104,7 +2243,11 @@ blessed: ${promoted.length === 0 ? "nothing" : promoted.join(", ")}`);
2104
2243
  }
2105
2244
  }
2106
2245
  const moved = [.../* @__PURE__ */ new Set([...committed, ...adoptionBranches.map((entry) => entry.dir)])];
2107
- const keep = new Set(adoptionBranches.map((entry) => `${entry.dir}\0${entry.branch}`));
2246
+ const keep = new Set(
2247
+ adoptionBranches.flatMap(
2248
+ (entry) => [entry.branch, entry.output].flatMap((branch) => branch ? [`${entry.dir}\0${branch}`] : [])
2249
+ )
2250
+ );
2108
2251
  const superseded = (repoDir) => {
2109
2252
  const refs = (prefix) => git(repoDir, "for-each-ref", "--format=%(refname:short)", prefix).stdout.split("\n").map((ref) => ref.trim().replace(/^origin\//, "")).filter((branch) => branch.startsWith("train/") && !keep.has(`${repoDir}\0${branch}`));
2110
2253
  return { local: refs("refs/heads/train/"), remote: refs("refs/remotes/origin/train/") };
@@ -2124,12 +2267,14 @@ dry run complete \u2014 a real train would commit in ${committed.size} repo(s) a
2124
2267
  process.exit(1);
2125
2268
  }
2126
2269
  }
2127
- for (const { dir: repoDir, branch } of adoptionBranches) {
2128
- const push = spawnSync3("git", ["push", "-u", "origin", branch], {
2129
- cwd: repoDir,
2130
- stdio: "inherit"
2131
- });
2132
- if (push.status !== 0) console.error(`\u2717 could not push ${branch} in ${repoDir}`);
2270
+ for (const { dir: repoDir, branch, output } of adoptionBranches) {
2271
+ for (const ref of output ? [branch, output] : [branch]) {
2272
+ const push = spawnSync3("git", ["push", "-u", "origin", ref], {
2273
+ cwd: repoDir,
2274
+ stdio: "inherit"
2275
+ });
2276
+ if (push.status !== 0) console.error(`\u2717 could not push ${ref} in ${repoDir}`);
2277
+ }
2133
2278
  }
2134
2279
  for (const repoDir of moved) {
2135
2280
  git(repoDir, "fetch", "--quiet", "--prune", "origin");
@@ -2150,8 +2295,9 @@ dry run complete \u2014 a real train would commit in ${committed.size} repo(s) a
2150
2295
  } else if (committed.size > 0 || adoptionBranches.length > 0) {
2151
2296
  console.log("train does not push without --push \u2014 review its commits, then push:");
2152
2297
  for (const repoDir of committed) console.log(` git -C ${repoDir} push`);
2153
- for (const { dir: repoDir, branch } of adoptionBranches) {
2298
+ for (const { dir: repoDir, branch, output } of adoptionBranches) {
2154
2299
  console.log(` git -C ${repoDir} push -u origin ${branch}`);
2300
+ if (output) console.log(` git -C ${repoDir} push -u origin ${output}`);
2155
2301
  }
2156
2302
  for (const repoDir of moved) {
2157
2303
  const { local, remote } = superseded(repoDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/config",
3
- "version": "0.5.201",
3
+ "version": "0.5.203",
4
4
  "description": "Shared toolchain and theme for the inixiative packages: tsconfig/biome/tsup presets, theme CSS, a version manifest (BOM), and a sync/check/train CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/versions.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "@types/bun"
14
14
  ],
15
15
  "ecosystem": {
16
- "@inixiative/config": "0.5.201",
16
+ "@inixiative/config": "0.5.203",
17
17
  "@inixiative/json-rules": "3.16.0",
18
18
  "@inixiative/permissions": "0.21.1",
19
19
  "@inixiative/transitions": "0.7.2",
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "candidates": {
54
54
  "@inixiative/atlas": "0.5.0",
55
- "@inixiative/json-rules": "3.21.0"
55
+ "@inixiative/json-rules": "3.21.0",
56
+ "@inixiative/signet": "0.14.0"
56
57
  }
57
58
  }