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