@open-tomato/rafa 0.33.0 → 0.34.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.
@@ -15,7 +15,7 @@ import {
15
15
  parseConfigText,
16
16
  readRafaBlocks,
17
17
  resolveConfig
18
- } from "./index-dv0ycmp8.js";
18
+ } from "./index-vp0xghk2.js";
19
19
  import {
20
20
  bless,
21
21
  merge
@@ -161,7 +161,7 @@ function appendRows(path, rows, keyOf) {
161
161
  // src/effort/store/location.ts
162
162
  import { existsSync as existsSync2, realpathSync } from "fs";
163
163
  import { tmpdir } from "os";
164
- import { isAbsolute, join as join2, relative, resolve, sep } from "path";
164
+ import { basename, dirname as dirname2, isAbsolute, join as join2, relative, resolve, sep } from "path";
165
165
  var EFFORT_DIR_VARIABLE = "RAFA_EFFORT_DIR";
166
166
  var TEST_PROCESS_VARIABLE = "RAFA_TEST";
167
167
  var TEST_ENTRY_SUFFIX = ".test.ts";
@@ -169,6 +169,13 @@ function canonical(path) {
169
169
  const resolved = resolve(path);
170
170
  return existsSync2(resolved) ? realpathSync(resolved) : resolved;
171
171
  }
172
+ function realPathOf(path) {
173
+ const resolved = resolve(path);
174
+ if (existsSync2(resolved))
175
+ return realpathSync(resolved);
176
+ const parent = dirname2(resolved);
177
+ return parent === resolved ? resolved : join2(realPathOf(parent), basename(resolved));
178
+ }
172
179
  function isWithin(path, dir) {
173
180
  const between = relative(resolve(dir), resolve(path));
174
181
  const climbsOut = between === ".." || between.startsWith(`..${sep}`);
@@ -193,7 +200,7 @@ function isTestProcess(main = Bun.main, env = process.env) {
193
200
  return main.endsWith(TEST_ENTRY_SUFFIX) || env[TEST_PROCESS_VARIABLE] === "1";
194
201
  }
195
202
  function isUnderTempDir(path, tempDir = tmpdir()) {
196
- return isWithin(path, tempDir) || isWithin(path, canonical(tempDir));
203
+ return isWithin(path, tempDir) || isWithin(realPathOf(path), realPathOf(tempDir));
197
204
  }
198
205
  function currentProbe() {
199
206
  return { main: Bun.main, env: process.env, tempDir: tmpdir() };
@@ -247,12 +254,12 @@ function openNdjsonStore(repoRoot) {
247
254
 
248
255
  // src/effort/store/sqlite.ts
249
256
  import { existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
250
- import { dirname as dirname9, join as join9 } from "path";
257
+ import { dirname as dirname10, join as join9 } from "path";
251
258
  import { Database } from "bun:sqlite";
252
259
 
253
260
  // package.json
254
261
  var name = "@open-tomato/rafa";
255
- var version = "0.33.0";
262
+ var version = "0.34.0";
256
263
 
257
264
  // src/cli/version.ts
258
265
  var RAFA_VERSION = version;
@@ -262,7 +269,7 @@ function versionLine() {
262
269
 
263
270
  // src/effort/store/development-build.ts
264
271
  import { tmpdir as tmpdir2 } from "os";
265
- import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute5 } from "path";
272
+ import { basename as basename3, dirname as dirname8, isAbsolute as isAbsolute5 } from "path";
266
273
 
267
274
  // src/loop/sessions.ts
268
275
  import {
@@ -274,11 +281,11 @@ import {
274
281
  rmSync as rmSync3,
275
282
  writeFileSync as writeFileSync3
276
283
  } from "fs";
277
- import { dirname as dirname5, join as join7 } from "path";
284
+ import { dirname as dirname6, join as join7 } from "path";
278
285
 
279
286
  // src/project/scope.ts
280
287
  import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
281
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join4 } from "path";
288
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4 } from "path";
282
289
 
283
290
  // src/project/worktree-root.ts
284
291
  import { isAbsolute as isAbsolute2 } from "path";
@@ -345,7 +352,7 @@ function mainCheckoutOf(dir, seams = {}) {
345
352
  }
346
353
 
347
354
  // src/project/scope.ts
348
- var SCOPE_DIR = dirname2(CONFIG_FILE);
355
+ var SCOPE_DIR = dirname3(CONFIG_FILE);
349
356
  var INIT_COMMAND = "rafa init";
350
357
  var DISK_FILE_SYSTEM = Object.freeze({
351
358
  exists: (path) => existsSync3(path),
@@ -393,7 +400,7 @@ function realHomeOf(home, fs) {
393
400
  }
394
401
  }
395
402
  function selfAndAncestors(path) {
396
- const parent = dirname2(path);
403
+ const parent = dirname3(path);
397
404
  return parent === path ? [path] : [path, ...selfAndAncestors(parent)];
398
405
  }
399
406
  var GIT_MARKER = ".git";
@@ -453,17 +460,17 @@ function resolveScope(start, seams) {
453
460
  }
454
461
 
455
462
  // src/loop/session-record-parse.ts
456
- import { basename, isAbsolute as isAbsolute4 } from "path";
463
+ import { basename as basename2, isAbsolute as isAbsolute4 } from "path";
457
464
 
458
465
  // src/next/hop-record.ts
459
466
  import { randomUUID as randomUUID2 } from "crypto";
460
467
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
461
- import { dirname as dirname4, join as join6 } from "path";
468
+ import { dirname as dirname5, join as join6 } from "path";
462
469
 
463
470
  // src/project/position.ts
464
471
  import { randomUUID } from "crypto";
465
472
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync } from "fs";
466
- import { dirname as dirname3, join as join5 } from "path";
473
+ import { dirname as dirname4, join as join5 } from "path";
467
474
  var POSITION_FILE = "position.json";
468
475
  function positionFilePath(root) {
469
476
  return join5(scopeAt(root).dir, POSITION_FILE);
@@ -513,7 +520,7 @@ function readPositionFile(root) {
513
520
  }
514
521
  function writePositionFile(root, position) {
515
522
  const file = positionFilePath(root);
516
- mkdirSync2(dirname3(file), { recursive: true });
523
+ mkdirSync2(dirname4(file), { recursive: true });
517
524
  const temporary = `${file}.${String(process.pid)}.${randomUUID()}.tmp`;
518
525
  const { current, previous, home } = position;
519
526
  try {
@@ -627,7 +634,7 @@ function ownKeys(record) {
627
634
  }
628
635
  function writeHopRecord(root, record) {
629
636
  const file = hopFilePath(root);
630
- mkdirSync3(dirname4(file), { recursive: true });
637
+ mkdirSync3(dirname5(file), { recursive: true });
631
638
  const temporary = `${file}.${String(process.pid)}.${randomUUID2()}.tmp`;
632
639
  try {
633
640
  writeFileSync2(temporary, `${JSON.stringify(ownKeys(record), null, 2)}
@@ -759,7 +766,7 @@ function sessionIdProblem(sessionId, file) {
759
766
  if (typeof sessionId !== "string" || !isSessionId(sessionId)) {
760
767
  return `sessionId is ${describeValue2(sessionId)}, expected a plain file name`;
761
768
  }
762
- return basename(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
769
+ return basename2(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
763
770
  }
764
771
  function planStubProblem(planStub) {
765
772
  return planStub === null || typeof planStub === "string" && isStampableStub(planStub) ? null : `planStub is ${describeValue2(planStub)}, expected null or a plan stub`;
@@ -956,7 +963,7 @@ function checkRecord(record, file) {
956
963
  }
957
964
  function writeRecordFile(file, record, isNew) {
958
965
  checkRecord(record, file);
959
- mkdirSync4(dirname5(file), { recursive: true });
966
+ mkdirSync4(dirname6(file), { recursive: true });
960
967
  const temporary = `${file}.${process.pid}.tmp`;
961
968
  writeFileSync3(temporary, `${JSON.stringify(record, null, 2)}
962
969
  `);
@@ -1000,7 +1007,7 @@ function updateSession(root, sessionId, change) {
1000
1007
 
1001
1008
  // src/runtime/identity.ts
1002
1009
  import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3 } from "fs";
1003
- import { dirname as dirname6, join as join8, resolve as resolve2 } from "path";
1010
+ import { dirname as dirname7, join as join8, resolve as resolve2 } from "path";
1004
1011
  var MANIFEST = "package.json";
1005
1012
  var CHECKOUT_ENTRY = join8("src", "rafa.ts");
1006
1013
  function realOrResolved(path) {
@@ -1025,7 +1032,7 @@ function nearestRafaPackage(start) {
1025
1032
  const manifest = join8(dir, MANIFEST);
1026
1033
  if (existsSync4(manifest) && manifestName(manifest) === name)
1027
1034
  return dir;
1028
- const parent = dirname6(dir);
1035
+ const parent = dirname7(dir);
1029
1036
  if (parent === dir)
1030
1037
  return null;
1031
1038
  dir = parent;
@@ -1033,7 +1040,7 @@ function nearestRafaPackage(start) {
1033
1040
  }
1034
1041
  function readRuntimeIdentity(entry = Bun.main) {
1035
1042
  const real = realOrResolved(entry);
1036
- const packageDir = nearestRafaPackage(dirname6(real));
1043
+ const packageDir = nearestRafaPackage(dirname7(real));
1037
1044
  if (packageDir !== null && existsSync4(join8(packageDir, CHECKOUT_ENTRY))) {
1038
1045
  return { kind: "development", entry: real, checkout: packageDir };
1039
1046
  }
@@ -1060,11 +1067,11 @@ class DevelopmentBuildRefusedError extends Error {
1060
1067
  }
1061
1068
  }
1062
1069
  function storeProjectRoot(path) {
1063
- const storeDir = dirname7(path);
1064
- const scopeDir = dirname7(storeDir);
1065
- if (basename2(storeDir) !== EFFORT_DIR_NAME || basename2(scopeDir) !== SCOPE_DIR)
1070
+ const storeDir = dirname8(path);
1071
+ const scopeDir = dirname8(storeDir);
1072
+ if (basename3(storeDir) !== EFFORT_DIR_NAME || basename3(scopeDir) !== SCOPE_DIR)
1066
1073
  return null;
1067
- return dirname7(scopeDir);
1074
+ return dirname8(scopeDir);
1068
1075
  }
1069
1076
  function isOwnedStore(path, env, tempDir) {
1070
1077
  if (isUnderTempDir(path, tempDir))
@@ -1694,7 +1701,7 @@ function activeStoreSettings() {
1694
1701
 
1695
1702
  // src/effort/store/store-meta.ts
1696
1703
  import { randomUUID as randomUUID3 } from "crypto";
1697
- import { dirname as dirname8 } from "path";
1704
+ import { dirname as dirname9 } from "path";
1698
1705
 
1699
1706
  // src/effort/store/store-identity.ts
1700
1707
  import { spawnSync as spawnSync3 } from "child_process";
@@ -1836,7 +1843,7 @@ function readStoreMeta(db) {
1836
1843
  };
1837
1844
  }
1838
1845
  function projectFor(storePath, recorded, seams) {
1839
- const read = (seams.readProject ?? readProjectIdentity)(dirname8(storePath));
1846
+ const read = (seams.readProject ?? readProjectIdentity)(dirname9(storePath));
1840
1847
  if (read.rootCommit !== null)
1841
1848
  return { rootCommit: read.rootCommit, remote: read.remote };
1842
1849
  if (recorded === null)
@@ -1907,7 +1914,7 @@ function batchEntries(kind, rows) {
1907
1914
  function withSqliteStore(path, access, create, use, identity = {}) {
1908
1915
  guardTestProcess(path);
1909
1916
  if (create)
1910
- mkdirSync5(dirname9(path), { recursive: true });
1917
+ mkdirSync5(dirname10(path), { recursive: true });
1911
1918
  const db = new Database(path, { readwrite: true, create });
1912
1919
  try {
1913
1920
  db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
@@ -2567,7 +2574,7 @@ function copyEffortStore(request) {
2567
2574
  import { randomUUID as randomUUID4 } from "crypto";
2568
2575
  import { existsSync as existsSync11, mkdtempSync, rmSync as rmSync6 } from "fs";
2569
2576
  import { tmpdir as tmpdir3 } from "os";
2570
- import { dirname as dirname10, join as join11 } from "path";
2577
+ import { dirname as dirname11, join as join11 } from "path";
2571
2578
  import { Database as Database5, SQLiteError as SQLiteError2 } from "bun:sqlite";
2572
2579
 
2573
2580
  // src/effort/commits.ts
@@ -3123,7 +3130,7 @@ function recordedMeta(path) {
3123
3130
  }
3124
3131
  function refuseOtherProject(options) {
3125
3132
  const other = recordedMeta(options.otherPath);
3126
- const here = recordedMeta(options.path)?.projectRootCommit ?? (options.readProject ?? readProjectIdentity)(dirname10(options.path)).rootCommit;
3133
+ const here = recordedMeta(options.path)?.projectRootCommit ?? (options.readProject ?? readProjectIdentity)(dirname11(options.path)).rootCommit;
3127
3134
  const there = other?.projectRootCommit ?? null;
3128
3135
  if (here !== null && there !== null && here !== there) {
3129
3136
  throw new MergeRefusal("other-project", `REFUSED \u2014 ${options.otherPath} is the effort store of another project (root commit ${there});` + ` this store's project has root commit ${here}. ${NOTHING_MERGED}`);
@@ -4965,9 +4972,12 @@ var GITHUB_LABELS = Object.freeze({
4965
4972
  blockedByPrefix: "blocked-by:OPT-"
4966
4973
  });
4967
4974
  var VIEW_FIELDS = "number,title,body,state,stateReason,labels,url";
4975
+ var LIST_COMMAND = "gh issue list";
4968
4976
  var LIST_FIELDS = "number,url,labels";
4977
+ var OPEN_LIST_FIELDS = "number,url,labels,title,body";
4978
+ var OPEN_ISSUES_LIMIT = 1000;
4969
4979
  var DEFAULT_FIND_LIMIT = 30;
4970
- var NOT_PLANNED = "NOT_PLANNED";
4980
+ var CANCELLED_REASONS = new Set(["NOT_PLANNED", "DUPLICATE"]);
4971
4981
  var ISSUE_NUMBER = /^[1-9]\d*$/;
4972
4982
  var REPO_PART = /^[\w.-]+$/;
4973
4983
  var CREATED_ISSUE_URL = /^https:\/\/[^\s/]+\/[\w.-]+\/[\w.-]+\/issues\/([1-9]\d*)$/;
@@ -5139,28 +5149,49 @@ function viewedIssue(stdout, number) {
5139
5149
  const checked = payload;
5140
5150
  return { ...checked, labels: labelNames(checked.labels) ?? [] };
5141
5151
  }
5142
- function listedIssues(stdout) {
5143
- const command = "gh issue list";
5144
- const payload = parseJson(stdout, command);
5152
+ function listedRows(stdout) {
5153
+ const payload = parseJson(stdout, LIST_COMMAND);
5145
5154
  if (!Array.isArray(payload)) {
5146
- throw new Error(`${PREFIX2}: ${command} answered ${describeValue(payload)}, expected a list`);
5147
- }
5148
- return payload.map((row, index) => {
5149
- const refuse2 = (problem) => new Error(`${PREFIX2}: ${command} answered row ${index} ${problem}`);
5150
- if (!isMapping(row))
5151
- throw refuse2(`${describeValue(row)}, expected a mapping`);
5152
- const { number, url } = row;
5153
- const labels = labelNames(row["labels"]);
5154
- if (typeof number !== "number" || !isIssueNumber3(String(number))) {
5155
- throw refuse2(`with number ${describeValue(number)}, expected an issue number`);
5156
- }
5157
- if (typeof url !== "string")
5158
- throw refuse2(`with url ${describeValue(url)}, expected a string`);
5159
- if (labels === null)
5160
- throw refuse2("with labels that are not a list of named labels");
5161
- return { number, url, labels };
5155
+ throw new Error(`${PREFIX2}: ${LIST_COMMAND} answered ${describeValue(payload)}, expected a list`);
5156
+ }
5157
+ return payload;
5158
+ }
5159
+ function rowRefusal(index, problem) {
5160
+ return new Error(`${PREFIX2}: ${LIST_COMMAND} answered row ${index} ${problem}`);
5161
+ }
5162
+ function listedRow(row, index) {
5163
+ if (!isMapping(row))
5164
+ throw rowRefusal(index, `${describeValue(row)}, expected a mapping`);
5165
+ const { number, url } = row;
5166
+ const labels = labelNames(row["labels"]);
5167
+ if (typeof number !== "number" || !isIssueNumber3(String(number))) {
5168
+ throw rowRefusal(index, `with number ${describeValue(number)}, expected an issue number`);
5169
+ }
5170
+ if (typeof url !== "string")
5171
+ throw rowRefusal(index, `with url ${describeValue(url)}, expected a string`);
5172
+ if (labels === null)
5173
+ throw rowRefusal(index, "with labels that are not a list of named labels");
5174
+ return { number, url, labels };
5175
+ }
5176
+ function listedIssues(stdout) {
5177
+ return listedRows(stdout).map(listedRow);
5178
+ }
5179
+ function listedOpenIssues(stdout) {
5180
+ return listedRows(stdout).map((row, index) => {
5181
+ const listed2 = listedRow(row, index);
5182
+ const { title, body } = row;
5183
+ if (typeof title !== "string")
5184
+ throw rowRefusal(index, `with title ${describeValue(title)}, expected a string`);
5185
+ if (typeof body !== "string")
5186
+ throw rowRefusal(index, `with body ${describeValue(body)}, expected a string`);
5187
+ return { ...listed2, title, body };
5162
5188
  });
5163
5189
  }
5190
+ function refOfRow(row) {
5191
+ const module = labelValue(row.labels, GITHUB_LABELS.modulePrefix);
5192
+ const ref = { opt: 0, kind: "github", externalId: String(row.number), url: row.url };
5193
+ return module === undefined ? ref : { ...ref, module };
5194
+ }
5164
5195
  function labelValue(labels, prefix) {
5165
5196
  return labels.find((label) => label.startsWith(prefix))?.slice(prefix.length);
5166
5197
  }
@@ -5174,7 +5205,7 @@ function moduleOfLabels(labels) {
5174
5205
  function stateOf(viewed) {
5175
5206
  if (viewed.state === "OPEN")
5176
5207
  return "todo";
5177
- return viewed.stateReason === NOT_PLANNED ? "cancelled" : "done";
5208
+ return viewed.stateReason !== null && CANCELLED_REASONS.has(viewed.stateReason) ? "cancelled" : "done";
5178
5209
  }
5179
5210
  function issueOf(ref, viewed) {
5180
5211
  const priority = labelValue(viewed.labels, GITHUB_LABELS.priorityPrefix);
@@ -5285,12 +5316,26 @@ function createGithubTracker(options) {
5285
5316
  ...optionalFlag("--label", moduleLabel),
5286
5317
  ...optionalFlag("--label", typeLabel),
5287
5318
  ...optionalFlag("--search", query.text)
5288
- ], "gh issue list");
5289
- return listedIssues(stdout).map((row) => {
5290
- const module = labelValue(row.labels, GITHUB_LABELS.modulePrefix);
5291
- const ref = { opt: 0, kind: "github", externalId: String(row.number), url: row.url };
5292
- return module === undefined ? ref : { ...ref, module };
5293
- });
5319
+ ], LIST_COMMAND);
5320
+ return listedIssues(stdout).map(refOfRow);
5321
+ },
5322
+ openIssues: async (type) => {
5323
+ if (!isOneOf(ISSUE_TYPES, type)) {
5324
+ throw new TypeError(`${PREFIX2}: openIssues refused type ${describeValue(type)}, expected one of: ${ISSUE_TYPES.join(", ")}`);
5325
+ }
5326
+ const stdout = await run([
5327
+ "issue",
5328
+ "list",
5329
+ "--state",
5330
+ "open",
5331
+ "--json",
5332
+ OPEN_LIST_FIELDS,
5333
+ "--limit",
5334
+ String(OPEN_ISSUES_LIMIT),
5335
+ "--label",
5336
+ `${GITHUB_LABELS.typePrefix}${type}`
5337
+ ], LIST_COMMAND);
5338
+ return listedOpenIssues(stdout).map((row) => ({ ref: refOfRow(row), title: row.title, body: row.body }));
5294
5339
  },
5295
5340
  comment: async (ref, body) => {
5296
5341
  const number = issueNumberOfRef(ref);
@@ -5328,6 +5373,7 @@ import { join as join16 } from "path";
5328
5373
  var PREFIX3 = "local tracker";
5329
5374
  var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
5330
5375
  var ISSUE_NUMBER2 = /^[1-9]\d*$/;
5376
+ var CLOSED_STATES = new Set(["done", "released", "cancelled"]);
5331
5377
  var ISSUE_EXTENSION = ".md";
5332
5378
  function isString2(value) {
5333
5379
  return typeof value === "string";
@@ -5459,7 +5505,7 @@ function parseLocalIssue(contents, sourcePath) {
5459
5505
  function warnThroughActiveOutput2(message) {
5460
5506
  activeOutput().warn(message);
5461
5507
  }
5462
- async function listLocalIssues(issuesDir, warn = warnThroughActiveOutput2) {
5508
+ async function listLocalIssues(issuesDir, warn = warnThroughActiveOutput2, reader = "find") {
5463
5509
  const numbers = await issueNumbers(issuesDir);
5464
5510
  const settled = await Promise.allSettled(numbers.map(async (number) => {
5465
5511
  const path = issuePath(issuesDir, number);
@@ -5474,7 +5520,7 @@ async function listLocalIssues(issuesDir, warn = warnThroughActiveOutput2) {
5474
5520
  return settled.flatMap((result) => {
5475
5521
  if (result.status === "fulfilled")
5476
5522
  return [result.value];
5477
- warn(`${messageOf(result.reason)}; find skipped it`);
5523
+ warn(`${messageOf(result.reason)}; ${reader} skipped it`);
5478
5524
  return [];
5479
5525
  });
5480
5526
  }
@@ -5560,6 +5606,14 @@ function createLocalTracker(options) {
5560
5606
  const matched = (await listLocalIssues(issuesDir, warn)).filter(({ record }) => matchesQuery(record, query));
5561
5607
  return matched.slice(0, query.limit ?? matched.length).map(({ number, record }) => refFor(number, record.draft.opt));
5562
5608
  },
5609
+ openIssues: async (type) => {
5610
+ const open = (await listLocalIssues(issuesDir, warn, "openIssues")).filter(({ record }) => record.draft.type === type && !CLOSED_STATES.has(record.state));
5611
+ return open.map(({ number, record }) => ({
5612
+ ref: refFor(number, record.draft.opt),
5613
+ title: record.draft.title,
5614
+ body: record.draft.body
5615
+ }));
5616
+ },
5563
5617
  comment: async (ref, body) => {
5564
5618
  const { number, record } = await read(ref);
5565
5619
  await rewrite(number, { ...record, comments: [...record.comments, `${now()}: ${body}`] });
@@ -3,7 +3,7 @@ import {
3
3
  activeOutput,
4
4
  isStampableStub,
5
5
  readRafaBlocks
6
- } from "./index-dv0ycmp8.js";
6
+ } from "./index-vp0xghk2.js";
7
7
  import {
8
8
  messageOf,
9
9
  parseTaskDeclaration
@@ -324,6 +324,35 @@ var TESTS_SETTINGS = {
324
324
  testsIntegration: { key: "tests.integration", read: globList, cli: false }
325
325
  };
326
326
 
327
+ // src/config-schema-triage.ts
328
+ var SIMILARITY_THRESHOLD_MAX = 1;
329
+ var SIMILARITY_CANDIDATES_MIN = 1;
330
+ var SIMILARITY_CANDIDATES_MAX = 10;
331
+ function isThreshold(raw) {
332
+ return typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw <= SIMILARITY_THRESHOLD_MAX;
333
+ }
334
+ function isCandidateCount(raw) {
335
+ return typeof raw === "number" && Number.isSafeInteger(raw) && raw >= SIMILARITY_CANDIDATES_MIN && raw <= SIMILARITY_CANDIDATES_MAX;
336
+ }
337
+ var similarityThreshold = (raw, at) => raw === false || isThreshold(raw) ? { value: raw, problems: [], extras: [] } : refused(at, raw, `false or a number above 0 and at most ${String(SIMILARITY_THRESHOLD_MAX)}`);
338
+ var similarityCandidates = (raw, at) => isCandidateCount(raw) ? { value: raw, problems: [], extras: [] } : refused(at, raw, `a whole number from ${String(SIMILARITY_CANDIDATES_MIN)} to ${String(SIMILARITY_CANDIDATES_MAX)}`);
339
+ var TRIAGE_DEFAULTS = Object.freeze({
340
+ triageSimilarityThreshold: 0.3,
341
+ triageSimilarityCandidates: 3
342
+ });
343
+ var TRIAGE_SETTINGS = {
344
+ triageSimilarityThreshold: {
345
+ key: "triage.similarity.threshold",
346
+ read: similarityThreshold,
347
+ cli: false
348
+ },
349
+ triageSimilarityCandidates: {
350
+ key: "triage.similarity.candidates",
351
+ read: similarityCandidates,
352
+ cli: false
353
+ }
354
+ };
355
+
327
356
  // src/config-schema-wrap-up.ts
328
357
  var WRAP_UP_RETRIES_MIN = 1;
329
358
  var WRAP_UP_RETRIES_MAX = 3;
@@ -536,6 +565,7 @@ var CONFIG_DEFAULTS = Object.freeze({
536
565
  roadmapIssue: null,
537
566
  claimsStaleAfter: "3d",
538
567
  claimsAhead: "off",
568
+ ...TRIAGE_DEFAULTS,
539
569
  ...RELEASE_DEFAULTS,
540
570
  cleanupStaleDays: 30,
541
571
  cleanupWorktreeIdleDays: 7,
@@ -623,6 +653,7 @@ var SETTINGS = {
623
653
  roadmapIssue: { key: "roadmap.issue", read: issueNumber, cli: false },
624
654
  claimsStaleAfter: { key: "claims.staleAfter", read: claimsStaleAfter, cli: false },
625
655
  claimsAhead: { key: "claims.ahead", read: claimsAhead, cli: false },
656
+ ...TRIAGE_SETTINGS,
626
657
  ...RELEASE_SETTINGS,
627
658
  cleanupStaleDays: { key: "cleanup.staleDays", read: dayCount, cli: false },
628
659
  cleanupWorktreeIdleDays: {
@@ -758,6 +789,8 @@ function readLayer(rawOf, atOf) {
758
789
  roadmapIssue: read("roadmapIssue"),
759
790
  claimsStaleAfter: read("claimsStaleAfter"),
760
791
  claimsAhead: read("claimsAhead"),
792
+ triageSimilarityThreshold: read("triageSimilarityThreshold"),
793
+ triageSimilarityCandidates: read("triageSimilarityCandidates"),
761
794
  releaseEnabled: read("releaseEnabled"),
762
795
  releaseVersionFile: read("releaseVersionFile"),
763
796
  releaseChangelog: read("releaseChangelog"),
@@ -821,7 +854,14 @@ function sortKeys(document, path) {
821
854
  }
822
855
  return { named, extras, problems };
823
856
  }
857
+ var TAB_INDENTED_LINE = /^[ \t]*\t[ \t]*[^\s#]/;
858
+ function tabIndentProblems(text2, path) {
859
+ return text2.split(/\r?\n/).flatMap((line, index) => TAB_INDENTED_LINE.test(line) ? [`${path}: line ${index + 1} is indented with a tab; YAML indents with spaces only`] : []);
860
+ }
824
861
  function parseYaml(text2, path) {
862
+ const tabProblems = tabIndentProblems(text2, path);
863
+ if (tabProblems.length > 0)
864
+ throw new ConfigError(tabProblems);
825
865
  try {
826
866
  return Bun.YAML.parse(text2);
827
867
  } catch (error) {
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  start,
11
11
  usage,
12
12
  validateManifest
13
- } from "./index-w3vyb08t.js";
13
+ } from "./index-4b3m7s0z.js";
14
14
  import {
15
15
  PLAN_BLOCK_KINDS,
16
16
  PLAN_HEADER_FIELDS,
@@ -24,7 +24,7 @@ import {
24
24
  renderInjection,
25
25
  runPreflight,
26
26
  runShellProbe
27
- } from "./index-zn08ywsv.js";
27
+ } from "./index-v8d7c6sw.js";
28
28
  import {
29
29
  CORE_ADAPTER_REGISTRY,
30
30
  DISK_FILE_SYSTEM,
@@ -53,7 +53,7 @@ import {
53
53
  scopeAt,
54
54
  selectEffortStore,
55
55
  selfAndAncestors
56
- } from "./index-kz7e4gtt.js";
56
+ } from "./index-bvfbfa63.js";
57
57
  import {
58
58
  CONFIG_DEFAULTS,
59
59
  CONFIG_FILE,
@@ -70,7 +70,7 @@ import {
70
70
  parseReport,
71
71
  readRafaBlocks,
72
72
  resolveConfig
73
- } from "./index-dv0ycmp8.js";
73
+ } from "./index-vp0xghk2.js";
74
74
  import"./index-d6d0942q.js";
75
75
  import"./index-k6sd4ym5.js";
76
76
  import"./index-3mzw3kzh.js";
@@ -12,7 +12,7 @@ import {
12
12
  renderInjection,
13
13
  runPreflight,
14
14
  runShellProbe
15
- } from "../index-zn08ywsv.js";
15
+ } from "../index-v8d7c6sw.js";
16
16
  import {
17
17
  FINDING_KINDS,
18
18
  FINDING_SIGNALS,
@@ -21,7 +21,7 @@ import {
21
21
  isRafaBlockKind,
22
22
  parseReport,
23
23
  readRafaBlocks
24
- } from "../index-dv0ycmp8.js";
24
+ } from "../index-vp0xghk2.js";
25
25
  import {
26
26
  INJECT_MODES
27
27
  } from "../index-fzhd3d4y.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-tomato/rafa",
3
- "version": "0.33.0",
3
+ "version": "0.34.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": {