@open-tomato/rafa 0.24.0 → 0.26.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,461 @@ 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 mkdirSync3 } from "fs";
242
+ import { dirname as dirname6, join as join7 } from "path";
178
243
  import { Database } from "bun:sqlite";
179
244
 
245
+ // package.json
246
+ var name = "@open-tomato/rafa";
247
+ var version = "0.26.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 dirname5, isAbsolute as isAbsolute3 } from "path";
258
+
259
+ // src/loop/sessions.ts
260
+ import {
261
+ linkSync,
262
+ mkdirSync as mkdirSync2,
263
+ readdirSync,
264
+ readFileSync as readFileSync2,
265
+ renameSync,
266
+ rmSync,
267
+ writeFileSync
268
+ } from "fs";
269
+ import { basename, dirname as dirname3, join as join5 } from "path";
270
+
271
+ // src/project/scope.ts
272
+ import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
273
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4 } from "path";
274
+ var SCOPE_DIR = dirname2(CONFIG_FILE);
275
+ var INIT_COMMAND = "rafa init";
276
+ var DISK_FILE_SYSTEM = Object.freeze({
277
+ exists: (path) => existsSync3(path),
278
+ realpath: (path) => realpathSync2(path)
279
+ });
280
+
281
+ class ScopeError extends Error {
282
+ constructor(message, options) {
283
+ super(`rafa scope: ${message}`, options);
284
+ this.name = "ScopeError";
285
+ }
286
+ }
287
+ function scopeAt(base) {
288
+ return { dir: join4(base, SCOPE_DIR), configFile: configFilePath(base) };
289
+ }
290
+ function initHint(start, passedUserConfig = null) {
291
+ const passed = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
292
+ return [
293
+ `not inside a rafa project: no ${CONFIG_FILE} in ${start} or any directory above it`,
294
+ ...passed,
295
+ `run \`${INIT_COMMAND}\` to set one up`
296
+ ].join(`
297
+ `);
298
+ }
299
+ function requireAbsolute(label, path) {
300
+ if (!isAbsolute2(path)) {
301
+ throw new ScopeError(`${label} is ${describeValue(path)}, expected an absolute path`);
302
+ }
303
+ }
304
+ function realStartOf(start, fs) {
305
+ try {
306
+ return fs.realpath(start);
307
+ } catch (error) {
308
+ const message = `start directory ${start} does not resolve (${messageOf(error)})`;
309
+ throw new ScopeError(message, { cause: error });
310
+ }
311
+ }
312
+ function realHomeOf(home, fs) {
313
+ try {
314
+ return fs.realpath(home);
315
+ } catch {
316
+ return null;
317
+ }
318
+ }
319
+ function selfAndAncestors(path) {
320
+ const parent = dirname2(path);
321
+ return parent === path ? [path] : [path, ...selfAndAncestors(parent)];
322
+ }
323
+ function walkUp(realStart, realHome, fs) {
324
+ let passedUserConfig = false;
325
+ for (const dir of selfAndAncestors(realStart)) {
326
+ if (!fs.exists(configFilePath(dir)))
327
+ continue;
328
+ if (dir !== realHome)
329
+ return { root: dir, passedUserConfig };
330
+ passedUserConfig = true;
331
+ }
332
+ return { root: null, passedUserConfig };
333
+ }
334
+ function resolveScope(start, seams) {
335
+ const { home, fs = DISK_FILE_SYSTEM } = seams;
336
+ requireAbsolute("start directory", start);
337
+ requireAbsolute("home directory", home);
338
+ const user = scopeAt(home);
339
+ const walk = walkUp(realStartOf(start, fs), realHomeOf(home, fs), fs);
340
+ if (walk.root === null) {
341
+ const passed = walk.passedUserConfig ? user.configFile : null;
342
+ return { found: false, start, home, user, hint: initHint(start, passed) };
343
+ }
344
+ return { found: true, root: walk.root, home, project: scopeAt(walk.root), user };
345
+ }
346
+
347
+ // src/loop/sessions.ts
348
+ var SESSION_STATES = Object.freeze(["running", "paused", "stopped", "done"]);
349
+ var RUNS_DIR = "runs";
350
+ var RECORD_EXTENSION = ".json";
351
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
352
+
353
+ class SessionRecordError extends Error {
354
+ file;
355
+ constructor(file, problem) {
356
+ super(`session record ${file}: ${problem}`);
357
+ this.name = "SessionRecordError";
358
+ this.file = file;
359
+ }
360
+ }
361
+
362
+ class SessionConflictError extends Error {
363
+ conflicts;
364
+ constructor(conflicts) {
365
+ const named = conflicts.map((conflict) => `${conflict.record.sessionId} (${conflict.reason})`);
366
+ super(`the plan's sessions refuse the run: ${named.join(", ")}`);
367
+ this.name = "SessionConflictError";
368
+ this.conflicts = conflicts;
369
+ }
370
+ }
371
+
372
+ class SessionStateError extends Error {
373
+ file;
374
+ state;
375
+ constructor(file, state, onlyFrom) {
376
+ const acted = onlyFrom.length === 0 ? "no state" : onlyFrom.join(" or ");
377
+ super(`session record ${file}: stores ${state}, and the change acts on ${acted} alone`);
378
+ this.name = "SessionStateError";
379
+ this.file = file;
380
+ this.state = state;
381
+ }
382
+ }
383
+ function isSessionId(value) {
384
+ return SESSION_ID_PATTERN.test(value);
385
+ }
386
+ function runsDir(root) {
387
+ return join5(scopeAt(root).dir, RUNS_DIR);
388
+ }
389
+ function sessionFilePath(root, sessionId) {
390
+ if (!isSessionId(sessionId)) {
391
+ throw new Error(`session record: unusable session id ${JSON.stringify(sessionId)}`);
392
+ }
393
+ return join5(runsDir(root), `${sessionId}${RECORD_EXTENSION}`);
394
+ }
395
+ function describeValue2(value) {
396
+ return value === undefined ? "missing" : JSON.stringify(value) ?? String(value);
397
+ }
398
+ function field(fields, key) {
399
+ return Object.hasOwn(fields, key) ? fields[key] : undefined;
400
+ }
401
+ function isObject(value) {
402
+ return typeof value === "object" && value !== null && !Array.isArray(value);
403
+ }
404
+ function isPositiveWhole(value) {
405
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
406
+ }
407
+ function isText(value) {
408
+ return typeof value === "string" && value.trim() !== "";
409
+ }
410
+ function taskProblem(task) {
411
+ if (task === null)
412
+ return null;
413
+ if (!isObject(task))
414
+ return `task is ${describeValue2(task)}, expected null or an object`;
415
+ if (!isPositiveWhole(field(task, "line"))) {
416
+ return `task.line is ${describeValue2(field(task, "line"))}, expected a whole number from 1`;
417
+ }
418
+ return isText(field(task, "text")) ? null : `task.text is ${describeValue2(field(task, "text"))}, expected a non-empty string`;
419
+ }
420
+ function sessionIdProblem(sessionId, file) {
421
+ if (typeof sessionId !== "string" || !isSessionId(sessionId)) {
422
+ return `sessionId is ${describeValue2(sessionId)}, expected a plain file name`;
423
+ }
424
+ return basename(file) === `${sessionId}${RECORD_EXTENSION}` ? null : `sessionId ${JSON.stringify(sessionId)} is not the file's name`;
425
+ }
426
+ function planStubProblem(planStub) {
427
+ return planStub === null || typeof planStub === "string" && isStampableStub(planStub) ? null : `planStub is ${describeValue2(planStub)}, expected null or a plan stub`;
428
+ }
429
+ function textProblems(fields) {
430
+ return ["plan", "branch"].filter((key) => !isText(field(fields, key))).map((key) => `${key} is ${describeValue2(field(fields, key))}, expected a non-empty string`);
431
+ }
432
+ function recordProblems(fields, file) {
433
+ const pid = field(fields, "pid");
434
+ const startedAt = field(fields, "startedAt");
435
+ const state = field(fields, "state");
436
+ const problems = [
437
+ sessionIdProblem(field(fields, "sessionId"), file),
438
+ planStubProblem(field(fields, "planStub")),
439
+ ...textProblems(fields),
440
+ isPositiveWhole(pid) ? null : `pid is ${describeValue2(pid)}, expected a whole number from 1`,
441
+ typeof startedAt === "string" && !Number.isNaN(Date.parse(startedAt)) ? null : `startedAt is ${describeValue2(startedAt)}, expected a timestamp`,
442
+ SESSION_STATES.includes(state) ? null : `state is ${describeValue2(state)}, expected one of ${SESSION_STATES.join(", ")}`,
443
+ taskProblem(field(fields, "task"))
444
+ ];
445
+ return problems.filter((problem) => problem !== null);
446
+ }
447
+ function freezeRecord(fields) {
448
+ const task = field(fields, "task");
449
+ return Object.freeze({
450
+ sessionId: field(fields, "sessionId"),
451
+ planStub: field(fields, "planStub"),
452
+ plan: field(fields, "plan"),
453
+ branch: field(fields, "branch"),
454
+ pid: field(fields, "pid"),
455
+ startedAt: field(fields, "startedAt"),
456
+ state: field(fields, "state"),
457
+ task: isObject(task) ? Object.freeze({ line: field(task, "line"), text: field(task, "text") }) : null
458
+ });
459
+ }
460
+ function parseSessionRecord(text, file) {
461
+ let parsed;
462
+ try {
463
+ parsed = JSON.parse(text);
464
+ } catch (error) {
465
+ throw new SessionRecordError(file, `holds no JSON: ${messageOf(error)}`);
466
+ }
467
+ if (!isObject(parsed))
468
+ throw new SessionRecordError(file, "holds no JSON object");
469
+ const problems = recordProblems(parsed, file);
470
+ if (problems.length > 0)
471
+ throw new SessionRecordError(file, problems.join("; "));
472
+ return freezeRecord(parsed);
473
+ }
474
+ function errorCode(error) {
475
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
476
+ }
477
+ function isPidAlive(pid) {
478
+ if (!isPositiveWhole(pid))
479
+ return false;
480
+ try {
481
+ process.kill(pid, 0);
482
+ return true;
483
+ } catch (error) {
484
+ return errorCode(error) === "EPERM";
485
+ }
486
+ }
487
+ function readState(record, isAlive = isPidAlive) {
488
+ if (record.state === "stopped" || record.state === "done")
489
+ return record.state;
490
+ return isAlive(record.pid) ? record.state : "stopped";
491
+ }
492
+ function readRecordFile(file) {
493
+ let text;
494
+ try {
495
+ text = readFileSync2(file, "utf8");
496
+ } catch (error) {
497
+ throw new SessionRecordError(file, `cannot be read: ${messageOf(error)}`);
498
+ }
499
+ return parseSessionRecord(text, file);
500
+ }
501
+ function recordNames(dir) {
502
+ try {
503
+ return readdirSync(dir);
504
+ } catch (error) {
505
+ if (errorCode(error) === "ENOENT")
506
+ return [];
507
+ throw new SessionRecordError(dir, `cannot be listed: ${messageOf(error)}`);
508
+ }
509
+ }
510
+ function byStart(a, b) {
511
+ return Date.parse(a.startedAt) - Date.parse(b.startedAt) || a.sessionId.localeCompare(b.sessionId);
512
+ }
513
+ function readSessions(root, seams = {}) {
514
+ const dir = runsDir(root);
515
+ const isAlive = seams.isAlive ?? isPidAlive;
516
+ const records = recordNames(dir).filter((name2) => name2.endsWith(RECORD_EXTENSION)).map((name2) => readRecordFile(join5(dir, name2))).map((record) => Object.freeze({ ...record, state: readState(record, isAlive) }));
517
+ return Object.freeze(records.sort(byStart));
518
+ }
519
+ function readSession(root, sessionId, seams = {}) {
520
+ const record = readRecordFile(sessionFilePath(root, sessionId));
521
+ return Object.freeze({ ...record, state: readState(record, seams.isAlive ?? isPidAlive) });
522
+ }
523
+ function samePlan(a, b) {
524
+ if (a.planStub !== null || b.planStub !== null)
525
+ return a.planStub === b.planStub;
526
+ return a.plan === b.plan;
527
+ }
528
+ function sessionConflicts(records, candidate) {
529
+ return records.filter((record) => samePlan(record, candidate)).flatMap((record) => {
530
+ if (record.branch !== candidate.branch)
531
+ return [{ reason: "branch", record }];
532
+ return record.state === "running" || record.state === "paused" ? [{ reason: "live", record }] : [];
533
+ });
534
+ }
535
+ function checkRecord(record, file) {
536
+ const problems = recordProblems(record, file);
537
+ if (problems.length > 0)
538
+ throw new SessionRecordError(file, `not written: ${problems.join("; ")}`);
539
+ }
540
+ function writeRecordFile(file, record, isNew) {
541
+ checkRecord(record, file);
542
+ mkdirSync2(dirname3(file), { recursive: true });
543
+ const temporary = `${file}.${process.pid}.tmp`;
544
+ writeFileSync(temporary, `${JSON.stringify(record, null, 2)}
545
+ `);
546
+ try {
547
+ if (isNew)
548
+ linkSync(temporary, file);
549
+ else
550
+ renameSync(temporary, file);
551
+ } finally {
552
+ rmSync(temporary, { force: true });
553
+ }
554
+ }
555
+ function beginSession(root, draft, seams = {}) {
556
+ const file = sessionFilePath(root, draft.sessionId);
557
+ const record = freezeRecord({ ...draft, state: "running", task: null });
558
+ const conflicts = sessionConflicts(readSessions(root, seams), record);
559
+ if (conflicts.length > 0)
560
+ throw new SessionConflictError(conflicts);
561
+ writeRecordFile(file, record, true);
562
+ return record;
563
+ }
564
+ function updateSession(root, sessionId, change) {
565
+ const file = sessionFilePath(root, sessionId);
566
+ const stored = readRecordFile(file);
567
+ if (change.onlyFrom !== undefined && !change.onlyFrom.includes(stored.state)) {
568
+ throw new SessionStateError(file, stored.state, change.onlyFrom);
569
+ }
570
+ const record = freezeRecord({
571
+ ...stored,
572
+ state: change.state ?? stored.state,
573
+ task: change.task === undefined ? stored.task : change.task
574
+ });
575
+ writeRecordFile(file, record, false);
576
+ return record;
577
+ }
578
+
579
+ // src/runtime/identity.ts
580
+ import { existsSync as existsSync4, readFileSync as readFileSync3, realpathSync as realpathSync3 } from "fs";
581
+ import { dirname as dirname4, join as join6, resolve as resolve2 } from "path";
582
+ var MANIFEST = "package.json";
583
+ var CHECKOUT_ENTRY = join6("src", "rafa.ts");
584
+ function realOrResolved(path) {
585
+ const resolved = resolve2(path);
586
+ return existsSync4(resolved) ? realpathSync3(resolved) : resolved;
587
+ }
588
+ function manifestName(file) {
589
+ let parsed;
590
+ try {
591
+ parsed = JSON.parse(readFileSync3(file, "utf8"));
592
+ } catch {
593
+ return null;
594
+ }
595
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
596
+ return null;
597
+ const name2 = parsed["name"];
598
+ return typeof name2 === "string" ? name2 : null;
599
+ }
600
+ function nearestRafaPackage(start) {
601
+ let dir = start;
602
+ for (;; ) {
603
+ const manifest = join6(dir, MANIFEST);
604
+ if (existsSync4(manifest) && manifestName(manifest) === name)
605
+ return dir;
606
+ const parent = dirname4(dir);
607
+ if (parent === dir)
608
+ return null;
609
+ dir = parent;
610
+ }
611
+ }
612
+ function readRuntimeIdentity(entry = Bun.main) {
613
+ const real = realOrResolved(entry);
614
+ const packageDir = nearestRafaPackage(dirname4(real));
615
+ if (packageDir !== null && existsSync4(join6(packageDir, CHECKOUT_ENTRY))) {
616
+ return { kind: "development", entry: real, checkout: packageDir };
617
+ }
618
+ return { kind: "installed", entry: real };
619
+ }
620
+
621
+ // src/effort/store/development-build.ts
622
+ var DEVELOPMENT_NEXT_STEP = "rafa effort copy";
623
+ var ADOPTION_NAME = "schema_migrations";
624
+ var EFFORT_DIR_NAME = "effort";
625
+ var DEVELOPMENT_MARK = "+dev:";
626
+ function appliedByName(identity) {
627
+ return identity.kind === "development" ? `${RAFA_VERSION}${DEVELOPMENT_MARK}${identity.checkout}` : RAFA_VERSION;
628
+ }
629
+
630
+ class DevelopmentBuildRefusedError extends Error {
631
+ path;
632
+ nextStep;
633
+ constructor(path, message) {
634
+ super(message);
635
+ this.name = "DevelopmentBuildRefusedError";
636
+ this.path = path;
637
+ this.nextStep = DEVELOPMENT_NEXT_STEP;
638
+ }
639
+ }
640
+ function storeProjectRoot(path) {
641
+ const storeDir = dirname5(path);
642
+ const scopeDir = dirname5(storeDir);
643
+ if (basename2(storeDir) !== EFFORT_DIR_NAME || basename2(scopeDir) !== SCOPE_DIR)
644
+ return null;
645
+ return dirname5(scopeDir);
646
+ }
647
+ function isOwnedStore(path, env, tempDir) {
648
+ if (isUnderTempDir(path, tempDir))
649
+ return true;
650
+ const override = env[EFFORT_DIR_VARIABLE];
651
+ if (override === undefined || override === "" || !isAbsolute3(override))
652
+ return false;
653
+ return storeProjectRoot(path) === null && isUnderTempDir(path, override);
654
+ }
655
+ function loopSentence(record) {
656
+ return ` Loop ${record.sessionId} (pid ${String(record.pid)}, plan ${record.planStub ?? record.plan}) is running on this store.`;
657
+ }
658
+ function liveLoopSentences(path, isAlive) {
659
+ const root = storeProjectRoot(path);
660
+ if (root === null)
661
+ return "";
662
+ try {
663
+ return readSessions(root, { isAlive }).filter((record) => record.state === "running" || record.state === "paused").map(loopSentence).join("");
664
+ } catch (error) {
665
+ return ` The loop records under ${runsDir(root)} could not be read (${messageOf(error)}).`;
666
+ }
667
+ }
668
+ function developmentRefusalText(path, needs, checkout, loops) {
669
+ 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;
670
+ }
671
+ function refuseUnownedDevelopmentWrite(path, needs, probe = {}) {
672
+ const identity = probe.identity ?? readRuntimeIdentity();
673
+ if (identity.kind !== "development")
674
+ return;
675
+ if (isOwnedStore(path, probe.env ?? process.env, probe.tempDir ?? tmpdir2()))
676
+ return;
677
+ const loops = liveLoopSentences(path, probe.isAlive ?? isPidAlive);
678
+ throw new DevelopmentBuildRefusedError(path, developmentRefusalText(path, needs, identity.checkout, loops));
679
+ }
680
+
180
681
  // src/effort/store/migrations.ts
682
+ import { createHash } from "crypto";
683
+ var LEGACY_GATE_OPEN = 13;
684
+ var LEGACY_GATE_CLOSED = 1000;
181
685
  var SQLITE_MIGRATIONS = [
182
- `
686
+ {
687
+ id: "kind-tables",
688
+ breaks: [],
689
+ sql: `
183
690
  CREATE TABLE sessions (
184
691
  seq INTEGER PRIMARY KEY,
185
692
  session_id TEXT NOT NULL UNIQUE CHECK (session_id <> ''),
@@ -191,8 +698,12 @@ var SQLITE_MIGRATIONS = [
191
698
  sha TEXT NOT NULL UNIQUE CHECK (sha <> ''),
192
699
  row_json TEXT NOT NULL
193
700
  );
194
- `,
195
701
  `
702
+ },
703
+ {
704
+ id: "findings",
705
+ breaks: [],
706
+ sql: `
196
707
  CREATE TABLE findings (
197
708
  seq INTEGER PRIMARY KEY,
198
709
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -220,8 +731,12 @@ var SQLITE_MIGRATIONS = [
220
731
  CREATE UNIQUE INDEX findings_by_trigger_what
221
732
  ON findings (session_id, trigger, what)
222
733
  WHERE artifact IS NULL;
223
- `,
224
734
  `
735
+ },
736
+ {
737
+ id: "blockers-and-bugs",
738
+ breaks: [],
739
+ sql: `
225
740
  CREATE TABLE blockers (
226
741
  seq INTEGER PRIMARY KEY,
227
742
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -252,8 +767,12 @@ var SQLITE_MIGRATIONS = [
252
767
 
253
768
  CREATE UNIQUE INDEX out_of_scope_bugs_by_entry
254
769
  ON out_of_scope_bugs (session_id, what, ifnull(artifact, ''), ifnull(security, -1));
255
- `,
256
770
  `
771
+ },
772
+ {
773
+ id: "report-absences",
774
+ breaks: [],
775
+ sql: `
257
776
  CREATE TABLE report_absences (
258
777
  seq INTEGER PRIMARY KEY,
259
778
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -266,8 +785,12 @@ var SQLITE_MIGRATIONS = [
266
785
  outcome TEXT NOT NULL,
267
786
  collected_at TEXT NOT NULL
268
787
  );
269
- `,
270
788
  `
789
+ },
790
+ {
791
+ id: "task-reports",
792
+ breaks: [],
793
+ sql: `
271
794
  CREATE TABLE task_reports (
272
795
  seq INTEGER PRIMARY KEY,
273
796
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -278,8 +801,12 @@ var SQLITE_MIGRATIONS = [
278
801
  outcome TEXT NOT NULL,
279
802
  collected_at TEXT NOT NULL
280
803
  );
281
- `,
282
804
  `
805
+ },
806
+ {
807
+ id: "preflight",
808
+ breaks: [],
809
+ sql: `
283
810
  CREATE TABLE preflight (
284
811
  seq INTEGER PRIMARY KEY,
285
812
  run_id TEXT NOT NULL CHECK (run_id <> ''),
@@ -295,8 +822,12 @@ var SQLITE_MIGRATIONS = [
295
822
  UNIQUE (run_id, position),
296
823
  CHECK ((outcome = 'pass') = (failure IS NULL))
297
824
  );
298
- `,
299
825
  `
826
+ },
827
+ {
828
+ id: "dispatches",
829
+ breaks: [],
830
+ sql: `
300
831
  CREATE TABLE dispatches (
301
832
  seq INTEGER PRIMARY KEY,
302
833
  session_id TEXT NOT NULL UNIQUE CHECK (session_id <> ''),
@@ -311,8 +842,12 @@ var SQLITE_MIGRATIONS = [
311
842
  flags TEXT NOT NULL,
312
843
  collected_at TEXT NOT NULL
313
844
  );
314
- `,
315
845
  `
846
+ },
847
+ {
848
+ id: "changes",
849
+ breaks: [],
850
+ sql: `
316
851
  CREATE TABLE changes (
317
852
  seq INTEGER PRIMARY KEY,
318
853
  id TEXT NOT NULL UNIQUE CHECK (id <> ''),
@@ -328,20 +863,32 @@ var SQLITE_MIGRATIONS = [
328
863
 
329
864
  CREATE UNIQUE INDEX changes_by_entry
330
865
  ON changes (session_id, level, ifnull(area, ''), summary);
331
- `,
332
866
  `
867
+ },
868
+ {
869
+ id: "out-of-scope-bug-scope",
870
+ breaks: [],
871
+ sql: `
333
872
  ALTER TABLE out_of_scope_bugs
334
873
  ADD COLUMN scope TEXT CHECK (scope IN ('machine', 'rafa'));
335
- `,
336
874
  `
875
+ },
876
+ {
877
+ id: "dispatch-skills",
878
+ breaks: [],
879
+ sql: `
337
880
  ALTER TABLE dispatches
338
881
  ADD COLUMN resolver TEXT CHECK (resolver IN ('planner', 'tag', 'none'));
339
882
  ALTER TABLE dispatches
340
883
  ADD COLUMN skills_offered TEXT CHECK (skills_offered IS NULL OR json_type(skills_offered) = 'array');
341
884
  ALTER TABLE dispatches
342
885
  ADD COLUMN lessons_offered TEXT CHECK (lessons_offered IS NULL OR json_type(lessons_offered) = 'array');
343
- `,
344
886
  `
887
+ },
888
+ {
889
+ id: "skill-invocations",
890
+ breaks: [],
891
+ sql: `
345
892
  CREATE TABLE skill_invocations (
346
893
  seq INTEGER PRIMARY KEY,
347
894
  session_id TEXT NOT NULL CHECK (session_id <> ''),
@@ -353,12 +900,20 @@ var SQLITE_MIGRATIONS = [
353
900
 
354
901
  CREATE UNIQUE INDEX skill_invocations_by_use
355
902
  ON skill_invocations (session_id, ifnull(name, ''), ifnull(sidechain, -1));
356
- `,
357
903
  `
904
+ },
905
+ {
906
+ id: "task-report-skills",
907
+ breaks: [],
908
+ sql: `
358
909
  ALTER TABLE task_reports
359
910
  ADD COLUMN skills_used TEXT CHECK (skills_used IS NULL OR json_type(skills_used) = 'array');
360
- `,
361
911
  `
912
+ },
913
+ {
914
+ id: "plan-ci",
915
+ breaks: [],
916
+ sql: `
362
917
  CREATE TABLE plan_ci (
363
918
  seq INTEGER PRIMARY KEY,
364
919
  plan_stub TEXT NOT NULL CHECK (plan_stub <> ''),
@@ -371,13 +926,231 @@ var SQLITE_MIGRATIONS = [
371
926
  CHECK ((verdict = 'red') = (json_array_length(failing) > 0))
372
927
  );
373
928
  `
929
+ }
374
930
  ];
375
931
  var SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.length;
932
+ function legacyGate(applied) {
933
+ return applied.every(({ breaks }) => breaks.length === 0) ? LEGACY_GATE_OPEN : LEGACY_GATE_CLOSED;
934
+ }
935
+ function migrationChecksum(migration) {
936
+ return createHash("sha256").update(migration.sql, "utf8").digest("hex");
937
+ }
938
+
939
+ // src/effort/store/schema-plan.ts
940
+ var FIX_SCHEMA_STEP = "rafa effort fix-schema --dry-run";
941
+ function sqliteCatalogue(migrations) {
942
+ return migrations.map(({ sql, ...spec }) => ({ ...spec, sha256: migrationChecksum({ sql }) }));
943
+ }
944
+ function planSchema(store, catalogue, access, caller) {
945
+ const { path, userVersion, log } = store;
946
+ if (log === null && (userVersion < 0 || userVersion > LEGACY_GATE_OPEN)) {
947
+ 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.");
948
+ }
949
+ if (log !== null && userVersion !== LEGACY_GATE_OPEN && userVersion !== LEGACY_GATE_CLOSED) {
950
+ 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.");
951
+ }
952
+ const adopted = log === null ? adoptedEntries(catalogue, userVersion) : [];
953
+ const held = log ?? [];
954
+ const logRefusal = judgeLog(path, held, catalogue);
955
+ if (logRefusal !== null)
956
+ return logRefusal;
957
+ const heldIds = new Set([...adopted, ...held].map(({ id }) => id));
958
+ const pending = catalogue.filter(({ id }) => !heldIds.has(id));
959
+ const unknown = held.filter(({ id }) => !catalogue.some((entry) => entry.id === id));
960
+ const writes = access === "write" || pending.length > 0;
961
+ const writersBroken = unknown.find(({ breaks }) => breaks.includes("writers"));
962
+ if (writes && writersBroken !== undefined) {
963
+ return refuseUnknown("unknown-breaks-writers", path, writersBroken);
964
+ }
965
+ const orderRefusal = judgePending(path, catalogue, heldIds, pending, caller);
966
+ if (orderRefusal !== null)
967
+ return orderRefusal;
968
+ return {
969
+ verdict: "use",
970
+ adopted,
971
+ pending,
972
+ unknown,
973
+ gate: legacyGate([...adopted, ...held, ...pending])
974
+ };
975
+ }
976
+ function adoptedEntries(catalogue, count) {
977
+ const adopted = catalogue.slice(0, count);
978
+ if (adopted.length < count) {
979
+ 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`);
980
+ }
981
+ return adopted;
982
+ }
983
+ function judgeLog(path, log, catalogue) {
984
+ const byId = new Map(catalogue.map((entry) => [entry.id, entry]));
985
+ for (const row of log) {
986
+ const known = byId.get(row.id);
987
+ if (known !== undefined && known.sha256 !== row.sha256) {
988
+ 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.");
989
+ }
990
+ }
991
+ const readersBroken = log.find(({ id, breaks }) => !byId.has(id) && breaks.some((word) => word !== "writers"));
992
+ return readersBroken === undefined ? null : refuseUnknown("unknown-breaks-readers", path, readersBroken);
993
+ }
994
+ function judgePending(path, catalogue, heldIds, pending, caller) {
995
+ const breaking = pending.filter(({ breaks }) => breaks.length > 0);
996
+ for (const entry of breaking) {
997
+ const later = catalogue.slice(catalogue.indexOf(entry) + 1).find(({ id }) => heldIds.has(id));
998
+ if (later !== undefined) {
999
+ 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.");
1000
+ }
1001
+ }
1002
+ const [first] = breaking;
1003
+ if (first === undefined || caller === "migrate")
1004
+ return null;
1005
+ 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.");
1006
+ }
1007
+ function refuseUnknown(reason, path, row) {
1008
+ const ending = reason === "unknown-breaks-readers" ? "which breaks older readers; refusing to read or write it." : "which breaks older writers; refusing to write it.";
1009
+ 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}`);
1010
+ }
1011
+ function dateOf(appliedAt) {
1012
+ return /^\d{4}-\d{2}-\d{2}/.test(appliedAt) ? appliedAt.slice(0, 10) : appliedAt;
1013
+ }
1014
+ function refuse(reason, nextStep, text) {
1015
+ return { verdict: "refuse", reason, message: `${text} Next safe step: ${nextStep}`, nextStep };
1016
+ }
1017
+
1018
+ // src/effort/store/bring-forward.ts
1019
+ var MIGRATION_LOG_TABLE = "schema_migrations";
1020
+ var CREATE_MIGRATION_LOG = `
1021
+ CREATE TABLE IF NOT EXISTS ${MIGRATION_LOG_TABLE} (
1022
+ seq INTEGER PRIMARY KEY,
1023
+ id TEXT NOT NULL UNIQUE CHECK (id <> ''),
1024
+ sha256 TEXT NOT NULL CHECK (length(sha256) = 64),
1025
+ breaks TEXT NOT NULL,
1026
+ applied_at TEXT NOT NULL,
1027
+ applied_by TEXT NOT NULL CHECK (applied_by <> '')
1028
+ );
1029
+ `;
1030
+
1031
+ class SchemaRefusedError extends Error {
1032
+ reason;
1033
+ nextStep;
1034
+ constructor(refusal) {
1035
+ super(refusal.message);
1036
+ this.name = "SchemaRefusedError";
1037
+ this.reason = refusal.reason;
1038
+ this.nextStep = refusal.nextStep;
1039
+ }
1040
+ }
1041
+ function bringForward(db, path, access, caller, options = {}) {
1042
+ const migrations = options.migrations ?? SQLITE_MIGRATIONS;
1043
+ const catalogue = sqliteCatalogue(migrations);
1044
+ const first = usablePlan(readStoreSchema(db, path), catalogue, access, caller);
1045
+ if (!hasWork(first))
1046
+ return nothingWritten(db);
1047
+ if (options.builtAside !== true)
1048
+ refuseUnownedDevelopmentWrite(path, writeNames(first), options);
1049
+ const apply = db.transaction(() => {
1050
+ const store = readStoreSchema(db, path);
1051
+ const plan = usablePlan(store, catalogue, access, caller);
1052
+ if (!hasWork(plan))
1053
+ return nothingWritten(db);
1054
+ const stamp = {
1055
+ appliedAt: (options.now ?? (() => new Date))().toISOString(),
1056
+ appliedBy: options.appliedBy ?? RAFA_VERSION
1057
+ };
1058
+ db.run(CREATE_MIGRATION_LOG);
1059
+ for (const entry of plan.adopted)
1060
+ logMigration(db, entry, stamp);
1061
+ for (const entry of plan.pending) {
1062
+ db.run(bodyOf(migrations, entry.id));
1063
+ logMigration(db, entry, stamp);
1064
+ }
1065
+ db.run(`PRAGMA user_version = ${String(plan.gate)}`);
1066
+ return {
1067
+ adopted: plan.adopted.map(({ id }) => id),
1068
+ applied: plan.pending.map(({ id }) => id),
1069
+ userVersion: plan.gate
1070
+ };
1071
+ });
1072
+ return apply.immediate();
1073
+ }
1074
+ function readStoreSchema(db, path) {
1075
+ return { path, userVersion: readUserVersion(db), log: readMigrationLog(db, path) };
1076
+ }
1077
+ function usablePlan(store, catalogue, access, caller) {
1078
+ const plan = planSchema(store, catalogue, access, caller);
1079
+ if (plan.verdict === "refuse")
1080
+ throw new SchemaRefusedError(plan);
1081
+ return plan;
1082
+ }
1083
+ function hasWork(plan) {
1084
+ return plan.adopted.length > 0 || plan.pending.length > 0;
1085
+ }
1086
+ function writeNames(plan) {
1087
+ const adoption = plan.adopted.length > 0 ? [ADOPTION_NAME] : [];
1088
+ return [...adoption, ...plan.pending.map(({ id }) => id)];
1089
+ }
1090
+ function nothingWritten(db) {
1091
+ return { adopted: [], applied: [], userVersion: readUserVersion(db) };
1092
+ }
1093
+ function readUserVersion(db) {
1094
+ const row = db.query("PRAGMA user_version").get();
1095
+ return row?.user_version ?? 0;
1096
+ }
1097
+ function readMigrationLog(db, path) {
1098
+ const table = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(MIGRATION_LOG_TABLE);
1099
+ if (table === null)
1100
+ return null;
1101
+ return db.query(`SELECT id, sha256, breaks, applied_at, applied_by FROM ${MIGRATION_LOG_TABLE} ORDER BY seq`).all().map((row) => ({
1102
+ id: row.id,
1103
+ sha256: row.sha256,
1104
+ breaks: parseBreaks(row.breaks, row.id, path),
1105
+ appliedAt: row.applied_at,
1106
+ appliedBy: row.applied_by
1107
+ }));
1108
+ }
1109
+ function parseBreaks(text, id, path) {
1110
+ 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");
1111
+ let parsed;
1112
+ try {
1113
+ parsed = JSON.parse(text);
1114
+ } catch (cause) {
1115
+ throw new Error(refused.message, { cause });
1116
+ }
1117
+ if (!Array.isArray(parsed) || !parsed.every((word) => typeof word === "string"))
1118
+ throw refused;
1119
+ return parsed;
1120
+ }
1121
+ function bodyOf(migrations, id) {
1122
+ const migration = migrations.find((candidate) => candidate.id === id);
1123
+ if (migration === undefined)
1124
+ throw new Error(`bring forward: no migration ${id} in the catalogue it planned from`);
1125
+ return migration.sql;
1126
+ }
1127
+ function logMigration(db, entry, stamp) {
1128
+ 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]);
1129
+ }
1130
+
1131
+ // src/effort/store/settings.ts
1132
+ var DEFAULT_STORE_SETTINGS = Object.freeze({
1133
+ busyTimeoutMs: CONFIG_DEFAULTS.effortBusyTimeoutMs
1134
+ });
1135
+ var active = DEFAULT_STORE_SETTINGS;
1136
+ function setActiveStoreSettings(settings) {
1137
+ if (settings === null) {
1138
+ active = DEFAULT_STORE_SETTINGS;
1139
+ return;
1140
+ }
1141
+ if (!isBusyTimeoutMs(settings.busyTimeoutMs)) {
1142
+ 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)}`);
1143
+ }
1144
+ active = Object.freeze({ busyTimeoutMs: settings.busyTimeoutMs });
1145
+ }
1146
+ function activeStoreSettings() {
1147
+ return active;
1148
+ }
376
1149
 
377
1150
  // src/effort/store/sqlite.ts
378
- var STORE_FILE_NAME = "effort.sqlite";
1151
+ var SQLITE_STORE_FILE_NAME = "effort.sqlite";
379
1152
  function sqliteStorePath(repoRoot) {
380
- return join2(repoRoot, EFFORT_STORE_DIR, STORE_FILE_NAME);
1153
+ return join7(effortStoreDir(repoRoot), SQLITE_STORE_FILE_NAME);
381
1154
  }
382
1155
  var KIND_TABLES = {
383
1156
  sessions: { name: "sessions", keyColumn: "session_id" },
@@ -405,35 +1178,14 @@ function batchEntries(kind, rows) {
405
1178
  }
406
1179
  return keyed.map(({ key, row }) => ({ key, body: JSON.stringify(row) }));
407
1180
  }
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) {
1181
+ function withSqliteStore(path, access, create, use) {
1182
+ guardTestProcess(path);
432
1183
  if (create)
433
- mkdirSync2(dirname2(path), { recursive: true });
1184
+ mkdirSync3(dirname6(path), { recursive: true });
434
1185
  const db = new Database(path, { readwrite: true, create });
435
1186
  try {
436
- migrateSchema(db, path);
1187
+ db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
1188
+ bringForward(db, path, access, "open");
437
1189
  return use(db);
438
1190
  } finally {
439
1191
  db.close();
@@ -445,11 +1197,11 @@ function insertBatch(db, table, entries) {
445
1197
  return insertAll.immediate();
446
1198
  }
447
1199
  function writeSqliteStore(path, rowCount, empty, insert) {
448
- const exists = existsSync2(path);
1200
+ const exists = existsSync5(path);
449
1201
  if (rowCount > 0)
450
- return withSqliteStore(path, !exists, insert);
1202
+ return withSqliteStore(path, "write", !exists, insert);
451
1203
  if (exists)
452
- withSqliteStore(path, false, () => {
1204
+ withSqliteStore(path, "write", false, () => {
453
1205
  return;
454
1206
  });
455
1207
  return empty;
@@ -461,10 +1213,10 @@ function appendKind2(path, kind, rows) {
461
1213
  return { path, appended, skipped: entries.length - appended };
462
1214
  }
463
1215
  function keysOfKind2(path, kind) {
464
- if (!existsSync2(path))
1216
+ if (!existsSync5(path))
465
1217
  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());
1218
+ const { name: name2, keyColumn } = KIND_TABLES[kind];
1219
+ const keys = withSqliteStore(path, "read", false, (db) => db.query(`SELECT ${keyColumn} AS key FROM ${name2} ORDER BY seq`).all());
468
1220
  return new Set(keys.map(({ key }) => key));
469
1221
  }
470
1222
  function parsedObject(body) {
@@ -483,11 +1235,11 @@ function rowOf(body, where) {
483
1235
  return value;
484
1236
  }
485
1237
  function readKind2(path, kind) {
486
- if (!existsSync2(path))
1238
+ if (!existsSync5(path))
487
1239
  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}`));
1240
+ const { name: name2 } = KIND_TABLES[kind];
1241
+ const stored = withSqliteStore(path, "read", false, (db) => db.query(`SELECT seq, row_json AS body FROM ${name2} ORDER BY seq`).all());
1242
+ return stored.map(({ seq, body }) => rowOf(body, `${path}: ${name2} row ${seq}`));
491
1243
  }
492
1244
  function openSqliteStore(repoRoot) {
493
1245
  const path = sqliteStorePath(repoRoot);
@@ -501,15 +1253,15 @@ function openSqliteStore(repoRoot) {
501
1253
 
502
1254
  // src/adapters/learning/local.ts
503
1255
  import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
504
- import { join as join5 } from "path";
1256
+ import { join as join10 } from "path";
505
1257
 
506
1258
  // 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";
1259
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
1260
+ import { join as join9 } from "path";
509
1261
 
510
1262
  // src/check/layout.ts
511
- import { readdirSync, statSync } from "fs";
512
- import { join as join3, relative } from "path";
1263
+ import { readdirSync as readdirSync2, statSync } from "fs";
1264
+ import { join as join8, relative as relative2 } from "path";
513
1265
  var SKILL_FILE = "SKILL.md";
514
1266
  var MARKDOWN_EXTENSION = ".md";
515
1267
  var LAYOUT_SEVERITY = Object.freeze({
@@ -538,16 +1290,16 @@ function entryKind(path) {
538
1290
  }
539
1291
  }
540
1292
  function visibleNames(dir) {
541
- return readdirSync(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
1293
+ return readdirSync2(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
542
1294
  }
543
- function isMarkdown(name) {
544
- return name.endsWith(MARKDOWN_EXTENSION);
1295
+ function isMarkdown(name2) {
1296
+ return name2.endsWith(MARKDOWN_EXTENSION);
545
1297
  }
546
- function markdownStem(name) {
547
- return name.slice(0, -MARKDOWN_EXTENSION.length);
1298
+ function markdownStem(name2) {
1299
+ return name2.slice(0, -MARKDOWN_EXTENSION.length);
548
1300
  }
549
1301
  function shown(root, path) {
550
- return relative(root, path) || ".";
1302
+ return relative2(root, path) || ".";
551
1303
  }
552
1304
  function dotfileOnlyEntry(kind, root, dir) {
553
1305
  return {
@@ -569,37 +1321,37 @@ function unreadableEntry(kind, root, path) {
569
1321
  issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
570
1322
  };
571
1323
  }
572
- function skillFileEntry(path, name) {
573
- return { kind: "skill", path, isFile: true, shape: "skill-file", name, issues: [] };
1324
+ function skillFileEntry(path, name2) {
1325
+ return { kind: "skill", path, isFile: true, shape: "skill-file", name: name2, issues: [] };
574
1326
  }
575
- function flatGroupedEntry(root, path, name) {
1327
+ function flatGroupedEntry(root, path, name2) {
576
1328
  return {
577
1329
  kind: "skill",
578
1330
  path,
579
1331
  isFile: true,
580
1332
  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}`)]
1333
+ name: name2,
1334
+ 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
1335
  };
584
1336
  }
585
- function nestedGroupEntry(root, path, name) {
1337
+ function nestedGroupEntry(root, path, name2) {
586
1338
  return {
587
1339
  kind: "skill",
588
1340
  path,
589
1341
  isFile: true,
590
1342
  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}`)]
1343
+ name: name2,
1344
+ 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
1345
  };
594
1346
  }
595
- function looseFileEntry(root, path, name) {
1347
+ function looseFileEntry(root, path, name2) {
596
1348
  return {
597
1349
  kind: "skill",
598
1350
  path,
599
1351
  isFile: true,
600
1352
  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}`)]
1353
+ name: name2,
1354
+ 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
1355
  };
604
1356
  }
605
1357
  function noSkillFileEntry(root, dir) {
@@ -612,30 +1364,30 @@ function noSkillFileEntry(root, dir) {
612
1364
  issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
613
1365
  };
614
1366
  }
615
- function nestedInstinctEntry(root, path, name) {
1367
+ function nestedInstinctEntry(root, path, name2) {
616
1368
  return {
617
1369
  kind: "instinct",
618
1370
  path,
619
1371
  isFile: true,
620
1372
  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}`)]
1373
+ name: name2,
1374
+ issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name2}${MARKDOWN_EXTENSION}`)]
623
1375
  };
624
1376
  }
625
- function scanGroupMember(root, dir, name) {
1377
+ function scanGroupMember(root, dir, name2) {
626
1378
  const children = visibleNames(dir);
627
1379
  if (children.length === 0)
628
1380
  return [dotfileOnlyEntry("skill", root, dir)];
629
- const skillPath = join3(dir, SKILL_FILE);
1381
+ const skillPath = join8(dir, SKILL_FILE);
630
1382
  if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
631
- return [nestedGroupEntry(root, skillPath, name)];
1383
+ return [nestedGroupEntry(root, skillPath, name2)];
632
1384
  }
633
1385
  return [noSkillFileEntry(root, dir)];
634
1386
  }
635
1387
  function scanSkillGroup(root, dir, children) {
636
1388
  const entries = [];
637
1389
  for (const child of children) {
638
- const path = join3(dir, child);
1390
+ const path = join8(dir, child);
639
1391
  const found = entryKind(path);
640
1392
  if (found === null) {
641
1393
  entries.push(unreadableEntry("skill", root, path));
@@ -652,31 +1404,31 @@ function scanSkillGroup(root, dir, children) {
652
1404
  return [noSkillFileEntry(root, dir)];
653
1405
  return entries;
654
1406
  }
655
- function scanSkillDirectory(root, dir, name) {
1407
+ function scanSkillDirectory(root, dir, name2) {
656
1408
  const children = visibleNames(dir);
657
1409
  if (children.length === 0)
658
1410
  return [dotfileOnlyEntry("skill", root, dir)];
659
- const skillPath = join3(dir, SKILL_FILE);
1411
+ const skillPath = join8(dir, SKILL_FILE);
660
1412
  if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
661
- return [skillFileEntry(skillPath, name)];
1413
+ return [skillFileEntry(skillPath, name2)];
662
1414
  }
663
1415
  return scanSkillGroup(root, dir, children);
664
1416
  }
665
1417
  function scanSkillRoot(root) {
666
1418
  const entries = [];
667
- for (const name of visibleNames(root)) {
668
- const path = join3(root, name);
1419
+ for (const name2 of visibleNames(root)) {
1420
+ const path = join8(root, name2);
669
1421
  const found = entryKind(path);
670
1422
  if (found === null) {
671
1423
  entries.push(unreadableEntry("skill", root, path));
672
1424
  continue;
673
1425
  }
674
1426
  if (found === "directory") {
675
- entries.push(...scanSkillDirectory(root, path, name));
1427
+ entries.push(...scanSkillDirectory(root, path, name2));
676
1428
  continue;
677
1429
  }
678
- if (isMarkdown(name))
679
- entries.push(looseFileEntry(root, path, markdownStem(name)));
1430
+ if (isMarkdown(name2))
1431
+ entries.push(looseFileEntry(root, path, markdownStem(name2)));
680
1432
  }
681
1433
  return entries;
682
1434
  }
@@ -686,7 +1438,7 @@ function scanInstinctSubdirectory(root, dir) {
686
1438
  return [dotfileOnlyEntry("instinct", root, dir)];
687
1439
  const entries = [];
688
1440
  for (const child of children) {
689
- const path = join3(dir, child);
1441
+ const path = join8(dir, child);
690
1442
  const found = entryKind(path);
691
1443
  if (found === null) {
692
1444
  entries.push(unreadableEntry("instinct", root, path));
@@ -703,8 +1455,8 @@ function scanInstinctSubdirectory(root, dir) {
703
1455
  }
704
1456
  function scanInstinctRoot(root) {
705
1457
  const entries = [];
706
- for (const name of visibleNames(root)) {
707
- const path = join3(root, name);
1458
+ for (const name2 of visibleNames(root)) {
1459
+ const path = join8(root, name2);
708
1460
  const found = entryKind(path);
709
1461
  if (found === null) {
710
1462
  entries.push(unreadableEntry("instinct", root, path));
@@ -714,14 +1466,14 @@ function scanInstinctRoot(root) {
714
1466
  entries.push(...scanInstinctSubdirectory(root, path));
715
1467
  continue;
716
1468
  }
717
- if (!isMarkdown(name))
1469
+ if (!isMarkdown(name2))
718
1470
  continue;
719
1471
  entries.push({
720
1472
  kind: "instinct",
721
1473
  path,
722
1474
  isFile: true,
723
1475
  shape: "instinct-file",
724
- name: markdownStem(name),
1476
+ name: markdownStem(name2),
725
1477
  issues: []
726
1478
  });
727
1479
  }
@@ -741,9 +1493,9 @@ function scanLayout(root, kind) {
741
1493
  function declaredNameIssue(entry, declared) {
742
1494
  if (entry.name === null || declared === null || declared === entry.name)
743
1495
  return null;
744
- const field = entry.kind === "skill" ? "name" : "id";
1496
+ const field2 = entry.kind === "skill" ? "name" : "id";
745
1497
  const source = entry.kind === "skill" ? "directory name" : "file name";
746
- return layoutIssue("name-mismatch", `frontmatter ${field} "${declared}" is not the ${source} "${entry.name}"`);
1498
+ return layoutIssue("name-mismatch", `frontmatter ${field2} "${declared}" is not the ${source} "${entry.name}"`);
747
1499
  }
748
1500
 
749
1501
  // src/schema/frontmatter.ts
@@ -889,8 +1641,8 @@ var INSTINCT_KEY_ORDER = [
889
1641
  "created_at",
890
1642
  "updated_at"
891
1643
  ];
892
- function issue(code, field, message) {
893
- return { code, field, message };
1644
+ function issue(code, field2, message) {
1645
+ return { code, field: field2, message };
894
1646
  }
895
1647
  function typeName(value) {
896
1648
  if (value === null)
@@ -905,25 +1657,25 @@ function typeName(value) {
905
1657
  return "a number";
906
1658
  return `a ${typeof value}`;
907
1659
  }
908
- function requiredString(data, field) {
909
- const value = data[field];
1660
+ function requiredString(data, field2) {
1661
+ const value = data[field2];
910
1662
  if (value === undefined || value === null) {
911
- return [issue("missing-field", field, `${field} is required and is absent`)];
1663
+ return [issue("missing-field", field2, `${field2} is required and is absent`)];
912
1664
  }
913
1665
  if (typeof value !== "string") {
914
- return [issue("wrong-type", field, `${field} must be a string, not ${typeName(value)}`)];
1666
+ return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(value)}`)];
915
1667
  }
916
1668
  if (value.trim() === "") {
917
- return [issue("missing-field", field, `${field} is required and is empty`)];
1669
+ return [issue("missing-field", field2, `${field2} is required and is empty`)];
918
1670
  }
919
1671
  return [];
920
1672
  }
921
- function requiredChoice(data, field, allowed, code) {
922
- const issues = requiredString(data, field);
1673
+ function requiredChoice(data, field2, allowed, code) {
1674
+ const issues = requiredString(data, field2);
923
1675
  if (issues.length > 0)
924
1676
  return issues;
925
- const value = data[field];
926
- return allowed.includes(value) ? [] : [issue(code, field, `${field} "${value}" is not one of ${allowed.join(", ")}`)];
1677
+ const value = data[field2];
1678
+ return allowed.includes(value) ? [] : [issue(code, field2, `${field2} "${value}" is not one of ${allowed.join(", ")}`)];
927
1679
  }
928
1680
  function checkId(data) {
929
1681
  const issues = requiredString(data, "id");
@@ -959,11 +1711,11 @@ function checkSources(data) {
959
1711
  return [issue("wrong-type", "sources", `sources must be a list, not ${typeName(value)}`)];
960
1712
  }
961
1713
  return value.flatMap((entry, index) => {
962
- const field = `sources[${index}]`;
1714
+ const field2 = `sources[${index}]`;
963
1715
  if (typeof entry !== "string") {
964
- return [issue("wrong-type", field, `sources entries must be strings, and this one is ${typeName(entry)}`)];
1716
+ return [issue("wrong-type", field2, `sources entries must be strings, and this one is ${typeName(entry)}`)];
965
1717
  }
966
- return entry.trim() === "" ? [issue("wrong-type", field, "sources entries must name a source, and this one is blank")] : [];
1718
+ return entry.trim() === "" ? [issue("wrong-type", field2, "sources entries must name a source, and this one is blank")] : [];
967
1719
  });
968
1720
  }
969
1721
  function checkPromotedTo(data) {
@@ -992,26 +1744,26 @@ function checkProjectId(data) {
992
1744
  }
993
1745
  return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
994
1746
  }
995
- function checkTimestamp(data, field) {
996
- const issues = requiredString(data, field);
1747
+ function checkTimestamp(data, field2) {
1748
+ const issues = requiredString(data, field2);
997
1749
  if (issues.length > 0)
998
1750
  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`)];
1751
+ const value = data[field2];
1752
+ 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
1753
  }
1002
1754
  function isScalar(value) {
1003
1755
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1004
1756
  }
1005
1757
  function checkEvidenceEntry(entry, index) {
1006
- const field = `evidence[${index}]`;
1758
+ const field2 = `evidence[${index}]`;
1007
1759
  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)}`)];
1760
+ return [issue("wrong-type", field2, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
1009
1761
  }
1010
1762
  const entries = Object.entries(entry);
1011
1763
  if (entries.length === 0) {
1012
- return [issue("wrong-type", field, "evidence entries must say something, and this one is empty")];
1764
+ return [issue("wrong-type", field2, "evidence entries must say something, and this one is empty")];
1013
1765
  }
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)}`)]);
1766
+ 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
1767
  }
1016
1768
  function checkEvidence(data) {
1017
1769
  const value = data["evidence"];
@@ -1171,11 +1923,11 @@ function writeInstinct(instinct, newline = `
1171
1923
  }
1172
1924
 
1173
1925
  // src/commands/instinct/instinct-records.ts
1174
- function isRecordFile(name) {
1175
- return !name.startsWith(".") && name.endsWith(MARKDOWN_EXTENSION) && name !== MARKDOWN_EXTENSION;
1926
+ function isRecordFile(name2) {
1927
+ return !name2.startsWith(".") && name2.endsWith(MARKDOWN_EXTENSION) && name2 !== MARKDOWN_EXTENSION;
1176
1928
  }
1177
- function recordId(name) {
1178
- return name.slice(0, -MARKDOWN_EXTENSION.length);
1929
+ function recordId(name2) {
1930
+ return name2.slice(0, -MARKDOWN_EXTENSION.length);
1179
1931
  }
1180
1932
  function unreadableIssue(message) {
1181
1933
  return { code: "missing-frontmatter", field: "file", message: `the file could not be read: ${message}` };
@@ -1183,7 +1935,7 @@ function unreadableIssue(message) {
1183
1935
  function readRecord(path, scope, id) {
1184
1936
  let text;
1185
1937
  try {
1186
- text = readFileSync2(path, "utf8");
1938
+ text = readFileSync4(path, "utf8");
1187
1939
  } catch (error) {
1188
1940
  const message = error instanceof Error ? error.message : String(error);
1189
1941
  return { scope, path, id, instinct: null, issues: [unreadableIssue(message)] };
@@ -1199,7 +1951,7 @@ function isFile(path) {
1199
1951
  }
1200
1952
  }
1201
1953
  function recordFiles(dir) {
1202
- return readdirSync2(dir).filter((name) => isRecordFile(name) && isFile(join4(dir, name))).sort((left, right) => left.localeCompare(right));
1954
+ return readdirSync3(dir).filter((name2) => isRecordFile(name2) && isFile(join9(dir, name2))).sort((left, right) => left.localeCompare(right));
1203
1955
  }
1204
1956
  function readScope(scope, dir) {
1205
1957
  if (!tierExists(dir))
@@ -1208,7 +1960,7 @@ function readScope(scope, dir) {
1208
1960
  scope,
1209
1961
  dir,
1210
1962
  exists: true,
1211
- records: recordFiles(dir).map((name) => readRecord(join4(dir, name), scope, recordId(name)))
1963
+ records: recordFiles(dir).map((name2) => readRecord(join9(dir, name2), scope, recordId(name2)))
1212
1964
  };
1213
1965
  }
1214
1966
  function readScopes(seams) {
@@ -1220,9 +1972,9 @@ function allRecords(listings) {
1220
1972
  function findRecords(listings, id) {
1221
1973
  return allRecords(listings).filter((entry) => entry.id === id);
1222
1974
  }
1223
- function instinctProject(context, name) {
1975
+ function instinctProject(context, name2) {
1224
1976
  if (context.project === null)
1225
- throw new Error(`${name} runs inside a project, and was handed none`);
1977
+ throw new Error(`${name2} runs inside a project, and was handed none`);
1226
1978
  return context.project;
1227
1979
  }
1228
1980
 
@@ -1305,7 +2057,7 @@ var PREFIX = "local learning";
1305
2057
  var PUSH_REFUSAL = `${PREFIX}: refused to store a push`;
1306
2058
  var INSTINCTS_FILE = "instincts.ndjson";
1307
2059
  var FLAGS_FILE = "flags.ndjson";
1308
- var RECORD_EXTENSION = ".md";
2060
+ var RECORD_EXTENSION2 = ".md";
1309
2061
  var MIN_CONFIDENCE = 0.3;
1310
2062
  var MAX_CONFIDENCE = 0.9;
1311
2063
  var SIGNALS = ["loud", "silent"];
@@ -1489,7 +2241,7 @@ function producedRecords(result) {
1489
2241
  for (const record of result.decisions.flatMap((decision) => decision.produced)) {
1490
2242
  const found = byId.get(record.id);
1491
2243
  if (found !== undefined && found !== record) {
1492
- throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION}`);
2244
+ throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION2}`);
1493
2245
  }
1494
2246
  byId.set(record.id, record);
1495
2247
  }
@@ -1501,7 +2253,7 @@ function heldChanges(dir, held, result, described) {
1501
2253
  const writes = [];
1502
2254
  const kept = new Set;
1503
2255
  for (const record of producedRecords(result)) {
1504
- const file = `${record.id}${RECORD_EXTENSION}`;
2256
+ const file = `${record.id}${RECORD_EXTENSION2}`;
1505
2257
  const instinct = toHeldInstinct(record, heldInstincts, payloadDescription(record, described));
1506
2258
  if (instinct === null) {
1507
2259
  throw new Error(`${PUSH_REFUSAL}: ${file} has no held member and no payload description to be written with`);
@@ -1511,7 +2263,7 @@ function heldChanges(dir, held, result, described) {
1511
2263
  if (issue2 !== undefined) {
1512
2264
  throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${issue2.field}: ${issue2.message}`);
1513
2265
  }
1514
- const path = join5(dir, file);
2266
+ const path = join10(dir, file);
1515
2267
  if (held.unreadable.has(path)) {
1516
2268
  throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which could not be read`);
1517
2269
  }
@@ -1541,18 +2293,18 @@ function pushLogLines(sourceId, described, result) {
1541
2293
  });
1542
2294
  }
1543
2295
  function localInstinctsDir(repoRoot) {
1544
- return join5(repoRoot, RAFA_INSTINCTS_PATH);
2296
+ return join10(repoRoot, RAFA_INSTINCTS_PATH);
1545
2297
  }
1546
2298
  function userInstinctsDir(home) {
1547
- return join5(home, RAFA_INSTINCTS_PATH);
2299
+ return join10(home, RAFA_INSTINCTS_PATH);
1548
2300
  }
1549
2301
  function createLocalLearning(options) {
1550
2302
  const { instinctsDir, minConfidence } = options;
1551
2303
  const userDir = userInstinctsDir(options.home);
1552
2304
  const now = options.now ?? (() => new Date().toISOString());
1553
2305
  const warn = options.warn ?? warnThroughActiveOutput;
1554
- const instinctsPath = join5(instinctsDir, INSTINCTS_FILE);
1555
- const flagsPath = join5(instinctsDir, FLAGS_FILE);
2306
+ const instinctsPath = join10(instinctsDir, INSTINCTS_FILE);
2307
+ const flagsPath = join10(instinctsDir, FLAGS_FILE);
1556
2308
  function readHeld(scope, dir) {
1557
2309
  const { records } = readScope(scope, dir);
1558
2310
  const unreadable = new Set;
@@ -1669,9 +2421,9 @@ function createJsonOutput({
1669
2421
  }
1670
2422
 
1671
2423
  // src/adapters/planner/claude.ts
1672
- import { existsSync as existsSync3 } from "fs";
2424
+ import { existsSync as existsSync6 } from "fs";
1673
2425
  import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
1674
- import { posix, resolve } from "path";
2426
+ import { posix, resolve as resolve3 } from "path";
1675
2427
 
1676
2428
  // src/board/spec-review.ts
1677
2429
  var SPEC_REVIEW_KIND = "spec-review";
@@ -1693,7 +2445,7 @@ var VERDICT_BREAK = /[\s_]+/gu;
1693
2445
  function isMapping2(value) {
1694
2446
  return typeof value === "object" && value !== null && !Array.isArray(value);
1695
2447
  }
1696
- function describeValue2(value) {
2448
+ function describeValue3(value) {
1697
2449
  if (typeof value === "string")
1698
2450
  return JSON.stringify(value);
1699
2451
  if (value === null)
@@ -1726,39 +2478,39 @@ function verdictOf(value) {
1726
2478
  const word = spelled.replace(VERDICT_BREAK, "-");
1727
2479
  return SPEC_REVIEW_VERDICTS.includes(word) ? word : null;
1728
2480
  }
1729
- function issueAt(field, text) {
1730
- return { field, text };
2481
+ function issueAt(field2, text) {
2482
+ return { field: field2, text };
1731
2483
  }
1732
- function readBlocking(item, field, issues) {
2484
+ function readBlocking(item, field2, issues) {
1733
2485
  const value = valueAt(item, "blocking");
1734
2486
  if (typeof value === "boolean")
1735
2487
  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));
2488
+ const text = `${field2}.blocking is ${describeValue3(value)}, not true or false; ` + "the gap is read as blocking";
2489
+ issues.push(issueAt(`${field2}.blocking`, text));
1738
2490
  return true;
1739
2491
  }
1740
- function readGap(item, field, issues) {
2492
+ function readGap(item, field2, issues) {
1741
2493
  if (!isMapping2(item)) {
1742
- const text = `${field} is ${describeValue2(item)}, not a mapping of heading and what; dropped`;
1743
- issues.push(issueAt(field, text));
2494
+ const text = `${field2} is ${describeValue3(item)}, not a mapping of heading and what; dropped`;
2495
+ issues.push(issueAt(field2, text));
1744
2496
  return null;
1745
2497
  }
1746
2498
  const what = stringAt(item, "what");
1747
2499
  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));
2500
+ const text = `${field2}.what is ${describeValue3(valueAt(item, "what"))}, ` + "not a sentence; the gap is dropped";
2501
+ issues.push(issueAt(`${field2}.what`, text));
1750
2502
  return null;
1751
2503
  }
1752
2504
  const heading = stringAt(item, "heading");
1753
2505
  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));
2506
+ const text = `${field2}.heading is ${describeValue3(valueAt(item, "heading"))}, ` + `not a heading; the gap is filed under ${REVIEW_HEADING}`;
2507
+ issues.push(issueAt(`${field2}.heading`, text));
1756
2508
  }
1757
- const blocking = readBlocking(item, field, issues);
2509
+ const blocking = readBlocking(item, field2, issues);
1758
2510
  const assumption = stringAt(item, "assumption");
1759
2511
  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));
2512
+ 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";
2513
+ issues.push(issueAt(`${field2}.assumption`, text));
1762
2514
  return { heading: heading ?? REVIEW_HEADING, what, blocking: true, assumption: null };
1763
2515
  }
1764
2516
  return { heading: heading ?? REVIEW_HEADING, what, blocking, assumption };
@@ -1768,7 +2520,7 @@ function readGaps(document, issues) {
1768
2520
  if (value === null)
1769
2521
  return [];
1770
2522
  if (!Array.isArray(value)) {
1771
- const text = `gaps is ${describeValue2(value)}, not a list; no gap is read from it`;
2523
+ const text = `gaps is ${describeValue3(value)}, not a list; no gap is read from it`;
1772
2524
  issues.push(issueAt("gaps", text));
1773
2525
  return [];
1774
2526
  }
@@ -1824,12 +2576,12 @@ function parseSpecReview(output) {
1824
2576
  return unreadable("malformed", block, text);
1825
2577
  }
1826
2578
  if (!isMapping2(document)) {
1827
- const text = `the rafa:spec-review block at line ${line} holds ${describeValue2(document)}, ` + "not a mapping of verdict and gaps";
2579
+ const text = `the rafa:spec-review block at line ${line} holds ${describeValue3(document)}, ` + "not a mapping of verdict and gaps";
1828
2580
  return unreadable("malformed", block, text);
1829
2581
  }
1830
2582
  const verdict = verdictOf(valueAt(document, "verdict"));
1831
2583
  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(", ")}`;
2584
+ 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
2585
  return unreadable("malformed", block, text);
1834
2586
  }
1835
2587
  const issues = [];
@@ -1905,11 +2657,11 @@ class UndeclaredSpendError extends Error {
1905
2657
  name = "UndeclaredSpendError";
1906
2658
  }
1907
2659
  function carriesFlag(flags, flag) {
1908
- const name = flag.replace(/^--/, "");
1909
- const value = flags[name];
2660
+ const name2 = flag.replace(/^--/, "");
2661
+ const value = flags[name2];
1910
2662
  if (value !== undefined && value !== false)
1911
2663
  return true;
1912
- return name.startsWith("no-") && flags[name.slice(3)] === false;
2664
+ return name2.startsWith("no-") && flags[name2.slice(3)] === false;
1913
2665
  }
1914
2666
  function spendRefusal(running2) {
1915
2667
  if (running2 === null)
@@ -2035,12 +2787,12 @@ function createClaudePlanner(options) {
2035
2787
  const create = async ({ specPath, stub }) => {
2036
2788
  const planPath = planFilePath(planDir, `PLAN-${stub}.md`);
2037
2789
  const prerequisitesPath = planFilePath(planDir, `PREREQUISITES-${stub}.md`);
2038
- const isWritten = (path) => existsSync3(resolve(repoRoot, path));
2790
+ const isWritten = (path) => existsSync6(resolve3(repoRoot, path));
2039
2791
  if (isWritten(planPath)) {
2040
2792
  throw new ClaudePlannerError(`${planPath} already exists`, FAILURE_EXIT_CODE);
2041
2793
  }
2042
- const specContent = await readFile2(resolve(repoRoot, specPath), "utf8");
2043
- await mkdir2(resolve(repoRoot, planDir), { recursive: true });
2794
+ const specContent = await readFile2(resolve3(repoRoot, specPath), "utf8");
2795
+ await mkdir2(resolve3(repoRoot, planDir), { recursive: true });
2044
2796
  const session = await runClaudeCaptured(buildPrompt(specContent, stub), settingSources, [], spawn);
2045
2797
  const review = parseSpecReview(session.stdout);
2046
2798
  const { exitCode } = session;
@@ -2151,8 +2903,8 @@ async function withDeadline(work, timeoutMs) {
2151
2903
  if (timeoutMs === undefined)
2152
2904
  return work;
2153
2905
  let timer;
2154
- const deadline = new Promise((resolve2) => {
2155
- timer = setTimeout(() => resolve2(TIMED_OUT), timeoutMs);
2906
+ const deadline = new Promise((resolve4) => {
2907
+ timer = setTimeout(() => resolve4(TIMED_OUT), timeoutMs);
2156
2908
  });
2157
2909
  try {
2158
2910
  return await Promise.race([work, deadline]);
@@ -2179,8 +2931,8 @@ function isRepoName(value) {
2179
2931
  function isLabelPart(value) {
2180
2932
  return typeof value === "string" && !value.includes(",");
2181
2933
  }
2182
- function optionalFlag(name, value) {
2183
- return value === undefined ? [] : [name, value];
2934
+ function optionalFlag(name2, value) {
2935
+ return value === undefined ? [] : [name2, value];
2184
2936
  }
2185
2937
  function detailOf(result) {
2186
2938
  return result.stderr.trim() || result.stdout.trim() || "it exited non-zero and wrote nothing";
@@ -2243,7 +2995,7 @@ function labelNames(value) {
2243
2995
  if (!Array.isArray(value))
2244
2996
  return null;
2245
2997
  const names = value.map((label) => isMapping(label) && typeof label["name"] === "string" ? label["name"] : null);
2246
- return names.every((name) => name !== null) ? names : null;
2998
+ return names.every((name2) => name2 !== null) ? names : null;
2247
2999
  }
2248
3000
  function viewProblem(payload, number) {
2249
3001
  if (!isMapping(payload))
@@ -2279,18 +3031,18 @@ function listedIssues(stdout) {
2279
3031
  throw new Error(`${PREFIX2}: ${command} answered ${describeValue(payload)}, expected a list`);
2280
3032
  }
2281
3033
  return payload.map((row, index) => {
2282
- const refuse = (problem) => new Error(`${PREFIX2}: ${command} answered row ${index} ${problem}`);
3034
+ const refuse2 = (problem) => new Error(`${PREFIX2}: ${command} answered row ${index} ${problem}`);
2283
3035
  if (!isMapping(row))
2284
- throw refuse(`${describeValue(row)}, expected a mapping`);
3036
+ throw refuse2(`${describeValue(row)}, expected a mapping`);
2285
3037
  const { number, url } = row;
2286
3038
  const labels = labelNames(row["labels"]);
2287
3039
  if (typeof number !== "number" || !isIssueNumber(String(number))) {
2288
- throw refuse(`with number ${describeValue(number)}, expected an issue number`);
3040
+ throw refuse2(`with number ${describeValue(number)}, expected an issue number`);
2289
3041
  }
2290
3042
  if (typeof url !== "string")
2291
- throw refuse(`with url ${describeValue(url)}, expected a string`);
3043
+ throw refuse2(`with url ${describeValue(url)}, expected a string`);
2292
3044
  if (labels === null)
2293
- throw refuse("with labels that are not a list of named labels");
3045
+ throw refuse2("with labels that are not a list of named labels");
2294
3046
  return { number, url, labels };
2295
3047
  });
2296
3048
  }
@@ -2344,9 +3096,9 @@ function createGithubTracker(options) {
2344
3096
  return result.stdout;
2345
3097
  }
2346
3098
  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)}`);
3099
+ const missing = [...new Set(names)].filter((name2) => !knownLabels.has(name2));
3100
+ for (const name2 of missing) {
3101
+ await run(["label", "create", name2, "--force"], `gh label create ${describeValue(name2)}`);
2350
3102
  }
2351
3103
  knownLabels = new Set([...knownLabels, ...missing]);
2352
3104
  }
@@ -2457,7 +3209,7 @@ function createGithubTracker(options) {
2457
3209
 
2458
3210
  // src/adapters/tracker/local.ts
2459
3211
  import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
2460
- import { join as join6 } from "path";
3212
+ import { join as join11 } from "path";
2461
3213
  var PREFIX3 = "local tracker";
2462
3214
  var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
2463
3215
  var ISSUE_NUMBER2 = /^[1-9]\d*$/;
@@ -2510,11 +3262,11 @@ function parseIssueNumber(text) {
2510
3262
  const number = Number(text);
2511
3263
  return Number.isSafeInteger(number) ? number : null;
2512
3264
  }
2513
- function issueNumberOfFile(name) {
2514
- return name.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name.slice(0, -ISSUE_EXTENSION.length)) : null;
3265
+ function issueNumberOfFile(name2) {
3266
+ return name2.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name2.slice(0, -ISSUE_EXTENSION.length)) : null;
2515
3267
  }
2516
3268
  function issuePath(issuesDir, number) {
2517
- return join6(issuesDir, `${number}${ISSUE_EXTENSION}`);
3269
+ return join11(issuesDir, `${number}${ISSUE_EXTENSION}`);
2518
3270
  }
2519
3271
  async function issueNumbers(issuesDir) {
2520
3272
  let names;
@@ -2528,7 +3280,7 @@ async function issueNumbers(issuesDir) {
2528
3280
  return names.map(issueNumberOfFile).filter((number) => number !== null).sort((a, b) => a - b);
2529
3281
  }
2530
3282
  function localIssuesDir(repoRoot) {
2531
- return join6(repoRoot, ".rafa", "issues");
3283
+ return join11(repoRoot, ".rafa", "issues");
2532
3284
  }
2533
3285
  function renderLocalIssue(record) {
2534
3286
  const { draft } = record;
@@ -2849,7 +3601,7 @@ var CORE_ADAPTERS = [
2849
3601
  var CORE_ADAPTER_REGISTRY = createAdapterRegistry(CORE_ADAPTERS);
2850
3602
 
2851
3603
  // src/effort/store/index.ts
2852
- function describeValue3(value) {
3604
+ function describeValue4(value) {
2853
3605
  if (typeof value === "string")
2854
3606
  return JSON.stringify(value);
2855
3607
  if (typeof value === "object" && value !== null)
@@ -2860,9 +3612,9 @@ function selectEffortStore(repoRoot, config) {
2860
3612
  const adapter = CORE_ADAPTER_REGISTRY.find("store", config.store);
2861
3613
  if (adapter === undefined) {
2862
3614
  const expected = CORE_ADAPTER_REGISTRY.kinds("store").join(", ");
2863
- throw new TypeError(`effort store: store is ${describeValue3(config.store)},` + ` expected one of: ${expected}`);
3615
+ throw new TypeError(`effort store: store is ${describeValue4(config.store)},` + ` expected one of: ${expected}`);
2864
3616
  }
2865
3617
  return adapter.create({ repoRoot });
2866
3618
  }
2867
3619
 
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 };
3620
+ 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, 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, bringForward, readStoreSchema, writeNames, setActiveStoreSettings, activeStoreSettings, SQLITE_STORE_FILE_NAME, sqliteStorePath, LONE_SURROGATE, 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 };