@brainervirus/workit-cursor 1.2.1 → 1.3.1

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({
@@ -20741,7 +20757,47 @@ function operationJsonSchema(family) {
20741
20757
  return toJSONSchema(operationSchemas[family], { target: "draft-2020-12" });
20742
20758
  }
20743
20759
  var OPERATION_SCHEMA_DEPTH = 1;
20744
- var canonicalFieldsDescription = (fields) => `Canonical object fields: ${fields.join(", ")}. Workit validates the complete nested value.`;
20760
+ var schemaShapeDescription = (value, depth = 0) => {
20761
+ if (!value || typeof value !== "object")
20762
+ return "value";
20763
+ const schema = value;
20764
+ if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
20765
+ const options = Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf;
20766
+ return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
20767
+ }
20768
+ if (schema.const !== undefined)
20769
+ return `=${JSON.stringify(schema.const)}`;
20770
+ if (Array.isArray(schema.enum))
20771
+ return `[${schema.enum.map(String).join("|")}]`;
20772
+ const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
20773
+ if (type === "array")
20774
+ return `arr<${schemaShapeDescription(schema.items, depth)}>`;
20775
+ if (type === "object" && schema.properties && typeof schema.properties === "object") {
20776
+ if (depth >= 2)
20777
+ return "obj";
20778
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
20779
+ const properties = schema.properties;
20780
+ const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`);
20781
+ return `obj{${fields.join(",")}}`;
20782
+ }
20783
+ if (typeof type !== "string")
20784
+ return "?";
20785
+ return type.split("/").map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part).join("|");
20786
+ };
20787
+ var canonicalFieldsDescription = (value) => {
20788
+ if (Array.isArray(value))
20789
+ return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
20790
+ const isZodType = (schema) => ("_zod" in schema) && typeof schema._zod === "object";
20791
+ const schema = isZodType(value) ? toJSONSchema(value, { target: "draft-2020-12" }) : value;
20792
+ if (!schema.properties || typeof schema.properties !== "object") {
20793
+ const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : null;
20794
+ return variants ? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.` : "Canonical nested value; Workit validates the complete value.";
20795
+ }
20796
+ const properties = schema.properties;
20797
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
20798
+ const fields = Object.entries(properties).map(([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`);
20799
+ return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
20800
+ };
20745
20801
  function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, stringifiedObjects = false) {
20746
20802
  const tolerate = (projected) => {
20747
20803
  if (!stringifiedObjects)
@@ -20765,9 +20821,10 @@ function boundedOperationJsonSchema(family, maxDepth = OPERATION_SCHEMA_DEPTH, s
20765
20821
  return node;
20766
20822
  const record = node;
20767
20823
  if (record.properties && typeof record.properties === "object") {
20768
- const fields = Object.keys(record.properties);
20824
+ const properties = record.properties;
20825
+ const fields = Object.keys(properties);
20769
20826
  if (currentDepth >= maxDepth || fields.length === 0) {
20770
- const description = stringifiedObjects ? `${canonicalFieldsDescription(fields)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(fields);
20827
+ const description = stringifiedObjects ? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.` : canonicalFieldsDescription(record);
20771
20828
  return tolerate({ type: "object", description });
20772
20829
  }
20773
20830
  return tolerate({
@@ -20888,6 +20945,7 @@ function requirementId(input) {
20888
20945
  }
20889
20946
  // packages/workit-core/src/core/task-store.ts
20890
20947
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
20948
+ import { AsyncLocalStorage } from "node:async_hooks";
20891
20949
  import * as fs4 from "node:fs";
20892
20950
  import { hostname as hostname2 } from "node:os";
20893
20951
  import path4 from "node:path";
@@ -21523,7 +21581,8 @@ var metadataLockSchema = object4({
21523
21581
  pid: number5().int().nonnegative().safe(),
21524
21582
  processStart: string5().nullable(),
21525
21583
  host: string5().min(1),
21526
- nonce: string5().min(1)
21584
+ nonce: string5().min(1),
21585
+ externalAction: literal3(true).optional()
21527
21586
  }).strict();
21528
21587
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21529
21588
  var cachedRuntimeVersion = null;
@@ -21575,6 +21634,28 @@ var sameMetadataLock = (left, right) => {
21575
21634
  const parsed = metadataLockSchema.safeParse(left);
21576
21635
  return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
21577
21636
  };
21637
+ var sameDirectoryIdentity = (left, right) => {
21638
+ if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
21639
+ return false;
21640
+ const normalizedLeft = path4.resolve(left);
21641
+ const normalizedRight = path4.resolve(right);
21642
+ try {
21643
+ const a = fs4.statSync(normalizedLeft, { bigint: true });
21644
+ const b = fs4.statSync(normalizedRight, { bigint: true });
21645
+ if (!a.isDirectory() || !b.isDirectory())
21646
+ return false;
21647
+ if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
21648
+ return true;
21649
+ const canonicalLeft = fs4.realpathSync(normalizedLeft);
21650
+ const canonicalRight = fs4.realpathSync(normalizedRight);
21651
+ if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
21652
+ return true;
21653
+ return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
21654
+ } catch {
21655
+ return false;
21656
+ }
21657
+ };
21658
+ var externalActionLockRoots = new AsyncLocalStorage;
21578
21659
 
21579
21660
  class TaskStore {
21580
21661
  root;
@@ -21635,7 +21716,7 @@ class TaskStore {
21635
21716
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
21636
21717
  if (!item.exists)
21637
21718
  return success(null, null, null);
21638
- if (item.result.ok && item.result.data.root !== this.root)
21719
+ if (item.result.ok && item.result.data.root !== this.root && !sameDirectoryIdentity(item.result.data.root, this.root))
21639
21720
  return failure("recovery_required", "workspace root binding is invalid", {
21640
21721
  path: this.workspacePath
21641
21722
  });
@@ -21726,6 +21807,29 @@ class TaskStore {
21726
21807
  const current = this.readWorkspace();
21727
21808
  if (!current.ok)
21728
21809
  return current;
21810
+ const origin = input.task.origin;
21811
+ if (current.data && origin) {
21812
+ if (origin.workspaceId === current.data.id) {
21813
+ const source = this.readTask(origin.taskId);
21814
+ if (source.ok)
21815
+ return success(source.data.revision, current.data.revision, source.data);
21816
+ if (source.code !== "not_found")
21817
+ return source;
21818
+ return failure("not_found", "source task is not present in this shared workspace");
21819
+ }
21820
+ const tasks = this.listTasks();
21821
+ if (!tasks.ok)
21822
+ return tasks;
21823
+ const prior = tasks.data.filter((task) => task.origin?.workspaceId === origin.workspaceId && task.origin?.taskId === origin.taskId);
21824
+ if (prior.length > 1)
21825
+ return failure("recovery_required", `source task has multiple imported mappings: ${prior.map((task) => task.id).join(", ")}`);
21826
+ if (prior.length === 1) {
21827
+ const existing = prior[0];
21828
+ if (existing.origin.exportDigest !== origin.exportDigest)
21829
+ return failure("revision_conflict", "source task export changed; reconcile the mapped destination task before importing", { taskId: existing.id });
21830
+ return success(existing.revision, current.data.revision, existing);
21831
+ }
21832
+ }
21729
21833
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
21730
21834
  return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21731
21835
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
@@ -22114,6 +22218,73 @@ class TaskStore {
22114
22218
  })
22115
22219
  };
22116
22220
  }
22221
+ externalActionLockOptions() {
22222
+ const options = this.metadataLockOptions();
22223
+ return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
22224
+ }
22225
+ async withExternalActionLock(operation, reentrant = false) {
22226
+ const activeRoots = externalActionLockRoots.getStore();
22227
+ if (activeRoots && [...activeRoots].some((root) => root === this.root || sameDirectoryIdentity(root, this.root)))
22228
+ return operation();
22229
+ try {
22230
+ this.initializeMutationStorage();
22231
+ } catch (error) {
22232
+ return failure("storage_error", `unable to initialize store: ${String(error)}`, {
22233
+ path: this.workitDir
22234
+ });
22235
+ }
22236
+ let handle;
22237
+ try {
22238
+ handle = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
22239
+ } catch (error) {
22240
+ return this.lockFailure(error);
22241
+ }
22242
+ let result;
22243
+ try {
22244
+ if (!handle.verifyStillHeld())
22245
+ result = failure("recovery_required", "metadata lock was compromised", {
22246
+ path: this.lockPath
22247
+ });
22248
+ else {
22249
+ try {
22250
+ const run = () => operation();
22251
+ result = await (reentrant ? externalActionLockRoots.run(new Set([...activeRoots ?? [], this.root]), run) : run());
22252
+ } catch {
22253
+ result = failure("external_outcome_unknown", "external action outcome is unknown", {
22254
+ outcome: "unknown"
22255
+ });
22256
+ }
22257
+ try {
22258
+ if (!handle.verifyStillHeld())
22259
+ result = failure("external_outcome_unknown", "metadata lock was compromised", {
22260
+ outcome: "unknown",
22261
+ path: this.lockPath
22262
+ });
22263
+ } catch {
22264
+ result = failure("external_outcome_unknown", "metadata lock could not be verified", {
22265
+ outcome: "unknown",
22266
+ path: this.lockPath
22267
+ });
22268
+ }
22269
+ }
22270
+ } catch {
22271
+ result = failure("external_outcome_unknown", "external action lock operation failed", {
22272
+ outcome: "unknown",
22273
+ path: this.lockPath
22274
+ });
22275
+ }
22276
+ try {
22277
+ handle.release();
22278
+ } catch (error) {
22279
+ const released = this.lockFailure(error);
22280
+ const releaseError = released.ok ? "metadata lock release failed" : released.error;
22281
+ return result.ok ? failure("external_outcome_unknown", "external action metadata lock release is uncertain", {
22282
+ outcome: "unknown",
22283
+ path: this.lockPath
22284
+ }) : failure("recovery_required", `${result.error}; ${releaseError}`, result.details);
22285
+ }
22286
+ return result;
22287
+ }
22117
22288
  recoveryLockOptions(observed, evidence, gate) {
22118
22289
  const options = this.metadataLockOptions();
22119
22290
  options.staleRecovery = "remove-if-unchanged";
@@ -22183,6 +22354,14 @@ class TaskStore {
22183
22354
  lockFailure(error) {
22184
22355
  const value = error;
22185
22356
  const code = typeof value?.code === "string" ? value.code : "";
22357
+ if (code === "EEXIST" || code === "file_lock_timeout") {
22358
+ const lock = this.readLockSnapshot();
22359
+ if (lock.ok && lock.data?.data.externalAction)
22360
+ return failure("writer_conflict", "workspace is reserved by a managed external action", {
22361
+ outcome: "not_started",
22362
+ path: this.lockPath
22363
+ });
22364
+ }
22186
22365
  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
22366
  return failure(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
22188
22367
  }
@@ -22450,21 +22629,23 @@ function assertProductWriteAllowed(input) {
22450
22629
  }
22451
22630
 
22452
22631
  // packages/workit-core/src/core/task-evaluation.ts
22453
- import { createHash as createHash5 } from "node:crypto";
22632
+ import { createHash as createHash6 } from "node:crypto";
22454
22633
  import * as fs7 from "node:fs";
22455
22634
  import { spawnSync as spawnSync2 } from "node:child_process";
22456
- import path9 from "node:path";
22635
+ import path10 from "node:path";
22457
22636
 
22458
22637
  // packages/workit-core/src/core/authority.ts
22459
- import { createHash as createHash4 } from "node:crypto";
22638
+ import { createHash as createHash5 } from "node:crypto";
22460
22639
  import * as fs6 from "node:fs";
22461
- import path8 from "node:path";
22640
+ import path9 from "node:path";
22462
22641
 
22463
22642
  // packages/workit-core/src/core/auto-approval.ts
22464
- import { createHash as createHash3 } from "node:crypto";
22465
- import { readFileSync as readFileSync4 } from "node:fs";
22643
+ import { createHash as createHash4 } from "node:crypto";
22644
+ import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
22645
+ import path8 from "node:path";
22466
22646
 
22467
22647
  // packages/workit-core/src/core/workspaces.ts
22648
+ import { createHash as createHash3 } from "node:crypto";
22468
22649
  import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
22469
22650
  import path6 from "node:path";
22470
22651
 
@@ -22482,7 +22663,7 @@ import os from "node:os";
22482
22663
  import path5 from "node:path";
22483
22664
 
22484
22665
  // packages/workit-core/src/core/boundary.ts
22485
- import { statSync } from "node:fs";
22666
+ import { statSync as statSync2 } from "node:fs";
22486
22667
  var EVENT = {
22487
22668
  initialization: "initialization",
22488
22669
  provenance: "provenance",
@@ -22510,7 +22691,7 @@ var changedSourcesSinceLoad = (marker) => {
22510
22691
  const changed = [];
22511
22692
  for (const file of marker.files) {
22512
22693
  try {
22513
- if (statSync(file).mtimeMs > marker.loadedAt)
22694
+ if (statSync2(file).mtimeMs > marker.loadedAt)
22514
22695
  changed.push(file);
22515
22696
  } catch {}
22516
22697
  }
@@ -22669,33 +22850,197 @@ var COMMIT_PRESETS = [
22669
22850
  "custom",
22670
22851
  "auto"
22671
22852
  ];
22853
+ var resolveCommitPolicy = (config, workspace) => {
22854
+ const wp = workspace?.commitPolicy ?? {};
22855
+ if (COMMIT_PRESETS.includes(String(wp.preset ?? ""))) {
22856
+ return {
22857
+ preset: wp.preset,
22858
+ ...typeof wp.pattern === "string" ? { pattern: wp.pattern } : {}
22859
+ };
22860
+ }
22861
+ return config.commitPolicy ?? { preset: "conventional" };
22862
+ };
22672
22863
 
22673
22864
  // packages/workit-core/src/core/workspaces.ts
22865
+ var nonBlank = string5().refine((value) => value.trim().length > 0, "must not be blank");
22866
+ var branchPreset = custom2((value) => typeof value === "string" && Object.hasOwn(PRESETS, value), "unsupported branch preset");
22867
+ var commitPreset = custom2((value) => typeof value === "string" && COMMIT_PRESETS.includes(value), "unsupported commit preset");
22868
+ var workspaceBranchPolicySchema = object4({
22869
+ preset: branchPreset,
22870
+ developBranch: nonBlank.optional(),
22871
+ prefixes: object4({
22872
+ feature: nonBlank,
22873
+ bugfix: nonBlank,
22874
+ release: nonBlank,
22875
+ hotfix: nonBlank
22876
+ }).passthrough().optional(),
22877
+ allowed: array3(nonBlank).optional(),
22878
+ protected: array3(nonBlank).optional(),
22879
+ integration: _enum2(["pr", "merge"]).optional()
22880
+ }).strict();
22881
+ var workspaceCommitPolicySchema = object4({ preset: commitPreset, pattern: string5().optional() }).strict();
22882
+ var workspaceProfileSchema = object4({
22883
+ branchPolicy: workspaceBranchPolicySchema.optional(),
22884
+ commitPolicy: workspaceCommitPolicySchema.optional()
22885
+ }).strict().refine((profile) => profile.branchPolicy !== undefined || profile.commitPolicy !== undefined, {
22886
+ message: "a profile must define branchPolicy or commitPolicy"
22887
+ });
22888
+ var versionSourceSchema = discriminatedUnion2("kind", [
22889
+ object4({
22890
+ kind: literal3("package-json"),
22891
+ path: nonBlank,
22892
+ field: nonBlank
22893
+ }).strict(),
22894
+ object4({ kind: literal3("git-tag") }).strict(),
22895
+ object4({ kind: literal3("manual") }).strict()
22896
+ ]);
22897
+ var releaseTrackSchema = object4({
22898
+ strategy: branchPreset,
22899
+ productionBranch: nonBlank,
22900
+ integrationBranch: nonBlank,
22901
+ naming: object4({ feature: nonBlank, release: nonBlank, hotfix: nonBlank }).strict(),
22902
+ baseBranch: nonBlank,
22903
+ mergeBackBranches: array3(nonBlank),
22904
+ pullRequestTarget: nonBlank,
22905
+ tagNamespace: string5(),
22906
+ versionSource: versionSourceSchema,
22907
+ requiredChecks: array3(nonBlank)
22908
+ }).strict().superRefine((track, context) => {
22909
+ for (const [field, values] of [
22910
+ ["mergeBackBranches", track.mergeBackBranches],
22911
+ ["requiredChecks", track.requiredChecks]
22912
+ ]) {
22913
+ if (new Set(values).size !== values.length) {
22914
+ context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
22915
+ }
22916
+ }
22917
+ 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(".."))) {
22918
+ context.addIssue({
22919
+ code: "custom",
22920
+ path: ["versionSource", "path"],
22921
+ message: "must stay inside the repository"
22922
+ });
22923
+ }
22924
+ });
22925
+ var workspaceConfigSchema = object4({
22926
+ name: nonBlank,
22927
+ glob: nonBlank,
22928
+ vcs: object4({
22929
+ provider: _enum2(["gitlab", "github"]),
22930
+ defaultTargetBranch: nonBlank.optional(),
22931
+ tokenFile: nonBlank.optional(),
22932
+ account: nonBlank.optional()
22933
+ }).passthrough().optional(),
22934
+ autoApprove: union3([boolean5(), array3(nonBlank)]).optional(),
22935
+ youtrack: object4({ baseUrl: nonBlank.optional(), link_issues: boolean5().optional() }).passthrough().optional(),
22936
+ issues: object4({ provider: literal3("github").optional(), link_on_pr: boolean5().optional() }).passthrough().optional(),
22937
+ branchPolicy: workspaceBranchPolicySchema.optional(),
22938
+ commitPolicy: workspaceCommitPolicySchema.optional(),
22939
+ defaultProfile: nonBlank.optional(),
22940
+ profiles: record3(nonBlank, workspaceProfileSchema).optional(),
22941
+ releaseTracks: record3(nonBlank, releaseTrackSchema).optional()
22942
+ }).passthrough().superRefine((workspace, context) => {
22943
+ if (workspace.youtrack && workspace.vcs?.provider !== "gitlab") {
22944
+ context.addIssue({
22945
+ code: "custom",
22946
+ path: ["youtrack"],
22947
+ message: `YouTrack issue linking requires the gitlab provider, got ${workspace.vcs?.provider ?? "unset"}`
22948
+ });
22949
+ }
22950
+ if (workspace.issues && workspace.vcs?.provider !== "github") {
22951
+ context.addIssue({
22952
+ code: "custom",
22953
+ path: ["issues"],
22954
+ message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
22955
+ });
22956
+ }
22957
+ if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
22958
+ context.addIssue({
22959
+ code: "custom",
22960
+ path: ["defaultProfile"],
22961
+ message: `does not name a configured profile`
22962
+ });
22963
+ }
22964
+ });
22965
+ var workspacesFileSchema = object4({ workspaces: array3(workspaceConfigSchema).optional() }).passthrough().superRefine((file, context) => {
22966
+ const names = new Set;
22967
+ for (const [index, workspace] of (file.workspaces ?? []).entries()) {
22968
+ if (names.has(workspace.name)) {
22969
+ context.addIssue({
22970
+ code: "custom",
22971
+ path: ["workspaces", index, "name"],
22972
+ message: `duplicate workspace name ${JSON.stringify(workspace.name)}`
22973
+ });
22974
+ }
22975
+ names.add(workspace.name);
22976
+ }
22977
+ });
22978
+ var workspacesRevision = (contents) => createHash3("sha256").update(contents ?? "<missing>").digest("hex");
22979
+ var validateWorkspacesDocument = (parsed, file) => {
22980
+ if (!isConfigObject(parsed)) {
22981
+ return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
22982
+ }
22983
+ const validated = workspacesFileSchema.safeParse(parsed, { reportInput: true });
22984
+ if (!validated.success) {
22985
+ const detail = validated.error.issues.map((issue) => {
22986
+ const field = String(issue.path[issue.path.length - 1] ?? "");
22987
+ const attempted = ["provider", "preset", "strategy"].includes(field) && typeof issue.input === "string" ? ` (${JSON.stringify(issue.input)})` : "";
22988
+ return `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}${attempted}`;
22989
+ }).join("; ");
22990
+ return {
22991
+ status: "invalid",
22992
+ path: file,
22993
+ entries: [],
22994
+ error: `${file} has invalid workspace configuration: ${detail}`
22995
+ };
22996
+ }
22997
+ for (const [index, workspace] of (validated.data.workspaces ?? []).entries()) {
22998
+ const glob = validateWorkspaceGlob(workspace.glob);
22999
+ if (!glob.ok) {
23000
+ return {
23001
+ status: "invalid",
23002
+ path: file,
23003
+ entries: [],
23004
+ error: `${file} workspaces.${index}.glob: ${glob.error}`
23005
+ };
23006
+ }
23007
+ }
23008
+ return {
23009
+ status: "valid",
23010
+ path: file,
23011
+ entries: validated.data.workspaces ?? [],
23012
+ document: parsed
23013
+ };
23014
+ };
22674
23015
  var readWorkspacesResult = (dir = configDir()) => {
22675
23016
  const file = path6.join(dir, "workspaces.json");
22676
23017
  let raw;
22677
23018
  try {
22678
23019
  raw = readFileSync3(file, "utf8");
22679
- } catch {
22680
- return { status: "missing", path: file, entries: [] };
23020
+ } catch (error) {
23021
+ if (error.code === "ENOENT")
23022
+ return { status: "missing", path: file, entries: [], revision: workspacesRevision(null) };
23023
+ return {
23024
+ status: "malformed",
23025
+ path: file,
23026
+ entries: [],
23027
+ error: `${file} could not be read: ${error instanceof Error ? error.message : String(error)}`
23028
+ };
22681
23029
  }
22682
23030
  let parsed;
22683
23031
  try {
22684
23032
  parsed = JSON.parse(raw);
22685
23033
  } 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` };
23034
+ return {
23035
+ status: "malformed",
23036
+ path: file,
23037
+ entries: [],
23038
+ revision: workspacesRevision(raw),
23039
+ error: `${file} is not valid JSON`
23040
+ };
22690
23041
  }
22691
- const list = parsed.workspaces;
22692
- return {
22693
- status: "valid",
22694
- path: file,
22695
- entries: Array.isArray(list) ? list : []
22696
- };
23042
+ return { ...validateWorkspacesDocument(parsed, file), revision: workspacesRevision(raw) };
22697
23043
  };
22698
- var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
22699
23044
  var globToRegExp = (glob) => {
22700
23045
  let out = "";
22701
23046
  for (let i = 0;i < glob.length; i++) {
@@ -22730,6 +23075,30 @@ var globToRegExp = (glob) => {
22730
23075
  return new RegExp(`^${out}$`);
22731
23076
  };
22732
23077
  var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
23078
+ var UNSUPPORTED_GLOB = /[?[\]{}]/;
23079
+ var UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
23080
+ var validateWorkspaceGlob = (glob) => {
23081
+ const trimmed = glob.trim();
23082
+ if (!trimmed)
23083
+ return { ok: false, error: "workspace pattern is required" };
23084
+ const m = UNSUPPORTED_GLOB.exec(trimmed);
23085
+ if (m) {
23086
+ return {
23087
+ ok: false,
23088
+ error: `unsupported glob character ${JSON.stringify(m[0])} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
23089
+ };
23090
+ }
23091
+ const ext = UNSUPPORTED_EXTGLOB.exec(trimmed);
23092
+ if (ext) {
23093
+ const token = ext[0];
23094
+ const kind = token === "!" ? "negation (!)" : `extglob (${token})`;
23095
+ return {
23096
+ ok: false,
23097
+ error: `unsupported glob ${kind} in workspace pattern ${JSON.stringify(glob)} — the matcher supports * and ** only (e.g. /work/**)`
23098
+ };
23099
+ }
23100
+ return { ok: true };
23101
+ };
22733
23102
  var realpathOf = (p) => {
22734
23103
  const real = realpathSync2.native ?? realpathSync2;
22735
23104
  try {
@@ -22747,29 +23116,132 @@ var canonicalGlob = (glob) => {
22747
23116
  const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
22748
23117
  return real + (prefix.endsWith("/") ? "/" : "") + rest;
22749
23118
  };
22750
- var resolveWorkspaceFrom = (cwd, dir) => {
23119
+ var globSpecificity = (glob) => glob.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && !/[*?[\]{]/.test(segment)).length;
23120
+ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
23121
+ if (workspaceName !== undefined) {
23122
+ const named = matches.filter((entry) => entry.name === workspaceName);
23123
+ if (named.length === 1)
23124
+ return named[0];
23125
+ if (named.length > 1)
23126
+ throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
23127
+ if (matches.length > 0) {
23128
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
23129
+ }
23130
+ throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
23131
+ }
23132
+ if (matches.length < 2)
23133
+ return matches[0];
23134
+ const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
23135
+ const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
23136
+ if (mostSpecific.length > 1) {
23137
+ throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
23138
+ }
23139
+ return mostSpecific[0];
23140
+ };
23141
+ var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
23142
+ const result = readWorkspacesResult(dir);
23143
+ if (result.status === "malformed" || result.status === "invalid")
23144
+ throw new Error(result.error);
22751
23145
  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;
23146
+ const matches = [];
23147
+ for (const ws of result.entries) {
22758
23148
  const glob = ws.glob.replaceAll("\\", "/");
22759
23149
  const canonical = canonicalGlob(glob);
23150
+ let matched = false;
22760
23151
  for (const target of targets) {
22761
- if (matchWorkspace(glob, target))
22762
- return ws;
22763
- if (canonical !== glob && matchWorkspace(canonical, target))
22764
- return ws;
23152
+ if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
23153
+ matched = true;
23154
+ break;
23155
+ }
22765
23156
  }
23157
+ if (matched)
23158
+ matches.push(ws);
22766
23159
  }
22767
- return null;
23160
+ return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
23161
+ };
23162
+ var runtimeWorkspacePolicySchema = (kind) => {
23163
+ const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
23164
+ const policySchema = kind === "branch" ? workspaceBranchPolicySchema : kind === "commit" ? workspaceCommitPolicySchema : object4({
23165
+ provider: _enum2(["gitlab", "github"]),
23166
+ defaultTargetBranch: nonBlank.optional(),
23167
+ account: nonBlank.optional()
23168
+ }).passthrough();
23169
+ const profileSchema = object4({ [key]: policySchema.optional() }).passthrough();
23170
+ const entrySchema = object4({
23171
+ name: nonBlank,
23172
+ glob: nonBlank,
23173
+ [key]: policySchema.optional(),
23174
+ defaultProfile: nonBlank.optional(),
23175
+ profiles: record3(nonBlank, profileSchema).optional()
23176
+ }).passthrough();
23177
+ return entrySchema;
23178
+ };
23179
+ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
23180
+ var runtimeWorkspaceIndexSchema = object4({
23181
+ workspaces: array3(object4({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
23182
+ }).passthrough();
23183
+ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
23184
+ const file = path6.join(configDir(), "workspaces.json");
23185
+ let raw;
23186
+ try {
23187
+ raw = readFileSync3(file, "utf8");
23188
+ } catch (error) {
23189
+ if (error.code === "ENOENT")
23190
+ return null;
23191
+ throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`);
23192
+ }
23193
+ let parsed;
23194
+ try {
23195
+ parsed = JSON.parse(raw);
23196
+ } catch {
23197
+ throw new Error(`${file} is not valid JSON`);
23198
+ }
23199
+ const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
23200
+ if (!validated.success) {
23201
+ const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
23202
+ throw new Error(`${file} has invalid workspace matching data: ${detail}`);
23203
+ }
23204
+ const entries = validated.data.workspaces;
23205
+ for (const entry of entries ?? []) {
23206
+ const glob = validateWorkspaceGlob(entry.glob);
23207
+ if (!glob.ok)
23208
+ throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
23209
+ }
23210
+ const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
23211
+ const matches = (entries ?? []).filter((entry) => {
23212
+ const glob = entry.glob.replaceAll("\\", "/");
23213
+ const canonical = canonicalGlob(glob);
23214
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
23215
+ });
23216
+ const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
23217
+ const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
23218
+ if (!selected)
23219
+ return null;
23220
+ const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
23221
+ if (!policy.success) {
23222
+ const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
23223
+ throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
23224
+ }
23225
+ return policy.data;
23226
+ };
23227
+ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
23228
+ const candidate = resolveRuntimeWorkspaceCandidate(cwd, kind);
23229
+ if (!candidate)
23230
+ return { policy: undefined, source: "user-default" };
23231
+ const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
23232
+ const profileName = requestedProfile?.trim() || candidate.defaultProfile;
23233
+ if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
23234
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
23235
+ const profile = profileName ? candidate.profiles?.[profileName] : undefined;
23236
+ if (profileName && !profile)
23237
+ throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
23238
+ const profilePolicy = profile?.[key];
23239
+ const workspacePolicy = candidate[key];
23240
+ return {
23241
+ policy: profilePolicy ?? workspacePolicy,
23242
+ source: profilePolicy ? `profile:${profileName}` : workspacePolicy ? `workspace:${candidate.name}` : "user-default"
23243
+ };
22768
23244
  };
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
23245
 
22774
23246
  // packages/workit-core/src/core/git.ts
22775
23247
  import { execFileSync } from "node:child_process";
@@ -22843,7 +23315,6 @@ var gitContext = (workspaceRoot, paths = []) => {
22843
23315
  import fs5 from "node:fs";
22844
23316
  import path7 from "node:path";
22845
23317
  import { spawnSync } from "node:child_process";
22846
- var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
22847
23318
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
22848
23319
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
22849
23320
  var remoteProvider = (cwd) => {
@@ -22857,6 +23328,79 @@ var remoteProvider = (cwd) => {
22857
23328
  return "gitlab";
22858
23329
  return null;
22859
23330
  };
23331
+ var remoteEndpoint = (remote) => {
23332
+ const raw = remote.trim();
23333
+ if (!raw)
23334
+ return null;
23335
+ const scp = /^(?:[^@\s]+@)?([^:]+):(.+)$/u.exec(raw);
23336
+ if (scp && !raw.includes("://"))
23337
+ return {
23338
+ host: scp[1].toLowerCase(),
23339
+ path: scp[2].replace(/^\//u, "").replace(/\.git$/u, ""),
23340
+ protocol: "ssh:",
23341
+ port: ""
23342
+ };
23343
+ try {
23344
+ const url = new URL(raw);
23345
+ return {
23346
+ host: url.hostname.toLowerCase(),
23347
+ path: url.pathname.replace(/^\//u, "").replace(/\.git$/u, "").replace(/\/$/u, ""),
23348
+ protocol: url.protocol,
23349
+ port: url.port
23350
+ };
23351
+ } catch {
23352
+ return null;
23353
+ }
23354
+ };
23355
+ var configuredSshEndpoint = (host) => {
23356
+ if (host.startsWith("-"))
23357
+ return null;
23358
+ const resolved = spawnSync("ssh", ["-G", host], {
23359
+ encoding: "utf8",
23360
+ timeout: 5000,
23361
+ maxBuffer: 1024 * 1024,
23362
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
23363
+ });
23364
+ if (resolved.status !== 0)
23365
+ return null;
23366
+ const output = resolved.stdout ?? "";
23367
+ const hostname = /^hostname\s+(\S+)/imu.exec(output)?.[1]?.toLowerCase();
23368
+ if (!hostname)
23369
+ return null;
23370
+ return { host: hostname, port: /^port\s+(\d+)/imu.exec(output)?.[1] ?? "22" };
23371
+ };
23372
+ var sshApiHostAliases = {
23373
+ "github.com": [{ host: "ssh.github.com", port: "443" }],
23374
+ "gitlab.com": [{ host: "altssh.gitlab.com", port: "443" }]
23375
+ };
23376
+ var hostingApiHostMatches = (remote, apiHost, resolveSshEndpoint = configuredSshEndpoint) => {
23377
+ const endpoint = remoteEndpoint(remote);
23378
+ if (!endpoint)
23379
+ return false;
23380
+ let expected;
23381
+ try {
23382
+ const url = new URL(`https://${apiHost}`);
23383
+ if (url.pathname !== "/" || url.search || url.hash || url.username || url.password)
23384
+ return false;
23385
+ expected = {
23386
+ host: url.hostname.toLowerCase().replace(/\.$/u, ""),
23387
+ port: url.port
23388
+ };
23389
+ } catch {
23390
+ return false;
23391
+ }
23392
+ if (endpoint.protocol !== "ssh:") {
23393
+ return endpoint.host.replace(/\.$/u, "") === expected.host && endpoint.port === expected.port;
23394
+ }
23395
+ const resolved = resolveSshEndpoint(endpoint.host);
23396
+ if (!resolved)
23397
+ return false;
23398
+ const hostname = resolved.host.toLowerCase().replace(/\.$/u, "");
23399
+ const port = endpoint.port || resolved.port;
23400
+ if (hostname === expected.host)
23401
+ return port === (expected.port || "22");
23402
+ return Boolean(sshApiHostAliases[expected.host]?.some((alias) => alias.host === hostname && alias.port === port && (!expected.port || expected.port === port)));
23403
+ };
22860
23404
  var readVcsConfig = () => {
22861
23405
  const file = vcsConfigPath();
22862
23406
  let raw;
@@ -22877,7 +23421,7 @@ var readVcsConfig = () => {
22877
23421
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22878
23422
  };
22879
23423
  function vcsConfig(mode, cwd) {
22880
- const ws = resolveWorkspace(vcsCwd(cwd));
23424
+ const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
22881
23425
  const wsVcs = ws?.vcs ?? {};
22882
23426
  const wsYt = ws?.youtrack ?? {};
22883
23427
  const wsIssues = ws?.issues ?? {};
@@ -22890,8 +23434,8 @@ function vcsConfig(mode, cwd) {
22890
23434
  const hasWorkspacePolicy = typeof wp.preset === "string" && Object.hasOwn(PRESETS, wp.preset);
22891
23435
  const policyDefault = resolveBranchPolicyFor(root).defaultTargetBranch;
22892
23436
  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;
23437
+ const linkIssues = provider === "gitlab" && typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
23438
+ const youtrackBaseUrl = provider === "gitlab" && typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
22895
23439
  let issuesProvider = null;
22896
23440
  let linkOnPr = null;
22897
23441
  if (provider === "github" && typeof wsIssues.provider === "string" && wsIssues.provider.toLowerCase() === "github") {
@@ -22915,9 +23459,8 @@ function vcsConfig(mode, cwd) {
22915
23459
  if (cfgStatus === "malformed") {
22916
23460
  return { ok: false, error: cfgError, configPath: cfgPath };
22917
23461
  }
22918
- if (cfgStatus === "missing") {
23462
+ if (cfgStatus === "missing" && provider === null)
22919
23463
  return { ok: false, error: `vcs.json is missing: ${cfgPath}`, configPath: cfgPath };
22920
- }
22921
23464
  if (provider === null) {
22922
23465
  return {
22923
23466
  ok: false,
@@ -22926,24 +23469,12 @@ function vcsConfig(mode, cwd) {
22926
23469
  };
22927
23470
  }
22928
23471
  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
23472
  const out = {
22939
23473
  ok: true,
22940
23474
  configPath: path7.resolve(cfgPath),
22941
23475
  provider,
22942
23476
  defaultTargetBranch: defaultTarget,
22943
23477
  pr: cfg.pr ?? {},
22944
- tokenPath,
22945
- tokenPresent: fs5.existsSync(tokenPath),
22946
- tokenReady: tokenOk,
22947
23478
  workspace_name: ws?.name ?? null,
22948
23479
  link_issues: linkIssues,
22949
23480
  youtrack_base_url: youtrackBaseUrl,
@@ -22958,16 +23489,58 @@ function vcsConfig(mode, cwd) {
22958
23489
  } else if (provider === "github") {
22959
23490
  out.github = { host: prov.host ?? "github.com" };
22960
23491
  }
22961
- if (mode === "summary")
22962
- delete out.tokenPath;
22963
23492
  return out;
22964
23493
  }
23494
+ function vcsCliIdentity(cwd) {
23495
+ const root = vcsCwd(cwd);
23496
+ const cfg = vcsConfig("load", root);
23497
+ if (!cfg.ok)
23498
+ return { ok: false, error: cfg.error ?? "vcs config not ready" };
23499
+ const provider = cfg.provider;
23500
+ if (provider !== "gitlab" && provider !== "github")
23501
+ return { ok: false, error: `unsupported provider: ${provider}` };
23502
+ const bin = provider === "gitlab" ? "glab" : "gh";
23503
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
23504
+ const env = {
23505
+ ...process.env,
23506
+ PATH: process.env.PATH ?? "",
23507
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
23508
+ };
23509
+ const result = spawnSync(bin, ["api", "user"], {
23510
+ cwd: root,
23511
+ encoding: "utf8",
23512
+ env
23513
+ });
23514
+ if (result.status !== 0)
23515
+ return { ok: false, provider, error: `${bin} is missing or not authenticated` };
23516
+ try {
23517
+ const user = JSON.parse(result.stdout ?? "");
23518
+ const username = provider === "gitlab" ? user.username : user.login;
23519
+ if (typeof username !== "string" || !username)
23520
+ return { ok: false, provider, error: `invalid ${bin} identity response` };
23521
+ const expected = resolveRuntimeWorkspaceVcs(root)?.vcs?.account;
23522
+ if (expected && username.toLowerCase() !== expected.toLowerCase())
23523
+ return {
23524
+ ok: false,
23525
+ provider,
23526
+ error: `${bin} account ${username} does not match ${expected}`
23527
+ };
23528
+ return { ok: true, provider, host, username, name: user.name };
23529
+ } catch {
23530
+ return { ok: false, provider, error: `invalid JSON from ${bin} api user` };
23531
+ }
23532
+ }
22965
23533
  function mergedPrStyle(limit = 6, cwd) {
22966
23534
  const cfg = vcsConfig("load", cwd);
22967
- if (!cfg.ok || !cfg.tokenReady)
23535
+ if (!cfg.ok)
22968
23536
  return { ok: false, error: "vcs not configured" };
22969
23537
  const provider = cfg.provider;
22970
- const token = fs5.readFileSync(cfg.tokenPath, "utf8").trim();
23538
+ const host = String(provider === "github" ? cfg.github?.host ?? "github.com" : cfg.gitlab?.host ?? "gitlab.com").toLowerCase();
23539
+ const env = {
23540
+ ...process.env,
23541
+ PATH: process.env.PATH ?? "",
23542
+ ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
23543
+ };
22971
23544
  const examples = [];
22972
23545
  const descInfo = (desc, caseInsensitiveNotes = false) => ({
22973
23546
  hasNotesSection: caseInsensitiveNotes ? /##\s*notes/i.test(desc) : /##\s*Notes/.test(desc),
@@ -22975,23 +23548,30 @@ function mergedPrStyle(limit = 6, cwd) {
22975
23548
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
22976
23549
  descriptionPreview: desc.slice(0, 600)
22977
23550
  });
23551
+ const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
23552
+ cwd,
23553
+ encoding: "utf8"
23554
+ });
23555
+ const remotes = (remoteResult.stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
23556
+ if (remotes.length === 0)
23557
+ return { ok: false, error: "no origin remote" };
23558
+ if (remotes.length !== 1)
23559
+ return { ok: false, error: "origin has ambiguous push destinations" };
23560
+ const remote = remotes[0];
23561
+ const endpoint = remoteEndpoint(remote);
23562
+ if (!endpoint?.path)
23563
+ return { ok: false, error: "hosting repository could not be resolved" };
23564
+ if (!hostingApiHostMatches(remote, host))
23565
+ return { ok: false, error: "PR style target does not match the provider host" };
22978
23566
  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 });
23567
+ const run = (args) => spawnSync("glab", ["api", ...args], {
23568
+ cwd,
23569
+ encoding: "utf8",
23570
+ env
23571
+ });
22989
23572
  let r = run([
22990
- `projects/${project.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
23573
+ `projects/${endpoint.path.replaceAll("/", "%2F")}/merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`
22991
23574
  ]);
22992
- if (r.status !== 0) {
22993
- r = run([`merge_requests?state=merged&per_page=${limit}&order_by=updated_at&sort=desc`]);
22994
- }
22995
23575
  if (r.status !== 0)
22996
23576
  return { ok: false, error: "could not list merge requests" };
22997
23577
  for (const mr of JSON.parse(r.stdout ?? "[]")) {
@@ -23004,7 +23584,18 @@ function mergedPrStyle(limit = 6, cwd) {
23004
23584
  });
23005
23585
  }
23006
23586
  } 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 } });
23587
+ const r = spawnSync("gh", [
23588
+ "pr",
23589
+ "list",
23590
+ "--repo",
23591
+ `${host}/${endpoint.path}`,
23592
+ "--state",
23593
+ "merged",
23594
+ "--limit",
23595
+ String(limit),
23596
+ "--json",
23597
+ "title,url,body"
23598
+ ], { cwd, encoding: "utf8", env });
23008
23599
  if (r.status !== 0)
23009
23600
  return { ok: false, error: "could not list pull requests" };
23010
23601
  for (const pr of JSON.parse(r.stdout ?? "[]")) {
@@ -23026,26 +23617,28 @@ function mergedPrStyle(limit = 6, cwd) {
23026
23617
  }
23027
23618
 
23028
23619
  // packages/workit-core/src/core/branch.ts
23029
- var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
23620
+ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
23621
+ const selected = resolveRuntimeWorkspacePolicy(workspaceRoot, kind, profileName?.trim() || process.env.WORKFLOW_PROFILE?.trim() || undefined);
23622
+ const config = readConfig();
23623
+ return kind === "branch" ? {
23624
+ branchPolicy: resolveBranchPolicy(config, selected.policy ? { branchPolicy: selected.policy } : null),
23625
+ provenance: { branchPolicy: selected.source }
23626
+ } : {
23627
+ commitPolicy: resolveCommitPolicy(config, selected.policy ? { commitPolicy: selected.policy } : null),
23628
+ provenance: { commitPolicy: selected.source }
23629
+ };
23630
+ }
23631
+ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
23030
23632
  var verifyPushIdentity = (root, provider, account) => {
23031
23633
  if (!account || typeof account !== "string")
23032
23634
  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())
23635
+ const identity = vcsCliIdentity(root);
23636
+ if (!identity.ok || identity.provider !== provider)
23637
+ return { ok: false, error: identity.error ?? "hosting CLI identity could not be resolved" };
23638
+ if (identity.username?.toLowerCase() !== account.toLowerCase())
23046
23639
  return {
23047
23640
  ok: false,
23048
- error: `push identity ${login || "(unknown)"} does not match area account ${account}`
23641
+ error: `push identity ${identity.username || "(unknown)"} does not match area account ${account}`
23049
23642
  };
23050
23643
  return { ok: true };
23051
23644
  };
@@ -23063,7 +23656,7 @@ var operationAutoClass = (operation) => typeof operation === "string" ? OPERATIO
23063
23656
  var digestOf = (configDirPath) => {
23064
23657
  try {
23065
23658
  const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
23066
- return createHash3("sha256").update(raw).digest("hex");
23659
+ return createHash4("sha256").update(raw).digest("hex");
23067
23660
  } catch {
23068
23661
  return null;
23069
23662
  }
@@ -23101,14 +23694,30 @@ var resolveAutoApproval = (root, dir = configDir()) => {
23101
23694
  };
23102
23695
  };
23103
23696
  var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
23104
- var standingApprovalLive = (root, receipt, cls, dir) => {
23697
+ var targetOf = (root, descriptor) => {
23698
+ try {
23699
+ const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
23700
+ const cwd = parsed?.payload?.cwd;
23701
+ if (cwd === undefined)
23702
+ return root;
23703
+ if (typeof cwd !== "string" || !path8.isAbsolute(cwd))
23704
+ return null;
23705
+ return realpathSync3(cwd);
23706
+ } catch {
23707
+ return null;
23708
+ }
23709
+ };
23710
+ var standingApprovalLive = (root, receipt, cls, dir, descriptor) => {
23105
23711
  if (!isStandingReceipt(receipt))
23106
23712
  return false;
23107
23713
  if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
23108
23714
  return false;
23109
23715
  if (receipt.class !== cls)
23110
23716
  return false;
23111
- const resolved = resolveAutoApproval(root, dir);
23717
+ const target = targetOf(root, descriptor);
23718
+ if (!target)
23719
+ return false;
23720
+ const resolved = resolveAutoApproval(target, dir);
23112
23721
  if (resolved.status !== "on")
23113
23722
  return false;
23114
23723
  if (resolved.workspace !== receipt.workspace)
@@ -23144,16 +23753,19 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23144
23753
  const cls = operationAutoClass(operation);
23145
23754
  if (!cls || cls !== standing.class)
23146
23755
  return failure("permission_denied", "standing approval does not cover this operation");
23147
- const resolved = resolveAutoApproval(root);
23756
+ const target = targetOf(root, binding.approvedContent);
23757
+ if (!target)
23758
+ return failure("permission_denied", "standing action target is invalid");
23759
+ const resolved = resolveAutoApproval(target);
23148
23760
  if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
23149
23761
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23150
23762
  if (cls === "push") {
23151
- const workspace = resolveWorkspaceFrom(root, configDir());
23763
+ const workspace = resolveWorkspaceFrom(target, configDir());
23152
23764
  const provider = workspace?.vcs?.provider;
23153
23765
  const account = resolved.account;
23154
23766
  if (!provider || !account)
23155
23767
  return failure("permission_denied", "auto-push requires a workspace provider and area account");
23156
- const identity = verifyPushIdentity(root, provider, account);
23768
+ const identity = verifyPushIdentity(target, provider, account);
23157
23769
  if (!identity.ok)
23158
23770
  return failure("permission_denied", identity.error);
23159
23771
  }
@@ -23162,7 +23774,7 @@ var verifyStandingApproval = (root, task, caller, binding) => {
23162
23774
  host: caller.host,
23163
23775
  session: { kind: "host", host: caller.host, handle: caller.actor },
23164
23776
  workerId: null,
23165
- receipts: [standingReceiptFor(root, standing.workspace, cls)]
23777
+ receipts: [standingReceiptFor(target, standing.workspace, cls)]
23166
23778
  });
23167
23779
  };
23168
23780
 
@@ -23313,7 +23925,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
23313
23925
  return [];
23314
23926
  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
23927
  }
23316
- var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
23928
+ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
23317
23929
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23318
23930
  let root;
23319
23931
  try {
@@ -23326,23 +23938,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
23326
23938
  continue;
23327
23939
  if (!reference.digest)
23328
23940
  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}`))
23941
+ const target = path9.resolve(root, reference.path);
23942
+ if (target !== root && !target.startsWith(`${root}${path9.sep}`))
23331
23943
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23332
23944
  fields: [
23333
23945
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
23334
23946
  ]
23335
23947
  });
23336
23948
  try {
23337
- const relative = path8.relative(root, target);
23949
+ const relative = path9.relative(root, target);
23338
23950
  let current = root;
23339
- for (const segment of relative.split(path8.sep)) {
23951
+ for (const segment of relative.split(path9.sep)) {
23340
23952
  if (!segment)
23341
23953
  continue;
23342
- current = path8.join(current, segment);
23954
+ current = path9.join(current, segment);
23343
23955
  const stat = fs6.lstatSync(current);
23344
23956
  const real = fs6.realpathSync(current);
23345
- if (real !== root && !real.startsWith(`${root}${path8.sep}`))
23957
+ if (real !== root && !real.startsWith(`${root}${path9.sep}`))
23346
23958
  return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
23347
23959
  fields: [
23348
23960
  {
@@ -23420,7 +24032,7 @@ var validateAction = (store, task, workspaceId, input, authority) => {
23420
24032
  operation = null;
23421
24033
  }
23422
24034
  const cls = operationAutoClass(operation);
23423
- if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
24035
+ if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls, undefined, decision.binding.approvedContent)))
23424
24036
  return failure("permission_denied", "standing auto-approval is not live for this operation");
23425
24037
  }
23426
24038
  if (decision.revoked)
@@ -23722,15 +24334,15 @@ var bindingCovers = scopeCovers;
23722
24334
  var verifyDecisionContent = verifyContentRefs;
23723
24335
 
23724
24336
  // 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("/") || ".";
24337
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
24338
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
24339
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
23728
24340
  var canonicalPath = (value) => {
23729
24341
  if (typeof value !== "string" || !value)
23730
24342
  return null;
23731
24343
  if (value.includes("\\") || value.split("/").includes(".."))
23732
24344
  return null;
23733
- const normalized = path9.posix.normalize(value);
24345
+ const normalized = path10.posix.normalize(value);
23734
24346
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
23735
24347
  return null;
23736
24348
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -23778,14 +24390,14 @@ var gitPaths = (root) => {
23778
24390
  git: /not a git repository/i.test(stderr) ? false : true
23779
24391
  };
23780
24392
  }
23781
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")) : [];
24393
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
23782
24394
  const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
23783
24395
  if (stagedDeleted.status !== 0) {
23784
24396
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
23785
24397
  if (!/not a git repository/i.test(stderr))
23786
24398
  return { paths, uncertain: true, git: true };
23787
24399
  } else if (stagedDeleted.stdout) {
23788
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path9.sep).join("/")));
24400
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
23789
24401
  }
23790
24402
  return { paths: [...new Set(paths)], uncertain: false, git: true };
23791
24403
  };
@@ -23800,7 +24412,7 @@ var walk = (root, directory, output) => {
23800
24412
  for (const name of names) {
23801
24413
  if (name === ".git" || name === ".workit")
23802
24414
  continue;
23803
- const target = path9.join(directory, name);
24415
+ const target = path10.join(directory, name);
23804
24416
  let stat;
23805
24417
  try {
23806
24418
  stat = fs7.lstatSync(target);
@@ -23818,12 +24430,12 @@ var walk = (root, directory, output) => {
23818
24430
  var scopeRoots = (root, scope) => {
23819
24431
  const roots = [];
23820
24432
  for (const item of scope.paths.length ? scope.paths : ["."]) {
23821
- const target = path9.resolve(root, item);
24433
+ const target = path10.resolve(root, item);
23822
24434
  if (!inside(root, target))
23823
24435
  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
24436
  let ancestor = target;
23825
24437
  while (!fs7.existsSync(ancestor) && ancestor !== root)
23826
- ancestor = path9.dirname(ancestor);
24438
+ ancestor = path10.dirname(ancestor);
23827
24439
  try {
23828
24440
  if (!inside(root, fs7.realpathSync(ancestor)))
23829
24441
  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 +24487,7 @@ function captureCandidate(root, scope, environment = []) {
23875
24487
  for (const item of [...names].sort()) {
23876
24488
  if (!scopeMatches(item, normalizedScope))
23877
24489
  continue;
23878
- const target = path9.join(checkout, item);
24490
+ const target = path10.join(checkout, item);
23879
24491
  try {
23880
24492
  const stat = fs7.lstatSync(target);
23881
24493
  if (stat.isSymbolicLink()) {
@@ -24257,7 +24869,8 @@ var externalActionSchema = discriminatedUnion2("operation", [
24257
24869
  action: _enum2(["setup", "reapply_stash"]).optional(),
24258
24870
  sdd_dir: string5().optional(),
24259
24871
  target_branch: string5().optional(),
24260
- stash: _enum2(["yes", "no"]).optional()
24872
+ stash: _enum2(["yes", "no"]).optional(),
24873
+ cwd: string5().min(1).optional()
24261
24874
  }).strict()
24262
24875
  }).strict(),
24263
24876
  object4({
@@ -24265,12 +24878,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24265
24878
  payload: object4({
24266
24879
  message: string5().min(1).optional(),
24267
24880
  plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
24268
- plan_branch: string5().min(1).optional()
24881
+ plan_branch: string5().min(1).optional(),
24882
+ cwd: string5().min(1).optional()
24269
24883
  }).strict()
24270
24884
  }).strict(),
24271
24885
  object4({
24272
24886
  operation: literal3("git.push"),
24273
- payload: object4({ branch: string5().min(1).optional() }).strict()
24887
+ payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
24274
24888
  }).strict(),
24275
24889
  object4({
24276
24890
  operation: literal3("hosting.pull_request"),
@@ -24279,16 +24893,22 @@ var externalActionSchema = discriminatedUnion2("operation", [
24279
24893
  body: string5().optional(),
24280
24894
  draft: boolean5().optional(),
24281
24895
  target_branch: string5().optional(),
24282
- babysit: boolean5().optional()
24896
+ babysit: boolean5().optional(),
24897
+ cwd: string5().min(1).optional()
24283
24898
  }).strict()
24284
24899
  }).strict(),
24285
24900
  object4({
24286
24901
  operation: literal3("hosting.merge"),
24287
24902
  payload: object4({
24288
24903
  target_branch: string5().min(1).optional(),
24289
- source_branch: string5().min(1).optional()
24904
+ source_branch: string5().min(1).optional(),
24905
+ cwd: string5().min(1).optional()
24290
24906
  }).strict()
24291
24907
  }).strict(),
24908
+ object4({
24909
+ operation: literal3("hosting.delete_branch"),
24910
+ payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
24911
+ }).strict(),
24292
24912
  object4({
24293
24913
  operation: literal3("youtrack.update"),
24294
24914
  payload: object4({
@@ -24344,12 +24964,13 @@ var externalActionSchema = discriminatedUnion2("operation", [
24344
24964
  issueRef: string5().optional(),
24345
24965
  mode: string5().optional(),
24346
24966
  specPath: string5().optional(),
24347
- planPath: string5().optional()
24967
+ planPath: string5().optional(),
24968
+ cwd: string5().min(1).optional()
24348
24969
  }).strict()
24349
24970
  }).strict()
24350
24971
  ]);
24351
24972
  // packages/workit-core/src/core/task-engine.ts
24352
- import { realpathSync as realpathSync5 } from "node:fs";
24973
+ import { realpathSync as realpathSync6 } from "node:fs";
24353
24974
 
24354
24975
  // packages/workit-core/src/core/policy-resolver.ts
24355
24976
  var resolverInputSchema = object4({
@@ -24522,6 +25143,7 @@ function resolveRequirements(input) {
24522
25143
  dependentAction: null,
24523
25144
  acceptanceAllowed: false
24524
25145
  }));
25146
+ const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
24525
25147
  if (signals.behaviorChange.value === true)
24526
25148
  requirements.push(requirement({
24527
25149
  ruleId: "behavioral-verification",
@@ -24533,14 +25155,14 @@ function resolveRequirements(input) {
24533
25155
  dependentAction: null,
24534
25156
  acceptanceAllowed: false
24535
25157
  }), requirement({
24536
- ruleId: "fresh-context-review",
25158
+ ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
24537
25159
  dimension: "review",
24538
25160
  scope,
24539
- reason: "A non-mechanical behavior change requires fresh-context review.",
24540
- satisfaction: "A separate review context examines the current candidate and records its result.",
25161
+ reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
25162
+ satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
24541
25163
  before: "close",
24542
25164
  dependentAction: null,
24543
- acceptanceAllowed: false
25165
+ acceptanceAllowed: !independentBehaviorReview
24544
25166
  }));
24545
25167
  else if (signals.mechanicalLowRisk.value === true)
24546
25168
  requirements.push(requirement({
@@ -24894,6 +25516,18 @@ var portableTask = (task) => {
24894
25516
  };
24895
25517
  for (const entry of clone.assessments)
24896
25518
  demote(entry.data.facts, entry.data.signals, entry.data.consequences);
25519
+ const sourceDecisions = new Map(task.decisions.map((entry) => [entry.id, entry]));
25520
+ for (const entry of clone.decisions) {
25521
+ const consumption = sourceDecisions.get(entry.id)?.data.consumption;
25522
+ if (!consumption)
25523
+ continue;
25524
+ entry.data.consumption = {
25525
+ ...consumption,
25526
+ state: consumption.state === "reserved" ? "uncertain" : consumption.state,
25527
+ actionRef: { kind: "record", collection: "decisions", id: entry.id },
25528
+ ...consumption.historicalActionId ? { historicalActionId: consumption.historicalActionId } : consumption.actionRef.kind === "host" ? { historicalActionId: consumption.actionRef.handle } : {}
25529
+ };
25530
+ }
24897
25531
  clone.candidates = [];
24898
25532
  return clone;
24899
25533
  };
@@ -25019,7 +25653,11 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
25019
25653
  taskId: "",
25020
25654
  workspaceId: destinationWorkspaceId
25021
25655
  },
25022
- consumption: null
25656
+ consumption: data.consumption ? {
25657
+ ...data.consumption,
25658
+ state: data.consumption.state === "reserved" ? "uncertain" : data.consumption.state,
25659
+ actionRef: { kind: "record", collection: "decisions", id: entry.id }
25660
+ } : null
25023
25661
  }
25024
25662
  };
25025
25663
  }),
@@ -25060,7 +25698,7 @@ class WorkitCore {
25060
25698
  }
25061
25699
  contextRootError() {
25062
25700
  try {
25063
- if (realpathSync5(this.context.root) !== this.store.root)
25701
+ if (realpathSync6(this.context.root) !== this.store.root)
25064
25702
  return failure("invalid_input", "operation context root does not match the task store root");
25065
25703
  } catch {
25066
25704
  return failure("invalid_input", "operation context root cannot be resolved");
@@ -25219,7 +25857,7 @@ class WorkitCore {
25219
25857
  return parsed;
25220
25858
  const input = parsed.data;
25221
25859
  if ((this.context.workerId ?? null) !== null) {
25222
- if (input.action !== "inspect")
25860
+ if (input.action !== "inspect" && input.action !== "list")
25223
25861
  return failure("permission_denied", "helpers cannot control task lifecycle or scope");
25224
25862
  }
25225
25863
  switch (input.action) {
@@ -25246,7 +25884,18 @@ class WorkitCore {
25246
25884
  return listed;
25247
25885
  const status = input.status ?? "open";
25248
25886
  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);
25887
+ const query = input.query?.toLowerCase().split(/\s+/);
25888
+ 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({
25889
+ id: task.id,
25890
+ workspaceId: task.workspaceId,
25891
+ origin: task.origin,
25892
+ createdAt: task.createdAt,
25893
+ updatedAt: task.updatedAt,
25894
+ source: task.intent.provenance,
25895
+ objective: task.intent.data.objective,
25896
+ progress: task.progress,
25897
+ decisions: task.decisions.map(({ data }) => data)
25898
+ }).toLowerCase().includes(term))).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, limit);
25250
25899
  if (selected.length === 0)
25251
25900
  return success(null, null, []);
25252
25901
  const workspace = this.store.readWorkspace();
@@ -25265,7 +25914,11 @@ class WorkitCore {
25265
25914
  status: task.status,
25266
25915
  closure: task.closure,
25267
25916
  progress: task.progress,
25268
- writer: task.status === "closed" ? null : workspace.data.writer
25917
+ writer: task.status === "closed" ? null : workspace.data.writer,
25918
+ source: {
25919
+ host: task.intent.provenance.host,
25920
+ kind: task.intent.provenance.kind
25921
+ }
25269
25922
  }));
25270
25923
  return success(null, null, tasks);
25271
25924
  }
@@ -26361,7 +27014,7 @@ class WorkitCore {
26361
27014
  if (view.task.workspaceId !== view.workspace.id)
26362
27015
  return failure("invalid_input", "task and workspace bindings are invalid");
26363
27016
  try {
26364
- if (realpathSync5(view.workspace.root) !== this.store.root)
27017
+ if (realpathSync6(view.workspace.root) !== this.store.root)
26365
27018
  return failure("invalid_input", "task view root does not match the task store");
26366
27019
  } catch {
26367
27020
  return failure("invalid_input", "task view root cannot be resolved");
@@ -26638,22 +27291,22 @@ function resolveGitRevision(root, value) {
26638
27291
  }
26639
27292
  }
26640
27293
  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)) {
27294
+ const base = realpathSync7(root);
27295
+ const target = path11.resolve(base, candidate);
27296
+ if (target !== base && !target.startsWith(base + path11.sep)) {
26644
27297
  throw new Error("path must stay inside repository root");
26645
27298
  }
26646
27299
  let ancestor = target;
26647
27300
  while (!existsSync5(ancestor))
26648
- ancestor = path10.dirname(ancestor);
26649
- const canonicalAncestor = realpathSync6(ancestor);
26650
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path10.sep)) {
27301
+ ancestor = path11.dirname(ancestor);
27302
+ const canonicalAncestor = realpathSync7(ancestor);
27303
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
26651
27304
  throw new Error("path must stay inside repository root");
26652
27305
  }
26653
27306
  return target;
26654
27307
  }
26655
27308
  function run2(root, executable, args, env = {}) {
26656
- const cwd = realpathSync6(root);
27309
+ const cwd = realpathSync7(root);
26657
27310
  const result = spawnSync3(executable, args, {
26658
27311
  cwd,
26659
27312
  encoding: "utf8",
@@ -26726,18 +27379,19 @@ var redactSecrets = (value) => applyPatterns(value);
26726
27379
 
26727
27380
  // packages/workit-core/src/core/pr-create.ts
26728
27381
  import fs8 from "node:fs";
26729
- import path11 from "node:path";
27382
+ import path12 from "node:path";
27383
+ import { spawnSync as spawnSync4 } from "node:child_process";
26730
27384
  function parseGhIssue(value) {
26731
27385
  const m = /issues\/(\d+)/.exec(value);
26732
27386
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26733
27387
  }
26734
27388
  function whichOnPath(tool) {
26735
27389
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26736
- for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
27390
+ for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
26737
27391
  if (!dir)
26738
27392
  continue;
26739
27393
  for (const name of names) {
26740
- const candidate = path11.join(dir, name);
27394
+ const candidate = path12.join(dir, name);
26741
27395
  try {
26742
27396
  fs8.accessSync(candidate, fs8.constants.X_OK);
26743
27397
  return candidate;
@@ -26747,11 +27401,21 @@ function whichOnPath(tool) {
26747
27401
  return null;
26748
27402
  }
26749
27403
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
27404
+ var pushRemote = (cwd) => {
27405
+ const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
27406
+ cwd,
27407
+ encoding: "utf8"
27408
+ });
27409
+ if (result.status !== 0)
27410
+ return null;
27411
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
27412
+ return urls.length === 1 ? urls[0] : null;
27413
+ };
26750
27414
 
26751
27415
  // packages/workit-core/src/core/repo-context.ts
26752
- import { spawnSync as spawnSync4 } from "node:child_process";
27416
+ import { spawnSync as spawnSync5 } from "node:child_process";
26753
27417
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
26754
- import path12 from "node:path";
27418
+ import path13 from "node:path";
26755
27419
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26756
27420
  try {
26757
27421
  gitRevisionParts(value);
@@ -26778,7 +27442,7 @@ var invalidRange = (cwd) => ({
26778
27442
  cwd
26779
27443
  });
26780
27444
  var runGit = (cwd, args) => {
26781
- const result = spawnSync4("git", args, { cwd, encoding: "utf8" });
27445
+ const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
26782
27446
  return {
26783
27447
  stdout: (result.stdout ?? "").trimEnd(),
26784
27448
  stderr: (result.stderr ?? "").trimEnd(),
@@ -26885,7 +27549,7 @@ var findPrTemplate = (cwd) => {
26885
27549
  ]) {
26886
27550
  const parts = rel.split("/");
26887
27551
  const base = parts[parts.length - 1];
26888
- const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
27552
+ const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
26889
27553
  const actual = probeCaseInsensitive(dir, base);
26890
27554
  if (actual)
26891
27555
  return [...parts.slice(0, -1), actual].join("/");
@@ -26916,7 +27580,7 @@ var diffStatForRange = (cwd, range) => {
26916
27580
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26917
27581
  };
26918
27582
  var packageScripts = (cwd, keys) => {
26919
- const pkgPath = path12.join(cwd, "package.json");
27583
+ const pkgPath = path13.join(cwd, "package.json");
26920
27584
  if (!existsSync6(pkgPath))
26921
27585
  return [];
26922
27586
  try {
@@ -26942,9 +27606,9 @@ var documentationFiles = (cwd) => {
26942
27606
  if (entry.isDirectory()) {
26943
27607
  if (excluded.has(entry.name))
26944
27608
  continue;
26945
- walk(path12.join(dir, entry.name), depth + 1);
27609
+ walk(path13.join(dir, entry.name), depth + 1);
26946
27610
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26947
- matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
27611
+ matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
26948
27612
  }
26949
27613
  }
26950
27614
  };
@@ -26960,7 +27624,7 @@ var readTrimmed = (file, maxLines) => {
26960
27624
  `);
26961
27625
  };
26962
27626
  function prReadyContext(root, range) {
26963
- const cwd = path12.resolve(repoRoot(root));
27627
+ const cwd = path13.resolve(repoRoot(root));
26964
27628
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26965
27629
  return invalidRange(cwd);
26966
27630
  let stdout = "";
@@ -27018,7 +27682,7 @@ ${status.stderr}` : "");
27018
27682
  stdout += `template_path: ${template}
27019
27683
 
27020
27684
  `;
27021
- stdout += readTrimmed(path12.join(cwd, template), 220);
27685
+ stdout += readTrimmed(path13.join(cwd, template), 220);
27022
27686
  } else {
27023
27687
  stdout += `template_path: none
27024
27688
 
@@ -27028,7 +27692,7 @@ ${status.stderr}` : "");
27028
27692
  stdout += `
27029
27693
  `;
27030
27694
  stdout += printSection("Recent Validation Signals");
27031
- if (existsSync6(path12.join(cwd, "package.json"))) {
27695
+ if (existsSync6(path13.join(cwd, "package.json"))) {
27032
27696
  stdout += `package.json detected. Common scripts:
27033
27697
  `;
27034
27698
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -27037,7 +27701,7 @@ ${status.stderr}` : "");
27037
27701
  `) + `
27038
27702
  `;
27039
27703
  }
27040
- if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
27704
+ if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
27041
27705
  stdout += `Rust project detected.
27042
27706
  `;
27043
27707
  }
@@ -27070,7 +27734,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
27070
27734
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
27071
27735
  - If Unreleased already has duplicate category headings, normalize_only first.`;
27072
27736
  function changelogContext(root, range) {
27073
- const cwd = path12.resolve(repoRoot(root));
27737
+ const cwd = path13.resolve(repoRoot(root));
27074
27738
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27075
27739
  return invalidRange(cwd);
27076
27740
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27088,7 +27752,7 @@ function changelogContext(root, range) {
27088
27752
  stdout += CHANGELOG_RULES + `
27089
27753
  `;
27090
27754
  stdout += printSection("Existing CHANGELOG.md");
27091
- const changelogPath = path12.join(cwd, "CHANGELOG.md");
27755
+ const changelogPath = path13.join(cwd, "CHANGELOG.md");
27092
27756
  if (existsSync6(changelogPath)) {
27093
27757
  stdout += readTrimmed(changelogPath, 260) + `
27094
27758
  `;
@@ -27108,7 +27772,7 @@ function changelogContext(root, range) {
27108
27772
  return { stdout, stderr: "", exitCode: 0, cwd };
27109
27773
  }
27110
27774
  function docsRefreshContext(root, range) {
27111
- const cwd = path12.resolve(repoRoot(root));
27775
+ const cwd = path13.resolve(repoRoot(root));
27112
27776
  if (range !== undefined && !isResolvableContextRange(cwd, range))
27113
27777
  return invalidRange(cwd);
27114
27778
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -27130,7 +27794,7 @@ function docsRefreshContext(root, range) {
27130
27794
  `) + `
27131
27795
  `;
27132
27796
  stdout += printSection("README Preview");
27133
- const readme = path12.join(cwd, "README.md");
27797
+ const readme = path13.join(cwd, "README.md");
27134
27798
  if (existsSync6(readme)) {
27135
27799
  stdout += readTrimmed(readme, 220) + `
27136
27800
  `;
@@ -27139,7 +27803,7 @@ function docsRefreshContext(root, range) {
27139
27803
  `;
27140
27804
  }
27141
27805
  stdout += printSection("Package Scripts");
27142
- const pkgPath = path12.join(cwd, "package.json");
27806
+ const pkgPath = path13.join(cwd, "package.json");
27143
27807
  if (existsSync6(pkgPath)) {
27144
27808
  try {
27145
27809
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
@@ -27153,7 +27817,7 @@ function docsRefreshContext(root, range) {
27153
27817
  return { stdout, stderr: "", exitCode: 0, cwd };
27154
27818
  }
27155
27819
  function releaseNotesContext(root, rangeOrTag) {
27156
- const cwd = path12.resolve(repoRoot(root));
27820
+ const cwd = path13.resolve(repoRoot(root));
27157
27821
  if (!rangeOrTag) {
27158
27822
  return { stdout: "", stderr: `ERROR: release tag or range required
27159
27823
  `, exitCode: 1, cwd };
@@ -27191,7 +27855,7 @@ function releaseNotesContext(root, rangeOrTag) {
27191
27855
  `;
27192
27856
  stdout += printSection("Existing Release Files");
27193
27857
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
27194
- if (existsSync6(path12.join(cwd, rel)))
27858
+ if (existsSync6(path13.join(cwd, rel)))
27195
27859
  stdout += rel + `
27196
27860
  `;
27197
27861
  }
@@ -27219,19 +27883,7 @@ function releaseNotesContext(root, rangeOrTag) {
27219
27883
  }
27220
27884
 
27221
27885
  // 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
- };
27886
+ import { spawnSync as spawnSync6 } from "node:child_process";
27235
27887
  var repoPathFromRemote = (remote) => {
27236
27888
  let rest = (remote || "").trim().replace(/\/+$/, "");
27237
27889
  if (!rest)
@@ -27252,7 +27904,11 @@ var defaultCli = (bin, available) => {
27252
27904
  if (!available)
27253
27905
  return null;
27254
27906
  return (args, root) => {
27255
- const out = spawnSync5(bin, args, { cwd: root, encoding: "utf8" });
27907
+ const out = spawnSync6(bin, args, {
27908
+ cwd: root,
27909
+ encoding: "utf8",
27910
+ env: { ...process.env, PATH: process.env.PATH ?? "" }
27911
+ });
27256
27912
  return Promise.resolve({
27257
27913
  status: out.status ?? 1,
27258
27914
  stdout: out.stdout ?? "",
@@ -27260,49 +27916,22 @@ var defaultCli = (bin, available) => {
27260
27916
  });
27261
27917
  };
27262
27918
  };
27263
- var tryCliTriple = async (cli, args, root, id, idKeys, bodyKeys) => {
27919
+ var tryCliTriple = async (cli, name, args, root, id, idKeys, bodyKeys) => {
27264
27920
  if (!cli)
27265
- return null;
27921
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27266
27922
  const run = cli(args, root);
27267
27923
  if (!run)
27268
- return null;
27924
+ return { error: `${name} CLI is unavailable`, kind: "creds" };
27269
27925
  const out = await run;
27270
27926
  if (out.status !== 0)
27271
- return null;
27927
+ return { error: `${name} CLI is not authenticated or the issue is unavailable`, kind: "creds" };
27272
27928
  try {
27273
- return parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27929
+ const triple = parseTriple(JSON.parse(out.stdout), id, idKeys, bodyKeys);
27930
+ return triple ? { data: triple } : { error: `unexpected ${name} issue response`, kind: "request" };
27274
27931
  } catch {
27275
- return null;
27932
+ return { error: `invalid ${name} issue response`, kind: "request" };
27276
27933
  }
27277
27934
  };
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
27935
  var parseTriple = (parsed, id, idKeys, bodyKeys) => {
27307
27936
  const title = parsed.title;
27308
27937
  if (typeof title !== "string")
@@ -27325,62 +27954,36 @@ var fetchGitHubIssueBody = async (ref, root, deps = {}) => {
27325
27954
  const id = parseGhIssue(ref);
27326
27955
  if (!/^\d+$/.test(id))
27327
27956
  return { error: `invalid GitHub issue ref "${ref}"`, kind: "input" };
27328
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27957
+ const remote = (deps.remote ?? pushRemote)(root);
27958
+ const repo = repoPathFromRemote(remote ?? "");
27329
27959
  if (!repo)
27330
27960
  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
- }
27961
+ const cfg = vcsConfig("load", root);
27962
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "github" ? cfg.github?.host : null) ?? "github.com").toLowerCase();
27963
+ if (!remote || !hostingApiHostMatches(remote, host))
27964
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27965
+ const repoArg = host === "github.com" ? repo : `${host}/${repo}`;
27966
+ return tryCliTriple(deps.cli ?? defaultCli("gh", hostingCliAvailable("github")), "gh", ["issue", "view", id, "--repo", repoArg, "--json", "number,title,body,state"], root, id, ["number"], ["body"]);
27354
27967
  };
27355
27968
  var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
27356
27969
  const id = parseGhIssue(ref);
27357
27970
  if (!/^\d+$/.test(id))
27358
27971
  return { error: `invalid GitLab issue ref "${ref}"`, kind: "input" };
27359
- const repo = repoPathFromRemote((deps.remote ?? originRemote)(root) ?? "");
27972
+ const remote = (deps.remote ?? pushRemote)(root);
27973
+ const repo = repoPathFromRemote(remote ?? "");
27360
27974
  if (!repo)
27361
27975
  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
- }
27976
+ const cfg = vcsConfig("load", root);
27977
+ const host = (deps.host ?? (cfg.ok && cfg.provider === "gitlab" ? cfg.gitlab?.host : null) ?? "gitlab.com").toLowerCase();
27978
+ if (!remote || !hostingApiHostMatches(remote, host))
27979
+ return { error: "hosting API host does not match the push destination", kind: "input" };
27980
+ const repoArg = host === "gitlab.com" ? repo : `https://${host}/${repo}`;
27981
+ return tryCliTriple(deps.cli ?? defaultCli("glab", hostingCliAvailable("gitlab")), "glab", ["issue", "view", id, "-R", repoArg, "-F", "json"], root, id, ["iid"], ["description"]);
27379
27982
  };
27380
27983
 
27381
27984
  // packages/workit-core/src/core/youtrack.ts
27382
- import fs10 from "node:fs";
27383
- import path13 from "node:path";
27985
+ import fs9 from "node:fs";
27986
+ import path14 from "node:path";
27384
27987
 
27385
27988
  // packages/workit-core/src/core/templates.ts
27386
27989
  var repoRoot2 = assetRoot();
@@ -27392,23 +27995,23 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
27392
27995
 
27393
27996
  // packages/workit-core/src/core/youtrack.ts
27394
27997
  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");
27998
+ var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
27999
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
28000
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
27398
28001
  var youTrackTokenModeOk = (p) => {
27399
28002
  if (process.platform === "win32")
27400
28003
  return true;
27401
- const mode = fs10.statSync(p).mode & 511;
28004
+ const mode = fs9.statSync(p).mode & 511;
27402
28005
  return mode === 384;
27403
28006
  };
27404
28007
  function readYouTrackConfig(required) {
27405
28008
  const cfgPath = youTrackConfigPath();
27406
- if (!fs10.existsSync(cfgPath)) {
28009
+ if (!fs9.existsSync(cfgPath)) {
27407
28010
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
27408
28011
  }
27409
28012
  let parsed;
27410
28013
  try {
27411
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28014
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27412
28015
  } catch {
27413
28016
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
27414
28017
  }
@@ -27419,11 +28022,11 @@ function readYouTrackConfig(required) {
27419
28022
  }
27420
28023
  var readYouTrackContextConfig = () => {
27421
28024
  const cfgPath = youTrackReadOnlyConfigPath();
27422
- if (!fs10.existsSync(cfgPath))
28025
+ if (!fs9.existsSync(cfgPath))
27423
28026
  return { data: {} };
27424
28027
  let parsed;
27425
28028
  try {
27426
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28029
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27427
28030
  } catch {
27428
28031
  return { error: "YouTrack configuration is unavailable" };
27429
28032
  }
@@ -27448,12 +28051,12 @@ function youTrackGreeting(configOverride) {
27448
28051
  const cfgPath = configOverride ?? youTrackConfigPath();
27449
28052
  let parsed;
27450
28053
  try {
27451
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
28054
+ parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
27452
28055
  } catch {
27453
28056
  return {
27454
28057
  stdout: "",
27455
28058
  exitCode: 1,
27456
- stderr: fs10.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
28059
+ stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
27457
28060
  };
27458
28061
  }
27459
28062
  if (!isConfigObject(parsed)) {
@@ -27499,15 +28102,15 @@ var youTrackToken = () => {
27499
28102
  if ("error" in loaded)
27500
28103
  return loaded;
27501
28104
  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))
28105
+ const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
28106
+ if (!tokenPath || !fs9.existsSync(tokenPath))
27504
28107
  return { error: "missing youtrack.token" };
27505
28108
  if (!youTrackTokenModeOk(tokenPath))
27506
28109
  return { error: "youtrack.token mode must be 0600" };
27507
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
28110
+ const token = fs9.readFileSync(tokenPath, "utf8").trim();
27508
28111
  if (!token)
27509
28112
  return { error: "empty token file" };
27510
- if (token === TOKEN_PLACEHOLDER2 || token.startsWith(TOKEN_PLACEHOLDER2)) {
28113
+ if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
27511
28114
  return {
27512
28115
  error: "token file still has placeholder YOUR_TOKEN_HERE — edit the file locally, then run /wk-status"
27513
28116
  };
@@ -27570,9 +28173,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
27570
28173
  } catch {
27571
28174
  continue;
27572
28175
  }
27573
- if (!fs10.existsSync(full))
28176
+ if (!fs9.existsSync(full))
27574
28177
  continue;
27575
- const text = fs10.readFileSync(full, "utf8");
28178
+ const text = fs9.readFileSync(full, "utf8");
27576
28179
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
27577
28180
  if (m)
27578
28181
  return m[1];
@@ -27806,6 +28409,7 @@ var readExternalContext = async (root, payload) => {
27806
28409
  }
27807
28410
  }
27808
28411
  };
28412
+ var targetWriterActionLease = Symbol("target-writer-action-lease");
27809
28413
 
27810
28414
  // packages/workit-mcp/src/server.ts
27811
28415
  class McpCapabilityUnavailableError extends Error {
@@ -27850,13 +28454,13 @@ var rootVariants = (workspaceRoot) => {
27850
28454
  if (!workspaceRoot)
27851
28455
  return [];
27852
28456
  const roots = new Set;
27853
- for (const candidate of [workspaceRoot, path14.resolve(workspaceRoot)]) {
27854
- if (path14.isAbsolute(candidate) && candidate !== path14.parse(candidate).root)
28457
+ for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
28458
+ if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
27855
28459
  roots.add(candidate);
27856
28460
  }
27857
28461
  try {
27858
- const canonical = realpathSync7(workspaceRoot);
27859
- if (canonical !== path14.parse(canonical).root)
28462
+ const canonical = realpathSync8(workspaceRoot);
28463
+ if (canonical !== path15.parse(canonical).root)
27860
28464
  roots.add(canonical);
27861
28465
  } catch {}
27862
28466
  return [...roots].sort((left, right) => right.length - left.length);
@@ -27957,11 +28561,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
27957
28561
  };
27958
28562
  function createMcpServer(host, contextProvider) {
27959
28563
  assertMcpHost(host);
27960
- const here = path14.dirname(fileURLToPath2(import.meta.url));
28564
+ const here = path15.dirname(fileURLToPath2(import.meta.url));
27961
28565
  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")
28566
+ path15.join(here, "server.ts"),
28567
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
28568
+ path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
27965
28569
  ].filter((file) => existsSync7(file)));
27966
28570
  let staleWarned = false;
27967
28571
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
@@ -28141,8 +28745,8 @@ var main = async () => {
28141
28745
  process.exitCode = 2;
28142
28746
  }
28143
28747
  };
28144
- var entryPath = process.argv[1] ? path15.resolve(process.argv[1]) : "";
28145
- if (entryPath === path15.resolve(fileURLToPath3(import.meta.url)))
28748
+ var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
28749
+ if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
28146
28750
  await main();
28147
28751
 
28148
28752
  // packages/workit-cursor/hooks/workit-hook.ts
@@ -28209,6 +28813,7 @@ var eventNames = new Set([
28209
28813
  "subagentStop",
28210
28814
  "preCompact"
28211
28815
  ]);
28816
+ var historyOfferSessions = new Set;
28212
28817
  if (false)
28213
28818
  ;
28214
28819
  // packages/workit-cursor/mcp/run-server.ts