@brainervirus/workit-pi 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.
package/dist/worker.js CHANGED
@@ -7546,6 +7546,8 @@ var text = string2().check((ctx) => {
7546
7546
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7547
7547
  });
7548
7548
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7549
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7550
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7549
7551
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7550
7552
  var revision = text.regex(uuidPattern, "expected UUID revision");
7551
7553
  var validUtc = (value) => {
@@ -7825,7 +7827,7 @@ var workerReportSchema = object({
7825
7827
  outcome: _enum(["completed", "failed", "cancelled"]),
7826
7828
  summary: text,
7827
7829
  evidenceIds: array(id),
7828
- findingIds: array(id)
7830
+ findingIds: array(idRef)
7829
7831
  }).strict();
7830
7832
  var workerSchema = object({
7831
7833
  assignment: assignmentSchema,
@@ -8018,7 +8020,7 @@ var findingOperations = {
8018
8020
  action: literal("resolve"),
8019
8021
  ...taskId,
8020
8022
  expectedRevision: revision.optional(),
8021
- findingId: id,
8023
+ findingId: idRef,
8022
8024
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8023
8025
  reason: text,
8024
8026
  evidenceIds: array(id),
@@ -8059,7 +8061,7 @@ var workerOperations = {
8059
8061
  ...taskId,
8060
8062
  expectedRevision: revision.optional(),
8061
8063
  expectedWorkspaceRevision: revision.optional(),
8062
- workerId: id,
8064
+ workerId: idRef,
8063
8065
  report: workerReportSchema
8064
8066
  }),
8065
8067
  cancel: operation({
@@ -8067,7 +8069,7 @@ var workerOperations = {
8067
8069
  ...taskId,
8068
8070
  ...revisions,
8069
8071
  expectedWorkspaceRevision: revision.optional(),
8070
- workerId: id,
8072
+ workerId: idRef,
8071
8073
  reason: text
8072
8074
  })
8073
8075
  };
@@ -8544,20 +8546,19 @@ var workspaceConfigSchema = object({
8544
8546
  union([boolean2(), literal("verified")]).optional()
8545
8547
  ]))).passthrough().optional(),
8546
8548
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
8547
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
8549
+ issues: object({
8550
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
8551
+ link: boolean2().optional(),
8552
+ relation: unknown().optional(),
8553
+ backlink: boolean2().optional(),
8554
+ link_on_pr: boolean2().optional()
8555
+ }).passthrough().optional(),
8548
8556
  branchPolicy: workspaceBranchPolicySchema.optional(),
8549
8557
  commitPolicy: workspaceCommitPolicySchema.optional(),
8550
8558
  defaultProfile: nonBlank.optional(),
8551
8559
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
8552
8560
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
8553
8561
  }).passthrough().superRefine((workspace, context) => {
8554
- if (workspace.issues && workspace.vcs?.provider !== "github") {
8555
- context.addIssue({
8556
- code: "custom",
8557
- path: ["issues"],
8558
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
8559
- });
8560
- }
8561
8562
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
8562
8563
  context.addIssue({
8563
8564
  code: "custom",
@@ -8611,11 +8612,19 @@ var KNOWN_FIELDS = new Set([
8611
8612
  ]);
8612
8613
  var memo2 = new Map;
8613
8614
 
8615
+ // packages/workit-core/src/core/vcs-config.ts
8616
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
8617
+
8614
8618
  // packages/workit-core/src/ledger.ts
8615
8619
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
8616
8620
  var FAILING = new Set(["failed", "blocked"]);
8617
8621
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
8618
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
8622
+ var PR_ROW_TYPES = new Set([
8623
+ "pr.created",
8624
+ "pr.status",
8625
+ "pr.merged",
8626
+ "pr.linked"
8627
+ ]);
8619
8628
  var NETWORK_FS_TYPES = new Set([
8620
8629
  26985,
8621
8630
  20859,
package/dist/workit.js CHANGED
@@ -7771,6 +7771,8 @@ var text = string2().check((ctx) => {
7771
7771
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7772
7772
  });
7773
7773
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7774
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7775
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7774
7776
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7775
7777
  var revision = text.regex(uuidPattern, "expected UUID revision");
7776
7778
  var validUtc = (value) => {
@@ -8148,7 +8150,7 @@ var workerReportSchema = object({
8148
8150
  outcome: _enum(["completed", "failed", "cancelled"]),
8149
8151
  summary: text,
8150
8152
  evidenceIds: array(id),
8151
- findingIds: array(id)
8153
+ findingIds: array(idRef)
8152
8154
  }).strict();
8153
8155
  var workerSchema = object({
8154
8156
  assignment: assignmentSchema,
@@ -8394,7 +8396,7 @@ var findingOperations = {
8394
8396
  action: literal("resolve"),
8395
8397
  ...taskId,
8396
8398
  expectedRevision: revision.optional(),
8397
- findingId: id,
8399
+ findingId: idRef,
8398
8400
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8399
8401
  reason: text,
8400
8402
  evidenceIds: array(id),
@@ -8435,7 +8437,7 @@ var workerOperations = {
8435
8437
  ...taskId,
8436
8438
  expectedRevision: revision.optional(),
8437
8439
  expectedWorkspaceRevision: revision.optional(),
8438
- workerId: id,
8440
+ workerId: idRef,
8439
8441
  report: workerReportSchema
8440
8442
  }),
8441
8443
  cancel: operation({
@@ -8443,7 +8445,7 @@ var workerOperations = {
8443
8445
  ...taskId,
8444
8446
  ...revisions,
8445
8447
  expectedWorkspaceRevision: revision.optional(),
8446
- workerId: id,
8448
+ workerId: idRef,
8447
8449
  reason: text
8448
8450
  })
8449
8451
  };
@@ -8493,6 +8495,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8493
8495
  data
8494
8496
  });
8495
8497
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8498
+ function resolveIdRef(kind, ref, ids) {
8499
+ if (ids.includes(ref))
8500
+ return success(null, null, ref);
8501
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8502
+ if (matches.length === 1)
8503
+ return success(null, null, matches[0]);
8504
+ if (matches.length === 0)
8505
+ return failure2("not_found", `${kind} not found`);
8506
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8507
+ }
8496
8508
  function parseOperation(family, input) {
8497
8509
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8498
8510
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -10022,7 +10034,7 @@ var removeIfEmpty = (dir) => {
10022
10034
  };
10023
10035
  var tokenHash = (token) => createHash3("sha256").update(token).digest("hex");
10024
10036
  function recordIssuedSession(cwd, lead, issued) {
10025
- const token = randomBytes2(18).toString("base64url");
10037
+ const token = randomBytes2(18).toString("hex");
10026
10038
  try {
10027
10039
  migrateRepoIssued(cwd);
10028
10040
  const own = hash(issued);
@@ -11036,20 +11048,26 @@ class TaskStore {
11036
11048
  return success(null, null, { key: key.data, bound: bound.data });
11037
11049
  }
11038
11050
  taskForWorker(workerId) {
11039
- if (typeof workerId !== "string" || !validId(workerId))
11051
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
11040
11052
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
11041
11053
  const listed = this.listTaskIndex();
11042
11054
  if (!listed.ok)
11043
11055
  return listed;
11056
+ const owners = new Map;
11044
11057
  for (const entry of listed.data) {
11045
11058
  if (entry.status === "closed")
11046
11059
  continue;
11047
11060
  const loaded = this.load(entry.id);
11048
11061
  if (!loaded.ok)
11049
11062
  return loaded;
11050
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
11051
- return success(null, null, entry.id);
11063
+ for (const worker of loaded.data?.record.workers ?? [])
11064
+ owners.set(worker.id, entry.id);
11052
11065
  }
11066
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
11067
+ if (resolved.ok)
11068
+ return success(null, null, owners.get(resolved.data) ?? null);
11069
+ if (resolved.code !== "not_found")
11070
+ return resolved;
11053
11071
  return success(null, null, null);
11054
11072
  }
11055
11073
  originTaskEntry() {
@@ -13174,20 +13192,19 @@ var workspaceConfigSchema = object({
13174
13192
  union([boolean2(), literal("verified")]).optional()
13175
13193
  ]))).passthrough().optional(),
13176
13194
  youtrack: object({ baseUrl: nonBlank.optional(), link_issues: boolean2().optional() }).passthrough().optional(),
13177
- issues: object({ provider: literal("github").optional(), link_on_pr: boolean2().optional() }).passthrough().optional(),
13195
+ issues: object({
13196
+ provider: _enum(["github", "gitlab", "youtrack"]).optional(),
13197
+ link: boolean2().optional(),
13198
+ relation: unknown().optional(),
13199
+ backlink: boolean2().optional(),
13200
+ link_on_pr: boolean2().optional()
13201
+ }).passthrough().optional(),
13178
13202
  branchPolicy: workspaceBranchPolicySchema.optional(),
13179
13203
  commitPolicy: workspaceCommitPolicySchema.optional(),
13180
13204
  defaultProfile: nonBlank.optional(),
13181
13205
  profiles: record(nonBlank, workspaceProfileSchema).optional(),
13182
13206
  releaseTracks: record(nonBlank, releaseTrackSchema).optional()
13183
13207
  }).passthrough().superRefine((workspace, context) => {
13184
- if (workspace.issues && workspace.vcs?.provider !== "github") {
13185
- context.addIssue({
13186
- code: "custom",
13187
- path: ["issues"],
13188
- message: `GitHub issue linking requires the github provider, got ${workspace.vcs?.provider ?? "unset"}`
13189
- });
13190
- }
13191
13208
  if (workspace.defaultProfile !== undefined && !Object.hasOwn(workspace.profiles ?? {}, workspace.defaultProfile)) {
13192
13209
  context.addIssue({
13193
13210
  code: "custom",
@@ -14062,6 +14079,44 @@ var readVcsConfig = () => {
14062
14079
  }
14063
14080
  return { status: "malformed", path: file, config: {}, error: `${file} is not a JSON object` };
14064
14081
  };
14082
+ var TRACKERS = new Set(["github", "gitlab", "youtrack"]);
14083
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
14084
+ var flag = (value) => typeof value === "boolean" ? value : null;
14085
+ var text2 = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
14086
+ function issueTrackerOf(issues, youtrack) {
14087
+ const iss = asRecord(issues);
14088
+ const yt = asRecord(youtrack);
14089
+ const warnings = [];
14090
+ const named = typeof iss.provider === "string" && TRACKERS.has(iss.provider.toLowerCase()) ? iss.provider.toLowerCase() : null;
14091
+ if (iss.relation !== undefined && iss.relation !== "closes" && iss.relation !== "refs")
14092
+ warnings.push(`issues.relation ${JSON.stringify(iss.relation)} is not closes or refs; read as closes`);
14093
+ for (const key of ["repo", "host"])
14094
+ if (iss[key] !== undefined && text2(iss[key]) === null)
14095
+ warnings.push(`issues.${key} is not a non-empty string; ignored`);
14096
+ const common = {
14097
+ relation: iss.relation === "refs" ? "refs" : "closes",
14098
+ backlink: iss.backlink === true,
14099
+ baseUrl: text2(yt.baseUrl),
14100
+ repo: text2(iss.repo)?.replace(/^\/+|\/+$/gu, "") ?? null,
14101
+ host: text2(iss.host)?.toLowerCase() ?? null,
14102
+ warnings
14103
+ };
14104
+ const legacyGithub = named === "github" && flag(iss.link) === null;
14105
+ if (flag(yt.link_issues) === true && (named === null || legacyGithub))
14106
+ return { provider: "youtrack", link: true, ...common, source: "youtrack.link_issues" };
14107
+ if (named) {
14108
+ const link = flag(iss.link) ?? flag(iss.link_on_pr) ?? (named === "youtrack" ? flag(yt.link_issues) : null) ?? false;
14109
+ return {
14110
+ provider: named,
14111
+ link,
14112
+ ...common,
14113
+ source: flag(iss.link) === null && flag(iss.link_on_pr) !== null ? "issues.link_on_pr" : "issues"
14114
+ };
14115
+ }
14116
+ if (common.baseUrl)
14117
+ return { provider: "youtrack", link: false, ...common, source: "youtrack.baseUrl" };
14118
+ return null;
14119
+ }
14065
14120
  var trackSummary = (resolution) => resolution.status === "resolved" ? {
14066
14121
  name: resolution.track?.name ?? null,
14067
14122
  source: resolution.source,
@@ -14082,6 +14137,7 @@ function vcsConfig(mode, cwd, options = {}) {
14082
14137
  const rawProvider = wsVcs.provider ?? (determinateRoot ? remoteProvider(root) : null) ?? cfg.provider ?? null;
14083
14138
  const provider = rawProvider === null || rawProvider === undefined ? null : String(rawProvider).toLowerCase() || null;
14084
14139
  const workspaceDefault = workspaceDefaultTarget(root, cfg.defaultTargetBranch);
14140
+ const issueTracker = issueTrackerOf(wsIssues, wsYt);
14085
14141
  const linkIssues = typeof wsYt.link_issues === "boolean" ? wsYt.link_issues : null;
14086
14142
  const youtrackBaseUrl = typeof wsYt.baseUrl === "string" ? wsYt.baseUrl : null;
14087
14143
  let issuesProvider = null;
@@ -14114,7 +14170,8 @@ function vcsConfig(mode, cwd, options = {}) {
14114
14170
  link_issues: linkIssues,
14115
14171
  youtrack_base_url: youtrackBaseUrl,
14116
14172
  issues_provider: issuesProvider,
14117
- link_on_pr: linkOnPr
14173
+ link_on_pr: linkOnPr,
14174
+ issue_tracker: issueTracker
14118
14175
  };
14119
14176
  }
14120
14177
  if (cfgStatus === "malformed") {
@@ -14140,7 +14197,8 @@ function vcsConfig(mode, cwd, options = {}) {
14140
14197
  link_issues: linkIssues,
14141
14198
  youtrack_base_url: youtrackBaseUrl,
14142
14199
  issues_provider: issuesProvider,
14143
- link_on_pr: linkOnPr
14200
+ link_on_pr: linkOnPr,
14201
+ issue_tracker: issueTracker
14144
14202
  };
14145
14203
  if (provider === "gitlab") {
14146
14204
  out.gitlab = {
@@ -14244,7 +14302,12 @@ var MAX_LINE_BYTES = 4096;
14244
14302
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
14245
14303
  var FAILING = new Set(["failed", "blocked"]);
14246
14304
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
14247
- var PR_ROW_TYPES = new Set(["pr.created", "pr.status", "pr.merged"]);
14305
+ var PR_ROW_TYPES = new Set([
14306
+ "pr.created",
14307
+ "pr.status",
14308
+ "pr.merged",
14309
+ "pr.linked"
14310
+ ]);
14248
14311
  var err = (code, error, unblock) => ({
14249
14312
  ok: false,
14250
14313
  code,
@@ -18572,7 +18635,7 @@ var RISK = {
18572
18635
  };
18573
18636
  var YES = new Set(["true", "yes", "y", "1", "on"]);
18574
18637
  var NO = new Set(["false", "no", "n", "0", "off", "none"]);
18575
- var flag = (value, unknownAs) => {
18638
+ var flag2 = (value, unknownAs) => {
18576
18639
  if (typeof value === "boolean")
18577
18640
  return { ok: true, value };
18578
18641
  if (typeof value === "number" && (value === 0 || value === 1))
@@ -18638,12 +18701,12 @@ function normalizeJudgment(raw, previous) {
18638
18701
  const items = Array.isArray(value) ? value : [value];
18639
18702
  judgment.refs.push(...items.filter((item) => item !== null && item !== "").map(refOf));
18640
18703
  } else {
18641
- if (name === "needsPlan" && typeof value === "string" && !flag(value, false).ok && value.trim()) {
18704
+ if (name === "needsPlan" && typeof value === "string" && !flag2(value, false).ok && value.trim()) {
18642
18705
  judgment.needsPlan = true;
18643
18706
  judgment.refs.push(refOf(value.trim()));
18644
18707
  continue;
18645
18708
  }
18646
- const parsed = flag(value, name === "behaviorChange");
18709
+ const parsed = flag2(value, name === "behaviorChange");
18647
18710
  if (parsed.ok)
18648
18711
  judgment[name] = parsed.value;
18649
18712
  else
@@ -18998,6 +19061,8 @@ var TASK_SCOPED_ACTIONS = new Set([
18998
19061
  "worker.cancel",
18999
19062
  "state.export"
19000
19063
  ]);
19064
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
19065
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
19001
19066
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
19002
19067
  var workerCommand = (taskId, request) => {
19003
19068
  const { action, workerId, reason, report } = request;
@@ -19895,6 +19960,10 @@ class WorkitCore {
19895
19960
  }
19896
19961
  if (helper?.ok)
19897
19962
  return failure2("permission_denied", "helpers cannot resolve findings");
19963
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
19964
+ if (!findingRef.ok)
19965
+ return findingRef;
19966
+ input.findingId = findingRef.data;
19898
19967
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
19899
19968
  if (!finding)
19900
19969
  return failure2("not_found", "finding not found");
@@ -20046,15 +20115,31 @@ class WorkitCore {
20046
20115
  return changed;
20047
20116
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
20048
20117
  }
20118
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
20119
+ if (!workerRef.ok)
20120
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
20121
+ input.workerId = workerRef.data;
20049
20122
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
20050
20123
  if (!entry)
20051
20124
  return failure2("not_found", "worker not found");
20052
20125
  if (input.action === "report") {
20053
20126
  if (helperId === null || helperId !== input.workerId)
20054
- return failure2("permission_denied", "workers can submit only their own report");
20127
+ return failure2("permission_denied", ONLY_OWN_REPORT);
20055
20128
  const helper = this.helperEntry(task.data, true, true);
20056
20129
  if (!helper.ok)
20057
20130
  return helper;
20131
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
20132
+ const reported = [];
20133
+ for (const findingRef of input.report.findingIds) {
20134
+ const found = resolveIdRef("finding", findingRef, ownFindings);
20135
+ if (found.ok)
20136
+ reported.push(found.data);
20137
+ else if (found.code === "not_found")
20138
+ reported.push(findingRef);
20139
+ else
20140
+ return found;
20141
+ }
20142
+ input.report.findingIds = reported;
20058
20143
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
20059
20144
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
20060
20145
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -20082,7 +20167,7 @@ class WorkitCore {
20082
20167
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
20083
20168
  }
20084
20169
  if (helperId !== null)
20085
- return failure2("permission_denied", "helpers cannot cancel or control workers");
20170
+ return failure2("permission_denied", NO_HELPER_CONTROL);
20086
20171
  if (task.data.status === "closed")
20087
20172
  return failure2("invalid_transition", "closed task cannot cancel workers");
20088
20173
  if (entry.data.state === "stopped") {
@@ -23779,7 +23864,10 @@ var readAssignment = (ctx, request) => {
23779
23864
  return failure2("permission_denied", "worker assignment is not bound to this workspace");
23780
23865
  if (task.data.status !== "active")
23781
23866
  return failure2("invalid_transition", "paused or closed tasks cannot supervise workers");
23782
- const worker = task.data.workers.find((candidate) => candidate.id === request.workerId);
23867
+ const workerId = resolveIdRef("worker", request.workerId, task.data.workers.map((candidate) => candidate.id));
23868
+ if (!workerId.ok)
23869
+ return workerId;
23870
+ const worker = task.data.workers.find((candidate) => candidate.id === workerId.data);
23783
23871
  if (!worker)
23784
23872
  return failure2("not_found", "worker assignment not found");
23785
23873
  return success(null, workspace.data.revision, {
@@ -23926,7 +24014,7 @@ function extension(pi) {
23926
24014
  if (!validated.ok)
23927
24015
  return validated;
23928
24016
  const { store, task, workspace, worker } = validated.data;
23929
- const current = [...workers.values()].find((handle) => handle.workerId === request.workerId);
24017
+ const current = [...workers.values()].find((handle) => handle.workerId === worker.id);
23930
24018
  if (request.action === "cancel") {
23931
24019
  if (!current)
23932
24020
  return failure2("recovery_required", "worker process is not live in this session");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "8.14.0",
3
+ "version": "8.15.0",
4
4
  "description": "Workit native extension for Pi — tools, method skills, and session continuity",
5
5
  "keywords": [
6
6
  "agentic-coding",
@@ -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