@brainervirus/workit-mcp 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/index.js +496 -61
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -16574,7 +16574,7 @@ class StdioServerTransport {
16574
16574
  import { fileURLToPath as fileURLToPath2 } from "node:url";
16575
16575
 
16576
16576
  // packages/workit-core/src/core.ts
16577
- import { spawnSync as spawnSync11 } from "node:child_process";
16577
+ import { spawnSync as spawnSync12 } from "node:child_process";
16578
16578
  import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
16579
16579
  import path19 from "node:path";
16580
16580
 
@@ -21260,6 +21260,11 @@ function worktreeSignal(cwd, options = {}) {
21260
21260
  }
21261
21261
  return `sig:${hash.digest("hex")}`;
21262
21262
  }
21263
+ function mergeBase(cwd, a, b) {
21264
+ if (!safeArg(a) || !safeArg(b))
21265
+ return null;
21266
+ return line(git2(cwd, ["merge-base", a, b]));
21267
+ }
21263
21268
  function patchId(cwd, base, head) {
21264
21269
  if (!safeArg(base) || !safeArg(head))
21265
21270
  return null;
@@ -21283,9 +21288,33 @@ function patchId(cwd, base, head) {
21283
21288
  const id = git2(cwd, ["patch-id", "--stable"], { input: diff.stdout });
21284
21289
  return line(id)?.split(/\s+/u)[0] ?? null;
21285
21290
  }
21291
+ function remoteNames(cwd) {
21292
+ return (git2(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
21293
+ }
21294
+ function resolveRef(cwd, ref) {
21295
+ if (!safeArg(ref))
21296
+ return null;
21297
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
21298
+ }
21299
+ var config2 = (cwd, key) => line(git2(cwd, ["config", "--get", key]));
21286
21300
  function currentBranch(cwd) {
21287
21301
  return line(git2(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
21288
21302
  }
21303
+ function pushRemoteName(cwd, branch) {
21304
+ const name = branch ?? currentBranch(cwd);
21305
+ const remotes = remoteNames(cwd);
21306
+ const candidates = [
21307
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
21308
+ config2(cwd, "remote.pushDefault"),
21309
+ name ? config2(cwd, `branch.${name}.remote`) : null
21310
+ ];
21311
+ for (const candidate of candidates)
21312
+ if (candidate && candidate !== "." && remotes.includes(candidate))
21313
+ return candidate;
21314
+ if (remotes.includes("origin"))
21315
+ return "origin";
21316
+ return remotes.length === 1 ? remotes[0] : null;
21317
+ }
21289
21318
 
21290
21319
  // packages/workit-core/src/core/policy/derive.ts
21291
21320
  import { existsSync as existsSync3 } from "node:fs";
@@ -22899,6 +22928,8 @@ var readConfig = () => {
22899
22928
  throw new Error(result.error);
22900
22929
  return result.config ?? DEFAULTS;
22901
22930
  };
22931
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path12.join(configDir(), "vcs.json");
22932
+ var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
22902
22933
  var resolveBranchPolicy = (config, workspace) => {
22903
22934
  const wp = workspace?.branchPolicy ?? {};
22904
22935
  const preset = Object.hasOwn(PRESETS, wp.preset) ? wp.preset : config.branchPolicy?.preset ?? "gitflow";
@@ -22906,7 +22937,7 @@ var resolveBranchPolicy = (config, workspace) => {
22906
22937
  allowed: wp.allowed,
22907
22938
  protectedNames: wp.protected
22908
22939
  }, config);
22909
- const allowed = merged.allowed.map((p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"));
22940
+ const allowed = merged.allowed.map(branchGlobPattern);
22910
22941
  return {
22911
22942
  preset,
22912
22943
  allowed,
@@ -22923,16 +22954,6 @@ var COMMIT_PRESETS = [
22923
22954
  "custom",
22924
22955
  "auto"
22925
22956
  ];
22926
- var resolveCommitPolicy = (config, workspace) => {
22927
- const wp = workspace?.commitPolicy ?? {};
22928
- if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
22929
- return {
22930
- preset: wp.preset,
22931
- ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
22932
- };
22933
- }
22934
- return config.commitPolicy ?? { preset: "conventional" };
22935
- };
22936
22957
 
22937
22958
  // packages/workit-core/src/core/workspaces.ts
22938
22959
  import { createHash as createHash8 } from "node:crypto";
@@ -22971,26 +22992,31 @@ var versionSourceSchema = discriminatedUnion("kind", [
22971
22992
  object2({ kind: literal("manual") }).strict()
22972
22993
  ]);
22973
22994
  var releaseTrackSchema = object2({
22974
- strategy: branchPreset,
22995
+ strategy: branchPreset.optional(),
22975
22996
  productionBranch: nonBlank,
22976
22997
  integrationBranch: nonBlank,
22977
- naming: object2({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
22978
- baseBranch: nonBlank,
22979
- mergeBackBranches: array(nonBlank),
22980
- pullRequestTarget: nonBlank,
22981
- tagNamespace: string2(),
22982
- versionSource: versionSourceSchema,
22983
- requiredChecks: array(nonBlank)
22984
- }).strict().superRefine((track, context) => {
22998
+ naming: object2({
22999
+ feature: nonBlank.optional(),
23000
+ release: nonBlank.optional(),
23001
+ hotfix: nonBlank.optional()
23002
+ }).passthrough().optional(),
23003
+ baseBranch: nonBlank.optional(),
23004
+ mergeBackBranches: array(nonBlank).optional(),
23005
+ pullRequestTarget: nonBlank.optional(),
23006
+ tagNamespace: string2().optional(),
23007
+ versionSource: versionSourceSchema.optional(),
23008
+ requiredChecks: array(nonBlank).optional(),
23009
+ critical: array(nonBlank).optional()
23010
+ }).passthrough().superRefine((track, context) => {
22985
23011
  for (const [field, values] of [
22986
- ["mergeBackBranches", track.mergeBackBranches],
22987
- ["requiredChecks", track.requiredChecks]
23012
+ ["mergeBackBranches", track.mergeBackBranches ?? []],
23013
+ ["requiredChecks", track.requiredChecks ?? []]
22988
23014
  ]) {
22989
23015
  if (new Set(values).size !== values.length) {
22990
23016
  context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
22991
23017
  }
22992
23018
  }
22993
- 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(".."))) {
23019
+ 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(".."))) {
22994
23020
  context.addIssue({
22995
23021
  code: "custom",
22996
23022
  path: ["versionSource", "path"],
@@ -23258,7 +23284,7 @@ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd,
23258
23284
  var runtimeWorkspaceIndexSchema = object2({
23259
23285
  workspaces: array(object2({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
23260
23286
  }).passthrough();
23261
- var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23287
+ var matchRuntimeWorkspace = (cwd) => {
23262
23288
  const file = path13.join(configDir(), "workspaces.json");
23263
23289
  let raw;
23264
23290
  try {
@@ -23293,8 +23319,17 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23293
23319
  });
23294
23320
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
23295
23321
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
23296
- if (!selected)
23322
+ return selected ? { file, selected } : null;
23323
+ };
23324
+ var resolveRuntimeReleaseTracks = (cwd) => {
23325
+ const matched = matchRuntimeWorkspace(cwd);
23326
+ return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
23327
+ };
23328
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23329
+ const matched = matchRuntimeWorkspace(cwd);
23330
+ if (!matched)
23297
23331
  return null;
23332
+ const { file, selected } = matched;
23298
23333
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
23299
23334
  if (!policy.success) {
23300
23335
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
@@ -23320,15 +23355,410 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
23320
23355
  source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
23321
23356
  };
23322
23357
  };
23358
+ function workspaceDefaultTarget(cwd, globalDefault) {
23359
+ const ws = resolveRuntimeWorkspaceVcs(cwd);
23360
+ const wp = ws?.branchPolicy ?? {};
23361
+ const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
23362
+ const selected = resolveRuntimeWorkspacePolicy(cwd, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined);
23363
+ const policyDefault = resolveBranchPolicy(readConfig(), selected.policy ? { branchPolicy: selected.policy } : null).defaultTargetBranch;
23364
+ return String(ws?.vcs?.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : globalDefault ?? policyDefault) ?? "develop");
23365
+ }
23323
23366
 
23324
23367
  // packages/workit-core/src/core/vcs-config.ts
23325
23368
  import fs12 from "node:fs";
23326
23369
  import path14 from "node:path";
23370
+ import { spawnSync as spawnSync8 } from "node:child_process";
23371
+
23372
+ // packages/workit-core/src/core/release-tracks.ts
23373
+ import { createHash as createHash9 } from "node:crypto";
23327
23374
  import { spawnSync as spawnSync7 } from "node:child_process";
23328
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path14.join(configDir(), "vcs.json");
23375
+ var KNOWN_FIELDS = new Set([
23376
+ "strategy",
23377
+ "productionBranch",
23378
+ "integrationBranch",
23379
+ "naming",
23380
+ "baseBranch",
23381
+ "mergeBackBranches",
23382
+ "pullRequestTarget",
23383
+ "tagNamespace",
23384
+ "versionSource",
23385
+ "requiredChecks",
23386
+ "critical"
23387
+ ]);
23388
+ var BRANCH_FIELDS = ["productionBranch", "integrationBranch", "baseBranch", "pullRequestTarget"];
23389
+ var PLACEHOLDER = /\{[^}]*\}/gu;
23390
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23391
+ var branchName = (value) => typeof value === "string" && value.trim() && !value.trim().startsWith("-") ? value.trim() : null;
23392
+ var namedBranches = (entry) => [
23393
+ ...BRANCH_FIELDS.map((field) => branchName(entry[field])),
23394
+ ...Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : []
23395
+ ].filter((name) => name !== null);
23396
+ var literalOf = (template) => template.replace(PLACEHOLDER, "").replace(/[*/]/gu, "").trim();
23397
+ function readReleaseTracks(raw) {
23398
+ const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
23399
+ if (raw === undefined || raw === null)
23400
+ return out;
23401
+ if (!isRecord3(raw)) {
23402
+ out.issues.push("releaseTracks is not an object; ignored");
23403
+ return out;
23404
+ }
23405
+ const fatal = [];
23406
+ const protectedNames = new Set;
23407
+ for (const [name, entry] of Object.entries(raw).toSorted(([a], [b]) => a < b ? -1 : 1)) {
23408
+ const label = `release track ${JSON.stringify(name)}`;
23409
+ if (!isRecord3(entry)) {
23410
+ out.issues.push(`${label} is not an object; ignored`);
23411
+ continue;
23412
+ }
23413
+ for (const branch of namedBranches(entry))
23414
+ protectedNames.add(branch);
23415
+ if (!name.trim()) {
23416
+ out.issues.push("a release track with a blank name was ignored");
23417
+ continue;
23418
+ }
23419
+ let critical = [];
23420
+ if (Array.isArray(entry.critical))
23421
+ critical = entry.critical.filter((item) => typeof item === "string");
23422
+ else if (entry.critical !== undefined)
23423
+ out.issues.push(`${label} critical: must be a list of field names; ignored`);
23424
+ const isCritical = (field) => critical.some((item) => field === item || field.startsWith(`${item}.`));
23425
+ const problem = (field, message) => {
23426
+ if (isCritical(field))
23427
+ fatal.push(`${label} ${field}: ${message}`);
23428
+ else
23429
+ out.issues.push(`${label} ${field}: ${message}`);
23430
+ };
23431
+ for (const key of Object.keys(entry))
23432
+ if (!KNOWN_FIELDS.has(key))
23433
+ problem(key, "unknown field, ignored");
23434
+ const productionBranch = branchName(entry.productionBranch);
23435
+ const integrationBranch = branchName(entry.integrationBranch);
23436
+ if (!productionBranch)
23437
+ problem("productionBranch", "missing or not a branch name");
23438
+ if (!integrationBranch)
23439
+ problem("integrationBranch", "missing or not a branch name");
23440
+ const optionalBranch = (field) => {
23441
+ if (entry[field] === undefined)
23442
+ return null;
23443
+ const value = branchName(entry[field]);
23444
+ if (!value)
23445
+ problem(field, "not a branch name; using the integration branch");
23446
+ return value;
23447
+ };
23448
+ const baseBranch = optionalBranch("baseBranch");
23449
+ const pullRequestTarget = optionalBranch("pullRequestTarget");
23450
+ let mergeBackBranches = [];
23451
+ if (entry.mergeBackBranches !== undefined) {
23452
+ const list = Array.isArray(entry.mergeBackBranches) ? entry.mergeBackBranches.map(branchName) : [null];
23453
+ if (list.some((item) => item === null))
23454
+ problem("mergeBackBranches", "must be a list of branch names; ignored");
23455
+ else
23456
+ mergeBackBranches = [...new Set(list)];
23457
+ }
23458
+ const naming = {};
23459
+ if (entry.naming !== undefined) {
23460
+ if (!isRecord3(entry.naming))
23461
+ problem("naming", "not an object; ignored");
23462
+ else
23463
+ for (const kind of ["feature", "release", "hotfix"]) {
23464
+ const template = entry.naming[kind];
23465
+ if (template === undefined)
23466
+ continue;
23467
+ if (typeof template !== "string" || !template.trim())
23468
+ problem(`naming.${kind}`, "not a template string; ignored");
23469
+ else if (!literalOf(template))
23470
+ problem(`naming.${kind}`, "needs literal text besides placeholders; ignored");
23471
+ else
23472
+ naming[kind] = template.trim();
23473
+ }
23474
+ }
23475
+ if (!productionBranch || !integrationBranch) {
23476
+ out.issues.push(`${label} was skipped: it needs a productionBranch and integrationBranch`);
23477
+ continue;
23478
+ }
23479
+ out.tracks.push({
23480
+ name,
23481
+ productionBranch,
23482
+ integrationBranch,
23483
+ baseBranch: baseBranch ?? integrationBranch,
23484
+ pullRequestTarget: pullRequestTarget ?? integrationBranch,
23485
+ mergeBackBranches,
23486
+ naming
23487
+ });
23488
+ }
23489
+ out.protectedBranches = [...protectedNames];
23490
+ if (fatal.length)
23491
+ out.error = `workspace releaseTracks cannot be read by this Workit (${fatal.join("; ")}); upgrade Workit or fix the track`;
23492
+ return out;
23493
+ }
23494
+ var IMPLICIT = "";
23495
+ var ownedBy = (tracks, branch) => tracks.filter((track) => [track.integrationBranch, track.productionBranch, track.baseBranch].includes(branch));
23496
+ var shortRef = (ref, remotes) => {
23497
+ const bare = ref.replace(/^refs\/heads\//u, "").replace(/^refs\/remotes\/[^/]+\//u, "");
23498
+ const remote = remotes.find((name) => bare.startsWith(`${name}/`));
23499
+ return remote ? bare.slice(remote.length + 1) : bare;
23500
+ };
23501
+ var MAX_RECORDED_HOPS = 10;
23502
+ function resolveReleaseTrack(input) {
23503
+ const tracks = [...input.tracks];
23504
+ const issues = [...input.issues ?? []];
23505
+ const requested = input.requested?.trim();
23506
+ if (tracks.length === 0) {
23507
+ if (requested)
23508
+ return {
23509
+ status: "invalid",
23510
+ error: `no release tracks configured for this workspace; --track ${requested} cannot apply`,
23511
+ issues
23512
+ };
23513
+ return { status: "none", issues };
23514
+ }
23515
+ const names = tracks.map((track) => track.name).join(", ");
23516
+ let envProblem = null;
23517
+ const done = (track, source, detail, warnings = [], ambiguous = null) => ({
23518
+ status: "resolved",
23519
+ track: track && track.name !== IMPLICIT ? track : null,
23520
+ source,
23521
+ detail,
23522
+ warnings: [...issues, ...envProblem ? [envProblem] : [], ...warnings],
23523
+ tracks,
23524
+ blocking: envProblem ?? ambiguous
23525
+ });
23526
+ if (requested) {
23527
+ const named = tracks.find((track) => track.name === requested);
23528
+ if (!named)
23529
+ return {
23530
+ status: "invalid",
23531
+ error: `no release track ${JSON.stringify(requested)}; configured: ${names}`,
23532
+ issues
23533
+ };
23534
+ return done(named, "flag", `track ${named.name} was requested explicitly`);
23535
+ }
23536
+ const owned = (branch) => branch !== null && tracks.some((track) => [
23537
+ track.integrationBranch,
23538
+ track.productionBranch,
23539
+ track.baseBranch,
23540
+ track.pullRequestTarget
23541
+ ].includes(branch));
23542
+ const implicit = input.defaultBranch && !owned(input.defaultBranch) ? {
23543
+ name: IMPLICIT,
23544
+ productionBranch: input.defaultBranch,
23545
+ integrationBranch: input.defaultBranch,
23546
+ baseBranch: input.defaultBranch,
23547
+ pullRequestTarget: input.defaultBranch,
23548
+ mergeBackBranches: [],
23549
+ naming: {}
23550
+ } : null;
23551
+ const candidates = implicit ? [...tracks, implicit] : tracks;
23552
+ const label = (track) => track.name === IMPLICIT ? `the workspace default line (${track.integrationBranch})` : `track ${track.name}`;
23553
+ const fallback = (why) => {
23554
+ const byDefault = input.defaultBranch ? tracks.filter((track) => track.integrationBranch === input.defaultBranch || track.pullRequestTarget === input.defaultBranch) : [];
23555
+ const track = byDefault.length === 1 ? byDefault[0] : null;
23556
+ const target = track ? `track ${track.name}` : `default target ${input.defaultBranch ?? "(none)"}`;
23557
+ 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;
23558
+ return done(track, "default", `${why}; using the workspace ${target}`, [
23559
+ `release track not determined: ${why}; using the workspace ${target} (pass --track <name> to choose)`
23560
+ ], ambiguous);
23561
+ };
23562
+ const branch = input.branch;
23563
+ if (branch) {
23564
+ const owners = ownedBy(candidates, branch);
23565
+ if (owners.length === 1)
23566
+ return done(owners[0], "branch", `${branch} is a branch of ${label(owners[0])}`);
23567
+ if (owners.length > 1)
23568
+ return fallback(`${branch} belongs to more than one track (${owners.map((track) => track.name).join(", ")})`);
23569
+ }
23570
+ const env = input.envRequested?.trim();
23571
+ if (env) {
23572
+ const named = tracks.find((track) => track.name === env);
23573
+ if (named)
23574
+ return done(named, "env", `WORKFLOW_RELEASE_TRACK selects track ${named.name}`);
23575
+ envProblem = `WORKFLOW_RELEASE_TRACK=${env} names no release track of this workspace (${names}); ignored`;
23576
+ }
23577
+ if (!branch)
23578
+ return fallback("HEAD is detached");
23579
+ if (tracks.length === 1 && !implicit)
23580
+ return done(tracks[0], "only-track", `${tracks[0].name} is the only track`);
23581
+ let remotes = null;
23582
+ const short = (ref) => shortRef(ref, remotes ??= input.probe.remotes());
23583
+ const ownerOf = (ref) => {
23584
+ const owner = ownedBy(candidates, short(ref));
23585
+ return owner.length === 1 ? owner[0] : null;
23586
+ };
23587
+ const followRecorded = (start) => {
23588
+ let cursor = start;
23589
+ const seen = new Set([start]);
23590
+ for (let hop = 0;hop < MAX_RECORDED_HOPS; hop += 1) {
23591
+ const recorded = input.probe.recordedBase(cursor);
23592
+ if (!recorded)
23593
+ return null;
23594
+ const base = short(recorded);
23595
+ const owner = ownerOf(base);
23596
+ if (owner)
23597
+ return owner;
23598
+ if (ownedBy(candidates, base).length > 1 || seen.has(base))
23599
+ return null;
23600
+ seen.add(base);
23601
+ cursor = base;
23602
+ }
23603
+ return null;
23604
+ };
23605
+ const recordedOwner = followRecorded(branch);
23606
+ if (recordedOwner)
23607
+ return done(recordedOwner, "recorded-base", `${branch} was created on ${label(recordedOwner)}'s line (recorded base)`);
23608
+ for (const source of ["reflog", "checkout"]) {
23609
+ const ref = source === "reflog" ? input.probe.createdFrom(branch) : input.probe.checkedOutFrom(branch);
23610
+ if (!ref)
23611
+ continue;
23612
+ const owner = ownerOf(ref) ?? followRecorded(short(ref));
23613
+ if (owner)
23614
+ return done(owner, source, `${branch} was created from ${short(ref)} (${source === "reflog" ? "branch reflog" : "HEAD reflog"})`);
23615
+ }
23616
+ const forks = [];
23617
+ for (const track of candidates)
23618
+ for (const ref of new Set([track.integrationBranch, track.baseBranch])) {
23619
+ const sha = input.probe.mergeBase(branch, ref);
23620
+ if (sha)
23621
+ forks.push({ track, ref, sha });
23622
+ }
23623
+ if (forks.length === 0)
23624
+ return fallback(`no track branch could be compared with ${branch} (fetch the track branches)`);
23625
+ const ancestry = new Map;
23626
+ const descends = (older, newer) => {
23627
+ if (older === newer)
23628
+ return false;
23629
+ const key = `${older}>${newer}`;
23630
+ if (!ancestry.has(key))
23631
+ ancestry.set(key, input.probe.isAncestor(older, newer));
23632
+ return ancestry.get(key);
23633
+ };
23634
+ const winners = forks.filter((fork) => forks.every((other) => other.track === fork.track || descends(other.sha, fork.sha)));
23635
+ const winningTracks = [...new Set(winners.map((fork) => fork.track))];
23636
+ if (winningTracks.length === 1 && forks.some((fork) => fork.track !== winningTracks[0])) {
23637
+ const win = winners[0];
23638
+ return done(win.track, "ancestry", `${branch} forked from ${win.ref} at ${win.sha.slice(0, 12)}, after every other line's fork point`);
23639
+ }
23640
+ if (winningTracks.length === 1)
23641
+ return fallback(`only ${label(winningTracks[0])} could be compared with ${branch}`);
23642
+ const newest = forks.filter((fork) => !forks.some((other) => descends(fork.sha, other.sha)));
23643
+ 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 ")}`);
23644
+ }
23645
+ var git4 = (cwd, args) => {
23646
+ const run = spawnSync7("git", args, {
23647
+ cwd,
23648
+ encoding: "utf8",
23649
+ timeout: 20000,
23650
+ windowsHide: true,
23651
+ stdio: ["ignore", "pipe", "ignore"]
23652
+ });
23653
+ return run.status === 0 ? (run.stdout ?? "").trim() : null;
23654
+ };
23655
+ var recordedBaseKey = (branch) => `branch.${branch}.workitBase`;
23656
+ var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
23657
+ var reflog = (cwd, ref) => (git4(cwd, ["log", "-g", "--date=unix", "--format=%H%x09%gd%x09%gs", ref, "--"]) ?? "").split(`
23658
+ `).map((line) => {
23659
+ const [sha = "", selector = "", ...subject] = line.split("\t");
23660
+ return {
23661
+ sha,
23662
+ time: Number(/@\{(\d+)\}$/u.exec(selector)?.[1] ?? Number.NaN),
23663
+ subject: subject.join("\t")
23664
+ };
23665
+ }).filter((entry) => entry.sha && Number.isFinite(entry.time));
23666
+ function gitTrackProbe(cwd) {
23667
+ let remote;
23668
+ let remotes;
23669
+ const tip = (branch) => {
23670
+ remote ??= pushRemoteName(cwd) ?? "origin";
23671
+ return resolveRef(cwd, `refs/remotes/${remote}/${branch}`) ?? resolveRef(cwd, `refs/heads/${branch}`);
23672
+ };
23673
+ return {
23674
+ remotes: () => remotes ??= remoteNames(cwd),
23675
+ recordedBase: (branch) => git4(cwd, ["config", "--get", recordedBaseKey(branch)]) || null,
23676
+ createdFrom: (branch) => {
23677
+ const log = git4(cwd, ["reflog", "show", "--format=%gs", `refs/heads/${branch}`, "--"]);
23678
+ const oldest = log?.split(`
23679
+ `).findLast(Boolean) ?? "";
23680
+ const match = /^branch: Created from (\S+)$/u.exec(oldest);
23681
+ return match && match[1] !== "HEAD" ? match[1] : null;
23682
+ },
23683
+ checkedOutFrom: (branch) => {
23684
+ const own = reflog(cwd, `refs/heads/${branch}`);
23685
+ const created = own.at(-1);
23686
+ if (!created || created.subject !== "branch: Created from HEAD")
23687
+ return null;
23688
+ const pattern = new RegExp(`^checkout: moving from (\\S+) to ${escapeRegExp(branch)}$`, "u");
23689
+ const entry = reflog(cwd, "HEAD").findLast((item) => item.time >= created.time && item.sha === created.sha && pattern.test(item.subject));
23690
+ return entry ? pattern.exec(entry.subject)?.[1] ?? null : null;
23691
+ },
23692
+ mergeBase: (branch, trackBranch) => {
23693
+ const base = tip(trackBranch);
23694
+ return base && resolveRef(cwd, `refs/heads/${branch}`) ? mergeBase(cwd, base, `refs/heads/${branch}`) : null;
23695
+ },
23696
+ isAncestor: (ancestor, descendant) => git4(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]) !== null
23697
+ };
23698
+ }
23699
+ function workspaceReleaseTracks(cwd) {
23700
+ const raw = resolveRuntimeReleaseTracks(cwd);
23701
+ return { workspace: raw?.workspace ?? null, ...readReleaseTracks(raw?.releaseTracks) };
23702
+ }
23703
+ var memo3 = new Map;
23704
+ var MEMO_LIMIT = 64;
23705
+ function resolveTrackFor(cwd, options) {
23706
+ const read = workspaceReleaseTracks(cwd);
23707
+ if (read.error)
23708
+ return { status: "invalid", error: read.error, issues: read.issues };
23709
+ if (read.tracks.length === 0 && !options.track?.trim())
23710
+ return { status: "none", issues: read.issues };
23711
+ const branch = options.branch === undefined ? currentBranch(cwd) : options.branch;
23712
+ const envRequested = process.env.WORKFLOW_RELEASE_TRACK?.trim() || null;
23713
+ const input = {
23714
+ tracks: read.tracks,
23715
+ requested: options.track ?? null,
23716
+ envRequested,
23717
+ branch,
23718
+ defaultBranch: options.defaultBranch,
23719
+ issues: read.issues
23720
+ };
23721
+ let key = null;
23722
+ if (!options.probe) {
23723
+ const refs = git4(cwd, [
23724
+ "for-each-ref",
23725
+ "--format=%(refname) %(objectname)",
23726
+ "refs/heads",
23727
+ "refs/remotes"
23728
+ ]) ?? "";
23729
+ const head = git4(cwd, ["reflog", "show", "-n", "1", "--format=%H %gs", "HEAD", "--"]) ?? "";
23730
+ const own = branch ? [
23731
+ git4(cwd, [
23732
+ "log",
23733
+ "-g",
23734
+ "-n",
23735
+ "1",
23736
+ "--date=unix",
23737
+ "--format=%H %gd %gs",
23738
+ `refs/heads/${branch}`,
23739
+ "--"
23740
+ ]) ?? "",
23741
+ git4(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
23742
+ ] : [];
23743
+ key = createHash9("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
23744
+ const hit = memo3.get(key);
23745
+ if (hit)
23746
+ return hit;
23747
+ }
23748
+ const resolution = resolveReleaseTrack({ ...input, probe: options.probe ?? gitTrackProbe(cwd) });
23749
+ if (key) {
23750
+ if (memo3.size >= MEMO_LIMIT)
23751
+ memo3.delete(memo3.keys().next().value);
23752
+ memo3.set(key, resolution);
23753
+ }
23754
+ return resolution;
23755
+ }
23756
+
23757
+ // packages/workit-core/src/core/vcs-config.ts
23758
+ var workspacesPath = () => path14.join(configDir(), "workspaces.json");
23329
23759
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
23330
23760
  var remoteProvider = (cwd) => {
23331
- const r = spawnSync7("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
23761
+ const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
23332
23762
  if (r.status !== 0)
23333
23763
  return null;
23334
23764
  const url = (r.stdout ?? "").trim();
@@ -23365,7 +23795,7 @@ var remoteEndpoint = (remote) => {
23365
23795
  var configuredSshEndpoint = (host) => {
23366
23796
  if (host.startsWith("-"))
23367
23797
  return null;
23368
- const resolved = spawnSync7("ssh", ["-G", host], {
23798
+ const resolved = spawnSync8("ssh", ["-G", host], {
23369
23799
  encoding: "utf8",
23370
23800
  timeout: 5000,
23371
23801
  maxBuffer: 1024 * 1024,
@@ -23430,7 +23860,16 @@ var readVcsConfig = () => {
23430
23860
  }
23431
23861
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
23432
23862
  };
23433
- function vcsConfig(mode, cwd) {
23863
+ var trackSummary = (resolution) => resolution.status === "resolved" ? {
23864
+ name: resolution.track?.name ?? null,
23865
+ source: resolution.source,
23866
+ detail: resolution.detail,
23867
+ warnings: resolution.warnings,
23868
+ track: resolution.track,
23869
+ tracks: resolution.tracks,
23870
+ blocking: resolution.blocking
23871
+ } : null;
23872
+ function vcsConfig(mode, cwd, options = {}) {
23434
23873
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
23435
23874
  const wsVcs = ws?.vcs ?? {};
23436
23875
  const wsYt = ws?.youtrack ?? {};
@@ -23440,10 +23879,7 @@ function vcsConfig(mode, cwd) {
23440
23879
  const determinateRoot = cwd !== undefined || process.env.WORKFLOW_WORKSPACE_ROOT !== undefined;
23441
23880
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
23442
23881
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
23443
- const wp = ws?.branchPolicy ?? {};
23444
- const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
23445
- const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
23446
- const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
23882
+ const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
23447
23883
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
23448
23884
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
23449
23885
  let issuesProvider = null;
@@ -23455,11 +23891,24 @@ function vcsConfig(mode, cwd) {
23455
23891
  if (mode === "resolve") {
23456
23892
  if (cfgStatus === "malformed")
23457
23893
  return { ok: false, error: cfgError, configPath: cfgPath };
23894
+ const trackResolution = resolveTrackFor(root, {
23895
+ track: options.track,
23896
+ ...options.branch !== undefined ? { branch: options.branch } : {},
23897
+ defaultBranch: workspaceDefault
23898
+ });
23899
+ if (trackResolution.status === "invalid")
23900
+ return { ok: false, error: trackResolution.error, configPath: workspacesPath() };
23901
+ const releaseTrack = trackSummary(trackResolution);
23902
+ const defaultTarget = releaseTrack?.track?.pullRequestTarget ?? workspaceDefault;
23903
+ const baseBranch = releaseTrack?.track?.baseBranch ?? defaultTarget;
23458
23904
  return {
23459
23905
  ok: true,
23460
23906
  workspace_name: ws?.name ?? null,
23461
23907
  provider,
23462
23908
  defaultTargetBranch: defaultTarget,
23909
+ workspaceDefaultTargetBranch: workspaceDefault,
23910
+ baseBranch,
23911
+ releaseTrack,
23463
23912
  link_issues: linkIssues,
23464
23913
  youtrack_base_url: youtrackBaseUrl,
23465
23914
  issues_provider: issuesProvider,
@@ -23483,7 +23932,7 @@ function vcsConfig(mode, cwd) {
23483
23932
  ok: true,
23484
23933
  configPath: path14.resolve(cfgPath),
23485
23934
  provider,
23486
- defaultTargetBranch: defaultTarget,
23935
+ defaultTargetBranch: workspaceDefault,
23487
23936
  pr: cfg.pr ?? {},
23488
23937
  workspace_name: ws?.name ?? null,
23489
23938
  link_issues: linkIssues,
@@ -23519,7 +23968,7 @@ function mergedPrStyle(limit = 6, cwd) {
23519
23968
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
23520
23969
  descriptionPreview: desc.slice(0, 600)
23521
23970
  });
23522
- const remoteResult = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
23971
+ const remoteResult = spawnSync8("git", ["remote", "get-url", "--push", "--all", "origin"], {
23523
23972
  cwd,
23524
23973
  encoding: "utf8"
23525
23974
  });
@@ -23535,7 +23984,7 @@ function mergedPrStyle(limit = 6, cwd) {
23535
23984
  if (!hostingApiHostMatches(remote, host))
23536
23985
  return { ok: false, error: "PR style target does not match the provider host" };
23537
23986
  if (provider === "gitlab") {
23538
- const run = (args) => spawnSync7("glab", ["api", ...args], {
23987
+ const run = (args) => spawnSync8("glab", ["api", ...args], {
23539
23988
  cwd,
23540
23989
  encoding: "utf8",
23541
23990
  env
@@ -23555,7 +24004,7 @@ function mergedPrStyle(limit = 6, cwd) {
23555
24004
  });
23556
24005
  }
23557
24006
  } else if (provider === "github") {
23558
- const r = spawnSync7("gh", [
24007
+ const r = spawnSync8("gh", [
23559
24008
  "pr",
23560
24009
  "list",
23561
24010
  "--repo",
@@ -23586,20 +24035,6 @@ function mergedPrStyle(limit = 6, cwd) {
23586
24035
  examples
23587
24036
  };
23588
24037
  }
23589
-
23590
- // packages/workit-core/src/core/branch.ts
23591
- function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
23592
- const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
23593
- const config = readConfig();
23594
- return kind === "branch" ? {
23595
- branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
23596
- provenance: { branchPolicy: selected.source }
23597
- } : {
23598
- commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
23599
- provenance: { commitPolicy: selected.source }
23600
- };
23601
- }
23602
- var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
23603
24038
  // packages/workit-core/src/core/task-engine.ts
23604
24039
  import { realpathSync as realpathSync4 } from "node:fs";
23605
24040
 
@@ -25453,7 +25888,7 @@ class WorkitCore {
25453
25888
  }
25454
25889
  }
25455
25890
  // packages/workit-core/src/core/repo-context.ts
25456
- import { spawnSync as spawnSync8 } from "node:child_process";
25891
+ import { spawnSync as spawnSync9 } from "node:child_process";
25457
25892
  import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4 } from "node:fs";
25458
25893
  import path16 from "node:path";
25459
25894
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
@@ -25482,7 +25917,7 @@ var invalidRange = (cwd) => ({
25482
25917
  cwd
25483
25918
  });
25484
25919
  var runGit = (cwd, args) => {
25485
- const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
25920
+ const result = spawnSync9("git", args, { cwd, encoding: "utf8" });
25486
25921
  return {
25487
25922
  stdout: (result.stdout ?? "").trimEnd(),
25488
25923
  stderr: (result.stderr ?? "").trimEnd(),
@@ -25923,12 +26358,12 @@ function releaseNotesContext(root, rangeOrTag) {
25923
26358
  }
25924
26359
 
25925
26360
  // packages/workit-core/src/core/tracker-issues.ts
25926
- import { spawnSync as spawnSync10 } from "node:child_process";
26361
+ import { spawnSync as spawnSync11 } from "node:child_process";
25927
26362
 
25928
26363
  // packages/workit-core/src/core/pr-create.ts
25929
26364
  import fs13 from "node:fs";
25930
26365
  import path17 from "node:path";
25931
- import { spawnSync as spawnSync9 } from "node:child_process";
26366
+ import { spawnSync as spawnSync10 } from "node:child_process";
25932
26367
  // packages/workit-core/src/forge/pr-body.ts
25933
26368
  function parseGhIssue(value) {
25934
26369
  const m = /issues\/(\d+)/.exec(value);
@@ -25953,7 +26388,7 @@ function whichOnPath(tool) {
25953
26388
  }
25954
26389
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
25955
26390
  var pushRemote = (cwd) => {
25956
- const result = spawnSync9("git", ["remote", "get-url", "--push", "--all", "origin"], {
26391
+ const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
25957
26392
  cwd,
25958
26393
  encoding: "utf8"
25959
26394
  });
@@ -25984,7 +26419,7 @@ var defaultCli = (bin, available) => {
25984
26419
  if (!available)
25985
26420
  return null;
25986
26421
  return (args, root) => {
25987
- const out = spawnSync10(bin, args, {
26422
+ const out = spawnSync11(bin, args, {
25988
26423
  cwd: root,
25989
26424
  encoding: "utf8",
25990
26425
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -26502,7 +26937,7 @@ function resolveInside(root, candidate) {
26502
26937
  }
26503
26938
  function run2(root, executable, args, env = {}) {
26504
26939
  const cwd = realpathSync5(root);
26505
- const result = spawnSync11(executable, args, {
26940
+ const result = spawnSync12(executable, args, {
26506
26941
  cwd,
26507
26942
  encoding: "utf8",
26508
26943
  env: { ...process.env, ...env }
@@ -26558,8 +26993,8 @@ var SENSITIVE_WORDS = new Set([
26558
26993
  "query"
26559
26994
  ]);
26560
26995
  var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
26561
- var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26562
- var homePattern = new RegExp(`${escapeRegExp(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
26996
+ var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26997
+ var homePattern = new RegExp(`${escapeRegExp2(os4.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
26563
26998
  var applyPatterns = (value) => {
26564
26999
  let out = value;
26565
27000
  out = out.replace(SECRET_VALUE, REDACTED);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-mcp",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "private": false,
5
5
  "description": "Workit shared MCP transport for the eight task and policy operation families",
6
6
  "keywords": [
@@ -42,7 +42,7 @@
42
42
  "build": "bun scripts/build.ts"
43
43
  },
44
44
  "dependencies": {
45
- "@brainervirus/workit-core": "^7.0.0",
45
+ "@brainervirus/workit-core": "^7.1.0",
46
46
  "@modelcontextprotocol/sdk": "1.32.0",
47
47
  "zod": "4.6.5"
48
48
  },