@brainervirus/workit-mcp 1.2.0 → 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.
Files changed (2) hide show
  1. package/dist/index.js +955 -315
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -6620,11 +6620,11 @@ var require_dist = __commonJS(function(exports, module) {
6620
6620
 
6621
6621
  // packages/workit-mcp/src/index.ts
6622
6622
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6623
- import path15 from "node:path";
6623
+ import path16 from "node:path";
6624
6624
 
6625
6625
  // packages/workit-mcp/src/server.ts
6626
- import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync7 } from "node:fs";
6627
- import path14 from "node:path";
6626
+ import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
6627
+ import path15 from "node:path";
6628
6628
 
6629
6629
  // node_modules/@modelcontextprotocol/sdk/node_modules/zod/v4/core/core.js
6630
6630
  var NEVER = Object.freeze({
@@ -12477,8 +12477,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
12477
12477
 
12478
12478
  // packages/workit-core/src/core.ts
12479
12479
  import { spawnSync as spawnSync3 } from "node:child_process";
12480
- import { existsSync as existsSync5, realpathSync as realpathSync6 } from "node:fs";
12481
- import path10 from "node:path";
12480
+ import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
12481
+ import path11 from "node:path";
12482
12482
 
12483
12483
  // packages/workit-core/src/core/task-contract.ts
12484
12484
  import { createHash, randomUUID } from "node:crypto";
@@ -17785,6 +17785,17 @@ function _array2(Class, element, params) {
17785
17785
  ...normalizeParams2(params)
17786
17786
  });
17787
17787
  }
17788
+ function _custom2(Class, fn, _params) {
17789
+ const norm = normalizeParams2(_params);
17790
+ norm.abort ?? (norm.abort = true);
17791
+ const schema = new Class({
17792
+ type: "custom",
17793
+ check: "custom",
17794
+ fn,
17795
+ ...norm
17796
+ });
17797
+ return schema;
17798
+ }
17788
17799
  function _refine2(Class, fn, _params) {
17789
17800
  const schema = new Class({
17790
17801
  type: "custom",
@@ -19938,6 +19949,9 @@ var ZodCustom2 = /* @__PURE__ */ $constructor2("ZodCustom", (inst, def) => {
19938
19949
  ZodType2.init(inst, def);
19939
19950
  inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
19940
19951
  });
19952
+ function custom2(fn, _params) {
19953
+ return _custom2(ZodCustom2, fn ?? (() => true), _params);
19954
+ }
19941
19955
  function refine2(fn, _params = {}) {
19942
19956
  return _refine2(ZodCustom2, fn, _params);
19943
19957
  }
@@ -20371,7 +20385,8 @@ var decisionSchema = object4({
20371
20385
  consumption: object4({
20372
20386
  state: _enum2(["reserved", "consumed", "uncertain"]),
20373
20387
  at: utc,
20374
- actionRef: refSchema
20388
+ actionRef: refSchema,
20389
+ historicalActionId: text.optional()
20375
20390
  }).strict().nullable()
20376
20391
  }).strict();
20377
20392
  var findingSchema = object4({
@@ -20402,6 +20417,7 @@ var workerSchema = object4({
20402
20417
  assignment: assignmentSchema,
20403
20418
  state: _enum2(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
20404
20419
  session: refSchema.nullable(),
20420
+ coordinator: refSchema.optional(),
20405
20421
  report: workerReportSchema.nullable()
20406
20422
  }).strict();
20407
20423
  var ownerSchema = object4({
@@ -20513,6 +20529,7 @@ var taskSummarySchema = object4({
20513
20529
  requirements: array3(requirementEvaluationSchema),
20514
20530
  writer: workspaceRecordSchema.shape.writer
20515
20531
  }).strict();
20532
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
20516
20533
  var exportBundleSchema = object4({
20517
20534
  schemaVersion: literal3(1),
20518
20535
  exportedAt: utc,
@@ -20534,11 +20551,16 @@ var taskOperations = {
20534
20551
  expectedWorkspaceRevision: revision.nullable().optional(),
20535
20552
  intent: intentSchema
20536
20553
  }),
20537
- list: operation({ action: literal3("list") }),
20554
+ list: operation({
20555
+ action: literal3("list"),
20556
+ status: _enum2(["open", "closed", "all"]).optional(),
20557
+ query: string5().trim().min(1).max(200).optional(),
20558
+ limit: number5().int().min(1).max(50).optional()
20559
+ }),
20538
20560
  inspect: operation({
20539
20561
  action: literal3("inspect"),
20540
20562
  ...taskId,
20541
- view: _enum2(["summary", "full"])
20563
+ view: _enum2(["summary", "full"]).optional()
20542
20564
  }),
20543
20565
  revise: operation({
20544
20566
  action: literal3("revise"),
@@ -20566,7 +20588,7 @@ var taskOperations = {
20566
20588
  ...taskId,
20567
20589
  ...revisions,
20568
20590
  expectedWorkspaceRevision: revision.optional(),
20569
- authorityRefs: array3(refSchema)
20591
+ authorityRefs: array3(refSchema).optional()
20570
20592
  }),
20571
20593
  close: operation({
20572
20594
  action: literal3("close"),
@@ -20880,6 +20902,7 @@ function requirementId(input) {
20880
20902
  }
20881
20903
  // packages/workit-core/src/core/task-store.ts
20882
20904
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
20905
+ import { AsyncLocalStorage } from "node:async_hooks";
20883
20906
  import * as fs4 from "node:fs";
20884
20907
  import { hostname as hostname2 } from "node:os";
20885
20908
  import path4 from "node:path";
@@ -21515,7 +21538,8 @@ var metadataLockSchema = object4({
21515
21538
  pid: number5().int().nonnegative().safe(),
21516
21539
  processStart: string5().nullable(),
21517
21540
  host: string5().min(1),
21518
- nonce: string5().min(1)
21541
+ nonce: string5().min(1),
21542
+ externalAction: literal3(true).optional()
21519
21543
  }).strict();
21520
21544
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21521
21545
  var cachedRuntimeVersion = null;
@@ -21567,6 +21591,28 @@ var sameMetadataLock = (left, right) => {
21567
21591
  const parsed = metadataLockSchema.safeParse(left);
21568
21592
  return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
21569
21593
  };
21594
+ var sameDirectoryIdentity = (left, right) => {
21595
+ if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
21596
+ return false;
21597
+ const normalizedLeft = path4.resolve(left);
21598
+ const normalizedRight = path4.resolve(right);
21599
+ try {
21600
+ const a = fs4.statSync(normalizedLeft, { bigint: true });
21601
+ const b = fs4.statSync(normalizedRight, { bigint: true });
21602
+ if (!a.isDirectory() || !b.isDirectory())
21603
+ return false;
21604
+ if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
21605
+ return true;
21606
+ const canonicalLeft = fs4.realpathSync(normalizedLeft);
21607
+ const canonicalRight = fs4.realpathSync(normalizedRight);
21608
+ if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
21609
+ return true;
21610
+ return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
21611
+ } catch {
21612
+ return false;
21613
+ }
21614
+ };
21615
+ var externalActionLockRoots = new AsyncLocalStorage;
21570
21616
 
21571
21617
  class TaskStore {
21572
21618
  root;
@@ -21627,7 +21673,7 @@ class TaskStore {
21627
21673
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
21628
21674
  if (!item.exists)
21629
21675
  return success(null, null, null);
21630
- if (item.result.ok && item.result.data.root !== this.root)
21676
+ if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
21631
21677
  return failure("recovery_required", "workspace root binding is invalid", {
21632
21678
  path: this.workspacePath
21633
21679
  });
@@ -21718,6 +21764,29 @@ class TaskStore {
21718
21764
  const current = this.readWorkspace();
21719
21765
  if (!current.ok)
21720
21766
  return current;
21767
+ const origin = input.task.origin;
21768
+ if (current.data && origin) {
21769
+ if (origin.workspaceId === current.data.id) {
21770
+ const source = this.readTask(origin.taskId);
21771
+ if (source.ok)
21772
+ return success(source.data.revision, current.data.revision, source.data);
21773
+ if (source.code !== "not_found")
21774
+ return source;
21775
+ return failure("not_found", "source task is not present in this shared workspace");
21776
+ }
21777
+ const tasks = this.listTasks();
21778
+ if (!tasks.ok)
21779
+ return tasks;
21780
+ const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
21781
+ if (prior.length > 1)
21782
+ return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
21783
+ if (prior.length === 1) {
21784
+ const existing = prior[0];
21785
+ if (existing.origin.exportDigest !== origin.exportDigest)
21786
+ return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
21787
+ return success(existing.revision, current.data.revision, existing);
21788
+ }
21789
+ }
21721
21790
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
21722
21791
  return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21723
21792
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
@@ -22106,6 +22175,73 @@ class TaskStore {
22106
22175
  })
22107
22176
  };
22108
22177
  }
22178
+ externalActionLockOptions() {
22179
+ const options = this.metadataLockOptions();
22180
+ return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
22181
+ }
22182
+ async withExternalActionLock(operation, reentrant = false) {
22183
+ const activeRoots = externalActionLockRoots.getStore();
22184
+ if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
22185
+ return operation();
22186
+ try {
22187
+ this.initializeMutationStorage();
22188
+ } catch (error) {
22189
+ return failure("storage_error", `unable to initialize store: ${String(error)}`, {
22190
+ path: this.workitDir
22191
+ });
22192
+ }
22193
+ let handle;
22194
+ try {
22195
+ handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
22196
+ } catch (error) {
22197
+ return this.lockFailure(error);
22198
+ }
22199
+ let result;
22200
+ try {
22201
+ if (!handle.verifyStillHeld())
22202
+ result = failure("recovery_required", "metadata lock was compromised", {
22203
+ path: this.lockPath
22204
+ });
22205
+ else {
22206
+ try {
22207
+ const run = () => operation();
22208
+ result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
22209
+ } catch {
22210
+ result = failure("external_outcome_unknown", "external action outcome is unknown", {
22211
+ outcome: "unknown"
22212
+ });
22213
+ }
22214
+ try {
22215
+ if (!handle.verifyStillHeld())
22216
+ result = failure("external_outcome_unknown", "metadata lock was compromised", {
22217
+ outcome: "unknown",
22218
+ path: this.lockPath
22219
+ });
22220
+ } catch {
22221
+ result = failure("external_outcome_unknown", "metadata lock could not be verified", {
22222
+ outcome: "unknown",
22223
+ path: this.lockPath
22224
+ });
22225
+ }
22226
+ }
22227
+ } catch {
22228
+ result = failure("external_outcome_unknown", "external action lock operation failed", {
22229
+ outcome: "unknown",
22230
+ path: this.lockPath
22231
+ });
22232
+ }
22233
+ try {
22234
+ handle.release();
22235
+ } catch (error) {
22236
+ const released = this.lockFailure(error);
22237
+ const releaseError = released.ok ? "metadata lock release failed" : released.error;
22238
+ return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
22239
+ outcome: "unknown",
22240
+ path: this.lockPath
22241
+ }) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
22242
+ }
22243
+ return result;
22244
+ }
22109
22245
  recoveryLockOptions(observed, evidence, gate) {
22110
22246
  const options = this.metadataLockOptions();
22111
22247
  options.staleRecovery = "remove-if-unchanged";
@@ -22175,6 +22311,14 @@ class TaskStore {
22175
22311
  lockFailure(error) {
22176
22312
  const value = error;
22177
22313
  const code = typeof value?.code === "string" ? value.code : "";
22314
+ if (code === "EEXIST" || code === "file_lock_timeout") {
22315
+ const lock = this.readLockSnapshot();
22316
+ if (lock.ok && lock.data?.data.externalAction)
22317
+ return failure("writer_conflict", "workspace is reserved by a managed external action", {
22318
+ outcome: "not_started",
22319
+ path: this.lockPath
22320
+ });
22321
+ }
22178
22322
  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));
22179
22323
  return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
22180
22324
  }
@@ -22399,6 +22543,10 @@ var validPath = (value) => typeof value === "string" && value.length > 0 && ![..
22399
22543
  }) && !value.split(/[\\/]/).includes("..");
22400
22544
  var callerValue = (value) => ({ host: value.host, actor: value.actor });
22401
22545
  var workerIdOf = (value) => value.workerId ?? null;
22546
+ var currentWriterOwnsTask = (task, workspace, caller) => {
22547
+ const owner = workspace.writer?.owner;
22548
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
22549
+ };
22402
22550
  function assertProductWriteAllowed(input) {
22403
22551
  if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
22404
22552
  return failure("invalid_input", "invalid product write path");
@@ -22438,21 +22586,23 @@ function assertProductWriteAllowed(input) {
22438
22586
  }
22439
22587
 
22440
22588
  // packages/workit-core/src/core/task-evaluation.ts
22441
- import { createHash as createHash5 } from "node:crypto";
22589
+ import { createHash as createHash6 } from "node:crypto";
22442
22590
  import * as fs7 from "node:fs";
22443
22591
  import { spawnSync as spawnSync2 } from "node:child_process";
22444
- import path9 from "node:path";
22592
+ import path10 from "node:path";
22445
22593
 
22446
22594
  // packages/workit-core/src/core/authority.ts
22447
- import { createHash as createHash4 } from "node:crypto";
22595
+ import { createHash as createHash5 } from "node:crypto";
22448
22596
  import * as fs6 from "node:fs";
22449
- import path8 from "node:path";
22597
+ import path9 from "node:path";
22450
22598
 
22451
22599
  // packages/workit-core/src/core/auto-approval.ts
22452
- import { createHash as createHash3 } from "node:crypto";
22453
- import { readFileSync as readFileSync4 } from "node:fs";
22600
+ import { createHash as createHash4 } from "node:crypto";
22601
+ import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
22602
+ import path8 from "node:path";
22454
22603
 
22455
22604
  // packages/workit-core/src/core/workspaces.ts
22605
+ import { createHash as createHash3 } from "node:crypto";
22456
22606
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
22457
22607
  import path6 from "node:path";
22458
22608
 
@@ -22470,7 +22620,7 @@ import os from "node:os";
22470
22620
  import path5 from "node:path";
22471
22621
 
22472
22622
  // packages/workit-core/src/core/boundary.ts
22473
- import { statSync } from "node:fs";
22623
+ import { statSync as statSync2 } from "node:fs";
22474
22624
  var EVENT = {
22475
22625
  initialization: "initialization",
22476
22626
  provenance: "provenance",
@@ -22498,7 +22648,7 @@ var changedSourcesSinceLoad = (marker) => {
22498
22648
  const changed = [];
22499
22649
  for (const file of marker.files) {
22500
22650
  try {
22501
- if (statSync(file).mtimeMs > marker.loadedAt)
22651
+ if (statSync2(file).mtimeMs > marker.loadedAt)
22502
22652
  changed.push(file);
22503
22653
  } catch {}
22504
22654
  }
@@ -22657,33 +22807,197 @@ var COMMIT_PRESETS = [
22657
22807
  "custom",
22658
22808
  "auto"
22659
22809
  ];
22810
+ var resolveCommitPolicy = (config, workspace) => {
22811
+ const wp = workspace?.commitPolicy ?? {};
22812
+ if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
22813
+ return {
22814
+ preset: wp.preset,
22815
+ ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
22816
+ };
22817
+ }
22818
+ return config.commitPolicy ?? { preset: "conventional" };
22819
+ };
22660
22820
 
22661
22821
  // packages/workit-core/src/core/workspaces.ts
22822
+ var nonBlank = string5().refine((value) => value.trim().length > 0, "must not be blank");
22823
+ var branchPreset = custom2((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
22824
+ var commitPreset = custom2((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
22825
+ var workspaceBranchPolicySchema = object4({
22826
+ preset: branchPreset,
22827
+ developBranch: nonBlank.optional(),
22828
+ prefixes: object4({
22829
+ feature: nonBlank,
22830
+ bugfix: nonBlank,
22831
+ release: nonBlank,
22832
+ hotfix: nonBlank
22833
+ }).passthrough().optional(),
22834
+ allowed: array3(nonBlank).optional(),
22835
+ protected: array3(nonBlank).optional(),
22836
+ integration: _enum2(["pr", "merge"]).optional()
22837
+ }).strict();
22838
+ var workspaceCommitPolicySchema = object4({ preset: commitPreset, pattern: string5().optional() }).strict();
22839
+ var workspaceProfileSchema = object4({
22840
+ branchPolicy: workspaceBranchPolicySchema.optional(),
22841
+ commitPolicy: workspaceCommitPolicySchema.optional()
22842
+ }).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
22843
+ message: "a profile must define branchPolicy or commitPolicy"
22844
+ });
22845
+ var versionSourceSchema = discriminatedUnion2("kind", [
22846
+ object4({
22847
+ kind: literal3("package-json"),
22848
+ path: nonBlank,
22849
+ field: nonBlank
22850
+ }).strict(),
22851
+ object4({ kind: literal3("git-tag") }).strict(),
22852
+ object4({ kind: literal3("manual") }).strict()
22853
+ ]);
22854
+ var releaseTrackSchema = object4({
22855
+ strategy: branchPreset,
22856
+ productionBranch: nonBlank,
22857
+ integrationBranch: nonBlank,
22858
+ naming: object4({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
22859
+ baseBranch: nonBlank,
22860
+ mergeBackBranches: array3(nonBlank),
22861
+ pullRequestTarget: nonBlank,
22862
+ tagNamespace: string5(),
22863
+ versionSource: versionSourceSchema,
22864
+ requiredChecks: array3(nonBlank)
22865
+ }).strict().superRefine((track, context) => {
22866
+ for (const [field, values] of [
22867
+ ["mergeBackBranches", track.mergeBackBranches],
22868
+ ["requiredChecks", track.requiredChecks]
22869
+ ]) {
22870
+ if (new Set(values).size !== values.length) {
22871
+ context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
22872
+ }
22873
+ }
22874
+ 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(".."))) {
22875
+ context.addIssue({
22876
+ code: "custom",
22877
+ path: ["versionSource", "path"],
22878
+ message: "must stay inside the repository"
22879
+ });
22880
+ }
22881
+ });
22882
+ var workspaceConfigSchema = object4({
22883
+ name: nonBlank,
22884
+ glob: nonBlank,
22885
+ vcs: object4({
22886
+ provider: _enum2(["gitlab", "github"]),
22887
+ defaultTargetBranch: nonBlank.optional(),
22888
+ tokenFile: nonBlank.optional(),
22889
+ account: nonBlank.optional()
22890
+ }).passthrough().optional(),
22891
+ autoApprove: union3([boolean5(), array3(nonBlank)]).optional(),
22892
+ youtrack: object4({ baseUrl: nonBlank.optional(), link_issues: boolean5().optional() }).passthrough().optional(),
22893
+ issues: object4({ provider: literal3("github").optional(), link_on_pr: boolean5().optional() }).passthrough().optional(),
22894
+ branchPolicy: workspaceBranchPolicySchema.optional(),
22895
+ commitPolicy: workspaceCommitPolicySchema.optional(),
22896
+ defaultProfile: nonBlank.optional(),
22897
+ profiles: record3(nonBlank, workspaceProfileSchema).optional(),
22898
+ releaseTracks: record3(nonBlank, releaseTrackSchema).optional()
22899
+ }).passthrough().superRefine((workspace, context) => {
22900
+ if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
22901
+ context.addIssue({
22902
+ code: "custom",
22903
+ path: ["youtrack"],
22904
+ message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
22905
+ });
22906
+ }
22907
+ if (workspace.issues && workspace.vcs?.provider !== "github") {
22908
+ context.addIssue({
22909
+ code: "custom",
22910
+ path: ["issues"],
22911
+ message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
22912
+ });
22913
+ }
22914
+ if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
22915
+ context.addIssue({
22916
+ code: "custom",
22917
+ path: ["defaultProfile"],
22918
+ message: `does not name a configured profile`
22919
+ });
22920
+ }
22921
+ });
22922
+ var workspacesFileSchema = object4({ workspaces: array3(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
22923
+ const names = new Set;
22924
+ for (const [index, workspace] of (file.workspaces ?? []).entries()) {
22925
+ if (names.has(workspace.name)) {
22926
+ context.addIssue({
22927
+ code: "custom",
22928
+ path: ["workspaces", index, "name"],
22929
+ message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
22930
+ });
22931
+ }
22932
+ names.add(workspace.name);
22933
+ }
22934
+ });
22935
+ var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
22936
+ var validateWorkspacesDocument = (parsed, file) => {
22937
+ if (!isConfigObject(parsed)) {
22938
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
22939
+ }
22940
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
22941
+ if (!validated.success) {
22942
+ const detail = validated.error.issues.map((issue) => {
22943
+ const field = String(issue.path[issue.path.length - 1] ?? "");
22944
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
22945
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
22946
+ }).join("; ");
22947
+ return {
22948
+ status: "invalid",
22949
+ path: file,
22950
+ entries: [],
22951
+ error: `${file} has invalid workspace configuration: ${detail}`
22952
+ };
22953
+ }
22954
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
22955
+ const glob = validateWorkspaceGlob(workspace.glob);
22956
+ if (!glob.ok) {
22957
+ return {
22958
+ status: "invalid",
22959
+ path: file,
22960
+ entries: [],
22961
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
22962
+ };
22963
+ }
22964
+ }
22965
+ return {
22966
+ status: "valid",
22967
+ path: file,
22968
+ entries: validated.data.workspaces ?? [],
22969
+ document: parsed
22970
+ };
22971
+ };
22662
22972
  var readWorkspacesResult = (dir = configDir()) => {
22663
22973
  const file = path6.join(dir, "workspaces.json");
22664
22974
  let raw;
22665
22975
  try {
22666
22976
  raw = readFileSync3(file, "utf8");
22667
- } catch {
22668
- return { status: "missing", path: file, entries: [] };
22977
+ } catch (error) {
22978
+ if (error.code === "ENOENT")
22979
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
22980
+ return {
22981
+ status: "malformed",
22982
+ path: file,
22983
+ entries: [],
22984
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
22985
+ };
22669
22986
  }
22670
22987
  let parsed;
22671
22988
  try {
22672
22989
  parsed = JSON.parse(raw);
22673
22990
  } catch {
22674
- return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
22675
- }
22676
- if (!isConfigObject(parsed)) {
22677
- return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
22991
+ return {
22992
+ status: "malformed",
22993
+ path: file,
22994
+ entries: [],
22995
+ revision: workspacesRevision(raw),
22996
+ error: `${file} is not valid JSON`
22997
+ };
22678
22998
  }
22679
- const list = parsed.workspaces;
22680
- return {
22681
- status: "valid",
22682
- path: file,
22683
- entries: Array.isArray(list) ? list : []
22684
- };
22999
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
22685
23000
  };
22686
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
22687
23001
  var globToRegExp = (glob) => {
22688
23002
  let out = "";
22689
23003
  for (let i = 0;i < glob.length; i++) {
@@ -22718,6 +23032,30 @@ var globToRegExp = (glob) => {
22718
23032
  return new RegExp(`^${out}$`);
22719
23033
  };
22720
23034
  var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
23035
+ var UNSUPPORTED_GLOB = /[?[\]{}]/;
23036
+ var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
23037
+ var validateWorkspaceGlob = (glob) => {
23038
+ const trimmed = glob.trim();
23039
+ if (!trimmed)
23040
+ return { ok: false, error: "workspace pattern is required" };
23041
+ const m = UNSUPPORTED_GLOB.exec(trimmed);
23042
+ if (m) {
23043
+ return {
23044
+ ok: false,
23045
+ error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
23046
+ };
23047
+ }
23048
+ const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
23049
+ if (ext) {
23050
+ const token = ext[0];
23051
+ const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
23052
+ return {
23053
+ ok: false,
23054
+ error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
23055
+ };
23056
+ }
23057
+ return { ok: true };
23058
+ };
22721
23059
  var realpathOf = (p) => {
22722
23060
  const real = realpathSync2.native ?? realpathSync2;
22723
23061
  try {
@@ -22735,29 +23073,137 @@ var canonicalGlob = (glob) => {
22735
23073
  const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
22736
23074
  return real + (prefix.endsWith("/") ? "/" : "") + rest;
22737
23075
  };
22738
- var resolveWorkspaceFrom = (cwd, dir) => {
23076
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
23077
+ const result = readWorkspacesResult(dir);
23078
+ if (result.status === "malformed" || result.status === "invalid")
23079
+ throw new Error(result.error);
22739
23080
  const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
22740
- for (const entry of loadWorkspacesFrom(dir)) {
22741
- if (!entry || typeof entry !== "object")
22742
- continue;
22743
- const ws = entry;
22744
- if (typeof ws.glob !== "string" || !ws.glob)
22745
- continue;
23081
+ const matches = [];
23082
+ for (const ws of result.entries) {
22746
23083
  const glob = ws.glob.replaceAll("\\", "/");
22747
23084
  const canonical = canonicalGlob(glob);
23085
+ let matched = false;
22748
23086
  for (const target of targets) {
22749
- if (matchWorkspace(glob, target))
22750
- return ws;
22751
- if (canonical !== glob && matchWorkspace(canonical, target))
22752
- return ws;
23087
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
23088
+ matched = true;
23089
+ break;
23090
+ }
22753
23091
  }
23092
+ if (matched)
23093
+ matches.push(ws);
22754
23094
  }
22755
- return null;
23095
+ if (workspaceName !== undefined) {
23096
+ const selected = matches.find((ws) => ws.name === workspaceName);
23097
+ if (!selected && matches.length > 0) {
23098
+ const matchingNames = matches.map((ws) => ws.name);
23099
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matchingNames.join(", ")}`);
23100
+ }
23101
+ if (selected)
23102
+ return selected;
23103
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
23104
+ }
23105
+ if (matches.length > 1) {
23106
+ throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((ws) => ws.name).join(", ")}`);
23107
+ }
23108
+ return matches[0] ?? null;
23109
+ };
23110
+ var runtimeWorkspacePolicySchema = (kind) => {
23111
+ const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
23112
+ const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object4({
23113
+ provider: _enum2(["gitlab", "github"]),
23114
+ defaultTargetBranch: nonBlank.optional(),
23115
+ account: nonBlank.optional()
23116
+ }).passthrough();
23117
+ const profileSchema = object4({ [key]: policySchema.optional() }).passthrough();
23118
+ const entrySchema = object4({
23119
+ name: nonBlank,
23120
+ glob: nonBlank,
23121
+ [key]: policySchema.optional(),
23122
+ defaultProfile: nonBlank.optional(),
23123
+ profiles: record3(nonBlank, profileSchema).optional()
23124
+ }).passthrough();
23125
+ return entrySchema;
23126
+ };
23127
+ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
23128
+ var runtimeWorkspaceIndexSchema = object4({
23129
+ workspaces: array3(object4({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
23130
+ }).passthrough();
23131
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23132
+ const file = path6.join(configDir(), "workspaces.json");
23133
+ let raw;
23134
+ try {
23135
+ raw = readFileSync3(file, "utf8");
23136
+ } catch (error) {
23137
+ if (error.code === "ENOENT")
23138
+ return null;
23139
+ throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
23140
+ }
23141
+ let parsed;
23142
+ try {
23143
+ parsed = JSON.parse(raw);
23144
+ } catch {
23145
+ throw new Error(`${file} is not valid JSON`);
23146
+ }
23147
+ const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
23148
+ if (!validated.success) {
23149
+ const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
23150
+ throw new Error(`${file} has invalid workspace matching data: ${detail}`);
23151
+ }
23152
+ const entries = validated.data.workspaces;
23153
+ for (const entry of entries ?? []) {
23154
+ const glob = validateWorkspaceGlob(entry.glob);
23155
+ if (!glob.ok)
23156
+ throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
23157
+ }
23158
+ const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
23159
+ const matches = (entries ?? []).filter((entry) => {
23160
+ const glob = entry.glob.replaceAll("\\", "/");
23161
+ const canonical = canonicalGlob(glob);
23162
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
23163
+ });
23164
+ const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
23165
+ let selected;
23166
+ if (workspaceName) {
23167
+ const named = matches.filter((entry) => entry.name === workspaceName);
23168
+ if (named.length === 1)
23169
+ selected = named[0];
23170
+ else if (named.length > 1)
23171
+ throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
23172
+ else if (matches.length)
23173
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
23174
+ else
23175
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
23176
+ } else if (matches.length > 1)
23177
+ throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${matches.map((entry) => entry.name).join(", ")}`);
23178
+ else
23179
+ selected = matches[0];
23180
+ if (!selected)
23181
+ return null;
23182
+ const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
23183
+ if (!policy.success) {
23184
+ const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
23185
+ throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
23186
+ }
23187
+ return policy.data;
23188
+ };
23189
+ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
23190
+ const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
23191
+ if (!candidate)
23192
+ return { policy: undefined, source: "user-default" };
23193
+ const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
23194
+ const profileName = requestedProfile?.trim() || candidate.defaultProfile;
23195
+ if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
23196
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
23197
+ const profile = profileName ? candidate.profiles?.[profileName] : undefined;
23198
+ if (profileName && !profile)
23199
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
23200
+ const profilePolicy = profile?.[key];
23201
+ const workspacePolicy = candidate[key];
23202
+ return {
23203
+ policy: profilePolicy ?? workspacePolicy,
23204
+ source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
23205
+ };
22756
23206
  };
22757
- var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
22758
-
22759
- // packages/workit-core/src/core/branch.ts
22760
- import { execFileSync as execFileSync2 } from "node:child_process";
22761
23207
 
22762
23208
  // packages/workit-core/src/core/git.ts
22763
23209
  import { execFileSync } from "node:child_process";
@@ -22831,7 +23277,6 @@ var gitContext = (workspaceRoot, paths = []) => {
22831
23277
  import fs5 from "node:fs";
22832
23278
  import path7 from "node:path";
22833
23279
  import { spawnSync } from "node:child_process";
22834
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
22835
23280
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
22836
23281
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22837
23282
  var remoteProvider = (cwd) => {
@@ -22845,6 +23290,79 @@ var remoteProvider = (cwd) => {
22845
23290
  return "gitlab";
22846
23291
  return null;
22847
23292
  };
23293
+ var remoteEndpoint = (remote) => {
23294
+ const raw = remote.trim();
23295
+ if (!raw)
23296
+ return null;
23297
+ const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
23298
+ if (scp && !raw.includes("://"))
23299
+ return {
23300
+ host: scp[1].toLowerCase(),
23301
+ path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
23302
+ protocol: "ssh:",
23303
+ port: ""
23304
+ };
23305
+ try {
23306
+ const url = new URL(raw);
23307
+ return {
23308
+ host: url.hostname.toLowerCase(),
23309
+ path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
23310
+ protocol: url.protocol,
23311
+ port: url.port
23312
+ };
23313
+ } catch {
23314
+ return null;
23315
+ }
23316
+ };
23317
+ var configuredSshEndpoint = (host) => {
23318
+ if (host.startsWith("-"))
23319
+ return null;
23320
+ const resolved = spawnSync("ssh", ["-G", host], {
23321
+ encoding: "utf8",
23322
+ timeout: 5000,
23323
+ maxBuffer: 1024 * 1024,
23324
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
23325
+ });
23326
+ if (resolved.status !== 0)
23327
+ return null;
23328
+ const output = resolved.stdout ?? "";
23329
+ const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
23330
+ if (!hostname)
23331
+ return null;
23332
+ return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
23333
+ };
23334
+ var sshApiHostAliases = {
23335
+ "github.com": [{ host: "ssh.github.com", port: "443" }],
23336
+ "gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
23337
+ };
23338
+ var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
23339
+ const endpoint = remoteEndpoint(remote);
23340
+ if (!endpoint)
23341
+ return false;
23342
+ let expected;
23343
+ try {
23344
+ const url = new URL(`https://${apiHost}`);
23345
+ if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
23346
+ return false;
23347
+ expected = {
23348
+ host: url.hostname.toLowerCase().replace(/\.$/u, ""),
23349
+ port: url.port
23350
+ };
23351
+ } catch {
23352
+ return false;
23353
+ }
23354
+ if (endpoint.protocol !== "ssh:") {
23355
+ return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
23356
+ }
23357
+ const resolved = resolveSshEndpoint(endpoint.host);
23358
+ if (!resolved)
23359
+ return false;
23360
+ const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
23361
+ const port = endpoint.port || resolved.port;
23362
+ if (hostname === expected.host)
23363
+ return port === (expected.port || "22");
23364
+ return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
23365
+ };
22848
23366
  var readVcsConfig = () => {
22849
23367
  const file = vcsConfigPath();
22850
23368
  let raw;
@@ -22865,7 +23383,7 @@ var readVcsConfig = () => {
22865
23383
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22866
23384
  };
22867
23385
  function vcsConfig(mode, cwd) {
22868
- const ws = resolveWorkspace(vcsCwd(cwd));
23386
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
22869
23387
  const wsVcs = ws?.vcs ?? {};
22870
23388
  const wsYt = ws?.youtrack ?? {};
22871
23389
  const wsIssues = ws?.issues ?? {};
@@ -22878,8 +23396,8 @@ function vcsConfig(mode, cwd) {
22878
23396
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
22879
23397
  const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
22880
23398
  const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
22881
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
22882
- const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
23399
+ const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
23400
+ const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
22883
23401
  let issuesProvider = null;
22884
23402
  let linkOnPr = null;
22885
23403
  if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
@@ -22903,9 +23421,8 @@ function vcsConfig(mode, cwd) {
22903
23421
  if (cfgStatus === "malformed") {
22904
23422
  return { ok: false, error: cfgError, configPath: cfgPath };
22905
23423
  }
22906
- if (cfgStatus === "missing") {
23424
+ if (cfgStatus === "missing" && provider === null)
22907
23425
  return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
22908
- }
22909
23426
  if (provider === null) {
22910
23427
  return {
22911
23428
  ok: false,
@@ -22914,24 +23431,12 @@ function vcsConfig(mode, cwd) {
22914
23431
  };
22915
23432
  }
22916
23433
  const prov = cfg[provider] ?? {};
22917
- const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
22918
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
22919
- const tokenPath = path7.resolve(tokenFile);
22920
- let tokenOk = false;
22921
- if (fs5.existsSync(tokenPath)) {
22922
- const token = fs5.readFileSync(tokenPath, "utf8").trim();
22923
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
22924
- tokenOk = !placeholder;
22925
- }
22926
23434
  const out = {
22927
23435
  ok: true,
22928
23436
  configPath: path7.resolve(cfgPath),
22929
23437
  provider,
22930
23438
  defaultTargetBranch: defaultTarget,
22931
23439
  pr: cfg.pr ?? {},
22932
- tokenPath,
22933
- tokenPresent: fs5.existsSync(tokenPath),
22934
- tokenReady: tokenOk,
22935
23440
  workspace_name: ws?.name ?? null,
22936
23441
  link_issues: linkIssues,
22937
23442
  youtrack_base_url: youtrackBaseUrl,
@@ -22946,16 +23451,58 @@ function vcsConfig(mode, cwd) {
22946
23451
  } else if (provider === "github") {
22947
23452
  out.github = { host: prov.host ?? "github.com" };
22948
23453
  }
22949
- if (mode === "summary")
22950
- delete out.tokenPath;
22951
23454
  return out;
22952
23455
  }
23456
+ function vcsCliIdentity(cwd) {
23457
+ const root = vcsCwd(cwd);
23458
+ const cfg = vcsConfig("load", root);
23459
+ if (!cfg.ok)
23460
+ return { ok: false, error: cfg.error ?? "vcs config not ready" };
23461
+ const provider = cfg.provider;
23462
+ if (provider !== "gitlab" && provider !== "github")
23463
+ return { ok: false, error: `unsupported provider: ${provider}` };
23464
+ const bin = provider === "gitlab" ? "glab" : "gh";
23465
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
23466
+ const env = {
23467
+ ...process.env,
23468
+ PATH: process.env.PATH ?? "",
23469
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
23470
+ };
23471
+ const result = spawnSync(bin, ["api", "user"], {
23472
+ cwd: root,
23473
+ encoding: "utf8",
23474
+ env
23475
+ });
23476
+ if (result.status !== 0)
23477
+ return { ok: false, provider, error: `${bin} is missing or not authenticated` };
23478
+ try {
23479
+ const user = JSON.parse(result.stdout ?? "");
23480
+ const username = provider === "gitlab" ? user.username : user.login;
23481
+ if (typeof username !== "string" || !username)
23482
+ return { ok: false, provider, error: `invalid ${bin} identity response` };
23483
+ const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
23484
+ if (expected && username.toLowerCase() !== expected.toLowerCase())
23485
+ return {
23486
+ ok: false,
23487
+ provider,
23488
+ error: `${bin} account ${username} does not match ${expected}`
23489
+ };
23490
+ return { ok: true, provider, host, username, name: user.name };
23491
+ } catch {
23492
+ return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
23493
+ }
23494
+ }
22953
23495
  function mergedPrStyle(limit = 6, cwd) {
22954
23496
  const cfg = vcsConfig("load", cwd);
22955
- if (!cfg.ok || !cfg.tokenReady)
23497
+ if (!cfg.ok)
22956
23498
  return { ok: false, error: "vcs not configured" };
22957
23499
  const provider = cfg.provider;
22958
- const token = fs5.readFileSync(cfg.tokenPath, "utf8").trim();
23500
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
23501
+ const env = {
23502
+ ...process.env,
23503
+ PATH: process.env.PATH ?? "",
23504
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
23505
+ };
22959
23506
  const examples = [];
22960
23507
  const descInfo = (desc, caseInsensitiveNotes = false) => ({
22961
23508
  hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
@@ -22963,23 +23510,30 @@ function mergedPrStyle(limit = 6, cwd) {
22963
23510
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22964
23511
  descriptionPreview: desc.slice(0, 600)
22965
23512
  });
23513
+ const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
23514
+ cwd,
23515
+ encoding: "utf8"
23516
+ });
23517
+ const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
23518
+ if (remotes.length === 0)
23519
+ return { ok: false, error: "no origin remote" };
23520
+ if (remotes.length !== 1)
23521
+ return { ok: false, error: "origin has ambiguous push destinations" };
23522
+ const remote = remotes[0];
23523
+ const endpoint = remoteEndpoint(remote);
23524
+ if (!endpoint?.path)
23525
+ return { ok: false, error: "hosting repository could not be resolved" };
23526
+ if (!hostingApiHostMatches(remote, host))
23527
+ return { ok: false, error: "PR style target does not match the provider host" };
22966
23528
  if (provider === "gitlab") {
22967
- const remote = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22968
- if (remote.status !== 0)
22969
- return { ok: false, error: "no origin remote" };
22970
- const url = (remote.stdout ?? "").trim();
22971
- const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
22972
- if (!m)
22973
- return { ok: false, error: "not a gitlab.com origin" };
22974
- const project = m[1];
22975
- const env = { ...process.env, GITLAB_TOKEN: token };
22976
- const run = (args) => spawnSync("glab", ["api", ...args], { cwd, encoding: "utf8", env });
23529
+ const run = (args) => spawnSync("glab", ["api", ...args], {
23530
+ cwd,
23531
+ encoding: "utf8",
23532
+ env
23533
+ });
22977
23534
  let r = run([
22978
- `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
23535
+ `projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
22979
23536
  ]);
22980
- if (r.status !== 0) {
22981
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
22982
- }
22983
23537
  if (r.status !== 0)
22984
23538
  return { ok: false, error: "could not list merge requests" };
22985
23539
  for (const mr of JSON.parse(r.stdout ?? "[]")) {
@@ -22992,7 +23546,18 @@ function mergedPrStyle(limit = 6, cwd) {
22992
23546
  });
22993
23547
  }
22994
23548
  } else if (provider === "github") {
22995
- const r = spawnSync("gh", ["pr", "list", "--state", "merged", "--limit", String(limit), "--json", "title,url,body"], { cwd, encoding: "utf8", env: { ...process.env, GH_TOKEN: token } });
23549
+ const r = spawnSync("gh", [
23550
+ "pr",
23551
+ "list",
23552
+ "--repo",
23553
+ `${host}/${endpoint.path}`,
23554
+ "--state",
23555
+ "merged",
23556
+ "--limit",
23557
+ String(limit),
23558
+ "--json",
23559
+ "title,url,body"
23560
+ ], { cwd, encoding: "utf8", env });
22996
23561
  if (r.status !== 0)
22997
23562
  return { ok: false, error: "could not list pull requests" };
22998
23563
  for (const pr of JSON.parse(r.stdout ?? "[]")) {
@@ -23014,26 +23579,28 @@ function mergedPrStyle(limit = 6, cwd) {
23014
23579
  }
23015
23580
 
23016
23581
  // packages/workit-core/src/core/branch.ts
23017
- var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
23582
+ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
23583
+ const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
23584
+ const config = readConfig();
23585
+ return kind === "branch" ? {
23586
+ branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
23587
+ provenance: { branchPolicy: selected.source }
23588
+ } : {
23589
+ commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
23590
+ provenance: { commitPolicy: selected.source }
23591
+ };
23592
+ }
23593
+ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
23018
23594
  var verifyPushIdentity = (root, provider, account) => {
23019
23595
  if (!account || typeof account !== "string")
23020
23596
  return { ok: false, error: "push identity requires a configured area account" };
23021
- const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
23022
- const bin = provider === "gitlab" ? "glab" : "gh";
23023
- let login;
23024
- try {
23025
- login = execFileSync2(bin, [...probe], {
23026
- cwd: root,
23027
- encoding: "utf8",
23028
- stdio: ["pipe", "pipe", "pipe"]
23029
- }).trim();
23030
- } catch {
23031
- return { ok: false, error: `${bin} identity could not be resolved` };
23032
- }
23033
- if (!login || login.toLowerCase() !== account.toLowerCase())
23597
+ const identity = vcsCliIdentity(root);
23598
+ if (!identity.ok || identity.provider !== provider)
23599
+ return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
23600
+ if (identity.username?.toLowerCase() !== account.toLowerCase())
23034
23601
  return {
23035
23602
  ok: false,
23036
- error: `push identity ${login || "(unknown)"} does not match area account ${account}`
23603
+ error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
23037
23604
  };
23038
23605
  return { ok: true };
23039
23606
  };
@@ -23051,7 +23618,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
23051
23618
  var digestOf = (configDirPath) => {
23052
23619
  try {
23053
23620
  const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
23054
- return createHash3("sha256").update(raw).digest("hex");
23621
+ return createHash4("sha256").update(raw).digest("hex");
23055
23622
  } catch {
23056
23623
  return null;
23057
23624
  }
@@ -23089,14 +23656,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
23089
23656
  };
23090
23657
  };
23091
23658
  var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
23092
- var standingApprovalLive = (root, receipt, cls, dir) => {
23659
+ var targetOf = (root, descriptor) => {
23660
+ try {
23661
+ const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
23662
+ const cwd = parsed?.payload?.cwd;
23663
+ if (cwd === undefined)
23664
+ return root;
23665
+ if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
23666
+ return null;
23667
+ return realpathSync3(cwd);
23668
+ } catch {
23669
+ return null;
23670
+ }
23671
+ };
23672
+ var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
23093
23673
  if (!isStandingReceipt(receipt))
23094
23674
  return false;
23095
23675
  if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
23096
23676
  return false;
23097
23677
  if (receipt.class !== cls)
23098
23678
  return false;
23099
- const resolved = resolveAutoApproval(root, dir);
23679
+ const target = targetOf(root, descriptor);
23680
+ if (!target)
23681
+ return false;
23682
+ const resolved = resolveAutoApproval(target, dir);
23100
23683
  if (resolved.status !== "on")
23101
23684
  return false;
23102
23685
  if (resolved.workspace !== receipt.workspace)
@@ -23115,9 +23698,11 @@ var standingReceiptFor = (root, workspace, cls, dir) => {
23115
23698
  var verifyStandingApproval = (root, task, caller, binding) => {
23116
23699
  if (task.status !== "active")
23117
23700
  return failure("invalid_transition", "only active tasks can authorize actions");
23118
- const session = task.intent?.provenance?.session;
23119
- if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
23120
- return failure("permission_denied", "standing approval requires the lead session");
23701
+ const workspace = new TaskStore(root).readWorkspace();
23702
+ if (!workspace.ok)
23703
+ return workspace;
23704
+ if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
23705
+ return failure("permission_denied", "standing approval requires the current writer session");
23121
23706
  const standing = binding.standing;
23122
23707
  if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
23123
23708
  return failure("invalid_input", "standing approval needs a workspace and class");
@@ -23130,16 +23715,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23130
23715
  const cls = operationAutoClass(operation);
23131
23716
  if (!cls || cls !== standing.class)
23132
23717
  return failure("permission_denied", "standing approval does not cover this operation");
23133
- const resolved = resolveAutoApproval(root);
23718
+ const target = targetOf(root, binding.approvedContent);
23719
+ if (!target)
23720
+ return failure("permission_denied", "standing action target is invalid");
23721
+ const resolved = resolveAutoApproval(target);
23134
23722
  if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
23135
23723
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23136
23724
  if (cls === "push") {
23137
- const workspace = resolveWorkspaceFrom(root, configDir());
23725
+ const workspace = resolveWorkspaceFrom(target, configDir());
23138
23726
  const provider = workspace?.vcs?.provider;
23139
23727
  const account = resolved.account;
23140
23728
  if (!provider || !account)
23141
23729
  return failure("permission_denied", "auto-push requires a workspace provider and area account");
23142
- const identity = verifyPushIdentity(root, provider, account);
23730
+ const identity = verifyPushIdentity(target, provider, account);
23143
23731
  if (!identity.ok)
23144
23732
  return failure("permission_denied", identity.error);
23145
23733
  }
@@ -23148,7 +23736,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23148
23736
  host: caller.host,
23149
23737
  session: { kind: "host", host: caller.host, handle: caller.actor },
23150
23738
  workerId: null,
23151
- receipts: [standingReceiptFor(root, standing.workspace, cls)]
23739
+ receipts: [standingReceiptFor(target, standing.workspace, cls)]
23152
23740
  });
23153
23741
  };
23154
23742
 
@@ -23299,7 +23887,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
23299
23887
  return [];
23300
23888
  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);
23301
23889
  }
23302
- var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
23890
+ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
23303
23891
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23304
23892
  let root;
23305
23893
  try {
@@ -23312,23 +23900,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23312
23900
  continue;
23313
23901
  if (!reference.digest)
23314
23902
  return failure("invalid_input", "document references require a byte digest");
23315
- const target = path8.resolve(root, reference.path);
23316
- if (target !== root && !target.startsWith(`${root}${path8.sep}`))
23903
+ const target = path9.resolve(root, reference.path);
23904
+ if (target !== root && !target.startsWith(`${root}${path9.sep}`))
23317
23905
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23318
23906
  fields: [
23319
23907
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
23320
23908
  ]
23321
23909
  });
23322
23910
  try {
23323
- const relative = path8.relative(root, target);
23911
+ const relative = path9.relative(root, target);
23324
23912
  let current = root;
23325
- for (const segment of relative.split(path8.sep)) {
23913
+ for (const segment of relative.split(path9.sep)) {
23326
23914
  if (!segment)
23327
23915
  continue;
23328
- current = path8.join(current, segment);
23916
+ current = path9.join(current, segment);
23329
23917
  const stat = fs6.lstatSync(current);
23330
23918
  const real = fs6.realpathSync(current);
23331
- if (real !== root && !real.startsWith(`${root}${path8.sep}`))
23919
+ if (real !== root && !real.startsWith(`${root}${path9.sep}`))
23332
23920
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23333
23921
  fields: [
23334
23922
  {
@@ -23354,6 +23942,20 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23354
23942
  };
23355
23943
  var verifyContentRefs = (store, binding) => verifyDecisionContentAtRoot(store.root, binding);
23356
23944
  var storedDecisionApplicable = (store, task, purpose, binding) => task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && verifyDecisionContentAtRoot(store.root, entry.data.binding).ok);
23945
+ var approvalAuthorityMatches = (store, task, approval, current) => {
23946
+ if (approval.kind !== "host_observed" || approval.receipts.length === 0)
23947
+ return false;
23948
+ if (provenanceMatches(approval, current))
23949
+ return true;
23950
+ const session = current.kind === "host_observed" ? current.session : null;
23951
+ if (!session || session.kind !== "host")
23952
+ return false;
23953
+ const workspace = store.readWorkspace();
23954
+ return workspace.ok && workspace.data !== null && currentWriterOwnsTask(task, workspace.data, {
23955
+ host: session.host,
23956
+ actor: session.handle
23957
+ });
23958
+ };
23357
23959
  var validateAction = (store, task, workspaceId, input, authority) => {
23358
23960
  if (task.status !== "active")
23359
23961
  return failure("invalid_transition", "only active tasks can authorize actions");
@@ -23379,7 +23981,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
23379
23981
  return failure("permission_denied", "native reservation authority does not match decision");
23380
23982
  if (decision.response !== "approved")
23381
23983
  return failure("permission_denied", "decision was rejected");
23382
- if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
23984
+ if (!approvalAuthorityMatches(store, task, entry.provenance, authority.provenance))
23383
23985
  return failure("permission_denied", "action approval lacks native receipt assurance");
23384
23986
  const receipts = entry.provenance.receipts;
23385
23987
  const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
@@ -23392,7 +23994,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
23392
23994
  operation = null;
23393
23995
  }
23394
23996
  const cls = operationAutoClass(operation);
23395
- if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
23997
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
23396
23998
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23397
23999
  }
23398
24000
  if (decision.revoked)
@@ -23546,7 +24148,7 @@ function settleAction(input) {
23546
24148
  return failure("not_found", "decision not found");
23547
24149
  if (!authorityDecisionMatches(authority, entry.data))
23548
24150
  return failure("permission_denied", "native settlement authority does not match decision");
23549
- if (!provenanceMatches(entry.provenance, authority.provenance))
24151
+ if (!approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
23550
24152
  return failure("permission_denied", "native settlement caller does not match approval");
23551
24153
  if (entry.data.purpose !== "action" || entry.data.digest !== decisionDigest(entry.data))
23552
24154
  return failure("permission_denied", "decision binding is invalid");
@@ -23645,7 +24247,7 @@ function reconcileAction(input) {
23645
24247
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
23646
24248
  if (!entry || entry.data.purpose !== "action")
23647
24249
  return failure("not_found", "decision not found");
23648
- if (!authorityDecisionMatches(authority, entry.data) || !provenanceMatches(entry.provenance, authority.provenance))
24250
+ if (!authorityDecisionMatches(authority, entry.data) || !approvalAuthorityMatches(input.store, current, entry.provenance, authority.provenance))
23649
24251
  return failure("permission_denied", "native reconciliation caller does not match approval");
23650
24252
  if (entry.data.response !== "approved" || entry.data.revoked !== null || entry.data.digest !== decisionDigest(entry.data) || entry.data.binding.taskId !== current.id || entry.data.binding.workspaceId !== current.workspaceId || entry.data.binding.workspaceId !== workspace.data.id)
23651
24253
  return failure("permission_denied", "reconciliation decision binding is invalid");
@@ -23694,15 +24296,15 @@ var bindingCovers = scopeCovers;
23694
24296
  var verifyDecisionContent = verifyContentRefs;
23695
24297
 
23696
24298
  // packages/workit-core/src/core/task-evaluation.ts
23697
- var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
23698
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
23699
- var relative = (root, candidate) => path9.relative(root, candidate).split(path9.sep).join("/") || ".";
24299
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
24300
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
24301
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
23700
24302
  var canonicalPath = (value) => {
23701
24303
  if (typeof value !== "string" || !value)
23702
24304
  return null;
23703
24305
  if (value.includes("\\") || value.split("/").includes(".."))
23704
24306
  return null;
23705
- const normalized = path9.posix.normalize(value);
24307
+ const normalized = path10.posix.normalize(value);
23706
24308
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
23707
24309
  return null;
23708
24310
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -23750,14 +24352,14 @@ var gitPaths = (root) => {
23750
24352
  git: /not a git repository/i.test(stderr) ? false : true
23751
24353
  };
23752
24354
  }
23753
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
24355
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
23754
24356
  const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
23755
24357
  if (stagedDeleted.status !== 0) {
23756
24358
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
23757
24359
  if (!/not a git repository/i.test(stderr))
23758
24360
  return { paths, uncertain: true, git: true };
23759
24361
  } else if (stagedDeleted.stdout) {
23760
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
24362
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
23761
24363
  }
23762
24364
  return { paths: [...new Set(paths)], uncertain: false, git: true };
23763
24365
  };
@@ -23772,7 +24374,7 @@ var walk = (root, directory, output) => {
23772
24374
  for (const name of names) {
23773
24375
  if (name === ".git" || name === ".workit")
23774
24376
  continue;
23775
- const target = path9.join(directory, name);
24377
+ const target = path10.join(directory, name);
23776
24378
  let stat;
23777
24379
  try {
23778
24380
  stat = fs7.lstatSync(target);
@@ -23790,12 +24392,12 @@ var walk = (root, directory, output) => {
23790
24392
  var scopeRoots = (root, scope) => {
23791
24393
  const roots = [];
23792
24394
  for (const item of scope.paths.length ? scope.paths : ["."]) {
23793
- const target = path9.resolve(root, item);
24395
+ const target = path10.resolve(root, item);
23794
24396
  if (!inside(root, target))
23795
24397
  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` }] });
23796
24398
  let ancestor = target;
23797
24399
  while (!fs7.existsSync(ancestor) && ancestor !== root)
23798
- ancestor = path9.dirname(ancestor);
24400
+ ancestor = path10.dirname(ancestor);
23799
24401
  try {
23800
24402
  if (!inside(root, fs7.realpathSync(ancestor)))
23801
24403
  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` }] });
@@ -23847,7 +24449,7 @@ function captureCandidate(root, scope, environment = []) {
23847
24449
  for (const item of [...names].sort()) {
23848
24450
  if (!scopeMatches(item, normalizedScope))
23849
24451
  continue;
23850
- const target = path9.join(checkout, item);
24452
+ const target = path10.join(checkout, item);
23851
24453
  try {
23852
24454
  const stat = fs7.lstatSync(target);
23853
24455
  if (stat.isSymbolicLink()) {
@@ -23982,7 +24584,7 @@ var resolveBinding = (evidence, pin, currentCandidateId) => {
23982
24584
  candidateId: evidence.candidateId ?? fallback
23983
24585
  };
23984
24586
  };
23985
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
24587
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
23986
24588
  var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
23987
24589
  var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
23988
24590
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
@@ -24217,6 +24819,11 @@ var exportDigest = (bundle) => sha256({
24217
24819
  task: bundle.task
24218
24820
  });
24219
24821
  // packages/workit-core/src/core/external-action.ts
24822
+ var chainStepSchema = union3([
24823
+ string5().min(1),
24824
+ object4({ branch: string5().min(1) }).strict(),
24825
+ object4({ pr: literal3(true) }).strict()
24826
+ ]);
24220
24827
  var externalActionSchema = discriminatedUnion2("operation", [
24221
24828
  object4({
24222
24829
  operation: literal3("git.branch_setup"),
@@ -24224,20 +24831,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
24224
24831
  action: _enum2(["setup", "reapply_stash"]).optional(),
24225
24832
  sdd_dir: string5().optional(),
24226
24833
  target_branch: string5().optional(),
24227
- stash: _enum2(["yes", "no"]).optional()
24834
+ stash: _enum2(["yes", "no"]).optional(),
24835
+ cwd: string5().min(1).optional()
24228
24836
  }).strict()
24229
24837
  }).strict(),
24230
24838
  object4({
24231
24839
  operation: literal3("git.commit"),
24232
24840
  payload: object4({
24233
24841
  message: string5().min(1).optional(),
24234
- plan_steps: array3(string5().min(1)).optional(),
24235
- plan_branch: string5().min(1).optional()
24842
+ plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
24843
+ plan_branch: string5().min(1).optional(),
24844
+ cwd: string5().min(1).optional()
24236
24845
  }).strict()
24237
24846
  }).strict(),
24238
24847
  object4({
24239
24848
  operation: literal3("git.push"),
24240
- payload: object4({ branch: string5().min(1).optional() }).strict()
24849
+ payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
24241
24850
  }).strict(),
24242
24851
  object4({
24243
24852
  operation: literal3("hosting.pull_request"),
@@ -24246,16 +24855,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
24246
24855
  body: string5().optional(),
24247
24856
  draft: boolean5().optional(),
24248
24857
  target_branch: string5().optional(),
24249
- babysit: boolean5().optional()
24858
+ babysit: boolean5().optional(),
24859
+ cwd: string5().min(1).optional()
24250
24860
  }).strict()
24251
24861
  }).strict(),
24252
24862
  object4({
24253
24863
  operation: literal3("hosting.merge"),
24254
24864
  payload: object4({
24255
24865
  target_branch: string5().min(1).optional(),
24256
- source_branch: string5().min(1).optional()
24866
+ source_branch: string5().min(1).optional(),
24867
+ cwd: string5().min(1).optional()
24257
24868
  }).strict()
24258
24869
  }).strict(),
24870
+ object4({
24871
+ operation: literal3("hosting.delete_branch"),
24872
+ payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
24873
+ }).strict(),
24259
24874
  object4({
24260
24875
  operation: literal3("youtrack.update"),
24261
24876
  payload: object4({
@@ -24311,12 +24926,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24311
24926
  issueRef: string5().optional(),
24312
24927
  mode: string5().optional(),
24313
24928
  specPath: string5().optional(),
24314
- planPath: string5().optional()
24929
+ planPath: string5().optional(),
24930
+ cwd: string5().min(1).optional()
24315
24931
  }).strict()
24316
24932
  }).strict()
24317
24933
  ]);
24318
24934
  // packages/workit-core/src/core/task-engine.ts
24319
- import { realpathSync as realpathSync5 } from "node:fs";
24935
+ import { realpathSync as realpathSync6 } from "node:fs";
24320
24936
 
24321
24937
  // packages/workit-core/src/core/policy-resolver.ts
24322
24938
  var resolverInputSchema = object4({
@@ -24717,6 +25333,7 @@ var verifyNativeWorker = (verifier, input, binding) => {
24717
25333
  expectedWorkspaceRevision: expected.expectedWorkspaceRevision,
24718
25334
  state: expected.state,
24719
25335
  session: expected.session,
25336
+ report: expected.report ?? null,
24720
25337
  provenance: result.data,
24721
25338
  ...binding
24722
25339
  });
@@ -24727,7 +25344,7 @@ var takeWorker = (token, binding, expected) => {
24727
25344
  verifiedWorkers.delete(token);
24728
25345
  if (!value)
24729
25346
  return null;
24730
- if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session))
25347
+ if (value.owner !== binding.owner || value.store !== binding.store || value.root !== binding.root || value.root !== value.store.root || !sameValue(value.caller, binding.caller) || value.taskId !== expected.taskId || value.workerId !== expected.workerId || value.expectedRevision !== expected.expectedRevision || value.expectedWorkspaceRevision !== expected.expectedWorkspaceRevision || value.state !== expected.state || !sameValue(value.session, expected.session) || !sameValue(value.report, expected.report ?? null))
24731
25348
  return null;
24732
25349
  return value;
24733
25350
  };
@@ -24768,6 +25385,8 @@ var applyWorkerLifecycle = (input) => {
24768
25385
  return failure("invalid_transition", "worker already has an observed session");
24769
25386
  if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
24770
25387
  return failure("invalid_transition", "worker launch was not claimed");
25388
+ if (input.report && input.state !== "stopped")
25389
+ return failure("invalid_input", "worker reports require a stopped worker");
24771
25390
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
24772
25391
  return failure("invalid_input", "running worker observations require a session");
24773
25392
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -24784,12 +25403,13 @@ var applyWorkerLifecycle = (input) => {
24784
25403
  data: {
24785
25404
  ...entry.data,
24786
25405
  state: terminalCancel ? "cancelling" : input.state,
24787
- session: input.session
25406
+ session: input.session,
25407
+ report: entry.data.report ?? input.report ?? null
24788
25408
  }
24789
25409
  };
24790
25410
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
24791
25411
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
24792
- if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
25412
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && sameValue(entry.data.report, nextEntry.data.report) && !shouldClear && !shouldUncertain)
24793
25413
  return success(task.data.revision, workspace.data.revision, entry);
24794
25414
  const changed = input.store.mutateTaskAndWorkspace({
24795
25415
  taskId: task.data.id,
@@ -24857,6 +25477,18 @@ var portableTask = (task) => {
24857
25477
  };
24858
25478
  for (const entry of clone.assessments)
24859
25479
  demote(entry.data.facts, entry.data.signals, entry.data.consequences);
25480
+ const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
25481
+ for (const entry of clone.decisions) {
25482
+ const consumption = sourceDecisions.get(entry.id)?.data.consumption;
25483
+ if (!consumption)
25484
+ continue;
25485
+ entry.data.consumption = {
25486
+ ...consumption,
25487
+ state: consumption.state === "reserved" ? "uncertain" : consumption.state,
25488
+ actionRef: { kind: "record", collection: "decisions", id: entry.id },
25489
+ ...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
25490
+ };
25491
+ }
24860
25492
  clone.candidates = [];
24861
25493
  return clone;
24862
25494
  };
@@ -24982,7 +25614,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
24982
25614
  taskId: "",
24983
25615
  workspaceId: destinationWorkspaceId
24984
25616
  },
24985
- consumption: null
25617
+ consumption: data.consumption ? {
25618
+ ...data.consumption,
25619
+ state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
25620
+ actionRef: { kind: "record", collection: "decisions", id: entry.id }
25621
+ } : null
24986
25622
  }
24987
25623
  };
24988
25624
  }),
@@ -25023,7 +25659,7 @@ class WorkitCore {
25023
25659
  }
25024
25660
  contextRootError() {
25025
25661
  try {
25026
- if (realpathSync5(this.context.root) !== this.store.root)
25662
+ if (realpathSync6(this.context.root) !== this.store.root)
25027
25663
  return failure("invalid_input", "operation context root does not match the task store root");
25028
25664
  } catch {
25029
25665
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -25182,7 +25818,7 @@ class WorkitCore {
25182
25818
  return parsed;
25183
25819
  const input = parsed.data;
25184
25820
  if ((this.context.workerId ?? null) !== null) {
25185
- if (input.action !== "inspect")
25821
+ if (input.action !== "inspect" && input.action !== "list")
25186
25822
  return failure("permission_denied", "helpers cannot control task lifecycle or scope");
25187
25823
  }
25188
25824
  switch (input.action) {
@@ -25207,14 +25843,45 @@ class WorkitCore {
25207
25843
  const listed = this.store.listTasks();
25208
25844
  if (!listed.ok)
25209
25845
  return listed;
25210
- const summaries = [];
25211
- for (const task of listed.data) {
25212
- const summary = this.summary(task);
25213
- if (!summary.ok)
25214
- return summary;
25215
- summaries.push(summary.data);
25216
- }
25217
- return success(null, null, summaries);
25846
+ const status = input.status ?? "open";
25847
+ const limit = input.limit ?? 20;
25848
+ const query = input.query?.toLowerCase().split(/\s+/);
25849
+ 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({
25850
+ id: task.id,
25851
+ workspaceId: task.workspaceId,
25852
+ origin: task.origin,
25853
+ createdAt: task.createdAt,
25854
+ updatedAt: task.updatedAt,
25855
+ source: task.intent.provenance,
25856
+ objective: task.intent.data.objective,
25857
+ progress: task.progress,
25858
+ decisions: task.decisions.map(({ data }) => data)
25859
+ }).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
25860
+ if (selected.length === 0)
25861
+ return success(null, null, []);
25862
+ const workspace = this.store.readWorkspace();
25863
+ if (!workspace.ok)
25864
+ return workspace;
25865
+ if (!workspace.data)
25866
+ return failure("not_found", "workspace not found");
25867
+ const tasks = selected.map((task) => ({
25868
+ id: task.id,
25869
+ revision: task.revision,
25870
+ workspaceRevision: workspace.data.revision,
25871
+ createdAt: task.createdAt,
25872
+ updatedAt: task.updatedAt,
25873
+ runtime: task.runtime ?? null,
25874
+ objective: task.intent.data.objective,
25875
+ status: task.status,
25876
+ closure: task.closure,
25877
+ progress: task.progress,
25878
+ writer: task.status === "closed" ? null : workspace.data.writer,
25879
+ source: {
25880
+ host: task.intent.provenance.host,
25881
+ kind: task.intent.provenance.kind
25882
+ }
25883
+ }));
25884
+ return success(null, null, tasks);
25218
25885
  }
25219
25886
  case "inspect": {
25220
25887
  const task = this.store.readTask(input.taskId);
@@ -25223,7 +25890,7 @@ class WorkitCore {
25223
25890
  const helper = this.helperTaskGuard(task.data, true);
25224
25891
  if (!helper.ok)
25225
25892
  return helper;
25226
- if (input.view === "summary")
25893
+ if ((input.view ?? "summary") === "summary")
25227
25894
  return this.summary(task.data);
25228
25895
  return this.view(task.data);
25229
25896
  }
@@ -25567,7 +26234,7 @@ class WorkitCore {
25567
26234
  const evaluations = evaluateEvidence(task.data, current.data);
25568
26235
  const verified = evidence.some((entry) => {
25569
26236
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
25570
- return findingVerificationPasses(finding.data.candidateId, {
26237
+ return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
25571
26238
  kind: entry.data.kind,
25572
26239
  candidateId: entry.data.candidateId,
25573
26240
  status: evaluation?.status ?? "missing"
@@ -25647,7 +26314,18 @@ class WorkitCore {
25647
26314
  if (task.data.status !== "active")
25648
26315
  return failure("invalid_transition", "paused or closed tasks cannot assign workers");
25649
26316
  if (!bindingCovers(task.data.intent.data.scope, input.assignment.scope))
25650
- return failure("permission_denied", "worker assignment is outside task scope");
26317
+ return failure("permission_denied", "worker assignment is outside task scope", {
26318
+ fields: [
26319
+ {
26320
+ path: "assignment.scope",
26321
+ reason: `paths ${JSON.stringify(input.assignment.scope.paths)} must be contained by lead paths ${JSON.stringify(task.data.intent.data.scope.paths)}`
26322
+ },
26323
+ {
26324
+ path: "task.intent.scope",
26325
+ reason: `lead paths are ${JSON.stringify(task.data.intent.data.scope.paths)}`
26326
+ }
26327
+ ]
26328
+ });
25651
26329
  if (input.assignment.decisionIds.some((id) => !task.data.decisions.some((entry) => entry.id === id)))
25652
26330
  return failure("invalid_input", "worker assignment references an unknown decision");
25653
26331
  if (input.assignment.requirementIds.some((id) => !task.data.policy?.requirements.some((requirement) => requirement.id === id)))
@@ -25854,7 +26532,15 @@ class WorkitCore {
25854
26532
  ...item,
25855
26533
  recordedAt: mutation.now,
25856
26534
  provenance: attested.data,
25857
- data: { ...item.data, state: "dispatching" }
26535
+ data: {
26536
+ ...item.data,
26537
+ state: "dispatching",
26538
+ coordinator: {
26539
+ kind: "host",
26540
+ host: this.context.caller.host,
26541
+ handle: this.context.caller.actor
26542
+ }
26543
+ }
25858
26544
  } : item)
25859
26545
  });
25860
26546
  }
@@ -25930,6 +26616,7 @@ class WorkitCore {
25930
26616
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
25931
26617
  state: "stopped",
25932
26618
  session: null,
26619
+ report: null,
25933
26620
  provenance: attested.data,
25934
26621
  owner: this.authorityOwner,
25935
26622
  store: this.store,
@@ -26237,10 +26924,12 @@ class WorkitCore {
26237
26924
  return workspace;
26238
26925
  if (!workspace.data)
26239
26926
  return failure("not_found", "workspace not found");
26240
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
26927
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
26928
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
26241
26929
  if (!current.ok)
26242
26930
  return current;
26243
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
26931
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
26932
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
26244
26933
  return success(task.revision, workspace.data.revision, {
26245
26934
  id: task.id,
26246
26935
  revision: task.revision,
@@ -26254,7 +26943,7 @@ class WorkitCore {
26254
26943
  progress: task.progress,
26255
26944
  policy: task.policy,
26256
26945
  requirements,
26257
- writer: workspace.data.writer
26946
+ writer: evaluationWorkspace.writer
26258
26947
  });
26259
26948
  }
26260
26949
  view(task) {
@@ -26263,13 +26952,15 @@ class WorkitCore {
26263
26952
  return workspace;
26264
26953
  if (!workspace.data)
26265
26954
  return failure("not_found", "workspace not found");
26266
- const current = captureCandidate(this.store.root, task.intent.data.scope, environment());
26955
+ const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
26956
+ const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
26267
26957
  if (!current.ok)
26268
26958
  return current;
26269
- const requirements = evaluateRequirements(task, workspace.data, this.context.capabilities, current.data, this.store.root, this.context.caller);
26959
+ const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
26960
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
26270
26961
  return success(task.revision, workspace.data.revision, {
26271
26962
  task,
26272
- workspace: workspace.data,
26963
+ workspace: evaluationWorkspace,
26273
26964
  capabilities: this.context.capabilities,
26274
26965
  requirements,
26275
26966
  evidence: evaluateEvidence(task, current.data)
@@ -26284,7 +26975,7 @@ class WorkitCore {
26284
26975
  if (view.task.workspaceId !== view.workspace.id)
26285
26976
  return failure("invalid_input", "task and workspace bindings are invalid");
26286
26977
  try {
26287
- if (realpathSync5(view.workspace.root) !== this.store.root)
26978
+ if (realpathSync6(view.workspace.root) !== this.store.root)
26288
26979
  return failure("invalid_input", "task view root does not match the task store");
26289
26980
  } catch {
26290
26981
  return failure("invalid_input", "task view root cannot be resolved");
@@ -26360,8 +27051,6 @@ class WorkitCore {
26360
27051
  return helper;
26361
27052
  if (status === "paused" && task.data.status !== "active" || status === "active" && task.data.status !== "paused")
26362
27053
  return failure("invalid_transition", `cannot transition ${task.data.status} to ${status}`);
26363
- if (status === "active" && input.authorityRefs.length === 0)
26364
- return failure("permission_denied", "resuming a task requires authority references");
26365
27054
  const workspace = this.store.readWorkspace();
26366
27055
  if (!workspace.ok)
26367
27056
  return workspace;
@@ -26391,7 +27080,7 @@ class WorkitCore {
26391
27080
  return failure("needs_input", "imported task requires fresh destination evidence");
26392
27081
  if (current.data.blockers.some((blocker) => blocker.dependentAction === "resume" || blocker.reason === "worker state requires reconciliation"))
26393
27082
  return failure("recovery_required", "imported task requires resume reconciliation");
26394
- if (!validResumeApproval(task.data, workspace.data, input.authorityRefs, this.context))
27083
+ if (!validResumeApproval(task.data, workspace.data, input.authorityRefs ?? [], this.context))
26395
27084
  return failure("permission_denied", "imported resume requires native destination approval");
26396
27085
  resumeCandidate = current.data.candidate;
26397
27086
  }
@@ -26521,7 +27210,7 @@ class WorkitCore {
26521
27210
  task: (value, context) => success(context.revision, null, {
26522
27211
  ...value,
26523
27212
  status: "closed",
26524
- candidates: value.candidates.some((item) => item.id === withCandidate.id) ? value.candidates : [...value.candidates, withCandidate],
27213
+ candidates: taskForView.candidates,
26525
27214
  closure: {
26526
27215
  outcome: closure.data.outcome,
26527
27216
  at: context.now,
@@ -26563,22 +27252,22 @@ function resolveGitRevision(root, value) {
26563
27252
  }
26564
27253
  }
26565
27254
  function resolveInside(root, candidate) {
26566
- const base = realpathSync6(root);
26567
- const target = path10.resolve(base, candidate);
26568
- if (target !== base && !target.startsWith(base + path10.sep)) {
27255
+ const base = realpathSync7(root);
27256
+ const target = path11.resolve(base, candidate);
27257
+ if (target !== base && !target.startsWith(base + path11.sep)) {
26569
27258
  throw new Error("path must stay inside repository root");
26570
27259
  }
26571
27260
  let ancestor = target;
26572
27261
  while (!existsSync5(ancestor))
26573
- ancestor = path10.dirname(ancestor);
26574
- const canonicalAncestor = realpathSync6(ancestor);
26575
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path10.sep)) {
27262
+ ancestor = path11.dirname(ancestor);
27263
+ const canonicalAncestor = realpathSync7(ancestor);
27264
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
26576
27265
  throw new Error("path must stay inside repository root");
26577
27266
  }
26578
27267
  return target;
26579
27268
  }
26580
27269
  function run2(root, executable, args, env = {}) {
26581
- const cwd = realpathSync6(root);
27270
+ const cwd = realpathSync7(root);
26582
27271
  const result = spawnSync3(executable, args, {
26583
27272
  cwd,
26584
27273
  encoding: "utf8",
@@ -26651,18 +27340,19 @@ var redactSecrets = (value) => applyPatterns(value);
26651
27340
 
26652
27341
  // packages/workit-core/src/core/pr-create.ts
26653
27342
  import fs8 from "node:fs";
26654
- import path11 from "node:path";
27343
+ import path12 from "node:path";
27344
+ import { spawnSync as spawnSync4 } from "node:child_process";
26655
27345
  function parseGhIssue(value) {
26656
27346
  const m = /issues\/(\d+)/.exec(value);
26657
27347
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26658
27348
  }
26659
27349
  function whichOnPath(tool) {
26660
27350
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26661
- for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
27351
+ for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
26662
27352
  if (!dir)
26663
27353
  continue;
26664
27354
  for (const name of names) {
26665
- const candidate = path11.join(dir, name);
27355
+ const candidate = path12.join(dir, name);
26666
27356
  try {
26667
27357
  fs8.accessSync(candidate, fs8.constants.X_OK);
26668
27358
  return candidate;
@@ -26672,11 +27362,21 @@ function whichOnPath(tool) {
26672
27362
  return null;
26673
27363
  }
26674
27364
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
27365
+ var pushRemote = (cwd) => {
27366
+ const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
27367
+ cwd,
27368
+ encoding: "utf8"
27369
+ });
27370
+ if (result.status !== 0)
27371
+ return null;
27372
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
27373
+ return urls.length === 1 ? urls[0] : null;
27374
+ };
26675
27375
 
26676
27376
  // packages/workit-core/src/core/repo-context.ts
26677
- import { spawnSync as spawnSync4 } from "node:child_process";
27377
+ import { spawnSync as spawnSync5 } from "node:child_process";
26678
27378
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26679
- import path12 from "node:path";
27379
+ import path13 from "node:path";
26680
27380
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26681
27381
  try {
26682
27382
  gitRevisionParts(value);
@@ -26703,7 +27403,7 @@ var invalidRange = (cwd) => ({
26703
27403
  cwd
26704
27404
  });
26705
27405
  var runGit = (cwd, args) => {
26706
- const result = spawnSync4("git", args, { cwd, encoding: "utf8" });
27406
+ const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
26707
27407
  return {
26708
27408
  stdout: (result.stdout ?? "").trimEnd(),
26709
27409
  stderr: (result.stderr ?? "").trimEnd(),
@@ -26810,7 +27510,7 @@ var findPrTemplate = (cwd) => {
26810
27510
  ]) {
26811
27511
  const parts = rel.split("/");
26812
27512
  const base = parts[parts.length - 1];
26813
- const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
27513
+ const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
26814
27514
  const actual = probeCaseInsensitive(dir, base);
26815
27515
  if (actual)
26816
27516
  return [...parts.slice(0, -1), actual].join("/");
@@ -26841,7 +27541,7 @@ var diffStatForRange = (cwd, range) => {
26841
27541
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26842
27542
  };
26843
27543
  var packageScripts = (cwd, keys) => {
26844
- const pkgPath = path12.join(cwd, "package.json");
27544
+ const pkgPath = path13.join(cwd, "package.json");
26845
27545
  if (!existsSync6(pkgPath))
26846
27546
  return [];
26847
27547
  try {
@@ -26867,9 +27567,9 @@ var documentationFiles = (cwd) => {
26867
27567
  if (entry.isDirectory()) {
26868
27568
  if (excluded.has(entry.name))
26869
27569
  continue;
26870
- walk(path12.join(dir, entry.name), depth + 1);
27570
+ walk(path13.join(dir, entry.name), depth + 1);
26871
27571
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26872
- matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
27572
+ matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
26873
27573
  }
26874
27574
  }
26875
27575
  };
@@ -26885,7 +27585,7 @@ var readTrimmed = (file, maxLines) => {
26885
27585
  `);
26886
27586
  };
26887
27587
  function prReadyContext(root, range) {
26888
- const cwd = path12.resolve(repoRoot(root));
27588
+ const cwd = path13.resolve(repoRoot(root));
26889
27589
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26890
27590
  return invalidRange(cwd);
26891
27591
  let stdout = "";
@@ -26943,7 +27643,7 @@ ${status.stderr}` : "");
26943
27643
  stdout += `template_path: ${template}
26944
27644
 
26945
27645
  `;
26946
- stdout += readTrimmed(path12.join(cwd, template), 220);
27646
+ stdout += readTrimmed(path13.join(cwd, template), 220);
26947
27647
  } else {
26948
27648
  stdout += `template_path: none
26949
27649
 
@@ -26953,7 +27653,7 @@ ${status.stderr}` : "");
26953
27653
  stdout += `
26954
27654
  `;
26955
27655
  stdout += printSection("Recent Validation Signals");
26956
- if (existsSync6(path12.join(cwd, "package.json"))) {
27656
+ if (existsSync6(path13.join(cwd, "package.json"))) {
26957
27657
  stdout += `package.json detected. Common scripts:
26958
27658
  `;
26959
27659
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -26962,7 +27662,7 @@ ${status.stderr}` : "");
26962
27662
  `) + `
26963
27663
  `;
26964
27664
  }
26965
- if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
27665
+ if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
26966
27666
  stdout += `Rust project detected.
26967
27667
  `;
26968
27668
  }
@@ -26995,7 +27695,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
26995
27695
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
26996
27696
  - If Unreleased already has duplicate category headings, normalize_only first.`;
26997
27697
  function changelogContext(root, range) {
26998
- const cwd = path12.resolve(repoRoot(root));
27698
+ const cwd = path13.resolve(repoRoot(root));
26999
27699
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27000
27700
  return invalidRange(cwd);
27001
27701
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27013,7 +27713,7 @@ function changelogContext(root, range) {
27013
27713
  stdout += CHANGELOG_RULES + `
27014
27714
  `;
27015
27715
  stdout += printSection("Existing CHANGELOG.md");
27016
- const changelogPath = path12.join(cwd, "CHANGELOG.md");
27716
+ const changelogPath = path13.join(cwd, "CHANGELOG.md");
27017
27717
  if (existsSync6(changelogPath)) {
27018
27718
  stdout += readTrimmed(changelogPath, 260) + `
27019
27719
  `;
@@ -27033,7 +27733,7 @@ function changelogContext(root, range) {
27033
27733
  return { stdout, stderr: "", exitCode: 0, cwd };
27034
27734
  }
27035
27735
  function docsRefreshContext(root, range) {
27036
- const cwd = path12.resolve(repoRoot(root));
27736
+ const cwd = path13.resolve(repoRoot(root));
27037
27737
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27038
27738
  return invalidRange(cwd);
27039
27739
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27055,7 +27755,7 @@ function docsRefreshContext(root, range) {
27055
27755
  `) + `
27056
27756
  `;
27057
27757
  stdout += printSection("README Preview");
27058
- const readme = path12.join(cwd, "README.md");
27758
+ const readme = path13.join(cwd, "README.md");
27059
27759
  if (existsSync6(readme)) {
27060
27760
  stdout += readTrimmed(readme, 220) + `
27061
27761
  `;
@@ -27064,7 +27764,7 @@ function docsRefreshContext(root, range) {
27064
27764
  `;
27065
27765
  }
27066
27766
  stdout += printSection("Package Scripts");
27067
- const pkgPath = path12.join(cwd, "package.json");
27767
+ const pkgPath = path13.join(cwd, "package.json");
27068
27768
  if (existsSync6(pkgPath)) {
27069
27769
  try {
27070
27770
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
@@ -27078,7 +27778,7 @@ function docsRefreshContext(root, range) {
27078
27778
  return { stdout, stderr: "", exitCode: 0, cwd };
27079
27779
  }
27080
27780
  function releaseNotesContext(root, rangeOrTag) {
27081
- const cwd = path12.resolve(repoRoot(root));
27781
+ const cwd = path13.resolve(repoRoot(root));
27082
27782
  if (!rangeOrTag) {
27083
27783
  return { stdout: "", stderr: `ERROR: release tag or range required
27084
27784
  `, exitCode: 1, cwd };
@@ -27116,7 +27816,7 @@ function releaseNotesContext(root, rangeOrTag) {
27116
27816
  `;
27117
27817
  stdout += printSection("Existing Release Files");
27118
27818
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
27119
- if (existsSync6(path12.join(cwd, rel)))
27819
+ if (existsSync6(path13.join(cwd, rel)))
27120
27820
  stdout += rel + `
27121
27821
  `;
27122
27822
  }
@@ -27144,19 +27844,7 @@ function releaseNotesContext(root, rangeOrTag) {
27144
27844
  }
27145
27845
 
27146
27846
  // packages/workit-core/src/core/tracker-issues.ts
27147
- import fs9 from "node:fs";
27148
- import { spawnSync as spawnSync5 } from "node:child_process";
27149
- var trackerRequest = async (url, init) => {
27150
- try {
27151
- const res = await fetch(url, { method: init.method, headers: init.headers });
27152
- const text = await res.text();
27153
- if (!res.ok)
27154
- return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
27155
- return { status: 0, stdout: text, stderr: "" };
27156
- } catch (err) {
27157
- return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
27158
- }
27159
- };
27847
+ import { spawnSync as spawnSync6 } from "node:child_process";
27160
27848
  var repoPathFromRemote = (remote) => {
27161
27849
  let rest = (remote || "").trim().replace(/\/+$/, "");
27162
27850
  if (!rest)
@@ -27177,7 +27865,11 @@ var defaultCli = (bin, available) => {
27177
27865
  if (!available)
27178
27866
  return null;
27179
27867
  return (args, root) => {
27180
- const out = spawnSync5(bin, args, { cwd: root, encoding: "utf8" });
27868
+ const out = spawnSync6(bin, args, {
27869
+ cwd: root,
27870
+ encoding: "utf8",
27871
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
27872
+ });
27181
27873
  return Promise.resolve({
27182
27874
  status: out.status ?? 1,
27183
27875
  stdout: out.stdout ?? "",
@@ -27185,49 +27877,22 @@ var defaultCli = (bin, available) => {
27185
27877
  });
27186
27878
  };
27187
27879
  };
27188
- var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
27880
+ var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
27189
27881
  if (!cli)
27190
- return null;
27882
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27191
27883
  const run = cli(args, root);
27192
27884
  if (!run)
27193
- return null;
27885
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27194
27886
  const out = await run;
27195
27887
  if (out.status !== 0)
27196
- return null;
27888
+ return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
27197
27889
  try {
27198
- return parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27890
+ const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27891
+ return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
27199
27892
  } catch {
27200
- return null;
27893
+ return { error: `invalid ${name} issue response`, kind: "request" };
27201
27894
  }
27202
27895
  };
27203
- var originRemote = (root) => {
27204
- const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
27205
- return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
27206
- };
27207
- var trackerCreds = (root, provider) => {
27208
- const cfg = vcsConfig("load", root);
27209
- if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
27210
- return { error: `unconfigured ${provider} provider` };
27211
- let token = "";
27212
- try {
27213
- token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
27214
- } catch {
27215
- return { error: `missing ${provider} token` };
27216
- }
27217
- if (!token)
27218
- return { error: `empty ${provider} token file` };
27219
- if (provider === "gitlab") {
27220
- const gitlab = cfg.gitlab;
27221
- const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
27222
- return { token, api: apiUrl };
27223
- }
27224
- const github = cfg.github;
27225
- const host = String(github?.host ?? "github.com").toLowerCase();
27226
- return {
27227
- token,
27228
- api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
27229
- };
27230
- };
27231
27896
  var parseTriple = (parsed, id, idKeys, bodyKeys) => {
27232
27897
  const title = parsed.title;
27233
27898
  if (typeof title !== "string")
@@ -27250,62 +27915,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
27250
27915
  const id = parseGhIssue(ref);
27251
27916
  if (!/^\d+$/.test(id))
27252
27917
  return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
27253
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27918
+ const remote = (deps.remote ?? pushRemote)(root);
27919
+ const repo = repoPathFromRemote(remote ?? "");
27254
27920
  if (!repo)
27255
27921
  return { error: "no origin remote to resolve the repository", kind: "input" };
27256
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), ["issue", "view", id, "--repo", repo, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27257
- if (viaCli)
27258
- return { data: viaCli };
27259
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "github")))(root);
27260
- if ("error" in credentials)
27261
- return { ...credentials, kind: "creds" };
27262
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
27263
- method: "GET",
27264
- headers: {
27265
- Authorization: `Bearer ${credentials.token}`,
27266
- Accept: "application/vnd.github+json"
27267
- }
27268
- });
27269
- if (out.status !== 0)
27270
- return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
27271
- try {
27272
- const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
27273
- if (!triple)
27274
- return { error: "unexpected GitHub issue shape", kind: "request" };
27275
- return { data: triple };
27276
- } catch {
27277
- return { error: "invalid GitHub issue response", kind: "request" };
27278
- }
27922
+ const cfg = vcsConfig("load", root);
27923
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
27924
+ if (!remote || !hostingApiHostMatches(remote, host))
27925
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27926
+ const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
27927
+ return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27279
27928
  };
27280
27929
  var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
27281
27930
  const id = parseGhIssue(ref);
27282
27931
  if (!/^\d+$/.test(id))
27283
27932
  return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
27284
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27933
+ const remote = (deps.remote ?? pushRemote)(root);
27934
+ const repo = repoPathFromRemote(remote ?? "");
27285
27935
  if (!repo)
27286
27936
  return { error: "no origin remote to resolve the project", kind: "input" };
27287
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), ["issue", "view", id, "-R", repo, "-F", "json"], root, id, ["iid"], ["description"]);
27288
- if (viaCli)
27289
- return { data: viaCli };
27290
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "gitlab")))(root);
27291
- if ("error" in credentials)
27292
- return { ...credentials, kind: "creds" };
27293
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
27294
- if (out.status !== 0)
27295
- return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
27296
- try {
27297
- const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
27298
- if (!triple)
27299
- return { error: "unexpected GitLab issue shape", kind: "request" };
27300
- return { data: triple };
27301
- } catch {
27302
- return { error: "invalid GitLab issue response", kind: "request" };
27303
- }
27937
+ const cfg = vcsConfig("load", root);
27938
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
27939
+ if (!remote || !hostingApiHostMatches(remote, host))
27940
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27941
+ const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
27942
+ return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
27304
27943
  };
27305
27944
 
27306
27945
  // packages/workit-core/src/core/youtrack.ts
27307
- import fs10 from "node:fs";
27308
- import path13 from "node:path";
27946
+ import fs9 from "node:fs";
27947
+ import path14 from "node:path";
27309
27948
 
27310
27949
  // packages/workit-core/src/core/templates.ts
27311
27950
  var repoRoot2 = assetRoot();
@@ -27317,23 +27956,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
27317
27956
 
27318
27957
  // packages/workit-core/src/core/youtrack.ts
27319
27958
  var ISSUE_RE = /^[A-Z]+-\d+$/;
27320
- var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
27321
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path13.join(configDir(), "youtrack.json");
27322
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path13.join(resolveConfigDir(), "youtrack.json");
27959
+ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
27960
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
27961
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
27323
27962
  var youTrackTokenModeOk = (p) => {
27324
27963
  if (process.platform === "win32")
27325
27964
  return true;
27326
- const mode = fs10.statSync(p).mode & 511;
27965
+ const mode = fs9.statSync(p).mode & 511;
27327
27966
  return mode === 384;
27328
27967
  };
27329
27968
  function readYouTrackConfig(required) {
27330
27969
  const cfgPath = youTrackConfigPath();
27331
- if (!fs10.existsSync(cfgPath)) {
27970
+ if (!fs9.existsSync(cfgPath)) {
27332
27971
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
27333
27972
  }
27334
27973
  let parsed;
27335
27974
  try {
27336
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
27975
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27337
27976
  } catch {
27338
27977
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
27339
27978
  }
@@ -27344,11 +27983,11 @@ function readYouTrackConfig(required) {
27344
27983
  }
27345
27984
  var readYouTrackContextConfig = () => {
27346
27985
  const cfgPath = youTrackReadOnlyConfigPath();
27347
- if (!fs10.existsSync(cfgPath))
27986
+ if (!fs9.existsSync(cfgPath))
27348
27987
  return { data: {} };
27349
27988
  let parsed;
27350
27989
  try {
27351
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
27990
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27352
27991
  } catch {
27353
27992
  return { error: "YouTrack configuration is unavailable" };
27354
27993
  }
@@ -27373,12 +28012,12 @@ function youTrackGreeting(configOverride) {
27373
28012
  const cfgPath = configOverride ?? youTrackConfigPath();
27374
28013
  let parsed;
27375
28014
  try {
27376
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28015
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27377
28016
  } catch {
27378
28017
  return {
27379
28018
  stdout: "",
27380
28019
  exitCode: 1,
27381
- stderr: fs10.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
28020
+ stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
27382
28021
  };
27383
28022
  }
27384
28023
  if (!isConfigObject(parsed)) {
@@ -27424,15 +28063,15 @@ var youTrackToken = () => {
27424
28063
  if ("error" in loaded)
27425
28064
  return loaded;
27426
28065
  const tokenFile = String(loaded.config.tokenFile ?? "");
27427
- const tokenPath = tokenFile ? path13.isAbsolute(tokenFile) ? path13.resolve(tokenFile) : path13.resolve(process.cwd(), tokenFile) : "";
27428
- if (!tokenPath || !fs10.existsSync(tokenPath))
28066
+ const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
28067
+ if (!tokenPath || !fs9.existsSync(tokenPath))
27429
28068
  return { error: "missing youtrack.token" };
27430
28069
  if (!youTrackTokenModeOk(tokenPath))
27431
28070
  return { error: "youtrack.token mode must be 0600" };
27432
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
28071
+ const token = fs9.readFileSync(tokenPath, "utf8").trim();
27433
28072
  if (!token)
27434
28073
  return { error: "empty token file" };
27435
- if (token === TOKEN_PLACEHOLDER2 || token.startsWith(TOKEN_PLACEHOLDER2)) {
28074
+ if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
27436
28075
  return {
27437
28076
  error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
27438
28077
  };
@@ -27495,9 +28134,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
27495
28134
  } catch {
27496
28135
  continue;
27497
28136
  }
27498
- if (!fs10.existsSync(full))
28137
+ if (!fs9.existsSync(full))
27499
28138
  continue;
27500
- const text = fs10.readFileSync(full, "utf8");
28139
+ const text = fs9.readFileSync(full, "utf8");
27501
28140
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
27502
28141
  if (m)
27503
28142
  return m[1];
@@ -27731,6 +28370,7 @@ var readExternalContext = async (root, payload) => {
27731
28370
  }
27732
28371
  }
27733
28372
  };
28373
+ var targetWriterActionLease = Symbol("target-writer-action-lease");
27734
28374
 
27735
28375
  // packages/workit-mcp/src/server.ts
27736
28376
  class McpCapabilityUnavailableError extends Error {
@@ -27775,13 +28415,13 @@ var rootVariants = (workspaceRoot) => {
27775
28415
  if (!workspaceRoot)
27776
28416
  return [];
27777
28417
  const roots = new Set;
27778
- for (const candidate of [workspaceRoot, path14.resolve(workspaceRoot)]) {
27779
- if (path14.isAbsolute(candidate) && candidate !== path14.parse(candidate).root)
28418
+ for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
28419
+ if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
27780
28420
  roots.add(candidate);
27781
28421
  }
27782
28422
  try {
27783
- const canonical = realpathSync7(workspaceRoot);
27784
- if (canonical !== path14.parse(canonical).root)
28423
+ const canonical = realpathSync8(workspaceRoot);
28424
+ if (canonical !== path15.parse(canonical).root)
27785
28425
  roots.add(canonical);
27786
28426
  } catch {}
27787
28427
  return [...roots].sort((left, right) => right.length - left.length);
@@ -27882,11 +28522,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
27882
28522
  };
27883
28523
  function createMcpServer(host, contextProvider) {
27884
28524
  assertMcpHost(host);
27885
- const here = path14.dirname(fileURLToPath2(import.meta.url));
28525
+ const here = path15.dirname(fileURLToPath2(import.meta.url));
27886
28526
  const sourceMarker = markSourcesLoaded([
27887
- path14.join(here, "server.ts"),
27888
- path14.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
27889
- path14.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
28527
+ path15.join(here, "server.ts"),
28528
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
28529
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
27890
28530
  ].filter((file) => existsSync7(file)));
27891
28531
  let staleWarned = false;
27892
28532
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
@@ -28066,8 +28706,8 @@ var main = async () => {
28066
28706
  process.exitCode = 2;
28067
28707
  }
28068
28708
  };
28069
- var entryPath = process.argv[1] ? path15.resolve(process.argv[1]) : "";
28070
- if (entryPath === path15.resolve(fileURLToPath3(import.meta.url)))
28709
+ var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
28710
+ if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
28071
28711
  await main();
28072
28712
  export {
28073
28713
  McpCapabilityUnavailableError,