@brainervirus/workit-cursor 8.14.0 → 8.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "name": "workit",
3
3
  "displayName": "Workit",
4
4
  "description": "Adaptive Workit workflow with shared MCP tools and native Cursor hooks",
5
- "version": "8.14.0",
5
+ "version": "8.15.0",
6
6
  "author": {
7
7
  "name": "Cristhofer Pincetti"
8
8
  },
@@ -7624,6 +7624,16 @@ var init_zod = __esm(() => {
7624
7624
 
7625
7625
  // packages/workit-core/src/core/task-contract.ts
7626
7626
  import { createHash, randomUUID } from "node:crypto";
7627
+ function resolveIdRef(kind, ref, ids) {
7628
+ if (ids.includes(ref))
7629
+ return success(null, null, ref);
7630
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
7631
+ if (matches.length === 1)
7632
+ return success(null, null, matches[0]);
7633
+ if (matches.length === 0)
7634
+ return failure2("not_found", `${kind} not found`);
7635
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
7636
+ }
7627
7637
  function parseOperation(family, input) {
7628
7638
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
7629
7639
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -7726,7 +7736,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
7726
7736
  return true;
7727
7737
  }
7728
7738
  return false;
7729
- }, text, id, digest, revision, validUtc = (value) => {
7739
+ }, text, id, idRefPattern, idRef, digest, revision, validUtc = (value) => {
7730
7740
  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?Z$/.exec(value);
7731
7741
  if (!match)
7732
7742
  return false;
@@ -7909,6 +7919,8 @@ var init_task_contract = __esm(() => {
7909
7919
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7910
7920
  });
7911
7921
  id = text.regex(uuidPattern, "expected lowercase UUID");
7922
+ idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7923
+ idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7912
7924
  digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7913
7925
  revision = text.regex(uuidPattern, "expected UUID revision");
7914
7926
  utcSchema = text.check((ctx) => {
@@ -8177,7 +8189,7 @@ var init_task_contract = __esm(() => {
8177
8189
  outcome: _enum(["completed", "failed", "cancelled"]),
8178
8190
  summary: text,
8179
8191
  evidenceIds: array(id),
8180
- findingIds: array(id)
8192
+ findingIds: array(idRef)
8181
8193
  }).strict();
8182
8194
  workerSchema = object({
8183
8195
  assignment: assignmentSchema,
@@ -8369,7 +8381,7 @@ var init_task_contract = __esm(() => {
8369
8381
  action: literal("resolve"),
8370
8382
  ...taskId,
8371
8383
  expectedRevision: revision.optional(),
8372
- findingId: id,
8384
+ findingId: idRef,
8373
8385
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8374
8386
  reason: text,
8375
8387
  evidenceIds: array(id),
@@ -8410,7 +8422,7 @@ var init_task_contract = __esm(() => {
8410
8422
  ...taskId,
8411
8423
  expectedRevision: revision.optional(),
8412
8424
  expectedWorkspaceRevision: revision.optional(),
8413
- workerId: id,
8425
+ workerId: idRef,
8414
8426
  report: workerReportSchema
8415
8427
  }),
8416
8428
  cancel: operation({
@@ -8418,7 +8430,7 @@ var init_task_contract = __esm(() => {
8418
8430
  ...taskId,
8419
8431
  ...revisions,
8420
8432
  expectedWorkspaceRevision: revision.optional(),
8421
- workerId: id,
8433
+ workerId: idRef,
8422
8434
  reason: text
8423
8435
  })
8424
8436
  };
@@ -9707,20 +9719,19 @@ var init_workspaces = __esm(() => {
9707
9719
  union([boolean2(), literal("verified")]).optional()
9708
9720
  ]))).passthrough().optional(),
9709
9721
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
9710
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
9722
+ issues: object({
9723
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
9724
+ link: boolean2().optional(),
9725
+ relation: unknown().optional(),
9726
+ backlink: boolean2().optional(),
9727
+ link_on_pr: boolean2().optional()
9728
+ }).passthrough().optional(),
9711
9729
  branchPolicy: workspaceBranchPolicySchema.optional(),
9712
9730
  commitPolicy: workspaceCommitPolicySchema.optional(),
9713
9731
  defaultProfile: nonBlank.optional(),
9714
9732
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
9715
9733
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
9716
9734
  }).passthrough().superRefine((workspace, context) => {
9717
- if (workspace.issues && workspace.vcs?.provider !== "github") {
9718
- context.addIssue({
9719
- code: "custom",
9720
- path: ["issues"],
9721
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
9722
- });
9723
- }
9724
9735
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
9725
9736
  context.addIssue({
9726
9737
  code: "custom",
@@ -10157,6 +10168,40 @@ var init_release_tracks = __esm(() => {
10157
10168
  import fs4 from "node:fs";
10158
10169
  import path5 from "node:path";
10159
10170
  import { spawnSync as spawnSync7 } from "node:child_process";
10171
+ function issueTrackerOf(issues, youtrack) {
10172
+ const iss = asRecord(issues);
10173
+ const yt = asRecord(youtrack);
10174
+ const warnings = [];
10175
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
10176
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
10177
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
10178
+ for (const key of ["repo", "host"])
10179
+ if (iss[key] !== undefined && text2(iss[key]) === null)
10180
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
10181
+ const common = {
10182
+ relation: iss.relation === "refs" ? "refs" : "closes",
10183
+ backlink: iss.backlink === true,
10184
+ baseUrl: text2(yt.baseUrl),
10185
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
10186
+ host: text2(iss.host)?.toLowerCase() ?? null,
10187
+ warnings
10188
+ };
10189
+ const legacyGithub = named === "github" && flag(iss.link) === null;
10190
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
10191
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
10192
+ if (named) {
10193
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
10194
+ return {
10195
+ provider: named,
10196
+ link,
10197
+ ...common,
10198
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
10199
+ };
10200
+ }
10201
+ if (common.baseUrl)
10202
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
10203
+ return null;
10204
+ }
10160
10205
  function vcsConfig(mode, cwd, options = {}) {
10161
10206
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
10162
10207
  const wsVcs = ws?.vcs ?? {};
@@ -10168,6 +10213,7 @@ function vcsConfig(mode, cwd, options = {}) {
10168
10213
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
10169
10214
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
10170
10215
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
10216
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
10171
10217
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
10172
10218
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
10173
10219
  let issuesProvider = null;
@@ -10200,7 +10246,8 @@ function vcsConfig(mode, cwd, options = {}) {
10200
10246
  link_issues: linkIssues,
10201
10247
  youtrack_base_url: youtrackBaseUrl,
10202
10248
  issues_provider: issuesProvider,
10203
- link_on_pr: linkOnPr
10249
+ link_on_pr: linkOnPr,
10250
+ issue_tracker: issueTracker
10204
10251
  };
10205
10252
  }
10206
10253
  if (cfgStatus === "malformed") {
@@ -10226,7 +10273,8 @@ function vcsConfig(mode, cwd, options = {}) {
10226
10273
  link_issues: linkIssues,
10227
10274
  youtrack_base_url: youtrackBaseUrl,
10228
10275
  issues_provider: issuesProvider,
10229
- link_on_pr: linkOnPr
10276
+ link_on_pr: linkOnPr,
10277
+ issue_tracker: issueTracker
10230
10278
  };
10231
10279
  if (provider === "gitlab") {
10232
10280
  out.gitlab = {
@@ -10266,7 +10314,7 @@ var workspacesPath = () => path5.join(configDir(), "workspaces.json"), vcsCwd =
10266
10314
  return { status: "valid", path: file, config: parsed };
10267
10315
  }
10268
10316
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
10269
- }, trackSummary = (resolution) => resolution.status === "resolved" ? {
10317
+ }, TRACKERS, asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {}, flag = (value) => typeof value === "boolean" ? value : null, text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null, trackSummary = (resolution) => resolution.status === "resolved" ? {
10270
10318
  name: resolution.track?.name ?? null,
10271
10319
  source: resolution.source,
10272
10320
  detail: resolution.detail,
@@ -10279,6 +10327,7 @@ var init_vcs_config = __esm(() => {
10279
10327
  init_config();
10280
10328
  init_workspaces();
10281
10329
  init_release_tracks();
10330
+ TRACKERS = new Set(["github", "gitlab", "youtrack"]);
10282
10331
  });
10283
10332
 
10284
10333
  // packages/workit-core/src/core/logger.ts
@@ -10399,7 +10448,7 @@ function migrateRepoIssued(cwd) {
10399
10448
  removeIfEmpty(old);
10400
10449
  }
10401
10450
  function recordIssuedSession(cwd, lead, issued) {
10402
- const token = randomBytes(18).toString("base64url");
10451
+ const token = randomBytes(18).toString("hex");
10403
10452
  try {
10404
10453
  migrateRepoIssued(cwd);
10405
10454
  const own = hash(issued);
@@ -11233,7 +11282,12 @@ var init_ledger = __esm(() => {
11233
11282
  PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
11234
11283
  FAILING = new Set(["failed", "blocked"]);
11235
11284
  STRONG_RESULTS = new Set(["verified", "tests-verified"]);
11236
- PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
11285
+ PR_ROW_TYPES = new Set([
11286
+ "pr.created",
11287
+ "pr.status",
11288
+ "pr.merged",
11289
+ "pr.linked"
11290
+ ]);
11237
11291
  NETWORK_FS_TYPES = new Set([
11238
11292
  26985,
11239
11293
  20859,
@@ -11948,12 +12002,12 @@ function normalizeJudgment(raw, previous) {
11948
12002
  const items = Array.isArray(value) ? value : [value];
11949
12003
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
11950
12004
  } else {
11951
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
12005
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
11952
12006
  judgment.needsPlan = true;
11953
12007
  judgment.refs.push(refOf(value.trim()));
11954
12008
  continue;
11955
12009
  }
11956
- const parsed = flag(value, name === "behaviorChange");
12010
+ const parsed = flag2(value, name === "behaviorChange");
11957
12011
  if (parsed.ok)
11958
12012
  judgment[name] = parsed.value;
11959
12013
  else
@@ -11977,7 +12031,7 @@ function normalizeJudgment(raw, previous) {
11977
12031
  });
11978
12032
  return success(null, null, { judgment: parsed.data, ignored, legacy });
11979
12033
  }
11980
- var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), squash = (key) => key.toLowerCase().replace(/[_-]/g, ""), ALIASES, LEGACY_KEYS, RISK, YES, NO, flag = (value, unknownAs) => {
12034
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), squash = (key) => key.toLowerCase().replace(/[_-]/g, ""), ALIASES, LEGACY_KEYS, RISK, YES, NO, flag2 = (value, unknownAs) => {
11981
12035
  if (typeof value === "boolean")
11982
12036
  return { ok: true, value };
11983
12037
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -13462,20 +13516,26 @@ class TaskStore {
13462
13516
  return success(null, null, { key: key.data, bound: bound.data });
13463
13517
  }
13464
13518
  taskForWorker(workerId) {
13465
- if (typeof workerId !== "string" || !validId(workerId))
13519
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
13466
13520
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
13467
13521
  const listed = this.listTaskIndex();
13468
13522
  if (!listed.ok)
13469
13523
  return listed;
13524
+ const owners = new Map;
13470
13525
  for (const entry of listed.data) {
13471
13526
  if (entry.status === "closed")
13472
13527
  continue;
13473
13528
  const loaded = this.load(entry.id);
13474
13529
  if (!loaded.ok)
13475
13530
  return loaded;
13476
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
13477
- return success(null, null, entry.id);
13531
+ for (const worker of loaded.data?.record.workers ?? [])
13532
+ owners.set(worker.id, entry.id);
13478
13533
  }
13534
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
13535
+ if (resolved.ok)
13536
+ return success(null, null, owners.get(resolved.data) ?? null);
13537
+ if (resolved.code !== "not_found")
13538
+ return resolved;
13479
13539
  return success(null, null, null);
13480
13540
  }
13481
13541
  originTaskEntry() {
@@ -16734,6 +16794,10 @@ class WorkitCore {
16734
16794
  }
16735
16795
  if (helper?.ok)
16736
16796
  return failure2("permission_denied", "helpers cannot resolve findings");
16797
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
16798
+ if (!findingRef.ok)
16799
+ return findingRef;
16800
+ input.findingId = findingRef.data;
16737
16801
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
16738
16802
  if (!finding)
16739
16803
  return failure2("not_found", "finding not found");
@@ -16885,15 +16949,31 @@ class WorkitCore {
16885
16949
  return changed;
16886
16950
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
16887
16951
  }
16952
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
16953
+ if (!workerRef.ok)
16954
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
16955
+ input.workerId = workerRef.data;
16888
16956
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
16889
16957
  if (!entry)
16890
16958
  return failure2("not_found", "worker not found");
16891
16959
  if (input.action === "report") {
16892
16960
  if (helperId === null || helperId !== input.workerId)
16893
- return failure2("permission_denied", "workers can submit only their own report");
16961
+ return failure2("permission_denied", ONLY_OWN_REPORT);
16894
16962
  const helper = this.helperEntry(task.data, true, true);
16895
16963
  if (!helper.ok)
16896
16964
  return helper;
16965
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
16966
+ const reported = [];
16967
+ for (const findingRef of input.report.findingIds) {
16968
+ const found = resolveIdRef("finding", findingRef, ownFindings);
16969
+ if (found.ok)
16970
+ reported.push(found.data);
16971
+ else if (found.code === "not_found")
16972
+ reported.push(findingRef);
16973
+ else
16974
+ return found;
16975
+ }
16976
+ input.report.findingIds = reported;
16897
16977
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
16898
16978
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
16899
16979
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -16921,7 +17001,7 @@ class WorkitCore {
16921
17001
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
16922
17002
  }
16923
17003
  if (helperId !== null)
16924
- return failure2("permission_denied", "helpers cannot cancel or control workers");
17004
+ return failure2("permission_denied", NO_HELPER_CONTROL);
16925
17005
  if (task.data.status === "closed")
16926
17006
  return failure2("invalid_transition", "closed task cannot cancel workers");
16927
17007
  if (entry.data.state === "stopped") {
@@ -17450,7 +17530,7 @@ var MAX_JUDGED_REFS = 5, workspaceVerification = (root) => {
17450
17530
  return changed;
17451
17531
  const updated = changed.data.task.workers.find((candidate) => candidate.id === input.workerId);
17452
17532
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
17453
- }, refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || scopeCovers(scope, { description: "", paths: [ref.path], exclusions: [] })), TASK_SCOPED_ACTIONS, WORKER_ADDRESSED_ACTIONS, workerCommand = (taskId, request) => {
17533
+ }, refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || scopeCovers(scope, { description: "", paths: [ref.path], exclusions: [] })), TASK_SCOPED_ACTIONS, ONLY_OWN_REPORT = "workers can submit only their own report", NO_HELPER_CONTROL = "helpers cannot cancel or control workers", WORKER_ADDRESSED_ACTIONS, workerCommand = (taskId, request) => {
17454
17534
  const { action, workerId, reason, report } = request;
17455
17535
  const rest = JSON.stringify({ reason, report });
17456
17536
  const payload = rest === "{}" ? "" : ` --payload '${rest.replace(/'/gu, "'\\''")}'`;
@@ -16617,6 +16617,8 @@ var text = string2().check((ctx) => {
16617
16617
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
16618
16618
  });
16619
16619
  var id = text.regex(uuidPattern, "expected lowercase UUID");
16620
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
16621
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
16620
16622
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
16621
16623
  var revision = text.regex(uuidPattern, "expected UUID revision");
16622
16624
  var validUtc = (value) => {
@@ -16994,7 +16996,7 @@ var workerReportSchema = object2({
16994
16996
  outcome: _enum(["completed", "failed", "cancelled"]),
16995
16997
  summary: text,
16996
16998
  evidenceIds: array(id),
16997
- findingIds: array(id)
16999
+ findingIds: array(idRef)
16998
17000
  }).strict();
16999
17001
  var workerSchema = object2({
17000
17002
  assignment: assignmentSchema,
@@ -17240,7 +17242,7 @@ var findingOperations = {
17240
17242
  action: literal("resolve"),
17241
17243
  ...taskId,
17242
17244
  expectedRevision: revision.optional(),
17243
- findingId: id,
17245
+ findingId: idRef,
17244
17246
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
17245
17247
  reason: text,
17246
17248
  evidenceIds: array(id),
@@ -17281,7 +17283,7 @@ var workerOperations = {
17281
17283
  ...taskId,
17282
17284
  expectedRevision: revision.optional(),
17283
17285
  expectedWorkspaceRevision: revision.optional(),
17284
- workerId: id,
17286
+ workerId: idRef,
17285
17287
  report: workerReportSchema
17286
17288
  }),
17287
17289
  cancel: operation({
@@ -17289,7 +17291,7 @@ var workerOperations = {
17289
17291
  ...taskId,
17290
17292
  ...revisions,
17291
17293
  expectedWorkspaceRevision: revision.optional(),
17292
- workerId: id,
17294
+ workerId: idRef,
17293
17295
  reason: text
17294
17296
  })
17295
17297
  };
@@ -17339,6 +17341,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
17339
17341
  data
17340
17342
  });
17341
17343
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
17344
+ function resolveIdRef(kind, ref, ids) {
17345
+ if (ids.includes(ref))
17346
+ return success(null, null, ref);
17347
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
17348
+ if (matches.length === 1)
17349
+ return success(null, null, matches[0]);
17350
+ if (matches.length === 0)
17351
+ return failure2("not_found", `${kind} not found`);
17352
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
17353
+ }
17342
17354
  function parseOperation(family, input) {
17343
17355
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
17344
17356
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -19857,20 +19869,26 @@ class TaskStore {
19857
19869
  return success(null, null, { key: key.data, bound: bound.data });
19858
19870
  }
19859
19871
  taskForWorker(workerId) {
19860
- if (typeof workerId !== "string" || !validId(workerId))
19872
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
19861
19873
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
19862
19874
  const listed = this.listTaskIndex();
19863
19875
  if (!listed.ok)
19864
19876
  return listed;
19877
+ const owners = new Map;
19865
19878
  for (const entry of listed.data) {
19866
19879
  if (entry.status === "closed")
19867
19880
  continue;
19868
19881
  const loaded = this.load(entry.id);
19869
19882
  if (!loaded.ok)
19870
19883
  return loaded;
19871
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
19872
- return success(null, null, entry.id);
19884
+ for (const worker of loaded.data?.record.workers ?? [])
19885
+ owners.set(worker.id, entry.id);
19873
19886
  }
19887
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
19888
+ if (resolved.ok)
19889
+ return success(null, null, owners.get(resolved.data) ?? null);
19890
+ if (resolved.code !== "not_found")
19891
+ return resolved;
19874
19892
  return success(null, null, null);
19875
19893
  }
19876
19894
  originTaskEntry() {
@@ -21990,20 +22008,19 @@ var workspaceConfigSchema = object2({
21990
22008
  union([boolean2(), literal("verified")]).optional()
21991
22009
  ]))).passthrough().optional(),
21992
22010
  youtrack: object2({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
21993
- issues: object2({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
22011
+ issues: object2({
22012
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
22013
+ link: boolean2().optional(),
22014
+ relation: unknown().optional(),
22015
+ backlink: boolean2().optional(),
22016
+ link_on_pr: boolean2().optional()
22017
+ }).passthrough().optional(),
21994
22018
  branchPolicy: workspaceBranchPolicySchema.optional(),
21995
22019
  commitPolicy: workspaceCommitPolicySchema.optional(),
21996
22020
  defaultProfile: nonBlank.optional(),
21997
22021
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
21998
22022
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
21999
22023
  }).passthrough().superRefine((workspace, context) => {
22000
- if (workspace.issues && workspace.vcs?.provider !== "github") {
22001
- context.addIssue({
22002
- code: "custom",
22003
- path: ["issues"],
22004
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
22005
- });
22006
- }
22007
22024
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
22008
22025
  context.addIssue({
22009
22026
  code: "custom",
@@ -22843,6 +22860,44 @@ var readVcsConfig = () => {
22843
22860
  }
22844
22861
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
22845
22862
  };
22863
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
22864
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
22865
+ var flag = (value) => typeof value === "boolean" ? value : null;
22866
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
22867
+ function issueTrackerOf(issues, youtrack) {
22868
+ const iss = asRecord(issues);
22869
+ const yt = asRecord(youtrack);
22870
+ const warnings = [];
22871
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
22872
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
22873
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
22874
+ for (const key of ["repo", "host"])
22875
+ if (iss[key] !== undefined && text2(iss[key]) === null)
22876
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
22877
+ const common = {
22878
+ relation: iss.relation === "refs" ? "refs" : "closes",
22879
+ backlink: iss.backlink === true,
22880
+ baseUrl: text2(yt.baseUrl),
22881
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
22882
+ host: text2(iss.host)?.toLowerCase() ?? null,
22883
+ warnings
22884
+ };
22885
+ const legacyGithub = named === "github" && flag(iss.link) === null;
22886
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
22887
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
22888
+ if (named) {
22889
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
22890
+ return {
22891
+ provider: named,
22892
+ link,
22893
+ ...common,
22894
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
22895
+ };
22896
+ }
22897
+ if (common.baseUrl)
22898
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
22899
+ return null;
22900
+ }
22846
22901
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
22847
22902
  name: resolution.track?.name ?? null,
22848
22903
  source: resolution.source,
@@ -22863,6 +22918,7 @@ function vcsConfig(mode, cwd, options = {}) {
22863
22918
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
22864
22919
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
22865
22920
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
22921
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
22866
22922
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
22867
22923
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
22868
22924
  let issuesProvider = null;
@@ -22895,7 +22951,8 @@ function vcsConfig(mode, cwd, options = {}) {
22895
22951
  link_issues: linkIssues,
22896
22952
  youtrack_base_url: youtrackBaseUrl,
22897
22953
  issues_provider: issuesProvider,
22898
- link_on_pr: linkOnPr
22954
+ link_on_pr: linkOnPr,
22955
+ issue_tracker: issueTracker
22899
22956
  };
22900
22957
  }
22901
22958
  if (cfgStatus === "malformed") {
@@ -22921,7 +22978,8 @@ function vcsConfig(mode, cwd, options = {}) {
22921
22978
  link_issues: linkIssues,
22922
22979
  youtrack_base_url: youtrackBaseUrl,
22923
22980
  issues_provider: issuesProvider,
22924
- link_on_pr: linkOnPr
22981
+ link_on_pr: linkOnPr,
22982
+ issue_tracker: issueTracker
22925
22983
  };
22926
22984
  if (provider === "gitlab") {
22927
22985
  out.gitlab = {
@@ -23025,7 +23083,12 @@ var MAX_LINE_BYTES = 4096;
23025
23083
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
23026
23084
  var FAILING = new Set(["failed", "blocked"]);
23027
23085
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
23028
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
23086
+ var PR_ROW_TYPES = new Set([
23087
+ "pr.created",
23088
+ "pr.status",
23089
+ "pr.merged",
23090
+ "pr.linked"
23091
+ ]);
23029
23092
  var err = (code, error, unblock) => ({
23030
23093
  ok: false,
23031
23094
  code,
@@ -25162,7 +25225,7 @@ var RISK = {
25162
25225
  };
25163
25226
  var YES = new Set(["true", "yes", "y", "1", "on"]);
25164
25227
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
25165
- var flag = (value, unknownAs) => {
25228
+ var flag2 = (value, unknownAs) => {
25166
25229
  if (typeof value === "boolean")
25167
25230
  return { ok: true, value };
25168
25231
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -25228,12 +25291,12 @@ function normalizeJudgment(raw, previous) {
25228
25291
  const items = Array.isArray(value) ? value : [value];
25229
25292
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
25230
25293
  } else {
25231
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
25294
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
25232
25295
  judgment.needsPlan = true;
25233
25296
  judgment.refs.push(refOf(value.trim()));
25234
25297
  continue;
25235
25298
  }
25236
- const parsed = flag(value, name === "behaviorChange");
25299
+ const parsed = flag2(value, name === "behaviorChange");
25237
25300
  if (parsed.ok)
25238
25301
  judgment[name] = parsed.value;
25239
25302
  else
@@ -25588,6 +25651,8 @@ var TASK_SCOPED_ACTIONS = new Set([
25588
25651
  "worker.cancel",
25589
25652
  "state.export"
25590
25653
  ]);
25654
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
25655
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
25591
25656
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
25592
25657
  var workerCommand = (taskId, request) => {
25593
25658
  const { action, workerId, reason, report } = request;
@@ -26485,6 +26550,10 @@ class WorkitCore {
26485
26550
  }
26486
26551
  if (helper?.ok)
26487
26552
  return failure2("permission_denied", "helpers cannot resolve findings");
26553
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
26554
+ if (!findingRef.ok)
26555
+ return findingRef;
26556
+ input.findingId = findingRef.data;
26488
26557
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
26489
26558
  if (!finding)
26490
26559
  return failure2("not_found", "finding not found");
@@ -26636,15 +26705,31 @@ class WorkitCore {
26636
26705
  return changed;
26637
26706
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
26638
26707
  }
26708
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
26709
+ if (!workerRef.ok)
26710
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
26711
+ input.workerId = workerRef.data;
26639
26712
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
26640
26713
  if (!entry)
26641
26714
  return failure2("not_found", "worker not found");
26642
26715
  if (input.action === "report") {
26643
26716
  if (helperId === null || helperId !== input.workerId)
26644
- return failure2("permission_denied", "workers can submit only their own report");
26717
+ return failure2("permission_denied", ONLY_OWN_REPORT);
26645
26718
  const helper = this.helperEntry(task.data, true, true);
26646
26719
  if (!helper.ok)
26647
26720
  return helper;
26721
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
26722
+ const reported = [];
26723
+ for (const findingRef of input.report.findingIds) {
26724
+ const found = resolveIdRef("finding", findingRef, ownFindings);
26725
+ if (found.ok)
26726
+ reported.push(found.data);
26727
+ else if (found.code === "not_found")
26728
+ reported.push(findingRef);
26729
+ else
26730
+ return found;
26731
+ }
26732
+ input.report.findingIds = reported;
26648
26733
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
26649
26734
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
26650
26735
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -26672,7 +26757,7 @@ class WorkitCore {
26672
26757
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
26673
26758
  }
26674
26759
  if (helperId !== null)
26675
- return failure2("permission_denied", "helpers cannot cancel or control workers");
26760
+ return failure2("permission_denied", NO_HELPER_CONTROL);
26676
26761
  if (task.data.status === "closed")
26677
26762
  return failure2("invalid_transition", "closed task cannot cancel workers");
26678
26763
  if (entry.data.state === "stopped") {
@@ -7548,6 +7548,8 @@ var text = string2().check((ctx) => {
7548
7548
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7549
7549
  });
7550
7550
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7551
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7552
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7551
7553
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7552
7554
  var revision = text.regex(uuidPattern, "expected UUID revision");
7553
7555
  var validUtc = (value) => {
@@ -7925,7 +7927,7 @@ var workerReportSchema = object({
7925
7927
  outcome: _enum(["completed", "failed", "cancelled"]),
7926
7928
  summary: text,
7927
7929
  evidenceIds: array(id),
7928
- findingIds: array(id)
7930
+ findingIds: array(idRef)
7929
7931
  }).strict();
7930
7932
  var workerSchema = object({
7931
7933
  assignment: assignmentSchema,
@@ -8171,7 +8173,7 @@ var findingOperations = {
8171
8173
  action: literal("resolve"),
8172
8174
  ...taskId,
8173
8175
  expectedRevision: revision.optional(),
8174
- findingId: id,
8176
+ findingId: idRef,
8175
8177
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8176
8178
  reason: text,
8177
8179
  evidenceIds: array(id),
@@ -8212,7 +8214,7 @@ var workerOperations = {
8212
8214
  ...taskId,
8213
8215
  expectedRevision: revision.optional(),
8214
8216
  expectedWorkspaceRevision: revision.optional(),
8215
- workerId: id,
8217
+ workerId: idRef,
8216
8218
  report: workerReportSchema
8217
8219
  }),
8218
8220
  cancel: operation({
@@ -8220,7 +8222,7 @@ var workerOperations = {
8220
8222
  ...taskId,
8221
8223
  ...revisions,
8222
8224
  expectedWorkspaceRevision: revision.optional(),
8223
- workerId: id,
8225
+ workerId: idRef,
8224
8226
  reason: text
8225
8227
  })
8226
8228
  };
@@ -8270,6 +8272,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8270
8272
  data
8271
8273
  });
8272
8274
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8275
+ function resolveIdRef(kind, ref, ids) {
8276
+ if (ids.includes(ref))
8277
+ return success(null, null, ref);
8278
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8279
+ if (matches.length === 1)
8280
+ return success(null, null, matches[0]);
8281
+ if (matches.length === 0)
8282
+ return failure2("not_found", `${kind} not found`);
8283
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8284
+ }
8273
8285
  function parseOperation(family, input) {
8274
8286
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8275
8287
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -9362,20 +9374,19 @@ var workspaceConfigSchema = object({
9362
9374
  union([boolean2(), literal("verified")]).optional()
9363
9375
  ]))).passthrough().optional(),
9364
9376
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
9365
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
9377
+ issues: object({
9378
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
9379
+ link: boolean2().optional(),
9380
+ relation: unknown().optional(),
9381
+ backlink: boolean2().optional(),
9382
+ link_on_pr: boolean2().optional()
9383
+ }).passthrough().optional(),
9366
9384
  branchPolicy: workspaceBranchPolicySchema.optional(),
9367
9385
  commitPolicy: workspaceCommitPolicySchema.optional(),
9368
9386
  defaultProfile: nonBlank.optional(),
9369
9387
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
9370
9388
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
9371
9389
  }).passthrough().superRefine((workspace, context) => {
9372
- if (workspace.issues && workspace.vcs?.provider !== "github") {
9373
- context.addIssue({
9374
- code: "custom",
9375
- path: ["issues"],
9376
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
9377
- });
9378
- }
9379
9390
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
9380
9391
  context.addIssue({
9381
9392
  code: "custom",
@@ -10177,6 +10188,44 @@ var readVcsConfig = () => {
10177
10188
  }
10178
10189
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
10179
10190
  };
10191
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
10192
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
10193
+ var flag = (value) => typeof value === "boolean" ? value : null;
10194
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
10195
+ function issueTrackerOf(issues, youtrack) {
10196
+ const iss = asRecord(issues);
10197
+ const yt = asRecord(youtrack);
10198
+ const warnings = [];
10199
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
10200
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
10201
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
10202
+ for (const key of ["repo", "host"])
10203
+ if (iss[key] !== undefined && text2(iss[key]) === null)
10204
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
10205
+ const common = {
10206
+ relation: iss.relation === "refs" ? "refs" : "closes",
10207
+ backlink: iss.backlink === true,
10208
+ baseUrl: text2(yt.baseUrl),
10209
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
10210
+ host: text2(iss.host)?.toLowerCase() ?? null,
10211
+ warnings
10212
+ };
10213
+ const legacyGithub = named === "github" && flag(iss.link) === null;
10214
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
10215
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
10216
+ if (named) {
10217
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
10218
+ return {
10219
+ provider: named,
10220
+ link,
10221
+ ...common,
10222
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
10223
+ };
10224
+ }
10225
+ if (common.baseUrl)
10226
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
10227
+ return null;
10228
+ }
10180
10229
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
10181
10230
  name: resolution.track?.name ?? null,
10182
10231
  source: resolution.source,
@@ -10197,6 +10246,7 @@ function vcsConfig(mode, cwd, options = {}) {
10197
10246
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
10198
10247
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
10199
10248
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
10249
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
10200
10250
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
10201
10251
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
10202
10252
  let issuesProvider = null;
@@ -10229,7 +10279,8 @@ function vcsConfig(mode, cwd, options = {}) {
10229
10279
  link_issues: linkIssues,
10230
10280
  youtrack_base_url: youtrackBaseUrl,
10231
10281
  issues_provider: issuesProvider,
10232
- link_on_pr: linkOnPr
10282
+ link_on_pr: linkOnPr,
10283
+ issue_tracker: issueTracker
10233
10284
  };
10234
10285
  }
10235
10286
  if (cfgStatus === "malformed") {
@@ -10255,7 +10306,8 @@ function vcsConfig(mode, cwd, options = {}) {
10255
10306
  link_issues: linkIssues,
10256
10307
  youtrack_base_url: youtrackBaseUrl,
10257
10308
  issues_provider: issuesProvider,
10258
- link_on_pr: linkOnPr
10309
+ link_on_pr: linkOnPr,
10310
+ issue_tracker: issueTracker
10259
10311
  };
10260
10312
  if (provider === "gitlab") {
10261
10313
  out.gitlab = {
@@ -10414,7 +10466,7 @@ var removeIfEmpty = (dir) => {
10414
10466
  };
10415
10467
  var tokenHash = (token) => createHash6("sha256").update(token).digest("hex");
10416
10468
  function recordIssuedSession(cwd, lead, issued) {
10417
- const token = randomBytes(18).toString("base64url");
10469
+ const token = randomBytes(18).toString("hex");
10418
10470
  try {
10419
10471
  migrateRepoIssued(cwd);
10420
10472
  const own = hash(issued);
@@ -10485,7 +10537,12 @@ var MAX_LINE_BYTES = 4096;
10485
10537
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
10486
10538
  var FAILING = new Set(["failed", "blocked"]);
10487
10539
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
10488
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
10540
+ var PR_ROW_TYPES = new Set([
10541
+ "pr.created",
10542
+ "pr.status",
10543
+ "pr.merged",
10544
+ "pr.linked"
10545
+ ]);
10489
10546
  var err = (code, error, unblock) => ({
10490
10547
  ok: false,
10491
10548
  code,
@@ -11966,7 +12023,7 @@ var RISK = {
11966
12023
  };
11967
12024
  var YES = new Set(["true", "yes", "y", "1", "on"]);
11968
12025
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
11969
- var flag = (value, unknownAs) => {
12026
+ var flag2 = (value, unknownAs) => {
11970
12027
  if (typeof value === "boolean")
11971
12028
  return { ok: true, value };
11972
12029
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -12032,12 +12089,12 @@ function normalizeJudgment(raw, previous) {
12032
12089
  const items = Array.isArray(value) ? value : [value];
12033
12090
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
12034
12091
  } else {
12035
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
12092
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
12036
12093
  judgment.needsPlan = true;
12037
12094
  judgment.refs.push(refOf(value.trim()));
12038
12095
  continue;
12039
12096
  }
12040
- const parsed = flag(value, name === "behaviorChange");
12097
+ const parsed = flag2(value, name === "behaviorChange");
12041
12098
  if (parsed.ok)
12042
12099
  judgment[name] = parsed.value;
12043
12100
  else
@@ -13631,20 +13688,26 @@ class TaskStore {
13631
13688
  return success(null, null, { key: key.data, bound: bound.data });
13632
13689
  }
13633
13690
  taskForWorker(workerId) {
13634
- if (typeof workerId !== "string" || !validId(workerId))
13691
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
13635
13692
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
13636
13693
  const listed = this.listTaskIndex();
13637
13694
  if (!listed.ok)
13638
13695
  return listed;
13696
+ const owners = new Map;
13639
13697
  for (const entry of listed.data) {
13640
13698
  if (entry.status === "closed")
13641
13699
  continue;
13642
13700
  const loaded = this.load(entry.id);
13643
13701
  if (!loaded.ok)
13644
13702
  return loaded;
13645
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
13646
- return success(null, null, entry.id);
13703
+ for (const worker of loaded.data?.record.workers ?? [])
13704
+ owners.set(worker.id, entry.id);
13647
13705
  }
13706
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
13707
+ if (resolved.ok)
13708
+ return success(null, null, owners.get(resolved.data) ?? null);
13709
+ if (resolved.code !== "not_found")
13710
+ return resolved;
13648
13711
  return success(null, null, null);
13649
13712
  }
13650
13713
  originTaskEntry() {
@@ -16270,6 +16333,8 @@ var TASK_SCOPED_ACTIONS = new Set([
16270
16333
  "worker.cancel",
16271
16334
  "state.export"
16272
16335
  ]);
16336
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
16337
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
16273
16338
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
16274
16339
  var workerCommand = (taskId, request) => {
16275
16340
  const { action, workerId, reason, report } = request;
@@ -17167,6 +17232,10 @@ class WorkitCore {
17167
17232
  }
17168
17233
  if (helper?.ok)
17169
17234
  return failure2("permission_denied", "helpers cannot resolve findings");
17235
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
17236
+ if (!findingRef.ok)
17237
+ return findingRef;
17238
+ input.findingId = findingRef.data;
17170
17239
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
17171
17240
  if (!finding)
17172
17241
  return failure2("not_found", "finding not found");
@@ -17318,15 +17387,31 @@ class WorkitCore {
17318
17387
  return changed;
17319
17388
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
17320
17389
  }
17390
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
17391
+ if (!workerRef.ok)
17392
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
17393
+ input.workerId = workerRef.data;
17321
17394
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
17322
17395
  if (!entry)
17323
17396
  return failure2("not_found", "worker not found");
17324
17397
  if (input.action === "report") {
17325
17398
  if (helperId === null || helperId !== input.workerId)
17326
- return failure2("permission_denied", "workers can submit only their own report");
17399
+ return failure2("permission_denied", ONLY_OWN_REPORT);
17327
17400
  const helper = this.helperEntry(task.data, true, true);
17328
17401
  if (!helper.ok)
17329
17402
  return helper;
17403
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
17404
+ const reported = [];
17405
+ for (const findingRef of input.report.findingIds) {
17406
+ const found = resolveIdRef("finding", findingRef, ownFindings);
17407
+ if (found.ok)
17408
+ reported.push(found.data);
17409
+ else if (found.code === "not_found")
17410
+ reported.push(findingRef);
17411
+ else
17412
+ return found;
17413
+ }
17414
+ input.report.findingIds = reported;
17330
17415
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
17331
17416
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
17332
17417
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -17354,7 +17439,7 @@ class WorkitCore {
17354
17439
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
17355
17440
  }
17356
17441
  if (helperId !== null)
17357
- return failure2("permission_denied", "helpers cannot cancel or control workers");
17442
+ return failure2("permission_denied", NO_HELPER_CONTROL);
17358
17443
  if (task.data.status === "closed")
17359
17444
  return failure2("invalid_transition", "closed task cannot cancel workers");
17360
17445
  if (entry.data.state === "stopped") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-cursor",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "private": false,
5
5
  "description": "Workit Cursor plugin — shared MCP transport, native hooks, and sixteen method skills",
6
6
  "keywords": [
@@ -46,8 +46,8 @@
46
46
  "build": "bun scripts/build.ts"
47
47
  },
48
48
  "devDependencies": {
49
- "@brainervirus/workit-core": "^8.14.0",
50
- "@brainervirus/workit-mcp": "^8.14.0"
49
+ "@brainervirus/workit-core": "^8.15.0",
50
+ "@brainervirus/workit-mcp": "^8.15.0"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=24"
@@ -33,8 +33,8 @@ that repeats because the base keeps moving, or after 3 failed fix attempts on
33
33
  the same check.
34
34
 
35
35
  1. **Open.** `workit git push`, then `workit pr create --title "<title>"
36
- --body-file <f|->` (idempotent; body: `references/pr-body.md`; edits:
37
- `workit pr edit`). A stack: `workit stack plan <bottom> ... <top>`, one
36
+ --body-file <f|->` plus `--issue <#n|ID>` per issue (`--refs`: no close; idempotent;
37
+ body: `references/pr-body.md`; edits: `workit pr edit`). A stack: `workit stack plan <bottom> ... <top>`, one
38
38
  `workit pr create --base <parent> --fill` per branch, then
39
39
  `workit stack sync`. Finish the whole stack before babysitting any PR.
40
40
  2. **Read state** with `workit pr status --json`. `REVIEW` alone is a pending