@brainervirus/workit-opencode 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.
@@ -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
package/dist/plugin.js CHANGED
@@ -7772,6 +7772,8 @@ var text = string2().check((ctx) => {
7772
7772
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7773
7773
  });
7774
7774
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7775
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7776
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7775
7777
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7776
7778
  var revision = text.regex(uuidPattern, "expected UUID revision");
7777
7779
  var validUtc = (value) => {
@@ -8149,7 +8151,7 @@ var workerReportSchema = object({
8149
8151
  outcome: _enum(["completed", "failed", "cancelled"]),
8150
8152
  summary: text,
8151
8153
  evidenceIds: array(id),
8152
- findingIds: array(id)
8154
+ findingIds: array(idRef)
8153
8155
  }).strict();
8154
8156
  var workerSchema = object({
8155
8157
  assignment: assignmentSchema,
@@ -8395,7 +8397,7 @@ var findingOperations = {
8395
8397
  action: literal("resolve"),
8396
8398
  ...taskId,
8397
8399
  expectedRevision: revision.optional(),
8398
- findingId: id,
8400
+ findingId: idRef,
8399
8401
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8400
8402
  reason: text,
8401
8403
  evidenceIds: array(id),
@@ -8436,7 +8438,7 @@ var workerOperations = {
8436
8438
  ...taskId,
8437
8439
  expectedRevision: revision.optional(),
8438
8440
  expectedWorkspaceRevision: revision.optional(),
8439
- workerId: id,
8441
+ workerId: idRef,
8440
8442
  report: workerReportSchema
8441
8443
  }),
8442
8444
  cancel: operation({
@@ -8444,7 +8446,7 @@ var workerOperations = {
8444
8446
  ...taskId,
8445
8447
  ...revisions,
8446
8448
  expectedWorkspaceRevision: revision.optional(),
8447
- workerId: id,
8449
+ workerId: idRef,
8448
8450
  reason: text
8449
8451
  })
8450
8452
  };
@@ -8494,6 +8496,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8494
8496
  data
8495
8497
  });
8496
8498
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8499
+ function resolveIdRef(kind, ref, ids) {
8500
+ if (ids.includes(ref))
8501
+ return success(null, null, ref);
8502
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8503
+ if (matches.length === 1)
8504
+ return success(null, null, matches[0]);
8505
+ if (matches.length === 0)
8506
+ return failure2("not_found", `${kind} not found`);
8507
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8508
+ }
8497
8509
  function parseOperation(family, input) {
8498
8510
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8499
8511
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -11003,20 +11015,26 @@ class TaskStore {
11003
11015
  return success(null, null, { key: key.data, bound: bound.data });
11004
11016
  }
11005
11017
  taskForWorker(workerId) {
11006
- if (typeof workerId !== "string" || !validId(workerId))
11018
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
11007
11019
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
11008
11020
  const listed = this.listTaskIndex();
11009
11021
  if (!listed.ok)
11010
11022
  return listed;
11023
+ const owners = new Map;
11011
11024
  for (const entry of listed.data) {
11012
11025
  if (entry.status === "closed")
11013
11026
  continue;
11014
11027
  const loaded = this.load(entry.id);
11015
11028
  if (!loaded.ok)
11016
11029
  return loaded;
11017
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
11018
- return success(null, null, entry.id);
11030
+ for (const worker of loaded.data?.record.workers ?? [])
11031
+ owners.set(worker.id, entry.id);
11019
11032
  }
11033
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
11034
+ if (resolved.ok)
11035
+ return success(null, null, owners.get(resolved.data) ?? null);
11036
+ if (resolved.code !== "not_found")
11037
+ return resolved;
11020
11038
  return success(null, null, null);
11021
11039
  }
11022
11040
  originTaskEntry() {
@@ -13168,20 +13186,19 @@ var workspaceConfigSchema = object({
13168
13186
  union([boolean2(), literal("verified")]).optional()
13169
13187
  ]))).passthrough().optional(),
13170
13188
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
13171
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
13189
+ issues: object({
13190
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
13191
+ link: boolean2().optional(),
13192
+ relation: unknown().optional(),
13193
+ backlink: boolean2().optional(),
13194
+ link_on_pr: boolean2().optional()
13195
+ }).passthrough().optional(),
13172
13196
  branchPolicy: workspaceBranchPolicySchema.optional(),
13173
13197
  commitPolicy: workspaceCommitPolicySchema.optional(),
13174
13198
  defaultProfile: nonBlank.optional(),
13175
13199
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
13176
13200
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
13177
13201
  }).passthrough().superRefine((workspace, context) => {
13178
- if (workspace.issues && workspace.vcs?.provider !== "github") {
13179
- context.addIssue({
13180
- code: "custom",
13181
- path: ["issues"],
13182
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
13183
- });
13184
- }
13185
13202
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
13186
13203
  context.addIssue({
13187
13204
  code: "custom",
@@ -14048,6 +14065,44 @@ var readVcsConfig = () => {
14048
14065
  }
14049
14066
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
14050
14067
  };
14068
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
14069
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
14070
+ var flag = (value) => typeof value === "boolean" ? value : null;
14071
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
14072
+ function issueTrackerOf(issues, youtrack) {
14073
+ const iss = asRecord(issues);
14074
+ const yt = asRecord(youtrack);
14075
+ const warnings = [];
14076
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
14077
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
14078
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
14079
+ for (const key of ["repo", "host"])
14080
+ if (iss[key] !== undefined && text2(iss[key]) === null)
14081
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
14082
+ const common = {
14083
+ relation: iss.relation === "refs" ? "refs" : "closes",
14084
+ backlink: iss.backlink === true,
14085
+ baseUrl: text2(yt.baseUrl),
14086
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
14087
+ host: text2(iss.host)?.toLowerCase() ?? null,
14088
+ warnings
14089
+ };
14090
+ const legacyGithub = named === "github" && flag(iss.link) === null;
14091
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
14092
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
14093
+ if (named) {
14094
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
14095
+ return {
14096
+ provider: named,
14097
+ link,
14098
+ ...common,
14099
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
14100
+ };
14101
+ }
14102
+ if (common.baseUrl)
14103
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
14104
+ return null;
14105
+ }
14051
14106
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
14052
14107
  name: resolution.track?.name ?? null,
14053
14108
  source: resolution.source,
@@ -14068,6 +14123,7 @@ function vcsConfig(mode, cwd, options = {}) {
14068
14123
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
14069
14124
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
14070
14125
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
14126
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
14071
14127
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
14072
14128
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
14073
14129
  let issuesProvider = null;
@@ -14100,7 +14156,8 @@ function vcsConfig(mode, cwd, options = {}) {
14100
14156
  link_issues: linkIssues,
14101
14157
  youtrack_base_url: youtrackBaseUrl,
14102
14158
  issues_provider: issuesProvider,
14103
- link_on_pr: linkOnPr
14159
+ link_on_pr: linkOnPr,
14160
+ issue_tracker: issueTracker
14104
14161
  };
14105
14162
  }
14106
14163
  if (cfgStatus === "malformed") {
@@ -14126,7 +14183,8 @@ function vcsConfig(mode, cwd, options = {}) {
14126
14183
  link_issues: linkIssues,
14127
14184
  youtrack_base_url: youtrackBaseUrl,
14128
14185
  issues_provider: issuesProvider,
14129
- link_on_pr: linkOnPr
14186
+ link_on_pr: linkOnPr,
14187
+ issue_tracker: issueTracker
14130
14188
  };
14131
14189
  if (provider === "gitlab") {
14132
14190
  out.gitlab = {
@@ -14230,7 +14288,12 @@ var MAX_LINE_BYTES = 4096;
14230
14288
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
14231
14289
  var FAILING = new Set(["failed", "blocked"]);
14232
14290
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
14233
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
14291
+ var PR_ROW_TYPES = new Set([
14292
+ "pr.created",
14293
+ "pr.status",
14294
+ "pr.merged",
14295
+ "pr.linked"
14296
+ ]);
14234
14297
  var err = (code, error, unblock) => ({
14235
14298
  ok: false,
14236
14299
  code,
@@ -18515,7 +18578,7 @@ var RISK = {
18515
18578
  };
18516
18579
  var YES = new Set(["true", "yes", "y", "1", "on"]);
18517
18580
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
18518
- var flag = (value, unknownAs) => {
18581
+ var flag2 = (value, unknownAs) => {
18519
18582
  if (typeof value === "boolean")
18520
18583
  return { ok: true, value };
18521
18584
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -18581,12 +18644,12 @@ function normalizeJudgment(raw, previous) {
18581
18644
  const items = Array.isArray(value) ? value : [value];
18582
18645
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
18583
18646
  } else {
18584
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
18647
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
18585
18648
  judgment.needsPlan = true;
18586
18649
  judgment.refs.push(refOf(value.trim()));
18587
18650
  continue;
18588
18651
  }
18589
- const parsed = flag(value, name === "behaviorChange");
18652
+ const parsed = flag2(value, name === "behaviorChange");
18590
18653
  if (parsed.ok)
18591
18654
  judgment[name] = parsed.value;
18592
18655
  else
@@ -18966,6 +19029,8 @@ var TASK_SCOPED_ACTIONS = new Set([
18966
19029
  "worker.cancel",
18967
19030
  "state.export"
18968
19031
  ]);
19032
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
19033
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
18969
19034
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
18970
19035
  var workerCommand = (taskId, request) => {
18971
19036
  const { action, workerId, reason, report } = request;
@@ -19863,6 +19928,10 @@ class WorkitCore {
19863
19928
  }
19864
19929
  if (helper?.ok)
19865
19930
  return failure2("permission_denied", "helpers cannot resolve findings");
19931
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
19932
+ if (!findingRef.ok)
19933
+ return findingRef;
19934
+ input.findingId = findingRef.data;
19866
19935
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
19867
19936
  if (!finding)
19868
19937
  return failure2("not_found", "finding not found");
@@ -20014,15 +20083,31 @@ class WorkitCore {
20014
20083
  return changed;
20015
20084
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
20016
20085
  }
20086
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
20087
+ if (!workerRef.ok)
20088
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
20089
+ input.workerId = workerRef.data;
20017
20090
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
20018
20091
  if (!entry)
20019
20092
  return failure2("not_found", "worker not found");
20020
20093
  if (input.action === "report") {
20021
20094
  if (helperId === null || helperId !== input.workerId)
20022
- return failure2("permission_denied", "workers can submit only their own report");
20095
+ return failure2("permission_denied", ONLY_OWN_REPORT);
20023
20096
  const helper = this.helperEntry(task.data, true, true);
20024
20097
  if (!helper.ok)
20025
20098
  return helper;
20099
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
20100
+ const reported = [];
20101
+ for (const findingRef of input.report.findingIds) {
20102
+ const found = resolveIdRef("finding", findingRef, ownFindings);
20103
+ if (found.ok)
20104
+ reported.push(found.data);
20105
+ else if (found.code === "not_found")
20106
+ reported.push(findingRef);
20107
+ else
20108
+ return found;
20109
+ }
20110
+ input.report.findingIds = reported;
20026
20111
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
20027
20112
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
20028
20113
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -20050,7 +20135,7 @@ class WorkitCore {
20050
20135
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
20051
20136
  }
20052
20137
  if (helperId !== null)
20053
- return failure2("permission_denied", "helpers cannot cancel or control workers");
20138
+ return failure2("permission_denied", NO_HELPER_CONTROL);
20054
20139
  if (task.data.status === "closed")
20055
20140
  return failure2("invalid_transition", "closed task cannot cancel workers");
20056
20141
  if (entry.data.state === "stopped") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "private": false,
5
5
  "description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.14.0",
42
+ "@brainervirus/workit-core": "^8.15.0",
43
43
  "@opencode/plugin": "2.0.18"
44
44
  },
45
45
  "engines": {