@brainervirus/workit-codex 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workit",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "description": "Adaptive engineering workflow rails for Codex",
5
5
  "author": {
6
6
  "name": "BrainerVirus"
@@ -14236,6 +14236,8 @@ var text = string2().check((ctx) => {
14236
14236
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
14237
14237
  });
14238
14238
  var id = text.regex(uuidPattern, "expected lowercase UUID");
14239
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
14240
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
14239
14241
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
14240
14242
  var revision = text.regex(uuidPattern, "expected UUID revision");
14241
14243
  var validUtc = (value) => {
@@ -14613,7 +14615,7 @@ var workerReportSchema = object({
14613
14615
  outcome: _enum(["completed", "failed", "cancelled"]),
14614
14616
  summary: text,
14615
14617
  evidenceIds: array(id),
14616
- findingIds: array(id)
14618
+ findingIds: array(idRef)
14617
14619
  }).strict();
14618
14620
  var workerSchema = object({
14619
14621
  assignment: assignmentSchema,
@@ -14859,7 +14861,7 @@ var findingOperations = {
14859
14861
  action: literal("resolve"),
14860
14862
  ...taskId,
14861
14863
  expectedRevision: revision.optional(),
14862
- findingId: id,
14864
+ findingId: idRef,
14863
14865
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
14864
14866
  reason: text,
14865
14867
  evidenceIds: array(id),
@@ -14900,7 +14902,7 @@ var workerOperations = {
14900
14902
  ...taskId,
14901
14903
  expectedRevision: revision.optional(),
14902
14904
  expectedWorkspaceRevision: revision.optional(),
14903
- workerId: id,
14905
+ workerId: idRef,
14904
14906
  report: workerReportSchema
14905
14907
  }),
14906
14908
  cancel: operation({
@@ -14908,7 +14910,7 @@ var workerOperations = {
14908
14910
  ...taskId,
14909
14911
  ...revisions,
14910
14912
  expectedWorkspaceRevision: revision.optional(),
14911
- workerId: id,
14913
+ workerId: idRef,
14912
14914
  reason: text
14913
14915
  })
14914
14916
  };
@@ -14958,6 +14960,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
14958
14960
  data
14959
14961
  });
14960
14962
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
14963
+ function resolveIdRef(kind, ref, ids) {
14964
+ if (ids.includes(ref))
14965
+ return success(null, null, ref);
14966
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
14967
+ if (matches.length === 1)
14968
+ return success(null, null, matches[0]);
14969
+ if (matches.length === 0)
14970
+ return failure2("not_found", `${kind} not found`);
14971
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
14972
+ }
14961
14973
  function parseOperation(family, input) {
14962
14974
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
14963
14975
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -17476,20 +17488,26 @@ class TaskStore {
17476
17488
  return success(null, null, { key: key.data, bound: bound.data });
17477
17489
  }
17478
17490
  taskForWorker(workerId) {
17479
- if (typeof workerId !== "string" || !validId(workerId))
17491
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
17480
17492
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
17481
17493
  const listed = this.listTaskIndex();
17482
17494
  if (!listed.ok)
17483
17495
  return listed;
17496
+ const owners = new Map;
17484
17497
  for (const entry of listed.data) {
17485
17498
  if (entry.status === "closed")
17486
17499
  continue;
17487
17500
  const loaded = this.load(entry.id);
17488
17501
  if (!loaded.ok)
17489
17502
  return loaded;
17490
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
17491
- return success(null, null, entry.id);
17503
+ for (const worker of loaded.data?.record.workers ?? [])
17504
+ owners.set(worker.id, entry.id);
17492
17505
  }
17506
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
17507
+ if (resolved.ok)
17508
+ return success(null, null, owners.get(resolved.data) ?? null);
17509
+ if (resolved.code !== "not_found")
17510
+ return resolved;
17493
17511
  return success(null, null, null);
17494
17512
  }
17495
17513
  originTaskEntry() {
@@ -19609,20 +19627,19 @@ var workspaceConfigSchema = object({
19609
19627
  union([boolean2(), literal("verified")]).optional()
19610
19628
  ]))).passthrough().optional(),
19611
19629
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
19612
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
19630
+ issues: object({
19631
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
19632
+ link: boolean2().optional(),
19633
+ relation: unknown().optional(),
19634
+ backlink: boolean2().optional(),
19635
+ link_on_pr: boolean2().optional()
19636
+ }).passthrough().optional(),
19613
19637
  branchPolicy: workspaceBranchPolicySchema.optional(),
19614
19638
  commitPolicy: workspaceCommitPolicySchema.optional(),
19615
19639
  defaultProfile: nonBlank.optional(),
19616
19640
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
19617
19641
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
19618
19642
  }).passthrough().superRefine((workspace, context) => {
19619
- if (workspace.issues && workspace.vcs?.provider !== "github") {
19620
- context.addIssue({
19621
- code: "custom",
19622
- path: ["issues"],
19623
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
19624
- });
19625
- }
19626
19643
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
19627
19644
  context.addIssue({
19628
19645
  code: "custom",
@@ -20462,6 +20479,44 @@ var readVcsConfig = () => {
20462
20479
  }
20463
20480
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
20464
20481
  };
20482
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
20483
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
20484
+ var flag = (value) => typeof value === "boolean" ? value : null;
20485
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
20486
+ function issueTrackerOf(issues, youtrack) {
20487
+ const iss = asRecord(issues);
20488
+ const yt = asRecord(youtrack);
20489
+ const warnings = [];
20490
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
20491
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
20492
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
20493
+ for (const key of ["repo", "host"])
20494
+ if (iss[key] !== undefined && text2(iss[key]) === null)
20495
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
20496
+ const common = {
20497
+ relation: iss.relation === "refs" ? "refs" : "closes",
20498
+ backlink: iss.backlink === true,
20499
+ baseUrl: text2(yt.baseUrl),
20500
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
20501
+ host: text2(iss.host)?.toLowerCase() ?? null,
20502
+ warnings
20503
+ };
20504
+ const legacyGithub = named === "github" && flag(iss.link) === null;
20505
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
20506
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
20507
+ if (named) {
20508
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
20509
+ return {
20510
+ provider: named,
20511
+ link,
20512
+ ...common,
20513
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
20514
+ };
20515
+ }
20516
+ if (common.baseUrl)
20517
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
20518
+ return null;
20519
+ }
20465
20520
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
20466
20521
  name: resolution.track?.name ?? null,
20467
20522
  source: resolution.source,
@@ -20482,6 +20537,7 @@ function vcsConfig(mode, cwd, options = {}) {
20482
20537
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
20483
20538
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
20484
20539
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
20540
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
20485
20541
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
20486
20542
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
20487
20543
  let issuesProvider = null;
@@ -20514,7 +20570,8 @@ function vcsConfig(mode, cwd, options = {}) {
20514
20570
  link_issues: linkIssues,
20515
20571
  youtrack_base_url: youtrackBaseUrl,
20516
20572
  issues_provider: issuesProvider,
20517
- link_on_pr: linkOnPr
20573
+ link_on_pr: linkOnPr,
20574
+ issue_tracker: issueTracker
20518
20575
  };
20519
20576
  }
20520
20577
  if (cfgStatus === "malformed") {
@@ -20540,7 +20597,8 @@ function vcsConfig(mode, cwd, options = {}) {
20540
20597
  link_issues: linkIssues,
20541
20598
  youtrack_base_url: youtrackBaseUrl,
20542
20599
  issues_provider: issuesProvider,
20543
- link_on_pr: linkOnPr
20600
+ link_on_pr: linkOnPr,
20601
+ issue_tracker: issueTracker
20544
20602
  };
20545
20603
  if (provider === "gitlab") {
20546
20604
  out.gitlab = {
@@ -20644,7 +20702,12 @@ var MAX_LINE_BYTES = 4096;
20644
20702
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
20645
20703
  var FAILING = new Set(["failed", "blocked"]);
20646
20704
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
20647
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
20705
+ var PR_ROW_TYPES = new Set([
20706
+ "pr.created",
20707
+ "pr.status",
20708
+ "pr.merged",
20709
+ "pr.linked"
20710
+ ]);
20648
20711
  var err = (code, error, unblock) => ({
20649
20712
  ok: false,
20650
20713
  code,
@@ -22781,7 +22844,7 @@ var RISK = {
22781
22844
  };
22782
22845
  var YES = new Set(["true", "yes", "y", "1", "on"]);
22783
22846
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
22784
- var flag = (value, unknownAs) => {
22847
+ var flag2 = (value, unknownAs) => {
22785
22848
  if (typeof value === "boolean")
22786
22849
  return { ok: true, value };
22787
22850
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -22847,12 +22910,12 @@ function normalizeJudgment(raw, previous) {
22847
22910
  const items = Array.isArray(value) ? value : [value];
22848
22911
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
22849
22912
  } else {
22850
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
22913
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
22851
22914
  judgment.needsPlan = true;
22852
22915
  judgment.refs.push(refOf(value.trim()));
22853
22916
  continue;
22854
22917
  }
22855
- const parsed = flag(value, name === "behaviorChange");
22918
+ const parsed = flag2(value, name === "behaviorChange");
22856
22919
  if (parsed.ok)
22857
22920
  judgment[name] = parsed.value;
22858
22921
  else
@@ -23207,6 +23270,8 @@ var TASK_SCOPED_ACTIONS = new Set([
23207
23270
  "worker.cancel",
23208
23271
  "state.export"
23209
23272
  ]);
23273
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
23274
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
23210
23275
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
23211
23276
  var workerCommand = (taskId, request) => {
23212
23277
  const { action, workerId, reason, report } = request;
@@ -24104,6 +24169,10 @@ class WorkitCore {
24104
24169
  }
24105
24170
  if (helper?.ok)
24106
24171
  return failure2("permission_denied", "helpers cannot resolve findings");
24172
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
24173
+ if (!findingRef.ok)
24174
+ return findingRef;
24175
+ input.findingId = findingRef.data;
24107
24176
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
24108
24177
  if (!finding)
24109
24178
  return failure2("not_found", "finding not found");
@@ -24255,15 +24324,31 @@ class WorkitCore {
24255
24324
  return changed;
24256
24325
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
24257
24326
  }
24327
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
24328
+ if (!workerRef.ok)
24329
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
24330
+ input.workerId = workerRef.data;
24258
24331
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
24259
24332
  if (!entry)
24260
24333
  return failure2("not_found", "worker not found");
24261
24334
  if (input.action === "report") {
24262
24335
  if (helperId === null || helperId !== input.workerId)
24263
- return failure2("permission_denied", "workers can submit only their own report");
24336
+ return failure2("permission_denied", ONLY_OWN_REPORT);
24264
24337
  const helper = this.helperEntry(task.data, true, true);
24265
24338
  if (!helper.ok)
24266
24339
  return helper;
24340
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
24341
+ const reported = [];
24342
+ for (const findingRef of input.report.findingIds) {
24343
+ const found = resolveIdRef("finding", findingRef, ownFindings);
24344
+ if (found.ok)
24345
+ reported.push(found.data);
24346
+ else if (found.code === "not_found")
24347
+ reported.push(findingRef);
24348
+ else
24349
+ return found;
24350
+ }
24351
+ input.report.findingIds = reported;
24267
24352
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
24268
24353
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
24269
24354
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -24291,7 +24376,7 @@ class WorkitCore {
24291
24376
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
24292
24377
  }
24293
24378
  if (helperId !== null)
24294
- return failure2("permission_denied", "helpers cannot cancel or control workers");
24379
+ return failure2("permission_denied", NO_HELPER_CONTROL);
24295
24380
  if (task.data.status === "closed")
24296
24381
  return failure2("invalid_transition", "closed task cannot cancel workers");
24297
24382
  if (entry.data.state === "stopped") {
@@ -7966,6 +7966,8 @@ var text = string2().check((ctx) => {
7966
7966
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7967
7967
  });
7968
7968
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7969
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7970
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7969
7971
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7970
7972
  var revision = text.regex(uuidPattern, "expected UUID revision");
7971
7973
  var validUtc = (value) => {
@@ -8343,7 +8345,7 @@ var workerReportSchema = object({
8343
8345
  outcome: _enum(["completed", "failed", "cancelled"]),
8344
8346
  summary: text,
8345
8347
  evidenceIds: array(id),
8346
- findingIds: array(id)
8348
+ findingIds: array(idRef)
8347
8349
  }).strict();
8348
8350
  var workerSchema = object({
8349
8351
  assignment: assignmentSchema,
@@ -8589,7 +8591,7 @@ var findingOperations = {
8589
8591
  action: literal("resolve"),
8590
8592
  ...taskId,
8591
8593
  expectedRevision: revision.optional(),
8592
- findingId: id,
8594
+ findingId: idRef,
8593
8595
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8594
8596
  reason: text,
8595
8597
  evidenceIds: array(id),
@@ -8630,7 +8632,7 @@ var workerOperations = {
8630
8632
  ...taskId,
8631
8633
  expectedRevision: revision.optional(),
8632
8634
  expectedWorkspaceRevision: revision.optional(),
8633
- workerId: id,
8635
+ workerId: idRef,
8634
8636
  report: workerReportSchema
8635
8637
  }),
8636
8638
  cancel: operation({
@@ -8638,7 +8640,7 @@ var workerOperations = {
8638
8640
  ...taskId,
8639
8641
  ...revisions,
8640
8642
  expectedWorkspaceRevision: revision.optional(),
8641
- workerId: id,
8643
+ workerId: idRef,
8642
8644
  reason: text
8643
8645
  })
8644
8646
  };
@@ -8688,6 +8690,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8688
8690
  data
8689
8691
  });
8690
8692
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8693
+ function resolveIdRef(kind, ref, ids) {
8694
+ if (ids.includes(ref))
8695
+ return success(null, null, ref);
8696
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8697
+ if (matches.length === 1)
8698
+ return success(null, null, matches[0]);
8699
+ if (matches.length === 0)
8700
+ return failure2("not_found", `${kind} not found`);
8701
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8702
+ }
8691
8703
  function parseOperation(family, input) {
8692
8704
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8693
8705
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -9934,7 +9946,7 @@ var removeIfEmpty = (dir) => {
9934
9946
  };
9935
9947
  var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
9936
9948
  function recordIssuedSession(cwd, lead, issued) {
9937
- const token = randomBytes2(18).toString("base64url");
9949
+ const token = randomBytes2(18).toString("hex");
9938
9950
  try {
9939
9951
  migrateRepoIssued(cwd);
9940
9952
  const own = hash(issued);
@@ -10937,20 +10949,26 @@ class TaskStore {
10937
10949
  return success(null, null, { key: key.data, bound: bound.data });
10938
10950
  }
10939
10951
  taskForWorker(workerId) {
10940
- if (typeof workerId !== "string" || !validId(workerId))
10952
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
10941
10953
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
10942
10954
  const listed = this.listTaskIndex();
10943
10955
  if (!listed.ok)
10944
10956
  return listed;
10957
+ const owners = new Map;
10945
10958
  for (const entry of listed.data) {
10946
10959
  if (entry.status === "closed")
10947
10960
  continue;
10948
10961
  const loaded = this.load(entry.id);
10949
10962
  if (!loaded.ok)
10950
10963
  return loaded;
10951
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
10952
- return success(null, null, entry.id);
10964
+ for (const worker of loaded.data?.record.workers ?? [])
10965
+ owners.set(worker.id, entry.id);
10953
10966
  }
10967
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
10968
+ if (resolved.ok)
10969
+ return success(null, null, owners.get(resolved.data) ?? null);
10970
+ if (resolved.code !== "not_found")
10971
+ return resolved;
10954
10972
  return success(null, null, null);
10955
10973
  }
10956
10974
  originTaskEntry() {
@@ -12679,20 +12697,19 @@ var workspaceConfigSchema = object({
12679
12697
  union([boolean2(), literal("verified")]).optional()
12680
12698
  ]))).passthrough().optional(),
12681
12699
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
12682
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
12700
+ issues: object({
12701
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
12702
+ link: boolean2().optional(),
12703
+ relation: unknown().optional(),
12704
+ backlink: boolean2().optional(),
12705
+ link_on_pr: boolean2().optional()
12706
+ }).passthrough().optional(),
12683
12707
  branchPolicy: workspaceBranchPolicySchema.optional(),
12684
12708
  commitPolicy: workspaceCommitPolicySchema.optional(),
12685
12709
  defaultProfile: nonBlank.optional(),
12686
12710
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
12687
12711
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
12688
12712
  }).passthrough().superRefine((workspace, context) => {
12689
- if (workspace.issues && workspace.vcs?.provider !== "github") {
12690
- context.addIssue({
12691
- code: "custom",
12692
- path: ["issues"],
12693
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
12694
- });
12695
- }
12696
12713
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
12697
12714
  context.addIssue({
12698
12715
  code: "custom",
@@ -13494,6 +13511,44 @@ var readVcsConfig = () => {
13494
13511
  }
13495
13512
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
13496
13513
  };
13514
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
13515
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
13516
+ var flag = (value) => typeof value === "boolean" ? value : null;
13517
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
13518
+ function issueTrackerOf(issues, youtrack) {
13519
+ const iss = asRecord(issues);
13520
+ const yt = asRecord(youtrack);
13521
+ const warnings = [];
13522
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
13523
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
13524
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
13525
+ for (const key of ["repo", "host"])
13526
+ if (iss[key] !== undefined && text2(iss[key]) === null)
13527
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
13528
+ const common = {
13529
+ relation: iss.relation === "refs" ? "refs" : "closes",
13530
+ backlink: iss.backlink === true,
13531
+ baseUrl: text2(yt.baseUrl),
13532
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
13533
+ host: text2(iss.host)?.toLowerCase() ?? null,
13534
+ warnings
13535
+ };
13536
+ const legacyGithub = named === "github" && flag(iss.link) === null;
13537
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
13538
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
13539
+ if (named) {
13540
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
13541
+ return {
13542
+ provider: named,
13543
+ link,
13544
+ ...common,
13545
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
13546
+ };
13547
+ }
13548
+ if (common.baseUrl)
13549
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
13550
+ return null;
13551
+ }
13497
13552
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
13498
13553
  name: resolution.track?.name ?? null,
13499
13554
  source: resolution.source,
@@ -13514,6 +13569,7 @@ function vcsConfig(mode, cwd, options = {}) {
13514
13569
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
13515
13570
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
13516
13571
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
13572
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
13517
13573
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
13518
13574
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
13519
13575
  let issuesProvider = null;
@@ -13546,7 +13602,8 @@ function vcsConfig(mode, cwd, options = {}) {
13546
13602
  link_issues: linkIssues,
13547
13603
  youtrack_base_url: youtrackBaseUrl,
13548
13604
  issues_provider: issuesProvider,
13549
- link_on_pr: linkOnPr
13605
+ link_on_pr: linkOnPr,
13606
+ issue_tracker: issueTracker
13550
13607
  };
13551
13608
  }
13552
13609
  if (cfgStatus === "malformed") {
@@ -13572,7 +13629,8 @@ function vcsConfig(mode, cwd, options = {}) {
13572
13629
  link_issues: linkIssues,
13573
13630
  youtrack_base_url: youtrackBaseUrl,
13574
13631
  issues_provider: issuesProvider,
13575
- link_on_pr: linkOnPr
13632
+ link_on_pr: linkOnPr,
13633
+ issue_tracker: issueTracker
13576
13634
  };
13577
13635
  if (provider === "gitlab") {
13578
13636
  out.gitlab = {
@@ -13591,7 +13649,12 @@ var MAX_LINE_BYTES = 4096;
13591
13649
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
13592
13650
  var FAILING = new Set(["failed", "blocked"]);
13593
13651
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
13594
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
13652
+ var PR_ROW_TYPES = new Set([
13653
+ "pr.created",
13654
+ "pr.status",
13655
+ "pr.merged",
13656
+ "pr.linked"
13657
+ ]);
13595
13658
  var err = (code, error, unblock) => ({
13596
13659
  ok: false,
13597
13660
  code,
@@ -15741,7 +15804,7 @@ var RISK = {
15741
15804
  };
15742
15805
  var YES = new Set(["true", "yes", "y", "1", "on"]);
15743
15806
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
15744
- var flag = (value, unknownAs) => {
15807
+ var flag2 = (value, unknownAs) => {
15745
15808
  if (typeof value === "boolean")
15746
15809
  return { ok: true, value };
15747
15810
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -15807,12 +15870,12 @@ function normalizeJudgment(raw, previous) {
15807
15870
  const items = Array.isArray(value) ? value : [value];
15808
15871
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
15809
15872
  } else {
15810
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
15873
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
15811
15874
  judgment.needsPlan = true;
15812
15875
  judgment.refs.push(refOf(value.trim()));
15813
15876
  continue;
15814
15877
  }
15815
- const parsed = flag(value, name === "behaviorChange");
15878
+ const parsed = flag2(value, name === "behaviorChange");
15816
15879
  if (parsed.ok)
15817
15880
  judgment[name] = parsed.value;
15818
15881
  else
@@ -16294,6 +16357,8 @@ var TASK_SCOPED_ACTIONS = new Set([
16294
16357
  "worker.cancel",
16295
16358
  "state.export"
16296
16359
  ]);
16360
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
16361
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
16297
16362
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
16298
16363
  var workerCommand = (taskId, request) => {
16299
16364
  const { action, workerId, reason, report } = request;
@@ -17191,6 +17256,10 @@ class WorkitCore {
17191
17256
  }
17192
17257
  if (helper?.ok)
17193
17258
  return failure2("permission_denied", "helpers cannot resolve findings");
17259
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
17260
+ if (!findingRef.ok)
17261
+ return findingRef;
17262
+ input.findingId = findingRef.data;
17194
17263
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
17195
17264
  if (!finding)
17196
17265
  return failure2("not_found", "finding not found");
@@ -17342,15 +17411,31 @@ class WorkitCore {
17342
17411
  return changed;
17343
17412
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
17344
17413
  }
17414
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
17415
+ if (!workerRef.ok)
17416
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
17417
+ input.workerId = workerRef.data;
17345
17418
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
17346
17419
  if (!entry)
17347
17420
  return failure2("not_found", "worker not found");
17348
17421
  if (input.action === "report") {
17349
17422
  if (helperId === null || helperId !== input.workerId)
17350
- return failure2("permission_denied", "workers can submit only their own report");
17423
+ return failure2("permission_denied", ONLY_OWN_REPORT);
17351
17424
  const helper = this.helperEntry(task.data, true, true);
17352
17425
  if (!helper.ok)
17353
17426
  return helper;
17427
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
17428
+ const reported = [];
17429
+ for (const findingRef of input.report.findingIds) {
17430
+ const found = resolveIdRef("finding", findingRef, ownFindings);
17431
+ if (found.ok)
17432
+ reported.push(found.data);
17433
+ else if (found.code === "not_found")
17434
+ reported.push(findingRef);
17435
+ else
17436
+ return found;
17437
+ }
17438
+ input.report.findingIds = reported;
17354
17439
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
17355
17440
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
17356
17441
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -17378,7 +17463,7 @@ class WorkitCore {
17378
17463
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
17379
17464
  }
17380
17465
  if (helperId !== null)
17381
- return failure2("permission_denied", "helpers cannot cancel or control workers");
17466
+ return failure2("permission_denied", NO_HELPER_CONTROL);
17382
17467
  if (task.data.status === "closed")
17383
17468
  return failure2("invalid_transition", "closed task cannot cancel workers");
17384
17469
  if (entry.data.state === "stopped") {
@@ -37,6 +37,6 @@ if (quiet)
37
37
  process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
38
38
  `);
39
39
  else {
40
- const { runCodexHook } = await import("./workit-hook-s8c0mzcq.js");
40
+ const { runCodexHook } = await import("./workit-hook-v4a1y79w.js");
41
41
  await runCodexHook([payload]);
42
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-codex",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "private": false,
5
5
  "description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
6
6
  "keywords": [
@@ -39,8 +39,8 @@
39
39
  "build": "bun scripts/build.ts"
40
40
  },
41
41
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.14.0",
43
- "@brainervirus/workit-mcp": "^8.14.0"
42
+ "@brainervirus/workit-core": "^8.15.0",
43
+ "@brainervirus/workit-mcp": "^8.15.0"
44
44
  },
45
45
  "engines": {
46
46
  "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