@open-tomato/rafa 0.24.1 → 0.28.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.
@@ -1,31 +1,37 @@
1
1
  // @bun
2
2
  import {
3
3
  CONFIG_DEFAULTS,
4
+ CONFIG_FILE,
4
5
  FINDING_DOMAINS,
5
6
  FINDING_KINDS,
6
7
  FINDING_SIGNALS,
7
8
  activeOutput,
8
9
  activeOutputMode,
10
+ configFilePath,
9
11
  createTextOutput,
12
+ isStampableStub,
10
13
  readRafaBlocks
11
- } from "./index-twbp38kz.js";
14
+ } from "./index-rv7y8y1j.js";
12
15
  import {
13
16
  bless,
14
17
  merge
15
18
  } from "./index-d6d0942q.js";
16
19
  import {
17
20
  commandSpelling
18
- } from "./index-4fwgqveh.js";
21
+ } from "./index-4j1e3h26.js";
19
22
  import {
23
+ BUSY_TIMEOUT_MAX_MS,
24
+ BUSY_TIMEOUT_MIN_MS,
20
25
  RAFA_INSTINCTS_PATH,
21
26
  STORE_BACKENDS,
22
27
  bundledBinDirectory,
23
28
  describeValue,
29
+ isBusyTimeoutMs,
24
30
  isMapping,
25
31
  messageOf,
26
32
  resolveInstinctScopes,
27
33
  tierExists
28
- } from "./index-pmkgvtfe.js";
34
+ } from "./index-sf2brh4w.js";
29
35
  import {
30
36
  actionHash,
31
37
  triggerKey
@@ -34,6 +40,9 @@ import {
34
40
  // src/adapters/registry.ts
35
41
  import { homedir } from "os";
36
42
 
43
+ // src/effort/store/ndjson.ts
44
+ import { join as join3 } from "path";
45
+
37
46
  // src/effort/store.ts
38
47
  import {
39
48
  appendFileSync,
@@ -53,8 +62,8 @@ function asObject(value) {
53
62
  function normaliseKey(key) {
54
63
  return typeof key === "string" && key.length > 0 ? key : null;
55
64
  }
56
- function effortStorePath(repoRoot, kind) {
57
- return join(repoRoot, EFFORT_STORE_DIR, STORE_FILE_NAMES[kind]);
65
+ function effortStoreFileName(kind) {
66
+ return STORE_FILE_NAMES[kind];
58
67
  }
59
68
  function readStoreText(path) {
60
69
  return existsSync(path) ? readFileSync(path, "utf8") : null;
@@ -141,6 +150,54 @@ function appendRows(path, rows, keyOf) {
141
150
  };
142
151
  }
143
152
 
153
+ // src/effort/store/location.ts
154
+ import { existsSync as existsSync2, realpathSync } from "fs";
155
+ import { tmpdir } from "os";
156
+ import { isAbsolute, join as join2, relative, resolve, sep } from "path";
157
+ var EFFORT_DIR_VARIABLE = "RAFA_EFFORT_DIR";
158
+ var TEST_PROCESS_VARIABLE = "RAFA_TEST";
159
+ var TEST_ENTRY_SUFFIX = ".test.ts";
160
+ function canonical(path) {
161
+ const resolved = resolve(path);
162
+ return existsSync2(resolved) ? realpathSync(resolved) : resolved;
163
+ }
164
+ function isWithin(path, dir) {
165
+ const between = relative(resolve(dir), resolve(path));
166
+ const climbsOut = between === ".." || between.startsWith(`..${sep}`);
167
+ return !climbsOut && !isAbsolute(between);
168
+ }
169
+ function readEffortDirOverride(repoRoot, env = process.env) {
170
+ const value = env[EFFORT_DIR_VARIABLE];
171
+ if (value === undefined || value === "")
172
+ return null;
173
+ if (!isAbsolute(value)) {
174
+ throw new Error(`effort store: ${EFFORT_DIR_VARIABLE} is not an absolute path (${value}); run this command again` + ` with ${EFFORT_DIR_VARIABLE} set to the absolute path 'rafa effort copy' printed.`);
175
+ }
176
+ if (canonical(value) === canonical(join2(repoRoot, EFFORT_STORE_DIR))) {
177
+ throw new Error(`effort store: ${EFFORT_DIR_VARIABLE} names this project's own store (${value}); it points a command` + " at a copy only. Copy the store with 'rafa effort copy' and run this command with" + ` ${EFFORT_DIR_VARIABLE}=<the copy>.`);
178
+ }
179
+ return value;
180
+ }
181
+ function effortStoreDir(repoRoot, env = process.env) {
182
+ return readEffortDirOverride(repoRoot, env) ?? join2(repoRoot, EFFORT_STORE_DIR);
183
+ }
184
+ function isTestProcess(main = Bun.main, env = process.env) {
185
+ return main.endsWith(TEST_ENTRY_SUFFIX) || env[TEST_PROCESS_VARIABLE] === "1";
186
+ }
187
+ function isUnderTempDir(path, tempDir = tmpdir()) {
188
+ return isWithin(path, tempDir) || isWithin(path, canonical(tempDir));
189
+ }
190
+ function currentProbe() {
191
+ return { main: Bun.main, env: process.env, tempDir: tmpdir() };
192
+ }
193
+ function guardTestProcess(path, probe = currentProbe()) {
194
+ if (!isTestProcess(probe.main, probe.env))
195
+ return;
196
+ if (isUnderTempDir(path, probe.tempDir))
197
+ return;
198
+ throw new Error(`effort store: a test opened ${resolve(path)}, outside the temp directory ${probe.tempDir};` + " a test opens stores under tmpdir() only");
199
+ }
200
+
144
201
  // src/effort/store/types.ts
145
202
  function keyOrNull(value) {
146
203
  return typeof value === "string" && value.length > 0 ? value : null;
@@ -151,15 +208,23 @@ var EFFORT_KEY_PROJECTIONS = {
151
208
  };
152
209
 
153
210
  // src/effort/store/ndjson.ts
211
+ function kindPath(repoRoot, kind) {
212
+ return join3(effortStoreDir(repoRoot), effortStoreFileName(kind));
213
+ }
214
+ function openedKindPath(repoRoot, kind) {
215
+ const path = kindPath(repoRoot, kind);
216
+ guardTestProcess(path);
217
+ return path;
218
+ }
154
219
  function readKind(repoRoot, kind) {
155
- return readStoreRows(effortStorePath(repoRoot, kind));
220
+ return readStoreRows(openedKindPath(repoRoot, kind));
156
221
  }
157
222
  function keysOfKind(repoRoot, kind) {
158
- const path = effortStorePath(repoRoot, kind);
223
+ const path = openedKindPath(repoRoot, kind);
159
224
  return collectedKeys(path, EFFORT_KEY_PROJECTIONS[kind]);
160
225
  }
161
226
  function appendKind(repoRoot, kind, rows) {
162
- const path = effortStorePath(repoRoot, kind);
227
+ const path = openedKindPath(repoRoot, kind);
163
228
  return appendRows(path, rows, EFFORT_KEY_PROJECTIONS[kind]);
164
229
  }
165
230
  function openNdjsonStore(repoRoot) {
@@ -167,19 +232,663 @@ function openNdjsonStore(repoRoot) {
167
232
  append: (kind, rows) => appendKind(repoRoot, kind, rows),
168
233
  keys: (kind) => keysOfKind(repoRoot, kind),
169
234
  read: (kind) => readKind(repoRoot, kind).rows,
170
- path: (kind) => effortStorePath(repoRoot, kind),
235
+ path: (kind) => kindPath(repoRoot, kind),
171
236
  readRows: (kind) => readKind(repoRoot, kind)
172
237
  };
173
238
  }
174
239
 
175
240
  // src/effort/store/sqlite.ts
176
- import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
177
- import { dirname as dirname2, join as join2 } from "path";
241
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
242
+ import { dirname as dirname9, join as join9 } from "path";
178
243
  import { Database } from "bun:sqlite";
179
244
 
245
+ // package.json
246
+ var name = "@open-tomato/rafa";
247
+ var version = "0.28.0";
248
+
249
+ // src/cli/version.ts
250
+ var RAFA_VERSION = version;
251
+ function versionLine() {
252
+ return `rafa ${RAFA_VERSION}`;
253
+ }
254
+
255
+ // src/effort/store/development-build.ts
256
+ import { tmpdir as tmpdir2 } from "os";
257
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute3 } from "path";
258
+
259
+ // src/loop/sessions.ts
260
+ import {
261
+ linkSync,
262
+ mkdirSync as mkdirSync4,
263
+ readdirSync,
264
+ readFileSync as readFileSync4,
265
+ renameSync as renameSync3,
266
+ rmSync as rmSync3,
267
+ writeFileSync as writeFileSync3
268
+ } from "fs";
269
+ import { basename, dirname as dirname5, join as join7 } from "path";
270
+
271
+ // src/next/hop-record.ts
272
+ import { randomUUID as randomUUID2 } from "crypto";
273
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
274
+ import { dirname as dirname4, join as join6 } from "path";
275
+
276
+ // src/project/position.ts
277
+ import { randomUUID } from "crypto";
278
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync } from "fs";
279
+ import { dirname as dirname3, join as join5 } from "path";
280
+
281
+ // src/project/scope.ts
282
+ import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
283
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4 } from "path";
284
+ var SCOPE_DIR = dirname2(CONFIG_FILE);
285
+ var INIT_COMMAND = "rafa init";
286
+ var DISK_FILE_SYSTEM = Object.freeze({
287
+ exists: (path) => existsSync3(path),
288
+ realpath: (path) => realpathSync2(path)
289
+ });
290
+
291
+ class ScopeError extends Error {
292
+ constructor(message, options) {
293
+ super(`rafa scope: ${message}`, options);
294
+ this.name = "ScopeError";
295
+ }
296
+ }
297
+ function scopeAt(base) {
298
+ return { dir: join4(base, SCOPE_DIR), configFile: configFilePath(base) };
299
+ }
300
+ function initHint(start, passedUserConfig = null) {
301
+ const passed = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
302
+ return [
303
+ `not inside a rafa project: no ${CONFIG_FILE} in ${start} or any directory above it`,
304
+ ...passed,
305
+ `run \`${INIT_COMMAND}\` to set one up`
306
+ ].join(`
307
+ `);
308
+ }
309
+ function requireAbsolute(label, path) {
310
+ if (!isAbsolute2(path)) {
311
+ throw new ScopeError(`${label} is ${describeValue(path)}, expected an absolute path`);
312
+ }
313
+ }
314
+ function realStartOf(start, fs) {
315
+ try {
316
+ return fs.realpath(start);
317
+ } catch (error) {
318
+ const message = `start directory ${start} does not resolve (${messageOf(error)})`;
319
+ throw new ScopeError(message, { cause: error });
320
+ }
321
+ }
322
+ function realHomeOf(home, fs) {
323
+ try {
324
+ return fs.realpath(home);
325
+ } catch {
326
+ return null;
327
+ }
328
+ }
329
+ function selfAndAncestors(path) {
330
+ const parent = dirname2(path);
331
+ return parent === path ? [path] : [path, ...selfAndAncestors(parent)];
332
+ }
333
+ function walkUp(realStart, realHome, fs) {
334
+ let passedUserConfig = false;
335
+ for (const dir of selfAndAncestors(realStart)) {
336
+ if (!fs.exists(configFilePath(dir)))
337
+ continue;
338
+ if (dir !== realHome)
339
+ return { root: dir, passedUserConfig };
340
+ passedUserConfig = true;
341
+ }
342
+ return { root: null, passedUserConfig };
343
+ }
344
+ function resolveScope(start, seams) {
345
+ const { home, fs = DISK_FILE_SYSTEM } = seams;
346
+ requireAbsolute("start directory", start);
347
+ requireAbsolute("home directory", home);
348
+ const user = scopeAt(home);
349
+ const walk = walkUp(realStartOf(start, fs), realHomeOf(home, fs), fs);
350
+ if (walk.root === null) {
351
+ const passed = walk.passedUserConfig ? user.configFile : null;
352
+ return { found: false, start, home, user, hint: initHint(start, passed) };
353
+ }
354
+ return { found: true, root: walk.root, home, project: scopeAt(walk.root), user };
355
+ }
356
+
357
+ // src/project/position.ts
358
+ var POSITION_FILE = "position.json";
359
+ function positionFilePath(root) {
360
+ return join5(scopeAt(root).dir, POSITION_FILE);
361
+ }
362
+ function isRecord(value) {
363
+ return typeof value === "object" && value !== null && !Array.isArray(value);
364
+ }
365
+ function isIssueNumber(value) {
366
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
367
+ }
368
+ function asPlace(value) {
369
+ if (!isRecord(value) || !isIssueNumber(value.board))
370
+ return null;
371
+ if (value.epic !== null && !isIssueNumber(value.epic))
372
+ return null;
373
+ return { board: value.board, epic: value.epic };
374
+ }
375
+ function asPosition(value) {
376
+ if (!isRecord(value))
377
+ return null;
378
+ const current = asPlace(value.current);
379
+ const home = asPlace(value.home);
380
+ const previous = value.previous === null ? null : asPlace(value.previous);
381
+ if (current === null || home === null)
382
+ return null;
383
+ if (previous === null && value.previous !== null)
384
+ return null;
385
+ return { current, previous, home };
386
+ }
387
+ function readPositionFile(root) {
388
+ const file = positionFilePath(root);
389
+ let text;
390
+ try {
391
+ text = readFileSync2(file, "utf8");
392
+ } catch (error) {
393
+ const code = error.code;
394
+ return code === "ENOENT" ? { set: false, reason: "absent", detail: `No position file at ${file}` } : { set: false, reason: "unreadable", detail: `Cannot read ${file}: ${messageOf(error)}` };
395
+ }
396
+ let parsed;
397
+ try {
398
+ parsed = JSON.parse(text);
399
+ } catch (error) {
400
+ return { set: false, reason: "invalid-json", detail: `${file} holds no JSON: ${messageOf(error)}` };
401
+ }
402
+ const position = asPosition(parsed);
403
+ return position === null ? { set: false, reason: "wrong-shape", detail: `${file} does not hold a current, previous and home place` } : { set: true, position };
404
+ }
405
+ function writePositionFile(root, position) {
406
+ const file = positionFilePath(root);
407
+ mkdirSync2(dirname3(file), { recursive: true });
408
+ const temporary = `${file}.${String(process.pid)}.${randomUUID()}.tmp`;
409
+ const { current, previous, home } = position;
410
+ try {
411
+ writeFileSync(temporary, `${JSON.stringify({ current, previous, home }, null, 2)}
412
+ `);
413
+ renameSync(temporary, file);
414
+ } catch (error) {
415
+ rmSync(temporary, { force: true });
416
+ throw error;
417
+ }
418
+ }
419
+ function positionAt(place) {
420
+ return { current: place, previous: null, home: place };
421
+ }
422
+ function rehome(position, place) {
423
+ return { current: place, previous: position.current, home: place };
424
+ }
425
+ function hop(position, place) {
426
+ return { current: place, previous: position.current, home: position.home };
427
+ }
428
+ function goHome(position) {
429
+ return { current: position.home, previous: position.current, home: position.home };
430
+ }
431
+
432
+ // src/next/hop-record.ts
433
+ var HOP_FILE = "hop.json";
434
+ var HOP_KINDS = ["blocker", "dry"];
435
+ var HOP_STATES = ["away", "waiting", "merged", "halted"];
436
+ function hopFilePath(root) {
437
+ return join6(scopeAt(root).dir, HOP_FILE);
438
+ }
439
+ function isRecord2(value) {
440
+ return typeof value === "object" && value !== null && !Array.isArray(value);
441
+ }
442
+ function isIssueNumber2(value) {
443
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
444
+ }
445
+ function isIssueOrNull(value) {
446
+ return value === null || isIssueNumber2(value);
447
+ }
448
+ function isTimestamp(value) {
449
+ return typeof value === "string" && !Number.isNaN(Date.parse(value));
450
+ }
451
+ function issuesMatchKind(kind, blocked, target) {
452
+ return kind === "blocker" ? blocked !== null && target !== null : blocked === null && target === null;
453
+ }
454
+ function asHopRecord(value) {
455
+ if (!isRecord2(value))
456
+ return null;
457
+ const { kind, blocked, target, targetEpic, targetBoard, state, pullRequest, startedAt } = value;
458
+ const home = asPlace(value.home);
459
+ const from = asPlace(value.from);
460
+ if (typeof kind !== "string" || !HOP_KINDS.includes(kind))
461
+ return null;
462
+ if (typeof state !== "string" || !HOP_STATES.includes(state))
463
+ return null;
464
+ if (home === null || from === null)
465
+ return null;
466
+ if (!isIssueOrNull(blocked) || !isIssueOrNull(target) || !isIssueOrNull(pullRequest))
467
+ return null;
468
+ if (!isIssueNumber2(targetEpic) || !isIssueNumber2(targetBoard) || !isTimestamp(startedAt))
469
+ return null;
470
+ const hopKind = kind;
471
+ if (!issuesMatchKind(hopKind, blocked, target))
472
+ return null;
473
+ return {
474
+ kind: hopKind,
475
+ home,
476
+ from,
477
+ blocked,
478
+ target,
479
+ targetEpic,
480
+ targetBoard,
481
+ state,
482
+ pullRequest,
483
+ startedAt
484
+ };
485
+ }
486
+ function readHopRecord(root) {
487
+ const file = hopFilePath(root);
488
+ let text;
489
+ try {
490
+ text = readFileSync3(file, "utf8");
491
+ } catch (error) {
492
+ const code = error.code;
493
+ return code === "ENOENT" ? { set: false, reason: "absent", detail: `No hop record at ${file}` } : { set: false, reason: "unreadable", detail: `Cannot read ${file}: ${messageOf(error)}` };
494
+ }
495
+ let parsed;
496
+ try {
497
+ parsed = JSON.parse(text);
498
+ } catch (error) {
499
+ return { set: false, reason: "wrong-shape", detail: `${file} holds no JSON: ${messageOf(error)}` };
500
+ }
501
+ const record = asHopRecord(parsed);
502
+ return record === null ? { set: false, reason: "wrong-shape", detail: `${file} does not hold a hop record` } : { set: true, record };
503
+ }
504
+ function ownKeys(record) {
505
+ const { kind, home, from, blocked, target, targetEpic, targetBoard, state, pullRequest, startedAt } = record;
506
+ return {
507
+ kind,
508
+ home: { board: home.board, epic: home.epic },
509
+ from: { board: from.board, epic: from.epic },
510
+ blocked,
511
+ target,
512
+ targetEpic,
513
+ targetBoard,
514
+ state,
515
+ pullRequest,
516
+ startedAt
517
+ };
518
+ }
519
+ function writeHopRecord(root, record) {
520
+ const file = hopFilePath(root);
521
+ mkdirSync3(dirname4(file), { recursive: true });
522
+ const temporary = `${file}.${String(process.pid)}.${randomUUID2()}.tmp`;
523
+ try {
524
+ writeFileSync2(temporary, `${JSON.stringify(ownKeys(record), null, 2)}
525
+ `);
526
+ renameSync2(temporary, file);
527
+ } catch (error) {
528
+ rmSync2(temporary, { force: true });
529
+ throw error;
530
+ }
531
+ }
532
+ function staleAgainst(record, position) {
533
+ return position.home.board !== record.home.board || position.home.epic !== record.home.epic;
534
+ }
535
+
536
+ // src/loop/sessions.ts
537
+ var SESSION_STATES = Object.freeze(["running", "paused", "stopped", "done"]);
538
+ var RUNS_DIR = "runs";
539
+ var RECORD_EXTENSION = ".json";
540
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
541
+
542
+ class SessionRecordError extends Error {
543
+ file;
544
+ constructor(file, problem) {
545
+ super(`session record ${file}: ${problem}`);
546
+ this.name = "SessionRecordError";
547
+ this.file = file;
548
+ }
549
+ }
550
+
551
+ class SessionConflictError extends Error {
552
+ conflicts;
553
+ constructor(conflicts) {
554
+ const named = conflicts.map((conflict) => `${conflict.record.sessionId} (${conflict.reason})`);
555
+ super(`the plan's sessions refuse the run: ${named.join(", ")}`);
556
+ this.name = "SessionConflictError";
557
+ this.conflicts = conflicts;
558
+ }
559
+ }
560
+
561
+ class SessionStateError extends Error {
562
+ file;
563
+ state;
564
+ constructor(file, state, onlyFrom) {
565
+ const acted = onlyFrom.length === 0 ? "no state" : onlyFrom.join(" or ");
566
+ super(`session record ${file}: stores ${state}, and the change acts on ${acted} alone`);
567
+ this.name = "SessionStateError";
568
+ this.file = file;
569
+ this.state = state;
570
+ }
571
+ }
572
+ function isSessionId(value) {
573
+ return SESSION_ID_PATTERN.test(value);
574
+ }
575
+ function runsDir(root) {
576
+ return join7(scopeAt(root).dir, RUNS_DIR);
577
+ }
578
+ function sessionFilePath(root, sessionId) {
579
+ if (!isSessionId(sessionId)) {
580
+ throw new Error(`session record: unusable session id ${JSON.stringify(sessionId)}`);
581
+ }
582
+ return join7(runsDir(root), `${sessionId}${RECORD_EXTENSION}`);
583
+ }
584
+ function describeValue2(value) {
585
+ return value === undefined ? "missing" : JSON.stringify(value) ?? String(value);
586
+ }
587
+ function field(fields, key) {
588
+ return Object.hasOwn(fields, key) ? fields[key] : undefined;
589
+ }
590
+ function isObject(value) {
591
+ return typeof value === "object" && value !== null && !Array.isArray(value);
592
+ }
593
+ function isPositiveWhole(value) {
594
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
595
+ }
596
+ function isText(value) {
597
+ return typeof value === "string" && value.trim() !== "";
598
+ }
599
+ function taskProblem(task) {
600
+ if (task === null)
601
+ return null;
602
+ if (!isObject(task))
603
+ return `task is ${describeValue2(task)}, expected null or an object`;
604
+ if (!isPositiveWhole(field(task, "line"))) {
605
+ return `task.line is ${describeValue2(field(task, "line"))}, expected a whole number from 1`;
606
+ }
607
+ return isText(field(task, "text")) ? null : `task.text is ${describeValue2(field(task, "text"))}, expected a non-empty string`;
608
+ }
609
+ function hopProblem(fields) {
610
+ if (!Object.hasOwn(fields, "hop"))
611
+ return null;
612
+ const hop2 = field(fields, "hop");
613
+ return asHopRecord(hop2) === null ? `hop is ${describeValue2(hop2)}, expected a hop record` : null;
614
+ }
615
+ function sessionIdProblem(sessionId, file) {
616
+ if (typeof sessionId !== "string" || !isSessionId(sessionId)) {
617
+ return `sessionId is ${describeValue2(sessionId)}, expected a plain file name`;
618
+ }
619
+ return basename(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
620
+ }
621
+ function planStubProblem(planStub) {
622
+ return planStub === null || typeof planStub === "string" && isStampableStub(planStub) ? null : `planStub is ${describeValue2(planStub)}, expected null or a plan stub`;
623
+ }
624
+ function textProblems(fields) {
625
+ return ["plan", "branch"].filter((key) => !isText(field(fields, key))).map((key) => `${key} is ${describeValue2(field(fields, key))}, expected a non-empty string`);
626
+ }
627
+ function recordProblems(fields, file) {
628
+ const pid = field(fields, "pid");
629
+ const startedAt = field(fields, "startedAt");
630
+ const state = field(fields, "state");
631
+ const problems = [
632
+ sessionIdProblem(field(fields, "sessionId"), file),
633
+ planStubProblem(field(fields, "planStub")),
634
+ ...textProblems(fields),
635
+ isPositiveWhole(pid) ? null : `pid is ${describeValue2(pid)}, expected a whole number from 1`,
636
+ typeof startedAt === "string" && !Number.isNaN(Date.parse(startedAt)) ? null : `startedAt is ${describeValue2(startedAt)}, expected a timestamp`,
637
+ SESSION_STATES.includes(state) ? null : `state is ${describeValue2(state)}, expected one of ${SESSION_STATES.join(", ")}`,
638
+ taskProblem(field(fields, "task")),
639
+ hopProblem(fields)
640
+ ];
641
+ return problems.filter((problem) => problem !== null);
642
+ }
643
+ function freezeHop(value) {
644
+ const hop2 = asHopRecord(value);
645
+ return Object.freeze({ ...hop2, home: Object.freeze(hop2.home), from: Object.freeze(hop2.from) });
646
+ }
647
+ function freezeRecord(fields) {
648
+ const task = field(fields, "task");
649
+ const hop2 = field(fields, "hop");
650
+ return Object.freeze({
651
+ sessionId: field(fields, "sessionId"),
652
+ planStub: field(fields, "planStub"),
653
+ plan: field(fields, "plan"),
654
+ branch: field(fields, "branch"),
655
+ pid: field(fields, "pid"),
656
+ startedAt: field(fields, "startedAt"),
657
+ state: field(fields, "state"),
658
+ task: isObject(task) ? Object.freeze({ line: field(task, "line"), text: field(task, "text") }) : null,
659
+ ...hop2 === undefined ? {} : { hop: freezeHop(hop2) }
660
+ });
661
+ }
662
+ function parseSessionRecord(text, file) {
663
+ let parsed;
664
+ try {
665
+ parsed = JSON.parse(text);
666
+ } catch (error) {
667
+ throw new SessionRecordError(file, `holds no JSON: ${messageOf(error)}`);
668
+ }
669
+ if (!isObject(parsed))
670
+ throw new SessionRecordError(file, "holds no JSON object");
671
+ const problems = recordProblems(parsed, file);
672
+ if (problems.length > 0)
673
+ throw new SessionRecordError(file, problems.join("; "));
674
+ return freezeRecord(parsed);
675
+ }
676
+ function errorCode(error) {
677
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
678
+ }
679
+ function isPidAlive(pid) {
680
+ if (!isPositiveWhole(pid))
681
+ return false;
682
+ try {
683
+ process.kill(pid, 0);
684
+ return true;
685
+ } catch (error) {
686
+ return errorCode(error) === "EPERM";
687
+ }
688
+ }
689
+ function readState(record, isAlive = isPidAlive) {
690
+ if (record.state === "stopped" || record.state === "done")
691
+ return record.state;
692
+ return isAlive(record.pid) ? record.state : "stopped";
693
+ }
694
+ function readRecordFile(file) {
695
+ let text;
696
+ try {
697
+ text = readFileSync4(file, "utf8");
698
+ } catch (error) {
699
+ throw new SessionRecordError(file, `cannot be read: ${messageOf(error)}`);
700
+ }
701
+ return parseSessionRecord(text, file);
702
+ }
703
+ function recordNames(dir) {
704
+ try {
705
+ return readdirSync(dir);
706
+ } catch (error) {
707
+ if (errorCode(error) === "ENOENT")
708
+ return [];
709
+ throw new SessionRecordError(dir, `cannot be listed: ${messageOf(error)}`);
710
+ }
711
+ }
712
+ function byStart(a, b) {
713
+ return Date.parse(a.startedAt) - Date.parse(b.startedAt) || a.sessionId.localeCompare(b.sessionId);
714
+ }
715
+ function readSessions(root, seams = {}) {
716
+ const dir = runsDir(root);
717
+ const isAlive = seams.isAlive ?? isPidAlive;
718
+ const records = recordNames(dir).filter((name2) => name2.endsWith(RECORD_EXTENSION)).map((name2) => readRecordFile(join7(dir, name2))).map((record) => Object.freeze({ ...record, state: readState(record, isAlive) }));
719
+ return Object.freeze(records.sort(byStart));
720
+ }
721
+ function readSession(root, sessionId, seams = {}) {
722
+ const record = readRecordFile(sessionFilePath(root, sessionId));
723
+ return Object.freeze({ ...record, state: readState(record, seams.isAlive ?? isPidAlive) });
724
+ }
725
+ function samePlan(a, b) {
726
+ if (a.planStub !== null || b.planStub !== null)
727
+ return a.planStub === b.planStub;
728
+ return a.plan === b.plan;
729
+ }
730
+ function sessionConflicts(records, candidate) {
731
+ return records.filter((record) => samePlan(record, candidate)).flatMap((record) => {
732
+ if (record.branch !== candidate.branch)
733
+ return [{ reason: "branch", record }];
734
+ return record.state === "running" || record.state === "paused" ? [{ reason: "live", record }] : [];
735
+ });
736
+ }
737
+ function checkRecord(record, file) {
738
+ const problems = recordProblems(record, file);
739
+ if (problems.length > 0)
740
+ throw new SessionRecordError(file, `not written: ${problems.join("; ")}`);
741
+ }
742
+ function writeRecordFile(file, record, isNew) {
743
+ checkRecord(record, file);
744
+ mkdirSync4(dirname5(file), { recursive: true });
745
+ const temporary = `${file}.${process.pid}.tmp`;
746
+ writeFileSync3(temporary, `${JSON.stringify(record, null, 2)}
747
+ `);
748
+ try {
749
+ if (isNew)
750
+ linkSync(temporary, file);
751
+ else
752
+ renameSync3(temporary, file);
753
+ } finally {
754
+ rmSync3(temporary, { force: true });
755
+ }
756
+ }
757
+ function beginSession(root, draft, seams = {}) {
758
+ const file = sessionFilePath(root, draft.sessionId);
759
+ const record = freezeRecord({ ...draft, state: "running", task: null });
760
+ const conflicts = sessionConflicts(readSessions(root, seams), record);
761
+ if (conflicts.length > 0)
762
+ throw new SessionConflictError(conflicts);
763
+ writeRecordFile(file, record, true);
764
+ return record;
765
+ }
766
+ function updateSession(root, sessionId, change) {
767
+ const file = sessionFilePath(root, sessionId);
768
+ const stored = readRecordFile(file);
769
+ if (change.onlyFrom !== undefined && !change.onlyFrom.includes(stored.state)) {
770
+ throw new SessionStateError(file, stored.state, change.onlyFrom);
771
+ }
772
+ const record = freezeRecord({
773
+ ...stored,
774
+ state: change.state ?? stored.state,
775
+ task: change.task === undefined ? stored.task : change.task
776
+ });
777
+ writeRecordFile(file, record, false);
778
+ return record;
779
+ }
780
+
781
+ // src/runtime/identity.ts
782
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync as realpathSync3 } from "fs";
783
+ import { dirname as dirname6, join as join8, resolve as resolve2 } from "path";
784
+ var MANIFEST = "package.json";
785
+ var CHECKOUT_ENTRY = join8("src", "rafa.ts");
786
+ function realOrResolved(path) {
787
+ const resolved = resolve2(path);
788
+ return existsSync4(resolved) ? realpathSync3(resolved) : resolved;
789
+ }
790
+ function manifestName(file) {
791
+ let parsed;
792
+ try {
793
+ parsed = JSON.parse(readFileSync5(file, "utf8"));
794
+ } catch {
795
+ return null;
796
+ }
797
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
798
+ return null;
799
+ const name2 = parsed["name"];
800
+ return typeof name2 === "string" ? name2 : null;
801
+ }
802
+ function nearestRafaPackage(start) {
803
+ let dir = start;
804
+ for (;; ) {
805
+ const manifest = join8(dir, MANIFEST);
806
+ if (existsSync4(manifest) && manifestName(manifest) === name)
807
+ return dir;
808
+ const parent = dirname6(dir);
809
+ if (parent === dir)
810
+ return null;
811
+ dir = parent;
812
+ }
813
+ }
814
+ function readRuntimeIdentity(entry = Bun.main) {
815
+ const real = realOrResolved(entry);
816
+ const packageDir = nearestRafaPackage(dirname6(real));
817
+ if (packageDir !== null && existsSync4(join8(packageDir, CHECKOUT_ENTRY))) {
818
+ return { kind: "development", entry: real, checkout: packageDir };
819
+ }
820
+ return { kind: "installed", entry: real };
821
+ }
822
+
823
+ // src/effort/store/development-build.ts
824
+ var DEVELOPMENT_NEXT_STEP = "rafa effort copy";
825
+ var ADOPTION_NAME = "schema_migrations";
826
+ var EFFORT_DIR_NAME = "effort";
827
+ var DEVELOPMENT_MARK = "+dev:";
828
+ function appliedByName(identity) {
829
+ return identity.kind === "development" ? `${RAFA_VERSION}${DEVELOPMENT_MARK}${identity.checkout}` : RAFA_VERSION;
830
+ }
831
+
832
+ class DevelopmentBuildRefusedError extends Error {
833
+ path;
834
+ nextStep;
835
+ constructor(path, message) {
836
+ super(message);
837
+ this.name = "DevelopmentBuildRefusedError";
838
+ this.path = path;
839
+ this.nextStep = DEVELOPMENT_NEXT_STEP;
840
+ }
841
+ }
842
+ function storeProjectRoot(path) {
843
+ const storeDir = dirname7(path);
844
+ const scopeDir = dirname7(storeDir);
845
+ if (basename2(storeDir) !== EFFORT_DIR_NAME || basename2(scopeDir) !== SCOPE_DIR)
846
+ return null;
847
+ return dirname7(scopeDir);
848
+ }
849
+ function isOwnedStore(path, env, tempDir) {
850
+ if (isUnderTempDir(path, tempDir))
851
+ return true;
852
+ const override = env[EFFORT_DIR_VARIABLE];
853
+ if (override === undefined || override === "" || !isAbsolute3(override))
854
+ return false;
855
+ return storeProjectRoot(path) === null && isUnderTempDir(path, override);
856
+ }
857
+ function loopSentence(record) {
858
+ return ` Loop ${record.sessionId} (pid ${String(record.pid)}, plan ${record.planStub ?? record.plan}) is running on this store.`;
859
+ }
860
+ function liveLoopSentences(path, isAlive) {
861
+ const root = storeProjectRoot(path);
862
+ if (root === null)
863
+ return "";
864
+ try {
865
+ return readSessions(root, { isAlive }).filter((record) => record.state === "running" || record.state === "paused").map(loopSentence).join("");
866
+ } catch (error) {
867
+ return ` The loop records under ${runsDir(root)} could not be read (${messageOf(error)}).`;
868
+ }
869
+ }
870
+ function developmentRefusalText(path, needs, checkout, loops) {
871
+ return `effort store: ${path} needs migration ${needs.join(", ")} and this rafa is a development build (${checkout});` + ` a development build migrates only a store under the temp directory or ${EFFORT_DIR_VARIABLE}.` + ` Copy it with '${DEVELOPMENT_NEXT_STEP}' and run this command with ${EFFORT_DIR_VARIABLE}=<the copy>.` + loops;
872
+ }
873
+ function refuseUnownedDevelopmentWrite(path, needs, probe = {}) {
874
+ const identity = probe.identity ?? readRuntimeIdentity();
875
+ if (identity.kind !== "development")
876
+ return;
877
+ if (isOwnedStore(path, probe.env ?? process.env, probe.tempDir ?? tmpdir2()))
878
+ return;
879
+ const loops = liveLoopSentences(path, probe.isAlive ?? isPidAlive);
880
+ throw new DevelopmentBuildRefusedError(path, developmentRefusalText(path, needs, identity.checkout, loops));
881
+ }
882
+
180
883
  // src/effort/store/migrations.ts
884
+ import { createHash } from "crypto";
885
+ var LEGACY_GATE_OPEN = 13;
886
+ var LEGACY_GATE_CLOSED = 1000;
181
887
  var SQLITE_MIGRATIONS = [
182
- `
888
+ {
889
+ id: "kind-tables",
890
+ breaks: [],
891
+ sql: `
183
892
  CREATE TABLE sessions (
184
893
  seq INTEGER PRIMARY KEY,
185
894
  session_id TEXT NOT NULL UNIQUE CHECK (session_id <> ''),
@@ -191,8 +900,12 @@ var SQLITE_MIGRATIONS = [
191
900
  sha TEXT NOT NULL UNIQUE CHECK (sha <> ''),
192
901
  row_json TEXT NOT NULL
193
902
  );
194
- `,
195
903
  `
904
+ },
905
+ {
906
+ id: "findings",
907
+ breaks: [],
908
+ sql: `
196
909
  CREATE TABLE findings (
197
910
  seq INTEGER PRIMARY KEY,
198
911
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -220,8 +933,12 @@ var SQLITE_MIGRATIONS = [
220
933
  CREATE UNIQUE INDEX findings_by_trigger_what
221
934
  ON findings (session_id, trigger, what)
222
935
  WHERE artifact IS NULL;
223
- `,
224
936
  `
937
+ },
938
+ {
939
+ id: "blockers-and-bugs",
940
+ breaks: [],
941
+ sql: `
225
942
  CREATE TABLE blockers (
226
943
  seq INTEGER PRIMARY KEY,
227
944
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -252,8 +969,12 @@ var SQLITE_MIGRATIONS = [
252
969
 
253
970
  CREATE UNIQUE INDEX out_of_scope_bugs_by_entry
254
971
  ON out_of_scope_bugs (session_id, what, ifnull(artifact, ''), ifnull(security, -1));
255
- `,
256
972
  `
973
+ },
974
+ {
975
+ id: "report-absences",
976
+ breaks: [],
977
+ sql: `
257
978
  CREATE TABLE report_absences (
258
979
  seq INTEGER PRIMARY KEY,
259
980
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -266,8 +987,12 @@ var SQLITE_MIGRATIONS = [
266
987
  outcome TEXT NOT NULL,
267
988
  collected_at TEXT NOT NULL
268
989
  );
269
- `,
270
990
  `
991
+ },
992
+ {
993
+ id: "task-reports",
994
+ breaks: [],
995
+ sql: `
271
996
  CREATE TABLE task_reports (
272
997
  seq INTEGER PRIMARY KEY,
273
998
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -278,8 +1003,12 @@ var SQLITE_MIGRATIONS = [
278
1003
  outcome TEXT NOT NULL,
279
1004
  collected_at TEXT NOT NULL
280
1005
  );
281
- `,
282
1006
  `
1007
+ },
1008
+ {
1009
+ id: "preflight",
1010
+ breaks: [],
1011
+ sql: `
283
1012
  CREATE TABLE preflight (
284
1013
  seq INTEGER PRIMARY KEY,
285
1014
  run_id TEXT NOT NULL CHECK (run_id <> ''),
@@ -295,8 +1024,12 @@ var SQLITE_MIGRATIONS = [
295
1024
  UNIQUE (run_id, position),
296
1025
  CHECK ((outcome = 'pass') = (failure IS NULL))
297
1026
  );
298
- `,
299
1027
  `
1028
+ },
1029
+ {
1030
+ id: "dispatches",
1031
+ breaks: [],
1032
+ sql: `
300
1033
  CREATE TABLE dispatches (
301
1034
  seq INTEGER PRIMARY KEY,
302
1035
  session_id TEXT NOT NULL UNIQUE CHECK (session_id <> ''),
@@ -311,8 +1044,12 @@ var SQLITE_MIGRATIONS = [
311
1044
  flags TEXT NOT NULL,
312
1045
  collected_at TEXT NOT NULL
313
1046
  );
314
- `,
315
1047
  `
1048
+ },
1049
+ {
1050
+ id: "changes",
1051
+ breaks: [],
1052
+ sql: `
316
1053
  CREATE TABLE changes (
317
1054
  seq INTEGER PRIMARY KEY,
318
1055
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -328,20 +1065,32 @@ var SQLITE_MIGRATIONS = [
328
1065
 
329
1066
  CREATE UNIQUE INDEX changes_by_entry
330
1067
  ON changes (session_id, level, ifnull(area, ''), summary);
331
- `,
332
1068
  `
1069
+ },
1070
+ {
1071
+ id: "out-of-scope-bug-scope",
1072
+ breaks: [],
1073
+ sql: `
333
1074
  ALTER TABLE out_of_scope_bugs
334
1075
  ADD COLUMN scope TEXT CHECK (scope IN ('machine', 'rafa'));
335
- `,
336
1076
  `
1077
+ },
1078
+ {
1079
+ id: "dispatch-skills",
1080
+ breaks: [],
1081
+ sql: `
337
1082
  ALTER TABLE dispatches
338
1083
  ADD COLUMN resolver TEXT CHECK (resolver IN ('planner', 'tag', 'none'));
339
1084
  ALTER TABLE dispatches
340
1085
  ADD COLUMN skills_offered TEXT CHECK (skills_offered IS NULL OR json_type(skills_offered) = 'array');
341
1086
  ALTER TABLE dispatches
342
1087
  ADD COLUMN lessons_offered TEXT CHECK (lessons_offered IS NULL OR json_type(lessons_offered) = 'array');
343
- `,
344
1088
  `
1089
+ },
1090
+ {
1091
+ id: "skill-invocations",
1092
+ breaks: [],
1093
+ sql: `
345
1094
  CREATE TABLE skill_invocations (
346
1095
  seq INTEGER PRIMARY KEY,
347
1096
  session_id TEXT NOT NULL CHECK (session_id <> ''),
@@ -353,12 +1102,20 @@ var SQLITE_MIGRATIONS = [
353
1102
 
354
1103
  CREATE UNIQUE INDEX skill_invocations_by_use
355
1104
  ON skill_invocations (session_id, ifnull(name, ''), ifnull(sidechain, -1));
356
- `,
357
1105
  `
1106
+ },
1107
+ {
1108
+ id: "task-report-skills",
1109
+ breaks: [],
1110
+ sql: `
358
1111
  ALTER TABLE task_reports
359
1112
  ADD COLUMN skills_used TEXT CHECK (skills_used IS NULL OR json_type(skills_used) = 'array');
360
- `,
361
1113
  `
1114
+ },
1115
+ {
1116
+ id: "plan-ci",
1117
+ breaks: [],
1118
+ sql: `
362
1119
  CREATE TABLE plan_ci (
363
1120
  seq INTEGER PRIMARY KEY,
364
1121
  plan_stub TEXT NOT NULL CHECK (plan_stub <> ''),
@@ -371,13 +1128,555 @@ var SQLITE_MIGRATIONS = [
371
1128
  CHECK ((verdict = 'red') = (json_array_length(failing) > 0))
372
1129
  );
373
1130
  `
1131
+ },
1132
+ {
1133
+ id: "row-origins",
1134
+ breaks: [],
1135
+ sql: `
1136
+ ALTER TABLE sessions ADD COLUMN origin_store TEXT;
1137
+ ALTER TABLE sessions ADD COLUMN origin_seq INTEGER;
1138
+ CREATE UNIQUE INDEX sessions_by_origin
1139
+ ON sessions (origin_store, origin_seq)
1140
+ WHERE origin_store IS NOT NULL;
1141
+
1142
+ ALTER TABLE commits ADD COLUMN origin_store TEXT;
1143
+ ALTER TABLE commits ADD COLUMN origin_seq INTEGER;
1144
+ CREATE UNIQUE INDEX commits_by_origin
1145
+ ON commits (origin_store, origin_seq)
1146
+ WHERE origin_store IS NOT NULL;
1147
+
1148
+ ALTER TABLE findings ADD COLUMN origin_store TEXT;
1149
+ ALTER TABLE findings ADD COLUMN origin_seq INTEGER;
1150
+ CREATE UNIQUE INDEX findings_by_origin
1151
+ ON findings (origin_store, origin_seq)
1152
+ WHERE origin_store IS NOT NULL;
1153
+
1154
+ ALTER TABLE blockers ADD COLUMN origin_store TEXT;
1155
+ ALTER TABLE blockers ADD COLUMN origin_seq INTEGER;
1156
+ CREATE UNIQUE INDEX blockers_by_origin
1157
+ ON blockers (origin_store, origin_seq)
1158
+ WHERE origin_store IS NOT NULL;
1159
+
1160
+ ALTER TABLE out_of_scope_bugs ADD COLUMN origin_store TEXT;
1161
+ ALTER TABLE out_of_scope_bugs ADD COLUMN origin_seq INTEGER;
1162
+ CREATE UNIQUE INDEX out_of_scope_bugs_by_origin
1163
+ ON out_of_scope_bugs (origin_store, origin_seq)
1164
+ WHERE origin_store IS NOT NULL;
1165
+
1166
+ ALTER TABLE report_absences ADD COLUMN origin_store TEXT;
1167
+ ALTER TABLE report_absences ADD COLUMN origin_seq INTEGER;
1168
+ CREATE UNIQUE INDEX report_absences_by_origin
1169
+ ON report_absences (origin_store, origin_seq)
1170
+ WHERE origin_store IS NOT NULL;
1171
+
1172
+ ALTER TABLE task_reports ADD COLUMN origin_store TEXT;
1173
+ ALTER TABLE task_reports ADD COLUMN origin_seq INTEGER;
1174
+ CREATE UNIQUE INDEX task_reports_by_origin
1175
+ ON task_reports (origin_store, origin_seq)
1176
+ WHERE origin_store IS NOT NULL;
1177
+
1178
+ ALTER TABLE preflight ADD COLUMN origin_store TEXT;
1179
+ ALTER TABLE preflight ADD COLUMN origin_seq INTEGER;
1180
+ CREATE UNIQUE INDEX preflight_by_origin
1181
+ ON preflight (origin_store, origin_seq)
1182
+ WHERE origin_store IS NOT NULL;
1183
+
1184
+ ALTER TABLE dispatches ADD COLUMN origin_store TEXT;
1185
+ ALTER TABLE dispatches ADD COLUMN origin_seq INTEGER;
1186
+ CREATE UNIQUE INDEX dispatches_by_origin
1187
+ ON dispatches (origin_store, origin_seq)
1188
+ WHERE origin_store IS NOT NULL;
1189
+
1190
+ ALTER TABLE changes ADD COLUMN origin_store TEXT;
1191
+ ALTER TABLE changes ADD COLUMN origin_seq INTEGER;
1192
+ CREATE UNIQUE INDEX changes_by_origin
1193
+ ON changes (origin_store, origin_seq)
1194
+ WHERE origin_store IS NOT NULL;
1195
+
1196
+ ALTER TABLE skill_invocations ADD COLUMN origin_store TEXT;
1197
+ ALTER TABLE skill_invocations ADD COLUMN origin_seq INTEGER;
1198
+ CREATE UNIQUE INDEX skill_invocations_by_origin
1199
+ ON skill_invocations (origin_store, origin_seq)
1200
+ WHERE origin_store IS NOT NULL;
1201
+
1202
+ ALTER TABLE plan_ci ADD COLUMN origin_store TEXT;
1203
+ ALTER TABLE plan_ci ADD COLUMN origin_seq INTEGER;
1204
+ CREATE UNIQUE INDEX plan_ci_by_origin
1205
+ ON plan_ci (origin_store, origin_seq)
1206
+ WHERE origin_store IS NOT NULL;
1207
+ `
1208
+ },
1209
+ {
1210
+ id: "store-meta",
1211
+ breaks: [],
1212
+ sql: `
1213
+ CREATE TABLE store_meta (
1214
+ id INTEGER PRIMARY KEY CHECK (id = 1),
1215
+ store_id TEXT NOT NULL CHECK (store_id <> ''),
1216
+ project_root_commit TEXT NOT NULL CHECK (project_root_commit <> ''),
1217
+ project_remote TEXT CHECK (project_remote <> ''),
1218
+ host_id TEXT NOT NULL CHECK (host_id <> ''),
1219
+ store_path TEXT NOT NULL CHECK (store_path <> ''),
1220
+ file_dev INTEGER NOT NULL CHECK (typeof(file_dev) = 'integer'),
1221
+ file_ino INTEGER NOT NULL CHECK (typeof(file_ino) = 'integer'),
1222
+ minted_at TEXT NOT NULL CHECK (minted_at <> '')
1223
+ );
1224
+
1225
+ CREATE TABLE merges (
1226
+ seq INTEGER PRIMARY KEY,
1227
+ id TEXT NOT NULL UNIQUE CHECK (id <> ''),
1228
+ other_store TEXT CHECK (other_store <> ''),
1229
+ merged_at TEXT NOT NULL CHECK (merged_at <> ''),
1230
+ rows_added INTEGER NOT NULL CHECK (typeof(rows_added) = 'integer' AND rows_added >= 0),
1231
+ rows_skipped INTEGER NOT NULL CHECK (typeof(rows_skipped) = 'integer' AND rows_skipped >= 0),
1232
+ rows_in_conflict INTEGER NOT NULL CHECK (typeof(rows_in_conflict) = 'integer' AND rows_in_conflict >= 0)
1233
+ );
1234
+
1235
+ CREATE TABLE merge_conflicts (
1236
+ seq INTEGER PRIMARY KEY,
1237
+ merge_id TEXT NOT NULL CHECK (merge_id <> ''),
1238
+ table_name TEXT NOT NULL CHECK (table_name <> ''),
1239
+ local_seq INTEGER NOT NULL CHECK (typeof(local_seq) = 'integer'),
1240
+ field TEXT CHECK (field <> ''),
1241
+ incoming TEXT NOT NULL CHECK (json_valid(incoming) AND json_type(incoming) = 'object'),
1242
+ recorded_at TEXT NOT NULL CHECK (recorded_at <> '')
1243
+ );
1244
+ `
1245
+ }
374
1246
  ];
375
1247
  var SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.length;
1248
+ function legacyGate(applied) {
1249
+ return applied.every(({ breaks }) => breaks.length === 0) ? LEGACY_GATE_OPEN : LEGACY_GATE_CLOSED;
1250
+ }
1251
+ function migrationChecksum(migration) {
1252
+ return createHash("sha256").update(migration.sql, "utf8").digest("hex");
1253
+ }
1254
+
1255
+ // src/effort/store/schema-plan.ts
1256
+ var FIX_SCHEMA_STEP = "rafa effort fix-schema --dry-run";
1257
+ function sqliteCatalogue(migrations) {
1258
+ return migrations.map(({ sql, ...spec }) => ({ ...spec, sha256: migrationChecksum({ sql }) }));
1259
+ }
1260
+ function planSchema(store, catalogue, access, caller) {
1261
+ const { path, userVersion, log } = store;
1262
+ if (log === null && (userVersion < 0 || userVersion > LEGACY_GATE_OPEN)) {
1263
+ return refuse("pre-log-unreleased", FIX_SCHEMA_STEP, `effort store: ${path} is at schema version` + ` ${String(userVersion)} with no migration log; no released rafa wrote that.` + " Refusing to read or write it.");
1264
+ }
1265
+ if (log !== null && userVersion !== LEGACY_GATE_OPEN && userVersion !== LEGACY_GATE_CLOSED) {
1266
+ return refuse("gate-mismatch", FIX_SCHEMA_STEP, `effort store: ${path} has a migration log and` + ` schema version ${String(userVersion)}; a build older than the log migrated it after the` + " log was made. Refusing to read or write it.");
1267
+ }
1268
+ const adopted = log === null ? adoptedEntries(catalogue, userVersion) : [];
1269
+ const held = log ?? [];
1270
+ const logRefusal = judgeLog(path, held, catalogue);
1271
+ if (logRefusal !== null)
1272
+ return logRefusal;
1273
+ const heldIds = new Set([...adopted, ...held].map(({ id }) => id));
1274
+ const pending = catalogue.filter(({ id }) => !heldIds.has(id));
1275
+ const unknown = held.filter(({ id }) => !catalogue.some((entry) => entry.id === id));
1276
+ const writes = access === "write" || pending.length > 0;
1277
+ const writersBroken = unknown.find(({ breaks }) => breaks.includes("writers"));
1278
+ if (writes && writersBroken !== undefined) {
1279
+ return refuseUnknown("unknown-breaks-writers", path, writersBroken);
1280
+ }
1281
+ const orderRefusal = judgePending(path, catalogue, heldIds, pending, caller);
1282
+ if (orderRefusal !== null)
1283
+ return orderRefusal;
1284
+ return {
1285
+ verdict: "use",
1286
+ adopted,
1287
+ pending,
1288
+ unknown,
1289
+ gate: legacyGate([...adopted, ...held, ...pending])
1290
+ };
1291
+ }
1292
+ function adoptedEntries(catalogue, count) {
1293
+ const adopted = catalogue.slice(0, count);
1294
+ if (adopted.length < count) {
1295
+ throw new Error(`schema plan: the catalogue holds ${String(catalogue.length)} migrations,` + ` fewer than the ${String(count)} legacy entries a store at that version holds`);
1296
+ }
1297
+ return adopted;
1298
+ }
1299
+ function judgeLog(path, log, catalogue) {
1300
+ const byId = new Map(catalogue.map((entry) => [entry.id, entry]));
1301
+ for (const row of log) {
1302
+ const known = byId.get(row.id);
1303
+ if (known !== undefined && known.sha256 !== row.sha256) {
1304
+ return refuse("edited", FIX_SCHEMA_STEP, `effort store: ${path} recorded migration ${row.id}` + ` as sha256 ${row.sha256}, and this rafa holds it as ${known.sha256}; one was edited` + " after it ran. Refusing to read or write the store.");
1305
+ }
1306
+ }
1307
+ const readersBroken = log.find(({ id, breaks }) => !byId.has(id) && breaks.some((word) => word !== "writers"));
1308
+ return readersBroken === undefined ? null : refuseUnknown("unknown-breaks-readers", path, readersBroken);
1309
+ }
1310
+ function judgePending(path, catalogue, heldIds, pending, caller) {
1311
+ const breaking = pending.filter(({ breaks }) => breaks.length > 0);
1312
+ for (const entry of breaking) {
1313
+ const later = catalogue.slice(catalogue.indexOf(entry) + 1).find(({ id }) => heldIds.has(id));
1314
+ if (later !== undefined) {
1315
+ return refuse("breaking-out-of-order", "rafa effort schema", `effort store: ${path} needs` + ` migration ${entry.id} ahead of ${later.id}, which is already applied; a migration that` + " breaks older runtimes applies only in order. Refusing to read or write the store.");
1316
+ }
1317
+ }
1318
+ const [first] = breaking;
1319
+ if (first === undefined || caller === "migrate")
1320
+ return null;
1321
+ return refuse("breaking-pending", "rafa effort migrate --dry-run", `effort store: ${path} needs` + ` migration ${first.id}, which breaks older runtimes and is applied only by` + " 'rafa effort migrate'; refusing to read or write the store until then.");
1322
+ }
1323
+ function refuseUnknown(reason, path, row) {
1324
+ const ending = reason === "unknown-breaks-readers" ? "which breaks older readers; refusing to read or write it." : "which breaks older writers; refusing to write it.";
1325
+ return refuse(reason, `install a rafa that knows ${row.id}, or ${FIX_SCHEMA_STEP}`, "effort store:" + ` ${path} holds migration ${row.id} (applied by ${row.appliedBy} on ${dateOf(row.appliedAt)}),` + ` which this rafa does not know and ${ending}`);
1326
+ }
1327
+ function dateOf(appliedAt) {
1328
+ return /^\d{4}-\d{2}-\d{2}/.test(appliedAt) ? appliedAt.slice(0, 10) : appliedAt;
1329
+ }
1330
+ function refuse(reason, nextStep, text) {
1331
+ return { verdict: "refuse", reason, message: `${text} Next safe step: ${nextStep}`, nextStep };
1332
+ }
1333
+
1334
+ // src/effort/store/bring-forward.ts
1335
+ var MIGRATION_LOG_TABLE = "schema_migrations";
1336
+ var CREATE_MIGRATION_LOG = `
1337
+ CREATE TABLE IF NOT EXISTS ${MIGRATION_LOG_TABLE} (
1338
+ seq INTEGER PRIMARY KEY,
1339
+ id TEXT NOT NULL UNIQUE CHECK (id <> ''),
1340
+ sha256 TEXT NOT NULL CHECK (length(sha256) = 64),
1341
+ breaks TEXT NOT NULL,
1342
+ applied_at TEXT NOT NULL,
1343
+ applied_by TEXT NOT NULL CHECK (applied_by <> '')
1344
+ );
1345
+ `;
1346
+
1347
+ class SchemaRefusedError extends Error {
1348
+ reason;
1349
+ nextStep;
1350
+ constructor(refusal) {
1351
+ super(refusal.message);
1352
+ this.name = "SchemaRefusedError";
1353
+ this.reason = refusal.reason;
1354
+ this.nextStep = refusal.nextStep;
1355
+ }
1356
+ }
1357
+ function bringForward(db, path, access, caller, options = {}) {
1358
+ const migrations = options.migrations ?? SQLITE_MIGRATIONS;
1359
+ const catalogue = sqliteCatalogue(migrations);
1360
+ const first = usablePlan(readStoreSchema(db, path), catalogue, access, caller);
1361
+ if (!hasWork(first))
1362
+ return nothingWritten(db);
1363
+ if (options.builtAside !== true)
1364
+ refuseUnownedDevelopmentWrite(path, writeNames(first), options);
1365
+ const apply = db.transaction(() => {
1366
+ const store = readStoreSchema(db, path);
1367
+ const plan = usablePlan(store, catalogue, access, caller);
1368
+ if (!hasWork(plan))
1369
+ return nothingWritten(db);
1370
+ const stamp = {
1371
+ appliedAt: (options.now ?? (() => new Date))().toISOString(),
1372
+ appliedBy: options.appliedBy ?? RAFA_VERSION
1373
+ };
1374
+ db.run(CREATE_MIGRATION_LOG);
1375
+ for (const entry of plan.adopted)
1376
+ logMigration(db, entry, stamp);
1377
+ for (const entry of plan.pending) {
1378
+ db.run(bodyOf(migrations, entry.id));
1379
+ logMigration(db, entry, stamp);
1380
+ }
1381
+ db.run(`PRAGMA user_version = ${String(plan.gate)}`);
1382
+ return {
1383
+ adopted: plan.adopted.map(({ id }) => id),
1384
+ applied: plan.pending.map(({ id }) => id),
1385
+ userVersion: plan.gate
1386
+ };
1387
+ });
1388
+ return apply.immediate();
1389
+ }
1390
+ function readStoreSchema(db, path) {
1391
+ return { path, userVersion: readUserVersion(db), log: readMigrationLog(db, path) };
1392
+ }
1393
+ function usablePlan(store, catalogue, access, caller) {
1394
+ const plan = planSchema(store, catalogue, access, caller);
1395
+ if (plan.verdict === "refuse")
1396
+ throw new SchemaRefusedError(plan);
1397
+ return plan;
1398
+ }
1399
+ function hasWork(plan) {
1400
+ return plan.adopted.length > 0 || plan.pending.length > 0;
1401
+ }
1402
+ function writeNames(plan) {
1403
+ const adoption = plan.adopted.length > 0 ? [ADOPTION_NAME] : [];
1404
+ return [...adoption, ...plan.pending.map(({ id }) => id)];
1405
+ }
1406
+ function nothingWritten(db) {
1407
+ return { adopted: [], applied: [], userVersion: readUserVersion(db) };
1408
+ }
1409
+ function readUserVersion(db) {
1410
+ const row = db.query("PRAGMA user_version").get();
1411
+ return row?.user_version ?? 0;
1412
+ }
1413
+ function readMigrationLog(db, path) {
1414
+ const table = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(MIGRATION_LOG_TABLE);
1415
+ if (table === null)
1416
+ return null;
1417
+ return db.query(`SELECT id, sha256, breaks, applied_at, applied_by FROM ${MIGRATION_LOG_TABLE} ORDER BY seq`).all().map((row) => ({
1418
+ id: row.id,
1419
+ sha256: row.sha256,
1420
+ breaks: parseBreaks(row.breaks, row.id, path),
1421
+ appliedAt: row.applied_at,
1422
+ appliedBy: row.applied_by
1423
+ }));
1424
+ }
1425
+ function parseBreaks(text, id, path) {
1426
+ const refused = new Error(`effort store: ${path} logs migration ${id} with breaks ${text},` + " which is not a JSON array of words; refusing to read or write it");
1427
+ let parsed;
1428
+ try {
1429
+ parsed = JSON.parse(text);
1430
+ } catch (cause) {
1431
+ throw new Error(refused.message, { cause });
1432
+ }
1433
+ if (!Array.isArray(parsed) || !parsed.every((word) => typeof word === "string"))
1434
+ throw refused;
1435
+ return parsed;
1436
+ }
1437
+ function bodyOf(migrations, id) {
1438
+ const migration = migrations.find((candidate) => candidate.id === id);
1439
+ if (migration === undefined)
1440
+ throw new Error(`bring forward: no migration ${id} in the catalogue it planned from`);
1441
+ return migration.sql;
1442
+ }
1443
+ function logMigration(db, entry, stamp) {
1444
+ db.run(`INSERT INTO ${MIGRATION_LOG_TABLE} (id, sha256, breaks, applied_at, applied_by) VALUES (?, ?, ?, ?, ?)`, [entry.id, entry.sha256, JSON.stringify(entry.breaks), stamp.appliedAt, stamp.appliedBy]);
1445
+ }
1446
+
1447
+ // src/effort/store/origins.ts
1448
+ var ACROSS_STORES_ORDER = "collected_at, origin_store, origin_seq, seq";
1449
+ var STAMPED_COLUMNS = "seq, origin_store, origin_seq";
1450
+ var STORE_ID = "(SELECT store_id FROM store_meta WHERE id = 1)";
1451
+ function stampedValues(table) {
1452
+ const nextSeq = `(SELECT COALESCE(MAX(seq), 0) + 1 FROM ${table})`;
1453
+ return `${nextSeq}, ${STORE_ID}, CASE WHEN ${STORE_ID} IS NULL THEN NULL ELSE ${nextSeq} END`;
1454
+ }
1455
+
1456
+ // src/effort/store/settings.ts
1457
+ var DEFAULT_STORE_SETTINGS = Object.freeze({
1458
+ busyTimeoutMs: CONFIG_DEFAULTS.effortBusyTimeoutMs
1459
+ });
1460
+ var active = DEFAULT_STORE_SETTINGS;
1461
+ function setActiveStoreSettings(settings) {
1462
+ if (settings === null) {
1463
+ active = DEFAULT_STORE_SETTINGS;
1464
+ return;
1465
+ }
1466
+ if (!isBusyTimeoutMs(settings.busyTimeoutMs)) {
1467
+ throw new RangeError(`effort store: busy timeout ${String(settings.busyTimeoutMs)} is not a whole number ` + `of milliseconds from ${String(BUSY_TIMEOUT_MIN_MS)} to ${String(BUSY_TIMEOUT_MAX_MS)}`);
1468
+ }
1469
+ active = Object.freeze({ busyTimeoutMs: settings.busyTimeoutMs });
1470
+ }
1471
+ function activeStoreSettings() {
1472
+ return active;
1473
+ }
1474
+
1475
+ // src/effort/store/store-meta.ts
1476
+ import { randomUUID as randomUUID3 } from "crypto";
1477
+ import { dirname as dirname8 } from "path";
1478
+
1479
+ // src/effort/store/store-identity.ts
1480
+ import { spawnSync as spawnSync3 } from "child_process";
1481
+ import { createHmac } from "crypto";
1482
+ import { readFileSync as readFileSync6, realpathSync as realpathSync4, statSync } from "fs";
1483
+ import { hostname, platform as currentPlatform } from "os";
1484
+
1485
+ // src/pr/git.ts
1486
+ import { spawnSync } from "child_process";
1487
+ function createGitRunner(cwd) {
1488
+ return (args) => {
1489
+ const result = spawnSync("git", [...args], {
1490
+ cwd,
1491
+ encoding: "utf8",
1492
+ env: { ...process.env, LC_ALL: "C" }
1493
+ });
1494
+ if (result.error !== undefined) {
1495
+ return { ok: false, stdout: "", stderr: `could not run git in ${cwd}: ${messageOf(result.error)}` };
1496
+ }
1497
+ return { ok: result.status === 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
1498
+ };
1499
+ }
1500
+ function gitSaid(result) {
1501
+ return [result.stderr, result.stdout].map((stream) => stream.trim()).filter((stream) => stream !== "").join(`
1502
+ `);
1503
+ }
1504
+
1505
+ // src/schema/project-id.ts
1506
+ import { spawnSync as spawnSync2 } from "child_process";
1507
+ var PROJECT_ID_LENGTH = 12;
1508
+ var PROJECT_ID_PATTERN = new RegExp(`^[0-9a-f]{${PROJECT_ID_LENGTH}}$`);
1509
+ function hasColonAfterAt(url) {
1510
+ const at = url.indexOf("@");
1511
+ return at !== -1 && url.lastIndexOf(":") > at;
1512
+ }
1513
+ function normalizeRemote(url) {
1514
+ const stripped = url.replace(/:\/\/[^@]+@/, "://");
1515
+ const network = !stripped.startsWith("file://") && (stripped.includes("://") || hasColonAfterAt(stripped));
1516
+ const trimmed = stripped.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "").replace(/^[^@/:]+@([^:/]+):/, "$1/").replace(/\.git\/?$/, "").replace(/\/+$/, "");
1517
+ return network ? trimmed.toLowerCase() : trimmed;
1518
+ }
1519
+ function gitRemoteUrl(dir) {
1520
+ const result = spawnSync2("git", ["remote", "get-url", "origin"], {
1521
+ cwd: dir,
1522
+ encoding: "utf8",
1523
+ env: { ...process.env, LC_ALL: "C" }
1524
+ });
1525
+ if (result.error !== undefined || result.status !== 0)
1526
+ return null;
1527
+ const url = result.stdout.trim();
1528
+ return url === "" ? null : url;
1529
+ }
1530
+
1531
+ // src/effort/store/store-identity.ts
1532
+ var HOST_ID_KEY = "rafa effort store host id";
1533
+ var MACHINE_ID_PATH = "/etc/machine-id";
1534
+ var IOREG_ARGS = ["-rd1", "-c", "IOPlatformExpertDevice"];
1535
+ function hostIdFrom(source, value) {
1536
+ return createHmac("sha256", HOST_ID_KEY).update(`${source}:${value}`).digest("hex");
1537
+ }
1538
+ function readMachineIdFile() {
1539
+ try {
1540
+ const value = readFileSync6(MACHINE_ID_PATH, "utf8").trim();
1541
+ return value === "" ? null : value;
1542
+ } catch {
1543
+ return null;
1544
+ }
1545
+ }
1546
+ function parseIoregPlatformUuid(output) {
1547
+ const match = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(output);
1548
+ return match?.[1] ?? null;
1549
+ }
1550
+ function readIoregPlatformUuid() {
1551
+ const result = spawnSync3("ioreg", [...IOREG_ARGS], { encoding: "utf8" });
1552
+ if (result.error !== undefined || result.status !== 0)
1553
+ return null;
1554
+ return parseIoregPlatformUuid(result.stdout);
1555
+ }
1556
+ function readHostId(seams = {}) {
1557
+ const platform = seams.platform ?? currentPlatform();
1558
+ if (platform === "linux") {
1559
+ const machineId = (seams.readMachineId ?? readMachineIdFile)();
1560
+ if (machineId !== null)
1561
+ return hostIdFrom("machine-id", machineId);
1562
+ }
1563
+ if (platform === "darwin") {
1564
+ const platformUuid = (seams.readPlatformUuid ?? readIoregPlatformUuid)();
1565
+ if (platformUuid !== null)
1566
+ return hostIdFrom("platform-uuid", platformUuid);
1567
+ }
1568
+ return hostIdFrom("hostname", (seams.readHostname ?? hostname)());
1569
+ }
1570
+ function readStoreFileFacts(path) {
1571
+ const storePath = realpathSync4(path);
1572
+ const stats = statSync(storePath, { bigint: true });
1573
+ return { storePath, fileDev: stats.dev, fileIno: stats.ino };
1574
+ }
1575
+ function observeStore(path, readHost = readHostId) {
1576
+ return { hostId: readHost(), ...readStoreFileFacts(path) };
1577
+ }
1578
+ function movedFacts(recorded, facts) {
1579
+ const sameFile = BigInt(recorded.fileDev) === facts.fileDev && BigInt(recorded.fileIno) === facts.fileIno;
1580
+ const moved = [
1581
+ ["host", recorded.hostId !== facts.hostId],
1582
+ ["path", recorded.storePath !== facts.storePath],
1583
+ ["file", !sameFile]
1584
+ ];
1585
+ return moved.filter(([, differs]) => differs).map(([reason]) => reason);
1586
+ }
1587
+ function decideStoreIdentity(access, recorded, observe) {
1588
+ if (access === "read")
1589
+ return { action: "none" };
1590
+ const facts = observe();
1591
+ if (recorded === null)
1592
+ return { action: "mint", reasons: ["unminted"], facts };
1593
+ const reasons = movedFacts(recorded, facts);
1594
+ return reasons.length === 0 ? { action: "keep", storeId: recorded.storeId } : { action: "mint", reasons, facts };
1595
+ }
1596
+ function smallestRootCommit(git) {
1597
+ const result = git(["rev-list", "--max-parents=0", "HEAD"]);
1598
+ if (!result.ok)
1599
+ return null;
1600
+ const roots = result.stdout.split(`
1601
+ `).map((line) => line.trim()).filter((line) => line !== "");
1602
+ return [...roots].sort()[0] ?? null;
1603
+ }
1604
+ function normalisedRemote(git) {
1605
+ const result = git(["remote", "get-url", "origin"]);
1606
+ if (!result.ok)
1607
+ return null;
1608
+ const remote = normalizeRemote(result.stdout.trim());
1609
+ return remote === "" ? null : remote;
1610
+ }
1611
+ function readProjectIdentity(dir, git = createGitRunner(dir)) {
1612
+ return { rootCommit: smallestRootCommit(git), remote: normalisedRemote(git) };
1613
+ }
1614
+
1615
+ // src/effort/store/store-meta.ts
1616
+ var STORED_INTEGER_BITS = 64;
1617
+ function toStoredInteger(value) {
1618
+ return BigInt.asIntN(STORED_INTEGER_BITS, value);
1619
+ }
1620
+ function fromStoredInteger(text) {
1621
+ return BigInt.asUintN(STORED_INTEGER_BITS, BigInt(text));
1622
+ }
1623
+ function readStoreMeta(db) {
1624
+ const row = db.query("SELECT store_id, project_root_commit, project_remote, host_id, store_path," + " CAST(file_dev AS TEXT) AS file_dev, CAST(file_ino AS TEXT) AS file_ino, minted_at" + " FROM store_meta WHERE id = 1").get();
1625
+ if (row === null)
1626
+ return null;
1627
+ return {
1628
+ storeId: row.store_id,
1629
+ projectRootCommit: row.project_root_commit,
1630
+ projectRemote: row.project_remote,
1631
+ hostId: row.host_id,
1632
+ storePath: row.store_path,
1633
+ fileDev: fromStoredInteger(row.file_dev),
1634
+ fileIno: fromStoredInteger(row.file_ino),
1635
+ mintedAt: row.minted_at
1636
+ };
1637
+ }
1638
+ function projectFor(storePath, recorded, seams) {
1639
+ const read = (seams.readProject ?? readProjectIdentity)(dirname8(storePath));
1640
+ if (read.rootCommit !== null)
1641
+ return { rootCommit: read.rootCommit, remote: read.remote };
1642
+ if (recorded === null)
1643
+ return null;
1644
+ return { rootCommit: recorded.projectRootCommit, remote: recorded.projectRemote };
1645
+ }
1646
+ function writeStoreMeta(db, facts, project, seams) {
1647
+ const storeId = (seams.newStoreId ?? randomUUID3)();
1648
+ const mintedAt = (seams.now?.() ?? new Date).toISOString();
1649
+ db.query("INSERT OR REPLACE INTO store_meta (id, store_id, project_root_commit, project_remote," + " host_id, store_path, file_dev, file_ino, minted_at) VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)").run(storeId, project.rootCommit, project.remote, facts.hostId, facts.storePath, toStoredInteger(facts.fileDev), toStoredInteger(facts.fileIno), mintedAt);
1650
+ return storeId;
1651
+ }
1652
+ function unminted(decision) {
1653
+ return decision.action === "keep" ? { action: "keep", storeId: decision.storeId } : { action: "none" };
1654
+ }
1655
+ function settleStoreIdentity(db, path, access, seams = {}) {
1656
+ if (access === "read")
1657
+ return { action: "none" };
1658
+ const observe = () => observeStore(path, seams.readHostId ?? readHostId);
1659
+ const recorded = readStoreMeta(db);
1660
+ const first = decideStoreIdentity(access, recorded, observe);
1661
+ if (first.action !== "mint")
1662
+ return unminted(first);
1663
+ const project = projectFor(first.facts.storePath, recorded, seams);
1664
+ if (project === null)
1665
+ return { action: "no-project", reasons: first.reasons };
1666
+ const mintUnderLock = db.transaction(() => {
1667
+ const second = decideStoreIdentity(access, readStoreMeta(db), observe);
1668
+ if (second.action !== "mint")
1669
+ return unminted(second);
1670
+ const storeId = writeStoreMeta(db, second.facts, project, seams);
1671
+ return { action: "mint", storeId, reasons: second.reasons };
1672
+ });
1673
+ return mintUnderLock.immediate();
1674
+ }
376
1675
 
377
1676
  // src/effort/store/sqlite.ts
378
- var STORE_FILE_NAME = "effort.sqlite";
1677
+ var SQLITE_STORE_FILE_NAME = "effort.sqlite";
379
1678
  function sqliteStorePath(repoRoot) {
380
- return join2(repoRoot, EFFORT_STORE_DIR, STORE_FILE_NAME);
1679
+ return join9(effortStoreDir(repoRoot), SQLITE_STORE_FILE_NAME);
381
1680
  }
382
1681
  var KIND_TABLES = {
383
1682
  sessions: { name: "sessions", keyColumn: "session_id" },
@@ -405,51 +1704,31 @@ function batchEntries(kind, rows) {
405
1704
  }
406
1705
  return keyed.map(({ key, row }) => ({ key, body: JSON.stringify(row) }));
407
1706
  }
408
- function userVersion(db) {
409
- const row = db.query("PRAGMA user_version").get();
410
- return row?.user_version ?? 0;
411
- }
412
- function checkedVersion(db, path, latest) {
413
- const version = userVersion(db);
414
- if (version > latest) {
415
- throw new Error(`effort store: ${path} is at schema version ${version}, past the` + ` ${latest} this rafa knows; refusing to read or write it`);
416
- }
417
- return version;
418
- }
419
- function migrateSchema(db, path, migrations = SQLITE_MIGRATIONS) {
420
- const latest = migrations.length;
421
- if (checkedVersion(db, path, latest) === latest)
422
- return;
423
- const applyPending = db.transaction(() => {
424
- const from = checkedVersion(db, path, latest);
425
- for (const sql of migrations.slice(from))
426
- db.run(sql);
427
- db.run(`PRAGMA user_version = ${latest}`);
428
- });
429
- applyPending.immediate();
430
- }
431
- function withSqliteStore(path, create, use) {
1707
+ function withSqliteStore(path, access, create, use, identity = {}) {
1708
+ guardTestProcess(path);
432
1709
  if (create)
433
- mkdirSync2(dirname2(path), { recursive: true });
1710
+ mkdirSync5(dirname9(path), { recursive: true });
434
1711
  const db = new Database(path, { readwrite: true, create });
435
1712
  try {
436
- migrateSchema(db, path);
1713
+ db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
1714
+ bringForward(db, path, access, "open");
1715
+ settleStoreIdentity(db, path, access, identity);
437
1716
  return use(db);
438
1717
  } finally {
439
1718
  db.close();
440
1719
  }
441
1720
  }
442
1721
  function insertBatch(db, table, entries) {
443
- const insert = db.query(`INSERT INTO ${table.name} (${table.keyColumn}, row_json) VALUES (?, ?)` + ` ON CONFLICT (${table.keyColumn}) DO NOTHING`);
1722
+ const insert = db.query(`INSERT INTO ${table.name} (${table.keyColumn}, row_json, ${STAMPED_COLUMNS})` + ` VALUES (?, ?, ${stampedValues(table.name)})` + ` ON CONFLICT (${table.keyColumn}) DO NOTHING`);
444
1723
  const insertAll = db.transaction(() => entries.reduce((appended, { key, body }) => appended + insert.run(key, body).changes, 0));
445
1724
  return insertAll.immediate();
446
1725
  }
447
1726
  function writeSqliteStore(path, rowCount, empty, insert) {
448
- const exists = existsSync2(path);
1727
+ const exists = existsSync5(path);
449
1728
  if (rowCount > 0)
450
- return withSqliteStore(path, !exists, insert);
1729
+ return withSqliteStore(path, "write", !exists, insert);
451
1730
  if (exists)
452
- withSqliteStore(path, false, () => {
1731
+ withSqliteStore(path, "write", false, () => {
453
1732
  return;
454
1733
  });
455
1734
  return empty;
@@ -461,10 +1740,10 @@ function appendKind2(path, kind, rows) {
461
1740
  return { path, appended, skipped: entries.length - appended };
462
1741
  }
463
1742
  function keysOfKind2(path, kind) {
464
- if (!existsSync2(path))
1743
+ if (!existsSync5(path))
465
1744
  return new Set;
466
- const { name, keyColumn } = KIND_TABLES[kind];
467
- const keys = withSqliteStore(path, false, (db) => db.query(`SELECT ${keyColumn} AS key FROM ${name} ORDER BY seq`).all());
1745
+ const { name: name2, keyColumn } = KIND_TABLES[kind];
1746
+ const keys = withSqliteStore(path, "read", false, (db) => db.query(`SELECT ${keyColumn} AS key FROM ${name2} ORDER BY seq`).all());
468
1747
  return new Set(keys.map(({ key }) => key));
469
1748
  }
470
1749
  function parsedObject(body) {
@@ -483,11 +1762,11 @@ function rowOf(body, where) {
483
1762
  return value;
484
1763
  }
485
1764
  function readKind2(path, kind) {
486
- if (!existsSync2(path))
1765
+ if (!existsSync5(path))
487
1766
  return [];
488
- const { name } = KIND_TABLES[kind];
489
- const stored = withSqliteStore(path, false, (db) => db.query(`SELECT seq, row_json AS body FROM ${name} ORDER BY seq`).all());
490
- return stored.map(({ seq, body }) => rowOf(body, `${path}: ${name} row ${seq}`));
1767
+ const { name: name2 } = KIND_TABLES[kind];
1768
+ const stored = withSqliteStore(path, "read", false, (db) => db.query(`SELECT seq, row_json AS body FROM ${name2} ORDER BY seq`).all());
1769
+ return stored.map(({ seq, body }) => rowOf(body, `${path}: ${name2} row ${seq}`));
491
1770
  }
492
1771
  function openSqliteStore(repoRoot) {
493
1772
  const path = sqliteStorePath(repoRoot);
@@ -501,15 +1780,15 @@ function openSqliteStore(repoRoot) {
501
1780
 
502
1781
  // src/adapters/learning/local.ts
503
1782
  import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
504
- import { join as join5 } from "path";
1783
+ import { join as join12 } from "path";
505
1784
 
506
1785
  // src/commands/instinct/instinct-records.ts
507
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
508
- import { join as join4 } from "path";
1786
+ import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
1787
+ import { join as join11 } from "path";
509
1788
 
510
1789
  // src/check/layout.ts
511
- import { readdirSync, statSync } from "fs";
512
- import { join as join3, relative } from "path";
1790
+ import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1791
+ import { join as join10, relative as relative2 } from "path";
513
1792
  var SKILL_FILE = "SKILL.md";
514
1793
  var MARKDOWN_EXTENSION = ".md";
515
1794
  var LAYOUT_SEVERITY = Object.freeze({
@@ -527,7 +1806,7 @@ function layoutIssue(code, message) {
527
1806
  }
528
1807
  function entryKind(path) {
529
1808
  try {
530
- const stats = statSync(path);
1809
+ const stats = statSync2(path);
531
1810
  if (stats.isFile())
532
1811
  return "file";
533
1812
  if (stats.isDirectory())
@@ -538,16 +1817,16 @@ function entryKind(path) {
538
1817
  }
539
1818
  }
540
1819
  function visibleNames(dir) {
541
- return readdirSync(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
1820
+ return readdirSync2(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
542
1821
  }
543
- function isMarkdown(name) {
544
- return name.endsWith(MARKDOWN_EXTENSION);
1822
+ function isMarkdown(name2) {
1823
+ return name2.endsWith(MARKDOWN_EXTENSION);
545
1824
  }
546
- function markdownStem(name) {
547
- return name.slice(0, -MARKDOWN_EXTENSION.length);
1825
+ function markdownStem(name2) {
1826
+ return name2.slice(0, -MARKDOWN_EXTENSION.length);
548
1827
  }
549
1828
  function shown(root, path) {
550
- return relative(root, path) || ".";
1829
+ return relative2(root, path) || ".";
551
1830
  }
552
1831
  function dotfileOnlyEntry(kind, root, dir) {
553
1832
  return {
@@ -569,37 +1848,37 @@ function unreadableEntry(kind, root, path) {
569
1848
  issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
570
1849
  };
571
1850
  }
572
- function skillFileEntry(path, name) {
573
- return { kind: "skill", path, isFile: true, shape: "skill-file", name, issues: [] };
1851
+ function skillFileEntry(path, name2) {
1852
+ return { kind: "skill", path, isFile: true, shape: "skill-file", name: name2, issues: [] };
574
1853
  }
575
- function flatGroupedEntry(root, path, name) {
1854
+ function flatGroupedEntry(root, path, name2) {
576
1855
  return {
577
1856
  kind: "skill",
578
1857
  path,
579
1858
  isFile: true,
580
1859
  shape: "flat-grouped",
581
- name,
582
- issues: [layoutIssue("flat-grouped-layout", `${shown(root, path)} is the flat grouped layout <dir>/<group>/<name>.md,` + ` which does not register; move it to <dir>/${name}/${SKILL_FILE}`)]
1860
+ name: name2,
1861
+ issues: [layoutIssue("flat-grouped-layout", `${shown(root, path)} is the flat grouped layout <dir>/<group>/<name>.md,` + ` which does not register; move it to <dir>/${name2}/${SKILL_FILE}`)]
583
1862
  };
584
1863
  }
585
- function nestedGroupEntry(root, path, name) {
1864
+ function nestedGroupEntry(root, path, name2) {
586
1865
  return {
587
1866
  kind: "skill",
588
1867
  path,
589
1868
  isFile: true,
590
1869
  shape: "nested-group",
591
- name,
592
- issues: [layoutIssue("nested-group-layout", `${shown(root, path)} is the nested layout <dir>/<group>/<name>/${SKILL_FILE},` + ` which does not register; move it to <dir>/${name}/${SKILL_FILE}`)]
1870
+ name: name2,
1871
+ issues: [layoutIssue("nested-group-layout", `${shown(root, path)} is the nested layout <dir>/<group>/<name>/${SKILL_FILE},` + ` which does not register; move it to <dir>/${name2}/${SKILL_FILE}`)]
593
1872
  };
594
1873
  }
595
- function looseFileEntry(root, path, name) {
1874
+ function looseFileEntry(root, path, name2) {
596
1875
  return {
597
1876
  kind: "skill",
598
1877
  path,
599
1878
  isFile: true,
600
1879
  shape: "loose-file",
601
- name,
602
- issues: [layoutIssue("loose-file", `${shown(root, path)} sits directly under the skills directory, which registers` + ` only <dir>/<name>/${SKILL_FILE}; move it to <dir>/${name}/${SKILL_FILE}`)]
1880
+ name: name2,
1881
+ issues: [layoutIssue("loose-file", `${shown(root, path)} sits directly under the skills directory, which registers` + ` only <dir>/<name>/${SKILL_FILE}; move it to <dir>/${name2}/${SKILL_FILE}`)]
603
1882
  };
604
1883
  }
605
1884
  function noSkillFileEntry(root, dir) {
@@ -612,30 +1891,30 @@ function noSkillFileEntry(root, dir) {
612
1891
  issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
613
1892
  };
614
1893
  }
615
- function nestedInstinctEntry(root, path, name) {
1894
+ function nestedInstinctEntry(root, path, name2) {
616
1895
  return {
617
1896
  kind: "instinct",
618
1897
  path,
619
1898
  isFile: true,
620
1899
  shape: "nested-instinct",
621
- name,
622
- issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name}${MARKDOWN_EXTENSION}`)]
1900
+ name: name2,
1901
+ issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name2}${MARKDOWN_EXTENSION}`)]
623
1902
  };
624
1903
  }
625
- function scanGroupMember(root, dir, name) {
1904
+ function scanGroupMember(root, dir, name2) {
626
1905
  const children = visibleNames(dir);
627
1906
  if (children.length === 0)
628
1907
  return [dotfileOnlyEntry("skill", root, dir)];
629
- const skillPath = join3(dir, SKILL_FILE);
1908
+ const skillPath = join10(dir, SKILL_FILE);
630
1909
  if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
631
- return [nestedGroupEntry(root, skillPath, name)];
1910
+ return [nestedGroupEntry(root, skillPath, name2)];
632
1911
  }
633
1912
  return [noSkillFileEntry(root, dir)];
634
1913
  }
635
1914
  function scanSkillGroup(root, dir, children) {
636
1915
  const entries = [];
637
1916
  for (const child of children) {
638
- const path = join3(dir, child);
1917
+ const path = join10(dir, child);
639
1918
  const found = entryKind(path);
640
1919
  if (found === null) {
641
1920
  entries.push(unreadableEntry("skill", root, path));
@@ -652,31 +1931,31 @@ function scanSkillGroup(root, dir, children) {
652
1931
  return [noSkillFileEntry(root, dir)];
653
1932
  return entries;
654
1933
  }
655
- function scanSkillDirectory(root, dir, name) {
1934
+ function scanSkillDirectory(root, dir, name2) {
656
1935
  const children = visibleNames(dir);
657
1936
  if (children.length === 0)
658
1937
  return [dotfileOnlyEntry("skill", root, dir)];
659
- const skillPath = join3(dir, SKILL_FILE);
1938
+ const skillPath = join10(dir, SKILL_FILE);
660
1939
  if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
661
- return [skillFileEntry(skillPath, name)];
1940
+ return [skillFileEntry(skillPath, name2)];
662
1941
  }
663
1942
  return scanSkillGroup(root, dir, children);
664
1943
  }
665
1944
  function scanSkillRoot(root) {
666
1945
  const entries = [];
667
- for (const name of visibleNames(root)) {
668
- const path = join3(root, name);
1946
+ for (const name2 of visibleNames(root)) {
1947
+ const path = join10(root, name2);
669
1948
  const found = entryKind(path);
670
1949
  if (found === null) {
671
1950
  entries.push(unreadableEntry("skill", root, path));
672
1951
  continue;
673
1952
  }
674
1953
  if (found === "directory") {
675
- entries.push(...scanSkillDirectory(root, path, name));
1954
+ entries.push(...scanSkillDirectory(root, path, name2));
676
1955
  continue;
677
1956
  }
678
- if (isMarkdown(name))
679
- entries.push(looseFileEntry(root, path, markdownStem(name)));
1957
+ if (isMarkdown(name2))
1958
+ entries.push(looseFileEntry(root, path, markdownStem(name2)));
680
1959
  }
681
1960
  return entries;
682
1961
  }
@@ -686,7 +1965,7 @@ function scanInstinctSubdirectory(root, dir) {
686
1965
  return [dotfileOnlyEntry("instinct", root, dir)];
687
1966
  const entries = [];
688
1967
  for (const child of children) {
689
- const path = join3(dir, child);
1968
+ const path = join10(dir, child);
690
1969
  const found = entryKind(path);
691
1970
  if (found === null) {
692
1971
  entries.push(unreadableEntry("instinct", root, path));
@@ -703,8 +1982,8 @@ function scanInstinctSubdirectory(root, dir) {
703
1982
  }
704
1983
  function scanInstinctRoot(root) {
705
1984
  const entries = [];
706
- for (const name of visibleNames(root)) {
707
- const path = join3(root, name);
1985
+ for (const name2 of visibleNames(root)) {
1986
+ const path = join10(root, name2);
708
1987
  const found = entryKind(path);
709
1988
  if (found === null) {
710
1989
  entries.push(unreadableEntry("instinct", root, path));
@@ -714,14 +1993,14 @@ function scanInstinctRoot(root) {
714
1993
  entries.push(...scanInstinctSubdirectory(root, path));
715
1994
  continue;
716
1995
  }
717
- if (!isMarkdown(name))
1996
+ if (!isMarkdown(name2))
718
1997
  continue;
719
1998
  entries.push({
720
1999
  kind: "instinct",
721
2000
  path,
722
2001
  isFile: true,
723
2002
  shape: "instinct-file",
724
- name: markdownStem(name),
2003
+ name: markdownStem(name2),
725
2004
  issues: []
726
2005
  });
727
2006
  }
@@ -741,9 +2020,9 @@ function scanLayout(root, kind) {
741
2020
  function declaredNameIssue(entry, declared) {
742
2021
  if (entry.name === null || declared === null || declared === entry.name)
743
2022
  return null;
744
- const field = entry.kind === "skill" ? "name" : "id";
2023
+ const field2 = entry.kind === "skill" ? "name" : "id";
745
2024
  const source = entry.kind === "skill" ? "directory name" : "file name";
746
- return layoutIssue("name-mismatch", `frontmatter ${field} "${declared}" is not the ${source} "${entry.name}"`);
2025
+ return layoutIssue("name-mismatch", `frontmatter ${field2} "${declared}" is not the ${source} "${entry.name}"`);
747
2026
  }
748
2027
 
749
2028
  // src/schema/frontmatter.ts
@@ -763,7 +2042,7 @@ function scanLine(text, offset) {
763
2042
  next: index + 1
764
2043
  };
765
2044
  }
766
- function isRecord(value) {
2045
+ function isRecord3(value) {
767
2046
  return typeof value === "object" && value !== null && !Array.isArray(value);
768
2047
  }
769
2048
  function parseBlock(yaml) {
@@ -774,7 +2053,7 @@ function parseBlock(yaml) {
774
2053
  } catch {
775
2054
  return null;
776
2055
  }
777
- return isRecord(parsed) ? parsed : null;
2056
+ return isRecord3(parsed) ? parsed : null;
778
2057
  }
779
2058
  function readFrontmatterDocument(text) {
780
2059
  const opening = scanLine(text, 0);
@@ -831,32 +2110,6 @@ function writeFrontmatter(document, data) {
831
2110
  ].join("");
832
2111
  }
833
2112
 
834
- // src/schema/project-id.ts
835
- import { spawnSync } from "child_process";
836
- var PROJECT_ID_LENGTH = 12;
837
- var PROJECT_ID_PATTERN = new RegExp(`^[0-9a-f]{${PROJECT_ID_LENGTH}}$`);
838
- function hasColonAfterAt(url) {
839
- const at = url.indexOf("@");
840
- return at !== -1 && url.lastIndexOf(":") > at;
841
- }
842
- function normalizeRemote(url) {
843
- const stripped = url.replace(/:\/\/[^@]+@/, "://");
844
- const network = !stripped.startsWith("file://") && (stripped.includes("://") || hasColonAfterAt(stripped));
845
- const trimmed = stripped.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "").replace(/^[^@/:]+@([^:/]+):/, "$1/").replace(/\.git\/?$/, "").replace(/\/+$/, "");
846
- return network ? trimmed.toLowerCase() : trimmed;
847
- }
848
- function gitRemoteUrl(dir) {
849
- const result = spawnSync("git", ["remote", "get-url", "origin"], {
850
- cwd: dir,
851
- encoding: "utf8",
852
- env: { ...process.env, LC_ALL: "C" }
853
- });
854
- if (result.error !== undefined || result.status !== 0)
855
- return null;
856
- const url = result.stdout.trim();
857
- return url === "" ? null : url;
858
- }
859
-
860
2113
  // src/schema/instinct.ts
861
2114
  var INSTINCT_KINDS = FINDING_KINDS;
862
2115
  var INSTINCT_SIGNALS = FINDING_SIGNALS;
@@ -889,8 +2142,8 @@ var INSTINCT_KEY_ORDER = [
889
2142
  "created_at",
890
2143
  "updated_at"
891
2144
  ];
892
- function issue(code, field, message) {
893
- return { code, field, message };
2145
+ function issue(code, field2, message) {
2146
+ return { code, field: field2, message };
894
2147
  }
895
2148
  function typeName(value) {
896
2149
  if (value === null)
@@ -905,25 +2158,25 @@ function typeName(value) {
905
2158
  return "a number";
906
2159
  return `a ${typeof value}`;
907
2160
  }
908
- function requiredString(data, field) {
909
- const value = data[field];
2161
+ function requiredString(data, field2) {
2162
+ const value = data[field2];
910
2163
  if (value === undefined || value === null) {
911
- return [issue("missing-field", field, `${field} is required and is absent`)];
2164
+ return [issue("missing-field", field2, `${field2} is required and is absent`)];
912
2165
  }
913
2166
  if (typeof value !== "string") {
914
- return [issue("wrong-type", field, `${field} must be a string, not ${typeName(value)}`)];
2167
+ return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(value)}`)];
915
2168
  }
916
2169
  if (value.trim() === "") {
917
- return [issue("missing-field", field, `${field} is required and is empty`)];
2170
+ return [issue("missing-field", field2, `${field2} is required and is empty`)];
918
2171
  }
919
2172
  return [];
920
2173
  }
921
- function requiredChoice(data, field, allowed, code) {
922
- const issues = requiredString(data, field);
2174
+ function requiredChoice(data, field2, allowed, code) {
2175
+ const issues = requiredString(data, field2);
923
2176
  if (issues.length > 0)
924
2177
  return issues;
925
- const value = data[field];
926
- return allowed.includes(value) ? [] : [issue(code, field, `${field} "${value}" is not one of ${allowed.join(", ")}`)];
2178
+ const value = data[field2];
2179
+ return allowed.includes(value) ? [] : [issue(code, field2, `${field2} "${value}" is not one of ${allowed.join(", ")}`)];
927
2180
  }
928
2181
  function checkId(data) {
929
2182
  const issues = requiredString(data, "id");
@@ -959,11 +2212,11 @@ function checkSources(data) {
959
2212
  return [issue("wrong-type", "sources", `sources must be a list, not ${typeName(value)}`)];
960
2213
  }
961
2214
  return value.flatMap((entry, index) => {
962
- const field = `sources[${index}]`;
2215
+ const field2 = `sources[${index}]`;
963
2216
  if (typeof entry !== "string") {
964
- return [issue("wrong-type", field, `sources entries must be strings, and this one is ${typeName(entry)}`)];
2217
+ return [issue("wrong-type", field2, `sources entries must be strings, and this one is ${typeName(entry)}`)];
965
2218
  }
966
- return entry.trim() === "" ? [issue("wrong-type", field, "sources entries must name a source, and this one is blank")] : [];
2219
+ return entry.trim() === "" ? [issue("wrong-type", field2, "sources entries must name a source, and this one is blank")] : [];
967
2220
  });
968
2221
  }
969
2222
  function checkPromotedTo(data) {
@@ -992,26 +2245,26 @@ function checkProjectId(data) {
992
2245
  }
993
2246
  return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
994
2247
  }
995
- function checkTimestamp(data, field) {
996
- const issues = requiredString(data, field);
2248
+ function checkTimestamp(data, field2) {
2249
+ const issues = requiredString(data, field2);
997
2250
  if (issues.length > 0)
998
2251
  return issues;
999
- const value = data[field];
1000
- return TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) ? [] : [issue("invalid-timestamp", field, `${field} "${value}" is not an ISO 8601 instant such as 2026-09-11T10:00:00Z`)];
2252
+ const value = data[field2];
2253
+ return TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) ? [] : [issue("invalid-timestamp", field2, `${field2} "${value}" is not an ISO 8601 instant such as 2026-09-11T10:00:00Z`)];
1001
2254
  }
1002
2255
  function isScalar(value) {
1003
2256
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1004
2257
  }
1005
2258
  function checkEvidenceEntry(entry, index) {
1006
- const field = `evidence[${index}]`;
2259
+ const field2 = `evidence[${index}]`;
1007
2260
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
1008
- return [issue("wrong-type", field, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
2261
+ return [issue("wrong-type", field2, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
1009
2262
  }
1010
2263
  const entries = Object.entries(entry);
1011
2264
  if (entries.length === 0) {
1012
- return [issue("wrong-type", field, "evidence entries must say something, and this one is empty")];
2265
+ return [issue("wrong-type", field2, "evidence entries must say something, and this one is empty")];
1013
2266
  }
1014
- return entries.flatMap(([key, value]) => isScalar(value) ? [] : [issue("wrong-type", `${field}.${key}`, `evidence values must be text, numbers or booleans, and this one is ${typeName(value)}`)]);
2267
+ return entries.flatMap(([key, value]) => isScalar(value) ? [] : [issue("wrong-type", `${field2}.${key}`, `evidence values must be text, numbers or booleans, and this one is ${typeName(value)}`)]);
1015
2268
  }
1016
2269
  function checkEvidence(data) {
1017
2270
  const value = data["evidence"];
@@ -1171,11 +2424,11 @@ function writeInstinct(instinct, newline = `
1171
2424
  }
1172
2425
 
1173
2426
  // src/commands/instinct/instinct-records.ts
1174
- function isRecordFile(name) {
1175
- return !name.startsWith(".") && name.endsWith(MARKDOWN_EXTENSION) && name !== MARKDOWN_EXTENSION;
2427
+ function isRecordFile(name2) {
2428
+ return !name2.startsWith(".") && name2.endsWith(MARKDOWN_EXTENSION) && name2 !== MARKDOWN_EXTENSION;
1176
2429
  }
1177
- function recordId(name) {
1178
- return name.slice(0, -MARKDOWN_EXTENSION.length);
2430
+ function recordId(name2) {
2431
+ return name2.slice(0, -MARKDOWN_EXTENSION.length);
1179
2432
  }
1180
2433
  function unreadableIssue(message) {
1181
2434
  return { code: "missing-frontmatter", field: "file", message: `the file could not be read: ${message}` };
@@ -1183,7 +2436,7 @@ function unreadableIssue(message) {
1183
2436
  function readRecord(path, scope, id) {
1184
2437
  let text;
1185
2438
  try {
1186
- text = readFileSync2(path, "utf8");
2439
+ text = readFileSync7(path, "utf8");
1187
2440
  } catch (error) {
1188
2441
  const message = error instanceof Error ? error.message : String(error);
1189
2442
  return { scope, path, id, instinct: null, issues: [unreadableIssue(message)] };
@@ -1193,13 +2446,13 @@ function readRecord(path, scope, id) {
1193
2446
  }
1194
2447
  function isFile(path) {
1195
2448
  try {
1196
- return statSync2(path).isFile();
2449
+ return statSync3(path).isFile();
1197
2450
  } catch {
1198
2451
  return false;
1199
2452
  }
1200
2453
  }
1201
2454
  function recordFiles(dir) {
1202
- return readdirSync2(dir).filter((name) => isRecordFile(name) && isFile(join4(dir, name))).sort((left, right) => left.localeCompare(right));
2455
+ return readdirSync3(dir).filter((name2) => isRecordFile(name2) && isFile(join11(dir, name2))).sort((left, right) => left.localeCompare(right));
1203
2456
  }
1204
2457
  function readScope(scope, dir) {
1205
2458
  if (!tierExists(dir))
@@ -1208,7 +2461,7 @@ function readScope(scope, dir) {
1208
2461
  scope,
1209
2462
  dir,
1210
2463
  exists: true,
1211
- records: recordFiles(dir).map((name) => readRecord(join4(dir, name), scope, recordId(name)))
2464
+ records: recordFiles(dir).map((name2) => readRecord(join11(dir, name2), scope, recordId(name2)))
1212
2465
  };
1213
2466
  }
1214
2467
  function readScopes(seams) {
@@ -1220,9 +2473,9 @@ function allRecords(listings) {
1220
2473
  function findRecords(listings, id) {
1221
2474
  return allRecords(listings).filter((entry) => entry.id === id);
1222
2475
  }
1223
- function instinctProject(context, name) {
2476
+ function instinctProject(context, name2) {
1224
2477
  if (context.project === null)
1225
- throw new Error(`${name} runs inside a project, and was handed none`);
2478
+ throw new Error(`${name2} runs inside a project, and was handed none`);
1226
2479
  return context.project;
1227
2480
  }
1228
2481
 
@@ -1305,7 +2558,7 @@ var PREFIX = "local learning";
1305
2558
  var PUSH_REFUSAL = `${PREFIX}: refused to store a push`;
1306
2559
  var INSTINCTS_FILE = "instincts.ndjson";
1307
2560
  var FLAGS_FILE = "flags.ndjson";
1308
- var RECORD_EXTENSION = ".md";
2561
+ var RECORD_EXTENSION2 = ".md";
1309
2562
  var MIN_CONFIDENCE = 0.3;
1310
2563
  var MAX_CONFIDENCE = 0.9;
1311
2564
  var SIGNALS = ["loud", "silent"];
@@ -1489,7 +2742,7 @@ function producedRecords(result) {
1489
2742
  for (const record of result.decisions.flatMap((decision) => decision.produced)) {
1490
2743
  const found = byId.get(record.id);
1491
2744
  if (found !== undefined && found !== record) {
1492
- throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION}`);
2745
+ throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION2}`);
1493
2746
  }
1494
2747
  byId.set(record.id, record);
1495
2748
  }
@@ -1501,7 +2754,7 @@ function heldChanges(dir, held, result, described) {
1501
2754
  const writes = [];
1502
2755
  const kept = new Set;
1503
2756
  for (const record of producedRecords(result)) {
1504
- const file = `${record.id}${RECORD_EXTENSION}`;
2757
+ const file = `${record.id}${RECORD_EXTENSION2}`;
1505
2758
  const instinct = toHeldInstinct(record, heldInstincts, payloadDescription(record, described));
1506
2759
  if (instinct === null) {
1507
2760
  throw new Error(`${PUSH_REFUSAL}: ${file} has no held member and no payload description to be written with`);
@@ -1511,7 +2764,7 @@ function heldChanges(dir, held, result, described) {
1511
2764
  if (issue2 !== undefined) {
1512
2765
  throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${issue2.field}: ${issue2.message}`);
1513
2766
  }
1514
- const path = join5(dir, file);
2767
+ const path = join12(dir, file);
1515
2768
  if (held.unreadable.has(path)) {
1516
2769
  throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which could not be read`);
1517
2770
  }
@@ -1541,18 +2794,18 @@ function pushLogLines(sourceId, described, result) {
1541
2794
  });
1542
2795
  }
1543
2796
  function localInstinctsDir(repoRoot) {
1544
- return join5(repoRoot, RAFA_INSTINCTS_PATH);
2797
+ return join12(repoRoot, RAFA_INSTINCTS_PATH);
1545
2798
  }
1546
2799
  function userInstinctsDir(home) {
1547
- return join5(home, RAFA_INSTINCTS_PATH);
2800
+ return join12(home, RAFA_INSTINCTS_PATH);
1548
2801
  }
1549
2802
  function createLocalLearning(options) {
1550
2803
  const { instinctsDir, minConfidence } = options;
1551
2804
  const userDir = userInstinctsDir(options.home);
1552
2805
  const now = options.now ?? (() => new Date().toISOString());
1553
2806
  const warn = options.warn ?? warnThroughActiveOutput;
1554
- const instinctsPath = join5(instinctsDir, INSTINCTS_FILE);
1555
- const flagsPath = join5(instinctsDir, FLAGS_FILE);
2807
+ const instinctsPath = join12(instinctsDir, INSTINCTS_FILE);
2808
+ const flagsPath = join12(instinctsDir, FLAGS_FILE);
1556
2809
  function readHeld(scope, dir) {
1557
2810
  const { records } = readScope(scope, dir);
1558
2811
  const unreadable = new Set;
@@ -1669,9 +2922,9 @@ function createJsonOutput({
1669
2922
  }
1670
2923
 
1671
2924
  // src/adapters/planner/claude.ts
1672
- import { existsSync as existsSync3 } from "fs";
2925
+ import { existsSync as existsSync6 } from "fs";
1673
2926
  import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
1674
- import { posix, resolve } from "path";
2927
+ import { posix, resolve as resolve3 } from "path";
1675
2928
 
1676
2929
  // src/board/spec-review.ts
1677
2930
  var SPEC_REVIEW_KIND = "spec-review";
@@ -1693,7 +2946,7 @@ var VERDICT_BREAK = /[\s_]+/gu;
1693
2946
  function isMapping2(value) {
1694
2947
  return typeof value === "object" && value !== null && !Array.isArray(value);
1695
2948
  }
1696
- function describeValue2(value) {
2949
+ function describeValue3(value) {
1697
2950
  if (typeof value === "string")
1698
2951
  return JSON.stringify(value);
1699
2952
  if (value === null)
@@ -1726,39 +2979,39 @@ function verdictOf(value) {
1726
2979
  const word = spelled.replace(VERDICT_BREAK, "-");
1727
2980
  return SPEC_REVIEW_VERDICTS.includes(word) ? word : null;
1728
2981
  }
1729
- function issueAt(field, text) {
1730
- return { field, text };
2982
+ function issueAt(field2, text) {
2983
+ return { field: field2, text };
1731
2984
  }
1732
- function readBlocking(item, field, issues) {
2985
+ function readBlocking(item, field2, issues) {
1733
2986
  const value = valueAt(item, "blocking");
1734
2987
  if (typeof value === "boolean")
1735
2988
  return value;
1736
- const text = `${field}.blocking is ${describeValue2(value)}, not true or false; ` + "the gap is read as blocking";
1737
- issues.push(issueAt(`${field}.blocking`, text));
2989
+ const text = `${field2}.blocking is ${describeValue3(value)}, not true or false; ` + "the gap is read as blocking";
2990
+ issues.push(issueAt(`${field2}.blocking`, text));
1738
2991
  return true;
1739
2992
  }
1740
- function readGap(item, field, issues) {
2993
+ function readGap(item, field2, issues) {
1741
2994
  if (!isMapping2(item)) {
1742
- const text = `${field} is ${describeValue2(item)}, not a mapping of heading and what; dropped`;
1743
- issues.push(issueAt(field, text));
2995
+ const text = `${field2} is ${describeValue3(item)}, not a mapping of heading and what; dropped`;
2996
+ issues.push(issueAt(field2, text));
1744
2997
  return null;
1745
2998
  }
1746
2999
  const what = stringAt(item, "what");
1747
3000
  if (what === null) {
1748
- const text = `${field}.what is ${describeValue2(valueAt(item, "what"))}, ` + "not a sentence; the gap is dropped";
1749
- issues.push(issueAt(`${field}.what`, text));
3001
+ const text = `${field2}.what is ${describeValue3(valueAt(item, "what"))}, ` + "not a sentence; the gap is dropped";
3002
+ issues.push(issueAt(`${field2}.what`, text));
1750
3003
  return null;
1751
3004
  }
1752
3005
  const heading = stringAt(item, "heading");
1753
3006
  if (heading === null) {
1754
- const text = `${field}.heading is ${describeValue2(valueAt(item, "heading"))}, ` + `not a heading; the gap is filed under ${REVIEW_HEADING}`;
1755
- issues.push(issueAt(`${field}.heading`, text));
3007
+ const text = `${field2}.heading is ${describeValue3(valueAt(item, "heading"))}, ` + `not a heading; the gap is filed under ${REVIEW_HEADING}`;
3008
+ issues.push(issueAt(`${field2}.heading`, text));
1756
3009
  }
1757
- const blocking = readBlocking(item, field, issues);
3010
+ const blocking = readBlocking(item, field2, issues);
1758
3011
  const assumption = stringAt(item, "assumption");
1759
3012
  if (!blocking && assumption === null) {
1760
- const text = `${field}.assumption is ${describeValue2(valueAt(item, "assumption"))}, ` + "not what a non-blocking gap would be planned under; the gap is read as blocking";
1761
- issues.push(issueAt(`${field}.assumption`, text));
3013
+ const text = `${field2}.assumption is ${describeValue3(valueAt(item, "assumption"))}, ` + "not what a non-blocking gap would be planned under; the gap is read as blocking";
3014
+ issues.push(issueAt(`${field2}.assumption`, text));
1762
3015
  return { heading: heading ?? REVIEW_HEADING, what, blocking: true, assumption: null };
1763
3016
  }
1764
3017
  return { heading: heading ?? REVIEW_HEADING, what, blocking, assumption };
@@ -1768,7 +3021,7 @@ function readGaps(document, issues) {
1768
3021
  if (value === null)
1769
3022
  return [];
1770
3023
  if (!Array.isArray(value)) {
1771
- const text = `gaps is ${describeValue2(value)}, not a list; no gap is read from it`;
3024
+ const text = `gaps is ${describeValue3(value)}, not a list; no gap is read from it`;
1772
3025
  issues.push(issueAt("gaps", text));
1773
3026
  return [];
1774
3027
  }
@@ -1824,12 +3077,12 @@ function parseSpecReview(output) {
1824
3077
  return unreadable("malformed", block, text);
1825
3078
  }
1826
3079
  if (!isMapping2(document)) {
1827
- const text = `the rafa:spec-review block at line ${line} holds ${describeValue2(document)}, ` + "not a mapping of verdict and gaps";
3080
+ const text = `the rafa:spec-review block at line ${line} holds ${describeValue3(document)}, ` + "not a mapping of verdict and gaps";
1828
3081
  return unreadable("malformed", block, text);
1829
3082
  }
1830
3083
  const verdict = verdictOf(valueAt(document, "verdict"));
1831
3084
  if (verdict === null) {
1832
- const text = `the rafa:spec-review block at line ${line} has a verdict of ` + `${describeValue2(valueAt(document, "verdict"))}, ` + `not one of ${SPEC_REVIEW_VERDICTS.join(", ")}`;
3085
+ const text = `the rafa:spec-review block at line ${line} has a verdict of ` + `${describeValue3(valueAt(document, "verdict"))}, ` + `not one of ${SPEC_REVIEW_VERDICTS.join(", ")}`;
1833
3086
  return unreadable("malformed", block, text);
1834
3087
  }
1835
3088
  const issues = [];
@@ -1905,11 +3158,11 @@ class UndeclaredSpendError extends Error {
1905
3158
  name = "UndeclaredSpendError";
1906
3159
  }
1907
3160
  function carriesFlag(flags, flag) {
1908
- const name = flag.replace(/^--/, "");
1909
- const value = flags[name];
3161
+ const name2 = flag.replace(/^--/, "");
3162
+ const value = flags[name2];
1910
3163
  if (value !== undefined && value !== false)
1911
3164
  return true;
1912
- return name.startsWith("no-") && flags[name.slice(3)] === false;
3165
+ return name2.startsWith("no-") && flags[name2.slice(3)] === false;
1913
3166
  }
1914
3167
  function spendRefusal(running2) {
1915
3168
  if (running2 === null)
@@ -2035,12 +3288,12 @@ function createClaudePlanner(options) {
2035
3288
  const create = async ({ specPath, stub }) => {
2036
3289
  const planPath = planFilePath(planDir, `PLAN-${stub}.md`);
2037
3290
  const prerequisitesPath = planFilePath(planDir, `PREREQUISITES-${stub}.md`);
2038
- const isWritten = (path) => existsSync3(resolve(repoRoot, path));
3291
+ const isWritten = (path) => existsSync6(resolve3(repoRoot, path));
2039
3292
  if (isWritten(planPath)) {
2040
3293
  throw new ClaudePlannerError(`${planPath} already exists`, FAILURE_EXIT_CODE);
2041
3294
  }
2042
- const specContent = await readFile2(resolve(repoRoot, specPath), "utf8");
2043
- await mkdir2(resolve(repoRoot, planDir), { recursive: true });
3295
+ const specContent = await readFile2(resolve3(repoRoot, specPath), "utf8");
3296
+ await mkdir2(resolve3(repoRoot, planDir), { recursive: true });
2044
3297
  const session = await runClaudeCaptured(buildPrompt(specContent, stub), settingSources, [], spawn);
2045
3298
  const review = parseSpecReview(session.stdout);
2046
3299
  const { exitCode } = session;
@@ -2151,8 +3404,8 @@ async function withDeadline(work, timeoutMs) {
2151
3404
  if (timeoutMs === undefined)
2152
3405
  return work;
2153
3406
  let timer;
2154
- const deadline = new Promise((resolve2) => {
2155
- timer = setTimeout(() => resolve2(TIMED_OUT), timeoutMs);
3407
+ const deadline = new Promise((resolve4) => {
3408
+ timer = setTimeout(() => resolve4(TIMED_OUT), timeoutMs);
2156
3409
  });
2157
3410
  try {
2158
3411
  return await Promise.race([work, deadline]);
@@ -2167,7 +3420,7 @@ function notOnPath(command, cwd, path) {
2167
3420
  function isOneOf(members, value) {
2168
3421
  return typeof value === "string" && members.includes(value);
2169
3422
  }
2170
- function isIssueNumber(value) {
3423
+ function isIssueNumber3(value) {
2171
3424
  return typeof value === "string" && ISSUE_NUMBER.test(value) && Number.isSafeInteger(Number(value));
2172
3425
  }
2173
3426
  function isRepoName(value) {
@@ -2179,8 +3432,8 @@ function isRepoName(value) {
2179
3432
  function isLabelPart(value) {
2180
3433
  return typeof value === "string" && !value.includes(",");
2181
3434
  }
2182
- function optionalFlag(name, value) {
2183
- return value === undefined ? [] : [name, value];
3435
+ function optionalFlag(name2, value) {
3436
+ return value === undefined ? [] : [name2, value];
2184
3437
  }
2185
3438
  function detailOf(result) {
2186
3439
  return result.stderr.trim() || result.stdout.trim() || "it exited non-zero and wrote nothing";
@@ -2222,7 +3475,7 @@ function issueNumberOfRef(ref) {
2222
3475
  if (ref.kind !== "github") {
2223
3476
  throw new Error(`${PREFIX2}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads github refs only`);
2224
3477
  }
2225
- if (!isIssueNumber(ref.externalId)) {
3478
+ if (!isIssueNumber3(ref.externalId)) {
2226
3479
  throw new Error(`${PREFIX2}: externalId ${describeValue(ref.externalId)} is not a GitHub issue number,` + " expected a positive whole number with no leading zero");
2227
3480
  }
2228
3481
  if (ref.repo !== undefined && !isRepoName(ref.repo)) {
@@ -2243,7 +3496,7 @@ function labelNames(value) {
2243
3496
  if (!Array.isArray(value))
2244
3497
  return null;
2245
3498
  const names = value.map((label) => isMapping(label) && typeof label["name"] === "string" ? label["name"] : null);
2246
- return names.every((name) => name !== null) ? names : null;
3499
+ return names.every((name2) => name2 !== null) ? names : null;
2247
3500
  }
2248
3501
  function viewProblem(payload, number) {
2249
3502
  if (!isMapping(payload))
@@ -2279,18 +3532,18 @@ function listedIssues(stdout) {
2279
3532
  throw new Error(`${PREFIX2}: ${command} answered ${describeValue(payload)}, expected a list`);
2280
3533
  }
2281
3534
  return payload.map((row, index) => {
2282
- const refuse = (problem) => new Error(`${PREFIX2}: ${command} answered row ${index} ${problem}`);
3535
+ const refuse2 = (problem) => new Error(`${PREFIX2}: ${command} answered row ${index} ${problem}`);
2283
3536
  if (!isMapping(row))
2284
- throw refuse(`${describeValue(row)}, expected a mapping`);
3537
+ throw refuse2(`${describeValue(row)}, expected a mapping`);
2285
3538
  const { number, url } = row;
2286
3539
  const labels = labelNames(row["labels"]);
2287
- if (typeof number !== "number" || !isIssueNumber(String(number))) {
2288
- throw refuse(`with number ${describeValue(number)}, expected an issue number`);
3540
+ if (typeof number !== "number" || !isIssueNumber3(String(number))) {
3541
+ throw refuse2(`with number ${describeValue(number)}, expected an issue number`);
2289
3542
  }
2290
3543
  if (typeof url !== "string")
2291
- throw refuse(`with url ${describeValue(url)}, expected a string`);
3544
+ throw refuse2(`with url ${describeValue(url)}, expected a string`);
2292
3545
  if (labels === null)
2293
- throw refuse("with labels that are not a list of named labels");
3546
+ throw refuse2("with labels that are not a list of named labels");
2294
3547
  return { number, url, labels };
2295
3548
  });
2296
3549
  }
@@ -2319,7 +3572,7 @@ function issueOf(ref, viewed) {
2319
3572
  module: moduleOfLabels(viewed.labels),
2320
3573
  priority: isOneOf(ISSUE_PRIORITIES, priority) ? priority : null,
2321
3574
  project: null,
2322
- blockedBy: viewed.labels.map((label) => label.startsWith(GITHUB_LABELS.blockedByPrefix) ? label.slice(GITHUB_LABELS.blockedByPrefix.length) : "").filter(isIssueNumber).map(Number),
3575
+ blockedBy: viewed.labels.map((label) => label.startsWith(GITHUB_LABELS.blockedByPrefix) ? label.slice(GITHUB_LABELS.blockedByPrefix.length) : "").filter(isIssueNumber3).map(Number),
2323
3576
  ref: { ...ref, url: viewed.url },
2324
3577
  state: stateOf(viewed)
2325
3578
  };
@@ -2344,9 +3597,9 @@ function createGithubTracker(options) {
2344
3597
  return result.stdout;
2345
3598
  }
2346
3599
  async function ensureLabels(names) {
2347
- const missing = [...new Set(names)].filter((name) => !knownLabels.has(name));
2348
- for (const name of missing) {
2349
- await run(["label", "create", name, "--force"], `gh label create ${describeValue(name)}`);
3600
+ const missing = [...new Set(names)].filter((name2) => !knownLabels.has(name2));
3601
+ for (const name2 of missing) {
3602
+ await run(["label", "create", name2, "--force"], `gh label create ${describeValue(name2)}`);
2350
3603
  }
2351
3604
  knownLabels = new Set([...knownLabels, ...missing]);
2352
3605
  }
@@ -2457,7 +3710,7 @@ function createGithubTracker(options) {
2457
3710
 
2458
3711
  // src/adapters/tracker/local.ts
2459
3712
  import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
2460
- import { join as join6 } from "path";
3713
+ import { join as join13 } from "path";
2461
3714
  var PREFIX3 = "local tracker";
2462
3715
  var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
2463
3716
  var ISSUE_NUMBER2 = /^[1-9]\d*$/;
@@ -2510,11 +3763,11 @@ function parseIssueNumber(text) {
2510
3763
  const number = Number(text);
2511
3764
  return Number.isSafeInteger(number) ? number : null;
2512
3765
  }
2513
- function issueNumberOfFile(name) {
2514
- return name.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name.slice(0, -ISSUE_EXTENSION.length)) : null;
3766
+ function issueNumberOfFile(name2) {
3767
+ return name2.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name2.slice(0, -ISSUE_EXTENSION.length)) : null;
2515
3768
  }
2516
3769
  function issuePath(issuesDir, number) {
2517
- return join6(issuesDir, `${number}${ISSUE_EXTENSION}`);
3770
+ return join13(issuesDir, `${number}${ISSUE_EXTENSION}`);
2518
3771
  }
2519
3772
  async function issueNumbers(issuesDir) {
2520
3773
  let names;
@@ -2528,7 +3781,7 @@ async function issueNumbers(issuesDir) {
2528
3781
  return names.map(issueNumberOfFile).filter((number) => number !== null).sort((a, b) => a - b);
2529
3782
  }
2530
3783
  function localIssuesDir(repoRoot) {
2531
- return join6(repoRoot, ".rafa", "issues");
3784
+ return join13(repoRoot, ".rafa", "issues");
2532
3785
  }
2533
3786
  function renderLocalIssue(record) {
2534
3787
  const { draft } = record;
@@ -2849,7 +4102,7 @@ var CORE_ADAPTERS = [
2849
4102
  var CORE_ADAPTER_REGISTRY = createAdapterRegistry(CORE_ADAPTERS);
2850
4103
 
2851
4104
  // src/effort/store/index.ts
2852
- function describeValue3(value) {
4105
+ function describeValue4(value) {
2853
4106
  if (typeof value === "string")
2854
4107
  return JSON.stringify(value);
2855
4108
  if (typeof value === "object" && value !== null)
@@ -2860,9 +4113,9 @@ function selectEffortStore(repoRoot, config) {
2860
4113
  const adapter = CORE_ADAPTER_REGISTRY.find("store", config.store);
2861
4114
  if (adapter === undefined) {
2862
4115
  const expected = CORE_ADAPTER_REGISTRY.kinds("store").join(", ");
2863
- throw new TypeError(`effort store: store is ${describeValue3(config.store)},` + ` expected one of: ${expected}`);
4116
+ throw new TypeError(`effort store: store is ${describeValue4(config.store)},` + ` expected one of: ${expected}`);
2864
4117
  }
2865
4118
  return adapter.create({ repoRoot });
2866
4119
  }
2867
4120
 
2868
- export { EFFORT_STORE_DIR, effortStorePath, EFFORT_KEY_PROJECTIONS, openNdjsonStore, SQLITE_MIGRATIONS, sqliteStorePath, LONE_SURROGATE, migrateSchema, withSqliteStore, writeSqliteStore, openSqliteStore, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, normalizeRemote, gitRemoteUrl, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, sessionSpawnEnv, getClaudeUsagePercent, checkUsage, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, selectEffortStore };
4121
+ export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess, EFFORT_KEY_PROJECTIONS, openNdjsonStore, name, version, RAFA_VERSION, versionLine, 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, SessionConflictError, SessionStateError, isSessionId, runsDir, sessionFilePath, errorCode, isPidAlive, readSessions, readSession, beginSession, updateSession, readRuntimeIdentity, DEVELOPMENT_NEXT_STEP, ADOPTION_NAME, DEVELOPMENT_MARK, appliedByName, DevelopmentBuildRefusedError, storeProjectRoot, refuseUnownedDevelopmentWrite, LEGACY_GATE_OPEN, LEGACY_GATE_CLOSED, SQLITE_MIGRATIONS, sqliteCatalogue, planSchema, MIGRATION_LOG_TABLE, SchemaRefusedError, bringForward, readStoreSchema, writeNames, ACROSS_STORES_ORDER, STAMPED_COLUMNS, stampedValues, setActiveStoreSettings, activeStoreSettings, createGitRunner, gitSaid, normalizeRemote, gitRemoteUrl, readProjectIdentity, readStoreMeta, SQLITE_STORE_FILE_NAME, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, sessionSpawnEnv, getClaudeUsagePercent, checkUsage, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, selectEffortStore };