@brainervirus/workit-cursor 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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({
@@ -20516,7 +20531,7 @@ var taskSummarySchema = object4({
20516
20531
  requirements: array3(requirementEvaluationSchema),
20517
20532
  writer: workspaceRecordSchema.shape.writer
20518
20533
  }).strict();
20519
- var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true });
20534
+ var taskListItemSchema = taskSummarySchema.omit({ policy: true, requirements: true }).extend({ source: provenanceSchema.pick({ host: true, kind: true }) }).strict();
20520
20535
  var exportBundleSchema = object4({
20521
20536
  schemaVersion: literal3(1),
20522
20537
  exportedAt: utc,
@@ -20541,6 +20556,7 @@ var taskOperations = {
20541
20556
  list: operation({
20542
20557
  action: literal3("list"),
20543
20558
  status: _enum2(["open", "closed", "all"]).optional(),
20559
+ query: string5().trim().min(1).max(200).optional(),
20544
20560
  limit: number5().int().min(1).max(50).optional()
20545
20561
  }),
20546
20562
  inspect: operation({
@@ -20888,6 +20904,7 @@ function requirementId(input) {
20888
20904
  }
20889
20905
  // packages/workit-core/src/core/task-store.ts
20890
20906
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
20907
+ import { AsyncLocalStorage } from "node:async_hooks";
20891
20908
  import * as fs4 from "node:fs";
20892
20909
  import { hostname as hostname2 } from "node:os";
20893
20910
  import path4 from "node:path";
@@ -21523,7 +21540,8 @@ var metadataLockSchema = object4({
21523
21540
  pid: number5().int().nonnegative().safe(),
21524
21541
  processStart: string5().nullable(),
21525
21542
  host: string5().min(1),
21526
- nonce: string5().min(1)
21543
+ nonce: string5().min(1),
21544
+ externalAction: literal3(true).optional()
21527
21545
  }).strict();
21528
21546
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21529
21547
  var cachedRuntimeVersion = null;
@@ -21575,6 +21593,28 @@ var sameMetadataLock = (left, right) => {
21575
21593
  const parsed = metadataLockSchema.safeParse(left);
21576
21594
  return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
21577
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;
21578
21618
 
21579
21619
  class TaskStore {
21580
21620
  root;
@@ -21635,7 +21675,7 @@ class TaskStore {
21635
21675
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
21636
21676
  if (!item.exists)
21637
21677
  return success(null, null, null);
21638
- 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))
21639
21679
  return failure("recovery_required", "workspace root binding is invalid", {
21640
21680
  path: this.workspacePath
21641
21681
  });
@@ -21726,6 +21766,29 @@ class TaskStore {
21726
21766
  const current = this.readWorkspace();
21727
21767
  if (!current.ok)
21728
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
+ }
21729
21792
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
21730
21793
  return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21731
21794
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
@@ -22114,6 +22177,73 @@ class TaskStore {
22114
22177
  })
22115
22178
  };
22116
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
+ }
22117
22247
  recoveryLockOptions(observed, evidence, gate) {
22118
22248
  const options = this.metadataLockOptions();
22119
22249
  options.staleRecovery = "remove-if-unchanged";
@@ -22183,6 +22313,14 @@ class TaskStore {
22183
22313
  lockFailure(error) {
22184
22314
  const value = error;
22185
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
+ }
22186
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));
22187
22325
  return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
22188
22326
  }
@@ -22450,21 +22588,23 @@ function assertProductWriteAllowed(input) {
22450
22588
  }
22451
22589
 
22452
22590
  // packages/workit-core/src/core/task-evaluation.ts
22453
- import { createHash as createHash5 } from "node:crypto";
22591
+ import { createHash as createHash6 } from "node:crypto";
22454
22592
  import * as fs7 from "node:fs";
22455
22593
  import { spawnSync as spawnSync2 } from "node:child_process";
22456
- import path9 from "node:path";
22594
+ import path10 from "node:path";
22457
22595
 
22458
22596
  // packages/workit-core/src/core/authority.ts
22459
- import { createHash as createHash4 } from "node:crypto";
22597
+ import { createHash as createHash5 } from "node:crypto";
22460
22598
  import * as fs6 from "node:fs";
22461
- import path8 from "node:path";
22599
+ import path9 from "node:path";
22462
22600
 
22463
22601
  // packages/workit-core/src/core/auto-approval.ts
22464
- import { createHash as createHash3 } from "node:crypto";
22465
- 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";
22466
22605
 
22467
22606
  // packages/workit-core/src/core/workspaces.ts
22607
+ import { createHash as createHash3 } from "node:crypto";
22468
22608
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
22469
22609
  import path6 from "node:path";
22470
22610
 
@@ -22482,7 +22622,7 @@ import os from "node:os";
22482
22622
  import path5 from "node:path";
22483
22623
 
22484
22624
  // packages/workit-core/src/core/boundary.ts
22485
- import { statSync } from "node:fs";
22625
+ import { statSync as statSync2 } from "node:fs";
22486
22626
  var EVENT = {
22487
22627
  initialization: "initialization",
22488
22628
  provenance: "provenance",
@@ -22510,7 +22650,7 @@ var changedSourcesSinceLoad = (marker) => {
22510
22650
  const changed = [];
22511
22651
  for (const file of marker.files) {
22512
22652
  try {
22513
- if (statSync(file).mtimeMs > marker.loadedAt)
22653
+ if (statSync2(file).mtimeMs > marker.loadedAt)
22514
22654
  changed.push(file);
22515
22655
  } catch {}
22516
22656
  }
@@ -22669,33 +22809,197 @@ var COMMIT_PRESETS = [
22669
22809
  "custom",
22670
22810
  "auto"
22671
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
+ };
22672
22822
 
22673
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
+ };
22674
22974
  var readWorkspacesResult = (dir = configDir()) => {
22675
22975
  const file = path6.join(dir, "workspaces.json");
22676
22976
  let raw;
22677
22977
  try {
22678
22978
  raw = readFileSync3(file, "utf8");
22679
- } catch {
22680
- 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
+ };
22681
22988
  }
22682
22989
  let parsed;
22683
22990
  try {
22684
22991
  parsed = JSON.parse(raw);
22685
22992
  } catch {
22686
- return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
22687
- }
22688
- if (!isConfigObject(parsed)) {
22689
- 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
+ };
22690
23000
  }
22691
- const list = parsed.workspaces;
22692
- return {
22693
- status: "valid",
22694
- path: file,
22695
- entries: Array.isArray(list) ? list : []
22696
- };
23001
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
22697
23002
  };
22698
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
22699
23003
  var globToRegExp = (glob) => {
22700
23004
  let out = "";
22701
23005
  for (let i = 0;i < glob.length; i++) {
@@ -22730,6 +23034,30 @@ var globToRegExp = (glob) => {
22730
23034
  return new RegExp(`^${out}$`);
22731
23035
  };
22732
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
+ };
22733
23061
  var realpathOf = (p) => {
22734
23062
  const real = realpathSync2.native ?? realpathSync2;
22735
23063
  try {
@@ -22747,29 +23075,137 @@ var canonicalGlob = (glob) => {
22747
23075
  const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
22748
23076
  return real + (prefix.endsWith("/") ? "/" : "") + rest;
22749
23077
  };
22750
- 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);
22751
23082
  const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
22752
- for (const entry of loadWorkspacesFrom(dir)) {
22753
- if (!entry || typeof entry !== "object")
22754
- continue;
22755
- const ws = entry;
22756
- if (typeof ws.glob !== "string" || !ws.glob)
22757
- continue;
23083
+ const matches = [];
23084
+ for (const ws of result.entries) {
22758
23085
  const glob = ws.glob.replaceAll("\\", "/");
22759
23086
  const canonical = canonicalGlob(glob);
23087
+ let matched = false;
22760
23088
  for (const target of targets) {
22761
- if (matchWorkspace(glob, target))
22762
- return ws;
22763
- if (canonical !== glob && matchWorkspace(canonical, target))
22764
- return ws;
23089
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
23090
+ matched = true;
23091
+ break;
23092
+ }
22765
23093
  }
23094
+ if (matched)
23095
+ matches.push(ws);
22766
23096
  }
22767
- 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
+ };
22768
23208
  };
22769
- var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
22770
-
22771
- // packages/workit-core/src/core/branch.ts
22772
- import { execFileSync as execFileSync2 } from "node:child_process";
22773
23209
 
22774
23210
  // packages/workit-core/src/core/git.ts
22775
23211
  import { execFileSync } from "node:child_process";
@@ -22843,7 +23279,6 @@ var gitContext = (workspaceRoot, paths = []) => {
22843
23279
  import fs5 from "node:fs";
22844
23280
  import path7 from "node:path";
22845
23281
  import { spawnSync } from "node:child_process";
22846
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
22847
23282
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
22848
23283
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22849
23284
  var remoteProvider = (cwd) => {
@@ -22857,6 +23292,79 @@ var remoteProvider = (cwd) => {
22857
23292
  return "gitlab";
22858
23293
  return null;
22859
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
+ };
22860
23368
  var readVcsConfig = () => {
22861
23369
  const file = vcsConfigPath();
22862
23370
  let raw;
@@ -22877,7 +23385,7 @@ var readVcsConfig = () => {
22877
23385
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22878
23386
  };
22879
23387
  function vcsConfig(mode, cwd) {
22880
- const ws = resolveWorkspace(vcsCwd(cwd));
23388
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
22881
23389
  const wsVcs = ws?.vcs ?? {};
22882
23390
  const wsYt = ws?.youtrack ?? {};
22883
23391
  const wsIssues = ws?.issues ?? {};
@@ -22890,8 +23398,8 @@ function vcsConfig(mode, cwd) {
22890
23398
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
22891
23399
  const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
22892
23400
  const defaultTarget = String(wsVcs.defaultTargetBranch ?? (hasWorkspacePolicy ? policyDefault : cfg.defaultTargetBranch ?? policyDefault) ?? "develop");
22893
- const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
22894
- 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;
22895
23403
  let issuesProvider = null;
22896
23404
  let linkOnPr = null;
22897
23405
  if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
@@ -22915,9 +23423,8 @@ function vcsConfig(mode, cwd) {
22915
23423
  if (cfgStatus === "malformed") {
22916
23424
  return { ok: false, error: cfgError, configPath: cfgPath };
22917
23425
  }
22918
- if (cfgStatus === "missing") {
23426
+ if (cfgStatus === "missing" && provider === null)
22919
23427
  return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
22920
- }
22921
23428
  if (provider === null) {
22922
23429
  return {
22923
23430
  ok: false,
@@ -22926,24 +23433,12 @@ function vcsConfig(mode, cwd) {
22926
23433
  };
22927
23434
  }
22928
23435
  const prov = cfg[provider] ?? {};
22929
- const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
22930
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path7.join(configDir(), `${provider}.token`));
22931
- const tokenPath = path7.resolve(tokenFile);
22932
- let tokenOk = false;
22933
- if (fs5.existsSync(tokenPath)) {
22934
- const token = fs5.readFileSync(tokenPath, "utf8").trim();
22935
- const placeholder = !token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER);
22936
- tokenOk = !placeholder;
22937
- }
22938
23436
  const out = {
22939
23437
  ok: true,
22940
23438
  configPath: path7.resolve(cfgPath),
22941
23439
  provider,
22942
23440
  defaultTargetBranch: defaultTarget,
22943
23441
  pr: cfg.pr ?? {},
22944
- tokenPath,
22945
- tokenPresent: fs5.existsSync(tokenPath),
22946
- tokenReady: tokenOk,
22947
23442
  workspace_name: ws?.name ?? null,
22948
23443
  link_issues: linkIssues,
22949
23444
  youtrack_base_url: youtrackBaseUrl,
@@ -22958,16 +23453,58 @@ function vcsConfig(mode, cwd) {
22958
23453
  } else if (provider === "github") {
22959
23454
  out.github = { host: prov.host ?? "github.com" };
22960
23455
  }
22961
- if (mode === "summary")
22962
- delete out.tokenPath;
22963
23456
  return out;
22964
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
+ }
22965
23497
  function mergedPrStyle(limit = 6, cwd) {
22966
23498
  const cfg = vcsConfig("load", cwd);
22967
- if (!cfg.ok || !cfg.tokenReady)
23499
+ if (!cfg.ok)
22968
23500
  return { ok: false, error: "vcs not configured" };
22969
23501
  const provider = cfg.provider;
22970
- 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
+ };
22971
23508
  const examples = [];
22972
23509
  const descInfo = (desc, caseInsensitiveNotes = false) => ({
22973
23510
  hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
@@ -22975,23 +23512,30 @@ function mergedPrStyle(limit = 6, cwd) {
22975
23512
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22976
23513
  descriptionPreview: desc.slice(0, 600)
22977
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" };
22978
23530
  if (provider === "gitlab") {
22979
- const remote = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
22980
- if (remote.status !== 0)
22981
- return { ok: false, error: "no origin remote" };
22982
- const url = (remote.stdout ?? "").trim();
22983
- const m = /gitlab\.com[:/](.+?)(?:\.git)?$/.exec(url);
22984
- if (!m)
22985
- return { ok: false, error: "not a gitlab.com origin" };
22986
- const project = m[1];
22987
- const env = { ...process.env, GITLAB_TOKEN: token };
22988
- 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
+ });
22989
23536
  let r = run([
22990
- `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`
22991
23538
  ]);
22992
- if (r.status !== 0) {
22993
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
22994
- }
22995
23539
  if (r.status !== 0)
22996
23540
  return { ok: false, error: "could not list merge requests" };
22997
23541
  for (const mr of JSON.parse(r.stdout ?? "[]")) {
@@ -23004,7 +23548,18 @@ function mergedPrStyle(limit = 6, cwd) {
23004
23548
  });
23005
23549
  }
23006
23550
  } else if (provider === "github") {
23007
- 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 });
23008
23563
  if (r.status !== 0)
23009
23564
  return { ok: false, error: "could not list pull requests" };
23010
23565
  for (const pr of JSON.parse(r.stdout ?? "[]")) {
@@ -23026,26 +23581,28 @@ function mergedPrStyle(limit = 6, cwd) {
23026
23581
  }
23027
23582
 
23028
23583
  // packages/workit-core/src/core/branch.ts
23029
- 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;
23030
23596
  var verifyPushIdentity = (root, provider, account) => {
23031
23597
  if (!account || typeof account !== "string")
23032
23598
  return { ok: false, error: "push identity requires a configured area account" };
23033
- const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
23034
- const bin = provider === "gitlab" ? "glab" : "gh";
23035
- let login;
23036
- try {
23037
- login = execFileSync2(bin, [...probe], {
23038
- cwd: root,
23039
- encoding: "utf8",
23040
- stdio: ["pipe", "pipe", "pipe"]
23041
- }).trim();
23042
- } catch {
23043
- return { ok: false, error: `${bin} identity could not be resolved` };
23044
- }
23045
- 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())
23046
23603
  return {
23047
23604
  ok: false,
23048
- error: `push identity ${login || "(unknown)"} does not match area account ${account}`
23605
+ error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
23049
23606
  };
23050
23607
  return { ok: true };
23051
23608
  };
@@ -23063,7 +23620,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
23063
23620
  var digestOf = (configDirPath) => {
23064
23621
  try {
23065
23622
  const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
23066
- return createHash3("sha256").update(raw).digest("hex");
23623
+ return createHash4("sha256").update(raw).digest("hex");
23067
23624
  } catch {
23068
23625
  return null;
23069
23626
  }
@@ -23101,14 +23658,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
23101
23658
  };
23102
23659
  };
23103
23660
  var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
23104
- 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) => {
23105
23675
  if (!isStandingReceipt(receipt))
23106
23676
  return false;
23107
23677
  if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
23108
23678
  return false;
23109
23679
  if (receipt.class !== cls)
23110
23680
  return false;
23111
- const resolved = resolveAutoApproval(root, dir);
23681
+ const target = targetOf(root, descriptor);
23682
+ if (!target)
23683
+ return false;
23684
+ const resolved = resolveAutoApproval(target, dir);
23112
23685
  if (resolved.status !== "on")
23113
23686
  return false;
23114
23687
  if (resolved.workspace !== receipt.workspace)
@@ -23144,16 +23717,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23144
23717
  const cls = operationAutoClass(operation);
23145
23718
  if (!cls || cls !== standing.class)
23146
23719
  return failure("permission_denied", "standing approval does not cover this operation");
23147
- 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);
23148
23724
  if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
23149
23725
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23150
23726
  if (cls === "push") {
23151
- const workspace = resolveWorkspaceFrom(root, configDir());
23727
+ const workspace = resolveWorkspaceFrom(target, configDir());
23152
23728
  const provider = workspace?.vcs?.provider;
23153
23729
  const account = resolved.account;
23154
23730
  if (!provider || !account)
23155
23731
  return failure("permission_denied", "auto-push requires a workspace provider and area account");
23156
- const identity = verifyPushIdentity(root, provider, account);
23732
+ const identity = verifyPushIdentity(target, provider, account);
23157
23733
  if (!identity.ok)
23158
23734
  return failure("permission_denied", identity.error);
23159
23735
  }
@@ -23162,7 +23738,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23162
23738
  host: caller.host,
23163
23739
  session: { kind: "host", host: caller.host, handle: caller.actor },
23164
23740
  workerId: null,
23165
- receipts: [standingReceiptFor(root, standing.workspace, cls)]
23741
+ receipts: [standingReceiptFor(target, standing.workspace, cls)]
23166
23742
  });
23167
23743
  };
23168
23744
 
@@ -23313,7 +23889,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
23313
23889
  return [];
23314
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);
23315
23891
  }
23316
- var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
23892
+ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
23317
23893
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23318
23894
  let root;
23319
23895
  try {
@@ -23326,23 +23902,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23326
23902
  continue;
23327
23903
  if (!reference.digest)
23328
23904
  return failure("invalid_input", "document references require a byte digest");
23329
- const target = path8.resolve(root, reference.path);
23330
- 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}`))
23331
23907
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23332
23908
  fields: [
23333
23909
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
23334
23910
  ]
23335
23911
  });
23336
23912
  try {
23337
- const relative = path8.relative(root, target);
23913
+ const relative = path9.relative(root, target);
23338
23914
  let current = root;
23339
- for (const segment of relative.split(path8.sep)) {
23915
+ for (const segment of relative.split(path9.sep)) {
23340
23916
  if (!segment)
23341
23917
  continue;
23342
- current = path8.join(current, segment);
23918
+ current = path9.join(current, segment);
23343
23919
  const stat = fs6.lstatSync(current);
23344
23920
  const real = fs6.realpathSync(current);
23345
- if (real !== root && !real.startsWith(`${root}${path8.sep}`))
23921
+ if (real !== root && !real.startsWith(`${root}${path9.sep}`))
23346
23922
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23347
23923
  fields: [
23348
23924
  {
@@ -23420,7 +23996,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
23420
23996
  operation = null;
23421
23997
  }
23422
23998
  const cls = operationAutoClass(operation);
23423
- 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)))
23424
24000
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23425
24001
  }
23426
24002
  if (decision.revoked)
@@ -23722,15 +24298,15 @@ var bindingCovers = scopeCovers;
23722
24298
  var verifyDecisionContent = verifyContentRefs;
23723
24299
 
23724
24300
  // packages/workit-core/src/core/task-evaluation.ts
23725
- var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
23726
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path9.sep}`);
23727
- 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("/") || ".";
23728
24304
  var canonicalPath = (value) => {
23729
24305
  if (typeof value !== "string" || !value)
23730
24306
  return null;
23731
24307
  if (value.includes("\\") || value.split("/").includes(".."))
23732
24308
  return null;
23733
- const normalized = path9.posix.normalize(value);
24309
+ const normalized = path10.posix.normalize(value);
23734
24310
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
23735
24311
  return null;
23736
24312
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -23778,14 +24354,14 @@ var gitPaths = (root) => {
23778
24354
  git: /not a git repository/i.test(stderr) ? false : true
23779
24355
  };
23780
24356
  }
23781
- 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("/")) : [];
23782
24358
  const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
23783
24359
  if (stagedDeleted.status !== 0) {
23784
24360
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
23785
24361
  if (!/not a git repository/i.test(stderr))
23786
24362
  return { paths, uncertain: true, git: true };
23787
24363
  } else if (stagedDeleted.stdout) {
23788
- 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("/")));
23789
24365
  }
23790
24366
  return { paths: [...new Set(paths)], uncertain: false, git: true };
23791
24367
  };
@@ -23800,7 +24376,7 @@ var walk = (root, directory, output) => {
23800
24376
  for (const name of names) {
23801
24377
  if (name === ".git" || name === ".workit")
23802
24378
  continue;
23803
- const target = path9.join(directory, name);
24379
+ const target = path10.join(directory, name);
23804
24380
  let stat;
23805
24381
  try {
23806
24382
  stat = fs7.lstatSync(target);
@@ -23818,12 +24394,12 @@ var walk = (root, directory, output) => {
23818
24394
  var scopeRoots = (root, scope) => {
23819
24395
  const roots = [];
23820
24396
  for (const item of scope.paths.length ? scope.paths : ["."]) {
23821
- const target = path9.resolve(root, item);
24397
+ const target = path10.resolve(root, item);
23822
24398
  if (!inside(root, target))
23823
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` }] });
23824
24400
  let ancestor = target;
23825
24401
  while (!fs7.existsSync(ancestor) && ancestor !== root)
23826
- ancestor = path9.dirname(ancestor);
24402
+ ancestor = path10.dirname(ancestor);
23827
24403
  try {
23828
24404
  if (!inside(root, fs7.realpathSync(ancestor)))
23829
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` }] });
@@ -23875,7 +24451,7 @@ function captureCandidate(root, scope, environment = []) {
23875
24451
  for (const item of [...names].sort()) {
23876
24452
  if (!scopeMatches(item, normalizedScope))
23877
24453
  continue;
23878
- const target = path9.join(checkout, item);
24454
+ const target = path10.join(checkout, item);
23879
24455
  try {
23880
24456
  const stat = fs7.lstatSync(target);
23881
24457
  if (stat.isSymbolicLink()) {
@@ -24257,7 +24833,8 @@ var externalActionSchema = discriminatedUnion2("operation", [
24257
24833
  action: _enum2(["setup", "reapply_stash"]).optional(),
24258
24834
  sdd_dir: string5().optional(),
24259
24835
  target_branch: string5().optional(),
24260
- stash: _enum2(["yes", "no"]).optional()
24836
+ stash: _enum2(["yes", "no"]).optional(),
24837
+ cwd: string5().min(1).optional()
24261
24838
  }).strict()
24262
24839
  }).strict(),
24263
24840
  object4({
@@ -24265,12 +24842,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24265
24842
  payload: object4({
24266
24843
  message: string5().min(1).optional(),
24267
24844
  plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
24268
- plan_branch: string5().min(1).optional()
24845
+ plan_branch: string5().min(1).optional(),
24846
+ cwd: string5().min(1).optional()
24269
24847
  }).strict()
24270
24848
  }).strict(),
24271
24849
  object4({
24272
24850
  operation: literal3("git.push"),
24273
- payload: object4({ branch: string5().min(1).optional() }).strict()
24851
+ payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
24274
24852
  }).strict(),
24275
24853
  object4({
24276
24854
  operation: literal3("hosting.pull_request"),
@@ -24279,16 +24857,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
24279
24857
  body: string5().optional(),
24280
24858
  draft: boolean5().optional(),
24281
24859
  target_branch: string5().optional(),
24282
- babysit: boolean5().optional()
24860
+ babysit: boolean5().optional(),
24861
+ cwd: string5().min(1).optional()
24283
24862
  }).strict()
24284
24863
  }).strict(),
24285
24864
  object4({
24286
24865
  operation: literal3("hosting.merge"),
24287
24866
  payload: object4({
24288
24867
  target_branch: string5().min(1).optional(),
24289
- source_branch: string5().min(1).optional()
24868
+ source_branch: string5().min(1).optional(),
24869
+ cwd: string5().min(1).optional()
24290
24870
  }).strict()
24291
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(),
24292
24876
  object4({
24293
24877
  operation: literal3("youtrack.update"),
24294
24878
  payload: object4({
@@ -24344,12 +24928,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24344
24928
  issueRef: string5().optional(),
24345
24929
  mode: string5().optional(),
24346
24930
  specPath: string5().optional(),
24347
- planPath: string5().optional()
24931
+ planPath: string5().optional(),
24932
+ cwd: string5().min(1).optional()
24348
24933
  }).strict()
24349
24934
  }).strict()
24350
24935
  ]);
24351
24936
  // packages/workit-core/src/core/task-engine.ts
24352
- import { realpathSync as realpathSync5 } from "node:fs";
24937
+ import { realpathSync as realpathSync6 } from "node:fs";
24353
24938
 
24354
24939
  // packages/workit-core/src/core/policy-resolver.ts
24355
24940
  var resolverInputSchema = object4({
@@ -24894,6 +25479,18 @@ var portableTask = (task) => {
24894
25479
  };
24895
25480
  for (const entry of clone.assessments)
24896
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
+ }
24897
25494
  clone.candidates = [];
24898
25495
  return clone;
24899
25496
  };
@@ -25019,7 +25616,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
25019
25616
  taskId: "",
25020
25617
  workspaceId: destinationWorkspaceId
25021
25618
  },
25022
- 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
25023
25624
  }
25024
25625
  };
25025
25626
  }),
@@ -25060,7 +25661,7 @@ class WorkitCore {
25060
25661
  }
25061
25662
  contextRootError() {
25062
25663
  try {
25063
- if (realpathSync5(this.context.root) !== this.store.root)
25664
+ if (realpathSync6(this.context.root) !== this.store.root)
25064
25665
  return failure("invalid_input", "operation context root does not match the task store root");
25065
25666
  } catch {
25066
25667
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -25219,7 +25820,7 @@ class WorkitCore {
25219
25820
  return parsed;
25220
25821
  const input = parsed.data;
25221
25822
  if ((this.context.workerId ?? null) !== null) {
25222
- if (input.action !== "inspect")
25823
+ if (input.action !== "inspect" && input.action !== "list")
25223
25824
  return failure("permission_denied", "helpers cannot control task lifecycle or scope");
25224
25825
  }
25225
25826
  switch (input.action) {
@@ -25246,7 +25847,18 @@ class WorkitCore {
25246
25847
  return listed;
25247
25848
  const status = input.status ?? "open";
25248
25849
  const limit = input.limit ?? 20;
25249
- const selected = listed.data.filter((task) => status === "all" ? true : status === "closed" ? task.status === "closed" : task.status !== "closed").sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
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);
25250
25862
  if (selected.length === 0)
25251
25863
  return success(null, null, []);
25252
25864
  const workspace = this.store.readWorkspace();
@@ -25265,7 +25877,11 @@ class WorkitCore {
25265
25877
  status: task.status,
25266
25878
  closure: task.closure,
25267
25879
  progress: task.progress,
25268
- writer: task.status === "closed" ? null : workspace.data.writer
25880
+ writer: task.status === "closed" ? null : workspace.data.writer,
25881
+ source: {
25882
+ host: task.intent.provenance.host,
25883
+ kind: task.intent.provenance.kind
25884
+ }
25269
25885
  }));
25270
25886
  return success(null, null, tasks);
25271
25887
  }
@@ -26361,7 +26977,7 @@ class WorkitCore {
26361
26977
  if (view.task.workspaceId !== view.workspace.id)
26362
26978
  return failure("invalid_input", "task and workspace bindings are invalid");
26363
26979
  try {
26364
- if (realpathSync5(view.workspace.root) !== this.store.root)
26980
+ if (realpathSync6(view.workspace.root) !== this.store.root)
26365
26981
  return failure("invalid_input", "task view root does not match the task store");
26366
26982
  } catch {
26367
26983
  return failure("invalid_input", "task view root cannot be resolved");
@@ -26638,22 +27254,22 @@ function resolveGitRevision(root, value) {
26638
27254
  }
26639
27255
  }
26640
27256
  function resolveInside(root, candidate) {
26641
- const base = realpathSync6(root);
26642
- const target = path10.resolve(base, candidate);
26643
- 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)) {
26644
27260
  throw new Error("path must stay inside repository root");
26645
27261
  }
26646
27262
  let ancestor = target;
26647
27263
  while (!existsSync5(ancestor))
26648
- ancestor = path10.dirname(ancestor);
26649
- const canonicalAncestor = realpathSync6(ancestor);
26650
- 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)) {
26651
27267
  throw new Error("path must stay inside repository root");
26652
27268
  }
26653
27269
  return target;
26654
27270
  }
26655
27271
  function run2(root, executable, args, env = {}) {
26656
- const cwd = realpathSync6(root);
27272
+ const cwd = realpathSync7(root);
26657
27273
  const result = spawnSync3(executable, args, {
26658
27274
  cwd,
26659
27275
  encoding: "utf8",
@@ -26726,18 +27342,19 @@ var redactSecrets = (value) => applyPatterns(value);
26726
27342
 
26727
27343
  // packages/workit-core/src/core/pr-create.ts
26728
27344
  import fs8 from "node:fs";
26729
- import path11 from "node:path";
27345
+ import path12 from "node:path";
27346
+ import { spawnSync as spawnSync4 } from "node:child_process";
26730
27347
  function parseGhIssue(value) {
26731
27348
  const m = /issues\/(\d+)/.exec(value);
26732
27349
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26733
27350
  }
26734
27351
  function whichOnPath(tool) {
26735
27352
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26736
- for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
27353
+ for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
26737
27354
  if (!dir)
26738
27355
  continue;
26739
27356
  for (const name of names) {
26740
- const candidate = path11.join(dir, name);
27357
+ const candidate = path12.join(dir, name);
26741
27358
  try {
26742
27359
  fs8.accessSync(candidate, fs8.constants.X_OK);
26743
27360
  return candidate;
@@ -26747,11 +27364,21 @@ function whichOnPath(tool) {
26747
27364
  return null;
26748
27365
  }
26749
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
+ };
26750
27377
 
26751
27378
  // packages/workit-core/src/core/repo-context.ts
26752
- import { spawnSync as spawnSync4 } from "node:child_process";
27379
+ import { spawnSync as spawnSync5 } from "node:child_process";
26753
27380
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26754
- import path12 from "node:path";
27381
+ import path13 from "node:path";
26755
27382
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26756
27383
  try {
26757
27384
  gitRevisionParts(value);
@@ -26778,7 +27405,7 @@ var invalidRange = (cwd) => ({
26778
27405
  cwd
26779
27406
  });
26780
27407
  var runGit = (cwd, args) => {
26781
- const result = spawnSync4("git", args, { cwd, encoding: "utf8" });
27408
+ const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
26782
27409
  return {
26783
27410
  stdout: (result.stdout ?? "").trimEnd(),
26784
27411
  stderr: (result.stderr ?? "").trimEnd(),
@@ -26885,7 +27512,7 @@ var findPrTemplate = (cwd) => {
26885
27512
  ]) {
26886
27513
  const parts = rel.split("/");
26887
27514
  const base = parts[parts.length - 1];
26888
- 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;
26889
27516
  const actual = probeCaseInsensitive(dir, base);
26890
27517
  if (actual)
26891
27518
  return [...parts.slice(0, -1), actual].join("/");
@@ -26916,7 +27543,7 @@ var diffStatForRange = (cwd, range) => {
26916
27543
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26917
27544
  };
26918
27545
  var packageScripts = (cwd, keys) => {
26919
- const pkgPath = path12.join(cwd, "package.json");
27546
+ const pkgPath = path13.join(cwd, "package.json");
26920
27547
  if (!existsSync6(pkgPath))
26921
27548
  return [];
26922
27549
  try {
@@ -26942,9 +27569,9 @@ var documentationFiles = (cwd) => {
26942
27569
  if (entry.isDirectory()) {
26943
27570
  if (excluded.has(entry.name))
26944
27571
  continue;
26945
- walk(path12.join(dir, entry.name), depth + 1);
27572
+ walk(path13.join(dir, entry.name), depth + 1);
26946
27573
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26947
- matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
27574
+ matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
26948
27575
  }
26949
27576
  }
26950
27577
  };
@@ -26960,7 +27587,7 @@ var readTrimmed = (file, maxLines) => {
26960
27587
  `);
26961
27588
  };
26962
27589
  function prReadyContext(root, range) {
26963
- const cwd = path12.resolve(repoRoot(root));
27590
+ const cwd = path13.resolve(repoRoot(root));
26964
27591
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26965
27592
  return invalidRange(cwd);
26966
27593
  let stdout = "";
@@ -27018,7 +27645,7 @@ ${status.stderr}` : "");
27018
27645
  stdout += `template_path: ${template}
27019
27646
 
27020
27647
  `;
27021
- stdout += readTrimmed(path12.join(cwd, template), 220);
27648
+ stdout += readTrimmed(path13.join(cwd, template), 220);
27022
27649
  } else {
27023
27650
  stdout += `template_path: none
27024
27651
 
@@ -27028,7 +27655,7 @@ ${status.stderr}` : "");
27028
27655
  stdout += `
27029
27656
  `;
27030
27657
  stdout += printSection("Recent Validation Signals");
27031
- if (existsSync6(path12.join(cwd, "package.json"))) {
27658
+ if (existsSync6(path13.join(cwd, "package.json"))) {
27032
27659
  stdout += `package.json detected. Common scripts:
27033
27660
  `;
27034
27661
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -27037,7 +27664,7 @@ ${status.stderr}` : "");
27037
27664
  `) + `
27038
27665
  `;
27039
27666
  }
27040
- 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"))) {
27041
27668
  stdout += `Rust project detected.
27042
27669
  `;
27043
27670
  }
@@ -27070,7 +27697,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
27070
27697
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
27071
27698
  - If Unreleased already has duplicate category headings, normalize_only first.`;
27072
27699
  function changelogContext(root, range) {
27073
- const cwd = path12.resolve(repoRoot(root));
27700
+ const cwd = path13.resolve(repoRoot(root));
27074
27701
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27075
27702
  return invalidRange(cwd);
27076
27703
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27088,7 +27715,7 @@ function changelogContext(root, range) {
27088
27715
  stdout += CHANGELOG_RULES + `
27089
27716
  `;
27090
27717
  stdout += printSection("Existing CHANGELOG.md");
27091
- const changelogPath = path12.join(cwd, "CHANGELOG.md");
27718
+ const changelogPath = path13.join(cwd, "CHANGELOG.md");
27092
27719
  if (existsSync6(changelogPath)) {
27093
27720
  stdout += readTrimmed(changelogPath, 260) + `
27094
27721
  `;
@@ -27108,7 +27735,7 @@ function changelogContext(root, range) {
27108
27735
  return { stdout, stderr: "", exitCode: 0, cwd };
27109
27736
  }
27110
27737
  function docsRefreshContext(root, range) {
27111
- const cwd = path12.resolve(repoRoot(root));
27738
+ const cwd = path13.resolve(repoRoot(root));
27112
27739
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27113
27740
  return invalidRange(cwd);
27114
27741
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27130,7 +27757,7 @@ function docsRefreshContext(root, range) {
27130
27757
  `) + `
27131
27758
  `;
27132
27759
  stdout += printSection("README Preview");
27133
- const readme = path12.join(cwd, "README.md");
27760
+ const readme = path13.join(cwd, "README.md");
27134
27761
  if (existsSync6(readme)) {
27135
27762
  stdout += readTrimmed(readme, 220) + `
27136
27763
  `;
@@ -27139,7 +27766,7 @@ function docsRefreshContext(root, range) {
27139
27766
  `;
27140
27767
  }
27141
27768
  stdout += printSection("Package Scripts");
27142
- const pkgPath = path12.join(cwd, "package.json");
27769
+ const pkgPath = path13.join(cwd, "package.json");
27143
27770
  if (existsSync6(pkgPath)) {
27144
27771
  try {
27145
27772
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
@@ -27153,7 +27780,7 @@ function docsRefreshContext(root, range) {
27153
27780
  return { stdout, stderr: "", exitCode: 0, cwd };
27154
27781
  }
27155
27782
  function releaseNotesContext(root, rangeOrTag) {
27156
- const cwd = path12.resolve(repoRoot(root));
27783
+ const cwd = path13.resolve(repoRoot(root));
27157
27784
  if (!rangeOrTag) {
27158
27785
  return { stdout: "", stderr: `ERROR: release tag or range required
27159
27786
  `, exitCode: 1, cwd };
@@ -27191,7 +27818,7 @@ function releaseNotesContext(root, rangeOrTag) {
27191
27818
  `;
27192
27819
  stdout += printSection("Existing Release Files");
27193
27820
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
27194
- if (existsSync6(path12.join(cwd, rel)))
27821
+ if (existsSync6(path13.join(cwd, rel)))
27195
27822
  stdout += rel + `
27196
27823
  `;
27197
27824
  }
@@ -27219,19 +27846,7 @@ function releaseNotesContext(root, rangeOrTag) {
27219
27846
  }
27220
27847
 
27221
27848
  // packages/workit-core/src/core/tracker-issues.ts
27222
- import fs9 from "node:fs";
27223
- import { spawnSync as spawnSync5 } from "node:child_process";
27224
- var trackerRequest = async (url, init) => {
27225
- try {
27226
- const res = await fetch(url, { method: init.method, headers: init.headers });
27227
- const text = await res.text();
27228
- if (!res.ok)
27229
- return { status: res.status, stdout: "", stderr: text.slice(0, 200) };
27230
- return { status: 0, stdout: text, stderr: "" };
27231
- } catch (err) {
27232
- return { status: 1, stdout: "", stderr: err instanceof Error ? err.message : "network error" };
27233
- }
27234
- };
27849
+ import { spawnSync as spawnSync6 } from "node:child_process";
27235
27850
  var repoPathFromRemote = (remote) => {
27236
27851
  let rest = (remote || "").trim().replace(/\/+$/, "");
27237
27852
  if (!rest)
@@ -27252,7 +27867,11 @@ var defaultCli = (bin, available) => {
27252
27867
  if (!available)
27253
27868
  return null;
27254
27869
  return (args, root) => {
27255
- 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
+ });
27256
27875
  return Promise.resolve({
27257
27876
  status: out.status ?? 1,
27258
27877
  stdout: out.stdout ?? "",
@@ -27260,49 +27879,22 @@ var defaultCli = (bin, available) => {
27260
27879
  });
27261
27880
  };
27262
27881
  };
27263
- var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
27882
+ var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
27264
27883
  if (!cli)
27265
- return null;
27884
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27266
27885
  const run = cli(args, root);
27267
27886
  if (!run)
27268
- return null;
27887
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27269
27888
  const out = await run;
27270
27889
  if (out.status !== 0)
27271
- return null;
27890
+ return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
27272
27891
  try {
27273
- 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" };
27274
27894
  } catch {
27275
- return null;
27895
+ return { error: `invalid ${name} issue response`, kind: "request" };
27276
27896
  }
27277
27897
  };
27278
- var originRemote = (root) => {
27279
- const out = spawnSync5("git", ["remote", "get-url", "origin"], { cwd: root, encoding: "utf8" });
27280
- return out.status === 0 ? (out.stdout ?? "").trim() || null : null;
27281
- };
27282
- var trackerCreds = (root, provider) => {
27283
- const cfg = vcsConfig("load", root);
27284
- if (!cfg || cfg.provider !== provider || typeof cfg.tokenPath !== "string" || !cfg.tokenPath)
27285
- return { error: `unconfigured ${provider} provider` };
27286
- let token = "";
27287
- try {
27288
- token = fs9.readFileSync(cfg.tokenPath, "utf8").trim();
27289
- } catch {
27290
- return { error: `missing ${provider} token` };
27291
- }
27292
- if (!token)
27293
- return { error: `empty ${provider} token file` };
27294
- if (provider === "gitlab") {
27295
- const gitlab = cfg.gitlab;
27296
- const apiUrl = String(gitlab?.apiUrl ?? "https://gitlab.com/api/v4").replace(/\/+$/, "");
27297
- return { token, api: apiUrl };
27298
- }
27299
- const github = cfg.github;
27300
- const host = String(github?.host ?? "github.com").toLowerCase();
27301
- return {
27302
- token,
27303
- api: host === "github.com" ? "https://api.github.com" : `https://${host}/api/v3`
27304
- };
27305
- };
27306
27898
  var parseTriple = (parsed, id, idKeys, bodyKeys) => {
27307
27899
  const title = parsed.title;
27308
27900
  if (typeof title !== "string")
@@ -27325,62 +27917,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
27325
27917
  const id = parseGhIssue(ref);
27326
27918
  if (!/^\d+$/.test(id))
27327
27919
  return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
27328
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27920
+ const remote = (deps.remote ?? pushRemote)(root);
27921
+ const repo = repoPathFromRemote(remote ?? "");
27329
27922
  if (!repo)
27330
27923
  return { error: "no origin remote to resolve the repository", kind: "input" };
27331
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), ["issue", "view", id, "--repo", repo, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27332
- if (viaCli)
27333
- return { data: viaCli };
27334
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "github")))(root);
27335
- if ("error" in credentials)
27336
- return { ...credentials, kind: "creds" };
27337
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/repos/${repo}/issues/${id}`, {
27338
- method: "GET",
27339
- headers: {
27340
- Authorization: `Bearer ${credentials.token}`,
27341
- Accept: "application/vnd.github+json"
27342
- }
27343
- });
27344
- if (out.status !== 0)
27345
- return { error: out.stderr || "GitHub issue fetch failed", kind: "request" };
27346
- try {
27347
- const triple = parseTriple(JSON.parse(out.stdout), id, ["number"], ["body"]);
27348
- if (!triple)
27349
- return { error: "unexpected GitHub issue shape", kind: "request" };
27350
- return { data: triple };
27351
- } catch {
27352
- return { error: "invalid GitHub issue response", kind: "request" };
27353
- }
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"]);
27354
27930
  };
27355
27931
  var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
27356
27932
  const id = parseGhIssue(ref);
27357
27933
  if (!/^\d+$/.test(id))
27358
27934
  return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
27359
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27935
+ const remote = (deps.remote ?? pushRemote)(root);
27936
+ const repo = repoPathFromRemote(remote ?? "");
27360
27937
  if (!repo)
27361
27938
  return { error: "no origin remote to resolve the project", kind: "input" };
27362
- const viaCli = await tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), ["issue", "view", id, "-R", repo, "-F", "json"], root, id, ["iid"], ["description"]);
27363
- if (viaCli)
27364
- return { data: viaCli };
27365
- const credentials = (deps.creds ?? ((r) => trackerCreds(r, "gitlab")))(root);
27366
- if ("error" in credentials)
27367
- return { ...credentials, kind: "creds" };
27368
- const out = await (deps.request ?? trackerRequest)(`${credentials.api}/projects/${encodeURIComponent(repo)}/issues/${id}`, { method: "GET", headers: { "PRIVATE-TOKEN": credentials.token } });
27369
- if (out.status !== 0)
27370
- return { error: out.stderr || "GitLab issue fetch failed", kind: "request" };
27371
- try {
27372
- const triple = parseTriple(JSON.parse(out.stdout), id, ["iid"], ["description"]);
27373
- if (!triple)
27374
- return { error: "unexpected GitLab issue shape", kind: "request" };
27375
- return { data: triple };
27376
- } catch {
27377
- return { error: "invalid GitLab issue response", kind: "request" };
27378
- }
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"]);
27379
27945
  };
27380
27946
 
27381
27947
  // packages/workit-core/src/core/youtrack.ts
27382
- import fs10 from "node:fs";
27383
- import path13 from "node:path";
27948
+ import fs9 from "node:fs";
27949
+ import path14 from "node:path";
27384
27950
 
27385
27951
  // packages/workit-core/src/core/templates.ts
27386
27952
  var repoRoot2 = assetRoot();
@@ -27392,23 +27958,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
27392
27958
 
27393
27959
  // packages/workit-core/src/core/youtrack.ts
27394
27960
  var ISSUE_RE = /^[A-Z]+-\d+$/;
27395
- var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE";
27396
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path13.join(configDir(), "youtrack.json");
27397
- 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");
27398
27964
  var youTrackTokenModeOk = (p) => {
27399
27965
  if (process.platform === "win32")
27400
27966
  return true;
27401
- const mode = fs10.statSync(p).mode & 511;
27967
+ const mode = fs9.statSync(p).mode & 511;
27402
27968
  return mode === 384;
27403
27969
  };
27404
27970
  function readYouTrackConfig(required) {
27405
27971
  const cfgPath = youTrackConfigPath();
27406
- if (!fs10.existsSync(cfgPath)) {
27972
+ if (!fs9.existsSync(cfgPath)) {
27407
27973
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
27408
27974
  }
27409
27975
  let parsed;
27410
27976
  try {
27411
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
27977
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27412
27978
  } catch {
27413
27979
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
27414
27980
  }
@@ -27419,11 +27985,11 @@ function readYouTrackConfig(required) {
27419
27985
  }
27420
27986
  var readYouTrackContextConfig = () => {
27421
27987
  const cfgPath = youTrackReadOnlyConfigPath();
27422
- if (!fs10.existsSync(cfgPath))
27988
+ if (!fs9.existsSync(cfgPath))
27423
27989
  return { data: {} };
27424
27990
  let parsed;
27425
27991
  try {
27426
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
27992
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27427
27993
  } catch {
27428
27994
  return { error: "YouTrack configuration is unavailable" };
27429
27995
  }
@@ -27448,12 +28014,12 @@ function youTrackGreeting(configOverride) {
27448
28014
  const cfgPath = configOverride ?? youTrackConfigPath();
27449
28015
  let parsed;
27450
28016
  try {
27451
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28017
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27452
28018
  } catch {
27453
28019
  return {
27454
28020
  stdout: "",
27455
28021
  exitCode: 1,
27456
- 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}`
27457
28023
  };
27458
28024
  }
27459
28025
  if (!isConfigObject(parsed)) {
@@ -27499,15 +28065,15 @@ var youTrackToken = () => {
27499
28065
  if ("error" in loaded)
27500
28066
  return loaded;
27501
28067
  const tokenFile = String(loaded.config.tokenFile ?? "");
27502
- const tokenPath = tokenFile ? path13.isAbsolute(tokenFile) ? path13.resolve(tokenFile) : path13.resolve(process.cwd(), tokenFile) : "";
27503
- 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))
27504
28070
  return { error: "missing youtrack.token" };
27505
28071
  if (!youTrackTokenModeOk(tokenPath))
27506
28072
  return { error: "youtrack.token mode must be 0600" };
27507
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
28073
+ const token = fs9.readFileSync(tokenPath, "utf8").trim();
27508
28074
  if (!token)
27509
28075
  return { error: "empty token file" };
27510
- if (token === TOKEN_PLACEHOLDER2 || token.startsWith(TOKEN_PLACEHOLDER2)) {
28076
+ if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
27511
28077
  return {
27512
28078
  error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
27513
28079
  };
@@ -27570,9 +28136,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
27570
28136
  } catch {
27571
28137
  continue;
27572
28138
  }
27573
- if (!fs10.existsSync(full))
28139
+ if (!fs9.existsSync(full))
27574
28140
  continue;
27575
- const text = fs10.readFileSync(full, "utf8");
28141
+ const text = fs9.readFileSync(full, "utf8");
27576
28142
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
27577
28143
  if (m)
27578
28144
  return m[1];
@@ -27806,6 +28372,7 @@ var readExternalContext = async (root, payload) => {
27806
28372
  }
27807
28373
  }
27808
28374
  };
28375
+ var targetWriterActionLease = Symbol("target-writer-action-lease");
27809
28376
 
27810
28377
  // packages/workit-mcp/src/server.ts
27811
28378
  class McpCapabilityUnavailableError extends Error {
@@ -27850,13 +28417,13 @@ var rootVariants = (workspaceRoot) => {
27850
28417
  if (!workspaceRoot)
27851
28418
  return [];
27852
28419
  const roots = new Set;
27853
- for (const candidate of [workspaceRoot, path14.resolve(workspaceRoot)]) {
27854
- 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)
27855
28422
  roots.add(candidate);
27856
28423
  }
27857
28424
  try {
27858
- const canonical = realpathSync7(workspaceRoot);
27859
- if (canonical !== path14.parse(canonical).root)
28425
+ const canonical = realpathSync8(workspaceRoot);
28426
+ if (canonical !== path15.parse(canonical).root)
27860
28427
  roots.add(canonical);
27861
28428
  } catch {}
27862
28429
  return [...roots].sort((left, right) => right.length - left.length);
@@ -27957,11 +28524,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
27957
28524
  };
27958
28525
  function createMcpServer(host, contextProvider) {
27959
28526
  assertMcpHost(host);
27960
- const here = path14.dirname(fileURLToPath2(import.meta.url));
28527
+ const here = path15.dirname(fileURLToPath2(import.meta.url));
27961
28528
  const sourceMarker = markSourcesLoaded([
27962
- path14.join(here, "server.ts"),
27963
- path14.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
27964
- 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")
27965
28532
  ].filter((file) => existsSync7(file)));
27966
28533
  let staleWarned = false;
27967
28534
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
@@ -28141,8 +28708,8 @@ var main = async () => {
28141
28708
  process.exitCode = 2;
28142
28709
  }
28143
28710
  };
28144
- var entryPath = process.argv[1] ? path15.resolve(process.argv[1]) : "";
28145
- 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)))
28146
28713
  await main();
28147
28714
 
28148
28715
  // packages/workit-cursor/hooks/workit-hook.ts
@@ -28209,6 +28776,7 @@ var eventNames = new Set([
28209
28776
  "subagentStop",
28210
28777
  "preCompact"
28211
28778
  ]);
28779
+ var historyOfferSessions = new Set;
28212
28780
  if (false)
28213
28781
  ;
28214
28782
  // packages/workit-cursor/mcp/run-server.ts