@brainervirus/workit-codex 1.2.1 → 1.3.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.
@@ -6621,13 +6621,13 @@ var require_dist = __commonJS(function(exports, module) {
6621
6621
  });
6622
6622
 
6623
6623
  // packages/workit-codex/scripts/launch-mcp.ts
6624
- import { existsSync as existsSync8, realpathSync as realpathSync8, statSync as statSync2 } from "node:fs";
6625
- import path15 from "node:path";
6624
+ import { existsSync as existsSync8, realpathSync as realpathSync9, statSync as statSync3 } from "node:fs";
6625
+ import path16 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-core/src/core.ts
6628
6628
  import { spawnSync as spawnSync3 } from "node:child_process";
6629
- import { existsSync as existsSync5, realpathSync as realpathSync6 } from "node:fs";
6630
- import path10 from "node:path";
6629
+ import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
6630
+ import path11 from "node:path";
6631
6631
 
6632
6632
  // packages/workit-core/src/core/task-contract.ts
6633
6633
  import { createHash, randomUUID } from "node:crypto";
@@ -11934,6 +11934,17 @@ function _array(Class, element, params) {
11934
11934
  ...normalizeParams(params)
11935
11935
  });
11936
11936
  }
11937
+ function _custom(Class, fn, _params) {
11938
+ const norm = normalizeParams(_params);
11939
+ norm.abort ?? (norm.abort = true);
11940
+ const schema = new Class({
11941
+ type: "custom",
11942
+ check: "custom",
11943
+ fn,
11944
+ ...norm
11945
+ });
11946
+ return schema;
11947
+ }
11937
11948
  function _refine(Class, fn, _params) {
11938
11949
  const schema = new Class({
11939
11950
  type: "custom",
@@ -14087,6 +14098,9 @@ var ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
14087
14098
  ZodType.init(inst, def);
14088
14099
  inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
14089
14100
  });
14101
+ function custom(fn, _params) {
14102
+ return _custom(ZodCustom, fn ?? (() => true), _params);
14103
+ }
14090
14104
  function refine(fn, _params = {}) {
14091
14105
  return _refine(ZodCustom, fn, _params);
14092
14106
  }
@@ -14520,7 +14534,8 @@ var decisionSchema = object({
14520
14534
  consumption: object({
14521
14535
  state: _enum(["reserved", "consumed", "uncertain"]),
14522
14536
  at: utc,
14523
- actionRef: refSchema
14537
+ actionRef: refSchema,
14538
+ historicalActionId: text.optional()
14524
14539
  }).strict().nullable()
14525
14540
  }).strict();
14526
14541
  var findingSchema = object({
@@ -14663,7 +14678,7 @@ var taskSummarySchema = object({
14663
14678
  requirements: array(requirementEvaluationSchema),
14664
14679
  writer: workspaceRecordSchema.shape.writer
14665
14680
  }).strict();
14666
- var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
14681
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
14667
14682
  var exportBundleSchema = object({
14668
14683
  schemaVersion: literal(1),
14669
14684
  exportedAt: utc,
@@ -14688,6 +14703,7 @@ var taskOperations = {
14688
14703
  list: operation({
14689
14704
  action: literal("list"),
14690
14705
  status: _enum(["open", "closed", "all"]).optional(),
14706
+ query: string2().trim().min(1).max(200).optional(),
14691
14707
  limit: number2().int().min(1).max(50).optional()
14692
14708
  }),
14693
14709
  inspect: operation({
@@ -15035,6 +15051,7 @@ function requirementId(input) {
15035
15051
  }
15036
15052
  // packages/workit-core/src/core/task-store.ts
15037
15053
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
15054
+ import { AsyncLocalStorage } from "node:async_hooks";
15038
15055
  import * as fs4 from "node:fs";
15039
15056
  import { hostname } from "node:os";
15040
15057
  import path4 from "node:path";
@@ -15670,7 +15687,8 @@ var metadataLockSchema = object({
15670
15687
  pid: number2().int().nonnegative().safe(),
15671
15688
  processStart: string2().nullable(),
15672
15689
  host: string2().min(1),
15673
- nonce: string2().min(1)
15690
+ nonce: string2().min(1),
15691
+ externalAction: literal(true).optional()
15674
15692
  }).strict();
15675
15693
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
15676
15694
  var cachedRuntimeVersion = null;
@@ -15722,6 +15740,28 @@ var sameMetadataLock = (left, right) => {
15722
15740
  const parsed = metadataLockSchema.safeParse(left);
15723
15741
  return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
15724
15742
  };
15743
+ var sameDirectoryIdentity = (left, right) => {
15744
+ if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
15745
+ return false;
15746
+ const normalizedLeft = path4.resolve(left);
15747
+ const normalizedRight = path4.resolve(right);
15748
+ try {
15749
+ const a = fs4.statSync(normalizedLeft, { bigint: true });
15750
+ const b = fs4.statSync(normalizedRight, { bigint: true });
15751
+ if (!a.isDirectory() || !b.isDirectory())
15752
+ return false;
15753
+ if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
15754
+ return true;
15755
+ const canonicalLeft = fs4.realpathSync(normalizedLeft);
15756
+ const canonicalRight = fs4.realpathSync(normalizedRight);
15757
+ if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
15758
+ return true;
15759
+ return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
15760
+ } catch {
15761
+ return false;
15762
+ }
15763
+ };
15764
+ var externalActionLockRoots = new AsyncLocalStorage;
15725
15765
 
15726
15766
  class TaskStore {
15727
15767
  root;
@@ -15782,7 +15822,7 @@ class TaskStore {
15782
15822
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
15783
15823
  if (!item.exists)
15784
15824
  return success(null, null, null);
15785
- if (item.result.ok && item.result.data.root !== this.root)
15825
+ if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
15786
15826
  return failure("recovery_required", "workspace root binding is invalid", {
15787
15827
  path: this.workspacePath
15788
15828
  });
@@ -15873,6 +15913,29 @@ class TaskStore {
15873
15913
  const current = this.readWorkspace();
15874
15914
  if (!current.ok)
15875
15915
  return current;
15916
+ const origin = input.task.origin;
15917
+ if (current.data && origin) {
15918
+ if (origin.workspaceId === current.data.id) {
15919
+ const source = this.readTask(origin.taskId);
15920
+ if (source.ok)
15921
+ return success(source.data.revision, current.data.revision, source.data);
15922
+ if (source.code !== "not_found")
15923
+ return source;
15924
+ return failure("not_found", "source task is not present in this shared workspace");
15925
+ }
15926
+ const tasks = this.listTasks();
15927
+ if (!tasks.ok)
15928
+ return tasks;
15929
+ const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
15930
+ if (prior.length > 1)
15931
+ return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
15932
+ if (prior.length === 1) {
15933
+ const existing = prior[0];
15934
+ if (existing.origin.exportDigest !== origin.exportDigest)
15935
+ return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
15936
+ return success(existing.revision, current.data.revision, existing);
15937
+ }
15938
+ }
15876
15939
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
15877
15940
  return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
15878
15941
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
@@ -16261,6 +16324,73 @@ class TaskStore {
16261
16324
  })
16262
16325
  };
16263
16326
  }
16327
+ externalActionLockOptions() {
16328
+ const options = this.metadataLockOptions();
16329
+ return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
16330
+ }
16331
+ async withExternalActionLock(operation, reentrant = false) {
16332
+ const activeRoots = externalActionLockRoots.getStore();
16333
+ if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
16334
+ return operation();
16335
+ try {
16336
+ this.initializeMutationStorage();
16337
+ } catch (error) {
16338
+ return failure("storage_error", `unable to initialize store: ${String(error)}`, {
16339
+ path: this.workitDir
16340
+ });
16341
+ }
16342
+ let handle;
16343
+ try {
16344
+ handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
16345
+ } catch (error) {
16346
+ return this.lockFailure(error);
16347
+ }
16348
+ let result;
16349
+ try {
16350
+ if (!handle.verifyStillHeld())
16351
+ result = failure("recovery_required", "metadata lock was compromised", {
16352
+ path: this.lockPath
16353
+ });
16354
+ else {
16355
+ try {
16356
+ const run = () => operation();
16357
+ result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
16358
+ } catch {
16359
+ result = failure("external_outcome_unknown", "external action outcome is unknown", {
16360
+ outcome: "unknown"
16361
+ });
16362
+ }
16363
+ try {
16364
+ if (!handle.verifyStillHeld())
16365
+ result = failure("external_outcome_unknown", "metadata lock was compromised", {
16366
+ outcome: "unknown",
16367
+ path: this.lockPath
16368
+ });
16369
+ } catch {
16370
+ result = failure("external_outcome_unknown", "metadata lock could not be verified", {
16371
+ outcome: "unknown",
16372
+ path: this.lockPath
16373
+ });
16374
+ }
16375
+ }
16376
+ } catch {
16377
+ result = failure("external_outcome_unknown", "external action lock operation failed", {
16378
+ outcome: "unknown",
16379
+ path: this.lockPath
16380
+ });
16381
+ }
16382
+ try {
16383
+ handle.release();
16384
+ } catch (error) {
16385
+ const released = this.lockFailure(error);
16386
+ const releaseError = released.ok ? "metadata lock release failed" : released.error;
16387
+ return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
16388
+ outcome: "unknown",
16389
+ path: this.lockPath
16390
+ }) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
16391
+ }
16392
+ return result;
16393
+ }
16264
16394
  recoveryLockOptions(observed, evidence, gate) {
16265
16395
  const options = this.metadataLockOptions();
16266
16396
  options.staleRecovery = "remove-if-unchanged";
@@ -16330,6 +16460,14 @@ class TaskStore {
16330
16460
  lockFailure(error) {
16331
16461
  const value = error;
16332
16462
  const code = typeof value?.code === "string" ? value.code : "";
16463
+ if (code === "EEXIST" || code === "file_lock_timeout") {
16464
+ const lock = this.readLockSnapshot();
16465
+ if (lock.ok && lock.data?.data.externalAction)
16466
+ return failure("writer_conflict", "workspace is reserved by a managed external action", {
16467
+ outcome: "not_started",
16468
+ path: this.lockPath
16469
+ });
16470
+ }
16333
16471
  const recovery = code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || code === "metadata_lock_invalid" || code === "not-file" || /metadata lock|file lock|reclaim/i.test(String(value?.message ?? error));
16334
16472
  return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
16335
16473
  }
@@ -16597,21 +16735,23 @@ function assertProductWriteAllowed(input) {
16597
16735
  }
16598
16736
 
16599
16737
  // packages/workit-core/src/core/task-evaluation.ts
16600
- import { createHash as createHash5 } from "node:crypto";
16738
+ import { createHash as createHash6 } from "node:crypto";
16601
16739
  import * as fs7 from "node:fs";
16602
16740
  import { spawnSync as spawnSync2 } from "node:child_process";
16603
- import path9 from "node:path";
16741
+ import path10 from "node:path";
16604
16742
 
16605
16743
  // packages/workit-core/src/core/authority.ts
16606
- import { createHash as createHash4 } from "node:crypto";
16744
+ import { createHash as createHash5 } from "node:crypto";
16607
16745
  import * as fs6 from "node:fs";
16608
- import path8 from "node:path";
16746
+ import path9 from "node:path";
16609
16747
 
16610
16748
  // packages/workit-core/src/core/auto-approval.ts
16611
- import { createHash as createHash3 } from "node:crypto";
16612
- import { readFileSync as readFileSync4 } from "node:fs";
16749
+ import { createHash as createHash4 } from "node:crypto";
16750
+ import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
16751
+ import path8 from "node:path";
16613
16752
 
16614
16753
  // packages/workit-core/src/core/workspaces.ts
16754
+ import { createHash as createHash3 } from "node:crypto";
16615
16755
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
16616
16756
  import path6 from "node:path";
16617
16757
 
@@ -16629,7 +16769,7 @@ import os from "node:os";
16629
16769
  import path5 from "node:path";
16630
16770
 
16631
16771
  // packages/workit-core/src/core/boundary.ts
16632
- import { statSync } from "node:fs";
16772
+ import { statSync as statSync2 } from "node:fs";
16633
16773
  var EVENT = {
16634
16774
  initialization: "initialization",
16635
16775
  provenance: "provenance",
@@ -16657,7 +16797,7 @@ var changedSourcesSinceLoad = (marker) => {
16657
16797
  const changed = [];
16658
16798
  for (const file of marker.files) {
16659
16799
  try {
16660
- if (statSync(file).mtimeMs > marker.loadedAt)
16800
+ if (statSync2(file).mtimeMs > marker.loadedAt)
16661
16801
  changed.push(file);
16662
16802
  } catch {}
16663
16803
  }
@@ -16816,33 +16956,197 @@ var COMMIT_PRESETS = [
16816
16956
  "custom",
16817
16957
  "auto"
16818
16958
  ];
16959
+ var resolveCommitPolicy = (config, workspace) => {
16960
+ const wp = workspace?.commitPolicy ?? {};
16961
+ if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
16962
+ return {
16963
+ preset: wp.preset,
16964
+ ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
16965
+ };
16966
+ }
16967
+ return config.commitPolicy ?? { preset: "conventional" };
16968
+ };
16819
16969
 
16820
16970
  // packages/workit-core/src/core/workspaces.ts
16971
+ var nonBlank = string2().refine((value) => value.trim().length > 0, "must not be blank");
16972
+ var branchPreset = custom((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
16973
+ var commitPreset = custom((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
16974
+ var workspaceBranchPolicySchema = object({
16975
+ preset: branchPreset,
16976
+ developBranch: nonBlank.optional(),
16977
+ prefixes: object({
16978
+ feature: nonBlank,
16979
+ bugfix: nonBlank,
16980
+ release: nonBlank,
16981
+ hotfix: nonBlank
16982
+ }).passthrough().optional(),
16983
+ allowed: array(nonBlank).optional(),
16984
+ protected: array(nonBlank).optional(),
16985
+ integration: _enum(["pr", "merge"]).optional()
16986
+ }).strict();
16987
+ var workspaceCommitPolicySchema = object({ preset: commitPreset, pattern: string2().optional() }).strict();
16988
+ var workspaceProfileSchema = object({
16989
+ branchPolicy: workspaceBranchPolicySchema.optional(),
16990
+ commitPolicy: workspaceCommitPolicySchema.optional()
16991
+ }).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
16992
+ message: "a profile must define branchPolicy or commitPolicy"
16993
+ });
16994
+ var versionSourceSchema = discriminatedUnion("kind", [
16995
+ object({
16996
+ kind: literal("package-json"),
16997
+ path: nonBlank,
16998
+ field: nonBlank
16999
+ }).strict(),
17000
+ object({ kind: literal("git-tag") }).strict(),
17001
+ object({ kind: literal("manual") }).strict()
17002
+ ]);
17003
+ var releaseTrackSchema = object({
17004
+ strategy: branchPreset,
17005
+ productionBranch: nonBlank,
17006
+ integrationBranch: nonBlank,
17007
+ naming: object({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
17008
+ baseBranch: nonBlank,
17009
+ mergeBackBranches: array(nonBlank),
17010
+ pullRequestTarget: nonBlank,
17011
+ tagNamespace: string2(),
17012
+ versionSource: versionSourceSchema,
17013
+ requiredChecks: array(nonBlank)
17014
+ }).strict().superRefine((track, context) => {
17015
+ for (const [field, values] of [
17016
+ ["mergeBackBranches", track.mergeBackBranches],
17017
+ ["requiredChecks", track.requiredChecks]
17018
+ ]) {
17019
+ if (new Set(values).size !== values.length) {
17020
+ context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
17021
+ }
17022
+ }
17023
+ if (track.versionSource.kind === "package-json" && (path6.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
17024
+ context.addIssue({
17025
+ code: "custom",
17026
+ path: ["versionSource", "path"],
17027
+ message: "must stay inside the repository"
17028
+ });
17029
+ }
17030
+ });
17031
+ var workspaceConfigSchema = object({
17032
+ name: nonBlank,
17033
+ glob: nonBlank,
17034
+ vcs: object({
17035
+ provider: _enum(["gitlab", "github"]),
17036
+ defaultTargetBranch: nonBlank.optional(),
17037
+ tokenFile: nonBlank.optional(),
17038
+ account: nonBlank.optional()
17039
+ }).passthrough().optional(),
17040
+ autoApprove: union([boolean2(), array(nonBlank)]).optional(),
17041
+ youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
17042
+ issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
17043
+ branchPolicy: workspaceBranchPolicySchema.optional(),
17044
+ commitPolicy: workspaceCommitPolicySchema.optional(),
17045
+ defaultProfile: nonBlank.optional(),
17046
+ profiles: record(nonBlank, workspaceProfileSchema).optional(),
17047
+ releaseTracks: record(nonBlank, releaseTrackSchema).optional()
17048
+ }).passthrough().superRefine((workspace, context) => {
17049
+ if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
17050
+ context.addIssue({
17051
+ code: "custom",
17052
+ path: ["youtrack"],
17053
+ message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
17054
+ });
17055
+ }
17056
+ if (workspace.issues && workspace.vcs?.provider !== "github") {
17057
+ context.addIssue({
17058
+ code: "custom",
17059
+ path: ["issues"],
17060
+ message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
17061
+ });
17062
+ }
17063
+ if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
17064
+ context.addIssue({
17065
+ code: "custom",
17066
+ path: ["defaultProfile"],
17067
+ message: `does not name a configured profile`
17068
+ });
17069
+ }
17070
+ });
17071
+ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
17072
+ const names = new Set;
17073
+ for (const [index, workspace] of (file.workspaces ?? []).entries()) {
17074
+ if (names.has(workspace.name)) {
17075
+ context.addIssue({
17076
+ code: "custom",
17077
+ path: ["workspaces", index, "name"],
17078
+ message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
17079
+ });
17080
+ }
17081
+ names.add(workspace.name);
17082
+ }
17083
+ });
17084
+ var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
17085
+ var validateWorkspacesDocument = (parsed, file) => {
17086
+ if (!isConfigObject(parsed)) {
17087
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
17088
+ }
17089
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
17090
+ if (!validated.success) {
17091
+ const detail = validated.error.issues.map((issue) => {
17092
+ const field = String(issue.path[issue.path.length - 1] ?? "");
17093
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
17094
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
17095
+ }).join("; ");
17096
+ return {
17097
+ status: "invalid",
17098
+ path: file,
17099
+ entries: [],
17100
+ error: `${file} has invalid workspace configuration: ${detail}`
17101
+ };
17102
+ }
17103
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
17104
+ const glob = validateWorkspaceGlob(workspace.glob);
17105
+ if (!glob.ok) {
17106
+ return {
17107
+ status: "invalid",
17108
+ path: file,
17109
+ entries: [],
17110
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
17111
+ };
17112
+ }
17113
+ }
17114
+ return {
17115
+ status: "valid",
17116
+ path: file,
17117
+ entries: validated.data.workspaces ?? [],
17118
+ document: parsed
17119
+ };
17120
+ };
16821
17121
  var readWorkspacesResult = (dir = configDir()) => {
16822
17122
  const file = path6.join(dir, "workspaces.json");
16823
17123
  let raw;
16824
17124
  try {
16825
17125
  raw = readFileSync3(file, "utf8");
16826
- } catch {
16827
- return { status: "missing", path: file, entries: [] };
17126
+ } catch (error) {
17127
+ if (error.code === "ENOENT")
17128
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
17129
+ return {
17130
+ status: "malformed",
17131
+ path: file,
17132
+ entries: [],
17133
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
17134
+ };
16828
17135
  }
16829
17136
  let parsed;
16830
17137
  try {
16831
17138
  parsed = JSON.parse(raw);
16832
17139
  } catch {
16833
- return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
16834
- }
16835
- if (!isConfigObject(parsed)) {
16836
- return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
17140
+ return {
17141
+ status: "malformed",
17142
+ path: file,
17143
+ entries: [],
17144
+ revision: workspacesRevision(raw),
17145
+ error: `${file} is not valid JSON`
17146
+ };
16837
17147
  }
16838
- const list = parsed.workspaces;
16839
- return {
16840
- status: "valid",
16841
- path: file,
16842
- entries: Array.isArray(list) ? list : []
16843
- };
17148
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
16844
17149
  };
16845
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
16846
17150
  var globToRegExp = (glob) => {
16847
17151
  let out = "";
16848
17152
  for (let i = 0;i < glob.length; i++) {
@@ -16877,6 +17181,30 @@ var globToRegExp = (glob) => {
16877
17181
  return new RegExp(`^${out}$`);
16878
17182
  };
16879
17183
  var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
17184
+ var UNSUPPORTED_GLOB = /[?[\]{}]/;
17185
+ var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
17186
+ var validateWorkspaceGlob = (glob) => {
17187
+ const trimmed = glob.trim();
17188
+ if (!trimmed)
17189
+ return { ok: false, error: "workspace pattern is required" };
17190
+ const m = UNSUPPORTED_GLOB.exec(trimmed);
17191
+ if (m) {
17192
+ return {
17193
+ ok: false,
17194
+ error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
17195
+ };
17196
+ }
17197
+ const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
17198
+ if (ext) {
17199
+ const token = ext[0];
17200
+ const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
17201
+ return {
17202
+ ok: false,
17203
+ error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
17204
+ };
17205
+ }
17206
+ return { ok: true };
17207
+ };
16880
17208
  var realpathOf = (p) => {
16881
17209
  const real = realpathSync2.native ?? realpathSync2;
16882
17210
  try {
@@ -16894,29 +17222,137 @@ var canonicalGlob = (glob) => {
16894
17222
  const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
16895
17223
  return real + (prefix.endsWith("/") ? "/" : "") + rest;
16896
17224
  };
16897
- var resolveWorkspaceFrom = (cwd, dir) => {
17225
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
17226
+ const result = readWorkspacesResult(dir);
17227
+ if (result.status === "malformed" || result.status === "invalid")
17228
+ throw new Error(result.error);
16898
17229
  const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
16899
- for (const entry of loadWorkspacesFrom(dir)) {
16900
- if (!entry || typeof entry !== "object")
16901
- continue;
16902
- const ws = entry;
16903
- if (typeof ws.glob !== "string" || !ws.glob)
16904
- continue;
17230
+ const matches = [];
17231
+ for (const ws of result.entries) {
16905
17232
  const glob = ws.glob.replaceAll("\\", "/");
16906
17233
  const canonical = canonicalGlob(glob);
17234
+ let matched = false;
16907
17235
  for (const target of targets) {
16908
- if (matchWorkspace(glob, target))
16909
- return ws;
16910
- if (canonical !== glob && matchWorkspace(canonical, target))
16911
- return ws;
17236
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
17237
+ matched = true;
17238
+ break;
17239
+ }
16912
17240
  }
17241
+ if (matched)
17242
+ matches.push(ws);
16913
17243
  }
16914
- return null;
17244
+ if (workspaceName !== undefined) {
17245
+ const selected = matches.find((ws) => ws.name === workspaceName);
17246
+ if (!selected && matches.length > 0) {
17247
+ const matchingNames = matches.map((ws) => ws.name);
17248
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matchingNames.join(", ")}`);
17249
+ }
17250
+ if (selected)
17251
+ return selected;
17252
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
17253
+ }
17254
+ if (matches.length > 1) {
17255
+ throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((ws) => ws.name).join(", ")}`);
17256
+ }
17257
+ return matches[0] ?? null;
17258
+ };
17259
+ var runtimeWorkspacePolicySchema = (kind) => {
17260
+ const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
17261
+ const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object({
17262
+ provider: _enum(["gitlab", "github"]),
17263
+ defaultTargetBranch: nonBlank.optional(),
17264
+ account: nonBlank.optional()
17265
+ }).passthrough();
17266
+ const profileSchema = object({ [key]: policySchema.optional() }).passthrough();
17267
+ const entrySchema = object({
17268
+ name: nonBlank,
17269
+ glob: nonBlank,
17270
+ [key]: policySchema.optional(),
17271
+ defaultProfile: nonBlank.optional(),
17272
+ profiles: record(nonBlank, profileSchema).optional()
17273
+ }).passthrough();
17274
+ return entrySchema;
17275
+ };
17276
+ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
17277
+ var runtimeWorkspaceIndexSchema = object({
17278
+ workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
17279
+ }).passthrough();
17280
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
17281
+ const file = path6.join(configDir(), "workspaces.json");
17282
+ let raw;
17283
+ try {
17284
+ raw = readFileSync3(file, "utf8");
17285
+ } catch (error) {
17286
+ if (error.code === "ENOENT")
17287
+ return null;
17288
+ throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
17289
+ }
17290
+ let parsed;
17291
+ try {
17292
+ parsed = JSON.parse(raw);
17293
+ } catch {
17294
+ throw new Error(`${file} is not valid JSON`);
17295
+ }
17296
+ const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
17297
+ if (!validated.success) {
17298
+ const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
17299
+ throw new Error(`${file} has invalid workspace matching data: ${detail}`);
17300
+ }
17301
+ const entries = validated.data.workspaces;
17302
+ for (const entry of entries ?? []) {
17303
+ const glob = validateWorkspaceGlob(entry.glob);
17304
+ if (!glob.ok)
17305
+ throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
17306
+ }
17307
+ const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
17308
+ const matches = (entries ?? []).filter((entry) => {
17309
+ const glob = entry.glob.replaceAll("\\", "/");
17310
+ const canonical = canonicalGlob(glob);
17311
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
17312
+ });
17313
+ const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
17314
+ let selected;
17315
+ if (workspaceName) {
17316
+ const named = matches.filter((entry) => entry.name === workspaceName);
17317
+ if (named.length === 1)
17318
+ selected = named[0];
17319
+ else if (named.length > 1)
17320
+ throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
17321
+ else if (matches.length)
17322
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
17323
+ else
17324
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
17325
+ } else if (matches.length > 1)
17326
+ throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((entry) => entry.name).join(", ")}`);
17327
+ else
17328
+ selected = matches[0];
17329
+ if (!selected)
17330
+ return null;
17331
+ const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
17332
+ if (!policy.success) {
17333
+ const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
17334
+ throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
17335
+ }
17336
+ return policy.data;
17337
+ };
17338
+ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
17339
+ const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
17340
+ if (!candidate)
17341
+ return { policy: undefined, source: "user-default" };
17342
+ const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
17343
+ const profileName = requestedProfile?.trim() || candidate.defaultProfile;
17344
+ if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
17345
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
17346
+ const profile = profileName ? candidate.profiles?.[profileName] : undefined;
17347
+ if (profileName && !profile)
17348
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
17349
+ const profilePolicy = profile?.[key];
17350
+ const workspacePolicy = candidate[key];
17351
+ return {
17352
+ policy: profilePolicy ?? workspacePolicy,
17353
+ source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
17354
+ };
16915
17355
  };
16916
- var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
16917
-
16918
- // packages/workit-core/src/core/branch.ts
16919
- import { execFileSync as execFileSync2 } from "node:child_process";
16920
17356
 
16921
17357
  // packages/workit-core/src/core/git.ts
16922
17358
  import { execFileSync } from "node:child_process";
@@ -16990,7 +17426,6 @@ var gitContext = (workspaceRoot, paths = []) => {
16990
17426
  import fs5 from "node:fs";
16991
17427
  import path7 from "node:path";
16992
17428
  import { spawnSync } from "node:child_process";
16993
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
16994
17429
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
16995
17430
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
16996
17431
  var remoteProvider = (cwd) => {
@@ -17004,6 +17439,79 @@ var remoteProvider = (cwd) => {
17004
17439
  return "gitlab";
17005
17440
  return null;
17006
17441
  };
17442
+ var remoteEndpoint = (remote) => {
17443
+ const raw = remote.trim();
17444
+ if (!raw)
17445
+ return null;
17446
+ const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
17447
+ if (scp && !raw.includes("://"))
17448
+ return {
17449
+ host: scp[1].toLowerCase(),
17450
+ path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
17451
+ protocol: "ssh:",
17452
+ port: ""
17453
+ };
17454
+ try {
17455
+ const url = new URL(raw);
17456
+ return {
17457
+ host: url.hostname.toLowerCase(),
17458
+ path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
17459
+ protocol: url.protocol,
17460
+ port: url.port
17461
+ };
17462
+ } catch {
17463
+ return null;
17464
+ }
17465
+ };
17466
+ var configuredSshEndpoint = (host) => {
17467
+ if (host.startsWith("-"))
17468
+ return null;
17469
+ const resolved = spawnSync("ssh", ["-G", host], {
17470
+ encoding: "utf8",
17471
+ timeout: 5000,
17472
+ maxBuffer: 1024 * 1024,
17473
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
17474
+ });
17475
+ if (resolved.status !== 0)
17476
+ return null;
17477
+ const output = resolved.stdout ?? "";
17478
+ const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
17479
+ if (!hostname)
17480
+ return null;
17481
+ return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
17482
+ };
17483
+ var sshApiHostAliases = {
17484
+ "github.com": [{ host: "ssh.github.com", port: "443" }],
17485
+ "gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
17486
+ };
17487
+ var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
17488
+ const endpoint = remoteEndpoint(remote);
17489
+ if (!endpoint)
17490
+ return false;
17491
+ let expected;
17492
+ try {
17493
+ const url = new URL(`https://${apiHost}`);
17494
+ if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
17495
+ return false;
17496
+ expected = {
17497
+ host: url.hostname.toLowerCase().replace(/\.$/u, ""),
17498
+ port: url.port
17499
+ };
17500
+ } catch {
17501
+ return false;
17502
+ }
17503
+ if (endpoint.protocol !== "ssh:") {
17504
+ return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
17505
+ }
17506
+ const resolved = resolveSshEndpoint(endpoint.host);
17507
+ if (!resolved)
17508
+ return false;
17509
+ const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
17510
+ const port = endpoint.port || resolved.port;
17511
+ if (hostname === expected.host)
17512
+ return port === (expected.port || "22");
17513
+ return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
17514
+ };
17007
17515
  var readVcsConfig = () => {
17008
17516
  const file = vcsConfigPath();
17009
17517
  let raw;
@@ -17024,7 +17532,7 @@ var readVcsConfig = () => {
17024
17532
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
17025
17533
  };
17026
17534
  function vcsConfig(mode, cwd) {
17027
- const ws = resolveWorkspace(vcsCwd(cwd));
17535
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
17028
17536
  const wsVcs = ws?.vcs ?? {};
17029
17537
  const wsYt = ws?.youtrack ?? {};
17030
17538
  const wsIssues = ws?.issues ?? {};
@@ -17037,8 +17545,8 @@ function vcsConfig(mode, cwd) {
17037
17545
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
17038
17546
  const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
17039
17547
  const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
17040
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
17041
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
17548
+ const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
17549
+ const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
17042
17550
  let issuesProvider = null;
17043
17551
  let linkOnPr = null;
17044
17552
  if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
@@ -17062,9 +17570,8 @@ function vcsConfig(mode, cwd) {
17062
17570
  if (cfgStatus === "malformed") {
17063
17571
  return { ok: false, error: cfgError, configPath: cfgPath };
17064
17572
  }
17065
- if (cfgStatus === "missing") {
17573
+ if (cfgStatus === "missing" && provider === null)
17066
17574
  return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
17067
- }
17068
17575
  if (provider === null) {
17069
17576
  return {
17070
17577
  ok: false,
@@ -17073,24 +17580,12 @@ function vcsConfig(mode, cwd) {
17073
17580
  };
17074
17581
  }
17075
17582
  const prov = cfg[provider] ?? {};
17076
- const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
17077
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
17078
- const tokenPath = path7.resolve(tokenFile);
17079
- let tokenOk = false;
17080
- if (fs5.existsSync(tokenPath)) {
17081
- const token = fs5.readFileSync(tokenPath, "utf8").trim();
17082
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
17083
- tokenOk = !placeholder;
17084
- }
17085
17583
  const out = {
17086
17584
  ok: true,
17087
17585
  configPath: path7.resolve(cfgPath),
17088
17586
  provider,
17089
17587
  defaultTargetBranch: defaultTarget,
17090
17588
  pr: cfg.pr ?? {},
17091
- tokenPath,
17092
- tokenPresent: fs5.existsSync(tokenPath),
17093
- tokenReady: tokenOk,
17094
17589
  workspace_name: ws?.name ?? null,
17095
17590
  link_issues: linkIssues,
17096
17591
  youtrack_base_url: youtrackBaseUrl,
@@ -17105,16 +17600,58 @@ function vcsConfig(mode, cwd) {
17105
17600
  } else if (provider === "github") {
17106
17601
  out.github = { host: prov.host ?? "github.com" };
17107
17602
  }
17108
- if (mode === "summary")
17109
- delete out.tokenPath;
17110
17603
  return out;
17111
17604
  }
17605
+ function vcsCliIdentity(cwd) {
17606
+ const root = vcsCwd(cwd);
17607
+ const cfg = vcsConfig("load", root);
17608
+ if (!cfg.ok)
17609
+ return { ok: false, error: cfg.error ?? "vcs config not ready" };
17610
+ const provider = cfg.provider;
17611
+ if (provider !== "gitlab" && provider !== "github")
17612
+ return { ok: false, error: `unsupported provider: ${provider}` };
17613
+ const bin = provider === "gitlab" ? "glab" : "gh";
17614
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
17615
+ const env = {
17616
+ ...process.env,
17617
+ PATH: process.env.PATH ?? "",
17618
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
17619
+ };
17620
+ const result = spawnSync(bin, ["api", "user"], {
17621
+ cwd: root,
17622
+ encoding: "utf8",
17623
+ env
17624
+ });
17625
+ if (result.status !== 0)
17626
+ return { ok: false, provider, error: `${bin} is missing or not authenticated` };
17627
+ try {
17628
+ const user = JSON.parse(result.stdout ?? "");
17629
+ const username = provider === "gitlab" ? user.username : user.login;
17630
+ if (typeof username !== "string" || !username)
17631
+ return { ok: false, provider, error: `invalid ${bin} identity response` };
17632
+ const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
17633
+ if (expected && username.toLowerCase() !== expected.toLowerCase())
17634
+ return {
17635
+ ok: false,
17636
+ provider,
17637
+ error: `${bin} account ${username} does not match ${expected}`
17638
+ };
17639
+ return { ok: true, provider, host, username, name: user.name };
17640
+ } catch {
17641
+ return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
17642
+ }
17643
+ }
17112
17644
  function mergedPrStyle(limit = 6, cwd) {
17113
17645
  const cfg = vcsConfig("load", cwd);
17114
- if (!cfg.ok || !cfg.tokenReady)
17646
+ if (!cfg.ok)
17115
17647
  return { ok: false, error: "vcs not configured" };
17116
17648
  const provider = cfg.provider;
17117
- const token = fs5.readFileSync(cfg.tokenPath, "utf8").trim();
17649
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
17650
+ const env = {
17651
+ ...process.env,
17652
+ PATH: process.env.PATH ?? "",
17653
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
17654
+ };
17118
17655
  const examples = [];
17119
17656
  const descInfo = (desc, caseInsensitiveNotes = false) => ({
17120
17657
  hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
@@ -17122,23 +17659,30 @@ function mergedPrStyle(limit = 6, cwd) {
17122
17659
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
17123
17660
  descriptionPreview: desc.slice(0, 600)
17124
17661
  });
17662
+ const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
17663
+ cwd,
17664
+ encoding: "utf8"
17665
+ });
17666
+ const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
17667
+ if (remotes.length === 0)
17668
+ return { ok: false, error: "no origin remote" };
17669
+ if (remotes.length !== 1)
17670
+ return { ok: false, error: "origin has ambiguous push destinations" };
17671
+ const remote = remotes[0];
17672
+ const endpoint = remoteEndpoint(remote);
17673
+ if (!endpoint?.path)
17674
+ return { ok: false, error: "hosting repository could not be resolved" };
17675
+ if (!hostingApiHostMatches(remote, host))
17676
+ return { ok: false, error: "PR style target does not match the provider host" };
17125
17677
  if (provider === "gitlab") {
17126
- const remote = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
17127
- if (remote.status !== 0)
17128
- return { ok: false, error: "no origin remote" };
17129
- const url = (remote.stdout ?? "").trim();
17130
- const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
17131
- if (!m)
17132
- return { ok: false, error: "not a gitlab.com origin" };
17133
- const project = m[1];
17134
- const env = { ...process.env, GITLAB_TOKEN: token };
17135
- const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
17678
+ const run = (args) => spawnSync("glab", ["api", ...args], {
17679
+ cwd,
17680
+ encoding: "utf8",
17681
+ env
17682
+ });
17136
17683
  let r = run([
17137
- `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
17684
+ `projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
17138
17685
  ]);
17139
- if (r.status !== 0) {
17140
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
17141
- }
17142
17686
  if (r.status !== 0)
17143
17687
  return { ok: false, error: "could not list merge requests" };
17144
17688
  for (const mr of JSON.parse(r.stdout ?? "[]")) {
@@ -17151,7 +17695,18 @@ function mergedPrStyle(limit = 6, cwd) {
17151
17695
  });
17152
17696
  }
17153
17697
  } else if (provider === "github") {
17154
- const r = spawnSync("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
17698
+ const r = spawnSync("gh", [
17699
+ "pr",
17700
+ "list",
17701
+ "--repo",
17702
+ `${host}/${endpoint.path}`,
17703
+ "--state",
17704
+ "merged",
17705
+ "--limit",
17706
+ String(limit),
17707
+ "--json",
17708
+ "title,url,body"
17709
+ ], { cwd, encoding: "utf8", env });
17155
17710
  if (r.status !== 0)
17156
17711
  return { ok: false, error: "could not list pull requests" };
17157
17712
  for (const pr of JSON.parse(r.stdout ?? "[]")) {
@@ -17173,26 +17728,28 @@ function mergedPrStyle(limit = 6, cwd) {
17173
17728
  }
17174
17729
 
17175
17730
  // packages/workit-core/src/core/branch.ts
17176
- var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
17731
+ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
17732
+ const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
17733
+ const config = readConfig();
17734
+ return kind === "branch" ? {
17735
+ branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
17736
+ provenance: { branchPolicy: selected.source }
17737
+ } : {
17738
+ commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
17739
+ provenance: { commitPolicy: selected.source }
17740
+ };
17741
+ }
17742
+ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
17177
17743
  var verifyPushIdentity = (root, provider, account) => {
17178
17744
  if (!account || typeof account !== "string")
17179
17745
  return { ok: false, error: "push identity requires a configured area account" };
17180
- const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
17181
- const bin = provider === "gitlab" ? "glab" : "gh";
17182
- let login;
17183
- try {
17184
- login = execFileSync2(bin, [...probe], {
17185
- cwd: root,
17186
- encoding: "utf8",
17187
- stdio: ["pipe", "pipe", "pipe"]
17188
- }).trim();
17189
- } catch {
17190
- return { ok: false, error: `${bin} identity could not be resolved` };
17191
- }
17192
- if (!login || login.toLowerCase() !== account.toLowerCase())
17746
+ const identity = vcsCliIdentity(root);
17747
+ if (!identity.ok || identity.provider !== provider)
17748
+ return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
17749
+ if (identity.username?.toLowerCase() !== account.toLowerCase())
17193
17750
  return {
17194
17751
  ok: false,
17195
- error: `push identity ${login || "(unknown)"} does not match area account ${account}`
17752
+ error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
17196
17753
  };
17197
17754
  return { ok: true };
17198
17755
  };
@@ -17210,7 +17767,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
17210
17767
  var digestOf = (configDirPath) => {
17211
17768
  try {
17212
17769
  const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
17213
- return createHash3("sha256").update(raw).digest("hex");
17770
+ return createHash4("sha256").update(raw).digest("hex");
17214
17771
  } catch {
17215
17772
  return null;
17216
17773
  }
@@ -17248,14 +17805,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
17248
17805
  };
17249
17806
  };
17250
17807
  var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
17251
- var standingApprovalLive = (root, receipt, cls, dir) => {
17808
+ var targetOf = (root, descriptor) => {
17809
+ try {
17810
+ const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
17811
+ const cwd = parsed?.payload?.cwd;
17812
+ if (cwd === undefined)
17813
+ return root;
17814
+ if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
17815
+ return null;
17816
+ return realpathSync3(cwd);
17817
+ } catch {
17818
+ return null;
17819
+ }
17820
+ };
17821
+ var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
17252
17822
  if (!isStandingReceipt(receipt))
17253
17823
  return false;
17254
17824
  if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
17255
17825
  return false;
17256
17826
  if (receipt.class !== cls)
17257
17827
  return false;
17258
- const resolved = resolveAutoApproval(root, dir);
17828
+ const target = targetOf(root, descriptor);
17829
+ if (!target)
17830
+ return false;
17831
+ const resolved = resolveAutoApproval(target, dir);
17259
17832
  if (resolved.status !== "on")
17260
17833
  return false;
17261
17834
  if (resolved.workspace !== receipt.workspace)
@@ -17291,16 +17864,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
17291
17864
  const cls = operationAutoClass(operation);
17292
17865
  if (!cls || cls !== standing.class)
17293
17866
  return failure("permission_denied", "standing approval does not cover this operation");
17294
- const resolved = resolveAutoApproval(root);
17867
+ const target = targetOf(root, binding.approvedContent);
17868
+ if (!target)
17869
+ return failure("permission_denied", "standing action target is invalid");
17870
+ const resolved = resolveAutoApproval(target);
17295
17871
  if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
17296
17872
  return failure("permission_denied", "standing auto-approval is not live for this operation");
17297
17873
  if (cls === "push") {
17298
- const workspace = resolveWorkspaceFrom(root, configDir());
17874
+ const workspace = resolveWorkspaceFrom(target, configDir());
17299
17875
  const provider = workspace?.vcs?.provider;
17300
17876
  const account = resolved.account;
17301
17877
  if (!provider || !account)
17302
17878
  return failure("permission_denied", "auto-push requires a workspace provider and area account");
17303
- const identity = verifyPushIdentity(root, provider, account);
17879
+ const identity = verifyPushIdentity(target, provider, account);
17304
17880
  if (!identity.ok)
17305
17881
  return failure("permission_denied", identity.error);
17306
17882
  }
@@ -17309,7 +17885,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
17309
17885
  host: caller.host,
17310
17886
  session: { kind: "host", host: caller.host, handle: caller.actor },
17311
17887
  workerId: null,
17312
- receipts: [standingReceiptFor(root, standing.workspace, cls)]
17888
+ receipts: [standingReceiptFor(target, standing.workspace, cls)]
17313
17889
  });
17314
17890
  };
17315
17891
 
@@ -17460,7 +18036,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
17460
18036
  return [];
17461
18037
  return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
17462
18038
  }
17463
- var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
18039
+ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
17464
18040
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
17465
18041
  let root;
17466
18042
  try {
@@ -17473,23 +18049,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
17473
18049
  continue;
17474
18050
  if (!reference.digest)
17475
18051
  return failure("invalid_input", "document references require a byte digest");
17476
- const target = path8.resolve(root, reference.path);
17477
- if (target !== root && !target.startsWith(`${root}${path8.sep}`))
18052
+ const target = path9.resolve(root, reference.path);
18053
+ if (target !== root && !target.startsWith(`${root}${path9.sep}`))
17478
18054
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
17479
18055
  fields: [
17480
18056
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
17481
18057
  ]
17482
18058
  });
17483
18059
  try {
17484
- const relative = path8.relative(root, target);
18060
+ const relative = path9.relative(root, target);
17485
18061
  let current = root;
17486
- for (const segment of relative.split(path8.sep)) {
18062
+ for (const segment of relative.split(path9.sep)) {
17487
18063
  if (!segment)
17488
18064
  continue;
17489
- current = path8.join(current, segment);
18065
+ current = path9.join(current, segment);
17490
18066
  const stat = fs6.lstatSync(current);
17491
18067
  const real = fs6.realpathSync(current);
17492
- if (real !== root && !real.startsWith(`${root}${path8.sep}`))
18068
+ if (real !== root && !real.startsWith(`${root}${path9.sep}`))
17493
18069
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
17494
18070
  fields: [
17495
18071
  {
@@ -17567,7 +18143,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
17567
18143
  operation = null;
17568
18144
  }
17569
18145
  const cls = operationAutoClass(operation);
17570
- if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
18146
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
17571
18147
  return failure("permission_denied", "standing auto-approval is not live for this operation");
17572
18148
  }
17573
18149
  if (decision.revoked)
@@ -17869,15 +18445,15 @@ var bindingCovers = scopeCovers;
17869
18445
  var verifyDecisionContent = verifyContentRefs;
17870
18446
 
17871
18447
  // packages/workit-core/src/core/task-evaluation.ts
17872
- var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
17873
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
17874
- var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
18448
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
18449
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
18450
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
17875
18451
  var canonicalPath = (value) => {
17876
18452
  if (typeof value !== "string" || !value)
17877
18453
  return null;
17878
18454
  if (value.includes("\\") || value.split("/").includes(".."))
17879
18455
  return null;
17880
- const normalized = path9.posix.normalize(value);
18456
+ const normalized = path10.posix.normalize(value);
17881
18457
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
17882
18458
  return null;
17883
18459
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -17925,14 +18501,14 @@ var gitPaths = (root) => {
17925
18501
  git: /not a git repository/i.test(stderr) ? false : true
17926
18502
  };
17927
18503
  }
17928
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
18504
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
17929
18505
  const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
17930
18506
  if (stagedDeleted.status !== 0) {
17931
18507
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
17932
18508
  if (!/not a git repository/i.test(stderr))
17933
18509
  return { paths, uncertain: true, git: true };
17934
18510
  } else if (stagedDeleted.stdout) {
17935
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
18511
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
17936
18512
  }
17937
18513
  return { paths: [...new Set(paths)], uncertain: false, git: true };
17938
18514
  };
@@ -17947,7 +18523,7 @@ var walk = (root, directory, output) => {
17947
18523
  for (const name of names) {
17948
18524
  if (name === ".git" || name === ".workit")
17949
18525
  continue;
17950
- const target = path9.join(directory, name);
18526
+ const target = path10.join(directory, name);
17951
18527
  let stat;
17952
18528
  try {
17953
18529
  stat = fs7.lstatSync(target);
@@ -17965,12 +18541,12 @@ var walk = (root, directory, output) => {
17965
18541
  var scopeRoots = (root, scope) => {
17966
18542
  const roots = [];
17967
18543
  for (const item of scope.paths.length ? scope.paths : ["."]) {
17968
- const target = path9.resolve(root, item);
18544
+ const target = path10.resolve(root, item);
17969
18545
  if (!inside(root, target))
17970
18546
  return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
17971
18547
  let ancestor = target;
17972
18548
  while (!fs7.existsSync(ancestor) && ancestor !== root)
17973
- ancestor = path9.dirname(ancestor);
18549
+ ancestor = path10.dirname(ancestor);
17974
18550
  try {
17975
18551
  if (!inside(root, fs7.realpathSync(ancestor)))
17976
18552
  return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
@@ -18022,7 +18598,7 @@ function captureCandidate(root, scope, environment = []) {
18022
18598
  for (const item of [...names].sort()) {
18023
18599
  if (!scopeMatches(item, normalizedScope))
18024
18600
  continue;
18025
- const target = path9.join(checkout, item);
18601
+ const target = path10.join(checkout, item);
18026
18602
  try {
18027
18603
  const stat = fs7.lstatSync(target);
18028
18604
  if (stat.isSymbolicLink()) {
@@ -18404,7 +18980,8 @@ var externalActionSchema = discriminatedUnion("operation", [
18404
18980
  action: _enum(["setup", "reapply_stash"]).optional(),
18405
18981
  sdd_dir: string2().optional(),
18406
18982
  target_branch: string2().optional(),
18407
- stash: _enum(["yes", "no"]).optional()
18983
+ stash: _enum(["yes", "no"]).optional(),
18984
+ cwd: string2().min(1).optional()
18408
18985
  }).strict()
18409
18986
  }).strict(),
18410
18987
  object({
@@ -18412,12 +18989,13 @@ var externalActionSchema = discriminatedUnion("operation", [
18412
18989
  payload: object({
18413
18990
  message: string2().min(1).optional(),
18414
18991
  plan_steps: array(chainStepSchema).min(1).max(32).optional(),
18415
- plan_branch: string2().min(1).optional()
18992
+ plan_branch: string2().min(1).optional(),
18993
+ cwd: string2().min(1).optional()
18416
18994
  }).strict()
18417
18995
  }).strict(),
18418
18996
  object({
18419
18997
  operation: literal("git.push"),
18420
- payload: object({ branch: string2().min(1).optional() }).strict()
18998
+ payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
18421
18999
  }).strict(),
18422
19000
  object({
18423
19001
  operation: literal("hosting.pull_request"),
@@ -18426,16 +19004,22 @@ var externalActionSchema = discriminatedUnion("operation", [
18426
19004
  body: string2().optional(),
18427
19005
  draft: boolean2().optional(),
18428
19006
  target_branch: string2().optional(),
18429
- babysit: boolean2().optional()
19007
+ babysit: boolean2().optional(),
19008
+ cwd: string2().min(1).optional()
18430
19009
  }).strict()
18431
19010
  }).strict(),
18432
19011
  object({
18433
19012
  operation: literal("hosting.merge"),
18434
19013
  payload: object({
18435
19014
  target_branch: string2().min(1).optional(),
18436
- source_branch: string2().min(1).optional()
19015
+ source_branch: string2().min(1).optional(),
19016
+ cwd: string2().min(1).optional()
18437
19017
  }).strict()
18438
19018
  }).strict(),
19019
+ object({
19020
+ operation: literal("hosting.delete_branch"),
19021
+ payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
19022
+ }).strict(),
18439
19023
  object({
18440
19024
  operation: literal("youtrack.update"),
18441
19025
  payload: object({
@@ -18491,12 +19075,13 @@ var externalActionSchema = discriminatedUnion("operation", [
18491
19075
  issueRef: string2().optional(),
18492
19076
  mode: string2().optional(),
18493
19077
  specPath: string2().optional(),
18494
- planPath: string2().optional()
19078
+ planPath: string2().optional(),
19079
+ cwd: string2().min(1).optional()
18495
19080
  }).strict()
18496
19081
  }).strict()
18497
19082
  ]);
18498
19083
  // packages/workit-core/src/core/task-engine.ts
18499
- import { realpathSync as realpathSync5 } from "node:fs";
19084
+ import { realpathSync as realpathSync6 } from "node:fs";
18500
19085
 
18501
19086
  // packages/workit-core/src/core/policy-resolver.ts
18502
19087
  var resolverInputSchema = object({
@@ -19041,6 +19626,18 @@ var portableTask = (task) => {
19041
19626
  };
19042
19627
  for (const entry of clone.assessments)
19043
19628
  demote(entry.data.facts, entry.data.signals, entry.data.consequences);
19629
+ const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
19630
+ for (const entry of clone.decisions) {
19631
+ const consumption = sourceDecisions.get(entry.id)?.data.consumption;
19632
+ if (!consumption)
19633
+ continue;
19634
+ entry.data.consumption = {
19635
+ ...consumption,
19636
+ state: consumption.state === "reserved" ? "uncertain" : consumption.state,
19637
+ actionRef: { kind: "record", collection: "decisions", id: entry.id },
19638
+ ...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
19639
+ };
19640
+ }
19044
19641
  clone.candidates = [];
19045
19642
  return clone;
19046
19643
  };
@@ -19166,7 +19763,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
19166
19763
  taskId: "",
19167
19764
  workspaceId: destinationWorkspaceId
19168
19765
  },
19169
- consumption: null
19766
+ consumption: data.consumption ? {
19767
+ ...data.consumption,
19768
+ state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
19769
+ actionRef: { kind: "record", collection: "decisions", id: entry.id }
19770
+ } : null
19170
19771
  }
19171
19772
  };
19172
19773
  }),
@@ -19207,7 +19808,7 @@ class WorkitCore {
19207
19808
  }
19208
19809
  contextRootError() {
19209
19810
  try {
19210
- if (realpathSync5(this.context.root) !== this.store.root)
19811
+ if (realpathSync6(this.context.root) !== this.store.root)
19211
19812
  return failure("invalid_input", "operation context root does not match the task store root");
19212
19813
  } catch {
19213
19814
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -19366,7 +19967,7 @@ class WorkitCore {
19366
19967
  return parsed;
19367
19968
  const input = parsed.data;
19368
19969
  if ((this.context.workerId ?? null) !== null) {
19369
- if (input.action !== "inspect")
19970
+ if (input.action !== "inspect" && input.action !== "list")
19370
19971
  return failure("permission_denied", "helpers cannot control task lifecycle or scope");
19371
19972
  }
19372
19973
  switch (input.action) {
@@ -19393,7 +19994,18 @@ class WorkitCore {
19393
19994
  return listed;
19394
19995
  const status = input.status ?? "open";
19395
19996
  const limit = input.limit ?? 20;
19396
- const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
19997
+ const query = input.query?.toLowerCase().split(/\s+/);
19998
+ const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").filter((task) => !query || query.every((term) => JSON.stringify({
19999
+ id: task.id,
20000
+ workspaceId: task.workspaceId,
20001
+ origin: task.origin,
20002
+ createdAt: task.createdAt,
20003
+ updatedAt: task.updatedAt,
20004
+ source: task.intent.provenance,
20005
+ objective: task.intent.data.objective,
20006
+ progress: task.progress,
20007
+ decisions: task.decisions.map(({ data }) => data)
20008
+ }).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
19397
20009
  if (selected.length === 0)
19398
20010
  return success(null, null, []);
19399
20011
  const workspace = this.store.readWorkspace();
@@ -19412,7 +20024,11 @@ class WorkitCore {
19412
20024
  status: task.status,
19413
20025
  closure: task.closure,
19414
20026
  progress: task.progress,
19415
- writer: task.status === "closed" ? null : workspace.data.writer
20027
+ writer: task.status === "closed" ? null : workspace.data.writer,
20028
+ source: {
20029
+ host: task.intent.provenance.host,
20030
+ kind: task.intent.provenance.kind
20031
+ }
19416
20032
  }));
19417
20033
  return success(null, null, tasks);
19418
20034
  }
@@ -20508,7 +21124,7 @@ class WorkitCore {
20508
21124
  if (view.task.workspaceId !== view.workspace.id)
20509
21125
  return failure("invalid_input", "task and workspace bindings are invalid");
20510
21126
  try {
20511
- if (realpathSync5(view.workspace.root) !== this.store.root)
21127
+ if (realpathSync6(view.workspace.root) !== this.store.root)
20512
21128
  return failure("invalid_input", "task view root does not match the task store");
20513
21129
  } catch {
20514
21130
  return failure("invalid_input", "task view root cannot be resolved");
@@ -20785,22 +21401,22 @@ function resolveGitRevision(root, value) {
20785
21401
  }
20786
21402
  }
20787
21403
  function resolveInside(root, candidate) {
20788
- const base = realpathSync6(root);
20789
- const target = path10.resolve(base, candidate);
20790
- if (target !== base && !target.startsWith(base + path10.sep)) {
21404
+ const base = realpathSync7(root);
21405
+ const target = path11.resolve(base, candidate);
21406
+ if (target !== base && !target.startsWith(base + path11.sep)) {
20791
21407
  throw new Error("path must stay inside repository root");
20792
21408
  }
20793
21409
  let ancestor = target;
20794
21410
  while (!existsSync5(ancestor))
20795
- ancestor = path10.dirname(ancestor);
20796
- const canonicalAncestor = realpathSync6(ancestor);
20797
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path10.sep)) {
21411
+ ancestor = path11.dirname(ancestor);
21412
+ const canonicalAncestor = realpathSync7(ancestor);
21413
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
20798
21414
  throw new Error("path must stay inside repository root");
20799
21415
  }
20800
21416
  return target;
20801
21417
  }
20802
21418
  function run2(root, executable, args, env = {}) {
20803
- const cwd = realpathSync6(root);
21419
+ const cwd = realpathSync7(root);
20804
21420
  const result = spawnSync3(executable, args, {
20805
21421
  cwd,
20806
21422
  encoding: "utf8",
@@ -20824,8 +21440,8 @@ var SUPPORT_MATRIX = {
20824
21440
  };
20825
21441
 
20826
21442
  // packages/workit-mcp/src/server.ts
20827
- import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync7 } from "node:fs";
20828
- import path14 from "node:path";
21443
+ import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
21444
+ import path15 from "node:path";
20829
21445
 
20830
21446
  // node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
20831
21447
  var NEVER2 = Object.freeze({
@@ -24352,7 +24968,7 @@ function check(fn) {
24352
24968
  ch._zod.check = fn;
24353
24969
  return ch;
24354
24970
  }
24355
- function custom(fn, _params) {
24971
+ function custom2(fn, _params) {
24356
24972
  return _custom2(ZodCustom2, fn ?? (() => true), _params);
24357
24973
  }
24358
24974
  function refine2(fn, _params = {}) {
@@ -24389,7 +25005,7 @@ var LATEST_PROTOCOL_VERSION = "2025-11-25";
24389
25005
  var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
24390
25006
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
24391
25007
  var JSONRPC_VERSION = "2.0";
24392
- var AssertObjectSchema = custom((v) => v !== null && (typeof v === "object" || typeof v === "function"));
25008
+ var AssertObjectSchema = custom2((v) => v !== null && (typeof v === "object" || typeof v === "function"));
24393
25009
  var ProgressTokenSchema = union2([string4(), number4().int()]);
24394
25010
  var CursorSchema = string4();
24395
25011
  var TaskCreationParamsSchema = looseObject({
@@ -26735,18 +27351,19 @@ var redactSecrets = (value) => applyPatterns(value);
26735
27351
 
26736
27352
  // packages/workit-core/src/core/pr-create.ts
26737
27353
  import fs8 from "node:fs";
26738
- import path11 from "node:path";
27354
+ import path12 from "node:path";
27355
+ import { spawnSync as spawnSync4 } from "node:child_process";
26739
27356
  function parseGhIssue(value) {
26740
27357
  const m = /issues\/(\d+)/.exec(value);
26741
27358
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26742
27359
  }
26743
27360
  function whichOnPath(tool) {
26744
27361
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26745
- for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
27362
+ for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
26746
27363
  if (!dir)
26747
27364
  continue;
26748
27365
  for (const name of names) {
26749
- const candidate = path11.join(dir, name);
27366
+ const candidate = path12.join(dir, name);
26750
27367
  try {
26751
27368
  fs8.accessSync(candidate, fs8.constants.X_OK);
26752
27369
  return candidate;
@@ -26756,11 +27373,21 @@ function whichOnPath(tool) {
26756
27373
  return null;
26757
27374
  }
26758
27375
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
27376
+ var pushRemote = (cwd) => {
27377
+ const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
27378
+ cwd,
27379
+ encoding: "utf8"
27380
+ });
27381
+ if (result.status !== 0)
27382
+ return null;
27383
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
27384
+ return urls.length === 1 ? urls[0] : null;
27385
+ };
26759
27386
 
26760
27387
  // packages/workit-core/src/core/repo-context.ts
26761
- import { spawnSync as spawnSync4 } from "node:child_process";
27388
+ import { spawnSync as spawnSync5 } from "node:child_process";
26762
27389
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26763
- import path12 from "node:path";
27390
+ import path13 from "node:path";
26764
27391
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26765
27392
  try {
26766
27393
  gitRevisionParts(value);
@@ -26787,7 +27414,7 @@ var invalidRange = (cwd) => ({
26787
27414
  cwd
26788
27415
  });
26789
27416
  var runGit = (cwd, args) => {
26790
- const result = spawnSync4("git", args, { cwd, encoding: "utf8" });
27417
+ const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
26791
27418
  return {
26792
27419
  stdout: (result.stdout ?? "").trimEnd(),
26793
27420
  stderr: (result.stderr ?? "").trimEnd(),
@@ -26894,7 +27521,7 @@ var findPrTemplate = (cwd) => {
26894
27521
  ]) {
26895
27522
  const parts = rel.split("/");
26896
27523
  const base = parts[parts.length - 1];
26897
- const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
27524
+ const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
26898
27525
  const actual = probeCaseInsensitive(dir, base);
26899
27526
  if (actual)
26900
27527
  return [...parts.slice(0, -1), actual].join("/");
@@ -26925,7 +27552,7 @@ var diffStatForRange = (cwd, range) => {
26925
27552
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26926
27553
  };
26927
27554
  var packageScripts = (cwd, keys) => {
26928
- const pkgPath = path12.join(cwd, "package.json");
27555
+ const pkgPath = path13.join(cwd, "package.json");
26929
27556
  if (!existsSync6(pkgPath))
26930
27557
  return [];
26931
27558
  try {
@@ -26951,9 +27578,9 @@ var documentationFiles = (cwd) => {
26951
27578
  if (entry.isDirectory()) {
26952
27579
  if (excluded.has(entry.name))
26953
27580
  continue;
26954
- walk(path12.join(dir, entry.name), depth + 1);
27581
+ walk(path13.join(dir, entry.name), depth + 1);
26955
27582
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26956
- matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
27583
+ matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
26957
27584
  }
26958
27585
  }
26959
27586
  };
@@ -26969,7 +27596,7 @@ var readTrimmed = (file, maxLines) => {
26969
27596
  `);
26970
27597
  };
26971
27598
  function prReadyContext(root, range) {
26972
- const cwd = path12.resolve(repoRoot(root));
27599
+ const cwd = path13.resolve(repoRoot(root));
26973
27600
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26974
27601
  return invalidRange(cwd);
26975
27602
  let stdout = "";
@@ -27027,7 +27654,7 @@ ${status.stderr}` : "");
27027
27654
  stdout += `template_path: ${template}
27028
27655
 
27029
27656
  `;
27030
- stdout += readTrimmed(path12.join(cwd, template), 220);
27657
+ stdout += readTrimmed(path13.join(cwd, template), 220);
27031
27658
  } else {
27032
27659
  stdout += `template_path: none
27033
27660
 
@@ -27037,7 +27664,7 @@ ${status.stderr}` : "");
27037
27664
  stdout += `
27038
27665
  `;
27039
27666
  stdout += printSection("Recent Validation Signals");
27040
- if (existsSync6(path12.join(cwd, "package.json"))) {
27667
+ if (existsSync6(path13.join(cwd, "package.json"))) {
27041
27668
  stdout += `package.json detected. Common scripts:
27042
27669
  `;
27043
27670
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -27046,7 +27673,7 @@ ${status.stderr}` : "");
27046
27673
  `) + `
27047
27674
  `;
27048
27675
  }
27049
- if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
27676
+ if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
27050
27677
  stdout += `Rust project detected.
27051
27678
  `;
27052
27679
  }
@@ -27079,7 +27706,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
27079
27706
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
27080
27707
  - If Unreleased already has duplicate category headings, normalize_only first.`;
27081
27708
  function changelogContext(root, range) {
27082
- const cwd = path12.resolve(repoRoot(root));
27709
+ const cwd = path13.resolve(repoRoot(root));
27083
27710
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27084
27711
  return invalidRange(cwd);
27085
27712
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27097,7 +27724,7 @@ function changelogContext(root, range) {
27097
27724
  stdout += CHANGELOG_RULES + `
27098
27725
  `;
27099
27726
  stdout += printSection("Existing CHANGELOG.md");
27100
- const changelogPath = path12.join(cwd, "CHANGELOG.md");
27727
+ const changelogPath = path13.join(cwd, "CHANGELOG.md");
27101
27728
  if (existsSync6(changelogPath)) {
27102
27729
  stdout += readTrimmed(changelogPath, 260) + `
27103
27730
  `;
@@ -27117,7 +27744,7 @@ function changelogContext(root, range) {
27117
27744
  return { stdout, stderr: "", exitCode: 0, cwd };
27118
27745
  }
27119
27746
  function docsRefreshContext(root, range) {
27120
- const cwd = path12.resolve(repoRoot(root));
27747
+ const cwd = path13.resolve(repoRoot(root));
27121
27748
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27122
27749
  return invalidRange(cwd);
27123
27750
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27139,7 +27766,7 @@ function docsRefreshContext(root, range) {
27139
27766
  `) + `
27140
27767
  `;
27141
27768
  stdout += printSection("README Preview");
27142
- const readme = path12.join(cwd, "README.md");
27769
+ const readme = path13.join(cwd, "README.md");
27143
27770
  if (existsSync6(readme)) {
27144
27771
  stdout += readTrimmed(readme, 220) + `
27145
27772
  `;
@@ -27148,7 +27775,7 @@ function docsRefreshContext(root, range) {
27148
27775
  `;
27149
27776
  }
27150
27777
  stdout += printSection("Package Scripts");
27151
- const pkgPath = path12.join(cwd, "package.json");
27778
+ const pkgPath = path13.join(cwd, "package.json");
27152
27779
  if (existsSync6(pkgPath)) {
27153
27780
  try {
27154
27781
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
@@ -27162,7 +27789,7 @@ function docsRefreshContext(root, range) {
27162
27789
  return { stdout, stderr: "", exitCode: 0, cwd };
27163
27790
  }
27164
27791
  function releaseNotesContext(root, rangeOrTag) {
27165
- const cwd = path12.resolve(repoRoot(root));
27792
+ const cwd = path13.resolve(repoRoot(root));
27166
27793
  if (!rangeOrTag) {
27167
27794
  return { stdout: "", stderr: `ERROR: release tag or range required
27168
27795
  `, exitCode: 1, cwd };
@@ -27200,7 +27827,7 @@ function releaseNotesContext(root, rangeOrTag) {
27200
27827
  `;
27201
27828
  stdout += printSection("Existing Release Files");
27202
27829
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
27203
- if (existsSync6(path12.join(cwd, rel)))
27830
+ if (existsSync6(path13.join(cwd, rel)))
27204
27831
  stdout += rel + `
27205
27832
  `;
27206
27833
  }
@@ -27228,19 +27855,7 @@ function releaseNotesContext(root, rangeOrTag) {
27228
27855
  }
27229
27856
 
27230
27857
  // packages/workit-core/src/core/tracker-issues.ts
27231
- import fs9 from "node:fs";
27232
- import { spawnSync as spawnSync5 } from "node:child_process";
27233
- var trackerRequest = async (url, init) => {
27234
- try {
27235
- const res = await fetch(url, { method: init.method, headers: init.headers });
27236
- const text = await res.text();
27237
- if (!res.ok)
27238
- return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
27239
- return { status: 0, stdout: text, stderr: "" };
27240
- } catch (err) {
27241
- return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
27242
- }
27243
- };
27858
+ import { spawnSync as spawnSync6 } from "node:child_process";
27244
27859
  var repoPathFromRemote = (remote) => {
27245
27860
  let rest = (remote || "").trim().replace(/\/+$/, "");
27246
27861
  if (!rest)
@@ -27261,7 +27876,11 @@ var defaultCli = (bin, available) => {
27261
27876
  if (!available)
27262
27877
  return null;
27263
27878
  return (args, root) => {
27264
- const out = spawnSync5(bin, args, { cwd: root, encoding: "utf8" });
27879
+ const out = spawnSync6(bin, args, {
27880
+ cwd: root,
27881
+ encoding: "utf8",
27882
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
27883
+ });
27265
27884
  return Promise.resolve({
27266
27885
  status: out.status ?? 1,
27267
27886
  stdout: out.stdout ?? "",
@@ -27269,49 +27888,22 @@ var defaultCli = (bin, available) => {
27269
27888
  });
27270
27889
  };
27271
27890
  };
27272
- var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
27891
+ var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
27273
27892
  if (!cli)
27274
- return null;
27893
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27275
27894
  const run = cli(args, root);
27276
27895
  if (!run)
27277
- return null;
27896
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27278
27897
  const out = await run;
27279
27898
  if (out.status !== 0)
27280
- return null;
27899
+ return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
27281
27900
  try {
27282
- return parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27901
+ const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27902
+ return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
27283
27903
  } catch {
27284
- return null;
27904
+ return { error: `invalid ${name} issue response`, kind: "request" };
27285
27905
  }
27286
27906
  };
27287
- var originRemote = (root) => {
27288
- const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
27289
- return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
27290
- };
27291
- var trackerCreds = (root, provider) => {
27292
- const cfg = vcsConfig("load", root);
27293
- if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
27294
- return { error: `unconfigured ${provider} provider` };
27295
- let token = "";
27296
- try {
27297
- token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
27298
- } catch {
27299
- return { error: `missing ${provider} token` };
27300
- }
27301
- if (!token)
27302
- return { error: `empty ${provider} token file` };
27303
- if (provider === "gitlab") {
27304
- const gitlab = cfg.gitlab;
27305
- const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
27306
- return { token, api: apiUrl };
27307
- }
27308
- const github = cfg.github;
27309
- const host = String(github?.host ?? "github.com").toLowerCase();
27310
- return {
27311
- token,
27312
- api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
27313
- };
27314
- };
27315
27907
  var parseTriple = (parsed, id, idKeys, bodyKeys) => {
27316
27908
  const title = parsed.title;
27317
27909
  if (typeof title !== "string")
@@ -27334,62 +27926,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
27334
27926
  const id = parseGhIssue(ref);
27335
27927
  if (!/^\d+$/.test(id))
27336
27928
  return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
27337
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27929
+ const remote = (deps.remote ?? pushRemote)(root);
27930
+ const repo = repoPathFromRemote(remote ?? "");
27338
27931
  if (!repo)
27339
27932
  return { error: "no origin remote to resolve the repository", kind: "input" };
27340
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), ["issue", "view", id, "--repo", repo, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27341
- if (viaCli)
27342
- return { data: viaCli };
27343
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "github")))(root);
27344
- if ("error" in credentials)
27345
- return { ...credentials, kind: "creds" };
27346
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
27347
- method: "GET",
27348
- headers: {
27349
- Authorization: `Bearer ${credentials.token}`,
27350
- Accept: "application/vnd.github+json"
27351
- }
27352
- });
27353
- if (out.status !== 0)
27354
- return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
27355
- try {
27356
- const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
27357
- if (!triple)
27358
- return { error: "unexpected GitHub issue shape", kind: "request" };
27359
- return { data: triple };
27360
- } catch {
27361
- return { error: "invalid GitHub issue response", kind: "request" };
27362
- }
27933
+ const cfg = vcsConfig("load", root);
27934
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
27935
+ if (!remote || !hostingApiHostMatches(remote, host))
27936
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27937
+ const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
27938
+ return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27363
27939
  };
27364
27940
  var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
27365
27941
  const id = parseGhIssue(ref);
27366
27942
  if (!/^\d+$/.test(id))
27367
27943
  return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
27368
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27944
+ const remote = (deps.remote ?? pushRemote)(root);
27945
+ const repo = repoPathFromRemote(remote ?? "");
27369
27946
  if (!repo)
27370
27947
  return { error: "no origin remote to resolve the project", kind: "input" };
27371
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), ["issue", "view", id, "-R", repo, "-F", "json"], root, id, ["iid"], ["description"]);
27372
- if (viaCli)
27373
- return { data: viaCli };
27374
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "gitlab")))(root);
27375
- if ("error" in credentials)
27376
- return { ...credentials, kind: "creds" };
27377
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
27378
- if (out.status !== 0)
27379
- return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
27380
- try {
27381
- const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
27382
- if (!triple)
27383
- return { error: "unexpected GitLab issue shape", kind: "request" };
27384
- return { data: triple };
27385
- } catch {
27386
- return { error: "invalid GitLab issue response", kind: "request" };
27387
- }
27948
+ const cfg = vcsConfig("load", root);
27949
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
27950
+ if (!remote || !hostingApiHostMatches(remote, host))
27951
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27952
+ const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
27953
+ return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
27388
27954
  };
27389
27955
 
27390
27956
  // packages/workit-core/src/core/youtrack.ts
27391
- import fs10 from "node:fs";
27392
- import path13 from "node:path";
27957
+ import fs9 from "node:fs";
27958
+ import path14 from "node:path";
27393
27959
 
27394
27960
  // packages/workit-core/src/core/templates.ts
27395
27961
  var repoRoot2 = assetRoot();
@@ -27401,23 +27967,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
27401
27967
 
27402
27968
  // packages/workit-core/src/core/youtrack.ts
27403
27969
  var ISSUE_RE = /^[A-Z]+-\d+$/;
27404
- var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
27405
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path13.join(configDir(), "youtrack.json");
27406
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path13.join(resolveConfigDir(), "youtrack.json");
27970
+ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
27971
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
27972
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
27407
27973
  var youTrackTokenModeOk = (p) => {
27408
27974
  if (process.platform === "win32")
27409
27975
  return true;
27410
- const mode = fs10.statSync(p).mode & 511;
27976
+ const mode = fs9.statSync(p).mode & 511;
27411
27977
  return mode === 384;
27412
27978
  };
27413
27979
  function readYouTrackConfig(required) {
27414
27980
  const cfgPath = youTrackConfigPath();
27415
- if (!fs10.existsSync(cfgPath)) {
27981
+ if (!fs9.existsSync(cfgPath)) {
27416
27982
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
27417
27983
  }
27418
27984
  let parsed;
27419
27985
  try {
27420
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
27986
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27421
27987
  } catch {
27422
27988
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
27423
27989
  }
@@ -27428,11 +27994,11 @@ function readYouTrackConfig(required) {
27428
27994
  }
27429
27995
  var readYouTrackContextConfig = () => {
27430
27996
  const cfgPath = youTrackReadOnlyConfigPath();
27431
- if (!fs10.existsSync(cfgPath))
27997
+ if (!fs9.existsSync(cfgPath))
27432
27998
  return { data: {} };
27433
27999
  let parsed;
27434
28000
  try {
27435
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28001
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27436
28002
  } catch {
27437
28003
  return { error: "YouTrack configuration is unavailable" };
27438
28004
  }
@@ -27457,12 +28023,12 @@ function youTrackGreeting(configOverride) {
27457
28023
  const cfgPath = configOverride ?? youTrackConfigPath();
27458
28024
  let parsed;
27459
28025
  try {
27460
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28026
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27461
28027
  } catch {
27462
28028
  return {
27463
28029
  stdout: "",
27464
28030
  exitCode: 1,
27465
- stderr: fs10.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
28031
+ stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
27466
28032
  };
27467
28033
  }
27468
28034
  if (!isConfigObject(parsed)) {
@@ -27508,15 +28074,15 @@ var youTrackToken = () => {
27508
28074
  if ("error" in loaded)
27509
28075
  return loaded;
27510
28076
  const tokenFile = String(loaded.config.tokenFile ?? "");
27511
- const tokenPath = tokenFile ? path13.isAbsolute(tokenFile) ? path13.resolve(tokenFile) : path13.resolve(process.cwd(), tokenFile) : "";
27512
- if (!tokenPath || !fs10.existsSync(tokenPath))
28077
+ const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
28078
+ if (!tokenPath || !fs9.existsSync(tokenPath))
27513
28079
  return { error: "missing youtrack.token" };
27514
28080
  if (!youTrackTokenModeOk(tokenPath))
27515
28081
  return { error: "youtrack.token mode must be 0600" };
27516
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
28082
+ const token = fs9.readFileSync(tokenPath, "utf8").trim();
27517
28083
  if (!token)
27518
28084
  return { error: "empty token file" };
27519
- if (token === TOKEN_PLACEHOLDER2 || token.startsWith(TOKEN_PLACEHOLDER2)) {
28085
+ if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
27520
28086
  return {
27521
28087
  error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
27522
28088
  };
@@ -27579,9 +28145,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
27579
28145
  } catch {
27580
28146
  continue;
27581
28147
  }
27582
- if (!fs10.existsSync(full))
28148
+ if (!fs9.existsSync(full))
27583
28149
  continue;
27584
- const text = fs10.readFileSync(full, "utf8");
28150
+ const text = fs9.readFileSync(full, "utf8");
27585
28151
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
27586
28152
  if (m)
27587
28153
  return m[1];
@@ -27815,6 +28381,7 @@ var readExternalContext = async (root, payload) => {
27815
28381
  }
27816
28382
  }
27817
28383
  };
28384
+ var targetWriterActionLease = Symbol("target-writer-action-lease");
27818
28385
 
27819
28386
  // packages/workit-mcp/src/server.ts
27820
28387
  class McpCapabilityUnavailableError extends Error {
@@ -27859,13 +28426,13 @@ var rootVariants = (workspaceRoot) => {
27859
28426
  if (!workspaceRoot)
27860
28427
  return [];
27861
28428
  const roots = new Set;
27862
- for (const candidate of [workspaceRoot, path14.resolve(workspaceRoot)]) {
27863
- if (path14.isAbsolute(candidate) && candidate !== path14.parse(candidate).root)
28429
+ for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
28430
+ if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
27864
28431
  roots.add(candidate);
27865
28432
  }
27866
28433
  try {
27867
- const canonical = realpathSync7(workspaceRoot);
27868
- if (canonical !== path14.parse(canonical).root)
28434
+ const canonical = realpathSync8(workspaceRoot);
28435
+ if (canonical !== path15.parse(canonical).root)
27869
28436
  roots.add(canonical);
27870
28437
  } catch {}
27871
28438
  return [...roots].sort((left, right) => right.length - left.length);
@@ -27966,11 +28533,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
27966
28533
  };
27967
28534
  function createMcpServer(host, contextProvider) {
27968
28535
  assertMcpHost(host);
27969
- const here = path14.dirname(fileURLToPath2(import.meta.url));
28536
+ const here = path15.dirname(fileURLToPath2(import.meta.url));
27970
28537
  const sourceMarker = markSourcesLoaded([
27971
- path14.join(here, "server.ts"),
27972
- path14.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
27973
- path14.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
28538
+ path15.join(here, "server.ts"),
28539
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
28540
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
27974
28541
  ].filter((file) => existsSync7(file)));
27975
28542
  let staleWarned = false;
27976
28543
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
@@ -28251,6 +28818,7 @@ var allowedKeys = {
28251
28818
  "stop_hook_active"
28252
28819
  ])
28253
28820
  };
28821
+ var historyOfferSessions = new Set;
28254
28822
  if (false)
28255
28823
  ;
28256
28824
 
@@ -28263,14 +28831,14 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
28263
28831
  var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
28264
28832
  const explicit = env.WORKFLOW_WORKSPACE_ROOT;
28265
28833
  const inherited = env.PWD;
28266
- const candidate = explicit ?? (inherited && path15.resolve(inherited) !== path15.resolve(pluginRoot) ? inherited : undefined);
28267
- if (!candidate || !path15.isAbsolute(candidate) || !existsSync8(candidate))
28834
+ const candidate = explicit ?? (inherited && path16.resolve(inherited) !== path16.resolve(pluginRoot) ? inherited : undefined);
28835
+ if (!candidate || !path16.isAbsolute(candidate) || !existsSync8(candidate))
28268
28836
  return null;
28269
28837
  try {
28270
- if (!statSync2(candidate).isDirectory())
28838
+ if (!statSync3(candidate).isDirectory())
28271
28839
  return null;
28272
- const canonical = realpathSync8(candidate);
28273
- if (canonical === realpathSync8(pluginRoot)) {
28840
+ const canonical = realpathSync9(candidate);
28841
+ if (canonical === realpathSync9(pluginRoot)) {
28274
28842
  if (!explicit)
28275
28843
  return null;
28276
28844
  const workspace = new TaskStore(canonical).readWorkspace();