@open-tomato/rafa 0.12.0 → 0.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.
@@ -4,10 +4,10 @@ import {
4
4
  openNdjsonStore,
5
5
  openSqliteStore,
6
6
  selectEffortStore
7
- } from "../../index-4g6vyznd.js";
7
+ } from "../../index-1x5t4t9r.js";
8
8
  import {
9
9
  STORE_BACKENDS
10
- } from "../../index-96pz9023.js";
10
+ } from "../../index-2256nwj0.js";
11
11
  export {
12
12
  selectEffortStore,
13
13
  openSqliteStore,
@@ -8,7 +8,7 @@ import {
8
8
  isMapping,
9
9
  messageOf,
10
10
  readRafaBlocks
11
- } from "./index-96pz9023.js";
11
+ } from "./index-2256nwj0.js";
12
12
 
13
13
  // src/effort/store.ts
14
14
  import {
@@ -867,6 +867,14 @@ function runningCommand() {
867
867
  return running;
868
868
  }
869
869
 
870
+ // src/utils/session-env.ts
871
+ function sessionSpawnEnv(base) {
872
+ return {
873
+ ...base,
874
+ CLAUDE_CODE_ENTRYPOINT: "cli"
875
+ };
876
+ }
877
+
870
878
  // src/utils/claude.ts
871
879
  async function getClaudeUsagePercent() {
872
880
  const envPct = process.env["CLAUDE_USAGE_PERCENT"];
@@ -912,12 +920,6 @@ function interruptClaudeSessions() {
912
920
  session.kill("SIGINT");
913
921
  return liveSessions.size;
914
922
  }
915
- function claudeSessionEnv() {
916
- return {
917
- ...process.env,
918
- CLAUDE_CODE_ENTRYPOINT: "cli"
919
- };
920
- }
921
923
 
922
924
  class UndeclaredSpendError extends Error {
923
925
  name = "UndeclaredSpendError";
@@ -962,7 +964,7 @@ async function spawnClaude(args, prompt) {
962
964
  stdin: new TextEncoder().encode(prompt),
963
965
  stdout: "inherit",
964
966
  stderr: "inherit",
965
- env: claudeSessionEnv()
967
+ env: sessionSpawnEnv(process.env)
966
968
  });
967
969
  liveSessions.add(proc);
968
970
  try {
@@ -1015,7 +1017,7 @@ async function spawnCaptured(args, prompt, options = {}) {
1015
1017
  stdin: new TextEncoder().encode(prompt),
1016
1018
  stdout: "pipe",
1017
1019
  stderr: "inherit",
1018
- env: claudeSessionEnv(),
1020
+ env: sessionSpawnEnv(process.env),
1019
1021
  ...options.cwd === undefined ? {} : { cwd: options.cwd }
1020
1022
  });
1021
1023
  liveSessions.add(proc);
@@ -1131,11 +1133,16 @@ var REST_STATES = {
1131
1133
  cancelled: "closed"
1132
1134
  };
1133
1135
  function createGhRunner(options) {
1134
- const { cwd, command = "gh" } = options;
1136
+ const { cwd, command = "gh", env } = options;
1135
1137
  return async (args) => {
1138
+ const executable = env === undefined ? command : Bun.which(command, { PATH: env.PATH ?? "", cwd });
1139
+ if (executable === null) {
1140
+ return { ok: false, stdout: "", stderr: notOnPath(command, cwd, env?.PATH) };
1141
+ }
1136
1142
  try {
1137
- const proc = Bun.spawn([command, ...args], {
1143
+ const proc = Bun.spawn([executable, ...args], {
1138
1144
  cwd,
1145
+ ...env === undefined ? {} : { env: { ...env } },
1139
1146
  stdin: "ignore",
1140
1147
  stdout: "pipe",
1141
1148
  stderr: "pipe"
@@ -1151,6 +1158,10 @@ function createGhRunner(options) {
1151
1158
  }
1152
1159
  };
1153
1160
  }
1161
+ function notOnPath(command, cwd, path) {
1162
+ const searched = path === undefined ? "an environment with no PATH" : `PATH ${JSON.stringify(path)}`;
1163
+ return `could not run ${command} in ${cwd}: not found on ${searched} (ENOENT)`;
1164
+ }
1154
1165
  function isOneOf(members, value) {
1155
1166
  return typeof value === "string" && members.includes(value);
1156
1167
  }
@@ -1284,20 +1295,26 @@ function listedIssues(stdout) {
1284
1295
  function labelValue(labels, prefix) {
1285
1296
  return labels.find((label) => label.startsWith(prefix))?.slice(prefix.length);
1286
1297
  }
1298
+ function typeOfLabels(labels) {
1299
+ const type = labelValue(labels, GITHUB_LABELS.typePrefix);
1300
+ return isOneOf(ISSUE_TYPES, type) ? type : "code";
1301
+ }
1302
+ function moduleOfLabels(labels) {
1303
+ return labelValue(labels, GITHUB_LABELS.modulePrefix) ?? "unassigned";
1304
+ }
1287
1305
  function stateOf(viewed) {
1288
1306
  if (viewed.state === "OPEN")
1289
1307
  return "todo";
1290
1308
  return viewed.stateReason === NOT_PLANNED ? "cancelled" : "done";
1291
1309
  }
1292
1310
  function issueOf(ref, viewed) {
1293
- const type = labelValue(viewed.labels, GITHUB_LABELS.typePrefix);
1294
1311
  const priority = labelValue(viewed.labels, GITHUB_LABELS.priorityPrefix);
1295
1312
  return {
1296
1313
  opt: ref.opt,
1297
1314
  title: viewed.title,
1298
1315
  body: viewed.body,
1299
- type: isOneOf(ISSUE_TYPES, type) ? type : "code",
1300
- module: labelValue(viewed.labels, GITHUB_LABELS.modulePrefix) ?? "unassigned",
1316
+ type: typeOfLabels(viewed.labels),
1317
+ module: moduleOfLabels(viewed.labels),
1301
1318
  priority: isOneOf(ISSUE_PRIORITIES, priority) ? priority : null,
1302
1319
  project: null,
1303
1320
  blockedBy: viewed.labels.map((label) => label.startsWith(GITHUB_LABELS.blockedByPrefix) ? label.slice(GITHUB_LABELS.blockedByPrefix.length) : "").filter(isIssueNumber).map(Number),
@@ -286,6 +286,7 @@ var usdAmount = (raw, at) => {
286
286
  return usd === null ? refused(at, raw, "a number of US dollars above zero, at most six digits either side of the point") : accepted(usd);
287
287
  };
288
288
  var issueNumber = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0 ? accepted(raw) : refused(at, raw, "an issue number, a whole number above zero");
289
+ var dayCount = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0 ? accepted(raw) : refused(at, raw, "a number of days, a whole number above zero");
289
290
  var releaseEnabled = (raw, at) => typeof raw === "boolean" || raw === RELEASE_AUTO ? accepted(raw) : refused(at, raw, `true, false or ${RELEASE_AUTO}`);
290
291
  var GITHUB_LOGIN = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\[bot\])?$/;
291
292
  function isGitHubLogin(value) {
@@ -421,7 +422,10 @@ var CONFIG_DEFAULTS = Object.freeze({
421
422
  releaseEnabled: RELEASE_AUTO,
422
423
  releaseVersionFile: "package.json",
423
424
  releaseChangelog: "CHANGELOG.md",
424
- releaseHeading: "## {version} \u2014 {date}, {title}"
425
+ releaseHeading: "## {version} \u2014 {date}, {title}",
426
+ cleanupStaleDays: 30,
427
+ cleanupWorktreeIdleDays: 7,
428
+ cleanupKeep: Object.freeze([])
425
429
  });
426
430
  var directory = text("a directory path");
427
431
  var trackerKind = text("a tracker kind name");
@@ -496,6 +500,17 @@ var SETTINGS = {
496
500
  key: "release.heading",
497
501
  read: text("a changelog heading template"),
498
502
  cli: false
503
+ },
504
+ cleanupStaleDays: { key: "cleanup.staleDays", read: dayCount, cli: false },
505
+ cleanupWorktreeIdleDays: {
506
+ key: "cleanup.worktreeIdleDays",
507
+ read: dayCount,
508
+ cli: false
509
+ },
510
+ cleanupKeep: {
511
+ key: "cleanup.keep",
512
+ read: listOf(text("a glob pattern"), "glob patterns"),
513
+ cli: false
499
514
  }
500
515
  };
501
516
  var SETTING_NAMES = Object.keys(SETTINGS);
@@ -590,7 +605,10 @@ function readLayer(rawOf, atOf) {
590
605
  releaseEnabled: read("releaseEnabled"),
591
606
  releaseVersionFile: read("releaseVersionFile"),
592
607
  releaseChangelog: read("releaseChangelog"),
593
- releaseHeading: read("releaseHeading")
608
+ releaseHeading: read("releaseHeading"),
609
+ cleanupStaleDays: read("cleanupStaleDays"),
610
+ cleanupWorktreeIdleDays: read("cleanupWorktreeIdleDays"),
611
+ cleanupKeep: read("cleanupKeep")
594
612
  };
595
613
  return { layer, problems, extras };
596
614
  }
@@ -4,7 +4,7 @@ import {
4
4
  messageOf,
5
5
  parseTaskDeclaration,
6
6
  readRafaBlocks
7
- } from "./index-96pz9023.js";
7
+ } from "./index-2256nwj0.js";
8
8
  // src/preflight/prerequisites-md.ts
9
9
  import { basename, dirname, join } from "path";
10
10
  var SECTION_AUTO_RE = /\[auto\]/i;
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  trackerPathFor,
27
27
  updateTrackerLine,
28
28
  writeTrackerBlocker
29
- } from "./index-p96zrptw.js";
29
+ } from "./index-p572cxex.js";
30
30
  import {
31
31
  CORE_ADAPTER_REGISTRY,
32
32
  ClaudePlannerError,
@@ -50,7 +50,7 @@ import {
50
50
  sqliteStorePath,
51
51
  withSqliteStore,
52
52
  writeSqliteStore
53
- } from "./index-4g6vyznd.js";
53
+ } from "./index-1x5t4t9r.js";
54
54
  import {
55
55
  BUDGET_FLAG,
56
56
  CLAUDE_SETTING_SOURCES,
@@ -86,7 +86,7 @@ import {
86
86
  resolveConfig,
87
87
  resolveDeclarationFlags,
88
88
  stripTaskDeclaration
89
- } from "./index-96pz9023.js";
89
+ } from "./index-2256nwj0.js";
90
90
  // src/config-load.ts
91
91
  import { existsSync, readFileSync, realpathSync } from "fs";
92
92
  import { isAbsolute } from "path";
@@ -1902,7 +1902,7 @@ async function report(args, repoRoot) {
1902
1902
  }
1903
1903
  }
1904
1904
  // package.json
1905
- var version = "0.12.0";
1905
+ var version = "0.15.0";
1906
1906
 
1907
1907
  // src/modules/manifest.ts
1908
1908
  var MANIFEST_VERSION = 1;
@@ -4191,6 +4191,7 @@ var BLOCKED_BY_FIELD = "Blocked by";
4191
4191
  var FENCE_LINE2 = /^ {0,3}(?:```|~~~)/u;
4192
4192
  var FIELD_LINE = /^ {0,3}(?:[-*+]\s+)?[*_]{0,2}blocked\s+by[*_]{0,2}\s*:(.*)$/iu;
4193
4193
  var ISSUE_ID = /#(\d+)/gu;
4194
+ var FOREIGN_ID = /[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+#\d+/gu;
4194
4195
  var REMEDY4 = `name them as "${BLOCKED_BY_FIELD}: #24 #26", or take the ${SPEC_BLOCKED_LABEL} label off`;
4195
4196
  function hasSpecBlockedLabel(labels) {
4196
4197
  return labels.some((label) => label.trim().toLowerCase() === SPEC_BLOCKED_LABEL);
@@ -4223,29 +4224,41 @@ function idsIn(text) {
4223
4224
  }
4224
4225
  return found;
4225
4226
  }
4226
- function reading(kind, issue, found, blockers, unknown) {
4227
+ function foreignIn(text) {
4228
+ const found = [];
4229
+ for (const [token] of text.matchAll(FOREIGN_ID)) {
4230
+ if (!found.includes(token))
4231
+ found.push(token);
4232
+ }
4233
+ return found;
4234
+ }
4235
+ var NAMED_NOTHING = { ids: [], foreign: [] };
4236
+ function reading(kind, issue, found, named, unknown) {
4227
4237
  return {
4228
4238
  kind,
4229
4239
  issue,
4230
4240
  line: found?.number ?? null,
4231
4241
  text: found?.text ?? null,
4232
- blockers,
4242
+ blockers: named.ids,
4243
+ foreign: named.foreign,
4233
4244
  unknown
4234
4245
  };
4235
4246
  }
4236
4247
  function readBlockedBy(issue, body, known) {
4237
4248
  const found = findFieldLine(body);
4238
4249
  if (found === null)
4239
- return reading("no-line", issue, null, [], []);
4240
- const ids = idsIn(found.text);
4250
+ return reading("no-line", issue, null, NAMED_NOTHING, []);
4251
+ const foreign = foreignIn(found.text);
4252
+ const ids = idsIn(found.text.replace(FOREIGN_ID, " "));
4253
+ const named = { ids, foreign };
4241
4254
  if (ids.length === 0)
4242
- return reading("no-ids", issue, found, [], []);
4255
+ return reading("no-ids", issue, found, named, []);
4243
4256
  const unknown = known === undefined ? [] : ids.filter((id) => id !== issue && !known.has(id));
4244
4257
  if (ids.includes(issue))
4245
- return reading("self-reference", issue, found, ids, unknown);
4258
+ return reading("self-reference", issue, found, named, unknown);
4246
4259
  if (unknown.length > 0)
4247
- return reading("unknown-issue", issue, found, ids, unknown);
4248
- return reading("blocked", issue, found, ids, unknown);
4260
+ return reading("unknown-issue", issue, found, named, unknown);
4261
+ return reading("blocked", issue, found, named, unknown);
4249
4262
  }
4250
4263
  function nameIds(ids) {
4251
4264
  return ids.map((id) => `#${String(id)}`).join(" ");
@@ -4255,6 +4268,9 @@ function faultClause(read) {
4255
4268
  if (read.kind === "no-line") {
4256
4269
  return `is labelled ${SPEC_BLOCKED_LABEL} and its body carries no "${BLOCKED_BY_FIELD}:" line`;
4257
4270
  }
4271
+ if (read.kind === "no-ids" && read.foreign.length > 0) {
4272
+ return `has a "${BLOCKED_BY_FIELD}:" line ${at} naming only issues on other repositories: ${read.foreign.join(" ")}`;
4273
+ }
4258
4274
  if (read.kind === "no-ids") {
4259
4275
  return `has a "${BLOCKED_BY_FIELD}:" line ${at} naming no issue: "${read.text ?? ""}"`;
4260
4276
  }
@@ -5001,8 +5017,10 @@ var REPO_PATH3 = "{owner}/{repo}";
5001
5017
  var SUMMARY_FIELDS = "author,baseRefName,headRefName,isCrossRepository,number,state,title,updatedAt,url";
5002
5018
  var DETAIL_FIELDS = `${SUMMARY_FIELDS},body,headRefOid,labels,mergeStateStatus,mergeable`;
5003
5019
  var CHECK_FIELDS = "name,state,link";
5020
+ var MERGED_FIELDS = "headRefName,headRefOid,mergedAt,number";
5004
5021
  var WORKFLOWS_PATH = `repos/${REPO_PATH3}/actions/workflows`;
5005
5022
  var LIST_LIMIT = 30;
5023
+ var MERGED_LIMIT = 100;
5006
5024
  var FIND_LIMIT = 1;
5007
5025
  var MISSING_PULL = "Could not resolve to a PullRequest";
5008
5026
  var NO_CHECKS = "no checks reported on the";
@@ -5111,6 +5129,15 @@ function readDetail(value, command, where) {
5111
5129
  labels: readLabels(pull["labels"], command, `${where}.labels`)
5112
5130
  };
5113
5131
  }
5132
+ function readMerged(value, command, where) {
5133
+ const pull = readMapping(value, command, where);
5134
+ return {
5135
+ number: readWholeNumber(pull["number"], command, `${where}.number`),
5136
+ headRefName: readString2(pull["headRefName"], command, `${where}.headRefName`),
5137
+ headRefOid: readString2(pull["headRefOid"], command, `${where}.headRefOid`),
5138
+ mergedAt: readString2(pull["mergedAt"], command, `${where}.mergedAt`)
5139
+ };
5140
+ }
5114
5141
  function readComment2(value, command, where) {
5115
5142
  const comment = readMapping(value, command, where);
5116
5143
  const user = readMapping(comment["user"], command, `${where}.user`);
@@ -5191,6 +5218,12 @@ function createGhPullRequests(options) {
5191
5218
  const rows = readList(parseJson2(stdout, command), command, "its output");
5192
5219
  return rows.map((row, index) => readSummary(row, command, `row ${index}`));
5193
5220
  },
5221
+ listMerged: async () => {
5222
+ const command = "gh pr list --state merged";
5223
+ const stdout = await succeed(["pr", "list", "--state", "merged", "--limit", String(MERGED_LIMIT), "--json", MERGED_FIELDS], command);
5224
+ const rows = readList(parseJson2(stdout, command), command, "its output");
5225
+ return rows.map((row, index) => readMerged(row, command, `row ${index}`));
5226
+ },
5194
5227
  get: async (number) => {
5195
5228
  const target = pullNumber(number, "get");
5196
5229
  const command = `gh pr view ${target}`;
@@ -16,13 +16,13 @@ import {
16
16
  renderInjection,
17
17
  runPreflight,
18
18
  runShellProbe
19
- } from "../index-p96zrptw.js";
19
+ } from "../index-p572cxex.js";
20
20
  import {
21
21
  INJECT_MODES,
22
22
  RAFA_BLOCK_KINDS,
23
23
  isRafaBlockKind,
24
24
  readRafaBlocks
25
- } from "../index-96pz9023.js";
25
+ } from "../index-2256nwj0.js";
26
26
  export {
27
27
  runShellProbe,
28
28
  runPreflight,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-tomato/rafa",
3
- "version": "0.12.0",
3
+ "version": "0.15.0",
4
4
  "description": "Alpha. Runs a plan through the ralph loop: one Claude Code session per task, one commit per task, then a pull request and a CI wait.",
5
5
  "private": false,
6
6
  "publishConfig": {