@brainervirus/workit-opencode 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/plugin.js +538 -43
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -4,7 +4,7 @@ function define(plugin) {
4
4
  }
5
5
 
6
6
  // packages/workit-core/src/core.ts
7
- import { spawnSync as spawnSync11 } from "node:child_process";
7
+ import { spawnSync as spawnSync12 } from "node:child_process";
8
8
  import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
9
9
  import path19 from "node:path";
10
10
 
@@ -12421,6 +12421,11 @@ function worktreeSignal(cwd, options = {}) {
12421
12421
  }
12422
12422
  return `sig:${hash.digest("hex")}`;
12423
12423
  }
12424
+ function mergeBase(cwd, a, b) {
12425
+ if (!safeArg(a) || !safeArg(b))
12426
+ return null;
12427
+ return line(git2(cwd, ["merge-base", a, b]));
12428
+ }
12424
12429
  function patchId(cwd, base, head) {
12425
12430
  if (!safeArg(base) || !safeArg(head))
12426
12431
  return null;
@@ -12444,9 +12449,33 @@ function patchId(cwd, base, head) {
12444
12449
  const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
12445
12450
  return line(id)?.split(/\s+/u)[0] ?? null;
12446
12451
  }
12452
+ function remoteNames(cwd) {
12453
+ return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
12454
+ }
12455
+ function resolveRef(cwd, ref) {
12456
+ if (!safeArg(ref))
12457
+ return null;
12458
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
12459
+ }
12460
+ var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
12447
12461
  function currentBranch(cwd) {
12448
12462
  return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
12449
12463
  }
12464
+ function pushRemoteName(cwd, branch) {
12465
+ const name = branch ?? currentBranch(cwd);
12466
+ const remotes = remoteNames(cwd);
12467
+ const candidates = [
12468
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
12469
+ config2(cwd, "remote.pushDefault"),
12470
+ name ? config2(cwd, `branch.${name}.remote`) : null
12471
+ ];
12472
+ for (const candidate of candidates)
12473
+ if (candidate && candidate !== "." && remotes.includes(candidate))
12474
+ return candidate;
12475
+ if (remotes.includes("origin"))
12476
+ return "origin";
12477
+ return remotes.length === 1 ? remotes[0] : null;
12478
+ }
12450
12479
 
12451
12480
  // packages/workit-core/src/core/policy/derive.ts
12452
12481
  import { existsSync as existsSync3 } from "node:fs";
@@ -13896,7 +13925,7 @@ var exportDigest = (bundle) => sha256({
13896
13925
  task: bundle.task
13897
13926
  });
13898
13927
  // packages/workit-core/src/core/branch.ts
13899
- import { createHash as createHash9 } from "node:crypto";
13928
+ import { createHash as createHash10 } from "node:crypto";
13900
13929
 
13901
13930
  // packages/workit-core/src/core/git.ts
13902
13931
  import { execFileSync } from "node:child_process";
@@ -14153,6 +14182,8 @@ var writeConfig = (config) => {
14153
14182
  writeFileSync2(path12.join(dir, "config.json"), JSON.stringify(config, null, 2) + `
14154
14183
  `, "utf8");
14155
14184
  };
14185
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
14186
+ var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
14156
14187
  var resolveBranchPolicy = (config, workspace) => {
14157
14188
  const wp = workspace?.branchPolicy ?? {};
14158
14189
  const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
@@ -14160,7 +14191,7 @@ var resolveBranchPolicy = (config, workspace) => {
14160
14191
  allowed: wp.allowed,
14161
14192
  protectedNames: wp.protected
14162
14193
  }, config);
14163
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
14194
+ const allowed = merged.allowed.map(branchGlobPattern);
14164
14195
  return {
14165
14196
  preset,
14166
14197
  allowed,
@@ -14225,26 +14256,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
14225
14256
  object({ kind: literal("manual") }).strict()
14226
14257
  ]);
14227
14258
  var releaseTrackSchema = object({
14228
- strategy: branchPreset,
14259
+ strategy: branchPreset.optional(),
14229
14260
  productionBranch: nonBlank,
14230
14261
  integrationBranch: nonBlank,
14231
- naming: object({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
14232
- baseBranch: nonBlank,
14233
- mergeBackBranches: array(nonBlank),
14234
- pullRequestTarget: nonBlank,
14235
- tagNamespace: string2(),
14236
- versionSource: versionSourceSchema,
14237
- requiredChecks: array(nonBlank)
14238
- }).strict().superRefine((track, context) => {
14262
+ naming: object({
14263
+ feature: nonBlank.optional(),
14264
+ release: nonBlank.optional(),
14265
+ hotfix: nonBlank.optional()
14266
+ }).passthrough().optional(),
14267
+ baseBranch: nonBlank.optional(),
14268
+ mergeBackBranches: array(nonBlank).optional(),
14269
+ pullRequestTarget: nonBlank.optional(),
14270
+ tagNamespace: string2().optional(),
14271
+ versionSource: versionSourceSchema.optional(),
14272
+ requiredChecks: array(nonBlank).optional(),
14273
+ critical: array(nonBlank).optional()
14274
+ }).passthrough().superRefine((track, context) => {
14239
14275
  for (const [field, values] of [
14240
- ["mergeBackBranches", track.mergeBackBranches],
14241
- ["requiredChecks", track.requiredChecks]
14276
+ ["mergeBackBranches", track.mergeBackBranches ?? []],
14277
+ ["requiredChecks", track.requiredChecks ?? []]
14242
14278
  ]) {
14243
14279
  if (new Set(values).size !== values.length) {
14244
14280
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
14245
14281
  }
14246
14282
  }
14247
- if (track.versionSource.kind === "package-json" && (path13.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
14283
+ if (track.versionSource?.kind === "package-json" && (path13.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
14248
14284
  context.addIssue({
14249
14285
  code: "custom",
14250
14286
  path: ["versionSource", "path"],
@@ -14512,7 +14548,7 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
14512
14548
  var runtimeWorkspaceIndexSchema = object({
14513
14549
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
14514
14550
  }).passthrough();
14515
- var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
14551
+ var matchRuntimeWorkspace = (cwd) => {
14516
14552
  const file = path13.join(configDir(), "workspaces.json");
14517
14553
  let raw;
14518
14554
  try {
@@ -14547,8 +14583,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
14547
14583
  });
14548
14584
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
14549
14585
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
14550
- if (!selected)
14586
+ return selected ? { file, selected } : null;
14587
+ };
14588
+ var resolveRuntimeReleaseTracks = (cwd) => {
14589
+ const matched = matchRuntimeWorkspace(cwd);
14590
+ return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
14591
+ };
14592
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
14593
+ const matched = matchRuntimeWorkspace(cwd);
14594
+ if (!matched)
14551
14595
  return null;
14596
+ const { file, selected } = matched;
14552
14597
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
14553
14598
  if (!policy.success) {
14554
14599
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
@@ -14574,15 +14619,423 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
14574
14619
  source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
14575
14620
  };
14576
14621
  };
14622
+ function workspaceDefaultTarget(cwd, globalDefault) {
14623
+ const ws = resolveRuntimeWorkspaceVcs(cwd);
14624
+ const wp = ws?.branchPolicy ?? {};
14625
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
14626
+ const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
14627
+ const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
14628
+ return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
14629
+ }
14577
14630
 
14578
14631
  // packages/workit-core/src/core/vcs-config.ts
14579
14632
  import fs12 from "node:fs";
14580
14633
  import path14 from "node:path";
14634
+ import { spawnSync as spawnSync8 } from "node:child_process";
14635
+
14636
+ // packages/workit-core/src/core/release-tracks.ts
14637
+ import { createHash as createHash9 } from "node:crypto";
14581
14638
  import { spawnSync as spawnSync7 } from "node:child_process";
14582
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path14.join(configDir(), "vcs.json");
14639
+ class ReleaseTrackError extends Error {
14640
+ }
14641
+ var KNOWN_FIELDS = new Set([
14642
+ "strategy",
14643
+ "productionBranch",
14644
+ "integrationBranch",
14645
+ "naming",
14646
+ "baseBranch",
14647
+ "mergeBackBranches",
14648
+ "pullRequestTarget",
14649
+ "tagNamespace",
14650
+ "versionSource",
14651
+ "requiredChecks",
14652
+ "critical"
14653
+ ]);
14654
+ var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
14655
+ var PLACEHOLDER = /\{[^}]*\}/gu;
14656
+ var HAS_PLACEHOLDER = /\{[^}]*\}/u;
14657
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14658
+ var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
14659
+ var namedBranches = (entry) => [
14660
+ ...BRANCH_FIELDS.map((field) => branchName(entry[field])),
14661
+ ...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
14662
+ ].filter((name) => name !== null);
14663
+ var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
14664
+ function readReleaseTracks(raw) {
14665
+ const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
14666
+ if (raw === undefined || raw === null)
14667
+ return out;
14668
+ if (!isRecord3(raw)) {
14669
+ out.issues.push("releaseTracks is not an object; ignored");
14670
+ return out;
14671
+ }
14672
+ const fatal = [];
14673
+ const protectedNames = new Set;
14674
+ for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
14675
+ const label = `release track ${JSON.stringify(name)}`;
14676
+ if (!isRecord3(entry)) {
14677
+ out.issues.push(`${label} is not an object; ignored`);
14678
+ continue;
14679
+ }
14680
+ for (const branch of namedBranches(entry))
14681
+ protectedNames.add(branch);
14682
+ if (!name.trim()) {
14683
+ out.issues.push("a release track with a blank name was ignored");
14684
+ continue;
14685
+ }
14686
+ let critical = [];
14687
+ if (Array.isArray(entry.critical))
14688
+ critical = entry.critical.filter((item) => typeof item === "string");
14689
+ else if (entry.critical !== undefined)
14690
+ out.issues.push(`${label} critical: must be a list of field names; ignored`);
14691
+ const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
14692
+ const problem = (field, message) => {
14693
+ if (isCritical(field))
14694
+ fatal.push(`${label} ${field}: ${message}`);
14695
+ else
14696
+ out.issues.push(`${label} ${field}: ${message}`);
14697
+ };
14698
+ for (const key of Object.keys(entry))
14699
+ if (!KNOWN_FIELDS.has(key))
14700
+ problem(key, "unknown field, ignored");
14701
+ const productionBranch = branchName(entry.productionBranch);
14702
+ const integrationBranch = branchName(entry.integrationBranch);
14703
+ if (!productionBranch)
14704
+ problem("productionBranch", "missing or not a branch name");
14705
+ if (!integrationBranch)
14706
+ problem("integrationBranch", "missing or not a branch name");
14707
+ const optionalBranch = (field) => {
14708
+ if (entry[field] === undefined)
14709
+ return null;
14710
+ const value = branchName(entry[field]);
14711
+ if (!value)
14712
+ problem(field, "not a branch name; using the integration branch");
14713
+ return value;
14714
+ };
14715
+ const baseBranch = optionalBranch("baseBranch");
14716
+ const pullRequestTarget = optionalBranch("pullRequestTarget");
14717
+ let mergeBackBranches = [];
14718
+ if (entry.mergeBackBranches !== undefined) {
14719
+ const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
14720
+ if (list.some((item) => item === null))
14721
+ problem("mergeBackBranches", "must be a list of branch names; ignored");
14722
+ else
14723
+ mergeBackBranches = [...new Set(list)];
14724
+ }
14725
+ const naming = {};
14726
+ if (entry.naming !== undefined) {
14727
+ if (!isRecord3(entry.naming))
14728
+ problem("naming", "not an object; ignored");
14729
+ else
14730
+ for (const kind of ["feature", "release", "hotfix"]) {
14731
+ const template = entry.naming[kind];
14732
+ if (template === undefined)
14733
+ continue;
14734
+ if (typeof template !== "string" || !template.trim())
14735
+ problem(`naming.${kind}`, "not a template string; ignored");
14736
+ else if (!literalOf(template))
14737
+ problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
14738
+ else
14739
+ naming[kind] = template.trim();
14740
+ }
14741
+ }
14742
+ if (!productionBranch || !integrationBranch) {
14743
+ out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
14744
+ continue;
14745
+ }
14746
+ out.tracks.push({
14747
+ name,
14748
+ productionBranch,
14749
+ integrationBranch,
14750
+ baseBranch: baseBranch ?? integrationBranch,
14751
+ pullRequestTarget: pullRequestTarget ?? integrationBranch,
14752
+ mergeBackBranches,
14753
+ naming
14754
+ });
14755
+ }
14756
+ out.protectedBranches = [...protectedNames];
14757
+ if (fatal.length)
14758
+ out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
14759
+ return out;
14760
+ }
14761
+ var trackBranches = (tracks) => [
14762
+ ...new Set(tracks.flatMap((track) => [
14763
+ track.productionBranch,
14764
+ track.integrationBranch,
14765
+ track.baseBranch,
14766
+ track.pullRequestTarget,
14767
+ ...track.mergeBackBranches
14768
+ ]))
14769
+ ];
14770
+ var namingGlob = (template) => HAS_PLACEHOLDER.test(template) ? template.replace(PLACEHOLDER, "*") : `${template.replace(/\/?\*?$/u, "")}/*`;
14771
+ var IMPLICIT = "";
14772
+ var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
14773
+ var shortRef = (ref, remotes) => {
14774
+ const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
14775
+ const remote = remotes.find((name) => bare.startsWith(`${name}/`));
14776
+ return remote ? bare.slice(remote.length + 1) : bare;
14777
+ };
14778
+ var MAX_RECORDED_HOPS = 10;
14779
+ function resolveReleaseTrack(input) {
14780
+ const tracks = [...input.tracks];
14781
+ const issues = [...input.issues ?? []];
14782
+ const requested = input.requested?.trim();
14783
+ if (tracks.length === 0) {
14784
+ if (requested)
14785
+ return {
14786
+ status: "invalid",
14787
+ error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
14788
+ issues
14789
+ };
14790
+ return { status: "none", issues };
14791
+ }
14792
+ const names = tracks.map((track) => track.name).join(", ");
14793
+ let envProblem = null;
14794
+ const done = (track, source, detail, warnings = [], ambiguous = null) => ({
14795
+ status: "resolved",
14796
+ track: track && track.name !== IMPLICIT ? track : null,
14797
+ source,
14798
+ detail,
14799
+ warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
14800
+ tracks,
14801
+ blocking: envProblem ?? ambiguous
14802
+ });
14803
+ if (requested) {
14804
+ const named = tracks.find((track) => track.name === requested);
14805
+ if (!named)
14806
+ return {
14807
+ status: "invalid",
14808
+ error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
14809
+ issues
14810
+ };
14811
+ return done(named, "flag", `track ${named.name} was requested explicitly`);
14812
+ }
14813
+ const owned = (branch) => branch !== null && tracks.some((track) => [
14814
+ track.integrationBranch,
14815
+ track.productionBranch,
14816
+ track.baseBranch,
14817
+ track.pullRequestTarget
14818
+ ].includes(branch));
14819
+ const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
14820
+ name: IMPLICIT,
14821
+ productionBranch: input.defaultBranch,
14822
+ integrationBranch: input.defaultBranch,
14823
+ baseBranch: input.defaultBranch,
14824
+ pullRequestTarget: input.defaultBranch,
14825
+ mergeBackBranches: [],
14826
+ naming: {}
14827
+ } : null;
14828
+ const candidates = implicit ? [...tracks, implicit] : tracks;
14829
+ const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
14830
+ const fallback = (why) => {
14831
+ const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
14832
+ const track = byDefault.length === 1 ? byDefault[0] : null;
14833
+ const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
14834
+ 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;
14835
+ return done(track, "default", `${why}; using the workspace ${target}`, [
14836
+ `release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
14837
+ ], ambiguous);
14838
+ };
14839
+ const branch = input.branch;
14840
+ if (branch) {
14841
+ const owners = ownedBy(candidates, branch);
14842
+ if (owners.length === 1)
14843
+ return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
14844
+ if (owners.length > 1)
14845
+ return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
14846
+ }
14847
+ const env = input.envRequested?.trim();
14848
+ if (env) {
14849
+ const named = tracks.find((track) => track.name === env);
14850
+ if (named)
14851
+ return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
14852
+ envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
14853
+ }
14854
+ if (!branch)
14855
+ return fallback("HEAD is detached");
14856
+ if (tracks.length === 1 && !implicit)
14857
+ return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
14858
+ let remotes = null;
14859
+ const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
14860
+ const ownerOf = (ref) => {
14861
+ const owner = ownedBy(candidates, short(ref));
14862
+ return owner.length === 1 ? owner[0] : null;
14863
+ };
14864
+ const followRecorded = (start) => {
14865
+ let cursor = start;
14866
+ const seen = new Set([start]);
14867
+ for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
14868
+ const recorded = input.probe.recordedBase(cursor);
14869
+ if (!recorded)
14870
+ return null;
14871
+ const base = short(recorded);
14872
+ const owner = ownerOf(base);
14873
+ if (owner)
14874
+ return owner;
14875
+ if (ownedBy(candidates, base).length > 1 || seen.has(base))
14876
+ return null;
14877
+ seen.add(base);
14878
+ cursor = base;
14879
+ }
14880
+ return null;
14881
+ };
14882
+ const recordedOwner = followRecorded(branch);
14883
+ if (recordedOwner)
14884
+ return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
14885
+ for (const source of ["reflog", "checkout"]) {
14886
+ const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
14887
+ if (!ref)
14888
+ continue;
14889
+ const owner = ownerOf(ref) ?? followRecorded(short(ref));
14890
+ if (owner)
14891
+ return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
14892
+ }
14893
+ const forks = [];
14894
+ for (const track of candidates)
14895
+ for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
14896
+ const sha = input.probe.mergeBase(branch, ref);
14897
+ if (sha)
14898
+ forks.push({ track, ref, sha });
14899
+ }
14900
+ if (forks.length === 0)
14901
+ return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
14902
+ const ancestry = new Map;
14903
+ const descends = (older, newer) => {
14904
+ if (older === newer)
14905
+ return false;
14906
+ const key = `${older}>${newer}`;
14907
+ if (!ancestry.has(key))
14908
+ ancestry.set(key, input.probe.isAncestor(older, newer));
14909
+ return ancestry.get(key);
14910
+ };
14911
+ const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
14912
+ const winningTracks = [...new Set(winners.map((fork) => fork.track))];
14913
+ if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
14914
+ const win = winners[0];
14915
+ return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
14916
+ }
14917
+ if (winningTracks.length === 1)
14918
+ return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
14919
+ const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
14920
+ 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 ")}`);
14921
+ }
14922
+ var git4 = (cwd, args) => {
14923
+ const run = spawnSync7("git", args, {
14924
+ cwd,
14925
+ encoding: "utf8",
14926
+ timeout: 20000,
14927
+ windowsHide: true,
14928
+ stdio: ["ignore", "pipe", "ignore"]
14929
+ });
14930
+ return run.status === 0 ? (run.stdout ?? "").trim() : null;
14931
+ };
14932
+ var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
14933
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
14934
+ var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
14935
+ `).map((line) => {
14936
+ const [sha = "", selector = "", ...subject] = line.split("\t");
14937
+ return {
14938
+ sha,
14939
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
14940
+ subject: subject.join("\t")
14941
+ };
14942
+ }).filter((entry) => entry.sha && Number.isFinite(entry.time));
14943
+ function gitTrackProbe(cwd) {
14944
+ let remote;
14945
+ let remotes;
14946
+ const tip = (branch) => {
14947
+ remote ??= pushRemoteName(cwd) ?? "origin";
14948
+ return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
14949
+ };
14950
+ return {
14951
+ remotes: () => remotes ??= remoteNames(cwd),
14952
+ recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
14953
+ createdFrom: (branch) => {
14954
+ const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
14955
+ const oldest = log?.split(`
14956
+ `).findLast(Boolean) ?? "";
14957
+ const match = /^branch: Created from (\S+)$/u.exec(oldest);
14958
+ return match && match[1] !== "HEAD" ? match[1] : null;
14959
+ },
14960
+ checkedOutFrom: (branch) => {
14961
+ const own = reflog(cwd, `refs/heads/${branch}`);
14962
+ const created = own.at(-1);
14963
+ if (!created || created.subject !== "branch: Created from HEAD")
14964
+ return null;
14965
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
14966
+ const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
14967
+ return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
14968
+ },
14969
+ mergeBase: (branch, trackBranch) => {
14970
+ const base = tip(trackBranch);
14971
+ return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
14972
+ },
14973
+ isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
14974
+ };
14975
+ }
14976
+ function workspaceReleaseTracks(cwd) {
14977
+ const raw = resolveRuntimeReleaseTracks(cwd);
14978
+ return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
14979
+ }
14980
+ var memo3 = new Map;
14981
+ var MEMO_LIMIT = 64;
14982
+ function resolveTrackFor(cwd, options) {
14983
+ const read = workspaceReleaseTracks(cwd);
14984
+ if (read.error)
14985
+ return { status: "invalid", error: read.error, issues: read.issues };
14986
+ if (read.tracks.length === 0 && !options.track?.trim())
14987
+ return { status: "none", issues: read.issues };
14988
+ const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
14989
+ const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
14990
+ const input = {
14991
+ tracks: read.tracks,
14992
+ requested: options.track ?? null,
14993
+ envRequested,
14994
+ branch,
14995
+ defaultBranch: options.defaultBranch,
14996
+ issues: read.issues
14997
+ };
14998
+ let key = null;
14999
+ if (!options.probe) {
15000
+ const refs = git4(cwd, [
15001
+ "for-each-ref",
15002
+ "--format=%(refname) %(objectname)",
15003
+ "refs/heads",
15004
+ "refs/remotes"
15005
+ ]) ?? "";
15006
+ const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
15007
+ const own = branch ? [
15008
+ git4(cwd, [
15009
+ "log",
15010
+ "-g",
15011
+ "-n",
15012
+ "1",
15013
+ "--date=unix",
15014
+ "--format=%H %gd %gs",
15015
+ `refs/heads/${branch}`,
15016
+ "--"
15017
+ ]) ?? "",
15018
+ git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
15019
+ ] : [];
15020
+ key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
15021
+ const hit = memo3.get(key);
15022
+ if (hit)
15023
+ return hit;
15024
+ }
15025
+ const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
15026
+ if (key) {
15027
+ if (memo3.size >= MEMO_LIMIT)
15028
+ memo3.delete(memo3.keys().next().value);
15029
+ memo3.set(key, resolution);
15030
+ }
15031
+ return resolution;
15032
+ }
15033
+
15034
+ // packages/workit-core/src/core/vcs-config.ts
15035
+ var workspacesPath = () => path14.join(configDir(), "workspaces.json");
14583
15036
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
14584
15037
  var remoteProvider = (cwd) => {
14585
- const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
15038
+ const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
14586
15039
  if (r.status !== 0)
14587
15040
  return null;
14588
15041
  const url = (r.stdout ?? "").trim();
@@ -14619,7 +15072,7 @@ var remoteEndpoint = (remote) => {
14619
15072
  var configuredSshEndpoint = (host) => {
14620
15073
  if (host.startsWith("-"))
14621
15074
  return null;
14622
- const resolved = spawnSync7("ssh", ["-G", host], {
15075
+ const resolved = spawnSync8("ssh", ["-G", host], {
14623
15076
  encoding: "utf8",
14624
15077
  timeout: 5000,
14625
15078
  maxBuffer: 1024 * 1024,
@@ -14684,7 +15137,16 @@ var readVcsConfig = () => {
14684
15137
  }
14685
15138
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
14686
15139
  };
14687
- function vcsConfig(mode, cwd) {
15140
+ var trackSummary = (resolution) => resolution.status === "resolved" ? {
15141
+ name: resolution.track?.name ?? null,
15142
+ source: resolution.source,
15143
+ detail: resolution.detail,
15144
+ warnings: resolution.warnings,
15145
+ track: resolution.track,
15146
+ tracks: resolution.tracks,
15147
+ blocking: resolution.blocking
15148
+ } : null;
15149
+ function vcsConfig(mode, cwd, options = {}) {
14688
15150
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
14689
15151
  const wsVcs = ws?.vcs ?? {};
14690
15152
  const wsYt = ws?.youtrack ?? {};
@@ -14694,10 +15156,7 @@ function vcsConfig(mode, cwd) {
14694
15156
  const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
14695
15157
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
14696
15158
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
14697
- const wp = ws?.branchPolicy ?? {};
14698
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
14699
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
14700
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
15159
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
14701
15160
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
14702
15161
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
14703
15162
  let issuesProvider = null;
@@ -14709,11 +15168,24 @@ function vcsConfig(mode, cwd) {
14709
15168
  if (mode === "resolve") {
14710
15169
  if (cfgStatus === "malformed")
14711
15170
  return { ok: false, error: cfgError, configPath: cfgPath };
15171
+ const trackResolution = resolveTrackFor(root, {
15172
+ track: options.track,
15173
+ ...options.branch !== undefined ? { branch: options.branch } : {},
15174
+ defaultBranch: workspaceDefault
15175
+ });
15176
+ if (trackResolution.status === "invalid")
15177
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
15178
+ const releaseTrack = trackSummary(trackResolution);
15179
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
15180
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
14712
15181
  return {
14713
15182
  ok: true,
14714
15183
  workspace_name: ws?.name ?? null,
14715
15184
  provider,
14716
15185
  defaultTargetBranch: defaultTarget,
15186
+ workspaceDefaultTargetBranch: workspaceDefault,
15187
+ baseBranch,
15188
+ releaseTrack,
14717
15189
  link_issues: linkIssues,
14718
15190
  youtrack_base_url: youtrackBaseUrl,
14719
15191
  issues_provider: issuesProvider,
@@ -14737,7 +15209,7 @@ function vcsConfig(mode, cwd) {
14737
15209
  ok: true,
14738
15210
  configPath: path14.resolve(cfgPath),
14739
15211
  provider,
14740
- defaultTargetBranch: defaultTarget,
15212
+ defaultTargetBranch: workspaceDefault,
14741
15213
  pr: cfg.pr ?? {},
14742
15214
  workspace_name: ws?.name ?? null,
14743
15215
  link_issues: linkIssues,
@@ -14773,7 +15245,7 @@ function mergedPrStyle(limit = 6, cwd) {
14773
15245
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
14774
15246
  descriptionPreview: desc.slice(0, 600)
14775
15247
  });
14776
- const remoteResult = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
15248
+ const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
14777
15249
  cwd,
14778
15250
  encoding: "utf8"
14779
15251
  });
@@ -14789,7 +15261,7 @@ function mergedPrStyle(limit = 6, cwd) {
14789
15261
  if (!hostingApiHostMatches(remote, host))
14790
15262
  return { ok: false, error: "PR style target does not match the provider host" };
14791
15263
  if (provider === "gitlab") {
14792
- const run = (args) => spawnSync7("glab", ["api", ...args], {
15264
+ const run = (args) => spawnSync8("glab", ["api", ...args], {
14793
15265
  cwd,
14794
15266
  encoding: "utf8",
14795
15267
  env
@@ -14809,7 +15281,7 @@ function mergedPrStyle(limit = 6, cwd) {
14809
15281
  });
14810
15282
  }
14811
15283
  } else if (provider === "github") {
14812
- const r = spawnSync7("gh", [
15284
+ const r = spawnSync8("gh", [
14813
15285
  "pr",
14814
15286
  "list",
14815
15287
  "--repo",
@@ -14842,11 +15314,35 @@ function mergedPrStyle(limit = 6, cwd) {
14842
15314
  }
14843
15315
 
14844
15316
  // packages/workit-core/src/core/branch.ts
15317
+ var withReleaseTracks = (workspaceRoot, policy) => {
15318
+ const read = workspaceReleaseTracks(workspaceRoot);
15319
+ if (read.error)
15320
+ throw new ReleaseTrackError(read.error);
15321
+ if (read.tracks.length === 0 && read.protectedBranches.length === 0)
15322
+ return policy;
15323
+ const known = new Set(policy.allowed.map((pattern) => pattern.source));
15324
+ const allowed = [...policy.allowed];
15325
+ for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
15326
+ const pattern = branchGlobPattern(glob);
15327
+ if (!known.has(pattern.source)) {
15328
+ known.add(pattern.source);
15329
+ allowed.push(pattern);
15330
+ }
15331
+ }
15332
+ return {
15333
+ ...policy,
15334
+ allowed,
15335
+ protected: new Set([
15336
+ ...policy.protected,
15337
+ ...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) => name.toLowerCase())
15338
+ ])
15339
+ };
15340
+ };
14845
15341
  function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
14846
15342
  const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
14847
15343
  const config = readConfig();
14848
15344
  return kind === "branch" ? {
14849
- branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
15345
+ branchPolicy: withReleaseTracks(workspaceRoot, resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null)),
14850
15346
  provenance: { branchPolicy: selected.source }
14851
15347
  } : {
14852
15348
  commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
@@ -14885,7 +15381,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
14885
15381
  const source = resolved.provenance.branchPolicy;
14886
15382
  const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
14887
15383
  const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
14888
- const fingerprint = createHash9("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
15384
+ const fingerprint = createHash10("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
14889
15385
  return {
14890
15386
  branchPolicy,
14891
15387
  namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
@@ -14909,7 +15405,6 @@ var validateBranchNameFor = (workspaceRoot, name) => {
14909
15405
  };
14910
15406
  }
14911
15407
  };
14912
- var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
14913
15408
 
14914
15409
  // packages/workit-core/src/core/route-intent.ts
14915
15410
  var shellBranchTarget = (command) => {
@@ -16804,7 +17299,7 @@ class WorkitCore {
16804
17299
  }
16805
17300
  }
16806
17301
  // packages/workit-core/src/core/repo-context.ts
16807
- import { spawnSync as spawnSync8 } from "node:child_process";
17302
+ import { spawnSync as spawnSync9 } from "node:child_process";
16808
17303
  import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
16809
17304
  import path16 from "node:path";
16810
17305
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
@@ -16833,7 +17328,7 @@ var invalidRange = (cwd) => ({
16833
17328
  cwd
16834
17329
  });
16835
17330
  var runGit = (cwd, args) => {
16836
- const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
17331
+ const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
16837
17332
  return {
16838
17333
  stdout: (result.stdout ?? "").trimEnd(),
16839
17334
  stderr: (result.stderr ?? "").trimEnd(),
@@ -17274,12 +17769,12 @@ function releaseNotesContext(root, rangeOrTag) {
17274
17769
  }
17275
17770
 
17276
17771
  // packages/workit-core/src/core/tracker-issues.ts
17277
- import { spawnSync as spawnSync10 } from "node:child_process";
17772
+ import { spawnSync as spawnSync11 } from "node:child_process";
17278
17773
 
17279
17774
  // packages/workit-core/src/core/pr-create.ts
17280
17775
  import fs13 from "node:fs";
17281
17776
  import path17 from "node:path";
17282
- import { spawnSync as spawnSync9 } from "node:child_process";
17777
+ import { spawnSync as spawnSync10 } from "node:child_process";
17283
17778
  // packages/workit-core/src/forge/pr-body.ts
17284
17779
  function parseGhIssue(value) {
17285
17780
  const m = /issues\/(\d+)/.exec(value);
@@ -17304,7 +17799,7 @@ function whichOnPath(tool) {
17304
17799
  }
17305
17800
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
17306
17801
  var pushRemote = (cwd) => {
17307
- const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
17802
+ const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
17308
17803
  cwd,
17309
17804
  encoding: "utf8"
17310
17805
  });
@@ -17335,7 +17830,7 @@ var defaultCli = (bin, available) => {
17335
17830
  if (!available)
17336
17831
  return null;
17337
17832
  return (args, root) => {
17338
- const out = spawnSync10(bin, args, {
17833
+ const out = spawnSync11(bin, args, {
17339
17834
  cwd: root,
17340
17835
  encoding: "utf8",
17341
17836
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -17911,7 +18406,7 @@ function resolveInside(root, candidate) {
17911
18406
  }
17912
18407
  function run2(root, executable, args, env = {}) {
17913
18408
  const cwd = realpathSync5(root);
17914
- const result = spawnSync11(executable, args, {
18409
+ const result = spawnSync12(executable, args, {
17915
18410
  cwd,
17916
18411
  encoding: "utf8",
17917
18412
  env: { ...process.env, ...env }
@@ -18532,7 +19027,7 @@ var ensureHygieneFiles = (root, opts) => {
18532
19027
  // packages/workit-core/src/core/init.ts
18533
19028
  import fs15 from "node:fs";
18534
19029
  import path25 from "node:path";
18535
- import { spawnSync as spawnSync13 } from "node:child_process";
19030
+ import { spawnSync as spawnSync14 } from "node:child_process";
18536
19031
 
18537
19032
  // packages/workit-core/src/core/workspace-branch-policy.ts
18538
19033
  import { mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
@@ -18540,7 +19035,7 @@ import path24 from "node:path";
18540
19035
  import { isDeepStrictEqual } from "node:util";
18541
19036
 
18542
19037
  // packages/workit-core/src/core/branch-policy.ts
18543
- import { spawnSync as spawnSync12 } from "node:child_process";
19038
+ import { spawnSync as spawnSync13 } from "node:child_process";
18544
19039
  var PREFIXES2 = {
18545
19040
  feature: "feature/*",
18546
19041
  bugfix: "bugfix/*",
@@ -18549,7 +19044,7 @@ var PREFIXES2 = {
18549
19044
  };
18550
19045
  var detectBranchPolicy = (workspaceRoot) => {
18551
19046
  const branchExists = (name) => {
18552
- const r = spawnSync12("git", ["branch", "--list", name], {
19047
+ const r = spawnSync13("git", ["branch", "--list", name], {
18553
19048
  cwd: workspaceRoot,
18554
19049
  encoding: "utf8"
18555
19050
  });
@@ -18717,7 +19212,7 @@ var resolveInitProvider = (cwd) => {
18717
19212
  return env.toLowerCase();
18718
19213
  const root = cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
18719
19214
  try {
18720
- const out = spawnSync13("git", ["remote", "get-url", "origin"], {
19215
+ const out = spawnSync14("git", ["remote", "get-url", "origin"], {
18721
19216
  cwd: root,
18722
19217
  encoding: "utf8"
18723
19218
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "private": false,
5
5
  "description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@brainervirus/workit-core": "^7.0.0"
42
+ "@brainervirus/workit-core": "^7.1.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode/plugin": "2.0.18"