@open-tomato/rafa 0.36.0 → 0.38.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.
@@ -3,9 +3,9 @@ import {
3
3
  DESCRIBE_BINARY,
4
4
  DESCRIBE_SCHEMA_VERSION,
5
5
  describeRegistry
6
- } from "./index-k6sd4ym5.js";
7
- import"./index-3mzw3kzh.js";
8
- import"./index-fzhd3d4y.js";
6
+ } from "./index-eyjq63mx.js";
7
+ import"./index-sr3kc4mf.js";
8
+ import"./index-m2ahxjw8.js";
9
9
  import"./index-wp8c28fd.js";
10
10
  export {
11
11
  describeRegistry,
@@ -14,13 +14,13 @@ import {
14
14
  openNdjsonStore,
15
15
  openSqliteStore,
16
16
  selectEffortStore
17
- } from "../../index-qvbeqrx3.js";
18
- import"../../index-jfas157k.js";
17
+ } from "../../index-74btmn1e.js";
18
+ import"../../index-w9naz372.js";
19
19
  import"../../index-d6d0942q.js";
20
- import"../../index-3mzw3kzh.js";
20
+ import"../../index-sr3kc4mf.js";
21
21
  import {
22
22
  STORE_BACKENDS
23
- } from "../../index-fzhd3d4y.js";
23
+ } from "../../index-m2ahxjw8.js";
24
24
  import"../../index-wp8c28fd.js";
25
25
  export {
26
26
  selectEffortStore,
@@ -15,7 +15,7 @@ import {
15
15
  parseConfigText,
16
16
  readRafaBlocks,
17
17
  resolveConfig
18
- } from "./index-jfas157k.js";
18
+ } from "./index-w9naz372.js";
19
19
  import {
20
20
  bless,
21
21
  merge
@@ -23,7 +23,7 @@ import {
23
23
  import {
24
24
  CommandExit,
25
25
  commandSpelling
26
- } from "./index-3mzw3kzh.js";
26
+ } from "./index-sr3kc4mf.js";
27
27
  import {
28
28
  BUSY_TIMEOUT_MAX_MS,
29
29
  BUSY_TIMEOUT_MIN_MS,
@@ -39,7 +39,7 @@ import {
39
39
  messageOf,
40
40
  resolveInstinctScopes,
41
41
  tierExists
42
- } from "./index-fzhd3d4y.js";
42
+ } from "./index-m2ahxjw8.js";
43
43
  import {
44
44
  actionHash,
45
45
  triggerKey
@@ -259,7 +259,7 @@ import { Database as Database2 } from "bun:sqlite";
259
259
 
260
260
  // package.json
261
261
  var name = "@open-tomato/rafa";
262
- var version = "0.36.0";
262
+ var version = "0.38.0";
263
263
 
264
264
  // src/cli/version.ts
265
265
  var RAFA_VERSION = version;
@@ -5120,6 +5120,62 @@ function createClaudePlanner(options) {
5120
5120
  return Object.freeze({ create });
5121
5121
  }
5122
5122
 
5123
+ // src/adapters/tracker/github-edit.ts
5124
+ var PREFIX2 = "github tracker";
5125
+ var EDITABLE_FIELDS = "title,body,state,labels,author";
5126
+ function viewedEditable(payload, labelNames) {
5127
+ if (!isMapping(payload))
5128
+ return `${describeValue(payload)}, expected a mapping`;
5129
+ const { title, body, state, author } = payload;
5130
+ if (typeof title !== "string")
5131
+ return `title ${describeValue(title)}, expected a string`;
5132
+ if (typeof body !== "string")
5133
+ return `body ${describeValue(body)}, expected a string`;
5134
+ if (state !== "OPEN" && state !== "CLOSED")
5135
+ return `state ${describeValue(state)}, expected "OPEN" or "CLOSED"`;
5136
+ const labels = labelNames(payload["labels"]);
5137
+ if (labels === null)
5138
+ return "labels that are not a list of named labels";
5139
+ const login = isMapping(author) ? author["login"] : undefined;
5140
+ if (typeof login !== "string")
5141
+ return `author ${describeValue(author)}, expected a mapping holding a string login`;
5142
+ return { title, body, open: state === "OPEN", labels, author: login };
5143
+ }
5144
+ function editProblem(change) {
5145
+ if (!isMapping(change))
5146
+ return `the change is ${describeValue(change)}, expected a mapping`;
5147
+ const { body, title } = change;
5148
+ if (body === undefined && title === undefined)
5149
+ return "the change names neither a body nor a title";
5150
+ if (body !== undefined && typeof body !== "string")
5151
+ return `body is ${describeValue(body)}, expected a string`;
5152
+ return title !== undefined && typeof title !== "string" ? `title is ${describeValue(title)}, expected a string` : null;
5153
+ }
5154
+ function optionalFlag(name2, value) {
5155
+ return value === undefined ? [] : [name2, value];
5156
+ }
5157
+ function createGithubEditMembers(readers) {
5158
+ const { run, issueNumberOf, parseJson, labelNames } = readers;
5159
+ const editable = async (ref) => {
5160
+ const number = issueNumberOf(ref);
5161
+ const command = `gh issue view ${number}`;
5162
+ const stdout = await run(["issue", "view", number, ...optionalFlag("--repo", ref.repo), "--json", EDITABLE_FIELDS], command);
5163
+ const viewed = viewedEditable(parseJson(stdout, command), labelNames);
5164
+ if (typeof viewed === "string")
5165
+ throw new Error(`${PREFIX2}: ${command} answered ${viewed}`);
5166
+ return { ref, ...viewed };
5167
+ };
5168
+ const edit = async (ref, change) => {
5169
+ const number = issueNumberOf(ref);
5170
+ const problem = editProblem(change);
5171
+ if (problem !== null)
5172
+ throw new TypeError(`${PREFIX2}: refused an edit of issue #${number}: ${problem}`);
5173
+ const bodyFlag = change.body === undefined ? [] : ["--body-file", "-"];
5174
+ await run(["issue", "edit", number, ...optionalFlag("--repo", ref.repo), ...optionalFlag("--title", change.title), ...bodyFlag], `gh issue edit ${number}`, change.body);
5175
+ };
5176
+ return Object.freeze({ editable, edit });
5177
+ }
5178
+
5123
5179
  // src/adapters/tracker/issue-values.ts
5124
5180
  var TYPE_MEMBERS = {
5125
5181
  code: true,
@@ -5151,7 +5207,7 @@ var ISSUE_PRIORITIES = Object.freeze(Object.keys(PRIORITY_MEMBERS));
5151
5207
  var ISSUE_STATES = Object.freeze(Object.keys(STATE_MEMBERS));
5152
5208
 
5153
5209
  // src/adapters/tracker/github.ts
5154
- var PREFIX2 = "github tracker";
5210
+ var PREFIX3 = "github tracker";
5155
5211
  var GITHUB_LABELS = Object.freeze({
5156
5212
  modulePrefix: "module:",
5157
5213
  typePrefix: "type:",
@@ -5181,7 +5237,7 @@ var REST_STATES = {
5181
5237
  };
5182
5238
  function createGhRunner(options) {
5183
5239
  const { cwd, command = "gh", env, timeoutMs } = options;
5184
- return async (args) => {
5240
+ return async (args, stdin) => {
5185
5241
  const executable = env === undefined ? command : Bun.which(command, { PATH: env.PATH ?? "", cwd });
5186
5242
  if (executable === null) {
5187
5243
  return { ok: false, stdout: "", stderr: notOnPath(command, cwd, env?.PATH) };
@@ -5190,7 +5246,7 @@ function createGhRunner(options) {
5190
5246
  const proc = Bun.spawn([executable, ...args], {
5191
5247
  cwd,
5192
5248
  ...env === undefined ? {} : { env: { ...env } },
5193
- stdin: "ignore",
5249
+ stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
5194
5250
  stdout: "pipe",
5195
5251
  stderr: "pipe"
5196
5252
  });
@@ -5245,7 +5301,7 @@ function isRepoName(value) {
5245
5301
  function isLabelPart(value) {
5246
5302
  return typeof value === "string" && !value.includes(",");
5247
5303
  }
5248
- function optionalFlag(name2, value) {
5304
+ function optionalFlag2(name2, value) {
5249
5305
  return value === undefined ? [] : [name2, value];
5250
5306
  }
5251
5307
  function detailOf(result) {
@@ -5288,13 +5344,13 @@ function labelsFor(draft) {
5288
5344
  }
5289
5345
  function issueNumberOfRef(ref) {
5290
5346
  if (ref.kind !== "github") {
5291
- throw new Error(`${PREFIX2}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads github refs only`);
5347
+ throw new Error(`${PREFIX3}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads github refs only`);
5292
5348
  }
5293
5349
  if (!isIssueNumber3(ref.externalId)) {
5294
- throw new Error(`${PREFIX2}: externalId ${describeValue(ref.externalId)} is not a GitHub issue number,` + " expected a positive whole number with no leading zero");
5350
+ throw new Error(`${PREFIX3}: externalId ${describeValue(ref.externalId)} is not a GitHub issue number,` + " expected a positive whole number with no leading zero");
5295
5351
  }
5296
5352
  if (ref.repo !== undefined && !isRepoName(ref.repo)) {
5297
- throw new Error(`${PREFIX2}: repo ${describeValue(ref.repo)} is not a repository, expected owner/name`);
5353
+ throw new Error(`${PREFIX3}: repo ${describeValue(ref.repo)} is not a repository, expected owner/name`);
5298
5354
  }
5299
5355
  return ref.externalId;
5300
5356
  }
@@ -5302,7 +5358,7 @@ function parseJson(stdout, command) {
5302
5358
  try {
5303
5359
  return JSON.parse(stdout);
5304
5360
  } catch (error) {
5305
- throw new Error(`${PREFIX2}: ${command} wrote output that is not JSON: ${messageOf(error)}`, {
5361
+ throw new Error(`${PREFIX3}: ${command} wrote output that is not JSON: ${messageOf(error)}`, {
5306
5362
  cause: error
5307
5363
  });
5308
5364
  }
@@ -5336,19 +5392,19 @@ function viewedIssue(stdout, number) {
5336
5392
  const payload = parseJson(stdout, command);
5337
5393
  const problem = viewProblem(payload, number);
5338
5394
  if (problem !== null)
5339
- throw new Error(`${PREFIX2}: ${command} answered ${problem}`);
5395
+ throw new Error(`${PREFIX3}: ${command} answered ${problem}`);
5340
5396
  const checked = payload;
5341
5397
  return { ...checked, labels: labelNames(checked.labels) ?? [] };
5342
5398
  }
5343
5399
  function listedRows(stdout) {
5344
5400
  const payload = parseJson(stdout, LIST_COMMAND);
5345
5401
  if (!Array.isArray(payload)) {
5346
- throw new Error(`${PREFIX2}: ${LIST_COMMAND} answered ${describeValue(payload)}, expected a list`);
5402
+ throw new Error(`${PREFIX3}: ${LIST_COMMAND} answered ${describeValue(payload)}, expected a list`);
5347
5403
  }
5348
5404
  return payload;
5349
5405
  }
5350
5406
  function rowRefusal(index, problem) {
5351
- return new Error(`${PREFIX2}: ${LIST_COMMAND} answered row ${index} ${problem}`);
5407
+ return new Error(`${PREFIX3}: ${LIST_COMMAND} answered row ${index} ${problem}`);
5352
5408
  }
5353
5409
  function listedRow(row, index) {
5354
5410
  if (!isMapping(row))
@@ -5421,15 +5477,15 @@ function readBackState(state) {
5421
5477
  function stateQueryRefusal(state) {
5422
5478
  const bucket = REST_STATES[state];
5423
5479
  const siblings = ISSUE_STATES.filter((other) => other !== state && REST_STATES[other] === bucket);
5424
- return `${PREFIX2}: find cannot narrow by state ${describeValue(state)}: GitHub holds an issue open or` + ` closed, and ${state} shares ${bucket} with ${siblings.join(", ")}, so a result would include` + " those too. Search without a state, then read each issue with get.";
5480
+ return `${PREFIX3}: find cannot narrow by state ${describeValue(state)}: GitHub holds an issue open or` + ` closed, and ${state} shares ${bucket} with ${siblings.join(", ")}, so a result would include` + " those too. Search without a state, then read each issue with get.";
5425
5481
  }
5426
5482
  function createGithubTracker(options) {
5427
5483
  const { gh } = options;
5428
5484
  let knownLabels = new Set;
5429
- async function run(args, command) {
5430
- const result = await gh(args);
5485
+ async function run(args, command, stdin) {
5486
+ const result = await gh(args, stdin);
5431
5487
  if (!result.ok)
5432
- throw new Error(`${PREFIX2}: ${command} failed: ${detailOf(result)}`);
5488
+ throw new Error(`${PREFIX3}: ${command} failed: ${detailOf(result)}`);
5433
5489
  return result.stdout;
5434
5490
  }
5435
5491
  async function ensureLabels(names) {
@@ -5462,7 +5518,7 @@ function createGithubTracker(options) {
5462
5518
  create: async (draft) => {
5463
5519
  const problem = draftProblem(draft);
5464
5520
  if (problem !== null)
5465
- throw new TypeError(`${PREFIX2}: refused to file an invalid draft: ${problem}`);
5521
+ throw new TypeError(`${PREFIX3}: refused to file an invalid draft: ${problem}`);
5466
5522
  const labels = labelsFor(draft);
5467
5523
  await ensureLabels(labels);
5468
5524
  const stdout = await run([
@@ -5478,20 +5534,20 @@ function createGithubTracker(options) {
5478
5534
  `).at(-1) ?? "";
5479
5535
  const number = CREATED_ISSUE_URL.exec(url)?.[1];
5480
5536
  if (number === undefined) {
5481
- throw new Error(`${PREFIX2}: gh issue create exited 0 and printed no issue URL, so the issue may exist` + ` unrecorded; it wrote ${describeValue(stdout)}`);
5537
+ throw new Error(`${PREFIX3}: gh issue create exited 0 and printed no issue URL, so the issue may exist` + ` unrecorded; it wrote ${describeValue(stdout)}`);
5482
5538
  }
5483
5539
  return { opt: draft.opt, kind: "github", externalId: number, url, module: draft.module };
5484
5540
  },
5485
5541
  get: async (ref) => {
5486
5542
  const number = issueNumberOfRef(ref);
5487
- const stdout = await run(["issue", "view", number, ...optionalFlag("--repo", ref.repo), "--json", VIEW_FIELDS], `gh issue view ${number}`);
5543
+ const stdout = await run(["issue", "view", number, ...optionalFlag2("--repo", ref.repo), "--json", VIEW_FIELDS], `gh issue view ${number}`);
5488
5544
  return issueOf(ref, viewedIssue(stdout, number));
5489
5545
  },
5490
5546
  find: async (query) => {
5491
5547
  if (query.state !== undefined)
5492
5548
  throw new Error(stateQueryRefusal(query.state));
5493
5549
  if (query.module !== undefined && !isLabelPart(query.module)) {
5494
- throw new TypeError(`${PREFIX2}: find refused module ${describeValue(query.module)}, expected a string holding no comma`);
5550
+ throw new TypeError(`${PREFIX3}: find refused module ${describeValue(query.module)}, expected a string holding no comma`);
5495
5551
  }
5496
5552
  const moduleLabel = query.module === undefined ? undefined : `${GITHUB_LABELS.modulePrefix}${query.module}`;
5497
5553
  const typeLabel = query.type === undefined ? undefined : `${GITHUB_LABELS.typePrefix}${query.type}`;
@@ -5504,15 +5560,15 @@ function createGithubTracker(options) {
5504
5560
  LIST_FIELDS,
5505
5561
  "--limit",
5506
5562
  String(query.limit ?? DEFAULT_FIND_LIMIT),
5507
- ...optionalFlag("--label", moduleLabel),
5508
- ...optionalFlag("--label", typeLabel),
5509
- ...optionalFlag("--search", query.text)
5563
+ ...optionalFlag2("--label", moduleLabel),
5564
+ ...optionalFlag2("--label", typeLabel),
5565
+ ...optionalFlag2("--search", query.text)
5510
5566
  ], LIST_COMMAND);
5511
5567
  return listedIssues(stdout).map(refOfRow);
5512
5568
  },
5513
5569
  openIssues: async (type) => {
5514
5570
  if (!isOneOf(ISSUE_TYPES, type)) {
5515
- throw new TypeError(`${PREFIX2}: openIssues refused type ${describeValue(type)}, expected one of: ${ISSUE_TYPES.join(", ")}`);
5571
+ throw new TypeError(`${PREFIX3}: openIssues refused type ${describeValue(type)}, expected one of: ${ISSUE_TYPES.join(", ")}`);
5516
5572
  }
5517
5573
  const stdout = await run([
5518
5574
  "issue",
@@ -5531,21 +5587,21 @@ function createGithubTracker(options) {
5531
5587
  comment: async (ref, body) => {
5532
5588
  const number = issueNumberOfRef(ref);
5533
5589
  if (typeof body !== "string") {
5534
- throw new TypeError(`${PREFIX2}: refused a comment body ${describeValue(body)}, expected a string`);
5590
+ throw new TypeError(`${PREFIX3}: refused a comment body ${describeValue(body)}, expected a string`);
5535
5591
  }
5536
- await run(["issue", "comment", number, ...optionalFlag("--repo", ref.repo), "--body", body], `gh issue comment ${number}`);
5592
+ await run(["issue", "comment", number, ...optionalFlag2("--repo", ref.repo), "--body", body], `gh issue comment ${number}`);
5537
5593
  },
5538
5594
  transition: async (ref, state) => {
5539
5595
  const number = issueNumberOfRef(ref);
5540
5596
  if (!isOneOf(ISSUE_STATES, state)) {
5541
- throw new TypeError(`${PREFIX2}: refused state ${describeValue(state)}, expected one of: ${ISSUE_STATES.join(", ")}`);
5597
+ throw new TypeError(`${PREFIX3}: refused state ${describeValue(state)}, expected one of: ${ISSUE_STATES.join(", ")}`);
5542
5598
  }
5543
5599
  if (REST_STATES[state] === "closed") {
5544
5600
  const path = `repos/${ref.repo ?? "{owner}/{repo}"}/issues/${number}`;
5545
5601
  const reason = state === "cancelled" ? "not_planned" : "completed";
5546
5602
  await run(["api", path, "-X", "PATCH", "-f", "state=closed", "-f", `state_reason=${reason}`], `gh api PATCH ${path}`);
5547
5603
  } else {
5548
- await run(["issue", "reopen", number, ...optionalFlag("--repo", ref.repo)], `gh issue reopen ${number}`);
5604
+ await run(["issue", "reopen", number, ...optionalFlag2("--repo", ref.repo)], `gh issue reopen ${number}`);
5549
5605
  }
5550
5606
  const readBack = readBackState(state);
5551
5607
  if (readBack === state)
@@ -5553,7 +5609,8 @@ function createGithubTracker(options) {
5553
5609
  return {
5554
5610
  warning: `issue #${number} is now ${REST_STATES[state]}, but GitHub Issues without a board` + ` holds no ${state} state, so get reads it back as ${readBack}`
5555
5611
  };
5556
- }
5612
+ },
5613
+ ...createGithubEditMembers({ run, issueNumberOf: issueNumberOfRef, parseJson, labelNames })
5557
5614
  };
5558
5615
  return Object.freeze(tracker);
5559
5616
  }
@@ -5561,7 +5618,7 @@ function createGithubTracker(options) {
5561
5618
  // src/adapters/tracker/local.ts
5562
5619
  import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
5563
5620
  import { join as join16 } from "path";
5564
- var PREFIX3 = "local tracker";
5621
+ var PREFIX4 = "local tracker";
5565
5622
  var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
5566
5623
  var ISSUE_NUMBER2 = /^[1-9]\d*$/;
5567
5624
  var CLOSED_STATES = new Set(["done", "released", "cancelled"]);
@@ -5659,7 +5716,7 @@ function renderLocalIssue(record) {
5659
5716
  };
5660
5717
  const problem = frontmatterProblem(frontmatter) ?? (typeof draft.body === "string" ? null : `body is ${describeValue(draft.body)}, expected a string`);
5661
5718
  if (problem !== null) {
5662
- throw new TypeError(`${PREFIX3}: refused to write an invalid issue: ${problem}`);
5719
+ throw new TypeError(`${PREFIX4}: refused to write an invalid issue: ${problem}`);
5663
5720
  }
5664
5721
  return `---
5665
5722
  ${Bun.YAML.stringify(frontmatter, null, 2)}
@@ -5670,18 +5727,18 @@ function parseLocalIssue(contents, sourcePath) {
5670
5727
  const at = sourcePath === undefined ? "" : ` at ${sourcePath}`;
5671
5728
  const match = FRONTMATTER.exec(contents);
5672
5729
  if (match === null) {
5673
- throw new Error(`${PREFIX3}: the issue${at} has no YAML frontmatter block`);
5730
+ throw new Error(`${PREFIX4}: the issue${at} has no YAML frontmatter block`);
5674
5731
  }
5675
5732
  const [, block = "", body = ""] = match;
5676
5733
  let meta;
5677
5734
  try {
5678
5735
  meta = Bun.YAML.parse(block);
5679
5736
  } catch (error) {
5680
- throw new Error(`${PREFIX3}: invalid issue${at}: ${messageOf(error)}`, { cause: error });
5737
+ throw new Error(`${PREFIX4}: invalid issue${at}: ${messageOf(error)}`, { cause: error });
5681
5738
  }
5682
5739
  const problem = frontmatterProblem(meta);
5683
5740
  if (problem !== null) {
5684
- throw new Error(`${PREFIX3}: invalid issue${at}: ${problem}`);
5741
+ throw new Error(`${PREFIX4}: invalid issue${at}: ${problem}`);
5685
5742
  }
5686
5743
  const fields = meta;
5687
5744
  return {
@@ -5713,7 +5770,7 @@ async function listLocalIssues(issuesDir, warn = warnThroughActiveOutput2, reade
5713
5770
  try {
5714
5771
  contents = await readFile3(path, "utf8");
5715
5772
  } catch (error) {
5716
- throw new Error(`${PREFIX3}: could not read ${path}: ${messageOf(error)}`, { cause: error });
5773
+ throw new Error(`${PREFIX4}: could not read ${path}: ${messageOf(error)}`, { cause: error });
5717
5774
  }
5718
5775
  return { number, path, record: parseLocalIssue(contents, path) };
5719
5776
  }));
@@ -5742,11 +5799,11 @@ function refFor(number, opt) {
5742
5799
  }
5743
5800
  function issueNumberOfRef2(ref) {
5744
5801
  if (ref.kind !== "local") {
5745
- throw new Error(`${PREFIX3}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads local refs only`);
5802
+ throw new Error(`${PREFIX4}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads local refs only`);
5746
5803
  }
5747
5804
  const number = typeof ref.externalId === "string" ? parseIssueNumber(ref.externalId) : null;
5748
5805
  if (number === null) {
5749
- throw new Error(`${PREFIX3}: externalId ${describeValue(ref.externalId)} is not a local issue number,` + " expected a positive whole number with no leading zero");
5806
+ throw new Error(`${PREFIX4}: externalId ${describeValue(ref.externalId)} is not a local issue number,` + " expected a positive whole number with no leading zero");
5750
5807
  }
5751
5808
  return number;
5752
5809
  }
@@ -5761,7 +5818,7 @@ function createLocalTracker(options) {
5761
5818
  try {
5762
5819
  contents = await readFile3(path, "utf8");
5763
5820
  } catch (cause) {
5764
- throw new Error(`${PREFIX3}: no issue ${number} under ${issuesDir}`, { cause });
5821
+ throw new Error(`${PREFIX4}: no issue ${number} under ${issuesDir}`, { cause });
5765
5822
  }
5766
5823
  return { number, record: parseLocalIssue(contents, path) };
5767
5824
  }
@@ -5796,7 +5853,7 @@ function createLocalTracker(options) {
5796
5853
  throw error;
5797
5854
  }
5798
5855
  }
5799
- throw new Error(`${PREFIX3}: no issue number is left under ${issuesDir}`);
5856
+ throw new Error(`${PREFIX4}: no issue number is left under ${issuesDir}`);
5800
5857
  },
5801
5858
  get: async (ref) => {
5802
5859
  const { record } = await read(ref);
@@ -5822,6 +5879,29 @@ function createLocalTracker(options) {
5822
5879
  const { number, record } = await read(ref);
5823
5880
  await rewrite(number, { ...record, state });
5824
5881
  return {};
5882
+ },
5883
+ editable: async (ref) => {
5884
+ const { number, record } = await read(ref);
5885
+ return {
5886
+ ref: refFor(number, record.draft.opt),
5887
+ title: record.draft.title,
5888
+ body: record.draft.body,
5889
+ open: !CLOSED_STATES.has(record.state),
5890
+ labels: [],
5891
+ author: ""
5892
+ };
5893
+ },
5894
+ edit: async (ref, change) => {
5895
+ if (change.body === undefined && change.title === undefined) {
5896
+ throw new TypeError(`${PREFIX4}: refused an edit naming neither a body nor a title`);
5897
+ }
5898
+ const { number, record } = await read(ref);
5899
+ const draft = {
5900
+ ...record.draft,
5901
+ ...change.body === undefined ? {} : { body: change.body },
5902
+ ...change.title === undefined ? {} : { title: change.title }
5903
+ };
5904
+ await rewrite(number, { ...record, draft });
5825
5905
  }
5826
5906
  };
5827
5907
  return Object.freeze(tracker);
@@ -6110,6 +6190,60 @@ function requireTrustedAuthor(item, reading) {
6110
6190
  return;
6111
6191
  throw new CommandExit(TRUST_REFUSAL_EXIT, trustRefusalMessage(item, reading));
6112
6192
  }
6193
+ var USER_COMMAND = "gh api user";
6194
+ function noLogin(detail) {
6195
+ return { login: null, detail };
6196
+ }
6197
+ function createGhEditorLogin(options) {
6198
+ const { gh } = options;
6199
+ return async () => {
6200
+ const result = await gh(["api", "user"]);
6201
+ if (!result.ok)
6202
+ return noLogin(detailOf2(result, USER_COMMAND));
6203
+ let payload;
6204
+ try {
6205
+ payload = JSON.parse(result.stdout);
6206
+ } catch (error) {
6207
+ return noLogin(`${USER_COMMAND} wrote output that is not JSON: ${messageOf(error)}`);
6208
+ }
6209
+ if (!isMapping(payload)) {
6210
+ return noLogin(`${USER_COMMAND} answered ${describeValue(payload)}, expected a mapping`);
6211
+ }
6212
+ const login = payload["login"];
6213
+ return typeof login === "string" && isGitHubLogin(login) ? { login, detail: "" } : noLogin(`${USER_COMMAND} answered ${describeValue(login)} as its login, which is not a GitHub login`);
6214
+ };
6215
+ }
6216
+ async function readEditorTrust(trust, editor) {
6217
+ const read = await editor();
6218
+ if (read.login === null) {
6219
+ return { login: null, trusted: false, refusal: "ownership-unknown", reading: null, detail: read.detail };
6220
+ }
6221
+ const reading = await readBoardTrust(trust, read.login);
6222
+ return {
6223
+ login: read.login,
6224
+ trusted: reading.trusted,
6225
+ refusal: editorRefusalOf(reading),
6226
+ reading,
6227
+ detail: ""
6228
+ };
6229
+ }
6230
+ function editorRefusalOf(reading) {
6231
+ if (reading.trusted)
6232
+ return null;
6233
+ return reading.refusal === "lookup-failed" ? "ownership-unknown" : "ownership";
6234
+ }
6235
+ function editorRefusalMessage(item, reading) {
6236
+ const what = `${item.kind} #${String(item.number)}`;
6237
+ if (reading.reading !== null) {
6238
+ const because = trustRefusalClause(item.repo, reading.reading);
6239
+ return `${what} cannot be edited by ${reading.reading.login}, ${because}; a member must make the edit`;
6240
+ }
6241
+ if (reading.trusted) {
6242
+ throw new TypeError("board trust: a trusted editor has no refusal to name");
6243
+ }
6244
+ const detail = reading.detail === "" ? UNREPORTED : reading.detail;
6245
+ return `${what} cannot be edited: the account running the edit could not be read (${detail}); ` + "log gh in with `gh auth login` and run it again";
6246
+ }
6113
6247
  // src/effort/sync/wire.ts
6114
6248
  import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync8 } from "fs";
6115
6249
  import { tmpdir as tmpdir4 } from "os";
@@ -6363,4 +6497,4 @@ function selectEffortStore(repoRoot, config) {
6363
6497
  return adapter.create({ repoRoot });
6364
6498
  }
6365
6499
 
6366
- export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess, EFFORT_KEY_PROJECTIONS, openNdjsonStore, name, version, RAFA_VERSION, versionLine, createGitRunner, gitSaid, NOT_A_REPOSITORY, mainCheckoutOf, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, POSITION_FILE, positionFilePath, readPositionFile, writePositionFile, positionAt, rehome, hop, goHome, HOP_FILE, hopFilePath, readHopRecord, writeHopRecord, staleAgainst, SessionRecordError, isSessionId, sessionPhase, SessionConflictError, SessionStateError, runsDir, sessionFilePath, errorCode, isPidAlive, readSessions, readSession, beginSession, updateSession, DEVELOPMENT_NEXT_STEP, DEVELOPMENT_MARK, DevelopmentBuildRefusedError, refuseUnownedDevelopmentWrite, LEGACY_GATE_OPEN, LEGACY_GATE_CLOSED, SQLITE_MIGRATIONS, sqliteCatalogue, planSchema, SchemaRefusedError, readStoreSchema, writeNames, ACROSS_STORES_ORDER, STAMPED_COLUMNS, stampedValues, activeStoreSettings, normalizeRemote, gitRemoteUrl, readStoreMeta, SQLITE_STORE_FILE_NAME, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, EffortCopyRefusal, EffortCopyFailure, copyEffortStore, RebuildRefusal, readConfigFile, loadConfig, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, stubOfPlanFile, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, fileStamp, keepsIdLine, fix_schema_default, minutesBetween, readCommitLog, UnionSchemaMismatch, migrateStore, MOVE_TO_SQLITE, MergeRefusal, mergeStore, createFileSync, SyncModuleMissing, selectSync, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, EVENT_PREFIX, padKind, oneLine, sessionSpawnEnv, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, spawnClaude, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, TRUSTED_PERMISSIONS, createGhPermissions, readAuthorTrust, ghBoardTrust, readBoardTrust, requireTrustedBoardAuthor, trustRefusalClause, requireTrustedAuthor, WIRE_FORMAT, WIRE_VERSION, WireFormatError, WireExportRefusal, encodeWirePayload, decodeWirePayload, exportWirePayload, materialiseWirePayload, selectEffortStore };
6500
+ export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess, EFFORT_KEY_PROJECTIONS, openNdjsonStore, name, version, RAFA_VERSION, versionLine, createGitRunner, gitSaid, NOT_A_REPOSITORY, mainCheckoutOf, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, POSITION_FILE, positionFilePath, readPositionFile, writePositionFile, positionAt, rehome, hop, goHome, HOP_FILE, hopFilePath, readHopRecord, writeHopRecord, staleAgainst, SessionRecordError, isSessionId, sessionPhase, SessionConflictError, SessionStateError, runsDir, sessionFilePath, errorCode, isPidAlive, readSessions, readSession, beginSession, updateSession, DEVELOPMENT_NEXT_STEP, DEVELOPMENT_MARK, DevelopmentBuildRefusedError, refuseUnownedDevelopmentWrite, LEGACY_GATE_OPEN, LEGACY_GATE_CLOSED, SQLITE_MIGRATIONS, sqliteCatalogue, planSchema, SchemaRefusedError, readStoreSchema, writeNames, ACROSS_STORES_ORDER, STAMPED_COLUMNS, stampedValues, activeStoreSettings, normalizeRemote, gitRemoteUrl, readStoreMeta, SQLITE_STORE_FILE_NAME, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, EffortCopyRefusal, EffortCopyFailure, copyEffortStore, RebuildRefusal, readConfigFile, loadConfig, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, stubOfPlanFile, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, fileStamp, keepsIdLine, fix_schema_default, minutesBetween, readCommitLog, UnionSchemaMismatch, migrateStore, MOVE_TO_SQLITE, MergeRefusal, mergeStore, createFileSync, SyncModuleMissing, selectSync, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, EVENT_PREFIX, padKind, oneLine, sessionSpawnEnv, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, spawnClaude, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, TRUSTED_PERMISSIONS, TRUST_REFUSAL_EXIT, createGhPermissions, readAuthorTrust, ghBoardTrust, readBoardTrust, requireTrustedBoardAuthor, trustRefusalClause, trustRefusalMessage, requireTrustedAuthor, createGhEditorLogin, readEditorTrust, editorRefusalMessage, WIRE_FORMAT, WIRE_VERSION, WireFormatError, WireExportRefusal, encodeWirePayload, decodeWirePayload, exportWirePayload, materialiseWirePayload, selectEffortStore };