@brainervirus/workit-claude-code 7.0.0 → 7.2.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.2.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.2.0",
107
+ "@brainervirus/workit-cursor": "^7.2.0",
108
+ "@brainervirus/workit-opencode": "^7.2.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"],
@@ -15453,7 +15473,7 @@ var init_workspaces = __esm(() => {
15453
15473
  account: nonBlank.optional()
15454
15474
  }).passthrough().optional(),
15455
15475
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
15456
- defaultEndpoint: _enum(["commit", "pr"]).optional(),
15476
+ defaultEndpoint: unknown().optional(),
15457
15477
  verification: _enum(["self", "independent"]).optional(),
15458
15478
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
15459
15479
  kind,
@@ -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
  }
@@ -15933,6 +16457,46 @@ var init_safe_write = () => {};
15933
16457
  import { copyFileSync as copyFileSync3, existsSync as existsSync6 } from "node:fs";
15934
16458
  import os3 from "node:os";
15935
16459
  import path18 from "node:path";
16460
+ function endpointOf(entry) {
16461
+ const raw = entry?.defaultEndpoint;
16462
+ if (raw === undefined)
16463
+ return { raw, endpoint: DEFAULT_ENDPOINT };
16464
+ if (isDefaultEndpoint(raw))
16465
+ return { raw, endpoint: raw };
16466
+ return {
16467
+ raw,
16468
+ endpoint: DEFAULT_ENDPOINT,
16469
+ issue: `defaultEndpoint ${JSON.stringify(raw)} is not one of ${DEFAULT_ENDPOINTS.join(", ")}; using ${DEFAULT_ENDPOINT}`
16470
+ };
16471
+ }
16472
+ function effectiveEndpoint(endpoint, context) {
16473
+ if (endpoint === "commit")
16474
+ return { endpoint };
16475
+ const missing = (kind) => `${kind} grant missing; ${grantHint(context.workspace, kind)}`;
16476
+ if (context.grants.push === false)
16477
+ return { endpoint: "commit", reason: missing("push") };
16478
+ if (context.grants.pr === false)
16479
+ return { endpoint: "commit", reason: missing("pr") };
16480
+ if (context.configured.length > 0 && !context.accountConfigured)
16481
+ return {
16482
+ endpoint: "commit",
16483
+ reason: `vcs.account missing; set vcs.account for workspace "${context.workspace ?? "?"}" in ~/.config/workit/workspaces.json`
16484
+ };
16485
+ if (endpoint === "merged" && context.grants.merge === false)
16486
+ return { endpoint: "green", reason: missing("merge") };
16487
+ return { endpoint };
16488
+ }
16489
+ function endpointView(entry, context) {
16490
+ const { raw, endpoint, issue } = endpointOf(entry);
16491
+ const effective = effectiveEndpoint(endpoint, context);
16492
+ return {
16493
+ defaultEndpoint: endpoint,
16494
+ configuredEndpoint: raw ?? null,
16495
+ effectiveEndpoint: effective.endpoint,
16496
+ ...effective.reason ? { endpointReason: effective.reason } : {},
16497
+ ...issue ? { endpointIssue: issue } : {}
16498
+ };
16499
+ }
15936
16500
  function configuredGrants(entry) {
15937
16501
  const record = entry ?? {};
15938
16502
  const raw = record.autonomy;
@@ -15958,18 +16522,28 @@ function resolveAutonomy(cwd) {
15958
16522
  source: "default",
15959
16523
  accountConfigured: false,
15960
16524
  defaultEndpoint: DEFAULT_ENDPOINT,
16525
+ configuredEndpoint: null,
16526
+ effectiveEndpoint: DEFAULT_ENDPOINT,
15961
16527
  verification: DEFAULT_VERIFICATION,
15962
16528
  note: override
15963
16529
  };
15964
16530
  const workspace = resolveWorkspaceFrom(cwd, grantsDir(), process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined);
15965
16531
  const { grants, source } = configuredGrants(workspace);
16532
+ const full = { ...DEFAULT_GRANTS, ...grants };
16533
+ const configured = GRANT_KINDS.filter((kind) => grants[kind] !== undefined);
16534
+ const accountConfigured = Boolean(workspace?.vcs?.account);
15966
16535
  return {
15967
16536
  workspace: workspace?.name ?? null,
15968
- grants: { ...DEFAULT_GRANTS, ...grants },
15969
- configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
16537
+ grants: full,
16538
+ configured,
15970
16539
  source,
15971
- accountConfigured: Boolean(workspace?.vcs?.account),
15972
- defaultEndpoint: endpointOf(workspace),
16540
+ accountConfigured,
16541
+ ...endpointView(workspace, {
16542
+ workspace: workspace?.name ?? null,
16543
+ grants: full,
16544
+ configured,
16545
+ accountConfigured
16546
+ }),
15973
16547
  verification: verificationOf(workspace)
15974
16548
  };
15975
16549
  }
@@ -16075,14 +16649,21 @@ function writeGrants(workspaceName, changes, dir = grantsDir(), endpoint, verifi
16075
16649
  error: `failed to write ${current.path}: ${error instanceof Error ? error.message : String(error)}`
16076
16650
  };
16077
16651
  }
16652
+ const full = { ...DEFAULT_GRANTS, ...grants };
16653
+ const configured = GRANT_KINDS.filter((kind) => grants[kind] !== undefined);
16078
16654
  return {
16079
16655
  ok: true,
16080
16656
  path: current.path,
16081
16657
  backup,
16082
16658
  workspace: workspaceName,
16083
- grants: { ...DEFAULT_GRANTS, ...grants },
16084
- configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
16085
- defaultEndpoint: endpointOf(entry),
16659
+ grants: full,
16660
+ configured,
16661
+ ...endpointView(entry, {
16662
+ workspace: workspaceName,
16663
+ grants: full,
16664
+ configured,
16665
+ accountConfigured: Boolean(accountOf(entry))
16666
+ }),
16086
16667
  verification: verificationOf(entry)
16087
16668
  };
16088
16669
  }
@@ -16094,12 +16675,19 @@ function listGrants(dir = grantsDir()) {
16094
16675
  path: current.path,
16095
16676
  workspaces: current.entries.map((entry) => {
16096
16677
  const { grants } = configuredGrants(entry);
16678
+ const full = { ...DEFAULT_GRANTS, ...grants };
16679
+ const configured = GRANT_KINDS.filter((kind) => grants[kind] !== undefined);
16097
16680
  return {
16098
16681
  name: entry.name,
16099
16682
  glob: entry.glob,
16100
- grants: { ...DEFAULT_GRANTS, ...grants },
16101
- configured: GRANT_KINDS.filter((kind) => grants[kind] !== undefined),
16102
- defaultEndpoint: endpointOf(entry),
16683
+ grants: full,
16684
+ configured,
16685
+ ...endpointView(entry, {
16686
+ workspace: entry.name,
16687
+ grants: full,
16688
+ configured,
16689
+ accountConfigured: Boolean(entry.vcs?.account)
16690
+ }),
16103
16691
  verification: verificationOf(entry)
16104
16692
  };
16105
16693
  })
@@ -16114,7 +16702,7 @@ var accountHome = () => {
16114
16702
  }, grantsHome, grantsDir = () => path18.join(grantsHome.resolve(), ".config", "workit"), grantsOverride = () => {
16115
16703
  const configured = path18.resolve(resolveConfigDir());
16116
16704
  return configured === path18.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path18.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
16117
- }, GRANT_KINDS, DEFAULT_GRANTS, DEFAULT_ENDPOINT = "commit", DEFAULT_VERIFICATION = "self", verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self", endpointOf = (entry) => entry?.defaultEndpoint === "pr" ? "pr" : "commit", isGrantKind = (value) => typeof value === "string" && GRANT_KINDS.includes(value), isGrantValue = (value) => value === true || value === false || value === "verified", normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value, fromAutoApprove = (value) => {
16705
+ }, GRANT_KINDS, DEFAULT_GRANTS, DEFAULT_ENDPOINTS, DEFAULT_ENDPOINT = "commit", isDefaultEndpoint = (value) => typeof value === "string" && DEFAULT_ENDPOINTS.includes(value), raisesEndpoint = (current, next) => DEFAULT_ENDPOINTS.indexOf(next) > DEFAULT_ENDPOINTS.indexOf(current), DEFAULT_VERIFICATION = "self", verificationOf = (entry) => entry?.verification === "independent" ? "independent" : "self", isGrantKind = (value) => typeof value === "string" && GRANT_KINDS.includes(value), isGrantValue = (value) => value === true || value === false || value === "verified", normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value, fromAutoApprove = (value) => {
16118
16706
  if (value === true)
16119
16707
  return { push: true, pr: true, merge: "verified" };
16120
16708
  if (!Array.isArray(value))
@@ -16126,7 +16714,7 @@ var accountHome = () => {
16126
16714
  else if (item === "merge")
16127
16715
  grants.merge = "verified";
16128
16716
  return Object.keys(grants).length > 0 ? grants : null;
16129
- }, suggested = (kind) => kind === "merge" ? "verified" : "true", grantHint = (workspace, kind) => `ask the user to run, in their own terminal: workit grant set ${workspace ?? "<workspace>"} ${kind}=${suggested(kind)}`, raises = (kind, current, next) => {
16717
+ }, accountOf = (entry) => entry?.vcs?.account, suggested = (kind) => kind === "merge" ? "verified" : "true", grantHint = (workspace, kind) => `ask the user to run, in their own terminal: workit grant set ${workspace ?? "<workspace>"} ${kind}=${suggested(kind)}`, raises = (kind, current, next) => {
16130
16718
  const rank = (value) => value === true ? 2 : value === "verified" ? 1 : 0;
16131
16719
  return rank(normalized(kind, next)) > rank(normalized(kind, current));
16132
16720
  };
@@ -16143,6 +16731,7 @@ var init_autonomy = __esm(() => {
16143
16731
  release: false,
16144
16732
  rerun: true
16145
16733
  });
16734
+ DEFAULT_ENDPOINTS = ["commit", "pr", "green", "merged"];
16146
16735
  });
16147
16736
 
16148
16737
  // packages/workit-core/src/core/task-engine.ts
@@ -17796,7 +18385,7 @@ var init_pr_body = __esm(() => {
17796
18385
 
17797
18386
  // packages/workit-core/src/core/pr-create.ts
17798
18387
  import path19 from "node:path";
17799
- import { spawnSync as spawnSync8 } from "node:child_process";
18388
+ import { spawnSync as spawnSync9 } from "node:child_process";
17800
18389
  import { pathToFileURL } from "node:url";
17801
18390
  var safePushUrl = (remote) => {
17802
18391
  const raw = remote.trim();
@@ -17824,7 +18413,7 @@ var safePushUrl = (remote) => {
17824
18413
  const target = safePushUrl(remote);
17825
18414
  if (!target)
17826
18415
  return false;
17827
- const result = spawnSync8("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18416
+ const result = spawnSync9("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
17828
18417
  if (result.status !== 0 && result.status !== 1)
17829
18418
  return false;
17830
18419
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -18400,7 +18989,7 @@ var init_core3 = __esm(() => {
18400
18989
  });
18401
18990
 
18402
18991
  // packages/workit-cli/src/task.ts
18403
- import { readFileSync as readFileSync6 } from "node:fs";
18992
+ import { readFileSync as readFileSync7 } from "node:fs";
18404
18993
  import path22 from "node:path";
18405
18994
  async function readInput(input) {
18406
18995
  if (typeof input === "string")
@@ -18417,7 +19006,7 @@ async function payloadValue(raw, deps) {
18417
19006
  return { ok: true, value: {} };
18418
19007
  let source;
18419
19008
  try {
18420
- source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync6(raw.slice(1))) : raw;
19009
+ source = raw === "-" ? await readInput(deps.stdin ?? process.stdin) : raw.startsWith("@") ? decodeUtf8(readFileSync7(raw.slice(1))) : raw;
18421
19010
  } catch (error) {
18422
19011
  return {
18423
19012
  ok: false,
@@ -18750,7 +19339,7 @@ function isImplicitTaskForm(argv) {
18750
19339
  const [action, first] = argv;
18751
19340
  if (!action)
18752
19341
  return false;
18753
- if (IMPLICIT.has(action))
19342
+ if (IMPLICIT2.has(action))
18754
19343
  return true;
18755
19344
  if (action === "start")
18756
19345
  return first !== undefined && !first.startsWith("--");
@@ -18948,7 +19537,7 @@ async function run(argv, io) {
18948
19537
  }
18949
19538
  return emit(io, fail("invalid_input", `unknown task action ${action ?? ""}`, { unblock: USAGE }));
18950
19539
  }
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}`, {
19540
+ 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
19541
  data: { code: result.code },
18953
19542
  ...unblock ? { unblock } : result.code === "busy" ? { unblock: "retry the same command" } : {}
18954
19543
  })), OUTCOMES, rootFor = (io) => io.env.WORKFLOW_WORKSPACE_ROOT || checkRoot(io.cwd);
@@ -18956,7 +19545,7 @@ var init_task2 = __esm(() => {
18956
19545
  init_check_config();
18957
19546
  init_output();
18958
19547
  init_task();
18959
- IMPLICIT = new Set(["status", "note", "adopt"]);
19548
+ IMPLICIT2 = new Set(["status", "note", "adopt"]);
18960
19549
  VALUE_FLAGS = new Set(["--next", "--objective", "--outcome", "--summary"]);
18961
19550
  SWITCHES = new Set(["--all", "--json", "--confirm"]);
18962
19551
  ENGINE_CODES = {
@@ -61542,7 +62131,7 @@ var init_gitignore = __esm(() => {
61542
62131
  // packages/workit-core/src/core/changelog.ts
61543
62132
  import fs16 from "node:fs";
61544
62133
  import path23 from "node:path";
61545
- import { createHash as createHash11 } from "node:crypto";
62134
+ import { createHash as createHash12 } from "node:crypto";
61546
62135
  function normalizeBullet(text) {
61547
62136
  text = text.trim();
61548
62137
  const m = BULLET_RE.exec(text);
@@ -61829,7 +62418,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61829
62418
 
61830
62419
  ## [Unreleased]
61831
62420
 
61832
- `, digestBytes2 = (value) => createHash11("sha256").update(value).digest("hex"), readChangelogSource = (target) => {
62421
+ `, digestBytes2 = (value) => createHash12("sha256").update(value).digest("hex"), readChangelogSource = (target) => {
61833
62422
  if (!fs16.existsSync(target)) {
61834
62423
  const bytes = Buffer.from(SKELETON, "utf8");
61835
62424
  return { exists: false, bytes, text: SKELETON };
@@ -61869,13 +62458,13 @@ var init_changelog = __esm(() => {
61869
62458
  });
61870
62459
 
61871
62460
  // packages/workit-core/src/core/hygiene.ts
61872
- import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
62461
+ import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
61873
62462
  import path24 from "node:path";
61874
62463
  var repoRoot2, templatesDir = () => path24.join(repoRoot2, "templates", "hygiene"), packageJson = (root) => {
61875
62464
  if (!existsSync9(path24.join(root, "package.json")))
61876
62465
  return null;
61877
62466
  try {
61878
- return JSON.parse(readFileSync8(path24.join(root, "package.json"), "utf8"));
62467
+ return JSON.parse(readFileSync9(path24.join(root, "package.json"), "utf8"));
61879
62468
  } catch {
61880
62469
  return null;
61881
62470
  }
@@ -61911,7 +62500,7 @@ var repoRoot2, templatesDir = () => path24.join(repoRoot2, "templates", "hygiene
61911
62500
  const tpl = path24.join(templatesDir(), file);
61912
62501
  if (!existsSync9(tpl))
61913
62502
  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));
62503
+ 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
62504
  planned.push({ path: path24.join(root, file), content });
61916
62505
  }
61917
62506
  return planned;
@@ -61923,13 +62512,13 @@ var init_hygiene = __esm(() => {
61923
62512
  });
61924
62513
 
61925
62514
  // packages/workit-cli/src/admin/setup-state.ts
61926
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
62515
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
61927
62516
  import path25 from "node:path";
61928
62517
  var classifySetupFile = (dir, name) => {
61929
62518
  const file = path25.join(dir, name);
61930
62519
  let raw;
61931
62520
  try {
61932
- raw = readFileSync9(file, "utf8");
62521
+ raw = readFileSync10(file, "utf8");
61933
62522
  } catch {
61934
62523
  if (existsSync10(file)) {
61935
62524
  return { file, status: "malformed", error: `${file} is not readable` };
@@ -61978,7 +62567,7 @@ function mergeOpenCodePlugins(plugin, pin) {
61978
62567
  return { config, changed };
61979
62568
  }
61980
62569
  function mergeOpenCodeConfig(config, pin) {
61981
- const base = isRecord3(config) ? { ...config } : {};
62570
+ const base = isRecord4(config) ? { ...config } : {};
61982
62571
  const changed = [];
61983
62572
  const plugins = mergeOpenCodePlugins(base.plugin, pin);
61984
62573
  if (plugins.changed.length > 0) {
@@ -61988,7 +62577,7 @@ function mergeOpenCodeConfig(config, pin) {
61988
62577
  return { config: base, changed };
61989
62578
  }
61990
62579
  function mergeCursorEnabledPlugins(enabled) {
61991
- const prev = isRecord3(enabled) ? { ...enabled } : {};
62580
+ const prev = isRecord4(enabled) ? { ...enabled } : {};
61992
62581
  const next = { ...prev, workit: true };
61993
62582
  delete next["workflow-toolkit"];
61994
62583
  delete next["local/workflow-toolkit"];
@@ -62010,7 +62599,7 @@ function mergeCursorPluginDirs(pluginDirs, pluginDir) {
62010
62599
  return { config: next, changed };
62011
62600
  }
62012
62601
  function mergeCursorSettings(settings, pluginDir) {
62013
- const base = isRecord3(settings) ? { ...settings } : {};
62602
+ const base = isRecord4(settings) ? { ...settings } : {};
62014
62603
  const changed = [];
62015
62604
  const enabled = mergeCursorEnabledPlugins(base.enabled_plugins);
62016
62605
  if (enabled.changed.length > 0) {
@@ -62025,8 +62614,8 @@ function mergeCursorSettings(settings, pluginDir) {
62025
62614
  return { config: base, changed };
62026
62615
  }
62027
62616
  function mergeCursorMcp(mcp, serverName, server) {
62028
- const base = isRecord3(mcp) ? { ...mcp } : {};
62029
- const servers = isRecord3(base.mcpServers) ? { ...base.mcpServers } : {};
62617
+ const base = isRecord4(mcp) ? { ...mcp } : {};
62618
+ const servers = isRecord4(base.mcpServers) ? { ...base.mcpServers } : {};
62030
62619
  delete servers["workflow-toolkit"];
62031
62620
  servers[serverName] = server;
62032
62621
  const changed = JSON.stringify(servers) !== JSON.stringify(base.mcpServers) ? ["mcpServers"] : [];
@@ -62034,7 +62623,7 @@ function mergeCursorMcp(mcp, serverName, server) {
62034
62623
  return { config: base, changed };
62035
62624
  }
62036
62625
  function cursorHookDrift(installed) {
62037
- if (!isRecord3(installed) || !isRecord3(installed.hooks))
62626
+ if (!isRecord4(installed) || !isRecord4(installed.hooks))
62038
62627
  return ["hooks file is not a hook map"];
62039
62628
  const hooks = installed.hooks;
62040
62629
  const drift = [];
@@ -62044,7 +62633,7 @@ function cursorHookDrift(installed) {
62044
62633
  continue;
62045
62634
  const canonical = canonicalHookEntry(event);
62046
62635
  const entry = Array.isArray(list) ? list[0] : undefined;
62047
- if (!isRecord3(entry) || entry.command !== canonical.command || event === "preToolUse" && entry.matcher !== canonical.matcher)
62636
+ if (!isRecord4(entry) || entry.command !== canonical.command || event === "preToolUse" && entry.matcher !== canonical.matcher)
62048
62637
  drift.push(event);
62049
62638
  }
62050
62639
  return drift;
@@ -62068,7 +62657,7 @@ function cursorHooksEntry(_packageDir) {
62068
62657
  args: []
62069
62658
  };
62070
62659
  }
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 };
62660
+ 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
62661
  var init_registration = __esm(() => {
62073
62662
  CURSOR_HOOK_RUN_COMMAND = `npx -y --prefer-online --min-release-age=0 --package=${CURSOR_RUNTIME_PACKAGE} workit-cursor-hook`;
62074
62663
  });
@@ -62087,12 +62676,12 @@ var init_support_matrix = __esm(() => {
62087
62676
  });
62088
62677
 
62089
62678
  // packages/workit-cli/src/admin/host-install.ts
62090
- import { spawnSync as spawnSync9 } from "node:child_process";
62679
+ import { spawnSync as spawnSync10 } from "node:child_process";
62091
62680
  import {
62092
62681
  accessSync,
62093
62682
  constants as constants2,
62094
62683
  existsSync as existsSync11,
62095
- readFileSync as readFileSync10,
62684
+ readFileSync as readFileSync11,
62096
62685
  readdirSync as readdirSync4,
62097
62686
  realpathSync as realpathSync6,
62098
62687
  statSync as statSync2
@@ -62358,7 +62947,7 @@ function runHostCommand(step, context = {}) {
62358
62947
  stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
62359
62948
  };
62360
62949
  }
62361
- const result = spawnSync9(step.command, step.args, {
62950
+ const result = spawnSync10(step.command, step.args, {
62362
62951
  cwd: step.cwd,
62363
62952
  env: (() => {
62364
62953
  const home = context.home ?? context.env?.HOME ?? os6.homedir();
@@ -62382,7 +62971,7 @@ function runHostCommand(step, context = {}) {
62382
62971
  }
62383
62972
  var resolveWindowsShimScript = (shim) => {
62384
62973
  try {
62385
- const contents = readFileSync10(shim, "utf8");
62974
+ const contents = readFileSync11(shim, "utf8");
62386
62975
  return resolveWindowsShimEntry(contents, shim);
62387
62976
  } catch {
62388
62977
  return null;
@@ -62403,7 +62992,7 @@ var resolveWindowsShimScript = (shim) => {
62403
62992
  }, isPiWorkitInstalled = (home, env = process.env) => {
62404
62993
  const settings = path27.join(env.PI_CODING_AGENT_DIR ?? path27.join(home, ".pi", "agent"), "settings.json");
62405
62994
  try {
62406
- const document2 = JSON.parse(readFileSync10(settings, "utf8"));
62995
+ const document2 = JSON.parse(readFileSync11(settings, "utf8"));
62407
62996
  return (document2.packages ?? []).some((entry) => {
62408
62997
  const source = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.source : entry;
62409
62998
  const value = String(source).replaceAll("\\", "/").replace(/\/+$/, "");
@@ -62418,7 +63007,7 @@ var resolveWindowsShimScript = (shim) => {
62418
63007
  path27.join(root, "package.json")
62419
63008
  ]) {
62420
63009
  try {
62421
- const manifest = JSON.parse(readFileSync10(file, "utf8"));
63010
+ const manifest = JSON.parse(readFileSync11(file, "utf8"));
62422
63011
  const repository = typeof manifest.repository === "string" ? manifest.repository : manifest.repository?.url;
62423
63012
  if ((manifest.name === "workit" || manifest.name === "@brainervirus/workit-codex") && repository?.replace(/\.git$/, "").toLowerCase() === "https://github.com/brainervirus/workit")
62424
63013
  return true;
@@ -62459,7 +63048,7 @@ var resolveWindowsShimScript = (shim) => {
62459
63048
  return relative === "" || !relative.startsWith("..") && !path27.isAbsolute(relative);
62460
63049
  }, isCanonicalClaudePlugin = (root) => {
62461
63050
  try {
62462
- const manifest = JSON.parse(readFileSync10(path27.join(root, ".claude-plugin", "plugin.json"), "utf8"));
63051
+ const manifest = JSON.parse(readFileSync11(path27.join(root, ".claude-plugin", "plugin.json"), "utf8"));
62463
63052
  const repository = typeof manifest.repository === "string" ? manifest.repository : manifest.repository?.url;
62464
63053
  return manifest.name === "workit" && repository?.replace(/\.git$/, "").toLowerCase() === "https://github.com/brainervirus/workit";
62465
63054
  } catch {
@@ -62469,7 +63058,7 @@ var resolveWindowsShimScript = (shim) => {
62469
63058
  const file = path27.join(claudeConfigDir(home, env), "plugins", "installed_plugins.json");
62470
63059
  let registry;
62471
63060
  try {
62472
- registry = JSON.parse(readFileSync10(file, "utf8"));
63061
+ registry = JSON.parse(readFileSync11(file, "utf8"));
62473
63062
  } catch {
62474
63063
  return [];
62475
63064
  }
@@ -62518,7 +63107,7 @@ var init_host_install = __esm(() => {
62518
63107
  });
62519
63108
 
62520
63109
  // 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";
63110
+ import { existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
62522
63111
  import path28 from "node:path";
62523
63112
  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
63113
  const actual = skillManifestNames(root);
@@ -62536,7 +63125,7 @@ var WORKIT_METHOD_SKILLS, skillManifestNames = (root) => existsSync12(root) ? re
62536
63125
  const file = path28.join(dir, entry.name);
62537
63126
  if (entry.isDirectory())
62538
63127
  pending.push(file);
62539
- else if ((statSync3(file).mode & 73) !== 0 || readFileSync11(file).subarray(0, 2).toString("latin1") === "#!") {
63128
+ else if ((statSync3(file).mode & 73) !== 0 || readFileSync12(file).subarray(0, 2).toString("latin1") === "#!") {
62540
63129
  return `Cursor vendor contains active file: ${file}`;
62541
63130
  }
62542
63131
  }
@@ -62561,13 +63150,13 @@ var init_skill_manifests = __esm(() => {
62561
63150
  });
62562
63151
 
62563
63152
  // packages/workit-cli/src/admin/doctor.ts
62564
- import { spawnSync as spawnSync10 } from "node:child_process";
63153
+ import { spawnSync as spawnSync11 } from "node:child_process";
62565
63154
  import {
62566
63155
  existsSync as existsSync13,
62567
63156
  lstatSync as lstatSync4,
62568
63157
  mkdirSync as mkdirSync4,
62569
63158
  mkdtempSync,
62570
- readFileSync as readFileSync12,
63159
+ readFileSync as readFileSync13,
62571
63160
  readlinkSync as readlinkSync2,
62572
63161
  rmSync as rmSync2,
62573
63162
  statSync as statSync4,
@@ -62611,14 +63200,14 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62611
63200
  };
62612
63201
  }, readJson = (p) => {
62613
63202
  try {
62614
- const parsed = JSON.parse(readFileSync12(p, "utf8"));
63203
+ const parsed = JSON.parse(readFileSync13(p, "utf8"));
62615
63204
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
62616
63205
  } catch {
62617
63206
  return null;
62618
63207
  }
62619
63208
  }, parsesAsConfigObject = (p) => {
62620
63209
  try {
62621
- return isConfigObject(JSON.parse(readFileSync12(p, "utf8")));
63210
+ return isConfigObject(JSON.parse(readFileSync13(p, "utf8")));
62622
63211
  } catch {
62623
63212
  return false;
62624
63213
  }
@@ -62645,7 +63234,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62645
63234
  }
62646
63235
  return false;
62647
63236
  }, versionOf = (bin, env, timeout) => {
62648
- const r = spawnSync10(bin, ["--version"], { encoding: "utf8", env, timeout });
63237
+ const r = spawnSync11(bin, ["--version"], { encoding: "utf8", env, timeout });
62649
63238
  if (r.error)
62650
63239
  return null;
62651
63240
  return (r.stdout ?? "").trim();
@@ -62890,11 +63479,11 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
62890
63479
  }, validNodeEntry = (entry, runtime, env) => {
62891
63480
  try {
62892
63481
  const stat = statSync4(entry);
62893
- if (!stat.isFile() || stat.size === 0 || !readFileSync12(entry, "utf8").startsWith(`#!/usr/bin/env node
63482
+ if (!stat.isFile() || stat.size === 0 || !readFileSync13(entry, "utf8").startsWith(`#!/usr/bin/env node
62894
63483
  `)) {
62895
63484
  return false;
62896
63485
  }
62897
- return spawnSync10(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63486
+ return spawnSync11(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
62898
63487
  } catch {
62899
63488
  return false;
62900
63489
  }
@@ -63108,7 +63697,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63108
63697
  if (!cmd)
63109
63698
  return null;
63110
63699
  try {
63111
- const r = spawnSync10(cmd, ["view", pkgName, "version"], {
63700
+ const r = spawnSync11(cmd, ["view", pkgName, "version"], {
63112
63701
  encoding: "utf8",
63113
63702
  timeout: 20000,
63114
63703
  env: res.env
@@ -63419,12 +64008,27 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63419
64008
  fix: `Choose a matching workspace with WORKFLOW_WORKSPACE_NAME or repair ${file}`
63420
64009
  };
63421
64010
  }
63422
- if (match)
63423
- return {
63424
- id: "workspace_mismatch",
63425
- status: "pass",
63426
- detail: `current directory matches workspace "${match.name}"`
63427
- };
64011
+ if (match) {
64012
+ const matched = `current directory matches workspace "${match.name}"`;
64013
+ const tracks = releaseTracksReport(res.cwd);
64014
+ const lines = describeReleaseTracks(tracks);
64015
+ const detail = [matched, ...lines].join("; ");
64016
+ if (tracks.error)
64017
+ return {
64018
+ id: "workspace_mismatch",
64019
+ status: "fail",
64020
+ detail,
64021
+ fix: `Repair releaseTracks in ${file} (or upgrade Workit for a critical field)`
64022
+ };
64023
+ if (tracks.warnings.length)
64024
+ return {
64025
+ id: "workspace_mismatch",
64026
+ status: "warn",
64027
+ detail,
64028
+ fix: `Fix the reported releaseTracks fields in ${file}, or pass --track <name> where the track is not determined`
64029
+ };
64030
+ return { id: "workspace_mismatch", status: "pass", detail };
64031
+ }
63428
64032
  return {
63429
64033
  id: "workspace_mismatch",
63430
64034
  status: "fail",
@@ -63433,7 +64037,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63433
64037
  };
63434
64038
  }, isPlaceholder = (p) => {
63435
64039
  try {
63436
- return readFileSync12(p, "utf8").trim() === TOKEN_PLACEHOLDER2;
64040
+ return readFileSync13(p, "utf8").trim() === TOKEN_PLACEHOLDER2;
63437
64041
  } catch {
63438
64042
  return false;
63439
64043
  }
@@ -63522,7 +64126,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63522
64126
  }, defaultIdentityProbes = (env) => ({
63523
64127
  origin: (cwd) => {
63524
64128
  try {
63525
- const out = spawnSync10("git", ["remote", "get-url", "origin"], {
64129
+ const out = spawnSync11("git", ["remote", "get-url", "origin"], {
63526
64130
  cwd,
63527
64131
  encoding: "utf8",
63528
64132
  env
@@ -63534,7 +64138,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63534
64138
  },
63535
64139
  ghLogin: (e) => {
63536
64140
  try {
63537
- const out = spawnSync10("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64141
+ const out = spawnSync11("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
63538
64142
  const login = (out.stdout ?? "").trim();
63539
64143
  return out.status === 0 && login ? login : null;
63540
64144
  } catch {
@@ -63543,7 +64147,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63543
64147
  },
63544
64148
  glabLogin: (e) => {
63545
64149
  try {
63546
- const out = spawnSync10("glab", ["api", "user", "--jq", ".username"], {
64150
+ const out = spawnSync11("glab", ["api", "user", "--jq", ".username"], {
63547
64151
  encoding: "utf8",
63548
64152
  env: e
63549
64153
  });
@@ -63555,7 +64159,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63555
64159
  },
63556
64160
  sshLogin: (host) => {
63557
64161
  try {
63558
- const out = spawnSync10("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64162
+ const out = spawnSync11("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
63559
64163
  encoding: "utf8",
63560
64164
  env
63561
64165
  });
@@ -63749,6 +64353,7 @@ var init_doctor = __esm(() => {
63749
64353
  init_store_lock();
63750
64354
  init_runtime_identity();
63751
64355
  init_boundary();
64356
+ init_release_tracks();
63752
64357
  init_config();
63753
64358
  init_package_root();
63754
64359
  init_registration();
@@ -63797,7 +64402,7 @@ import {
63797
64402
  lstatSync as lstatSync5,
63798
64403
  mkdirSync as mkdirSync5,
63799
64404
  mkdtempSync as mkdtempSync2,
63800
- readFileSync as readFileSync13,
64405
+ readFileSync as readFileSync14,
63801
64406
  readdirSync as readdirSync6,
63802
64407
  realpathSync as realpathSync7,
63803
64408
  renameSync as renameSync3,
@@ -63966,7 +64571,7 @@ function buildSetupPreview(values, opts = {}) {
63966
64571
  if (values.applyProject) {
63967
64572
  const root = path30.resolve(opts.cwd ?? process.cwd());
63968
64573
  const gitignorePath = path30.join(root, ".gitignore");
63969
- const existing = existsSync14(gitignorePath) ? readFileSync13(gitignorePath, "utf8") : "";
64574
+ const existing = existsSync14(gitignorePath) ? readFileSync14(gitignorePath, "utf8") : "";
63970
64575
  const existingLines = new Set(existing.split(`
63971
64576
  `).map((l) => l.trim()).filter(Boolean));
63972
64577
  const entries = GITIGNORE_ENTRIES.filter((e) => e.trim() !== "" && !existingLines.has(e.trim()));
@@ -63997,7 +64602,7 @@ function deepMerge(base, patch) {
63997
64602
  const out = { ...base };
63998
64603
  for (const [key, value] of Object.entries(patch)) {
63999
64604
  const prev = out[key];
64000
- out[key] = isRecord4(prev) && isRecord4(value) ? deepMerge(prev, value) : value;
64605
+ out[key] = isRecord5(prev) && isRecord5(value) ? deepMerge(prev, value) : value;
64001
64606
  }
64002
64607
  return out;
64003
64608
  }
@@ -64324,8 +64929,8 @@ function applyCursorMcp(root, res) {
64324
64929
  }
64325
64930
  const mcpBase = mcpExisting.kind === "record" ? mcpExisting.value : {};
64326
64931
  const server = cursorMcpServerEntry(res.cursorPluginDir);
64327
- const servers = isRecord4(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
64328
- const current = isRecord4(servers.workit) ? servers.workit : {};
64932
+ const servers = isRecord5(mcpBase.mcpServers) ? mcpBase.mcpServers : {};
64933
+ const current = isRecord5(servers.workit) ? servers.workit : {};
64329
64934
  const previousArgs = Array.isArray(current.args) ? current.args.map(String) : [];
64330
64935
  const previousPackage = previousArgs.find((arg) => arg.startsWith("--package=@brainervirus/workit-cursor@") && arg !== `--package=${CURSOR_RUNTIME_PACKAGE}`);
64331
64936
  if (previousPackage) {
@@ -64586,7 +65191,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64586
65191
  }, configuredTokenPath = (config, ...keys) => {
64587
65192
  let cursor = config;
64588
65193
  for (const key of keys) {
64589
- if (!isRecord4(cursor))
65194
+ if (!isRecord5(cursor))
64590
65195
  return null;
64591
65196
  cursor = cursor[key];
64592
65197
  }
@@ -64599,8 +65204,8 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64599
65204
  return configured ?? fallback;
64600
65205
  }, readConfigRecord = (file) => {
64601
65206
  try {
64602
- const value = JSON.parse(readFileSync13(file, "utf8"));
64603
- return isRecord4(value) ? value : null;
65207
+ const value = JSON.parse(readFileSync14(file, "utf8"));
65208
+ return isRecord5(value) ? value : null;
64604
65209
  } catch {
64605
65210
  return null;
64606
65211
  }
@@ -64617,10 +65222,10 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64617
65222
  cursorMcp: options.cursorMcp ?? path30.join(home, ".cursor", "mcp.json"),
64618
65223
  cursorPluginDir: options.cursorPluginDir ?? path30.join(home, ".cursor", "plugins", "local", "workit")
64619
65224
  };
64620
- }, isRecord4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readExisting = (p) => {
65225
+ }, isRecord5 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readExisting = (p) => {
64621
65226
  let raw;
64622
65227
  try {
64623
- raw = readFileSync13(p, "utf8");
65228
+ raw = readFileSync14(p, "utf8");
64624
65229
  } catch {
64625
65230
  if (existsSync14(p))
64626
65231
  return { kind: "malformed", error: `${p} is not readable` };
@@ -64632,12 +65237,12 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64632
65237
  } catch {
64633
65238
  return { kind: "malformed", error: `${p} is not valid JSON` };
64634
65239
  }
64635
- if (isRecord4(value))
65240
+ if (isRecord5(value))
64636
65241
  return { kind: "record", value, raw };
64637
65242
  return { kind: "malformed", error: `${p} is not a JSON object` };
64638
65243
  }, readFileSafe = (p) => {
64639
65244
  try {
64640
- return readFileSync13(p, "utf8");
65245
+ return readFileSync14(p, "utf8");
64641
65246
  } catch {
64642
65247
  return null;
64643
65248
  }
@@ -64652,7 +65257,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64652
65257
  }
64653
65258
  }, isAdapter = (root, platform) => {
64654
65259
  try {
64655
- const pkg = JSON.parse(readFileSync13(path30.join(root, "package.json"), "utf8"));
65260
+ const pkg = JSON.parse(readFileSync14(path30.join(root, "package.json"), "utf8"));
64656
65261
  return pkg.name === `@brainervirus/workit-${platform}`;
64657
65262
  } catch {
64658
65263
  return false;
@@ -64681,7 +65286,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64681
65286
  if (entry.isDirectory()) {
64682
65287
  if (!statSync5(installed).isDirectory() || !samePluginContent(source, installed, path30.join(relative, entry.name)))
64683
65288
  return false;
64684
- } else if (!readFileSync13(source).equals(readFileSync13(installed))) {
65289
+ } else if (!readFileSync14(source).equals(readFileSync14(installed))) {
64685
65290
  return false;
64686
65291
  }
64687
65292
  }
@@ -64709,7 +65314,7 @@ var TOKEN_PLACEHOLDER3 = "YOUR_TOKEN_HERE", YT_OVERRIDES, VCS_OVERRIDES, SETUP_O
64709
65314
  continue;
64710
65315
  if (existsSync14(path30.join(sourceRules, entry.name)))
64711
65316
  continue;
64712
- preserved.set(entry.name, readFileSync13(path30.join(installedRules, entry.name)));
65317
+ preserved.set(entry.name, readFileSync14(path30.join(installedRules, entry.name)));
64713
65318
  }
64714
65319
  } catch {}
64715
65320
  return preserved;
@@ -64763,10 +65368,10 @@ var init_setup = __esm(() => {
64763
65368
  });
64764
65369
 
64765
65370
  // packages/workit-core/src/core/branch-policy.ts
64766
- import { spawnSync as spawnSync11 } from "node:child_process";
65371
+ import { spawnSync as spawnSync12 } from "node:child_process";
64767
65372
  var PREFIXES, detectBranchPolicy = (workspaceRoot) => {
64768
65373
  const branchExists = (name) => {
64769
- const r = spawnSync11("git", ["branch", "--list", name], {
65374
+ const r = spawnSync12("git", ["branch", "--list", name], {
64770
65375
  cwd: workspaceRoot,
64771
65376
  encoding: "utf8"
64772
65377
  });
@@ -64826,7 +65431,7 @@ var init_branch_policy = __esm(() => {
64826
65431
  });
64827
65432
 
64828
65433
  // 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";
65434
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
64830
65435
  import path31 from "node:path";
64831
65436
  import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
64832
65437
  function applyWorkspaceBranchPolicy(opts) {
@@ -64924,7 +65529,7 @@ function applyWorkspaceBranchPolicy(opts) {
64924
65529
  }
64925
65530
  var readFileSafe2 = (p) => {
64926
65531
  try {
64927
- return readFileSync14(p, "utf8");
65532
+ return readFileSync15(p, "utf8");
64928
65533
  } catch {
64929
65534
  return null;
64930
65535
  }
@@ -65103,17 +65708,18 @@ function setProfileEditorValue(profile, field, value, defaults = {}) {
65103
65708
  };
65104
65709
  }
65105
65710
  function trackEditorValue(track, field) {
65711
+ const source = track.versionSource ?? { kind: "git-tag" };
65106
65712
  if (field.startsWith("naming."))
65107
- return track.naming[field.slice(7)];
65713
+ return track.naming?.[field.slice(7)] ?? "";
65108
65714
  if (field === "mergeBackBranches" || field === "requiredChecks")
65109
- return track[field].join(", ");
65715
+ return (track[field] ?? []).join(", ");
65110
65716
  if (field.startsWith("versionSource.")) {
65111
65717
  const key = field.slice(14);
65112
65718
  if (key === "kind")
65113
- return track.versionSource.kind;
65719
+ return source.kind;
65114
65720
  if (key === "path")
65115
- return track.versionSource.kind === "package-json" ? track.versionSource.path : "";
65116
- return track.versionSource.kind === "package-json" ? track.versionSource.field : "";
65721
+ return source.kind === "package-json" ? source.path : "";
65722
+ return source.kind === "package-json" ? source.field : "";
65117
65723
  }
65118
65724
  return String(track[field] ?? "");
65119
65725
  }
@@ -65123,12 +65729,13 @@ function setTrackEditorValue(track, field, value) {
65123
65729
  if (field === "mergeBackBranches" || field === "requiredChecks")
65124
65730
  return { ...track, [field]: splitList(value) };
65125
65731
  if (field.startsWith("versionSource.")) {
65732
+ const current = track.versionSource ?? { kind: "git-tag" };
65126
65733
  const key = field.slice(14);
65127
- const kind = key === "kind" ? value : track.versionSource.kind;
65734
+ const kind = key === "kind" ? value : current.kind;
65128
65735
  const versionSource = kind === "package-json" ? {
65129
65736
  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"
65737
+ path: key === "path" ? value : current.kind === kind ? current.path : "package.json",
65738
+ field: key === "field" ? value : current.kind === kind ? current.field : "version"
65132
65739
  } : kind === "manual" ? { kind } : { kind: "git-tag" };
65133
65740
  return { ...track, versionSource };
65134
65741
  }
@@ -65762,13 +66369,13 @@ var init_wizard_state = __esm(() => {
65762
66369
  });
65763
66370
 
65764
66371
  // 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";
66372
+ import { existsSync as existsSync16, readFileSync as readFileSync16, realpathSync as realpathSync8, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "node:fs";
65766
66373
  import os9 from "node:os";
65767
66374
  import path33 from "node:path";
65768
66375
  function cleanCursorSettings(settings, pluginDir) {
65769
66376
  const next = { ...settings };
65770
66377
  let changed = false;
65771
- if (isRecord5(next.enabled_plugins)) {
66378
+ if (isRecord6(next.enabled_plugins)) {
65772
66379
  const enabled = { ...next.enabled_plugins };
65773
66380
  for (const identity of ["workit", ...CURSOR_LEGACY_IDENTITIES]) {
65774
66381
  if (identity in enabled) {
@@ -65809,7 +66416,7 @@ function cleanOpenCodeConfig(config) {
65809
66416
  function cleanCursorMcp(mcp) {
65810
66417
  const next = { ...mcp };
65811
66418
  let changed = false;
65812
- if (isRecord5(next.mcpServers)) {
66419
+ if (isRecord6(next.mcpServers)) {
65813
66420
  const servers = { ...next.mcpServers };
65814
66421
  for (const name of ["workit", "workflow-toolkit"]) {
65815
66422
  if (name in servers) {
@@ -65834,7 +66441,7 @@ function cleanPiConfig(config) {
65834
66441
  changed = true;
65835
66442
  }
65836
66443
  }
65837
- const pi = isRecord5(next.pi) ? { ...next.pi } : null;
66444
+ const pi = isRecord6(next.pi) ? { ...next.pi } : null;
65838
66445
  if (pi && Array.isArray(pi.extensions)) {
65839
66446
  const kept = pi.extensions.filter((entry) => !String(entry).includes("workit"));
65840
66447
  if (kept.length !== pi.extensions.length) {
@@ -65982,10 +66589,10 @@ var resolveUninstallPaths = (options = {}) => {
65982
66589
  codexPluginDir: path33.join(home, ".codex", "plugins", "workit"),
65983
66590
  piConfig: path33.join(home, ".pi", "config.json")
65984
66591
  };
65985
- }, isRecord5 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readJsonRecord = (file) => {
66592
+ }, isRecord6 = (v) => v !== null && typeof v === "object" && !Array.isArray(v), readJsonRecord = (file) => {
65986
66593
  let raw;
65987
66594
  try {
65988
- raw = readFileSync15(file, "utf8");
66595
+ raw = readFileSync16(file, "utf8");
65989
66596
  } catch {
65990
66597
  if (existsSync16(file))
65991
66598
  return { kind: "malformed", error: `${file} is not readable` };
@@ -65993,7 +66600,7 @@ var resolveUninstallPaths = (options = {}) => {
65993
66600
  }
65994
66601
  try {
65995
66602
  const value = JSON.parse(raw);
65996
- return isRecord5(value) ? { kind: "record", value } : { kind: "malformed", error: `${file} is not a JSON object` };
66603
+ return isRecord6(value) ? { kind: "record", value } : { kind: "malformed", error: `${file} is not a JSON object` };
65997
66604
  } catch {
65998
66605
  return { kind: "malformed", error: `${file} is not valid JSON` };
65999
66606
  }
@@ -66035,7 +66642,7 @@ var resolveUninstallPaths = (options = {}) => {
66035
66642
  `;
66036
66643
  let current;
66037
66644
  try {
66038
- current = readFileSync15(target, "utf8");
66645
+ current = readFileSync16(target, "utf8");
66039
66646
  } catch (error) {
66040
66647
  return {
66041
66648
  status: "failed",
@@ -68305,7 +68912,7 @@ var init_steps = __esm(async () => {
68305
68912
  import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync7, unlinkSync as unlinkSync3 } from "node:fs";
68306
68913
  import os11 from "node:os";
68307
68914
  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 = () => {
68915
+ 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
68916
  const override = process.env.WORKFLOW_TOOLKIT_STATE;
68310
68917
  if (override)
68311
68918
  return override;
@@ -68507,7 +69114,7 @@ var init_logger = __esm(() => {
68507
69114
  "query"
68508
69115
  ]);
68509
69116
  STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
68510
- homePattern = new RegExp(`${escapeRegExp2(os11.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
69117
+ homePattern = new RegExp(`${escapeRegExp3(os11.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
68511
69118
  DEFAULT_OPTIONS = {
68512
69119
  maxFieldLength: DEFAULT_MAX_FIELD_LENGTH,
68513
69120
  maxStackLines: DEFAULT_MAX_STACK_LINES
@@ -68772,12 +69379,12 @@ var init_init = __esm(() => {
68772
69379
  });
68773
69380
 
68774
69381
  // packages/workit-cli/src/upgrade.ts
68775
- import { spawnSync as spawnSync12 } from "node:child_process";
69382
+ import { spawnSync as spawnSync13 } from "node:child_process";
68776
69383
  import {
68777
69384
  existsSync as existsSync17,
68778
69385
  mkdtempSync as mkdtempSync3,
68779
69386
  rmSync as rmSync6,
68780
- readFileSync as readFileSync16,
69387
+ readFileSync as readFileSync17,
68781
69388
  mkdirSync as mkdirSync8,
68782
69389
  copyFileSync as copyFileSync5,
68783
69390
  chmodSync,
@@ -68786,7 +69393,7 @@ import {
68786
69393
  } from "node:fs";
68787
69394
  import path35 from "node:path";
68788
69395
  import os12 from "node:os";
68789
- import { createHash as createHash12 } from "node:crypto";
69396
+ import { createHash as createHash13 } from "node:crypto";
68790
69397
  function previewUpgrade(hosts = HOSTS2, deps = {}) {
68791
69398
  const paths = upgradePaths(deps);
68792
69399
  const { home, configDir } = paths;
@@ -68914,7 +69521,7 @@ function applyUpgrade(plan, deps = {}) {
68914
69521
  mkdirSync8(backup, { recursive: true, mode: 448 });
68915
69522
  for (const file of Object.keys(plan.configDigests))
68916
69523
  if (existsSync17(file)) {
68917
- const target = path35.join(backup, createHash12("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
69524
+ const target = path35.join(backup, createHash13("sha256").update(file).digest("hex").slice(0, 12) + "-" + path35.basename(file));
68918
69525
  copyFileSync5(file, target);
68919
69526
  chmodSync(target, 384);
68920
69527
  }
@@ -69128,7 +69735,7 @@ async function runLaunchCommand(argv, deps = {}) {
69128
69735
  }
69129
69736
  if (deps.run)
69130
69737
  return deps.run(host, hostArgs).status ?? 1;
69131
- return spawnSync12(launch.command, launch.args, {
69738
+ return spawnSync13(launch.command, launch.args, {
69132
69739
  stdio: "inherit",
69133
69740
  env
69134
69741
  }).status ?? 1;
@@ -69162,14 +69769,14 @@ var HOSTS2, upgradePaths = (deps) => {
69162
69769
  return null;
69163
69770
  let data;
69164
69771
  try {
69165
- data = JSON.parse(readFileSync16(file, "utf8"));
69772
+ data = JSON.parse(readFileSync17(file, "utf8"));
69166
69773
  } catch {
69167
69774
  throw new Error(`${file} is not valid JSON; use the native host to inspect JSONC settings`);
69168
69775
  }
69169
69776
  if (!data || typeof data !== "object" || Array.isArray(data))
69170
69777
  throw new Error(`${file} is not a configuration object`);
69171
69778
  return data;
69172
- }, digest2 = (file) => existsSync17(file) ? createHash12("sha256").update(readFileSync16(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
69779
+ }, digest2 = (file) => existsSync17(file) ? createHash13("sha256").update(readFileSync17(file)).digest("hex") : "missing", runner = (deps) => deps.run ?? ((command, args) => {
69173
69780
  const { home, env } = upgradePaths(deps);
69174
69781
  let step;
69175
69782
  try {
@@ -69309,12 +69916,49 @@ var init_launch = __esm(() => {
69309
69916
  init_upgrade();
69310
69917
  });
69311
69918
 
69919
+ // packages/workit-cli/src/prompt.ts
69920
+ import { createInterface } from "node:readline/promises";
69921
+ async function askLine(question, output = process.stdout) {
69922
+ const prompt = createInterface({ input: process.stdin, output });
69923
+ try {
69924
+ return await prompt.question(question);
69925
+ } finally {
69926
+ prompt.close();
69927
+ }
69928
+ }
69929
+ async function askOrCancel(io, ask) {
69930
+ try {
69931
+ return await ask();
69932
+ } catch (error) {
69933
+ if (!isPromptAbort(error))
69934
+ throw error;
69935
+ io.stderr(`
69936
+ Cancelled.
69937
+ `);
69938
+ return null;
69939
+ }
69940
+ }
69941
+ function cancelled(io) {
69942
+ if (io.json) {
69943
+ const envelope = {
69944
+ ok: false,
69945
+ code: "failed",
69946
+ data: { reason: "cancelled" },
69947
+ error: "cancelled"
69948
+ };
69949
+ io.stdout(`${JSON.stringify(envelope)}
69950
+ `);
69951
+ }
69952
+ return CANCELLED_EXIT;
69953
+ }
69954
+ var CANCELLED_EXIT = 130, isPromptAbort = (error) => typeof error === "object" && error !== null && (error.name === "AbortError" || error.code === "ABORT_ERR");
69955
+ var init_prompt = () => {};
69956
+
69312
69957
  // packages/workit-cli/src/verbs/doctor.ts
69313
69958
  var exports_doctor = {};
69314
69959
  __export(exports_doctor, {
69315
69960
  run: () => run5
69316
69961
  });
69317
- import { createInterface } from "node:readline/promises";
69318
69962
  async function confirmForcedLockClear(root, args, io) {
69319
69963
  const lock = inspectMetadataLock(root);
69320
69964
  if (!lock.present)
@@ -69331,23 +69975,20 @@ async function confirmForcedLockClear(root, args, io) {
69331
69975
  `);
69332
69976
  return false;
69333
69977
  }
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
- }
69978
+ const answer = await askOrCancel(io, () => askLine("Remove this lock even if its holder may be alive? [y/N] ", io.json ? process.stderr : process.stdout));
69979
+ if (answer === null)
69980
+ return "cancelled";
69981
+ return /^y(es)?$/i.test(answer.trim());
69344
69982
  }
69345
69983
  async function run5(argv, io) {
69346
69984
  const root = workspaceRootFor();
69347
69985
  const force = argv.includes("--force");
69348
69986
  let fixLock = null;
69349
69987
  if (argv.includes("--fix-lock")) {
69350
- if (force && !await confirmForcedLockClear(root, argv, io)) {
69988
+ const confirmed = force ? await confirmForcedLockClear(root, argv, io) : true;
69989
+ if (confirmed === "cancelled")
69990
+ return cancelled(io);
69991
+ if (!confirmed) {
69351
69992
  if (!io.json)
69352
69993
  return 3;
69353
69994
  return emit(io, fail("blocked", "fix-lock --force: refusing without --yes outside an interactive terminal", {
@@ -69386,6 +70027,7 @@ var init_doctor2 = __esm(() => {
69386
70027
  init_doctor();
69387
70028
  init_store_lock();
69388
70029
  init_output();
70030
+ init_prompt();
69389
70031
  init_task();
69390
70032
  });
69391
70033
 
@@ -69493,8 +70135,8 @@ var init_redact = __esm(() => {
69493
70135
  });
69494
70136
 
69495
70137
  // packages/workit-core/src/checks.ts
69496
- import { spawn, spawnSync as spawnSync13 } from "node:child_process";
69497
- import { createHash as createHash13 } from "node:crypto";
70138
+ import { spawn, spawnSync as spawnSync14 } from "node:child_process";
70139
+ import { createHash as createHash14 } from "node:crypto";
69498
70140
  import fs17 from "node:fs";
69499
70141
  import os13 from "node:os";
69500
70142
  import path36 from "node:path";
@@ -69672,7 +70314,7 @@ function runCheckCommand(argv, options) {
69672
70314
  if (!child?.pid)
69673
70315
  return;
69674
70316
  if (windows)
69675
- spawnSync13("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
70317
+ spawnSync14("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
69676
70318
  stdio: "ignore",
69677
70319
  windowsHide: true
69678
70320
  });
@@ -69828,7 +70470,7 @@ function pruneCheckLogs(storeRoot, options = {}) {
69828
70470
  }
69829
70471
  return report;
69830
70472
  }
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;
70473
+ 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
70474
  var init_checks3 = __esm(() => {
69833
70475
  init_redact();
69834
70476
  MAX_LOG_BYTES = 2 * 1024 * 1024;
@@ -69917,7 +70559,6 @@ __export(exports_grant, {
69917
70559
  run: () => run8,
69918
70560
  runGrant: () => runGrant
69919
70561
  });
69920
- import { createInterface as createInterface2 } from "node:readline/promises";
69921
70562
  function show(argv, io) {
69922
70563
  const all = argv.includes("--all");
69923
70564
  const named = argv.filter((arg) => !arg.startsWith("--"));
@@ -69932,7 +70573,7 @@ function show(argv, io) {
69932
70573
  return emit(io, fail("not_found", `no workspace named "${named[0]}" in ${listed.path}`));
69933
70574
  return emit(io, ok({ path: listed.path, defaults: DEFAULT_GRANTS, workspaces: rows }), () => rows.flatMap((entry) => [
69934
70575
  `${entry.name} (${entry.glob})`,
69935
- ...describe2(entry.grants, entry.configured, entry.defaultEndpoint, entry.verification)
70576
+ ...describe2(entry.grants, entry.configured, entry, entry.verification)
69936
70577
  ]));
69937
70578
  }
69938
70579
  let autonomy;
@@ -69941,10 +70582,13 @@ function show(argv, io) {
69941
70582
  } catch (error) {
69942
70583
  return emit(io, fail("invalid_input", error instanceof Error ? error.message : String(error)));
69943
70584
  }
69944
- return emit(io, ok(autonomy), (data) => [
70585
+ const tracks = releaseTracksReport(io.cwd);
70586
+ const shown = tracks.tracks.length || tracks.error ? { ...autonomy, releaseTracks: tracks } : autonomy;
70587
+ return emit(io, ok(shown), (data) => [
69945
70588
  ...data.note ? [`note: ${data.note}`] : [],
69946
70589
  data.workspace ? `workspace ${data.workspace} (${data.source === "default" ? "D4 defaults" : `from ${data.source}`})` : "no workspace matches this checkout: D4 defaults apply",
69947
- ...describe2(data.grants, data.configured, data.defaultEndpoint, data.verification),
70590
+ ...describe2(data.grants, data.configured, data, data.verification),
70591
+ ...describeReleaseTracks(tracks).map((line) => ` ${line}`),
69948
70592
  "raising a grant needs the user: workit grant set <workspace> <kind>=<value>"
69949
70593
  ]);
69950
70594
  }
@@ -69966,10 +70610,10 @@ async function change(verb, argv, io, deps) {
69966
70610
  if (kind === "defaultEndpoint") {
69967
70611
  if (verb === "unset")
69968
70612
  endpoint = null;
69969
- else if (raw === "commit" || raw === "pr")
70613
+ else if (isDefaultEndpoint(raw))
69970
70614
  endpoint = raw;
69971
70615
  else
69972
- return usage2(io, `${spec}: defaultEndpoint must be commit or pr`);
70616
+ return usage2(io, `${spec}: defaultEndpoint must be ${DEFAULT_ENDPOINTS.join(", ")}`);
69973
70617
  continue;
69974
70618
  }
69975
70619
  if (kind === "verification") {
@@ -69993,8 +70637,8 @@ async function change(verb, argv, io, deps) {
69993
70637
  changes.push({ kind, value });
69994
70638
  }
69995
70639
  const raised = changes.filter(({ kind, value }) => raises(kind, entry.grants[kind], value ?? DEFAULT_GRANTS[kind])).map(({ kind, value }) => ({ kind, value: String(value ?? DEFAULT_GRANTS[kind]) }));
69996
- if (endpoint === "pr" && entry.defaultEndpoint !== "pr")
69997
- raised.push({ kind: "defaultEndpoint", value: "pr" });
70640
+ if (endpoint && raisesEndpoint(entry.defaultEndpoint, endpoint))
70641
+ raised.push({ kind: "defaultEndpoint", value: endpoint });
69998
70642
  if (verification !== undefined && verification !== "independent" && entry.verification === "independent")
69999
70643
  raised.push({ kind: "verification", value: "self" });
70000
70644
  if (raised.length > 0) {
@@ -70002,7 +70646,9 @@ async function change(verb, argv, io, deps) {
70002
70646
  const agent = agentMarker(io.env);
70003
70647
  if (!deps.interactive() || agent)
70004
70648
  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: `);
70649
+ 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: `));
70650
+ if (answer === null)
70651
+ return cancelled(io);
70006
70652
  if (answer.trim() !== workspace)
70007
70653
  return emit(io, fail("blocked", "grant change not confirmed; nothing was written"));
70008
70654
  }
@@ -70011,7 +70657,7 @@ async function change(verb, argv, io, deps) {
70011
70657
  return emit(io, fail(written.code, written.error));
70012
70658
  return emit(io, ok(written), (data) => [
70013
70659
  `${data.workspace}: grants updated in ${data.path}${data.backup ? ` (previous copy: ${data.backup})` : ""}`,
70014
- ...describe2(data.grants, data.configured, data.defaultEndpoint, data.verification)
70660
+ ...describe2(data.grants, data.configured, data, data.verification)
70015
70661
  ]);
70016
70662
  }
70017
70663
  async function runGrant(argv, io, deps = defaultDeps) {
@@ -70029,15 +70675,18 @@ async function runGrant(argv, io, deps = defaultDeps) {
70029
70675
  }
70030
70676
  return usage2(io, `unknown subcommand "${sub}"`);
70031
70677
  }
70032
- var USAGE2 = "workit grant show [<workspace>] [--all] | grant set <workspace> <kind>=<true|false|verified>… [defaultEndpoint=commit|pr] [verification=self|independent] | grant unset <workspace> <kind>… (kinds: push, pr, merge, release, rerun, defaultEndpoint, verification)", AGENT_ENV, agentMarker = (env) => AGENT_ENV.find((name) => env[name]) ?? Object.keys(env).find((name) => name.startsWith("CODEX_") && env[name]), defaultDeps, parseValue = (raw) => raw === "true" || raw === "yes" || raw === "on" ? true : raw === "false" || raw === "no" || raw === "off" ? false : raw === "verified" ? "verified" : null, describe2 = (grants, configured, endpoint, verification) => [
70678
+ var USAGE2 = "workit grant show [<workspace>] [--all] | grant set <workspace> <kind>=<true|false|verified>… [defaultEndpoint=commit|pr|green|merged] [verification=self|independent] | grant unset <workspace> <kind>… (kinds: push, pr, merge, release, rerun, defaultEndpoint, verification)", AGENT_ENV, agentMarker = (env) => AGENT_ENV.find((name) => env[name]) ?? Object.keys(env).find((name) => name.startsWith("CODEX_") && env[name]), defaultDeps, parseValue = (raw) => raw === "true" || raw === "yes" || raw === "on" ? true : raw === "false" || raw === "no" || raw === "off" ? false : raw === "verified" ? "verified" : null, STOPS_AT, endpointLabel = (view) => view.effectiveEndpoint === view.defaultEndpoint ? view.defaultEndpoint : `${view.defaultEndpoint} (effective: ${view.effectiveEndpoint}${view.endpointReason ? `, ${view.endpointReason}` : ""})`, describe2 = (grants, configured, endpoint, verification) => [
70033
70679
  ...GRANT_KINDS.map((kind) => ` ${kind.padEnd(8)}${String(grants[kind]).padEnd(10)}${configured.includes(kind) ? "configured" : "default"}${kind === "release" ? " (no consumer yet: reserved, not enforced)" : ""}`),
70034
- ` default endpoint: ${endpoint} (an unnamed request stops at ${endpoint === "pr" ? "an opened PR" : "a local commit"})`,
70680
+ ` default endpoint: ${endpointLabel(endpoint)} (an unnamed request stops at ${STOPS_AT[endpoint.effectiveEndpoint]})`,
70681
+ ...endpoint.endpointIssue ? [` note: ${endpoint.endpointIssue}`] : [],
70035
70682
  ` verification: ${verification} (${verification === "independent" ? "a normal-risk behavior change needs a non-author verdict" : "a normal-risk behavior change needs a passing check and the author's --self verdict, shown as self-reviewed"})`
70036
70683
  ], usage2 = (io, message) => emit(io, fail("invalid_input", `${message}
70037
70684
  usage: ${USAGE2}`)), run8 = (argv, io) => runGrant(argv, io);
70038
70685
  var init_grant = __esm(() => {
70039
70686
  init_autonomy();
70687
+ init_release_tracks();
70040
70688
  init_output();
70689
+ init_prompt();
70041
70690
  AGENT_ENV = [
70042
70691
  "CLAUDECODE",
70043
70692
  "OPENCODE",
@@ -70048,14 +70697,13 @@ var init_grant = __esm(() => {
70048
70697
  ];
70049
70698
  defaultDeps = {
70050
70699
  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
- }
70700
+ ask: (question) => askLine(question)
70701
+ };
70702
+ STOPS_AT = {
70703
+ commit: "a local commit",
70704
+ pr: "an opened PR",
70705
+ green: "a merge-ready PR: CI green, threads resolved, verified; never merged",
70706
+ merged: "a merged PR (workit pr merge)"
70059
70707
  };
70060
70708
  });
70061
70709
 
@@ -70392,9 +71040,9 @@ var init_check = __esm(() => {
70392
71040
  var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
70393
71041
 
70394
71042
  // packages/workit-core/src/git/ops.ts
70395
- import { spawnSync as spawnSync14 } from "node:child_process";
71043
+ import { spawnSync as spawnSync15 } from "node:child_process";
70396
71044
  function dirtEntries(cwd) {
70397
- const run = git4(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
71045
+ const run = git5(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
70398
71046
  if (!run.ok)
70399
71047
  return [];
70400
71048
  const parts = run.stdout.split("\x00");
@@ -70410,9 +71058,19 @@ function dirtEntries(cwd) {
70410
71058
  return out;
70411
71059
  }
70412
71060
  function gitBranch(cwd, input) {
70413
- const name = input.name.trim();
70414
71061
  if (!headSha(cwd) && currentBranch(cwd) === null)
70415
71062
  return failure3("invalid_input", "not inside a git repository with a commit");
71063
+ const explicitBase = input.base?.trim() || null;
71064
+ const resolved = vcsConfig("resolve", cwd, {
71065
+ track: input.track ?? null,
71066
+ ...explicitBase ? { branch: explicitBase } : {}
71067
+ });
71068
+ const releaseTrack = resolved.ok === false ? null : resolved.releaseTrack ?? null;
71069
+ if (releaseTrack?.blocking && (!explicitBase || input.name === undefined))
71070
+ return failure3("blocked", String(releaseTrack.blocking), "workit git branch … --track <name> # or --base <branch>");
71071
+ const name = (input.name ?? (input.kind && input.slug ? trackBranchName(releaseTrack?.track ?? null, input.kind, input.slug) ?? `${input.kind}/${input.slug}` : "")).trim();
71072
+ if (!name)
71073
+ return failure3("invalid_input", "missing branch name");
70416
71074
  if (!validRefName(cwd, name))
70417
71075
  return failure3("invalid_input", `invalid branch name ${JSON.stringify(name)}`);
70418
71076
  const policy = validateBranchNameFor(cwd, name);
@@ -70420,23 +71078,25 @@ function gitBranch(cwd, input) {
70420
71078
  return failure3("blocked", policy.error, policy.correction);
70421
71079
  if (resolveRef(cwd, `refs/heads/${name}`))
70422
71080
  return failure3("failed", `branch_exists: ${name} already exists`, `git switch ${name}`);
70423
- let base = input.base?.trim() || null;
71081
+ let base = explicitBase;
70424
71082
  let defaultTarget = null;
70425
- const resolved = vcsConfig("resolve", cwd);
70426
- if (resolved.ok === false && !base)
71083
+ let defaultBase = null;
71084
+ if (resolved.ok === false && (!base || input.track))
70427
71085
  return failure3("blocked", String(resolved.error), "fix ~/.config/workit/vcs.json or pass --base <branch>");
70428
- if (resolved.ok !== false)
71086
+ if (resolved.ok !== false) {
70429
71087
  defaultTarget = String(resolved.defaultTargetBranch ?? "") || null;
70430
- base ??= defaultTarget;
71088
+ defaultBase = String(resolved.baseBranch ?? "") || defaultTarget;
71089
+ }
71090
+ base ??= defaultBase;
70431
71091
  if (!base)
70432
71092
  return failure3("invalid_input", "no default target branch is configured; pass --base <branch>");
70433
71093
  if (base.startsWith("-"))
70434
71094
  return failure3("invalid_input", "--base must not start with -");
70435
- const notes = [];
71095
+ const notes = [...releaseTrack?.warnings ?? []];
70436
71096
  const remotes = remoteNames(cwd);
70437
71097
  const remote = remotes.includes("origin") ? "origin" : pushRemoteName(cwd);
70438
71098
  const localTip = resolveRef(cwd, `refs/heads/${base}`);
70439
- const stackParent = localTip !== null && base !== defaultTarget && !isProtectedTarget(cwd, base);
71099
+ const stackParent = localTip !== null && base !== defaultTarget && base !== defaultBase && !isProtectedTarget(cwd, base);
70440
71100
  let baseRef = null;
70441
71101
  if (stackParent)
70442
71102
  baseRef = `refs/heads/${base}`;
@@ -70454,18 +71114,19 @@ function gitBranch(cwd, input) {
70454
71114
  }
70455
71115
  } else if (localTip)
70456
71116
  baseRef = `refs/heads/${base}`;
70457
- if (!baseRef && git4(cwd, ["rev-parse", "--verify", "-q", `${base}^{commit}`]).ok)
71117
+ if (!baseRef && git5(cwd, ["rev-parse", "--verify", "-q", `${base}^{commit}`]).ok)
70458
71118
  baseRef = base;
70459
- const baseSha = baseRef ? git4(cwd, ["rev-parse", "--verify", "-q", `${baseRef}^{commit}`]).stdout.trim() : "";
71119
+ const baseSha = baseRef ? git5(cwd, ["rev-parse", "--verify", "-q", `${baseRef}^{commit}`]).stdout.trim() : "";
70460
71120
  if (!baseRef || !baseSha)
70461
71121
  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
71122
  const dirt = classifyBranchDirt(cwd);
70463
71123
  if (dirt === "stash-required" && !input.carry)
70464
71124
  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
71125
  const previous = currentBranch(cwd);
70466
- const switched = git4(cwd, ["switch", "--no-track", "-c", name, baseSha]);
71126
+ const switched = git5(cwd, ["switch", "--no-track", "-c", name, baseSha]);
70467
71127
  if (!switched.ok)
70468
71128
  return failure3("failed", `git switch -c ${name} failed: ${gitError(switched)}`);
71129
+ recordBranchBase(cwd, name, base);
70469
71130
  return success2({
70470
71131
  branch: name,
70471
71132
  previous,
@@ -70473,7 +71134,8 @@ function gitBranch(cwd, input) {
70473
71134
  baseRef,
70474
71135
  baseSha,
70475
71136
  carried: dirt !== "clean",
70476
- notes
71137
+ notes,
71138
+ releaseTrack
70477
71139
  });
70478
71140
  }
70479
71141
  function lintCommitMessage(cwd, message) {
@@ -70488,7 +71150,7 @@ function lintCommitMessage(cwd, message) {
70488
71150
  }
70489
71151
  if (preset !== "auto")
70490
71152
  return { ok: true };
70491
- const log = git4(cwd, ["log", "-n", "50", "--no-merges", "--format=%s"]);
71153
+ const log = git5(cwd, ["log", "-n", "50", "--no-merges", "--format=%s"]);
70492
71154
  const subjects = log.ok ? log.stdout.split(`
70493
71155
  `).filter(Boolean) : [];
70494
71156
  const detected = detectCommitFlavor(subjects).flavor;
@@ -70526,28 +71188,28 @@ function gitCommit(cwd, input) {
70526
71188
  if (paths.length) {
70527
71189
  if (paths.some((value) => value.startsWith("-") || !value.trim()))
70528
71190
  return failure3("invalid_input", "paths must not be empty or start with -");
70529
- const added = git4(cwd, ["add", "-A", "--", ...paths]);
71191
+ const added = git5(cwd, ["add", "-A", "--", ...paths]);
70530
71192
  if (!added.ok)
70531
71193
  return failure3("failed", `git add failed: ${gitError(added)}`);
70532
71194
  args.push("--", ...paths);
70533
71195
  } else if (input.all) {
70534
- const added = git4(cwd, ["add", "-A"]);
71196
+ const added = git5(cwd, ["add", "-A"]);
70535
71197
  if (!added.ok)
70536
71198
  return failure3("failed", `git add failed: ${gitError(added)}`);
70537
71199
  } else {
70538
- const staged = git4(cwd, ["diff", "--cached", "--name-only", "-z"]);
71200
+ const staged = git5(cwd, ["diff", "--cached", "--name-only", "-z"]);
70539
71201
  if (staged.ok && !staged.stdout.replaceAll("\x00", "")) {
70540
71202
  const dirt = dirtEntries(cwd);
70541
71203
  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
71204
  }
70543
71205
  }
70544
- const committed = git4(cwd, args, { timeoutMs: COMMIT_TIMEOUT_MS, env: input.env });
71206
+ const committed = git5(cwd, args, { timeoutMs: COMMIT_TIMEOUT_MS, env: input.env });
70545
71207
  if (!committed.ok)
70546
71208
  return failure3("failed", `git commit failed: ${gitError(committed, 6)}`);
70547
71209
  const sha = headSha(cwd);
70548
71210
  if (!sha)
70549
71211
  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);
71212
+ const files = nulList(git5(cwd, ["diff-tree", "--root", "--no-commit-id", "--name-only", "-r", "-z", sha]).stdout);
70551
71213
  const leftDirty = dirtEntries(cwd).length;
70552
71214
  const listed = [];
70553
71215
  let size = 0;
@@ -70619,9 +71281,9 @@ function recordedRemoteTip(cwd, plan) {
70619
71281
  return;
70620
71282
  }
70621
71283
  function includesRemoteTip(cwd, branch, tip) {
70622
- if (git4(cwd, ["merge-base", "--is-ancestor", tip, `refs/heads/${branch}`]).ok)
71284
+ if (git5(cwd, ["merge-base", "--is-ancestor", tip, `refs/heads/${branch}`]).ok)
70623
71285
  return true;
70624
- const reflog = git4(cwd, ["reflog", "show", "--format=%H", `refs/heads/${branch}`, "--"]);
71286
+ const reflog = git5(cwd, ["reflog", "show", "--format=%H", `refs/heads/${branch}`, "--"]);
70625
71287
  return reflog.ok && reflog.stdout.split(`
70626
71288
  `).some((line) => line.trim() === tip);
70627
71289
  }
@@ -70671,7 +71333,7 @@ ${run.stdout}`;
70671
71333
  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
71334
  let upstream = false;
70673
71335
  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;
71336
+ upstream = git5(cwd, ["config", `branch.${plan.branch}.remote`, plan.remote]).ok && git5(cwd, ["config", `branch.${plan.branch}.merge`, `refs/heads/${plan.branch}`]).ok;
70675
71337
  }
70676
71338
  const row = appendObserved(cwd, {
70677
71339
  type: pushed ? "push.verified" : "push.noop",
@@ -70721,8 +71383,8 @@ function deleteRemoteBranch(cwd, target, branch, expectedTip, options = {}) {
70721
71383
  ${run.stdout}`;
70722
71384
  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
71385
  }
70724
- var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git4 = (cwd, args, options = {}) => {
70725
- const result = spawnSync14("git", args, {
71386
+ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
71387
+ const result = spawnSync15("git", args, {
70726
71388
  cwd,
70727
71389
  encoding: "utf8",
70728
71390
  env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
@@ -70742,11 +71404,12 @@ var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git4 = (cwd, args, options = {}
70742
71404
  const lines = redactText(`${run.stderr}
70743
71405
  ${run.stdout ?? ""}`).split(/\r?\n/u).map((value) => value.trim()).filter((value) => value && !value.startsWith("hint:"));
70744
71406
  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;
71407
+ }, validRefName = (cwd, name) => !name.startsWith("-") && git5(cwd, ["check-ref-format", "--branch", name]).ok, nulList = (text) => text.split("\x00").filter(Boolean), SESSION_SAFE, SHA;
70746
71408
  var init_ops = __esm(() => {
70747
71409
  init_branch();
70748
71410
  init_commit_flavors();
70749
71411
  init_pr_create();
71412
+ init_release_tracks();
70750
71413
  init_vcs_config();
70751
71414
  init_redact();
70752
71415
  init_ledger();
@@ -70903,6 +71566,32 @@ function blockersOf(input) {
70903
71566
  }
70904
71567
  return out;
70905
71568
  }
71569
+ function babysitAction(next, reason, draft) {
71570
+ switch (next) {
71571
+ case "MERGED":
71572
+ return "merged";
71573
+ case "CLOSED":
71574
+ return null;
71575
+ case "RESOLVE_CONFLICTS":
71576
+ case "REBASE":
71577
+ return "update-branch";
71578
+ case "RESOLVE_THREADS":
71579
+ case "ADDRESS_REVIEW":
71580
+ return "address-threads";
71581
+ case "FIX_CI":
71582
+ return "fix-ci";
71583
+ case "WAITING_CI":
71584
+ return "wait";
71585
+ case "IN_MERGE_QUEUE":
71586
+ return "wait-forge";
71587
+ default:
71588
+ if (draft)
71589
+ return "mark-ready";
71590
+ if (next === "NOT_MERGEABLE" && reason === "mergeability_unknown")
71591
+ return "wait-forge";
71592
+ return "ready";
71593
+ }
71594
+ }
70906
71595
  function computeBehindBase(cwd, remotes, baseSha, headSha_, options = {}) {
70907
71596
  const empty = { behind: null, ahead: null, upToDate: null };
70908
71597
  if (!baseSha)
@@ -71027,7 +71716,8 @@ function buildStatusDoc(cwd, resolved, status, options) {
71027
71716
  blockers: status.state === "open" ? blockers.map((blocker) => blocker.reason) : [],
71028
71717
  identity: options.identity ?? null,
71029
71718
  truncated: status.truncated,
71030
- next: blockers[0]?.next ?? "READY"
71719
+ next: blockers[0]?.next ?? "READY",
71720
+ babysit: babysitAction(blockers[0]?.next ?? "READY", blockers[0]?.reason ?? null, status.draft || blockers.some((blocker) => blocker.next === "MARK_READY"))
71031
71721
  });
71032
71722
  }
71033
71723
  function prStatusReport(cwd, resolved, options = {}) {
@@ -71175,7 +71865,7 @@ var init_report = __esm(() => {
71175
71865
  });
71176
71866
 
71177
71867
  // packages/workit-core/src/forge/pr-ops.ts
71178
- import { spawnSync as spawnSync15 } from "node:child_process";
71868
+ import { spawnSync as spawnSync16 } from "node:child_process";
71179
71869
  function fillFromCommits(cwd, resolved, base) {
71180
71870
  const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
71181
71871
  if (!base.startsWith("-"))
@@ -71183,7 +71873,7 @@ function fillFromCommits(cwd, resolved, base) {
71183
71873
  const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
71184
71874
  if (!from)
71185
71875
  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`], {
71876
+ const log = spawnSync16("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
71187
71877
  cwd,
71188
71878
  encoding: "utf8",
71189
71879
  timeout: GIT_TIMEOUTS.local,
@@ -71382,6 +72072,7 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
71382
72072
  const gone = !deleted.ok && remoteRefTip(cwd, resolved.remote, `refs/heads/${branch}`);
71383
72073
  deletedBranch = deleted.ok || gone && gone.ok && gone.sha === null ? true : { error: deleted.error };
71384
72074
  }
72075
+ const mergeBack = mergeBackFor(cwd, doc.base);
71385
72076
  const row = appendObserved(cwd, {
71386
72077
  type: "pr.merged",
71387
72078
  actor: input.actor,
@@ -71407,11 +72098,25 @@ async function mergePullRequest(cwd, resolved, input, sleep) {
71407
72098
  verdict,
71408
72099
  grant: { source: grant.source },
71409
72100
  deletedBranch,
71410
- recorded: row.ok ? { id: String(row.value.id) } : { error: row.error }
72101
+ recorded: row.ok ? { id: String(row.value.id) } : { error: row.error },
72102
+ mergeBack: mergeBack.branches,
72103
+ mergeBackTrack: mergeBack.track
71411
72104
  }
71412
72105
  };
71413
72106
  }
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) => ({
72107
+ 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) => {
72108
+ try {
72109
+ const owners = workspaceReleaseTracks(cwd).tracks.filter((track) => track.productionBranch === base);
72110
+ if (owners.length !== 1)
72111
+ return { track: null, branches: [] };
72112
+ return {
72113
+ track: owners[0].name,
72114
+ branches: owners[0].mergeBackBranches.filter((branch) => branch !== base)
72115
+ };
72116
+ } catch {
72117
+ return { track: null, branches: [] };
72118
+ }
72119
+ }, refuse = (error, unblock, refusal) => ({
71415
72120
  ok: false,
71416
72121
  code: "blocked",
71417
72122
  error,
@@ -71422,6 +72127,7 @@ var init_pr_ops = __esm(() => {
71422
72127
  init_autonomy();
71423
72128
  init_branch();
71424
72129
  init_vcs_config();
72130
+ init_release_tracks();
71425
72131
  init_ops();
71426
72132
  init_rev();
71427
72133
  init_ledger();
@@ -71442,7 +72148,7 @@ var init_pr_ops = __esm(() => {
71442
72148
  });
71443
72149
 
71444
72150
  // packages/workit-core/src/forge/exec.ts
71445
- import { spawnSync as spawnSync16 } from "node:child_process";
72151
+ import { spawnSync as spawnSync17 } from "node:child_process";
71446
72152
  import fs19 from "node:fs";
71447
72153
  import path39 from "node:path";
71448
72154
  function findExecutable(bin, env) {
@@ -71477,7 +72183,7 @@ function systemRunner(kind, apiHost, env = process.env) {
71477
72183
  if (!exe)
71478
72184
  return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
71479
72185
  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], {
72186
+ const result = spawnSync17(exe, [...args], {
71481
72187
  encoding: "utf8",
71482
72188
  env: { ...childEnv, ...token },
71483
72189
  maxBuffer: MAX_BUFFER2,
@@ -72370,6 +73076,8 @@ function renderStatus(doc) {
72370
73076
  lines.push(`blockers: ${doc.blockers.join(", ")}`);
72371
73077
  if (doc.identity?.note)
72372
73078
  lines.push(`identity: ${doc.identity.note}`);
73079
+ if (doc.babysit)
73080
+ lines.push(`babysit: ${doc.babysit}`);
72373
73081
  lines.push(`next: ${doc.next}`);
72374
73082
  return lines;
72375
73083
  }
@@ -72390,7 +73098,7 @@ var exports_pr = {};
72390
73098
  __export(exports_pr, {
72391
73099
  run: () => run10
72392
73100
  });
72393
- import { readFileSync as readFileSync17 } from "node:fs";
73101
+ import { readFileSync as readFileSync18 } from "node:fs";
72394
73102
  import path40 from "node:path";
72395
73103
  async function status(argv, io) {
72396
73104
  const flags = parseFlags(argv, { pr: "value", branch: "value", "log-lines": "value" });
@@ -72443,6 +73151,7 @@ function verdictBlock(cwd, branch) {
72443
73151
  async function create4(argv, io) {
72444
73152
  const flags = parseFlags(argv, {
72445
73153
  base: "value",
73154
+ track: "value",
72446
73155
  title: "value",
72447
73156
  body: "value",
72448
73157
  "body-file": "value",
@@ -72461,18 +73170,26 @@ async function create4(argv, io) {
72461
73170
  let body = flags.values.body ?? null;
72462
73171
  if (flags.values["body-file"] !== undefined) {
72463
73172
  try {
72464
- body = readFileSync17(path40.resolve(io.cwd, flags.values["body-file"]), "utf8");
73173
+ body = readFileSync18(path40.resolve(io.cwd, flags.values["body-file"]), "utf8");
72465
73174
  } catch {
72466
73175
  return usage3(io, `cannot read --body-file ${flags.values["body-file"]}`, CREATE_USAGE);
72467
73176
  }
72468
73177
  }
73178
+ if (flags.values.base !== undefined && flags.values.track !== undefined)
73179
+ return usage3(io, "pass --base or --track, not both (--track picks the base for you)", CREATE_USAGE);
72469
73180
  let base = flags.values.base ?? null;
73181
+ let releaseTrack = null;
72470
73182
  if (!base) {
72471
- const resolved = vcsConfig("resolve", io.cwd);
73183
+ const resolved = vcsConfig("resolve", io.cwd, { track: flags.values.track ?? null });
72472
73184
  if (resolved.ok === false)
72473
73185
  return emit(io, fail("blocked", String(resolved.error), {
72474
73186
  unblock: "fix ~/.config/workit/vcs.json or pass --base <b>"
72475
73187
  }));
73188
+ releaseTrack = resolved.releaseTrack ?? null;
73189
+ if (releaseTrack?.blocking)
73190
+ return emit(io, fail("blocked", releaseTrack.blocking, {
73191
+ unblock: "workit pr create --track <name> … # or --base <branch>"
73192
+ }));
72476
73193
  base = String(resolved.defaultTargetBranch ?? "") || null;
72477
73194
  }
72478
73195
  if (!base)
@@ -72502,8 +73219,11 @@ async function create4(argv, io) {
72502
73219
  }, forgeDeps.sleep);
72503
73220
  if (!result.ok)
72504
73221
  return forgeFail(io, result);
72505
- return emit(io, ok(result.data), (data) => [
73222
+ const created = releaseTrack ? { ...result.data, releaseTrack } : result.data;
73223
+ return emit(io, ok(created), (data) => [
72506
73224
  `${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}`,
73225
+ ...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
73226
+ ...(data.releaseTrack?.warnings ?? []).map((warning) => `note: ${warning}`),
72507
73227
  `head ${data.head.slice(0, 12)} verified on the forge`,
72508
73228
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
72509
73229
  ]);
@@ -72532,6 +73252,9 @@ async function merge3(argv, io) {
72532
73252
  return emit(io, ok(result.data), (data) => [
72533
73253
  `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
73254
  data.verdict.required ? `verdict ${data.verdict.verdictId ?? "?"} accepted for that head` : "no verdict required (workspace grants merge: true)",
73255
+ ...data.mergeBack.length ? [
73256
+ `merge back: ${data.base} is the ${data.mergeBackTrack} production branch; bring it into ${data.mergeBack.join(", ")}`
73257
+ ] : [],
72535
73258
  ...data.deletedBranch === true ? [`deleted ${data.branch}`] : typeof data.deletedBranch === "object" ? [`branch not deleted: ${data.deletedBranch.error}`] : [],
72536
73259
  ..."error" in data.recorded ? [`ledger: not recorded (${data.recorded.error})`] : []
72537
73260
  ]);
@@ -72546,7 +73269,7 @@ async function run10(argv, io) {
72546
73269
  return merge3(rest, io);
72547
73270
  return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", USAGE4);
72548
73271
  }
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;
73272
+ 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
73273
  var init_pr = __esm(() => {
72551
73274
  init_pr_body();
72552
73275
  init_pr_ops();
@@ -72777,7 +73500,13 @@ __export(exports_git, {
72777
73500
  run: () => run12
72778
73501
  });
72779
73502
  async function branch(argv, io) {
72780
- const flags = parseFlags(argv, { base: "value", kind: "value", slug: "value", carry: "boolean" });
73503
+ const flags = parseFlags(argv, {
73504
+ base: "value",
73505
+ kind: "value",
73506
+ slug: "value",
73507
+ track: "value",
73508
+ carry: "boolean"
73509
+ });
72781
73510
  if (typeof flags === "string")
72782
73511
  return usage3(io, flags, BRANCH_USAGE);
72783
73512
  const { kind, slug } = flags.values;
@@ -72789,18 +73518,19 @@ async function branch(argv, io) {
72789
73518
  return usage3(io, "--kind must be feature, bugfix or hotfix", BRANCH_USAGE);
72790
73519
  if (kind !== undefined && flags.positionals.length)
72791
73520
  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)
73521
+ if (kind === undefined && !flags.positionals[0])
72794
73522
  return usage3(io, "missing branch name", BRANCH_USAGE);
72795
73523
  const result = gitBranch(io.cwd, {
72796
- name,
73524
+ ...kind !== undefined ? { kind, slug } : { name: flags.positionals[0] },
72797
73525
  base: flags.values.base ?? null,
73526
+ track: flags.values.track ?? null,
72798
73527
  carry: flags.booleans.has("carry")
72799
73528
  });
72800
73529
  if (!result.ok)
72801
73530
  return forgeFail(io, result);
72802
73531
  return emit(io, ok(result.data), (data) => [
72803
73532
  `switched to new branch ${data.branch} from ${data.baseRef} (${short2(data.baseSha)})${data.carried ? ", carrying uncommitted changes" : ""}`,
73533
+ ...data.releaseTrack?.name ? [`release track: ${data.releaseTrack.name} (${data.releaseTrack.detail})`] : [],
72804
73534
  ...data.notes.map((note) => `note: ${note}`)
72805
73535
  ]);
72806
73536
  }
@@ -72921,7 +73651,7 @@ async function run12(argv, io) {
72921
73651
  return push(rest, io);
72922
73652
  return usage3(io, sub && !sub.startsWith("-") ? `unknown git subcommand "${sub}"` : "missing git subcommand", USAGE5);
72923
73653
  }
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)";
73654
+ 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
73655
  var init_git2 = __esm(() => {
72926
73656
  init_autonomy();
72927
73657
  init_ops();
@@ -72932,7 +73662,7 @@ var init_git2 = __esm(() => {
72932
73662
  });
72933
73663
 
72934
73664
  // packages/workit-core/src/forge/verify.ts
72935
- import { spawnSync as spawnSync17 } from "node:child_process";
73665
+ import { spawnSync as spawnSync18 } from "node:child_process";
72936
73666
  function finish(cwd, report, actor) {
72937
73667
  const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
72938
73668
  const out = { ...report, delivered };
@@ -73145,7 +73875,7 @@ var NPM_NAME, SEMVER, SAFE_ARG, systemNpm = (args) => {
73145
73875
  };
73146
73876
  const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
73147
73877
  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);
73878
+ const result = process.platform === "win32" ? spawnSync18(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync18("npm", [...args], options);
73149
73879
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
73150
73880
  }, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
73151
73881
  try {
@@ -73252,8 +73982,8 @@ var init_verify_delivery = __esm(() => {
73252
73982
  });
73253
73983
 
73254
73984
  // 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";
73985
+ import { spawnSync as spawnSync19 } from "node:child_process";
73986
+ import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
73257
73987
  import fs21 from "node:fs";
73258
73988
  import path41 from "node:path";
73259
73989
  function worktreeOf(cwd, branch) {
@@ -73353,7 +74083,7 @@ function stacksDir(cwd) {
73353
74083
  return { ok: true, data: path41.join(root.value.root, "stacks") };
73354
74084
  }
73355
74085
  function parseStack(raw) {
73356
- if (!isRecord6(raw) || !Array.isArray(raw.branches))
74086
+ if (!isRecord7(raw) || !Array.isArray(raw.branches))
73357
74087
  return null;
73358
74088
  const name = str3(raw.name);
73359
74089
  const trunk = str3(raw.trunk);
@@ -73361,12 +74091,12 @@ function parseStack(raw) {
73361
74091
  return null;
73362
74092
  const branches = [];
73363
74093
  for (const item of raw.branches) {
73364
- if (!isRecord6(item))
74094
+ if (!isRecord7(item))
73365
74095
  return null;
73366
74096
  const branch = str3(item.branch);
73367
74097
  if (!branch)
73368
74098
  return null;
73369
- const merged = isRecord6(item.merged) ? {
74099
+ const merged = isRecord7(item.merged) ? {
73370
74100
  pr: typeof item.merged.pr === "number" ? item.merged.pr : null,
73371
74101
  mergeSha: str3(item.merged.mergeSha),
73372
74102
  at: str3(item.merged.at) ?? ""
@@ -73379,7 +74109,7 @@ function parseStack(raw) {
73379
74109
  lastParentHead: str3(item.lastParentHead),
73380
74110
  patchId: str3(item.patchId),
73381
74111
  merged,
73382
- ...isRecord6(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
74112
+ ...isRecord7(item.pendingRestack) && str3(item.pendingRestack.from) && str3(item.pendingRestack.tip) && str3(item.pendingRestack.onto) ? {
73383
74113
  pendingRestack: {
73384
74114
  from: str3(item.pendingRestack.from),
73385
74115
  tip: str3(item.pendingRestack.tip),
@@ -73765,7 +74495,7 @@ function commitKeys(cwd, range) {
73765
74495
  const patches = new Map;
73766
74496
  const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
73767
74497
  if (diff.ok && diff.stdout.trim()) {
73768
- const ids = spawnSync18("git", ["patch-id", "--stable"], {
74498
+ const ids = spawnSync19("git", ["patch-id", "--stable"], {
73769
74499
  cwd,
73770
74500
  input: diff.stdout,
73771
74501
  encoding: "utf8",
@@ -74373,7 +75103,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74373
75103
  ...unblock ? { unblock } : {},
74374
75104
  ...data ? { data } : {}
74375
75105
  }), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
74376
- const run = spawnSync18("git", args, {
75106
+ const run = spawnSync19("git", args, {
74377
75107
  cwd,
74378
75108
  encoding: "utf8",
74379
75109
  timeout: timeoutMs,
@@ -74404,7 +75134,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
74404
75134
  return dir.ok ? path41.join(dir.data, "worktrees") : null;
74405
75135
  }, firstLine = (run) => (`${run.stderr}
74406
75136
  ${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) => {
75137
+ `).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
75138
  if (name.length === 0 || name.length > 200)
74409
75139
  return false;
74410
75140
  for (let index = 0;index < name.length; index += 1) {
@@ -74413,7 +75143,7 @@ ${run.stdout}`.split(`
74413
75143
  return false;
74414
75144
  }
74415
75145
  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) => {
75146
+ }, 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
75147
  let raw = null;
74418
75148
  try {
74419
75149
  raw = fs21.readFileSync(path41.join(lock, OWNER), "utf8");
@@ -74459,7 +75189,7 @@ __export(exports_stack, {
74459
75189
  run: () => run14
74460
75190
  });
74461
75191
  async function plan(argv, io) {
74462
- const flags = parseFlags(argv, { name: "value", trunk: "value" });
75192
+ const flags = parseFlags(argv, { name: "value", trunk: "value", track: "value" });
74463
75193
  if (typeof flags === "string")
74464
75194
  return usage3(io, flags, PLAN_USAGE);
74465
75195
  const name = flags.values.name ?? null;
@@ -74472,7 +75202,20 @@ async function plan(argv, io) {
74472
75202
  if (selected.ok)
74473
75203
  existing = selected.data;
74474
75204
  }
74475
- const trunk = flags.values.trunk ?? existing?.trunk ?? defaultTrunk(io) ?? "main";
75205
+ const track = flags.values.track ?? null;
75206
+ if (track !== null && flags.values.trunk !== undefined)
75207
+ return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE);
75208
+ let trunk = flags.values.trunk ?? existing?.trunk ?? null;
75209
+ if (trunk === null || track !== null) {
75210
+ const derived = defaultTrunk(io, track, flags.positionals[0] ?? null);
75211
+ if ("error" in derived)
75212
+ return emit(io, fail("blocked", derived.error, {
75213
+ unblock: "workit stack plan --track <name> … # or --trunk <branch>"
75214
+ }));
75215
+ if (existing && track !== null && derived.trunk !== existing.trunk)
75216
+ 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" }));
75217
+ trunk ??= derived.trunk ?? "main";
75218
+ }
74476
75219
  const connected = connect(io, flags.positionals[0] ?? null);
74477
75220
  const resolved = connected.ok ? connected.data : null;
74478
75221
  const planned = planStack(io.cwd, resolved, { name, trunk, branches: flags.positionals }, existing);
@@ -74632,9 +75375,16 @@ async function run14(argv, io) {
74632
75375
  return land(rest, io);
74633
75376
  return usage3(io, sub && !sub.startsWith("-") ? `unknown stack subcommand "${sub}"` : "missing stack subcommand", USAGE7);
74634
75377
  }
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;
75378
+ 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) => {
75379
+ const resolved = vcsConfig("resolve", io.cwd, {
75380
+ track,
75381
+ ...branch ? { branch } : {}
75382
+ });
75383
+ if (resolved.ok === false)
75384
+ return track !== null || String(resolved.configPath ?? "").endsWith("workspaces.json") ? { error: String(resolved.error) } : { trunk: null };
75385
+ if (resolved.releaseTrack?.blocking)
75386
+ return { error: String(resolved.releaseTrack.blocking) };
75387
+ return { trunk: String(resolved.defaultTargetBranch ?? "") || null };
74638
75388
  }, label = (forge, pr) => pr === null ? "no PR" : `${forge === "gitlab" ? "MR !" : "PR #"}${pr}`, renderSync = (data) => [
74639
75389
  `${data.dryRun ? "would sync" : "synced"} stack ${data.name}${data.merged.length ? `; merged: ${data.merged.map((item) => item.branch).join(", ")}` : ""}`,
74640
75390
  ...data.steps.map((step) => [
@@ -90686,11 +91436,11 @@ var init_rules = __esm(() => {
90686
91436
  });
90687
91437
 
90688
91438
  // 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";
91439
+ import { spawnSync as spawnSync20 } from "node:child_process";
91440
+ import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync19, statSync as statSync7 } from "node:fs";
90691
91441
  import path43 from "node:path";
90692
- function git5(cwd, args) {
90693
- const run = spawnSync19("git", ["-c", "core.quotepath=off", ...args], {
91442
+ function git6(cwd, args) {
91443
+ const run = spawnSync20("git", ["-c", "core.quotepath=off", ...args], {
90694
91444
  cwd,
90695
91445
  encoding: "utf8",
90696
91446
  maxBuffer: 64 * 1024 * 1024
@@ -90726,20 +91476,20 @@ function expandPaths(cwd, inputs) {
90726
91476
  function resolveBase(cwd, base) {
90727
91477
  const candidates = base ? [base] : ["origin/main", "main", "origin/master", "master"];
90728
91478
  for (const candidate of candidates) {
90729
- if (!git5(cwd, ["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]).ok)
91479
+ if (!git6(cwd, ["rev-parse", "--verify", "--quiet", `${candidate}^{commit}`]).ok)
90730
91480
  continue;
90731
- const mergeBase = git5(cwd, ["merge-base", candidate, "HEAD"]);
91481
+ const mergeBase = git6(cwd, ["merge-base", candidate, "HEAD"]);
90732
91482
  return mergeBase.ok ? mergeBase.stdout.trim() : null;
90733
91483
  }
90734
91484
  return null;
90735
91485
  }
90736
91486
  function changedLines(cwd, base) {
90737
- const root = git5(cwd, ["rev-parse", "--show-toplevel"]).stdout.trim();
91487
+ const root = git6(cwd, ["rev-parse", "--show-toplevel"]).stdout.trim();
90738
91488
  const changed = new Map;
90739
- const names = git5(root, ["diff", "--name-only", "-z", "--no-renames", "--diff-filter=AM", base]);
91489
+ const names = git6(root, ["diff", "--name-only", "-z", "--no-renames", "--diff-filter=AM", base]);
90740
91490
  for (const file of names.stdout.split("\x00").filter(Boolean)) {
90741
91491
  const lines = new Set;
90742
- const diff = git5(root, ["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", file]);
91492
+ const diff = git6(root, ["diff", "-U0", "--no-color", "--no-ext-diff", base, "--", file]);
90743
91493
  for (const line of diff.stdout.split(`
90744
91494
  `)) {
90745
91495
  const hunk = /^@@ -\S+ \+(\d+)(?:,(\d+))? @@/.exec(line);
@@ -90752,7 +91502,7 @@ function changedLines(cwd, base) {
90752
91502
  }
90753
91503
  changed.set(path43.join(root, file), lines);
90754
91504
  }
90755
- const untracked = git5(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
91505
+ const untracked = git6(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
90756
91506
  for (const file of untracked.stdout.split("\x00").filter(Boolean))
90757
91507
  changed.set(path43.join(root, file), "all");
90758
91508
  return changed;
@@ -90767,7 +91517,7 @@ function auditFiles(files, display) {
90767
91517
  const shown = display(file);
90768
91518
  let parsed;
90769
91519
  try {
90770
- parsed = parseSource(readFileSync18(file, "utf8"), file);
91520
+ parsed = parseSource(readFileSync19(file, "utf8"), file);
90771
91521
  } catch (error) {
90772
91522
  parseErrors.push({
90773
91523
  file: shown,
@@ -90835,7 +91585,7 @@ import {
90835
91585
  mkdirSync as mkdirSync9,
90836
91586
  mkdtempSync as mkdtempSync4,
90837
91587
  readdirSync as readdirSync9,
90838
- readFileSync as readFileSync19,
91588
+ readFileSync as readFileSync20,
90839
91589
  readlinkSync as readlinkSync3,
90840
91590
  realpathSync as realpathSync9,
90841
91591
  renameSync as renameSync5,
@@ -90944,7 +91694,7 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
90944
91694
  const stat = lstatSync7(dir);
90945
91695
  if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
90946
91696
  continue;
90947
- const pid = Number(readFileSync19(path44.join(dir, PID_FILE), "utf8"));
91697
+ const pid = Number(readFileSync20(path44.join(dir, PID_FILE), "utf8"));
90948
91698
  if (Number.isInteger(pid) && pid > 0 && alive(pid))
90949
91699
  continue;
90950
91700
  } catch (error) {
@@ -91022,18 +91772,18 @@ function copyWorkingTree(repo) {
91022
91772
  const dir = mkdtempSync4(path44.join(os14.tmpdir(), TEMP_PREFIX));
91023
91773
  writeFileSync9(path44.join(dir, PID_FILE), String(process.pid));
91024
91774
  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();
91775
+ const head = git6(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
91776
+ if (head && git6(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
91777
+ const branch = git6(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
91028
91778
  if (branch) {
91029
- git5(tree, ["update-ref", `refs/heads/${branch}`, head]);
91030
- git5(tree, ["symbolic-ref", "HEAD", `refs/heads/${branch}`]);
91779
+ git6(tree, ["update-ref", `refs/heads/${branch}`, head]);
91780
+ git6(tree, ["symbolic-ref", "HEAD", `refs/heads/${branch}`]);
91031
91781
  } else
91032
- git5(tree, ["update-ref", "--no-deref", "HEAD", head]);
91033
- git5(tree, ["reset", "-q"]);
91782
+ git6(tree, ["update-ref", "--no-deref", "HEAD", head]);
91783
+ git6(tree, ["reset", "-q"]);
91034
91784
  } else
91035
91785
  mkdirSync9(tree, { recursive: true });
91036
- const listed = git5(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00");
91786
+ const listed = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00");
91037
91787
  const dirs = new Set([""]);
91038
91788
  for (const relative of listed) {
91039
91789
  if (!relative)
@@ -91096,10 +91846,10 @@ function runCommand(command, cwd, timeoutMs, env) {
91096
91846
  async function runMutation(options) {
91097
91847
  const started = performance.now();
91098
91848
  const { repo } = options;
91099
- const tracked = git5(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
91849
+ const tracked = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
91100
91850
  const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
91101
91851
  try {
91102
- return { file, text: readFileSync19(path44.join(repo, file), "utf8") };
91852
+ return { file, text: readFileSync20(path44.join(repo, file), "utf8") };
91103
91853
  } catch {
91104
91854
  return { file, text: "" };
91105
91855
  }
@@ -91112,7 +91862,7 @@ async function runMutation(options) {
91112
91862
  if (NOT_SOURCE.test(relative) || !existsSync19(absolute))
91113
91863
  continue;
91114
91864
  try {
91115
- candidates.push(...mutantsFor(readFileSync19(absolute, "utf8"), relative, lines));
91865
+ candidates.push(...mutantsFor(readFileSync20(absolute, "utf8"), relative, lines));
91116
91866
  } catch {}
91117
91867
  }
91118
91868
  const mutants = candidates.map((mutant, index) => ({ ...mutant, id: index + 1 }));
@@ -91192,7 +91942,7 @@ async function runMutation(options) {
91192
91942
  record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
91193
91943
  continue;
91194
91944
  }
91195
- const original = readFileSync19(path44.join(copy, mutant.file), "utf8");
91945
+ const original = readFileSync20(path44.join(copy, mutant.file), "utf8");
91196
91946
  const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
91197
91947
  current = { file: mutant.file, original };
91198
91948
  if (!writeInside(copy, mutant.file, mutated)) {
@@ -91398,7 +92148,7 @@ async function run16(argv, io) {
91398
92148
  if (typeof options === "string")
91399
92149
  return emit(io, fail("invalid_input", `${options}
91400
92150
  usage: ${USAGE9}`));
91401
- const top = git5(io.cwd, ["rev-parse", "--show-toplevel"]);
92151
+ const top = git6(io.cwd, ["rev-parse", "--show-toplevel"]);
91402
92152
  const repo = top.ok ? path45.resolve(top.stdout.trim()) : null;
91403
92153
  const display = (file) => toPosix(path45.relative(io.cwd, file) || file);
91404
92154
  let base = null;
@@ -91610,7 +92360,7 @@ var exports_youtrack = {};
91610
92360
  __export(exports_youtrack, {
91611
92361
  run: () => run17
91612
92362
  });
91613
- import { readFileSync as readFileSync20 } from "node:fs";
92363
+ import { readFileSync as readFileSync21 } from "node:fs";
91614
92364
  import path46 from "node:path";
91615
92365
  async function run17(argv, io) {
91616
92366
  const [sub, ...rest] = argv;
@@ -91657,7 +92407,7 @@ async function run17(argv, io) {
91657
92407
  let markdown = flags.values.markdown ?? "";
91658
92408
  if (flags.values.file)
91659
92409
  try {
91660
- markdown = readFileSync20(path46.resolve(io.cwd, flags.values.file), "utf8");
92410
+ markdown = readFileSync21(path46.resolve(io.cwd, flags.values.file), "utf8");
91661
92411
  } catch (error) {
91662
92412
  return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE10);
91663
92413
  }
@@ -91725,7 +92475,7 @@ var exports_changelog = {};
91725
92475
  __export(exports_changelog, {
91726
92476
  run: () => run18
91727
92477
  });
91728
- import { readFileSync as readFileSync21 } from "node:fs";
92478
+ import { readFileSync as readFileSync22 } from "node:fs";
91729
92479
  import path47 from "node:path";
91730
92480
  async function run18(argv, io) {
91731
92481
  const [sub, ...rest] = argv;
@@ -91772,7 +92522,7 @@ async function run18(argv, io) {
91772
92522
  ]);
91773
92523
  }
91774
92524
  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;
92525
+ const text = raw === "-" ? readFileSync22(0, "utf8") : raw.startsWith("@") ? readFileSync22(path47.resolve(cwd, raw.slice(1)), "utf8") : raw;
91776
92526
  return JSON.parse(text);
91777
92527
  };
91778
92528
  var init_changelog2 = __esm(() => {
@@ -91949,7 +92699,7 @@ var VERBS = [
91949
92699
  {
91950
92700
  name: "pr",
91951
92701
  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]",
92702
+ 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
92703
  summary: "PR/MR state with failing log tails and next action; open a SHA-verified PR; merge only when READY, verified and granted",
91954
92704
  load: () => Promise.resolve().then(() => (init_pr(), exports_pr))
91955
92705
  },
@@ -91963,7 +92713,7 @@ var VERBS = [
91963
92713
  {
91964
92714
  name: "git",
91965
92715
  group: "delivery",
91966
- usage: "workit git branch <name> [--base <b>] | git commit -m <msg> [--all | -- <paths…>] | git push [--set-upstream] [--force-with-lease]",
92716
+ usage: "workit git branch <name> [--base <b>] [--track <t>] | git commit -m <msg> [--all | -- <paths…>] | git push [--set-upstream] [--force-with-lease]",
91967
92717
  summary: "Policy-checked branch and commit (Workit-Session trailer); push with a lease and a verified remote tip",
91968
92718
  load: () => Promise.resolve().then(() => (init_git2(), exports_git))
91969
92719
  },