@brainervirus/workit-claude-code 7.0.0 → 7.1.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/workit.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "6.0.1",
68
+ version: "7.1.0",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "workspace:*",
107
- "@brainervirus/workit-cursor": "workspace:*",
108
- "@brainervirus/workit-opencode": "workspace:*",
106
+ "@brainervirus/workit-core": "^7.1.0",
107
+ "@brainervirus/workit-cursor": "^7.1.0",
108
+ "@brainervirus/workit-opencode": "^7.1.0",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -15029,14 +15029,14 @@ var diagnosticLogger, setDiagnosticLogger = (logger) => {
15029
15029
  if (result.status === "malformed")
15030
15030
  throw new Error(result.error);
15031
15031
  return result.config ?? DEFAULTS;
15032
- }, resolveBranchPolicy = (config, workspace) => {
15032
+ }, vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path13.join(configDir(), "vcs.json"), branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"), resolveBranchPolicy = (config, workspace) => {
15033
15033
  const wp = workspace?.branchPolicy ?? {};
15034
15034
  const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
15035
15035
  const merged = mergePreset(preset, {
15036
15036
  allowed: wp.allowed,
15037
15037
  protectedNames: wp.protected
15038
15038
  }, config);
15039
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
15039
+ const allowed = merged.allowed.map(branchGlobPattern);
15040
15040
  return {
15041
15041
  preset,
15042
15042
  allowed,
@@ -15090,6 +15090,14 @@ var init_config = __esm(() => {
15090
15090
  import { createHash as createHash8 } from "node:crypto";
15091
15091
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
15092
15092
  import path14 from "node:path";
15093
+ function workspaceDefaultTarget(cwd, globalDefault) {
15094
+ const ws = resolveRuntimeWorkspaceVcs(cwd);
15095
+ const wp = ws?.branchPolicy ?? {};
15096
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
15097
+ const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
15098
+ const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
15099
+ return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
15100
+ }
15093
15101
  var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspaceCommitPolicySchema, workspaceProfileSchema, versionSourceSchema, releaseTrackSchema, workspaceConfigSchema, workspacesFileSchema, workspacesRevision = (contents) => createHash8("sha256").update(contents ?? "<missing>").digest("hex"), validateWorkspacesDocument = (parsed, file) => {
15094
15102
  if (!isConfigObject(parsed)) {
15095
15103
  return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
@@ -15279,7 +15287,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15279
15287
  profiles: record(nonBlank, profileSchema).optional()
15280
15288
  }).passthrough();
15281
15289
  return entrySchema;
15282
- }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
15290
+ }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
15283
15291
  const file = path14.join(configDir(), "workspaces.json");
15284
15292
  let raw;
15285
15293
  try {
@@ -15314,8 +15322,15 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15314
15322
  });
15315
15323
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
15316
15324
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
15317
- if (!selected)
15325
+ return selected ? { file, selected } : null;
15326
+ }, resolveRuntimeReleaseTracks = (cwd) => {
15327
+ const matched = matchRuntimeWorkspace(cwd);
15328
+ return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
15329
+ }, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
15330
+ const matched = matchRuntimeWorkspace(cwd);
15331
+ if (!matched)
15318
15332
  return null;
15333
+ const { file, selected } = matched;
15319
15334
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
15320
15335
  if (!policy.success) {
15321
15336
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
@@ -15416,26 +15431,31 @@ var init_workspaces = __esm(() => {
15416
15431
  object({ kind: literal("manual") }).strict()
15417
15432
  ]);
15418
15433
  releaseTrackSchema = object({
15419
- strategy: branchPreset,
15434
+ strategy: branchPreset.optional(),
15420
15435
  productionBranch: nonBlank,
15421
15436
  integrationBranch: nonBlank,
15422
- naming: object({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
15423
- baseBranch: nonBlank,
15424
- mergeBackBranches: array(nonBlank),
15425
- pullRequestTarget: nonBlank,
15426
- tagNamespace: string2(),
15427
- versionSource: versionSourceSchema,
15428
- requiredChecks: array(nonBlank)
15429
- }).strict().superRefine((track, context) => {
15437
+ naming: object({
15438
+ feature: nonBlank.optional(),
15439
+ release: nonBlank.optional(),
15440
+ hotfix: nonBlank.optional()
15441
+ }).passthrough().optional(),
15442
+ baseBranch: nonBlank.optional(),
15443
+ mergeBackBranches: array(nonBlank).optional(),
15444
+ pullRequestTarget: nonBlank.optional(),
15445
+ tagNamespace: string2().optional(),
15446
+ versionSource: versionSourceSchema.optional(),
15447
+ requiredChecks: array(nonBlank).optional(),
15448
+ critical: array(nonBlank).optional()
15449
+ }).passthrough().superRefine((track, context) => {
15430
15450
  for (const [field, values] of [
15431
- ["mergeBackBranches", track.mergeBackBranches],
15432
- ["requiredChecks", track.requiredChecks]
15451
+ ["mergeBackBranches", track.mergeBackBranches ?? []],
15452
+ ["requiredChecks", track.requiredChecks ?? []]
15433
15453
  ]) {
15434
15454
  if (new Set(values).size !== values.length) {
15435
15455
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
15436
15456
  }
15437
15457
  }
15438
- if (track.versionSource.kind === "package-json" && (path14.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
15458
+ if (track.versionSource?.kind === "package-json" && (path14.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
15439
15459
  context.addIssue({
15440
15460
  code: "custom",
15441
15461
  path: ["versionSource", "path"],
@@ -15502,11 +15522,473 @@ var init_workspaces = __esm(() => {
15502
15522
  }).passthrough();
15503
15523
  });
15504
15524
 
15525
+ // packages/workit-core/src/core/release-tracks.ts
15526
+ import { createHash as createHash9 } from "node:crypto";
15527
+ import { spawnSync as spawnSync7 } from "node:child_process";
15528
+ import { readFileSync as readFileSync5 } from "node:fs";
15529
+ function readReleaseTracks(raw) {
15530
+ const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
15531
+ if (raw === undefined || raw === null)
15532
+ return out;
15533
+ if (!isRecord3(raw)) {
15534
+ out.issues.push("releaseTracks is not an object; ignored");
15535
+ return out;
15536
+ }
15537
+ const fatal = [];
15538
+ const protectedNames = new Set;
15539
+ for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
15540
+ const label = `release track ${JSON.stringify(name)}`;
15541
+ if (!isRecord3(entry)) {
15542
+ out.issues.push(`${label} is not an object; ignored`);
15543
+ continue;
15544
+ }
15545
+ for (const branch of namedBranches(entry))
15546
+ protectedNames.add(branch);
15547
+ if (!name.trim()) {
15548
+ out.issues.push("a release track with a blank name was ignored");
15549
+ continue;
15550
+ }
15551
+ let critical = [];
15552
+ if (Array.isArray(entry.critical))
15553
+ critical = entry.critical.filter((item) => typeof item === "string");
15554
+ else if (entry.critical !== undefined)
15555
+ out.issues.push(`${label} critical: must be a list of field names; ignored`);
15556
+ const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
15557
+ const problem = (field, message) => {
15558
+ if (isCritical(field))
15559
+ fatal.push(`${label} ${field}: ${message}`);
15560
+ else
15561
+ out.issues.push(`${label} ${field}: ${message}`);
15562
+ };
15563
+ for (const key of Object.keys(entry))
15564
+ if (!KNOWN_FIELDS.has(key))
15565
+ problem(key, "unknown field, ignored");
15566
+ const productionBranch = branchName(entry.productionBranch);
15567
+ const integrationBranch = branchName(entry.integrationBranch);
15568
+ if (!productionBranch)
15569
+ problem("productionBranch", "missing or not a branch name");
15570
+ if (!integrationBranch)
15571
+ problem("integrationBranch", "missing or not a branch name");
15572
+ const optionalBranch = (field) => {
15573
+ if (entry[field] === undefined)
15574
+ return null;
15575
+ const value = branchName(entry[field]);
15576
+ if (!value)
15577
+ problem(field, "not a branch name; using the integration branch");
15578
+ return value;
15579
+ };
15580
+ const baseBranch = optionalBranch("baseBranch");
15581
+ const pullRequestTarget = optionalBranch("pullRequestTarget");
15582
+ let mergeBackBranches = [];
15583
+ if (entry.mergeBackBranches !== undefined) {
15584
+ const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
15585
+ if (list.some((item) => item === null))
15586
+ problem("mergeBackBranches", "must be a list of branch names; ignored");
15587
+ else
15588
+ mergeBackBranches = [...new Set(list)];
15589
+ }
15590
+ const naming = {};
15591
+ if (entry.naming !== undefined) {
15592
+ if (!isRecord3(entry.naming))
15593
+ problem("naming", "not an object; ignored");
15594
+ else
15595
+ for (const kind of ["feature", "release", "hotfix"]) {
15596
+ const template = entry.naming[kind];
15597
+ if (template === undefined)
15598
+ continue;
15599
+ if (typeof template !== "string" || !template.trim())
15600
+ problem(`naming.${kind}`, "not a template string; ignored");
15601
+ else if (!literalOf(template))
15602
+ problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
15603
+ else
15604
+ naming[kind] = template.trim();
15605
+ }
15606
+ }
15607
+ if (!productionBranch || !integrationBranch) {
15608
+ out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
15609
+ continue;
15610
+ }
15611
+ out.tracks.push({
15612
+ name,
15613
+ productionBranch,
15614
+ integrationBranch,
15615
+ baseBranch: baseBranch ?? integrationBranch,
15616
+ pullRequestTarget: pullRequestTarget ?? integrationBranch,
15617
+ mergeBackBranches,
15618
+ naming
15619
+ });
15620
+ }
15621
+ out.protectedBranches = [...protectedNames];
15622
+ if (fatal.length)
15623
+ out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
15624
+ return out;
15625
+ }
15626
+ function resolveReleaseTrack(input) {
15627
+ const tracks = [...input.tracks];
15628
+ const issues = [...input.issues ?? []];
15629
+ const requested = input.requested?.trim();
15630
+ if (tracks.length === 0) {
15631
+ if (requested)
15632
+ return {
15633
+ status: "invalid",
15634
+ error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
15635
+ issues
15636
+ };
15637
+ return { status: "none", issues };
15638
+ }
15639
+ const names = tracks.map((track) => track.name).join(", ");
15640
+ let envProblem = null;
15641
+ const done = (track, source, detail, warnings = [], ambiguous = null) => ({
15642
+ status: "resolved",
15643
+ track: track && track.name !== IMPLICIT ? track : null,
15644
+ source,
15645
+ detail,
15646
+ warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
15647
+ tracks,
15648
+ blocking: envProblem ?? ambiguous
15649
+ });
15650
+ if (requested) {
15651
+ const named = tracks.find((track) => track.name === requested);
15652
+ if (!named)
15653
+ return {
15654
+ status: "invalid",
15655
+ error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
15656
+ issues
15657
+ };
15658
+ return done(named, "flag", `track ${named.name} was requested explicitly`);
15659
+ }
15660
+ const owned = (branch) => branch !== null && tracks.some((track) => [
15661
+ track.integrationBranch,
15662
+ track.productionBranch,
15663
+ track.baseBranch,
15664
+ track.pullRequestTarget
15665
+ ].includes(branch));
15666
+ const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
15667
+ name: IMPLICIT,
15668
+ productionBranch: input.defaultBranch,
15669
+ integrationBranch: input.defaultBranch,
15670
+ baseBranch: input.defaultBranch,
15671
+ pullRequestTarget: input.defaultBranch,
15672
+ mergeBackBranches: [],
15673
+ naming: {}
15674
+ } : null;
15675
+ const candidates = implicit ? [...tracks, implicit] : tracks;
15676
+ const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
15677
+ const fallback = (why) => {
15678
+ const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
15679
+ const track = byDefault.length === 1 ? byDefault[0] : null;
15680
+ const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
15681
+ const ambiguous = candidates.length >= 2 ? `can't tell which release line ${input.branch ?? "HEAD"} is on (${why}); pass --track <name> (${names}) or --base <branch>` : null;
15682
+ return done(track, "default", `${why}; using the workspace ${target}`, [
15683
+ `release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
15684
+ ], ambiguous);
15685
+ };
15686
+ const branch = input.branch;
15687
+ if (branch) {
15688
+ const owners = ownedBy(candidates, branch);
15689
+ if (owners.length === 1)
15690
+ return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
15691
+ if (owners.length > 1)
15692
+ return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
15693
+ }
15694
+ const env = input.envRequested?.trim();
15695
+ if (env) {
15696
+ const named = tracks.find((track) => track.name === env);
15697
+ if (named)
15698
+ return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
15699
+ envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
15700
+ }
15701
+ if (!branch)
15702
+ return fallback("HEAD is detached");
15703
+ if (tracks.length === 1 && !implicit)
15704
+ return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
15705
+ let remotes = null;
15706
+ const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
15707
+ const ownerOf = (ref) => {
15708
+ const owner = ownedBy(candidates, short(ref));
15709
+ return owner.length === 1 ? owner[0] : null;
15710
+ };
15711
+ const followRecorded = (start) => {
15712
+ let cursor = start;
15713
+ const seen = new Set([start]);
15714
+ for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
15715
+ const recorded = input.probe.recordedBase(cursor);
15716
+ if (!recorded)
15717
+ return null;
15718
+ const base = short(recorded);
15719
+ const owner = ownerOf(base);
15720
+ if (owner)
15721
+ return owner;
15722
+ if (ownedBy(candidates, base).length > 1 || seen.has(base))
15723
+ return null;
15724
+ seen.add(base);
15725
+ cursor = base;
15726
+ }
15727
+ return null;
15728
+ };
15729
+ const recordedOwner = followRecorded(branch);
15730
+ if (recordedOwner)
15731
+ return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
15732
+ for (const source of ["reflog", "checkout"]) {
15733
+ const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
15734
+ if (!ref)
15735
+ continue;
15736
+ const owner = ownerOf(ref) ?? followRecorded(short(ref));
15737
+ if (owner)
15738
+ return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
15739
+ }
15740
+ const forks = [];
15741
+ for (const track of candidates)
15742
+ for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
15743
+ const sha = input.probe.mergeBase(branch, ref);
15744
+ if (sha)
15745
+ forks.push({ track, ref, sha });
15746
+ }
15747
+ if (forks.length === 0)
15748
+ return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
15749
+ const ancestry = new Map;
15750
+ const descends = (older, newer) => {
15751
+ if (older === newer)
15752
+ return false;
15753
+ const key = `${older}>${newer}`;
15754
+ if (!ancestry.has(key))
15755
+ ancestry.set(key, input.probe.isAncestor(older, newer));
15756
+ return ancestry.get(key);
15757
+ };
15758
+ const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
15759
+ const winningTracks = [...new Set(winners.map((fork) => fork.track))];
15760
+ if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
15761
+ const win = winners[0];
15762
+ return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
15763
+ }
15764
+ if (winningTracks.length === 1)
15765
+ return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
15766
+ const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
15767
+ return fallback(`${branch} has the same or unrelated fork points with ${[...new Set(newest.map((fork) => `${fork.ref} (${fork.track.name === IMPLICIT ? "workspace default" : fork.track.name})`))].join(" and ")}`);
15768
+ }
15769
+ function gitTrackProbe(cwd) {
15770
+ let remote;
15771
+ let remotes;
15772
+ const tip = (branch) => {
15773
+ remote ??= pushRemoteName(cwd) ?? "origin";
15774
+ return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
15775
+ };
15776
+ return {
15777
+ remotes: () => remotes ??= remoteNames(cwd),
15778
+ recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
15779
+ createdFrom: (branch) => {
15780
+ const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
15781
+ const oldest = log?.split(`
15782
+ `).findLast(Boolean) ?? "";
15783
+ const match = /^branch: Created from (\S+)$/u.exec(oldest);
15784
+ return match && match[1] !== "HEAD" ? match[1] : null;
15785
+ },
15786
+ checkedOutFrom: (branch) => {
15787
+ const own = reflog(cwd, `refs/heads/${branch}`);
15788
+ const created = own.at(-1);
15789
+ if (!created || created.subject !== "branch: Created from HEAD")
15790
+ return null;
15791
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
15792
+ const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
15793
+ return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
15794
+ },
15795
+ mergeBase: (branch, trackBranch) => {
15796
+ const base = tip(trackBranch);
15797
+ return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
15798
+ },
15799
+ isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
15800
+ };
15801
+ }
15802
+ function workspaceReleaseTracks(cwd) {
15803
+ const raw = resolveRuntimeReleaseTracks(cwd);
15804
+ return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
15805
+ }
15806
+ function resolveTrackFor(cwd, options) {
15807
+ const read = workspaceReleaseTracks(cwd);
15808
+ if (read.error)
15809
+ return { status: "invalid", error: read.error, issues: read.issues };
15810
+ if (read.tracks.length === 0 && !options.track?.trim())
15811
+ return { status: "none", issues: read.issues };
15812
+ const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
15813
+ const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
15814
+ const input = {
15815
+ tracks: read.tracks,
15816
+ requested: options.track ?? null,
15817
+ envRequested,
15818
+ branch,
15819
+ defaultBranch: options.defaultBranch,
15820
+ issues: read.issues
15821
+ };
15822
+ let key = null;
15823
+ if (!options.probe) {
15824
+ const refs = git4(cwd, [
15825
+ "for-each-ref",
15826
+ "--format=%(refname) %(objectname)",
15827
+ "refs/heads",
15828
+ "refs/remotes"
15829
+ ]) ?? "";
15830
+ const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
15831
+ const own = branch ? [
15832
+ git4(cwd, [
15833
+ "log",
15834
+ "-g",
15835
+ "-n",
15836
+ "1",
15837
+ "--date=unix",
15838
+ "--format=%H %gd %gs",
15839
+ `refs/heads/${branch}`,
15840
+ "--"
15841
+ ]) ?? "",
15842
+ git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
15843
+ ] : [];
15844
+ key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
15845
+ const hit = memo3.get(key);
15846
+ if (hit)
15847
+ return hit;
15848
+ }
15849
+ const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
15850
+ if (key) {
15851
+ if (memo3.size >= MEMO_LIMIT)
15852
+ memo3.delete(memo3.keys().next().value);
15853
+ memo3.set(key, resolution);
15854
+ }
15855
+ return resolution;
15856
+ }
15857
+ function releaseTracksReport(cwd) {
15858
+ let raw;
15859
+ try {
15860
+ raw = resolveRuntimeReleaseTracks(cwd);
15861
+ } catch (error) {
15862
+ return {
15863
+ workspace: null,
15864
+ tracks: [],
15865
+ resolved: null,
15866
+ warnings: [],
15867
+ error: error instanceof Error ? error.message : String(error)
15868
+ };
15869
+ }
15870
+ const read = readReleaseTracks(raw?.releaseTracks);
15871
+ const tracks = read.tracks.map(({ name, integrationBranch, productionBranch }) => ({
15872
+ name,
15873
+ integrationBranch,
15874
+ productionBranch
15875
+ }));
15876
+ const base = { workspace: raw?.workspace ?? null, tracks };
15877
+ if (read.error)
15878
+ return { ...base, resolved: null, warnings: read.issues, error: read.error };
15879
+ if (tracks.length === 0)
15880
+ return { ...base, resolved: null, warnings: read.issues, error: null };
15881
+ let defaultBranch;
15882
+ try {
15883
+ defaultBranch = workspaceDefaultTarget(cwd, globalDefaultTarget());
15884
+ } catch (error) {
15885
+ return {
15886
+ ...base,
15887
+ resolved: null,
15888
+ warnings: read.issues,
15889
+ error: error instanceof Error ? error.message : String(error)
15890
+ };
15891
+ }
15892
+ const resolution = resolveTrackFor(cwd, { defaultBranch });
15893
+ if (resolution.status === "invalid")
15894
+ return { ...base, resolved: null, warnings: resolution.issues, error: resolution.error };
15895
+ if (resolution.status === "none")
15896
+ return { ...base, resolved: null, warnings: resolution.issues, error: null };
15897
+ return {
15898
+ ...base,
15899
+ resolved: {
15900
+ name: resolution.track?.name ?? null,
15901
+ source: resolution.source,
15902
+ detail: resolution.detail
15903
+ },
15904
+ warnings: resolution.warnings,
15905
+ error: null
15906
+ };
15907
+ }
15908
+ var ReleaseTrackError, KNOWN_FIELDS, BRANCH_FIELDS, PLACEHOLDER, HAS_PLACEHOLDER, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null, namedBranches = (entry) => [
15909
+ ...BRANCH_FIELDS.map((field) => branchName(entry[field])),
15910
+ ...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
15911
+ ].filter((name) => name !== null), literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim(), trackBranches = (tracks) => [
15912
+ ...new Set(tracks.flatMap((track) => [
15913
+ track.productionBranch,
15914
+ track.integrationBranch,
15915
+ track.baseBranch,
15916
+ track.pullRequestTarget,
15917
+ ...track.mergeBackBranches
15918
+ ]))
15919
+ ], namingGlob = (template) => HAS_PLACEHOLDER.test(template) ? template.replace(PLACEHOLDER, "*") : `${template.replace(/\/?\*?$/u, "")}/*`, trackBranchName = (track, kind, slug) => {
15920
+ const template = track?.naming[kind];
15921
+ if (!template)
15922
+ return null;
15923
+ return HAS_PLACEHOLDER.test(template) ? template.replace(PLACEHOLDER, slug) : `${template.replace(/\/?\*?$/u, "")}/${slug}`;
15924
+ }, IMPLICIT = "", ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch)), shortRef = (ref, remotes) => {
15925
+ const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
15926
+ const remote = remotes.find((name) => bare.startsWith(`${name}/`));
15927
+ return remote ? bare.slice(remote.length + 1) : bare;
15928
+ }, MAX_RECORDED_HOPS = 10, git4 = (cwd, args) => {
15929
+ const run = spawnSync7("git", args, {
15930
+ cwd,
15931
+ encoding: "utf8",
15932
+ timeout: 20000,
15933
+ windowsHide: true,
15934
+ stdio: ["ignore", "pipe", "ignore"]
15935
+ });
15936
+ return run.status === 0 ? (run.stdout ?? "").trim() : null;
15937
+ }, recordedBaseKey = (branch) => `branch.${branch}.workitBase`, recordBranchBase = (cwd, branch, base) => {
15938
+ git4(cwd, ["config", recordedBaseKey(branch), base]);
15939
+ }, escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"), reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
15940
+ `).map((line) => {
15941
+ const [sha = "", selector = "", ...subject] = line.split("\t");
15942
+ return {
15943
+ sha,
15944
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
15945
+ subject: subject.join("\t")
15946
+ };
15947
+ }).filter((entry) => entry.sha && Number.isFinite(entry.time)), globalDefaultTarget = () => {
15948
+ try {
15949
+ const parsed = JSON.parse(readFileSync5(vcsConfigPath(), "utf8"));
15950
+ return isRecord3(parsed) ? parsed.defaultTargetBranch : undefined;
15951
+ } catch {
15952
+ return;
15953
+ }
15954
+ }, memo3, MEMO_LIMIT = 64, describeReleaseTracks = (report) => report.tracks.length === 0 && !report.error ? [] : [
15955
+ `release tracks: ${report.tracks.map((track) => `${track.name} (${track.integrationBranch} -> ${track.productionBranch})`).join(", ") || "(none usable)"}`,
15956
+ ...report.resolved ? [
15957
+ `this checkout: ${report.resolved.name ?? "workspace default"} (${report.resolved.detail})`
15958
+ ] : [],
15959
+ ...report.error ? [`release tracks error: ${report.error}`] : [],
15960
+ ...report.warnings.map((warning) => `note: ${warning}`)
15961
+ ];
15962
+ var init_release_tracks = __esm(() => {
15963
+ init_rev();
15964
+ init_config();
15965
+ init_workspaces();
15966
+ ReleaseTrackError = class ReleaseTrackError extends Error {
15967
+ };
15968
+ KNOWN_FIELDS = new Set([
15969
+ "strategy",
15970
+ "productionBranch",
15971
+ "integrationBranch",
15972
+ "naming",
15973
+ "baseBranch",
15974
+ "mergeBackBranches",
15975
+ "pullRequestTarget",
15976
+ "tagNamespace",
15977
+ "versionSource",
15978
+ "requiredChecks",
15979
+ "critical"
15980
+ ]);
15981
+ BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
15982
+ PLACEHOLDER = /\{[^}]*\}/gu;
15983
+ HAS_PLACEHOLDER = /\{[^}]*\}/u;
15984
+ memo3 = new Map;
15985
+ });
15986
+
15505
15987
  // packages/workit-core/src/core/vcs-config.ts
15506
15988
  import fs12 from "node:fs";
15507
15989
  import path15 from "node:path";
15508
- import { spawnSync as spawnSync7 } from "node:child_process";
15509
- function vcsConfig(mode, cwd) {
15990
+ import { spawnSync as spawnSync8 } from "node:child_process";
15991
+ function vcsConfig(mode, cwd, options = {}) {
15510
15992
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
15511
15993
  const wsVcs = ws?.vcs ?? {};
15512
15994
  const wsYt = ws?.youtrack ?? {};
@@ -15516,10 +15998,7 @@ function vcsConfig(mode, cwd) {
15516
15998
  const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
15517
15999
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
15518
16000
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
15519
- const wp = ws?.branchPolicy ?? {};
15520
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
15521
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
15522
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
16001
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
15523
16002
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
15524
16003
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
15525
16004
  let issuesProvider = null;
@@ -15531,11 +16010,24 @@ function vcsConfig(mode, cwd) {
15531
16010
  if (mode === "resolve") {
15532
16011
  if (cfgStatus === "malformed")
15533
16012
  return { ok: false, error: cfgError, configPath: cfgPath };
16013
+ const trackResolution = resolveTrackFor(root, {
16014
+ track: options.track,
16015
+ ...options.branch !== undefined ? { branch: options.branch } : {},
16016
+ defaultBranch: workspaceDefault
16017
+ });
16018
+ if (trackResolution.status === "invalid")
16019
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
16020
+ const releaseTrack = trackSummary(trackResolution);
16021
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
16022
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
15534
16023
  return {
15535
16024
  ok: true,
15536
16025
  workspace_name: ws?.name ?? null,
15537
16026
  provider,
15538
16027
  defaultTargetBranch: defaultTarget,
16028
+ workspaceDefaultTargetBranch: workspaceDefault,
16029
+ baseBranch,
16030
+ releaseTrack,
15539
16031
  link_issues: linkIssues,
15540
16032
  youtrack_base_url: youtrackBaseUrl,
15541
16033
  issues_provider: issuesProvider,
@@ -15559,7 +16051,7 @@ function vcsConfig(mode, cwd) {
15559
16051
  ok: true,
15560
16052
  configPath: path15.resolve(cfgPath),
15561
16053
  provider,
15562
- defaultTargetBranch: defaultTarget,
16054
+ defaultTargetBranch: workspaceDefault,
15563
16055
  pr: cfg.pr ?? {},
15564
16056
  workspace_name: ws?.name ?? null,
15565
16057
  link_issues: linkIssues,
@@ -15577,8 +16069,8 @@ function vcsConfig(mode, cwd) {
15577
16069
  }
15578
16070
  return out;
15579
16071
  }
15580
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path15.join(configDir(), "vcs.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
15581
- const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16072
+ var workspacesPath = () => path15.join(configDir(), "workspaces.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
16073
+ const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
15582
16074
  if (r.status !== 0)
15583
16075
  return null;
15584
16076
  const url = (r.stdout ?? "").trim();
@@ -15605,29 +16097,60 @@ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path15.join(configD
15605
16097
  return { status: "valid", path: file, config: parsed };
15606
16098
  }
15607
16099
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
15608
- };
16100
+ }, trackSummary = (resolution) => resolution.status === "resolved" ? {
16101
+ name: resolution.track?.name ?? null,
16102
+ source: resolution.source,
16103
+ detail: resolution.detail,
16104
+ warnings: resolution.warnings,
16105
+ track: resolution.track,
16106
+ tracks: resolution.tracks,
16107
+ blocking: resolution.blocking
16108
+ } : null;
15609
16109
  var init_vcs_config = __esm(() => {
15610
16110
  init_config();
15611
16111
  init_workspaces();
15612
- init_branch();
16112
+ init_release_tracks();
15613
16113
  });
15614
16114
 
15615
16115
  // packages/workit-core/src/core/branch.ts
15616
16116
  import { execFileSync } from "node:child_process";
15617
- import { createHash as createHash9 } from "node:crypto";
16117
+ import { createHash as createHash10 } from "node:crypto";
15618
16118
  import path16 from "node:path";
15619
16119
  function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
15620
16120
  const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
15621
16121
  const config = readConfig();
15622
16122
  return kind === "branch" ? {
15623
- branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
16123
+ branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null)),
15624
16124
  provenance: { branchPolicy: selected.source }
15625
16125
  } : {
15626
16126
  commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
15627
16127
  provenance: { commitPolicy: selected.source }
15628
16128
  };
15629
16129
  }
15630
- var validateBranchNamePolicy = (name, policy, source) => {
16130
+ var withReleaseTracks = (workspaceRoot, policy) => {
16131
+ const read = workspaceReleaseTracks(workspaceRoot);
16132
+ if (read.error)
16133
+ throw new ReleaseTrackError(read.error);
16134
+ if (read.tracks.length === 0 && read.protectedBranches.length === 0)
16135
+ return policy;
16136
+ const known = new Set(policy.allowed.map((pattern) => pattern.source));
16137
+ const allowed = [...policy.allowed];
16138
+ for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
16139
+ const pattern = branchGlobPattern(glob);
16140
+ if (!known.has(pattern.source)) {
16141
+ known.add(pattern.source);
16142
+ allowed.push(pattern);
16143
+ }
16144
+ }
16145
+ return {
16146
+ ...policy,
16147
+ allowed,
16148
+ protected: new Set([
16149
+ ...policy.protected,
16150
+ ...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
16151
+ ])
16152
+ };
16153
+ }, validateBranchNamePolicy = (name, policy, source) => {
15631
16154
  const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
15632
16155
  if (policy.protected.has(name.toLowerCase())) {
15633
16156
  const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
@@ -15658,7 +16181,7 @@ var validateBranchNamePolicy = (name, policy, source) => {
15658
16181
  const source = resolved.provenance.branchPolicy;
15659
16182
  const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
15660
16183
  const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
15661
- const fingerprint = createHash9("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
16184
+ const fingerprint = createHash10("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
15662
16185
  return {
15663
16186
  branchPolicy,
15664
16187
  namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
@@ -15669,7 +16192,7 @@ var validateBranchNamePolicy = (name, policy, source) => {
15669
16192
  const resolved = effectiveWorkspacePolicy(workspaceRoot, "commit");
15670
16193
  const policy = resolved.commitPolicy;
15671
16194
  const source = resolved.provenance.commitPolicy;
15672
- const fingerprint = createHash9("sha256").update(JSON.stringify({ preset: policy.preset, pattern: policy.pattern ?? null, source })).digest("hex");
16195
+ const fingerprint = createHash10("sha256").update(JSON.stringify({ preset: policy.preset, pattern: policy.pattern ?? null, source })).digest("hex");
15673
16196
  return { policy, source, fingerprint };
15674
16197
  }, validateCommitMessagePolicy = (message, policy, source) => {
15675
16198
  const attempted = message.split(`
@@ -15759,6 +16282,7 @@ var init_branch = __esm(() => {
15759
16282
  init_config();
15760
16283
  init_workspaces();
15761
16284
  init_vcs_config();
16285
+ init_release_tracks();
15762
16286
  });
15763
16287
 
15764
16288
  // packages/workit-core/src/core/route-intent.ts
@@ -15767,11 +16291,11 @@ var init_route_intent = __esm(() => {
15767
16291
  });
15768
16292
 
15769
16293
  // packages/workit-core/src/core/runtime-identity.ts
15770
- import { createHash as createHash10 } from "node:crypto";
15771
- import { readFileSync as readFileSync5 } from "node:fs";
15772
- var sha256Hex = (bytes) => createHash10("sha256").update(bytes).digest("hex"), bundleHashOfFile = (file) => {
16294
+ import { createHash as createHash11 } from "node:crypto";
16295
+ import { readFileSync as readFileSync6 } from "node:fs";
16296
+ var sha256Hex = (bytes) => createHash11("sha256").update(bytes).digest("hex"), bundleHashOfFile = (file) => {
15773
16297
  try {
15774
- return sha256Hex(readFileSync5(file));
16298
+ return sha256Hex(readFileSync6(file));
15775
16299
  } catch {
15776
16300
  return null;
15777
16301
  }
@@ -17796,7 +18320,7 @@ var init_pr_body = __esm(() => {
17796
18320
 
17797
18321
  // packages/workit-core/src/core/pr-create.ts
17798
18322
  import path19 from "node:path";
17799
- import { spawnSync as spawnSync8 } from "node:child_process";
18323
+ import { spawnSync as spawnSync9 } from "node:child_process";
17800
18324
  import { pathToFileURL } from "node:url";
17801
18325
  var safePushUrl = (remote) => {
17802
18326
  const raw = remote.trim();
@@ -17824,7 +18348,7 @@ var safePushUrl = (remote) => {
17824
18348
  const target = safePushUrl(remote);
17825
18349
  if (!target)
17826
18350
  return false;
17827
- const result = spawnSync8("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18351
+ const result = spawnSync9("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
17828
18352
  if (result.status !== 0 && result.status !== 1)
17829
18353
  return false;
17830
18354
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -18400,7 +18924,7 @@ var init_core3 = __esm(() => {
18400
18924
  });
18401
18925
 
18402
18926
  // packages/workit-cli/src/task.ts
18403
- import { readFileSync as readFileSync6 } from "node:fs";
18927
+ import { readFileSync as readFileSync7 } from "node:fs";
18404
18928
  import path22 from "node:path";
18405
18929
  async function readInput(input) {
18406
18930
  if (typeof input === "string")
@@ -18417,7 +18941,7 @@ async function payloadValue(raw, deps) {
18417
18941
  return { ok: true, value: {} };
18418
18942
  let source;
18419
18943
  try {
18420
- source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync6(raw.slice(1))) : raw;
18944
+ source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync7(raw.slice(1))) : raw;
18421
18945
  } catch (error) {
18422
18946
  return {
18423
18947
  ok: false,
@@ -18750,7 +19274,7 @@ function isImplicitTaskForm(argv) {
18750
19274
  const [action, first] = argv;
18751
19275
  if (!action)
18752
19276
  return false;
18753
- if (IMPLICIT.has(action))
19277
+ if (IMPLICIT2.has(action))
18754
19278
  return true;
18755
19279
  if (action === "start")
18756
19280
  return first !== undefined && !first.startsWith("--");
@@ -18948,7 +19472,7 @@ async function run(argv, io) {
18948
19472
  }
18949
19473
  return emit(io, fail("invalid_input", `unknown task action ${action ?? ""}`, { unblock: USAGE }));
18950
19474
  }
18951
- var USAGE = 'workit task status [--all] | task start "<objective>" | task note "<text>" [--next "<t>"] [--objective "<t>"] | task close [--outcome verified|limited|stopped] [--summary "<t>"] [--confirm] | task adopt <id>', IMPLICIT, VALUE_FLAGS, SWITCHES, ENGINE_CODES, engineFailure = (io, result, unblock) => emit(io, fail(ENGINE_CODES[result.code] ?? "failed", `${result.code}: ${result.error}`, {
19475
+ var USAGE = 'workit task status [--all] | task start "<objective>" | task note "<text>" [--next "<t>"] [--objective "<t>"] | task close [--outcome verified|limited|stopped] [--summary "<t>"] [--confirm] | task adopt <id>', IMPLICIT2, VALUE_FLAGS, SWITCHES, ENGINE_CODES, engineFailure = (io, result, unblock) => emit(io, fail(ENGINE_CODES[result.code] ?? "failed", `${result.code}: ${result.error}`, {
18952
19476
  data: { code: result.code },
18953
19477
  ...unblock ? { unblock } : result.code === "busy" ? { unblock: "retry the same command" } : {}
18954
19478
  })), OUTCOMES, rootFor = (io) => io.env.WORKFLOW_WORKSPACE_ROOT || checkRoot(io.cwd);
@@ -18956,7 +19480,7 @@ var init_task2 = __esm(() => {
18956
19480
  init_check_config();
18957
19481
  init_output();
18958
19482
  init_task();
18959
- IMPLICIT = new Set(["status", "note", "adopt"]);
19483
+ IMPLICIT2 = new Set(["status", "note", "adopt"]);
18960
19484
  VALUE_FLAGS = new Set(["--next", "--objective", "--outcome", "--summary"]);
18961
19485
  SWITCHES = new Set(["--all", "--json", "--confirm"]);
18962
19486
  ENGINE_CODES = {
@@ -61542,7 +62066,7 @@ var init_gitignore = __esm(() => {
61542
62066
  // packages/workit-core/src/core/changelog.ts
61543
62067
  import fs16 from "node:fs";
61544
62068
  import path23 from "node:path";
61545
- import { createHash as createHash11 } from "node:crypto";
62069
+ import { createHash as createHash12 } from "node:crypto";
61546
62070
  function normalizeBullet(text) {
61547
62071
  text = text.trim();
61548
62072
  const m = BULLET_RE.exec(text);
@@ -61829,7 +62353,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61829
62353
 
61830
62354
  ## [Unreleased]
61831
62355
 
61832
- `, digestBytes2 = (value) => createHash11("sha256").update(value).digest("hex"), readChangelogSource = (target) => {
62356
+ `, digestBytes2 = (value) => createHash12("sha256").update(value).digest("hex"), readChangelogSource = (target) => {
61833
62357
  if (!fs16.existsSync(target)) {
61834
62358
  const bytes = Buffer.from(SKELETON, "utf8");
61835
62359
  return { exists: false, bytes, text: SKELETON };
@@ -61869,13 +62393,13 @@ var init_changelog = __esm(() => {
61869
62393
  });
61870
62394
 
61871
62395
  // packages/workit-core/src/core/hygiene.ts
61872
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
62396
+ import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
61873
62397
  import path24 from "node:path";
61874
62398
  var repoRoot2, templatesDir = () => path24.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
61875
62399
  if (!existsSync9(path24.join(root, "package.json")))
61876
62400
  return null;
61877
62401
  try {
61878
- return JSON.parse(readFileSync8(path24.join(root, "package.json"), "utf8"));
62402
+ return JSON.parse(readFileSync9(path24.join(root, "package.json"), "utf8"));
61879
62403
  } catch {
61880
62404
  return null;
61881
62405
  }
@@ -61911,7 +62435,7 @@ var repoRoot2, templatesDir = () => path24.join(repoRoot2, "templates", "hygiene
61911
62435
  const tpl = path24.join(templatesDir(), file);
61912
62436
  if (!existsSync9(tpl))
61913
62437
  continue;
61914
- const content = readFileSync8(tpl, "utf8").replace(/<PROJECT>/g, path24.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
62438
+ const content = readFileSync9(tpl, "utf8").replace(/<PROJECT>/g, path24.basename(root)).replace(/<YEAR>/g, String(new Date().getFullYear())).replace(/<HOLDER>\s*/g, licenseHolder(root));
61915
62439
  planned.push({ path: path24.join(root, file), content });
61916
62440
  }
61917
62441
  return planned;
@@ -61923,13 +62447,13 @@ var init_hygiene = __esm(() => {
61923
62447
  });
61924
62448
 
61925
62449
  // packages/workit-cli/src/admin/setup-state.ts
61926
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
62450
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
61927
62451
  import path25 from "node:path";
61928
62452
  var classifySetupFile = (dir, name) => {
61929
62453
  const file = path25.join(dir, name);
61930
62454
  let raw;
61931
62455
  try {
61932
- raw = readFileSync9(file, "utf8");
62456
+ raw = readFileSync10(file, "utf8");
61933
62457
  } catch {
61934
62458
  if (existsSync10(file)) {
61935
62459
  return { file, status: "malformed", error: `${file} is not readable` };
@@ -61978,7 +62502,7 @@ function mergeOpenCodePlugins(plugin, pin) {
61978
62502
  return { config, changed };
61979
62503
  }
61980
62504
  function mergeOpenCodeConfig(config, pin) {
61981
- const base = isRecord3(config) ? { ...config } : {};
62505
+ const base = isRecord4(config) ? { ...config } : {};
61982
62506
  const changed = [];
61983
62507
  const plugins = mergeOpenCodePlugins(base.plugin, pin);
61984
62508
  if (plugins.changed.length > 0) {
@@ -61988,7 +62512,7 @@ function mergeOpenCodeConfig(config, pin) {
61988
62512
  return { config: base, changed };
61989
62513
  }
61990
62514
  function mergeCursorEnabledPlugins(enabled) {
61991
- const prev = isRecord3(enabled) ? { ...enabled } : {};
62515
+ const prev = isRecord4(enabled) ? { ...enabled } : {};
61992
62516
  const next = { ...prev, workit: true };
61993
62517
  delete next["workflow-toolkit"];
61994
62518
  delete next["local/workflow-toolkit"];
@@ -62010,7 +62534,7 @@ function mergeCursorPluginDirs(pluginDirs, pluginDir) {
62010
62534
  return { config: next, changed };
62011
62535
  }
62012
62536
  function mergeCursorSettings(settings, pluginDir) {
62013
- const base = isRecord3(settings) ? { ...settings } : {};
62537
+ const base = isRecord4(settings) ? { ...settings } : {};
62014
62538
  const changed = [];
62015
62539
  const enabled = mergeCursorEnabledPlugins(base.enabled_plugins);
62016
62540
  if (enabled.changed.length > 0) {
@@ -62025,8 +62549,8 @@ function mergeCursorSettings(settings, pluginDir) {
62025
62549
  return { config: base, changed };
62026
62550
  }
62027
62551
  function mergeCursorMcp(mcp, serverName, server) {
62028
- const base = isRecord3(mcp) ? { ...mcp } : {};
62029
- const servers = isRecord3(base.mcpServers) ? { ...base.mcpServers } : {};
62552
+ const base = isRecord4(mcp) ? { ...mcp } : {};
62553
+ const servers = isRecord4(base.mcpServers) ? { ...base.mcpServers } : {};
62030
62554
  delete servers["workflow-toolkit"];
62031
62555
  servers[serverName] = server;
62032
62556
  const changed = JSON.stringify(servers) !== JSON.stringify(base.mcpServers) ? ["mcpServers"] : [];
@@ -62034,7 +62558,7 @@ function mergeCursorMcp(mcp, serverName, server) {
62034
62558
  return { config: base, changed };
62035
62559
  }
62036
62560
  function cursorHookDrift(installed) {
62037
- if (!isRecord3(installed) || !isRecord3(installed.hooks))
62561
+ if (!isRecord4(installed) || !isRecord4(installed.hooks))
62038
62562
  return ["hooks file is not a hook map"];
62039
62563
  const hooks = installed.hooks;
62040
62564
  const drift = [];
@@ -62044,7 +62568,7 @@ function cursorHookDrift(installed) {
62044
62568
  continue;
62045
62569
  const canonical = canonicalHookEntry(event);
62046
62570
  const entry = Array.isArray(list) ? list[0] : undefined;
62047
- if (!isRecord3(entry) || entry.command !== canonical.command || event === "preToolUse" && entry.matcher !== canonical.matcher)
62571
+ if (!isRecord4(entry) || entry.command !== canonical.command || event === "preToolUse" && entry.matcher !== canonical.matcher)
62048
62572
  drift.push(event);
62049
62573
  }
62050
62574
  return drift;
@@ -62068,7 +62592,7 @@ function cursorHooksEntry(_packageDir) {
62068
62592
  args: []
62069
62593
  };
62070
62594
  }
62071
- var OPENCODE_NPM_PIN = "@brainervirus/workit-opencode", isRecord3 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest", CURSOR_HOOK_RUN_COMMAND, CURSOR_PRETOOLUSE_MATCHER = "Write|Edit|Delete|Shell|Remove|ApplyPatch|Apply_Patch|Patch|Rename|Mkdir|Mv|Cp|Touch", canonicalHookEntry = (event) => event === "preToolUse" ? { command: CURSOR_HOOK_RUN_COMMAND, matcher: CURSOR_PRETOOLUSE_MATCHER, failClosed: true } : { command: CURSOR_HOOK_RUN_COMMAND, failClosed: true };
62595
+ var OPENCODE_NPM_PIN = "@brainervirus/workit-opencode", isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), named = (s, name) => s === name || s.startsWith(`${name}@`), CURSOR_RUNTIME_PACKAGE = "@brainervirus/workit-cursor@latest", CURSOR_HOOK_RUN_COMMAND, CURSOR_PRETOOLUSE_MATCHER = "Write|Edit|Delete|Shell|Remove|ApplyPatch|Apply_Patch|Patch|Rename|Mkdir|Mv|Cp|Touch", canonicalHookEntry = (event) => event === "preToolUse" ? { command: CURSOR_HOOK_RUN_COMMAND, matcher: CURSOR_PRETOOLUSE_MATCHER, failClosed: true } : { command: CURSOR_HOOK_RUN_COMMAND, failClosed: true };
62072
62596
  var init_registration = __esm(() => {
62073
62597
  CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
62074
62598
  });
@@ -62087,12 +62611,12 @@ var init_support_matrix = __esm(() => {
62087
62611
  });
62088
62612
 
62089
62613
  // packages/workit-cli/src/admin/host-install.ts
62090
- import { spawnSync as spawnSync9 } from "node:child_process";
62614
+ import { spawnSync as spawnSync10 } from "node:child_process";
62091
62615
  import {
62092
62616
  accessSync,
62093
62617
  constants as constants2,
62094
62618
  existsSync as existsSync11,
62095
- readFileSync as readFileSync10,
62619
+ readFileSync as readFileSync11,
62096
62620
  readdirSync as readdirSync4,
62097
62621
  realpathSync as realpathSync6,
62098
62622
  statSync as statSync2
@@ -62358,7 +62882,7 @@ function runHostCommand(step, context = {}) {
62358
62882
  stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
62359
62883
  };
62360
62884
  }
62361
- const result = spawnSync9(step.command, step.args, {
62885
+ const result = spawnSync10(step.command, step.args, {
62362
62886
  cwd: step.cwd,
62363
62887
  env: (() => {
62364
62888
  const home = context.home ?? context.env?.HOME ?? os6.homedir();
@@ -62382,7 +62906,7 @@ function runHostCommand(step, context = {}) {
62382
62906
  }
62383
62907
  var resolveWindowsShimScript = (shim) => {
62384
62908
  try {
62385
- const contents = readFileSync10(shim, "utf8");
62909
+ const contents = readFileSync11(shim, "utf8");
62386
62910
  return resolveWindowsShimEntry(contents, shim);
62387
62911
  } catch {
62388
62912
  return null;
@@ -62403,7 +62927,7 @@ var resolveWindowsShimScript = (shim) => {
62403
62927
  }, isPiWorkitInstalled = (home, env = process.env) => {
62404
62928
  const settings = path27.join(env.PI_CODING_AGENT_DIR ?? path27.join(home, ".pi", "agent"), "settings.json");
62405
62929
  try {
62406
- const document2 = JSON.parse(readFileSync10(settings, "utf8"));
62930
+ const document2 = JSON.parse(readFileSync11(settings, "utf8"));
62407
62931
  return (document2.packages ?? []).some((entry) => {
62408
62932
  const source = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.source : entry;
62409
62933
  const value = String(source).replaceAll("\\", "/").replace(/\/+$/, "");
@@ -62418,7 +62942,7 @@ var resolveWindowsShimScript = (shim) => {
62418
62942
  path27.join(root, "package.json")
62419
62943
  ]) {
62420
62944
  try {
62421
- const manifest = JSON.parse(readFileSync10(file, "utf8"));
62945
+ const manifest = JSON.parse(readFileSync11(file, "utf8"));
62422
62946
  const repository = typeof manifest.repository === "string" ? manifest.repository : manifest.repository?.url;
62423
62947
  if ((manifest.name === "workit" || manifest.name === "@brainervirus/workit-codex") && repository?.replace(/\.git$/, "").toLowerCase() === "https://github.com/brainervirus/workit")
62424
62948
  return true;
@@ -62459,7 +62983,7 @@ var resolveWindowsShimScript = (shim) => {
62459
62983
  return relative === "" || !relative.startsWith("..") && !path27.isAbsolute(relative);
62460
62984
  }, isCanonicalClaudePlugin = (root) => {
62461
62985
  try {
62462
- const manifest = JSON.parse(readFileSync10(path27.join(root, ".claude-plugin", "plugin.json"), "utf8"));
62986
+ const manifest = JSON.parse(readFileSync11(path27.join(root, ".claude-plugin", "plugin.json"), "utf8"));
62463
62987
  const repository = typeof manifest.repository === "string" ? manifest.repository : manifest.repository?.url;
62464
62988
  return manifest.name === "workit" && repository?.replace(/\.git$/, "").toLowerCase() === "https://github.com/brainervirus/workit";
62465
62989
  } catch {
@@ -62469,7 +62993,7 @@ var resolveWindowsShimScript = (shim) => {
62469
62993
  const file = path27.join(claudeConfigDir(home, env), "plugins", "installed_plugins.json");
62470
62994
  let registry;
62471
62995
  try {
62472
- registry = JSON.parse(readFileSync10(file, "utf8"));
62996
+ registry = JSON.parse(readFileSync11(file, "utf8"));
62473
62997
  } catch {
62474
62998
  return [];
62475
62999
  }
@@ -62518,7 +63042,7 @@ var init_host_install = __esm(() => {
62518
63042
  });
62519
63043
 
62520
63044
  // packages/workit-core/src/core/skill-manifests.ts
62521
- import { existsSync as existsSync12, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
63045
+ import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
62522
63046
  import path28 from "node:path";
62523
63047
  var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? readdirSync5(root).filter((name) => existsSync12(path28.join(root, name, "SKILL.md"))).toSorted() : [], validateSkillManifests = (root, expected, label) => {
62524
63048
  const actual = skillManifestNames(root);
@@ -62536,7 +63060,7 @@ var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? re
62536
63060
  const file = path28.join(dir, entry.name);
62537
63061
  if (entry.isDirectory())
62538
63062
  pending.push(file);
62539
- else if ((statSync3(file).mode & 73) !== 0 || readFileSync11(file).subarray(0, 2).toString("latin1") === "#!") {
63063
+ else if ((statSync3(file).mode & 73) !== 0 || readFileSync12(file).subarray(0, 2).toString("latin1") === "#!") {
62540
63064
  return `Cursor vendor contains active file: ${file}`;
62541
63065
  }
62542
63066
  }
@@ -62561,13 +63085,13 @@ var init_skill_manifests = __esm(() => {
62561
63085
  });
62562
63086
 
62563
63087
  // packages/workit-cli/src/admin/doctor.ts
62564
- import { spawnSync as spawnSync10 } from "node:child_process";
63088
+ import { spawnSync as spawnSync11 } from "node:child_process";
62565
63089
  import {
62566
63090
  existsSync as existsSync13,
62567
63091
  lstatSync as lstatSync4,
62568
63092
  mkdirSync as mkdirSync4,
62569
63093
  mkdtempSync,
62570
- readFileSync as readFileSync12,
63094
+ readFileSync as readFileSync13,
62571
63095
  readlinkSync as readlinkSync2,
62572
63096
  rmSync as rmSync2,
62573
63097
  statSync as statSync4,
@@ -62611,14 +63135,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62611
63135
  };
62612
63136
  }, readJson = (p) => {
62613
63137
  try {
62614
- const parsed = JSON.parse(readFileSync12(p, "utf8"));
63138
+ const parsed = JSON.parse(readFileSync13(p, "utf8"));
62615
63139
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
62616
63140
  } catch {
62617
63141
  return null;
62618
63142
  }
62619
63143
  }, parsesAsConfigObject = (p) => {
62620
63144
  try {
62621
- return isConfigObject(JSON.parse(readFileSync12(p, "utf8")));
63145
+ return isConfigObject(JSON.parse(readFileSync13(p, "utf8")));
62622
63146
  } catch {
62623
63147
  return false;
62624
63148
  }
@@ -62645,7 +63169,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62645
63169
  }
62646
63170
  return false;
62647
63171
  }, versionOf = (bin, env, timeout) => {
62648
- const r = spawnSync10(bin, ["--version"], { encoding: "utf8", env, timeout });
63172
+ const r = spawnSync11(bin, ["--version"], { encoding: "utf8", env, timeout });
62649
63173
  if (r.error)
62650
63174
  return null;
62651
63175
  return (r.stdout ?? "").trim();
@@ -62890,11 +63414,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62890
63414
  }, validNodeEntry = (entry, runtime, env) => {
62891
63415
  try {
62892
63416
  const stat = statSync4(entry);
62893
- if (!stat.isFile() || stat.size === 0 || !readFileSync12(entry, "utf8").startsWith(`#!/usr/bin/env node
63417
+ if (!stat.isFile() || stat.size === 0 || !readFileSync13(entry, "utf8").startsWith(`#!/usr/bin/env node
62894
63418
  `)) {
62895
63419
  return false;
62896
63420
  }
62897
- return spawnSync10(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63421
+ return spawnSync11(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
62898
63422
  } catch {
62899
63423
  return false;
62900
63424
  }
@@ -63108,7 +63632,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63108
63632
  if (!cmd)
63109
63633
  return null;
63110
63634
  try {
63111
- const r = spawnSync10(cmd, ["view", pkgName, "version"], {
63635
+ const r = spawnSync11(cmd, ["view", pkgName, "version"], {
63112
63636
  encoding: "utf8",
63113
63637
  timeout: 20000,
63114
63638
  env: res.env
@@ -63419,12 +63943,27 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63419
63943
  fix: `Choose a matching workspace with WORKFLOW_WORKSPACE_NAME or repair ${file}`
63420
63944
  };
63421
63945
  }
63422
- if (match)
63423
- return {
63424
- id: "workspace_mismatch",
63425
- status: "pass",
63426
- detail: `current directory matches workspace "${match.name}"`
63427
- };
63946
+ if (match) {
63947
+ const matched = `current directory matches workspace "${match.name}"`;
63948
+ const tracks = releaseTracksReport(res.cwd);
63949
+ const lines = describeReleaseTracks(tracks);
63950
+ const detail = [matched, ...lines].join("; ");
63951
+ if (tracks.error)
63952
+ return {
63953
+ id: "workspace_mismatch",
63954
+ status: "fail",
63955
+ detail,
63956
+ fix: `Repair releaseTracks in ${file} (or upgrade Workit for a critical field)`
63957
+ };
63958
+ if (tracks.warnings.length)
63959
+ return {
63960
+ id: "workspace_mismatch",
63961
+ status: "warn",
63962
+ detail,
63963
+ fix: `Fix the reported releaseTracks fields in ${file}, or pass --track <name> where the track is not determined`
63964
+ };
63965
+ return { id: "workspace_mismatch", status: "pass", detail };
63966
+ }
63428
63967
  return {
63429
63968
  id: "workspace_mismatch",
63430
63969
  status: "fail",
@@ -63433,7 +63972,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63433
63972
  };
63434
63973
  }, isPlaceholder = (p) => {
63435
63974
  try {
63436
- return readFileSync12(p, "utf8").trim() === TOKEN_PLACEHOLDER2;
63975
+ return readFileSync13(p, "utf8").trim() === TOKEN_PLACEHOLDER2;
63437
63976
  } catch {
63438
63977
  return false;
63439
63978
  }
@@ -63522,7 +64061,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63522
64061
  }, defaultIdentityProbes = (env) => ({
63523
64062
  origin: (cwd) => {
63524
64063
  try {
63525
- const out = spawnSync10("git", ["remote", "get-url", "origin"], {
64064
+ const out = spawnSync11("git", ["remote", "get-url", "origin"], {
63526
64065
  cwd,
63527
64066
  encoding: "utf8",
63528
64067
  env
@@ -63534,7 +64073,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63534
64073
  },
63535
64074
  ghLogin: (e) => {
63536
64075
  try {
63537
- const out = spawnSync10("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64076
+ const out = spawnSync11("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
63538
64077
  const login = (out.stdout ?? "").trim();
63539
64078
  return out.status === 0 && login ? login : null;
63540
64079
  } catch {
@@ -63543,7 +64082,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63543
64082
  },
63544
64083
  glabLogin: (e) => {
63545
64084
  try {
63546
- const out = spawnSync10("glab", ["api", "user", "--jq", ".username"], {
64085
+ const out = spawnSync11("glab", ["api", "user", "--jq", ".username"], {
63547
64086
  encoding: "utf8",
63548
64087
  env: e
63549
64088
  });
@@ -63555,7 +64094,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63555
64094
  },
63556
64095
  sshLogin: (host) => {
63557
64096
  try {
63558
- const out = spawnSync10("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64097
+ const out = spawnSync11("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
63559
64098
  encoding: "utf8",
63560
64099
  env
63561
64100
  });
@@ -63749,6 +64288,7 @@ var init_doctor = __esm(() => {
63749
64288
  init_store_lock();
63750
64289
  init_runtime_identity();
63751
64290
  init_boundary();
64291
+ init_release_tracks();
63752
64292
  init_config();
63753
64293
  init_package_root();
63754
64294
  init_registration();
@@ -63797,7 +64337,7 @@ import {
63797
64337
  lstatSync as lstatSync5,
63798
64338
  mkdirSync as mkdirSync5,
63799
64339
  mkdtempSync as mkdtempSync2,
63800
- readFileSync as readFileSync13,
64340
+ readFileSync as readFileSync14,
63801
64341
  readdirSync as readdirSync6,
63802
64342
  realpathSync as realpathSync7,
63803
64343
  renameSync as renameSync3,
@@ -63966,7 +64506,7 @@ function buildSetupPreview(values, opts = {}) {
63966
64506
  if (values.applyProject) {
63967
64507
  const root = path30.resolve(opts.cwd ?? process.cwd());
63968
64508
  const gitignorePath = path30.join(root, ".gitignore");
63969
- const existing = existsSync14(gitignorePath) ? readFileSync13(gitignorePath, "utf8") : "";
64509
+ const existing = existsSync14(gitignorePath) ? readFileSync14(gitignorePath, "utf8") : "";
63970
64510
  const existingLines = new Set(existing.split(`
63971
64511
  `).map((l) => l.trim()).filter(Boolean));
63972
64512
  const entries = GITIGNORE_ENTRIES.filter((e) => e.trim() !== "" && !existingLines.has(e.trim()));
@@ -63997,7 +64537,7 @@ function deepMerge(base, patch) {
63997
64537
  const out = { ...base };
63998
64538
  for (const [key, value] of Object.entries(patch)) {
63999
64539
  const prev = out[key];
64000
- out[key] = isRecord4(prev) && isRecord4(value) ? deepMerge(prev, value) : value;
64540
+ out[key] = isRecord5(prev) && isRecord5(value) ? deepMerge(prev, value) : value;
64001
64541
  }
64002
64542
  return out;
64003
64543
  }
@@ -64324,8 +64864,8 @@ function applyCursorMcp(root, res) {
64324
64864
  }
64325
64865
  const mcpBase = mcpExisting.kind === "record" ? mcpExisting.value : {};
64326
64866
  const server = cursorMcpServerEntry(res.cursorPluginDir);
64327
- const servers = isRecord4(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
64328
- const current = isRecord4(servers.workit) ? servers.workit : {};
64867
+ const servers = isRecord5(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
64868
+ const current = isRecord5(servers.workit) ? servers.workit : {};
64329
64869
  const previousArgs = Array.isArray(current.args) ? current.args.map(String) : [];
64330
64870
  const previousPackage = previousArgs.find((arg) => arg.startsWith("--package=@brainervirus/workit-cursor@") && arg !== `--package=${CURSOR_RUNTIME_PACKAGE}`);
64331
64871
  if (previousPackage) {
@@ -64586,7 +65126,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64586
65126
  }, configuredTokenPath = (config, ...keys) => {
64587
65127
  let cursor = config;
64588
65128
  for (const key of keys) {
64589
- if (!isRecord4(cursor))
65129
+ if (!isRecord5(cursor))
64590
65130
  return null;
64591
65131
  cursor = cursor[key];
64592
65132
  }
@@ -64599,8 +65139,8 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64599
65139
  return configured ?? fallback;
64600
65140
  }, readConfigRecord = (file) => {
64601
65141
  try {
64602
- const value = JSON.parse(readFileSync13(file, "utf8"));
64603
- return isRecord4(value) ? value : null;
65142
+ const value = JSON.parse(readFileSync14(file, "utf8"));
65143
+ return isRecord5(value) ? value : null;
64604
65144
  } catch {
64605
65145
  return null;
64606
65146
  }
@@ -64617,10 +65157,10 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64617
65157
  cursorMcp: options.cursorMcp ?? path30.join(home, ".cursor", "mcp.json"),
64618
65158
  cursorPluginDir: options.cursorPluginDir ?? path30.join(home, ".cursor", "plugins", "local", "workit")
64619
65159
  };
64620
- }, isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readExisting = (p) => {
65160
+ }, isRecord5 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readExisting = (p) => {
64621
65161
  let raw;
64622
65162
  try {
64623
- raw = readFileSync13(p, "utf8");
65163
+ raw = readFileSync14(p, "utf8");
64624
65164
  } catch {
64625
65165
  if (existsSync14(p))
64626
65166
  return { kind: "malformed", error: `${p} is not readable` };
@@ -64632,12 +65172,12 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64632
65172
  } catch {
64633
65173
  return { kind: "malformed", error: `${p} is not valid JSON` };
64634
65174
  }
64635
- if (isRecord4(value))
65175
+ if (isRecord5(value))
64636
65176
  return { kind: "record", value, raw };
64637
65177
  return { kind: "malformed", error: `${p} is not a JSON object` };
64638
65178
  }, readFileSafe = (p) => {
64639
65179
  try {
64640
- return readFileSync13(p, "utf8");
65180
+ return readFileSync14(p, "utf8");
64641
65181
  } catch {
64642
65182
  return null;
64643
65183
  }
@@ -64652,7 +65192,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64652
65192
  }
64653
65193
  }, isAdapter = (root, platform) => {
64654
65194
  try {
64655
- const pkg = JSON.parse(readFileSync13(path30.join(root, "package.json"), "utf8"));
65195
+ const pkg = JSON.parse(readFileSync14(path30.join(root, "package.json"), "utf8"));
64656
65196
  return pkg.name === `@brainervirus/workit-${platform}`;
64657
65197
  } catch {
64658
65198
  return false;
@@ -64681,7 +65221,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64681
65221
  if (entry.isDirectory()) {
64682
65222
  if (!statSync5(installed).isDirectory() || !samePluginContent(source, installed, path30.join(relative, entry.name)))
64683
65223
  return false;
64684
- } else if (!readFileSync13(source).equals(readFileSync13(installed))) {
65224
+ } else if (!readFileSync14(source).equals(readFileSync14(installed))) {
64685
65225
  return false;
64686
65226
  }
64687
65227
  }
@@ -64709,7 +65249,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64709
65249
  continue;
64710
65250
  if (existsSync14(path30.join(sourceRules, entry.name)))
64711
65251
  continue;
64712
- preserved.set(entry.name, readFileSync13(path30.join(installedRules, entry.name)));
65252
+ preserved.set(entry.name, readFileSync14(path30.join(installedRules, entry.name)));
64713
65253
  }
64714
65254
  } catch {}
64715
65255
  return preserved;
@@ -64763,10 +65303,10 @@ var init_setup = __esm(() => {
64763
65303
  });
64764
65304
 
64765
65305
  // packages/workit-core/src/core/branch-policy.ts
64766
- import { spawnSync as spawnSync11 } from "node:child_process";
65306
+ import { spawnSync as spawnSync12 } from "node:child_process";
64767
65307
  var PREFIXES, detectBranchPolicy = (workspaceRoot) => {
64768
65308
  const branchExists = (name) => {
64769
- const r = spawnSync11("git", ["branch", "--list", name], {
65309
+ const r = spawnSync12("git", ["branch", "--list", name], {
64770
65310
  cwd: workspaceRoot,
64771
65311
  encoding: "utf8"
64772
65312
  });
@@ -64826,7 +65366,7 @@ var init_branch_policy = __esm(() => {
64826
65366
  });
64827
65367
 
64828
65368
  // packages/workit-core/src/core/workspace-branch-policy.ts
64829
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
65369
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
64830
65370
  import path31 from "node:path";
64831
65371
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
64832
65372
  function applyWorkspaceBranchPolicy(opts) {
@@ -64924,7 +65464,7 @@ function applyWorkspaceBranchPolicy(opts) {
64924
65464
  }
64925
65465
  var readFileSafe2 = (p) => {
64926
65466
  try {
64927
- return readFileSync14(p, "utf8");
65467
+ return readFileSync15(p, "utf8");
64928
65468
  } catch {
64929
65469
  return null;
64930
65470
  }
@@ -65103,17 +65643,18 @@ function setProfileEditorValue(profile, field, value, defaults = {}) {
65103
65643
  };
65104
65644
  }
65105
65645
  function trackEditorValue(track, field) {
65646
+ const source = track.versionSource ?? { kind: "git-tag" };
65106
65647
  if (field.startsWith("naming."))
65107
- return track.naming[field.slice(7)];
65648
+ return track.naming?.[field.slice(7)] ?? "";
65108
65649
  if (field === "mergeBackBranches" || field === "requiredChecks")
65109
- return track[field].join(", ");
65650
+ return (track[field] ?? []).join(", ");
65110
65651
  if (field.startsWith("versionSource.")) {
65111
65652
  const key = field.slice(14);
65112
65653
  if (key === "kind")
65113
- return track.versionSource.kind;
65654
+ return source.kind;
65114
65655
  if (key === "path")
65115
- return track.versionSource.kind === "package-json" ? track.versionSource.path : "";
65116
- return track.versionSource.kind === "package-json" ? track.versionSource.field : "";
65656
+ return source.kind === "package-json" ? source.path : "";
65657
+ return source.kind === "package-json" ? source.field : "";
65117
65658
  }
65118
65659
  return String(track[field] ?? "");
65119
65660
  }
@@ -65123,12 +65664,13 @@ function setTrackEditorValue(track, field, value) {
65123
65664
  if (field === "mergeBackBranches" || field === "requiredChecks")
65124
65665
  return { ...track, [field]: splitList(value) };
65125
65666
  if (field.startsWith("versionSource.")) {
65667
+ const current = track.versionSource ?? { kind: "git-tag" };
65126
65668
  const key = field.slice(14);
65127
- const kind = key === "kind" ? value : track.versionSource.kind;
65669
+ const kind = key === "kind" ? value : current.kind;
65128
65670
  const versionSource = kind === "package-json" ? {
65129
65671
  kind,
65130
- path: key === "path" ? value : track.versionSource.kind === kind ? track.versionSource.path : "package.json",
65131
- field: key === "field" ? value : track.versionSource.kind === kind ? track.versionSource.field : "version"
65672
+ path: key === "path" ? value : current.kind === kind ? current.path : "package.json",
65673
+ field: key === "field" ? value : current.kind === kind ? current.field : "version"
65132
65674
  } : kind === "manual" ? { kind } : { kind: "git-tag" };
65133
65675
  return { ...track, versionSource };
65134
65676
  }
@@ -65762,13 +66304,13 @@ var init_wizard_state = __esm(() => {
65762
66304
  });
65763
66305
 
65764
66306
  // packages/workit-cli/src/admin/uninstall.ts
65765
- import { existsSync as existsSync16, readFileSync as readFileSync15, realpathSync as realpathSync8, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
66307
+ import { existsSync as existsSync16, readFileSync as readFileSync16, realpathSync as realpathSync8, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
65766
66308
  import os9 from "node:os";
65767
66309
  import path33 from "node:path";
65768
66310
  function cleanCursorSettings(settings, pluginDir) {
65769
66311
  const next = { ...settings };
65770
66312
  let changed = false;
65771
- if (isRecord5(next.enabled_plugins)) {
66313
+ if (isRecord6(next.enabled_plugins)) {
65772
66314
  const enabled = { ...next.enabled_plugins };
65773
66315
  for (const identity of ["workit", ...CURSOR_LEGACY_IDENTITIES]) {
65774
66316
  if (identity in enabled) {
@@ -65809,7 +66351,7 @@ function cleanOpenCodeConfig(config) {
65809
66351
  function cleanCursorMcp(mcp) {
65810
66352
  const next = { ...mcp };
65811
66353
  let changed = false;
65812
- if (isRecord5(next.mcpServers)) {
66354
+ if (isRecord6(next.mcpServers)) {
65813
66355
  const servers = { ...next.mcpServers };
65814
66356
  for (const name of ["workit", "workflow-toolkit"]) {
65815
66357
  if (name in servers) {
@@ -65834,7 +66376,7 @@ function cleanPiConfig(config) {
65834
66376
  changed = true;
65835
66377
  }
65836
66378
  }
65837
- const pi = isRecord5(next.pi) ? { ...next.pi } : null;
66379
+ const pi = isRecord6(next.pi) ? { ...next.pi } : null;
65838
66380
  if (pi && Array.isArray(pi.extensions)) {
65839
66381
  const kept = pi.extensions.filter((entry) => !String(entry).includes("workit"));
65840
66382
  if (kept.length !== pi.extensions.length) {
@@ -65982,10 +66524,10 @@ var resolveUninstallPaths = (options = {}) => {
65982
66524
  codexPluginDir: path33.join(home, ".codex", "plugins", "workit"),
65983
66525
  piConfig: path33.join(home, ".pi", "config.json")
65984
66526
  };
65985
- }, isRecord5 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readJsonRecord = (file) => {
66527
+ }, isRecord6 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readJsonRecord = (file) => {
65986
66528
  let raw;
65987
66529
  try {
65988
- raw = readFileSync15(file, "utf8");
66530
+ raw = readFileSync16(file, "utf8");
65989
66531
  } catch {
65990
66532
  if (existsSync16(file))
65991
66533
  return { kind: "malformed", error: `${file} is not readable` };
@@ -65993,7 +66535,7 @@ var resolveUninstallPaths = (options = {}) => {
65993
66535
  }
65994
66536
  try {
65995
66537
  const value = JSON.parse(raw);
65996
- return isRecord5(value) ? { kind: "record", value } : { kind: "malformed", error: `${file} is not a JSON object` };
66538
+ return isRecord6(value) ? { kind: "record", value } : { kind: "malformed", error: `${file} is not a JSON object` };
65997
66539
  } catch {
65998
66540
  return { kind: "malformed", error: `${file} is not valid JSON` };
65999
66541
  }
@@ -66035,7 +66577,7 @@ var resolveUninstallPaths = (options = {}) => {
66035
66577
  `;
66036
66578
  let current;
66037
66579
  try {
66038
- current = readFileSync15(target, "utf8");
66580
+ current = readFileSync16(target, "utf8");
66039
66581
  } catch (error) {
66040
66582
  return {
66041
66583
  status: "failed",
@@ -68305,7 +68847,7 @@ var init_steps = __esm(async () => {
68305
68847
  import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, unlinkSync as unlinkSync3 } from "node:fs";
68306
68848
  import os11 from "node:os";
68307
68849
  import path34 from "node:path";
68308
- var REDACTED = "[REDACTED]", DEFAULT_MAX_RATE = 20, DEFAULT_RATE_WINDOW_MS = 1000, DEFAULT_MAX_FIELD_LENGTH = 200, DEFAULT_MAX_STACK_LINES = 30, DAY_FILE, RETAINED_DAYS = 7, SECRET_VALUE, KEY_EQ_VALUE, URL_QUERY, splitKey = (key) => key.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/).map((word) => word.toLowerCase()), SENSITIVE_WORDS, STACK_WORDS, escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
68850
+ var REDACTED = "[REDACTED]", DEFAULT_MAX_RATE = 20, DEFAULT_RATE_WINDOW_MS = 1000, DEFAULT_MAX_FIELD_LENGTH = 200, DEFAULT_MAX_STACK_LINES = 30, DAY_FILE, RETAINED_DAYS = 7, SECRET_VALUE, KEY_EQ_VALUE, URL_QUERY, splitKey = (key) => key.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/).map((word) => word.toLowerCase()), SENSITIVE_WORDS, STACK_WORDS, escapeRegExp3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), resolveStateDir = () => {
68309
68851
  const override = process.env.WORKFLOW_TOOLKIT_STATE;
68310
68852
  if (override)
68311
68853
  return override;
@@ -68507,7 +69049,7 @@ var init_logger = __esm(() => {
68507
69049
  "query"
68508
69050
  ]);
68509
69051
  STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
68510
- homePattern = new RegExp(`${escapeRegExp2(os11.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
69052
+ homePattern = new RegExp(`${escapeRegExp3(os11.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
68511
69053
  DEFAULT_OPTIONS = {
68512
69054
  maxFieldLength: DEFAULT_MAX_FIELD_LENGTH,
68513
69055
  maxStackLines: DEFAULT_MAX_STACK_LINES
@@ -68772,12 +69314,12 @@ var init_init = __esm(() => {
68772
69314
  });
68773
69315
 
68774
69316
  // packages/workit-cli/src/upgrade.ts
68775
- import { spawnSync as spawnSync12 } from "node:child_process";
69317
+ import { spawnSync as spawnSync13 } from "node:child_process";
68776
69318
  import {
68777
69319
  existsSync as existsSync17,
68778
69320
  mkdtempSync as mkdtempSync3,
68779
69321
  rmSync as rmSync6,
68780
- readFileSync as readFileSync16,
69322
+ readFileSync as readFileSync17,
68781
69323
  mkdirSync as mkdirSync8,
68782
69324
  copyFileSync as copyFileSync5,
68783
69325
  chmodSync,
@@ -68786,7 +69328,7 @@ import {
68786
69328
  } from "node:fs";
68787
69329
  import path35 from "node:path";
68788
69330
  import os12 from "node:os";
68789
- import { createHash as createHash12 } from "node:crypto";
69331
+ import { createHash as createHash13 } from "node:crypto";
68790
69332
  function previewUpgrade(hosts = HOSTS2, deps = {}) {
68791
69333
  const paths = upgradePaths(deps);
68792
69334
  const { home, configDir } = paths;
@@ -68914,7 +69456,7 @@ function applyUpgrade(plan, deps = {}) {
68914
69456
  mkdirSync8(backup, { recursive: true, mode: 448 });
68915
69457
  for (const file of Object.keys(plan.configDigests))
68916
69458
  if (existsSync17(file)) {
68917
- const target = path35.join(backup, createHash12("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
69459
+ const target = path35.join(backup, createHash13("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
68918
69460
  copyFileSync5(file, target);
68919
69461
  chmodSync(target, 384);
68920
69462
  }
@@ -69128,7 +69670,7 @@ async function runLaunchCommand(argv, deps = {}) {
69128
69670
  }
69129
69671
  if (deps.run)
69130
69672
  return deps.run(host, hostArgs).status ?? 1;
69131
- return spawnSync12(launch.command, launch.args, {
69673
+ return spawnSync13(launch.command, launch.args, {
69132
69674
  stdio: "inherit",
69133
69675
  env
69134
69676
  }).status ?? 1;
@@ -69162,14 +69704,14 @@ var HOSTS2, upgradePaths = (deps) => {
69162
69704
  return null;
69163
69705
  let data;
69164
69706
  try {
69165
- data = JSON.parse(readFileSync16(file, "utf8"));
69707
+ data = JSON.parse(readFileSync17(file, "utf8"));
69166
69708
  } catch {
69167
69709
  throw new Error(`${file} is not valid JSON; use the native host to inspect JSONC settings`);
69168
69710
  }
69169
69711
  if (!data || typeof data !== "object" || Array.isArray(data))
69170
69712
  throw new Error(`${file} is not a configuration object`);
69171
69713
  return data;
69172
- }, digest2 = (file) => existsSync17(file) ? createHash12("sha256").update(readFileSync16(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
69714
+ }, digest2 = (file) => existsSync17(file) ? createHash13("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
69173
69715
  const { home, env } = upgradePaths(deps);
69174
69716
  let step;
69175
69717
  try {
@@ -69309,12 +69851,49 @@ var init_launch = __esm(() => {
69309
69851
  init_upgrade();
69310
69852
  });
69311
69853
 
69854
+ // packages/workit-cli/src/prompt.ts
69855
+ import { createInterface } from "node:readline/promises";
69856
+ async function askLine(question, output = process.stdout) {
69857
+ const prompt = createInterface({ input: process.stdin, output });
69858
+ try {
69859
+ return await prompt.question(question);
69860
+ } finally {
69861
+ prompt.close();
69862
+ }
69863
+ }
69864
+ async function askOrCancel(io, ask) {
69865
+ try {
69866
+ return await ask();
69867
+ } catch (error) {
69868
+ if (!isPromptAbort(error))
69869
+ throw error;
69870
+ io.stderr(`
69871
+ Cancelled.
69872
+ `);
69873
+ return null;
69874
+ }
69875
+ }
69876
+ function cancelled(io) {
69877
+ if (io.json) {
69878
+ const envelope = {
69879
+ ok: false,
69880
+ code: "failed",
69881
+ data: { reason: "cancelled" },
69882
+ error: "cancelled"
69883
+ };
69884
+ io.stdout(`${JSON.stringify(envelope)}
69885
+ `);
69886
+ }
69887
+ return CANCELLED_EXIT;
69888
+ }
69889
+ var CANCELLED_EXIT = 130, isPromptAbort = (error) => typeof error === "object" && error !== null && (error.name === "AbortError" || error.code === "ABORT_ERR");
69890
+ var init_prompt = () => {};
69891
+
69312
69892
  // packages/workit-cli/src/verbs/doctor.ts
69313
69893
  var exports_doctor = {};
69314
69894
  __export(exports_doctor, {
69315
69895
  run: () => run5
69316
69896
  });
69317
- import { createInterface } from "node:readline/promises";
69318
69897
  async function confirmForcedLockClear(root, args, io) {
69319
69898
  const lock = inspectMetadataLock(root);
69320
69899
  if (!lock.present)
@@ -69331,23 +69910,20 @@ async function confirmForcedLockClear(root, args, io) {
69331
69910
  `);
69332
69911
  return false;
69333
69912
  }
69334
- const rl = createInterface({
69335
- input: process.stdin,
69336
- output: io.json ? process.stderr : process.stdout
69337
- });
69338
- try {
69339
- const answer = await rl.question("Remove this lock even if its holder may be alive? [y/N] ");
69340
- return /^y(es)?$/i.test(answer.trim());
69341
- } finally {
69342
- rl.close();
69343
- }
69913
+ const answer = await askOrCancel(io, () => askLine("Remove this lock even if its holder may be alive? [y/N] ", io.json ? process.stderr : process.stdout));
69914
+ if (answer === null)
69915
+ return "cancelled";
69916
+ return /^y(es)?$/i.test(answer.trim());
69344
69917
  }
69345
69918
  async function run5(argv, io) {
69346
69919
  const root = workspaceRootFor();
69347
69920
  const force = argv.includes("--force");
69348
69921
  let fixLock = null;
69349
69922
  if (argv.includes("--fix-lock")) {
69350
- if (force && !await confirmForcedLockClear(root, argv, io)) {
69923
+ const confirmed = force ? await confirmForcedLockClear(root, argv, io) : true;
69924
+ if (confirmed === "cancelled")
69925
+ return cancelled(io);
69926
+ if (!confirmed) {
69351
69927
  if (!io.json)
69352
69928
  return 3;
69353
69929
  return emit(io, fail("blocked", "fix-lock --force: refusing without --yes outside an interactive terminal", {
@@ -69386,6 +69962,7 @@ var init_doctor2 = __esm(() => {
69386
69962
  init_doctor();
69387
69963
  init_store_lock();
69388
69964
  init_output();
69965
+ init_prompt();
69389
69966
  init_task();
69390
69967
  });
69391
69968
 
@@ -69493,8 +70070,8 @@ var init_redact = __esm(() => {
69493
70070
  });
69494
70071
 
69495
70072
  // packages/workit-core/src/checks.ts
69496
- import { spawn, spawnSync as spawnSync13 } from "node:child_process";
69497
- import { createHash as createHash13 } from "node:crypto";
70073
+ import { spawn, spawnSync as spawnSync14 } from "node:child_process";
70074
+ import { createHash as createHash14 } from "node:crypto";
69498
70075
  import fs17 from "node:fs";
69499
70076
  import os13 from "node:os";
69500
70077
  import path36 from "node:path";
@@ -69672,7 +70249,7 @@ function runCheckCommand(argv, options) {
69672
70249
  if (!child?.pid)
69673
70250
  return;
69674
70251
  if (windows)
69675
- spawnSync13("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
70252
+ spawnSync14("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
69676
70253
  stdio: "ignore",
69677
70254
  windowsHide: true
69678
70255
  });
@@ -69828,7 +70405,7 @@ function pruneCheckLogs(storeRoot, options = {}) {
69828
70405
  }
69829
70406
  return report;
69830
70407
  }
69831
- var MAX_LOG_BYTES, TAIL_LINES = 80, MAX_LINE_CHARS = 400, redactLog = (text) => redactText(text), PEM_BEGIN, PEM_END, BATCH_BYTES, MAX_PENDING, PEM_BODY, MAX_KEY_LINES = 4096, MAX_KEY_BYTES, CMD_META, DRAIN_MS = 250, FORWARDED_SIGNALS, logDigest = (log) => `sha256:${createHash13("sha256").update(log).digest("hex")}`, LOG_RETENTION;
70408
+ var MAX_LOG_BYTES, TAIL_LINES = 80, MAX_LINE_CHARS = 400, redactLog = (text) => redactText(text), PEM_BEGIN, PEM_END, BATCH_BYTES, MAX_PENDING, PEM_BODY, MAX_KEY_LINES = 4096, MAX_KEY_BYTES, CMD_META, DRAIN_MS = 250, FORWARDED_SIGNALS, logDigest = (log) => `sha256:${createHash14("sha256").update(log).digest("hex")}`, LOG_RETENTION;
69832
70409
  var init_checks3 = __esm(() => {
69833
70410
  init_redact();
69834
70411
  MAX_LOG_BYTES = 2 * 1024 * 1024;
@@ -69917,7 +70494,6 @@ __export(exports_grant, {
69917
70494
  run: () => run8,
69918
70495
  runGrant: () => runGrant
69919
70496
  });
69920
- import { createInterface as createInterface2 } from "node:readline/promises";
69921
70497
  function show(argv, io) {
69922
70498
  const all = argv.includes("--all");
69923
70499
  const named = argv.filter((arg) => !arg.startsWith("--"));
@@ -69941,10 +70517,13 @@ function show(argv, io) {
69941
70517
  } catch (error) {
69942
70518
  return emit(io, fail("invalid_input", error instanceof Error ? error.message : String(error)));
69943
70519
  }
69944
- return emit(io, ok(autonomy), (data) => [
70520
+ const tracks = releaseTracksReport(io.cwd);
70521
+ const shown = tracks.tracks.length || tracks.error ? { ...autonomy, releaseTracks: tracks } : autonomy;
70522
+ return emit(io, ok(shown), (data) => [
69945
70523
  ...data.note ? [`note: ${data.note}`] : [],
69946
70524
  data.workspace ? `workspace ${data.workspace} (${data.source === "default" ? "D4 defaults" : `from ${data.source}`})` : "no workspace matches this checkout: D4 defaults apply",
69947
70525
  ...describe2(data.grants, data.configured, data.defaultEndpoint, data.verification),
70526
+ ...describeReleaseTracks(tracks).map((line) => ` ${line}`),
69948
70527
  "raising a grant needs the user: workit grant set <workspace> <kind>=<value>"
69949
70528
  ]);
69950
70529
  }
@@ -70002,7 +70581,9 @@ async function change(verb, argv, io, deps) {
70002
70581
  const agent = agentMarker(io.env);
70003
70582
  if (!deps.interactive() || agent)
70004
70583
  return emit(io, fail("blocked", `grant_raise_refused: raising ${raised.map(({ kind }) => kind).join(", ")} needs the user at an interactive terminal${agent ? ` (this shell belongs to an agent: ${agent} is set)` : ""}; grants are never raised headless`, { unblock: `ask the user to run, in their own terminal: ${command}` }));
70005
- const answer = await deps.ask(`Raise ${raised.map(({ kind, value }) => `${kind}=${value}`).join(", ")} for workspace "${workspace}"? An agent there may then do this without asking. Type the workspace name to confirm: `);
70584
+ const answer = await askOrCancel(io, () => deps.ask(`Raise ${raised.map(({ kind, value }) => `${kind}=${value}`).join(", ")} for workspace "${workspace}"? An agent there may then do this without asking. Type the workspace name to confirm: `));
70585
+ if (answer === null)
70586
+ return cancelled(io);
70006
70587
  if (answer.trim() !== workspace)
70007
70588
  return emit(io, fail("blocked", "grant change not confirmed; nothing was written"));
70008
70589
  }
@@ -70037,7 +70618,9 @@ var USAGE2 = "workit grant show [<workspace>] [--all] | grant set <workspace> <k
70037
70618
  usage: ${USAGE2}`)), run8 = (argv, io) => runGrant(argv, io);
70038
70619
  var init_grant = __esm(() => {
70039
70620
  init_autonomy();
70621
+ init_release_tracks();
70040
70622
  init_output();
70623
+ init_prompt();
70041
70624
  AGENT_ENV = [
70042
70625
  "CLAUDECODE",
70043
70626
  "OPENCODE",
@@ -70048,14 +70631,7 @@ var init_grant = __esm(() => {
70048
70631
  ];
70049
70632
  defaultDeps = {
70050
70633
  interactive: () => process.stdin.isTTY === true && process.stdout.isTTY === true,
70051
- ask: async (question) => {
70052
- const prompt = createInterface2({ input: process.stdin, output: process.stdout });
70053
- try {
70054
- return await prompt.question(question);
70055
- } finally {
70056
- prompt.close();
70057
- }
70058
- }
70634
+ ask: (question) => askLine(question)
70059
70635
  };
70060
70636
  });
70061
70637
 
@@ -70392,9 +70968,9 @@ var init_check = __esm(() => {
70392
70968
  var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
70393
70969
 
70394
70970
  // packages/workit-core/src/git/ops.ts
70395
- import { spawnSync as spawnSync14 } from "node:child_process";
70971
+ import { spawnSync as spawnSync15 } from "node:child_process";
70396
70972
  function dirtEntries(cwd) {
70397
- const run = git4(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
70973
+ const run = git5(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
70398
70974
  if (!run.ok)
70399
70975
  return [];
70400
70976
  const parts = run.stdout.split("\x00");
@@ -70410,9 +70986,19 @@ function dirtEntries(cwd) {
70410
70986
  return out;
70411
70987
  }
70412
70988
  function gitBranch(cwd, input) {
70413
- const name = input.name.trim();
70414
70989
  if (!headSha(cwd) && currentBranch(cwd) === null)
70415
70990
  return failure3("invalid_input", "not inside a git repository with a commit");
70991
+ const explicitBase = input.base?.trim() || null;
70992
+ const resolved = vcsConfig("resolve", cwd, {
70993
+ track: input.track ?? null,
70994
+ ...explicitBase ? { branch: explicitBase } : {}
70995
+ });
70996
+ const releaseTrack = resolved.ok === false ? null : resolved.releaseTrack ?? null;
70997
+ if (releaseTrack?.blocking && (!explicitBase || input.name === undefined))
70998
+ return failure3("blocked", String(releaseTrack.blocking), "workit git branch … --track <name> # or --base <branch>");
70999
+ const name = (input.name ?? (input.kind && input.slug ? trackBranchName(releaseTrack?.track ?? null, input.kind, input.slug) ?? `${input.kind}/${input.slug}` : "")).trim();
71000
+ if (!name)
71001
+ return failure3("invalid_input", "missing branch name");
70416
71002
  if (!validRefName(cwd, name))
70417
71003
  return failure3("invalid_input", `invalid branch name ${JSON.stringify(name)}`);
70418
71004
  const policy = validateBranchNameFor(cwd, name);
@@ -70420,23 +71006,25 @@ function gitBranch(cwd, input) {
70420
71006
  return failure3("blocked", policy.error, policy.correction);
70421
71007
  if (resolveRef(cwd, `refs/heads/${name}`))
70422
71008
  return failure3("failed", `branch_exists: ${name} already exists`, `git switch ${name}`);
70423
- let base = input.base?.trim() || null;
71009
+ let base = explicitBase;
70424
71010
  let defaultTarget = null;
70425
- const resolved = vcsConfig("resolve", cwd);
70426
- if (resolved.ok === false && !base)
71011
+ let defaultBase = null;
71012
+ if (resolved.ok === false && (!base || input.track))
70427
71013
  return failure3("blocked", String(resolved.error), "fix ~/.config/workit/vcs.json or pass --base <branch>");
70428
- if (resolved.ok !== false)
71014
+ if (resolved.ok !== false) {
70429
71015
  defaultTarget = String(resolved.defaultTargetBranch ?? "") || null;
70430
- base ??= defaultTarget;
71016
+ defaultBase = String(resolved.baseBranch ?? "") || defaultTarget;
71017
+ }
71018
+ base ??= defaultBase;
70431
71019
  if (!base)
70432
71020
  return failure3("invalid_input", "no default target branch is configured; pass --base <branch>");
70433
71021
  if (base.startsWith("-"))
70434
71022
  return failure3("invalid_input", "--base must not start with -");
70435
- const notes = [];
71023
+ const notes = [...releaseTrack?.warnings ?? []];
70436
71024
  const remotes = remoteNames(cwd);
70437
71025
  const remote = remotes.includes("origin") ? "origin" : pushRemoteName(cwd);
70438
71026
  const localTip = resolveRef(cwd, `refs/heads/${base}`);
70439
- const stackParent = localTip !== null && base !== defaultTarget && !isProtectedTarget(cwd, base);
71027
+ const stackParent = localTip !== null && base !== defaultTarget && base !== defaultBase && !isProtectedTarget(cwd, base);
70440
71028
  let baseRef = null;
70441
71029
  if (stackParent)
70442
71030
  baseRef = `refs/heads/${base}`;
@@ -70454,18 +71042,19 @@ function gitBranch(cwd, input) {
70454
71042
  }
70455
71043
  } else if (localTip)
70456
71044
  baseRef = `refs/heads/${base}`;
70457
- if (!baseRef && git4(cwd, ["rev-parse", "--verify", "-q", `${base}^{commit}`]).ok)
71045
+ if (!baseRef && git5(cwd, ["rev-parse", "--verify", "-q", `${base}^{commit}`]).ok)
70458
71046
  baseRef = base;
70459
- const baseSha = baseRef ? git4(cwd, ["rev-parse", "--verify", "-q", `${baseRef}^{commit}`]).stdout.trim() : "";
71047
+ const baseSha = baseRef ? git5(cwd, ["rev-parse", "--verify", "-q", `${baseRef}^{commit}`]).stdout.trim() : "";
70460
71048
  if (!baseRef || !baseSha)
70461
71049
  return failure3("not_found", `base ${base} does not resolve (no ${remote ?? "remote"}/${base}, no local ${base})`, `git fetch ${remote ?? "origin"} ${base} # or pass --base <ref>`);
70462
71050
  const dirt = classifyBranchDirt(cwd);
70463
71051
  if (dirt === "stash-required" && !input.carry)
70464
71052
  return failure3("blocked", "dirty_worktree: tracked changes outside docs/ would ride along to the new branch", `commit them first (workit git commit -m … -- <paths>), or pass --carry to bring them along`);
70465
71053
  const previous = currentBranch(cwd);
70466
- const switched = git4(cwd, ["switch", "--no-track", "-c", name, baseSha]);
71054
+ const switched = git5(cwd, ["switch", "--no-track", "-c", name, baseSha]);
70467
71055
  if (!switched.ok)
70468
71056
  return failure3("failed", `git switch -c ${name} failed: ${gitError(switched)}`);
71057
+ recordBranchBase(cwd, name, base);
70469
71058
  return success2({
70470
71059
  branch: name,
70471
71060
  previous,
@@ -70473,7 +71062,8 @@ function gitBranch(cwd, input) {
70473
71062
  baseRef,
70474
71063
  baseSha,
70475
71064
  carried: dirt !== "clean",
70476
- notes
71065
+ notes,
71066
+ releaseTrack
70477
71067
  });
70478
71068
  }
70479
71069
  function lintCommitMessage(cwd, message) {
@@ -70488,7 +71078,7 @@ function lintCommitMessage(cwd, message) {
70488
71078
  }
70489
71079
  if (preset !== "auto")
70490
71080
  return { ok: true };
70491
- const log = git4(cwd, ["log", "-n", "50", "--no-merges", "--format=%s"]);
71081
+ const log = git5(cwd, ["log", "-n", "50", "--no-merges", "--format=%s"]);
70492
71082
  const subjects = log.ok ? log.stdout.split(`
70493
71083
  `).filter(Boolean) : [];
70494
71084
  const detected = detectCommitFlavor(subjects).flavor;
@@ -70526,28 +71116,28 @@ function gitCommit(cwd, input) {
70526
71116
  if (paths.length) {
70527
71117
  if (paths.some((value) => value.startsWith("-") || !value.trim()))
70528
71118
  return failure3("invalid_input", "paths must not be empty or start with -");
70529
- const added = git4(cwd, ["add", "-A", "--", ...paths]);
71119
+ const added = git5(cwd, ["add", "-A", "--", ...paths]);
70530
71120
  if (!added.ok)
70531
71121
  return failure3("failed", `git add failed: ${gitError(added)}`);
70532
71122
  args.push("--", ...paths);
70533
71123
  } else if (input.all) {
70534
- const added = git4(cwd, ["add", "-A"]);
71124
+ const added = git5(cwd, ["add", "-A"]);
70535
71125
  if (!added.ok)
70536
71126
  return failure3("failed", `git add failed: ${gitError(added)}`);
70537
71127
  } else {
70538
- const staged = git4(cwd, ["diff", "--cached", "--name-only", "-z"]);
71128
+ const staged = git5(cwd, ["diff", "--cached", "--name-only", "-z"]);
70539
71129
  if (staged.ok && !staged.stdout.replaceAll("\x00", "")) {
70540
71130
  const dirt = dirtEntries(cwd);
70541
71131
  return failure3(dirt.length ? "blocked" : "failed", dirt.length ? `nothing_staged: nothing is staged and workit never commits unrelated changes implicitly (${dirt.length} changed: ${dirt.slice(0, 5).map((entry) => entry.slice(3)).join(", ")}${dirt.length > 5 ? ", …" : ""})` : "nothing_to_commit: the working tree is clean", dirt.length ? `workit git commit -m '<msg>' -- <paths> # or --all to commit every change` : undefined);
70542
71132
  }
70543
71133
  }
70544
- const committed = git4(cwd, args, { timeoutMs: COMMIT_TIMEOUT_MS, env: input.env });
71134
+ const committed = git5(cwd, args, { timeoutMs: COMMIT_TIMEOUT_MS, env: input.env });
70545
71135
  if (!committed.ok)
70546
71136
  return failure3("failed", `git commit failed: ${gitError(committed, 6)}`);
70547
71137
  const sha = headSha(cwd);
70548
71138
  if (!sha)
70549
71139
  return failure3("failed", "the commit did not resolve");
70550
- const files = nulList(git4(cwd, ["diff-tree", "--root", "--no-commit-id", "--name-only", "-r", "-z", sha]).stdout);
71140
+ const files = nulList(git5(cwd, ["diff-tree", "--root", "--no-commit-id", "--name-only", "-r", "-z", sha]).stdout);
70551
71141
  const leftDirty = dirtEntries(cwd).length;
70552
71142
  const listed = [];
70553
71143
  let size = 0;
@@ -70619,9 +71209,9 @@ function recordedRemoteTip(cwd, plan) {
70619
71209
  return;
70620
71210
  }
70621
71211
  function includesRemoteTip(cwd, branch, tip) {
70622
- if (git4(cwd, ["merge-base", "--is-ancestor", tip, `refs/heads/${branch}`]).ok)
71212
+ if (git5(cwd, ["merge-base", "--is-ancestor", tip, `refs/heads/${branch}`]).ok)
70623
71213
  return true;
70624
- const reflog = git4(cwd, ["reflog", "show", "--format=%H", `refs/heads/${branch}`, "--"]);
71214
+ const reflog = git5(cwd, ["reflog", "show", "--format=%H", `refs/heads/${branch}`, "--"]);
70625
71215
  return reflog.ok && reflog.stdout.split(`
70626
71216
  `).some((line) => line.trim() === tip);
70627
71217
  }
@@ -70671,7 +71261,7 @@ ${run.stdout}`;
70671
71261
  return failure3("failed", `push_unverified: ${plan.remote}/${plan.branch} is at ${after.sha?.slice(0, 12) ?? "(absent)"}, not the pushed ${plan.sha.slice(0, 12)}`, "workit verify-delivery push");
70672
71262
  let upstream = false;
70673
71263
  if (input.setUpstream) {
70674
- upstream = git4(cwd, ["config", `branch.${plan.branch}.remote`, plan.remote]).ok && git4(cwd, ["config", `branch.${plan.branch}.merge`, `refs/heads/${plan.branch}`]).ok;
71264
+ upstream = git5(cwd, ["config", `branch.${plan.branch}.remote`, plan.remote]).ok && git5(cwd, ["config", `branch.${plan.branch}.merge`, `refs/heads/${plan.branch}`]).ok;
70675
71265
  }
70676
71266
  const row = appendObserved(cwd, {
70677
71267
  type: pushed ? "push.verified" : "push.noop",
@@ -70721,8 +71311,8 @@ function deleteRemoteBranch(cwd, target, branch, expectedTip, options = {}) {
70721
71311
  ${run.stdout}`;
70722
71312
  return /stale info|cannot lock ref|fetch first|non-fast-forward/iu.test(text) ? { ok: false, lease: true, error: "the remote branch tip changed before deletion" } : { ok: false, lease: false, error: gitError(run) };
70723
71313
  }
70724
- var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git4 = (cwd, args, options = {}) => {
70725
- const result = spawnSync14("git", args, {
71314
+ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
71315
+ const result = spawnSync15("git", args, {
70726
71316
  cwd,
70727
71317
  encoding: "utf8",
70728
71318
  env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
@@ -70742,11 +71332,12 @@ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git4 = (cwd, args, options = {}
70742
71332
  const lines = redactText(`${run.stderr}
70743
71333
  ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trim()).filter((value) => value && !value.startsWith("hint:"));
70744
71334
  return lines.slice(-max).join(" / ").slice(0, 400) || "git failed";
70745
- }, validRefName = (cwd, name) => !name.startsWith("-") && git4(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, SHA;
71335
+ }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, SHA;
70746
71336
  var init_ops = __esm(() => {
70747
71337
  init_branch();
70748
71338
  init_commit_flavors();
70749
71339
  init_pr_create();
71340
+ init_release_tracks();
70750
71341
  init_vcs_config();
70751
71342
  init_redact();
70752
71343
  init_ledger();
@@ -71175,7 +71766,7 @@ var init_report = __esm(() => {
71175
71766
  });
71176
71767
 
71177
71768
  // packages/workit-core/src/forge/pr-ops.ts
71178
- import { spawnSync as spawnSync15 } from "node:child_process";
71769
+ import { spawnSync as spawnSync16 } from "node:child_process";
71179
71770
  function fillFromCommits(cwd, resolved, base) {
71180
71771
  const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
71181
71772
  if (!base.startsWith("-"))
@@ -71183,7 +71774,7 @@ function fillFromCommits(cwd, resolved, base) {
71183
71774
  const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
71184
71775
  if (!from)
71185
71776
  return failure3("not_found", `base ${base} is not available locally`, `git fetch ${resolved.baseRemote} ${base}`);
71186
- const log = spawnSync15("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
71777
+ const log = spawnSync16("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
71187
71778
  cwd,
71188
71779
  encoding: "utf8",
71189
71780
  timeout: GIT_TIMEOUTS.local,
@@ -71382,6 +71973,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
71382
71973
  const gone = !deleted.ok && remoteRefTip(cwd, resolved.remote, `refs/heads/${branch}`);
71383
71974
  deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
71384
71975
  }
71976
+ const mergeBack = mergeBackFor(cwd, doc.base);
71385
71977
  const row = appendObserved(cwd, {
71386
71978
  type: "pr.merged",
71387
71979
  actor: input.actor,
@@ -71407,11 +71999,25 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
71407
71999
  verdict,
71408
72000
  grant: { source: grant.source },
71409
72001
  deletedBranch,
71410
- recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
72002
+ recorded: row.ok ? { id: String(row.value.id) } : { error: row.error },
72003
+ mergeBack: mergeBack.branches,
72004
+ mergeBackTrack: mergeBack.track
71411
72005
  }
71412
72006
  };
71413
72007
  }
71414
- var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "pull request" : "merge request", grantBlocked = (decision) => failure3("blocked", decision.error, decision.unblock), refuse = (error, unblock, refusal) => ({
72008
+ var VERIFY_DELAYS_MS, noun = (resolved) => resolved.forge.kind === "github" ? "pull request" : "merge request", grantBlocked = (decision) => failure3("blocked", decision.error, decision.unblock), mergeBackFor = (cwd, base) => {
72009
+ try {
72010
+ const owners = workspaceReleaseTracks(cwd).tracks.filter((track) => track.productionBranch === base);
72011
+ if (owners.length !== 1)
72012
+ return { track: null, branches: [] };
72013
+ return {
72014
+ track: owners[0].name,
72015
+ branches: owners[0].mergeBackBranches.filter((branch) => branch !== base)
72016
+ };
72017
+ } catch {
72018
+ return { track: null, branches: [] };
72019
+ }
72020
+ }, refuse = (error, unblock, refusal) => ({
71415
72021
  ok: false,
71416
72022
  code: "blocked",
71417
72023
  error,
@@ -71422,6 +72028,7 @@ var init_pr_ops = __esm(() => {
71422
72028
  init_autonomy();
71423
72029
  init_branch();
71424
72030
  init_vcs_config();
72031
+ init_release_tracks();
71425
72032
  init_ops();
71426
72033
  init_rev();
71427
72034
  init_ledger();
@@ -71442,7 +72049,7 @@ var init_pr_ops = __esm(() => {
71442
72049
  });
71443
72050
 
71444
72051
  // packages/workit-core/src/forge/exec.ts
71445
- import { spawnSync as spawnSync16 } from "node:child_process";
72052
+ import { spawnSync as spawnSync17 } from "node:child_process";
71446
72053
  import fs19 from "node:fs";
71447
72054
  import path39 from "node:path";
71448
72055
  function findExecutable(bin, env) {
@@ -71477,7 +72084,7 @@ function systemRunner(kind, apiHost, env = process.env) {
71477
72084
  if (!exe)
71478
72085
  return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
71479
72086
  const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
71480
- const result = spawnSync16(exe, [...args], {
72087
+ const result = spawnSync17(exe, [...args], {
71481
72088
  encoding: "utf8",
71482
72089
  env: { ...childEnv, ...token },
71483
72090
  maxBuffer: MAX_BUFFER2,
@@ -72390,7 +72997,7 @@ var exports_pr = {};
72390
72997
  __export(exports_pr, {
72391
72998
  run: () => run10
72392
72999
  });
72393
- import { readFileSync as readFileSync17 } from "node:fs";
73000
+ import { readFileSync as readFileSync18 } from "node:fs";
72394
73001
  import path40 from "node:path";
72395
73002
  async function status(argv, io) {
72396
73003
  const flags = parseFlags(argv, { pr: "value", branch: "value", "log-lines": "value" });
@@ -72443,6 +73050,7 @@ function verdictBlock(cwd, branch) {
72443
73050
  async function create4(argv, io) {
72444
73051
  const flags = parseFlags(argv, {
72445
73052
  base: "value",
73053
+ track: "value",
72446
73054
  title: "value",
72447
73055
  body: "value",
72448
73056
  "body-file": "value",
@@ -72461,18 +73069,26 @@ async function create4(argv, io) {
72461
73069
  let body = flags.values.body ?? null;
72462
73070
  if (flags.values["body-file"] !== undefined) {
72463
73071
  try {
72464
- body = readFileSync17(path40.resolve(io.cwd, flags.values["body-file"]), "utf8");
73072
+ body = readFileSync18(path40.resolve(io.cwd, flags.values["body-file"]), "utf8");
72465
73073
  } catch {
72466
73074
  return usage3(io, `cannot read --body-file ${flags.values["body-file"]}`, CREATE_USAGE);
72467
73075
  }
72468
73076
  }
73077
+ if (flags.values.base !== undefined && flags.values.track !== undefined)
73078
+ return usage3(io, "pass --base or --track, not both (--track picks the base for you)", CREATE_USAGE);
72469
73079
  let base = flags.values.base ?? null;
73080
+ let releaseTrack = null;
72470
73081
  if (!base) {
72471
- const resolved = vcsConfig("resolve", io.cwd);
73082
+ const resolved = vcsConfig("resolve", io.cwd, { track: flags.values.track ?? null });
72472
73083
  if (resolved.ok === false)
72473
73084
  return emit(io, fail("blocked", String(resolved.error), {
72474
73085
  unblock: "fix ~/.config/workit/vcs.json or pass --base <b>"
72475
73086
  }));
73087
+ releaseTrack = resolved.releaseTrack ?? null;
73088
+ if (releaseTrack?.blocking)
73089
+ return emit(io, fail("blocked", releaseTrack.blocking, {
73090
+ unblock: "workit pr create --track <name> … # or --base <branch>"
73091
+ }));
72476
73092
  base = String(resolved.defaultTargetBranch ?? "") || null;
72477
73093
  }
72478
73094
  if (!base)
@@ -72502,8 +73118,11 @@ async function create4(argv, io) {
72502
73118
  }, forgeDeps.sleep);
72503
73119
  if (!result.ok)
72504
73120
  return forgeFail(io, result);
72505
- return emit(io, ok(result.data), (data) => [
73121
+ const created = releaseTrack ? { ...result.data, releaseTrack } : result.data;
73122
+ return emit(io, ok(created), (data) => [
72506
73123
  `${data.created ? "opened" : "found open"} ${connected.data.forge.kind === "github" ? "PR #" : "MR !"}${data.number}${data.draft && data.created ? " (draft)" : ""}: ${data.branch} -> ${data.base} ${data.url}`,
73124
+ ...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
73125
+ ...(data.releaseTrack?.warnings ?? []).map((warning) => `note: ${warning}`),
72507
73126
  `head ${data.head.slice(0, 12)} verified on the forge`,
72508
73127
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
72509
73128
  ]);
@@ -72532,6 +73151,9 @@ async function merge3(argv, io) {
72532
73151
  return emit(io, ok(result.data), (data) => [
72533
73152
  `merged ${connected.data.forge.kind === "github" ? "PR #" : "MR !"}${data.number} (${data.method}) at head ${data.head.slice(0, 12)}${data.mergeSha ? ` -> ${data.mergeSha.slice(0, 12)}` : ""}`,
72534
73153
  data.verdict.required ? `verdict ${data.verdict.verdictId ?? "?"} accepted for that head` : "no verdict required (workspace grants merge: true)",
73154
+ ...data.mergeBack.length ? [
73155
+ `merge back: ${data.base} is the ${data.mergeBackTrack} production branch; bring it into ${data.mergeBack.join(", ")}`
73156
+ ] : [],
72535
73157
  ...data.deletedBranch === true ? [`deleted ${data.branch}`] : typeof data.deletedBranch === "object" ? [`branch not deleted: ${data.deletedBranch.error}`] : [],
72536
73158
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
72537
73159
  ]);
@@ -72546,7 +73168,7 @@ async function run10(argv, io) {
72546
73168
  return merge3(rest, io);
72547
73169
  return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
72548
73170
  }
72549
- var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b>] (--title <t> [--body <text> | --body-file <f>] | --fill) [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--json]", USAGE4 = "workit pr status|create|merge ... (workit help pr)", METHODS2;
73171
+ var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", CREATE_USAGE = "workit pr create [--base <b> | --track <t>] (--title <t> [--body <text> | --body-file <f>] | --fill) [--draft] [--json]", MERGE_USAGE = "workit pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch] [--json]", USAGE4 = "workit pr status|create|merge ... (workit help pr)", METHODS2;
72550
73172
  var init_pr = __esm(() => {
72551
73173
  init_pr_body();
72552
73174
  init_pr_ops();
@@ -72777,7 +73399,13 @@ __export(exports_git, {
72777
73399
  run: () => run12
72778
73400
  });
72779
73401
  async function branch(argv, io) {
72780
- const flags = parseFlags(argv, { base: "value", kind: "value", slug: "value", carry: "boolean" });
73402
+ const flags = parseFlags(argv, {
73403
+ base: "value",
73404
+ kind: "value",
73405
+ slug: "value",
73406
+ track: "value",
73407
+ carry: "boolean"
73408
+ });
72781
73409
  if (typeof flags === "string")
72782
73410
  return usage3(io, flags, BRANCH_USAGE);
72783
73411
  const { kind, slug } = flags.values;
@@ -72789,18 +73417,19 @@ async function branch(argv, io) {
72789
73417
  return usage3(io, "--kind must be feature, bugfix or hotfix", BRANCH_USAGE);
72790
73418
  if (kind !== undefined && flags.positionals.length)
72791
73419
  return usage3(io, "pass a name or --kind/--slug, not both", BRANCH_USAGE);
72792
- const name = kind !== undefined ? `${kind}/${slug}` : flags.positionals[0];
72793
- if (!name)
73420
+ if (kind === undefined && !flags.positionals[0])
72794
73421
  return usage3(io, "missing branch name", BRANCH_USAGE);
72795
73422
  const result = gitBranch(io.cwd, {
72796
- name,
73423
+ ...kind !== undefined ? { kind, slug } : { name: flags.positionals[0] },
72797
73424
  base: flags.values.base ?? null,
73425
+ track: flags.values.track ?? null,
72798
73426
  carry: flags.booleans.has("carry")
72799
73427
  });
72800
73428
  if (!result.ok)
72801
73429
  return forgeFail(io, result);
72802
73430
  return emit(io, ok(result.data), (data) => [
72803
73431
  `switched to new branch ${data.branch} from ${data.baseRef} (${short2(data.baseSha)})${data.carried ? ", carrying uncommitted changes" : ""}`,
73432
+ ...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
72804
73433
  ...data.notes.map((note) => `note: ${note}`)
72805
73434
  ]);
72806
73435
  }
@@ -72921,7 +73550,7 @@ async function run12(argv, io) {
72921
73550
  return push(rest, io);
72922
73551
  return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
72923
73552
  }
72924
- var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m <msg> [--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)";
73553
+ var BRANCH_USAGE = "workit git branch <name> | --kind feature|bugfix|hotfix --slug <s> [--base <b>] [--track <t>] [--carry] [--json]", COMMIT_USAGE = "workit git commit -m <msg> [--all | [--] <paths…>] [--json]", PUSH_USAGE = "workit git push [--set-upstream] [--force-with-lease [--expect <sha>] [--overwrite-unintegrated]] [--json]", USAGE5 = "workit git branch|commit|push ... (workit help git)", short2 = (sha) => sha ? sha.slice(0, 12) : "(none)";
72925
73554
  var init_git2 = __esm(() => {
72926
73555
  init_autonomy();
72927
73556
  init_ops();
@@ -72932,7 +73561,7 @@ var init_git2 = __esm(() => {
72932
73561
  });
72933
73562
 
72934
73563
  // packages/workit-core/src/forge/verify.ts
72935
- import { spawnSync as spawnSync17 } from "node:child_process";
73564
+ import { spawnSync as spawnSync18 } from "node:child_process";
72936
73565
  function finish(cwd, report, actor) {
72937
73566
  const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
72938
73567
  const out = { ...report, delivered };
@@ -73145,7 +73774,7 @@ var NPM_NAME, SEMVER, SAFE_ARG, systemNpm = (args) => {
73145
73774
  };
73146
73775
  const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
73147
73776
  const npmCmd = found && !/["%!^]/u.test(found) ? `"${found}"` : "npm.cmd";
73148
- const result = process.platform === "win32" ? spawnSync17(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync17("npm", [...args], options);
73777
+ const result = process.platform === "win32" ? spawnSync18(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync18("npm", [...args], options);
73149
73778
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
73150
73779
  }, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
73151
73780
  try {
@@ -73252,8 +73881,8 @@ var init_verify_delivery = __esm(() => {
73252
73881
  });
73253
73882
 
73254
73883
  // packages/workit-core/src/stack.ts
73255
- import { spawnSync as spawnSync18 } from "node:child_process";
73256
- import { createHash as createHash14, randomBytes as randomBytes3 } from "node:crypto";
73884
+ import { spawnSync as spawnSync19 } from "node:child_process";
73885
+ import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
73257
73886
  import fs21 from "node:fs";
73258
73887
  import path41 from "node:path";
73259
73888
  function worktreeOf(cwd, branch) {
@@ -73353,7 +73982,7 @@ function stacksDir(cwd) {
73353
73982
  return { ok: true, data: path41.join(root.value.root, "stacks") };
73354
73983
  }
73355
73984
  function parseStack(raw) {
73356
- if (!isRecord6(raw) || !Array.isArray(raw.branches))
73985
+ if (!isRecord7(raw) || !Array.isArray(raw.branches))
73357
73986
  return null;
73358
73987
  const name = str3(raw.name);
73359
73988
  const trunk = str3(raw.trunk);
@@ -73361,12 +73990,12 @@ function parseStack(raw) {
73361
73990
  return null;
73362
73991
  const branches = [];
73363
73992
  for (const item of raw.branches) {
73364
- if (!isRecord6(item))
73993
+ if (!isRecord7(item))
73365
73994
  return null;
73366
73995
  const branch = str3(item.branch);
73367
73996
  if (!branch)
73368
73997
  return null;
73369
- const merged = isRecord6(item.merged) ? {
73998
+ const merged = isRecord7(item.merged) ? {
73370
73999
  pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
73371
74000
  mergeSha: str3(item.merged.mergeSha),
73372
74001
  at: str3(item.merged.at) ?? ""
@@ -73379,7 +74008,7 @@ function parseStack(raw) {
73379
74008
  lastParentHead: str3(item.lastParentHead),
73380
74009
  patchId: str3(item.patchId),
73381
74010
  merged,
73382
- ...isRecord6(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
74011
+ ...isRecord7(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
73383
74012
  pendingRestack: {
73384
74013
  from: str3(item.pendingRestack.from),
73385
74014
  tip: str3(item.pendingRestack.tip),
@@ -73765,7 +74394,7 @@ function commitKeys(cwd, range) {
73765
74394
  const patches = new Map;
73766
74395
  const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
73767
74396
  if (diff.ok && diff.stdout.trim()) {
73768
- const ids = spawnSync18("git", ["patch-id", "--stable"], {
74397
+ const ids = spawnSync19("git", ["patch-id", "--stable"], {
73769
74398
  cwd,
73770
74399
  input: diff.stdout,
73771
74400
  encoding: "utf8",
@@ -74373,7 +75002,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74373
75002
  ...unblock ? { unblock } : {},
74374
75003
  ...data ? { data } : {}
74375
75004
  }), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
74376
- const run = spawnSync18("git", args, {
75005
+ const run = spawnSync19("git", args, {
74377
75006
  cwd,
74378
75007
  encoding: "utf8",
74379
75008
  timeout: timeoutMs,
@@ -74404,7 +75033,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74404
75033
  return dir.ok ? path41.join(dir.data, "worktrees") : null;
74405
75034
  }, firstLine = (run) => (`${run.stderr}
74406
75035
  ${run.stdout}`.split(`
74407
- `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash14("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
75036
+ `).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash15("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
74408
75037
  if (name.length === 0 || name.length > 200)
74409
75038
  return false;
74410
75039
  for (let index = 0;index < name.length; index += 1) {
@@ -74413,7 +75042,7 @@ ${run.stdout}`.split(`
74413
75042
  return false;
74414
75043
  }
74415
75044
  return true;
74416
- }, isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
75045
+ }, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
74417
75046
  let raw = null;
74418
75047
  try {
74419
75048
  raw = fs21.readFileSync(path41.join(lock, OWNER), "utf8");
@@ -74459,7 +75088,7 @@ __export(exports_stack, {
74459
75088
  run: () => run14
74460
75089
  });
74461
75090
  async function plan(argv, io) {
74462
- const flags = parseFlags(argv, { name: "value", trunk: "value" });
75091
+ const flags = parseFlags(argv, { name: "value", trunk: "value", track: "value" });
74463
75092
  if (typeof flags === "string")
74464
75093
  return usage3(io, flags, PLAN_USAGE);
74465
75094
  const name = flags.values.name ?? null;
@@ -74472,7 +75101,20 @@ async function plan(argv, io) {
74472
75101
  if (selected.ok)
74473
75102
  existing = selected.data;
74474
75103
  }
74475
- const trunk = flags.values.trunk ?? existing?.trunk ?? defaultTrunk(io) ?? "main";
75104
+ const track = flags.values.track ?? null;
75105
+ if (track !== null && flags.values.trunk !== undefined)
75106
+ return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE);
75107
+ let trunk = flags.values.trunk ?? existing?.trunk ?? null;
75108
+ if (trunk === null || track !== null) {
75109
+ const derived = defaultTrunk(io, track, flags.positionals[0] ?? null);
75110
+ if ("error" in derived)
75111
+ return emit(io, fail("blocked", derived.error, {
75112
+ unblock: "workit stack plan --track <name> … # or --trunk <branch>"
75113
+ }));
75114
+ if (existing && track !== null && derived.trunk !== existing.trunk)
75115
+ return emit(io, fail("invalid_input", `stack ${existing.name} is on trunk ${existing.trunk}; --track ${track} targets ${derived.trunk ?? "(none)"}`, { unblock: "drop --track to re-plan on the recorded trunk" }));
75116
+ trunk ??= derived.trunk ?? "main";
75117
+ }
74476
75118
  const connected = connect(io, flags.positionals[0] ?? null);
74477
75119
  const resolved = connected.ok ? connected.data : null;
74478
75120
  const planned = planStack(io.cwd, resolved, { name, trunk, branches: flags.positionals }, existing);
@@ -74632,9 +75274,16 @@ async function run14(argv, io) {
74632
75274
  return land(rest, io);
74633
75275
  return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand", USAGE7);
74634
75276
  }
74635
- var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--timeout 20m] [--interval 30s] [--json]", USAGE7 = "workit stack plan|status|sync|land ... (workit help stack)", short4 = (sha) => sha ? sha.slice(0, 12) : "?", stackFailed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), defaultTrunk = (io) => {
74636
- const resolved = vcsConfig("resolve", io.cwd);
74637
- return resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
75277
+ var PLAN_USAGE = "workit stack plan [--name <n>] [--trunk <b> | --track <t>] [<bottom> … <top>] [--json]", STATUS_USAGE2 = "workit stack status [--name <n>] [--json]", SYNC_USAGE = "workit stack sync [--name <n>] [--local] [--dry-run] [--force <branch>]… [--json]", LAND_USAGE = "workit stack land [--name <n>] [--dry-run] [--max <n>] [--method squash|merge|rebase] [--timeout 20m] [--interval 30s] [--json]", USAGE7 = "workit stack plan|status|sync|land ... (workit help stack)", short4 = (sha) => sha ? sha.slice(0, 12) : "?", stackFailed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), defaultTrunk = (io, track, branch) => {
75278
+ const resolved = vcsConfig("resolve", io.cwd, {
75279
+ track,
75280
+ ...branch ? { branch } : {}
75281
+ });
75282
+ if (resolved.ok === false)
75283
+ return track !== null || String(resolved.configPath ?? "").endsWith("workspaces.json") ? { error: String(resolved.error) } : { trunk: null };
75284
+ if (resolved.releaseTrack?.blocking)
75285
+ return { error: String(resolved.releaseTrack.blocking) };
75286
+ return { trunk: String(resolved.defaultTargetBranch ?? "") || null };
74638
75287
  }, label = (forge, pr) => pr === null ? "no PR" : `${forge === "gitlab" ? "MR !" : "PR #"}${pr}`, renderSync = (data) => [
74639
75288
  `${data.dryRun ? "would sync" : "synced"} stack ${data.name}${data.merged.length ? `; merged: ${data.merged.map((item) => item.branch).join(", ")}` : ""}`,
74640
75289
  ...data.steps.map((step) => [
@@ -90686,11 +91335,11 @@ var init_rules = __esm(() => {
90686
91335
  });
90687
91336
 
90688
91337
  // packages/workit-cli/src/test-audit/audit.ts
90689
- import { spawnSync as spawnSync19 } from "node:child_process";
90690
- import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync18, statSync as statSync7 } from "node:fs";
91338
+ import { spawnSync as spawnSync20 } from "node:child_process";
91339
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync19, statSync as statSync7 } from "node:fs";
90691
91340
  import path43 from "node:path";
90692
- function git5(cwd, args) {
90693
- const run = spawnSync19("git", ["-c", "core.quotepath=off", ...args], {
91341
+ function git6(cwd, args) {
91342
+ const run = spawnSync20("git", ["-c", "core.quotepath=off", ...args], {
90694
91343
  cwd,
90695
91344
  encoding: "utf8",
90696
91345
  maxBuffer: 64 * 1024 * 1024
@@ -90726,20 +91375,20 @@ function expandPaths(cwd, inputs) {
90726
91375
  function resolveBase(cwd, base) {
90727
91376
  const candidates = base ? [base] : ["origin/main", "main", "origin/master", "master"];
90728
91377
  for (const candidate of candidates) {
90729
- if (!git5(cwd, ["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]).ok)
91378
+ if (!git6(cwd, ["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]).ok)
90730
91379
  continue;
90731
- const mergeBase = git5(cwd, ["merge-base", candidate, "HEAD"]);
91380
+ const mergeBase = git6(cwd, ["merge-base", candidate, "HEAD"]);
90732
91381
  return mergeBase.ok ? mergeBase.stdout.trim() : null;
90733
91382
  }
90734
91383
  return null;
90735
91384
  }
90736
91385
  function changedLines(cwd, base) {
90737
- const root = git5(cwd, ["rev-parse", "--show-toplevel"]).stdout.trim();
91386
+ const root = git6(cwd, ["rev-parse", "--show-toplevel"]).stdout.trim();
90738
91387
  const changed = new Map;
90739
- const names = git5(root, ["diff", "--name-only", "-z", "--no-renames", "--diff-filter=AM", base]);
91388
+ const names = git6(root, ["diff", "--name-only", "-z", "--no-renames", "--diff-filter=AM", base]);
90740
91389
  for (const file of names.stdout.split("\x00").filter(Boolean)) {
90741
91390
  const lines = new Set;
90742
- const diff = git5(root, ["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", file]);
91391
+ const diff = git6(root, ["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", file]);
90743
91392
  for (const line of diff.stdout.split(`
90744
91393
  `)) {
90745
91394
  const hunk = /^@@ -\S+ \+(\d+)(?:,(\d+))? @@/.exec(line);
@@ -90752,7 +91401,7 @@ function changedLines(cwd, base) {
90752
91401
  }
90753
91402
  changed.set(path43.join(root, file), lines);
90754
91403
  }
90755
- const untracked = git5(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
91404
+ const untracked = git6(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
90756
91405
  for (const file of untracked.stdout.split("\x00").filter(Boolean))
90757
91406
  changed.set(path43.join(root, file), "all");
90758
91407
  return changed;
@@ -90767,7 +91416,7 @@ function auditFiles(files, display) {
90767
91416
  const shown = display(file);
90768
91417
  let parsed;
90769
91418
  try {
90770
- parsed = parseSource(readFileSync18(file, "utf8"), file);
91419
+ parsed = parseSource(readFileSync19(file, "utf8"), file);
90771
91420
  } catch (error) {
90772
91421
  parseErrors.push({
90773
91422
  file: shown,
@@ -90835,7 +91484,7 @@ import {
90835
91484
  mkdirSync as mkdirSync9,
90836
91485
  mkdtempSync as mkdtempSync4,
90837
91486
  readdirSync as readdirSync9,
90838
- readFileSync as readFileSync19,
91487
+ readFileSync as readFileSync20,
90839
91488
  readlinkSync as readlinkSync3,
90840
91489
  realpathSync as realpathSync9,
90841
91490
  renameSync as renameSync5,
@@ -90944,7 +91593,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
90944
91593
  const stat = lstatSync7(dir);
90945
91594
  if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
90946
91595
  continue;
90947
- const pid = Number(readFileSync19(path44.join(dir, PID_FILE), "utf8"));
91596
+ const pid = Number(readFileSync20(path44.join(dir, PID_FILE), "utf8"));
90948
91597
  if (Number.isInteger(pid) && pid > 0 && alive(pid))
90949
91598
  continue;
90950
91599
  } catch (error) {
@@ -91022,18 +91671,18 @@ function copyWorkingTree(repo) {
91022
91671
  const dir = mkdtempSync4(path44.join(os14.tmpdir(), TEMP_PREFIX));
91023
91672
  writeFileSync9(path44.join(dir, PID_FILE), String(process.pid));
91024
91673
  const tree = path44.join(dir, "tree");
91025
- const head = git5(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
91026
- if (head && git5(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
91027
- const branch = git5(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
91674
+ const head = git6(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
91675
+ if (head && git6(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
91676
+ const branch = git6(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
91028
91677
  if (branch) {
91029
- git5(tree, ["update-ref", `refs/heads/${branch}`, head]);
91030
- git5(tree, ["symbolic-ref", "HEAD", `refs/heads/${branch}`]);
91678
+ git6(tree, ["update-ref", `refs/heads/${branch}`, head]);
91679
+ git6(tree, ["symbolic-ref", "HEAD", `refs/heads/${branch}`]);
91031
91680
  } else
91032
- git5(tree, ["update-ref", "--no-deref", "HEAD", head]);
91033
- git5(tree, ["reset", "-q"]);
91681
+ git6(tree, ["update-ref", "--no-deref", "HEAD", head]);
91682
+ git6(tree, ["reset", "-q"]);
91034
91683
  } else
91035
91684
  mkdirSync9(tree, { recursive: true });
91036
- const listed = git5(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00");
91685
+ const listed = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00");
91037
91686
  const dirs = new Set([""]);
91038
91687
  for (const relative of listed) {
91039
91688
  if (!relative)
@@ -91096,10 +91745,10 @@ function runCommand(command, cwd, timeoutMs, env) {
91096
91745
  async function runMutation(options) {
91097
91746
  const started = performance.now();
91098
91747
  const { repo } = options;
91099
- const tracked = git5(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
91748
+ const tracked = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
91100
91749
  const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
91101
91750
  try {
91102
- return { file, text: readFileSync19(path44.join(repo, file), "utf8") };
91751
+ return { file, text: readFileSync20(path44.join(repo, file), "utf8") };
91103
91752
  } catch {
91104
91753
  return { file, text: "" };
91105
91754
  }
@@ -91112,7 +91761,7 @@ async function runMutation(options) {
91112
91761
  if (NOT_SOURCE.test(relative) || !existsSync19(absolute))
91113
91762
  continue;
91114
91763
  try {
91115
- candidates.push(...mutantsFor(readFileSync19(absolute, "utf8"), relative, lines));
91764
+ candidates.push(...mutantsFor(readFileSync20(absolute, "utf8"), relative, lines));
91116
91765
  } catch {}
91117
91766
  }
91118
91767
  const mutants = candidates.map((mutant, index) => ({ ...mutant, id: index + 1 }));
@@ -91192,7 +91841,7 @@ async function runMutation(options) {
91192
91841
  record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
91193
91842
  continue;
91194
91843
  }
91195
- const original = readFileSync19(path44.join(copy, mutant.file), "utf8");
91844
+ const original = readFileSync20(path44.join(copy, mutant.file), "utf8");
91196
91845
  const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
91197
91846
  current = { file: mutant.file, original };
91198
91847
  if (!writeInside(copy, mutant.file, mutated)) {
@@ -91398,7 +92047,7 @@ async function run16(argv, io) {
91398
92047
  if (typeof options === "string")
91399
92048
  return emit(io, fail("invalid_input", `${options}
91400
92049
  usage: ${USAGE9}`));
91401
- const top = git5(io.cwd, ["rev-parse", "--show-toplevel"]);
92050
+ const top = git6(io.cwd, ["rev-parse", "--show-toplevel"]);
91402
92051
  const repo = top.ok ? path45.resolve(top.stdout.trim()) : null;
91403
92052
  const display = (file) => toPosix(path45.relative(io.cwd, file) || file);
91404
92053
  let base = null;
@@ -91610,7 +92259,7 @@ var exports_youtrack = {};
91610
92259
  __export(exports_youtrack, {
91611
92260
  run: () => run17
91612
92261
  });
91613
- import { readFileSync as readFileSync20 } from "node:fs";
92262
+ import { readFileSync as readFileSync21 } from "node:fs";
91614
92263
  import path46 from "node:path";
91615
92264
  async function run17(argv, io) {
91616
92265
  const [sub, ...rest] = argv;
@@ -91657,7 +92306,7 @@ async function run17(argv, io) {
91657
92306
  let markdown = flags.values.markdown ?? "";
91658
92307
  if (flags.values.file)
91659
92308
  try {
91660
- markdown = readFileSync20(path46.resolve(io.cwd, flags.values.file), "utf8");
92309
+ markdown = readFileSync21(path46.resolve(io.cwd, flags.values.file), "utf8");
91661
92310
  } catch (error) {
91662
92311
  return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE10);
91663
92312
  }
@@ -91725,7 +92374,7 @@ var exports_changelog = {};
91725
92374
  __export(exports_changelog, {
91726
92375
  run: () => run18
91727
92376
  });
91728
- import { readFileSync as readFileSync21 } from "node:fs";
92377
+ import { readFileSync as readFileSync22 } from "node:fs";
91729
92378
  import path47 from "node:path";
91730
92379
  async function run18(argv, io) {
91731
92380
  const [sub, ...rest] = argv;
@@ -91772,7 +92421,7 @@ async function run18(argv, io) {
91772
92421
  ]);
91773
92422
  }
91774
92423
  var USAGE11 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
91775
- const text = raw === "-" ? readFileSync21(0, "utf8") : raw.startsWith("@") ? readFileSync21(path47.resolve(cwd, raw.slice(1)), "utf8") : raw;
92424
+ const text = raw === "-" ? readFileSync22(0, "utf8") : raw.startsWith("@") ? readFileSync22(path47.resolve(cwd, raw.slice(1)), "utf8") : raw;
91776
92425
  return JSON.parse(text);
91777
92426
  };
91778
92427
  var init_changelog2 = __esm(() => {
@@ -91949,7 +92598,7 @@ var VERBS = [
91949
92598
  {
91950
92599
  name: "pr",
91951
92600
  group: "delivery",
91952
- usage: "workit pr status [--pr <n>] | pr create [--base <b>] (--title <t> | --fill) [--draft] | pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch]",
92601
+ usage: "workit pr status [--pr <n>] | pr create [--base <b> | --track <t>] (--title <t> | --fill) [--draft] | pr merge [--pr <n>] [--method squash|merge|rebase] [--delete-branch]",
91953
92602
  summary: "PR/MR state with failing log tails and next action; open a SHA-verified PR; merge only when READY, verified and granted",
91954
92603
  load: () => Promise.resolve().then(() => (init_pr(), exports_pr))
91955
92604
  },
@@ -91963,7 +92612,7 @@ var VERBS = [
91963
92612
  {
91964
92613
  name: "git",
91965
92614
  group: "delivery",
91966
- usage: "workit git branch <name> [--base <b>] | git commit -m <msg> [--all | -- <paths…>] | git push [--set-upstream] [--force-with-lease]",
92615
+ usage: "workit git branch <name> [--base <b>] [--track <t>] | git commit -m <msg> [--all | -- <paths…>] | git push [--set-upstream] [--force-with-lease]",
91967
92616
  summary: "Policy-checked branch and commit (Workit-Session trailer); push with a lease and a verified remote tip",
91968
92617
  load: () => Promise.resolve().then(() => (init_git2(), exports_git))
91969
92618
  },