@open-tomato/rafa 0.35.0 → 0.36.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.
- package/dist/bundled/operators/agents/rafa-stretch-engineer.md +4 -4
- package/dist/bundled/operators/agents/rafa-stretch-watchtower.md +9 -9
- package/dist/cli.js +12102 -10915
- package/dist/describe-5hqfwbjc.js +14 -0
- package/dist/effort/store/index.js +36 -33
- package/dist/{index-dvrq9nfb.js → index-2n2nedrk.js} +4988 -4683
- package/dist/index-3mzw3kzh.js +129 -0
- package/dist/{index-41vzahew.js → index-7bb0fdb7.js} +33 -31
- package/dist/{index-hgcvjke7.js → index-d6d0942q.js} +17 -17
- package/dist/{index-se13pvag.js → index-ddzzmcaz.js} +2856 -2243
- package/dist/index-fzhd3d4y.js +446 -0
- package/dist/{index-2tjmxwvp.js → index-jfas157k.js} +111 -513
- package/dist/index-k6sd4ym5.js +243 -0
- package/dist/{index-ckqfhynj.js → index-qvbeqrx3.js} +417 -494
- package/dist/index-wp8c28fd.js +18 -0
- package/dist/index.js +152 -147
- package/dist/learning/index.js +20 -20
- package/dist/plan/index.js +45 -42
- package/package.json +1 -1
- package/dist/describe-59899b0p.js +0 -11
- package/dist/index-21jcfatr.js +0 -16
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
+
CONFIG_DEFAULTS,
|
|
4
|
+
CONFIG_FILE,
|
|
5
|
+
ConfigError,
|
|
6
|
+
FINDING_DOMAINS,
|
|
7
|
+
FINDING_KINDS,
|
|
8
|
+
FINDING_SIGNALS,
|
|
9
|
+
activeOutput,
|
|
10
|
+
activeOutputMode,
|
|
11
|
+
configFilePath,
|
|
12
|
+
createTextOutput,
|
|
13
|
+
hubContextOf,
|
|
14
|
+
isStampableStub,
|
|
15
|
+
parseConfigText,
|
|
16
|
+
readRafaBlocks,
|
|
17
|
+
resolveConfig
|
|
18
|
+
} from "./index-jfas157k.js";
|
|
19
|
+
import {
|
|
20
|
+
bless,
|
|
21
|
+
merge
|
|
22
|
+
} from "./index-d6d0942q.js";
|
|
23
|
+
import {
|
|
24
|
+
CommandExit,
|
|
25
|
+
commandSpelling
|
|
26
|
+
} from "./index-3mzw3kzh.js";
|
|
27
|
+
import {
|
|
28
|
+
BUSY_TIMEOUT_MAX_MS,
|
|
29
|
+
BUSY_TIMEOUT_MIN_MS,
|
|
3
30
|
RAFA_INSTINCTS_PATH,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
tierExists,
|
|
31
|
+
STORE_BACKENDS,
|
|
32
|
+
SYNC_STRATEGIES,
|
|
7
33
|
TEST_SCOPES,
|
|
8
|
-
|
|
34
|
+
bundledBinDirectory,
|
|
9
35
|
describeValue,
|
|
10
|
-
messageOf,
|
|
11
|
-
STORE_BACKENDS2,
|
|
12
|
-
SYNC_STRATEGIES2,
|
|
13
|
-
BUSY_TIMEOUT_MIN_MS,
|
|
14
|
-
BUSY_TIMEOUT_MAX_MS,
|
|
15
36
|
isBusyTimeoutMs,
|
|
16
37
|
isGitHubLogin,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
parseConfigText2,
|
|
23
|
-
resolveConfig2,
|
|
24
|
-
isStampableStub,
|
|
25
|
-
createTextOutput,
|
|
26
|
-
activeOutput,
|
|
27
|
-
activeOutputMode,
|
|
28
|
-
readRafaBlocks2,
|
|
29
|
-
FINDING_KINDS2,
|
|
30
|
-
FINDING_SIGNALS2,
|
|
31
|
-
FINDING_DOMAINS
|
|
32
|
-
} from "./index-2tjmxwvp.js";
|
|
33
|
-
import {
|
|
34
|
-
bless2,
|
|
35
|
-
merge2
|
|
36
|
-
} from "./index-hgcvjke7.js";
|
|
38
|
+
isMapping,
|
|
39
|
+
messageOf,
|
|
40
|
+
resolveInstinctScopes,
|
|
41
|
+
tierExists
|
|
42
|
+
} from "./index-fzhd3d4y.js";
|
|
37
43
|
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
} from "./index-
|
|
44
|
+
actionHash,
|
|
45
|
+
triggerKey
|
|
46
|
+
} from "./index-wp8c28fd.js";
|
|
41
47
|
|
|
42
48
|
// src/adapters/registry.ts
|
|
43
49
|
import { homedir } from "os";
|
|
@@ -211,7 +217,7 @@ function guardTestProcess(path, probe = currentProbe()) {
|
|
|
211
217
|
function keyOrNull(value) {
|
|
212
218
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
213
219
|
}
|
|
214
|
-
var
|
|
220
|
+
var EFFORT_KEY_PROJECTIONS = {
|
|
215
221
|
sessions: (row) => keyOrNull(row.sessionId),
|
|
216
222
|
commits: (row) => keyOrNull(row.sha)
|
|
217
223
|
};
|
|
@@ -230,13 +236,13 @@ function readKind(repoRoot, kind) {
|
|
|
230
236
|
}
|
|
231
237
|
function keysOfKind(repoRoot, kind) {
|
|
232
238
|
const path = openedKindPath(repoRoot, kind);
|
|
233
|
-
return collectedKeys(path,
|
|
239
|
+
return collectedKeys(path, EFFORT_KEY_PROJECTIONS[kind]);
|
|
234
240
|
}
|
|
235
241
|
function appendKind(repoRoot, kind, rows) {
|
|
236
242
|
const path = openedKindPath(repoRoot, kind);
|
|
237
|
-
return appendRows(path, rows,
|
|
243
|
+
return appendRows(path, rows, EFFORT_KEY_PROJECTIONS[kind]);
|
|
238
244
|
}
|
|
239
|
-
function
|
|
245
|
+
function openNdjsonStore(repoRoot) {
|
|
240
246
|
return {
|
|
241
247
|
append: (kind, rows) => appendKind(repoRoot, kind, rows),
|
|
242
248
|
keys: (kind) => keysOfKind(repoRoot, kind),
|
|
@@ -253,7 +259,7 @@ import { Database as Database2 } from "bun:sqlite";
|
|
|
253
259
|
|
|
254
260
|
// package.json
|
|
255
261
|
var name = "@open-tomato/rafa";
|
|
256
|
-
var version = "0.
|
|
262
|
+
var version = "0.36.0";
|
|
257
263
|
|
|
258
264
|
// src/cli/version.ts
|
|
259
265
|
var RAFA_VERSION = version;
|
|
@@ -346,36 +352,36 @@ function mainCheckoutOf(dir, seams = {}) {
|
|
|
346
352
|
}
|
|
347
353
|
|
|
348
354
|
// src/project/scope.ts
|
|
349
|
-
var
|
|
350
|
-
var
|
|
351
|
-
var
|
|
355
|
+
var SCOPE_DIR = dirname3(CONFIG_FILE);
|
|
356
|
+
var INIT_COMMAND = "rafa init";
|
|
357
|
+
var DISK_FILE_SYSTEM = Object.freeze({
|
|
352
358
|
exists: (path) => existsSync3(path),
|
|
353
359
|
realpath: (path) => realpathSync2(path)
|
|
354
360
|
});
|
|
355
361
|
|
|
356
|
-
class
|
|
362
|
+
class ScopeError extends Error {
|
|
357
363
|
constructor(message, options) {
|
|
358
364
|
super(`rafa scope: ${message}`, options);
|
|
359
365
|
this.name = "ScopeError";
|
|
360
366
|
}
|
|
361
367
|
}
|
|
362
|
-
function
|
|
363
|
-
return { dir: join4(base,
|
|
368
|
+
function scopeAt(base) {
|
|
369
|
+
return { dir: join4(base, SCOPE_DIR), configFile: configFilePath(base) };
|
|
364
370
|
}
|
|
365
|
-
function
|
|
371
|
+
function initHint(start, passedUserConfig = null, probedMainCheckout = null) {
|
|
366
372
|
const passed = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
|
|
367
373
|
const main = probedMainCheckout === null ? [] : [`nor in ${probedMainCheckout}, the main checkout of the repository holding it`];
|
|
368
374
|
return [
|
|
369
|
-
`not inside a rafa project: no ${
|
|
375
|
+
`not inside a rafa project: no ${CONFIG_FILE} in ${start} or any directory above it`,
|
|
370
376
|
...main,
|
|
371
377
|
...passed,
|
|
372
|
-
`run \`${
|
|
378
|
+
`run \`${INIT_COMMAND}\` to set one up`
|
|
373
379
|
].join(`
|
|
374
380
|
`);
|
|
375
381
|
}
|
|
376
382
|
function requireAbsolute(label, path) {
|
|
377
383
|
if (!isAbsolute3(path)) {
|
|
378
|
-
throw new
|
|
384
|
+
throw new ScopeError(`${label} is ${describeValue(path)}, expected an absolute path`);
|
|
379
385
|
}
|
|
380
386
|
}
|
|
381
387
|
function realStartOf(start, fs) {
|
|
@@ -383,7 +389,7 @@ function realStartOf(start, fs) {
|
|
|
383
389
|
return fs.realpath(start);
|
|
384
390
|
} catch (error) {
|
|
385
391
|
const message = `start directory ${start} does not resolve (${messageOf(error)})`;
|
|
386
|
-
throw new
|
|
392
|
+
throw new ScopeError(message, { cause: error });
|
|
387
393
|
}
|
|
388
394
|
}
|
|
389
395
|
function realHomeOf(home, fs) {
|
|
@@ -393,15 +399,15 @@ function realHomeOf(home, fs) {
|
|
|
393
399
|
return null;
|
|
394
400
|
}
|
|
395
401
|
}
|
|
396
|
-
function
|
|
402
|
+
function selfAndAncestors(path) {
|
|
397
403
|
const parent = dirname3(path);
|
|
398
|
-
return parent === path ? [path] : [path, ...
|
|
404
|
+
return parent === path ? [path] : [path, ...selfAndAncestors(parent)];
|
|
399
405
|
}
|
|
400
406
|
var GIT_MARKER = ".git";
|
|
401
407
|
function walkUp(dirs, realHome, fs, stopAtTopLevel) {
|
|
402
408
|
let passedUserConfig = false;
|
|
403
409
|
for (const dir of dirs) {
|
|
404
|
-
if (fs.exists(
|
|
410
|
+
if (fs.exists(configFilePath(dir))) {
|
|
405
411
|
if (dir !== realHome)
|
|
406
412
|
return { root: dir, passedUserConfig, topLevel: null };
|
|
407
413
|
passedUserConfig = true;
|
|
@@ -418,39 +424,39 @@ function mainCheckoutAt(realStart, mainCheckout) {
|
|
|
418
424
|
} catch (error) {
|
|
419
425
|
if (!(error instanceof WorktreeRootError))
|
|
420
426
|
throw error;
|
|
421
|
-
throw new
|
|
427
|
+
throw new ScopeError(`cannot read the main checkout of ${realStart} (${error.message})`, { cause: error });
|
|
422
428
|
}
|
|
423
429
|
}
|
|
424
430
|
function fallBack(realStart, realHome, fs, mainCheckout) {
|
|
425
431
|
const main = mainCheckoutAt(realStart, mainCheckout);
|
|
426
|
-
if (main === null || main === realHome ||
|
|
432
|
+
if (main === null || main === realHome || selfAndAncestors(realStart).includes(main)) {
|
|
427
433
|
return { root: null, probed: null };
|
|
428
434
|
}
|
|
429
|
-
return fs.exists(
|
|
435
|
+
return fs.exists(configFilePath(main)) ? { root: main, probed: main } : { root: null, probed: main };
|
|
430
436
|
}
|
|
431
437
|
function walkAbove(lower, realHome, fs) {
|
|
432
438
|
if (lower.topLevel === null)
|
|
433
439
|
return lower;
|
|
434
|
-
const above =
|
|
440
|
+
const above = selfAndAncestors(lower.topLevel).slice(1);
|
|
435
441
|
const upper = walkUp(above, realHome, fs, false);
|
|
436
442
|
return { ...upper, passedUserConfig: lower.passedUserConfig || upper.passedUserConfig };
|
|
437
443
|
}
|
|
438
|
-
function
|
|
439
|
-
const { home, fs =
|
|
444
|
+
function resolveScope(start, seams) {
|
|
445
|
+
const { home, fs = DISK_FILE_SYSTEM, mainCheckout = mainCheckoutOf } = seams;
|
|
440
446
|
requireAbsolute("start directory", start);
|
|
441
447
|
requireAbsolute("home directory", home);
|
|
442
|
-
const user =
|
|
448
|
+
const user = scopeAt(home);
|
|
443
449
|
const realStart = realStartOf(start, fs);
|
|
444
450
|
const realHome = realHomeOf(home, fs);
|
|
445
|
-
const lower = walkUp(
|
|
451
|
+
const lower = walkUp(selfAndAncestors(realStart), realHome, fs, true);
|
|
446
452
|
const fallback = lower.root === null ? fallBack(realStart, realHome, fs, mainCheckout) : { root: lower.root, probed: null };
|
|
447
453
|
const walk = fallback.root === null ? walkAbove(lower, realHome, fs) : lower;
|
|
448
454
|
const root = fallback.root ?? walk.root;
|
|
449
455
|
if (root === null) {
|
|
450
456
|
const passed = walk.passedUserConfig ? user.configFile : null;
|
|
451
|
-
return { found: false, start, home, user, hint:
|
|
457
|
+
return { found: false, start, home, user, hint: initHint(start, passed, fallback.probed) };
|
|
452
458
|
}
|
|
453
|
-
return { found: true, root, home, project:
|
|
459
|
+
return { found: true, root, home, project: scopeAt(root), user };
|
|
454
460
|
}
|
|
455
461
|
|
|
456
462
|
// src/loop/session-record-parse.ts
|
|
@@ -467,7 +473,7 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmS
|
|
|
467
473
|
import { dirname as dirname4, join as join5 } from "path";
|
|
468
474
|
var POSITION_FILE = "position.json";
|
|
469
475
|
function positionFilePath(root) {
|
|
470
|
-
return join5(
|
|
476
|
+
return join5(scopeAt(root).dir, POSITION_FILE);
|
|
471
477
|
}
|
|
472
478
|
function isRecord(value) {
|
|
473
479
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -544,7 +550,7 @@ var HOP_FILE = "hop.json";
|
|
|
544
550
|
var HOP_KINDS = ["blocker", "dry"];
|
|
545
551
|
var HOP_STATES = ["away", "waiting", "merged", "halted"];
|
|
546
552
|
function hopFilePath(root) {
|
|
547
|
-
return join6(
|
|
553
|
+
return join6(scopeAt(root).dir, HOP_FILE);
|
|
548
554
|
}
|
|
549
555
|
function isRecord2(value) {
|
|
550
556
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -650,6 +656,7 @@ var DEFAULT_PHASE = "task";
|
|
|
650
656
|
var RECORD_EXTENSION = ".json";
|
|
651
657
|
var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
652
658
|
var SESSION_STEP_KINDS = Object.freeze(["baseline", "task", "stage", "pre-wrap-up"]);
|
|
659
|
+
var SESSION_STEP_REASONS = Object.freeze(["declared", "trigger", "fallback", "no-module-tests", "stage"]);
|
|
653
660
|
|
|
654
661
|
class SessionRecordError extends Error {
|
|
655
662
|
file;
|
|
@@ -690,8 +697,8 @@ function taskProblem(task) {
|
|
|
690
697
|
function hopProblem(fields) {
|
|
691
698
|
if (!Object.hasOwn(fields, "hop"))
|
|
692
699
|
return null;
|
|
693
|
-
const
|
|
694
|
-
return asHopRecord(
|
|
700
|
+
const hop2 = field(fields, "hop");
|
|
701
|
+
return asHopRecord(hop2) === null ? `hop is ${describeValue2(hop2)}, expected a hop record` : null;
|
|
695
702
|
}
|
|
696
703
|
function worktreeProblem(fields) {
|
|
697
704
|
if (!Object.hasOwn(fields, "worktree"))
|
|
@@ -793,16 +800,23 @@ function phaseEntry(phase) {
|
|
|
793
800
|
return isSessionPhase(phase) ? { phase } : {};
|
|
794
801
|
}
|
|
795
802
|
function freezeHop(value) {
|
|
796
|
-
const
|
|
797
|
-
return Object.freeze({ ...
|
|
803
|
+
const hop2 = asHopRecord(value);
|
|
804
|
+
return Object.freeze({ ...hop2, home: Object.freeze(hop2.home), from: Object.freeze(hop2.from) });
|
|
798
805
|
}
|
|
799
806
|
function freezeFailures(failures) {
|
|
800
807
|
return Object.freeze(failures.map((failure) => Object.freeze({ file: failure.file, name: failure.name })));
|
|
801
808
|
}
|
|
809
|
+
function isSessionStepReason(value) {
|
|
810
|
+
return SESSION_STEP_REASONS.includes(value);
|
|
811
|
+
}
|
|
812
|
+
function reasonEntry(reason) {
|
|
813
|
+
return isSessionStepReason(reason) ? { reason } : {};
|
|
814
|
+
}
|
|
802
815
|
function freezeStep(step) {
|
|
803
816
|
return Object.freeze({
|
|
804
817
|
kind: step.kind,
|
|
805
818
|
scope: typeof step.scope === "string" ? step.scope : Object.freeze([...step.scope]),
|
|
819
|
+
...reasonEntry(field(step, "reason")),
|
|
806
820
|
command: Object.freeze([...step.command]),
|
|
807
821
|
exitCode: step.exitCode,
|
|
808
822
|
summary: step.summary,
|
|
@@ -818,7 +832,7 @@ function stepsEntry(steps) {
|
|
|
818
832
|
}
|
|
819
833
|
function freezeRecord(fields) {
|
|
820
834
|
const task = field(fields, "task");
|
|
821
|
-
const
|
|
835
|
+
const hop2 = field(fields, "hop");
|
|
822
836
|
const worktree = field(fields, "worktree");
|
|
823
837
|
return Object.freeze({
|
|
824
838
|
sessionId: field(fields, "sessionId"),
|
|
@@ -830,7 +844,7 @@ function freezeRecord(fields) {
|
|
|
830
844
|
state: field(fields, "state"),
|
|
831
845
|
task: isObject(task) ? Object.freeze({ line: field(task, "line"), text: field(task, "text") }) : null,
|
|
832
846
|
...phaseEntry(field(fields, "phase")),
|
|
833
|
-
...
|
|
847
|
+
...hop2 === undefined ? {} : { hop: freezeHop(hop2) },
|
|
834
848
|
...worktree === undefined ? {} : { worktree },
|
|
835
849
|
...stepsEntry(field(fields, "steps"))
|
|
836
850
|
});
|
|
@@ -881,7 +895,7 @@ class SessionStateError extends Error {
|
|
|
881
895
|
}
|
|
882
896
|
}
|
|
883
897
|
function runsDir(root) {
|
|
884
|
-
return join7(
|
|
898
|
+
return join7(scopeAt(root).dir, RUNS_DIR);
|
|
885
899
|
}
|
|
886
900
|
function sessionFilePath(root, sessionId) {
|
|
887
901
|
if (!isSessionId(sessionId)) {
|
|
@@ -931,7 +945,7 @@ function byStart(a, b) {
|
|
|
931
945
|
function readSessions(root, seams = {}) {
|
|
932
946
|
const dir = runsDir(root);
|
|
933
947
|
const isAlive = seams.isAlive ?? isPidAlive;
|
|
934
|
-
const records = recordNames(dir).filter((
|
|
948
|
+
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) }));
|
|
935
949
|
return Object.freeze(records.sort(byStart));
|
|
936
950
|
}
|
|
937
951
|
function readSession(root, sessionId, seams = {}) {
|
|
@@ -1017,8 +1031,8 @@ function manifestName(file) {
|
|
|
1017
1031
|
}
|
|
1018
1032
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
1019
1033
|
return null;
|
|
1020
|
-
const
|
|
1021
|
-
return typeof
|
|
1034
|
+
const name2 = parsed["name"];
|
|
1035
|
+
return typeof name2 === "string" ? name2 : null;
|
|
1022
1036
|
}
|
|
1023
1037
|
function nearestRafaPackage(start) {
|
|
1024
1038
|
let dir = start;
|
|
@@ -1063,7 +1077,7 @@ class DevelopmentBuildRefusedError extends Error {
|
|
|
1063
1077
|
function storeProjectRoot(path) {
|
|
1064
1078
|
const storeDir = dirname8(path);
|
|
1065
1079
|
const scopeDir = dirname8(storeDir);
|
|
1066
|
-
if (basename3(storeDir) !== EFFORT_DIR_NAME || basename3(scopeDir) !==
|
|
1080
|
+
if (basename3(storeDir) !== EFFORT_DIR_NAME || basename3(scopeDir) !== SCOPE_DIR)
|
|
1067
1081
|
return null;
|
|
1068
1082
|
return dirname8(scopeDir);
|
|
1069
1083
|
}
|
|
@@ -1470,6 +1484,13 @@ var SQLITE_MIGRATIONS = [
|
|
|
1470
1484
|
sql: `
|
|
1471
1485
|
ALTER TABLE store_meta ADD COLUMN generation TEXT CHECK (generation <> '');
|
|
1472
1486
|
`
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
id: "session-worktree",
|
|
1490
|
+
breaks: [],
|
|
1491
|
+
sql: `
|
|
1492
|
+
ALTER TABLE sessions ADD COLUMN worktree TEXT;
|
|
1493
|
+
`
|
|
1473
1494
|
}
|
|
1474
1495
|
];
|
|
1475
1496
|
var SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.length;
|
|
@@ -1683,7 +1704,7 @@ function stampedValues(table) {
|
|
|
1683
1704
|
|
|
1684
1705
|
// src/effort/store/settings.ts
|
|
1685
1706
|
var DEFAULT_STORE_SETTINGS = Object.freeze({
|
|
1686
|
-
busyTimeoutMs:
|
|
1707
|
+
busyTimeoutMs: CONFIG_DEFAULTS.effortBusyTimeoutMs
|
|
1687
1708
|
});
|
|
1688
1709
|
var active = DEFAULT_STORE_SETTINGS;
|
|
1689
1710
|
function setActiveStoreSettings(settings) {
|
|
@@ -2008,8 +2029,12 @@ function sqliteStorePath(repoRoot) {
|
|
|
2008
2029
|
return join9(effortStoreDir(repoRoot), SQLITE_STORE_FILE_NAME);
|
|
2009
2030
|
}
|
|
2010
2031
|
var KIND_TABLES = {
|
|
2011
|
-
sessions: {
|
|
2012
|
-
|
|
2032
|
+
sessions: {
|
|
2033
|
+
name: "sessions",
|
|
2034
|
+
keyColumn: "session_id",
|
|
2035
|
+
fieldColumns: [{ column: "worktree", value: (row) => row.worktree ?? null }]
|
|
2036
|
+
},
|
|
2037
|
+
commits: { name: "commits", keyColumn: "sha", fieldColumns: [] }
|
|
2013
2038
|
};
|
|
2014
2039
|
var LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
|
|
2015
2040
|
function hasKey(entry) {
|
|
@@ -2019,7 +2044,8 @@ function refused(index, length, reason) {
|
|
|
2019
2044
|
return new Error(`effort store: row ${index} of ${length} ${reason}; batch refused`);
|
|
2020
2045
|
}
|
|
2021
2046
|
function batchEntries(kind, rows) {
|
|
2022
|
-
const keyOf =
|
|
2047
|
+
const keyOf = EFFORT_KEY_PROJECTIONS[kind];
|
|
2048
|
+
const fieldColumns = KIND_TABLES[kind].fieldColumns;
|
|
2023
2049
|
const projected = rows.map((row) => ({ key: keyOf(row), row }));
|
|
2024
2050
|
const keyed = projected.filter(hasKey);
|
|
2025
2051
|
if (keyed.length < projected.length) {
|
|
@@ -2031,7 +2057,11 @@ function batchEntries(kind, rows) {
|
|
|
2031
2057
|
const reason = "carries a key with a lone UTF-16 surrogate," + " which SQLite text cannot hold";
|
|
2032
2058
|
throw refused(unholdableAt, rows.length, reason);
|
|
2033
2059
|
}
|
|
2034
|
-
return keyed.map(({ key, row }) => ({
|
|
2060
|
+
return keyed.map(({ key, row }) => ({
|
|
2061
|
+
key,
|
|
2062
|
+
body: JSON.stringify(row),
|
|
2063
|
+
fields: fieldColumns.map(({ value }) => value(row))
|
|
2064
|
+
}));
|
|
2035
2065
|
}
|
|
2036
2066
|
function withSqliteStore(path, access, create, use, identity = {}) {
|
|
2037
2067
|
guardTestProcess(path);
|
|
@@ -2048,8 +2078,11 @@ function withSqliteStore(path, access, create, use, identity = {}) {
|
|
|
2048
2078
|
}
|
|
2049
2079
|
}
|
|
2050
2080
|
function insertBatch(db, table, entries) {
|
|
2051
|
-
const
|
|
2052
|
-
const
|
|
2081
|
+
const fieldNames = table.fieldColumns.map(({ column }) => column);
|
|
2082
|
+
const columns = [table.keyColumn, "row_json", ...fieldNames].join(", ");
|
|
2083
|
+
const placeholders = ["?", "?", ...fieldNames.map(() => "?")].join(", ");
|
|
2084
|
+
const insert = db.query(`INSERT INTO ${table.name} (${columns}, ${STAMPED_COLUMNS})` + ` VALUES (${placeholders}, ${stampedValues(table.name)})` + ` ON CONFLICT (${table.keyColumn}) DO NOTHING`);
|
|
2085
|
+
const insertAll = db.transaction(() => entries.reduce((appended, { key, body, fields }) => appended + insert.run(key, body, ...fields).changes, 0));
|
|
2053
2086
|
return insertAll.immediate();
|
|
2054
2087
|
}
|
|
2055
2088
|
function writeSqliteStore(path, rowCount, empty, insert) {
|
|
@@ -2071,8 +2104,8 @@ function appendKind2(path, kind, rows) {
|
|
|
2071
2104
|
function keysOfKind2(path, kind) {
|
|
2072
2105
|
if (!existsSync5(path))
|
|
2073
2106
|
return new Set;
|
|
2074
|
-
const { name, keyColumn } = KIND_TABLES[kind];
|
|
2075
|
-
const keys = withSqliteStore(path, "read", false, (db) => db.query(`SELECT ${keyColumn} AS key FROM ${
|
|
2107
|
+
const { name: name2, keyColumn } = KIND_TABLES[kind];
|
|
2108
|
+
const keys = withSqliteStore(path, "read", false, (db) => db.query(`SELECT ${keyColumn} AS key FROM ${name2} ORDER BY seq`).all());
|
|
2076
2109
|
return new Set(keys.map(({ key }) => key));
|
|
2077
2110
|
}
|
|
2078
2111
|
function parsedObject(body) {
|
|
@@ -2093,11 +2126,11 @@ function rowOf(body, where) {
|
|
|
2093
2126
|
function readKind2(path, kind) {
|
|
2094
2127
|
if (!existsSync5(path))
|
|
2095
2128
|
return [];
|
|
2096
|
-
const { name } = KIND_TABLES[kind];
|
|
2097
|
-
const stored = withSqliteStore(path, "read", false, (db) => db.query(`SELECT seq, row_json AS body FROM ${
|
|
2098
|
-
return stored.map(({ seq, body }) => rowOf(body, `${path}: ${
|
|
2129
|
+
const { name: name2 } = KIND_TABLES[kind];
|
|
2130
|
+
const stored = withSqliteStore(path, "read", false, (db) => db.query(`SELECT seq, row_json AS body FROM ${name2} ORDER BY seq`).all());
|
|
2131
|
+
return stored.map(({ seq, body }) => rowOf(body, `${path}: ${name2} row ${seq}`));
|
|
2099
2132
|
}
|
|
2100
|
-
function
|
|
2133
|
+
function openSqliteStore(repoRoot) {
|
|
2101
2134
|
const path = sqliteStorePath(repoRoot);
|
|
2102
2135
|
return {
|
|
2103
2136
|
append: (kind, rows) => appendKind2(path, kind, rows),
|
|
@@ -2111,129 +2144,6 @@ function openSqliteStore2(repoRoot) {
|
|
|
2111
2144
|
import { existsSync as existsSync12 } from "fs";
|
|
2112
2145
|
import { join as join12, resolve as resolve5 } from "path";
|
|
2113
2146
|
|
|
2114
|
-
// src/cli/spends.ts
|
|
2115
|
-
var SPENDS_GLYPH = "\uD83E\uDE99";
|
|
2116
|
-
var SPENDS_WHEN = ["always", "with", "unless", "through"];
|
|
2117
|
-
var FLAGGED = ["with", "unless"];
|
|
2118
|
-
var FLAG_SPELLING = /^--[^\s-]\S*$/;
|
|
2119
|
-
function flagProblem(when, flag) {
|
|
2120
|
-
if (FLAGGED.includes(when)) {
|
|
2121
|
-
return typeof flag === "string" && FLAG_SPELLING.test(flag) ? null : `spends.flag is ${describeValue(flag)}, expected a flag as typed, starting with --`;
|
|
2122
|
-
}
|
|
2123
|
-
return flag === undefined ? null : `spends.flag is ${describeValue(flag)}, expected absent when spends.when is "${when}"`;
|
|
2124
|
-
}
|
|
2125
|
-
function spendsProblem(value) {
|
|
2126
|
-
if (value === undefined)
|
|
2127
|
-
return null;
|
|
2128
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2129
|
-
return `spends is ${describeValue(value)}, expected absent or a mapping`;
|
|
2130
|
-
}
|
|
2131
|
-
const { when, what, flag } = value;
|
|
2132
|
-
if (!SPENDS_WHEN.includes(when)) {
|
|
2133
|
-
return `spends.when is ${describeValue(when)}, expected one of ${SPENDS_WHEN.join(", ")}`;
|
|
2134
|
-
}
|
|
2135
|
-
if (typeof what !== "string") {
|
|
2136
|
-
return `spends.what is ${describeValue(what)}, expected a string`;
|
|
2137
|
-
}
|
|
2138
|
-
return flagProblem(when, flag);
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
// src/cli/command.ts
|
|
2142
|
-
var COMMAND_OUTPUTS = ["text", "json", "events", "tui"];
|
|
2143
|
-
var MAX_EXIT_CODE = 255;
|
|
2144
|
-
|
|
2145
|
-
class CommandExit extends Error {
|
|
2146
|
-
exitCode;
|
|
2147
|
-
constructor(exitCode, message = "") {
|
|
2148
|
-
super(message);
|
|
2149
|
-
if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > MAX_EXIT_CODE) {
|
|
2150
|
-
throw new TypeError(`CommandExit: exit code is ${describeValue(exitCode)}, expected a whole number from 0 to ${MAX_EXIT_CODE}`);
|
|
2151
|
-
}
|
|
2152
|
-
this.name = "CommandExit";
|
|
2153
|
-
this.exitCode = exitCode;
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
function isListOf(value, check) {
|
|
2157
|
-
return Array.isArray(value) && value.every(check);
|
|
2158
|
-
}
|
|
2159
|
-
function hasStrings(value, keys) {
|
|
2160
|
-
if (typeof value !== "object" || value === null)
|
|
2161
|
-
return false;
|
|
2162
|
-
const record = value;
|
|
2163
|
-
return keys.every((key) => typeof record[key] === "string");
|
|
2164
|
-
}
|
|
2165
|
-
function isRoutingWord(value) {
|
|
2166
|
-
return typeof value === "string" && /^[^\s/-][^\s/]*$/.test(value);
|
|
2167
|
-
}
|
|
2168
|
-
function isFlag(item) {
|
|
2169
|
-
if (!hasStrings(item, ["name"]))
|
|
2170
|
-
return false;
|
|
2171
|
-
const { deprecated } = item;
|
|
2172
|
-
return deprecated === undefined || hasStrings(deprecated, ["use"]);
|
|
2173
|
-
}
|
|
2174
|
-
function fieldProblem(fields) {
|
|
2175
|
-
const checks = [
|
|
2176
|
-
["subject", isRoutingWord(fields.subject), "a word with no space, no slash and no leading dash"],
|
|
2177
|
-
["action", isRoutingWord(fields.action), "a word with no space, no slash and no leading dash"],
|
|
2178
|
-
["name", typeof fields.name === "string", "a string"],
|
|
2179
|
-
["summary", typeof fields.summary === "string", "a string"],
|
|
2180
|
-
["description", typeof fields.description === "string", "a string"],
|
|
2181
|
-
["args", isListOf(fields.args, (item) => hasStrings(item, ["name"])), "a list of named arguments"],
|
|
2182
|
-
["flags", isListOf(fields.flags, isFlag), "a list of named flags, each deprecation naming its use"],
|
|
2183
|
-
["examples", isListOf(fields.examples, (item) => hasStrings(item, ["cmd", "note"])), "a list of examples"],
|
|
2184
|
-
[
|
|
2185
|
-
"outputs",
|
|
2186
|
-
isListOf(fields.outputs, (item) => COMMAND_OUTPUTS.includes(item)),
|
|
2187
|
-
`a list of ${COMMAND_OUTPUTS.join(", ")}`
|
|
2188
|
-
],
|
|
2189
|
-
[
|
|
2190
|
-
"aliases",
|
|
2191
|
-
fields.aliases === undefined || isListOf(fields.aliases, (item) => typeof item === "string"),
|
|
2192
|
-
"absent or a list of strings"
|
|
2193
|
-
],
|
|
2194
|
-
[
|
|
2195
|
-
"lastingAliases",
|
|
2196
|
-
fields.lastingAliases === undefined || isListOf(fields.lastingAliases, (item) => typeof item === "string"),
|
|
2197
|
-
"absent or a list of strings"
|
|
2198
|
-
],
|
|
2199
|
-
[
|
|
2200
|
-
"deprecated",
|
|
2201
|
-
fields.deprecated === undefined || hasStrings(fields.deprecated, ["since", "use"]),
|
|
2202
|
-
"absent or a mapping of since and use"
|
|
2203
|
-
],
|
|
2204
|
-
["hidden", fields.hidden === undefined || typeof fields.hidden === "boolean", "absent or a boolean"],
|
|
2205
|
-
[
|
|
2206
|
-
"needsProject",
|
|
2207
|
-
fields.needsProject === undefined || typeof fields.needsProject === "boolean",
|
|
2208
|
-
"absent or a boolean"
|
|
2209
|
-
],
|
|
2210
|
-
["exec", fields.exec === undefined || fields.exec === true, "absent or true"],
|
|
2211
|
-
["run", typeof fields.run === "function", "a function"]
|
|
2212
|
-
];
|
|
2213
|
-
const failed = checks.find(([, passes]) => !passes);
|
|
2214
|
-
if (failed === undefined)
|
|
2215
|
-
return null;
|
|
2216
|
-
const [key, , expected] = failed;
|
|
2217
|
-
return `${key} is ${describeValue(fields[key])}, expected ${expected}`;
|
|
2218
|
-
}
|
|
2219
|
-
function commandProblem(value) {
|
|
2220
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2221
|
-
return `a command is ${describeValue(value)}, expected a mapping`;
|
|
2222
|
-
}
|
|
2223
|
-
const fields = value;
|
|
2224
|
-
const problem = fieldProblem(fields) ?? spendsProblem(fields.spends);
|
|
2225
|
-
if (problem === null)
|
|
2226
|
-
return null;
|
|
2227
|
-
const named = isRoutingWord(fields.subject) && isRoutingWord(fields.action) ? `command "${commandSpelling(fields)}"` : "a command";
|
|
2228
|
-
return `${named}: ${problem}`;
|
|
2229
|
-
}
|
|
2230
|
-
function isTopLevel(command) {
|
|
2231
|
-
return command.subject === command.action;
|
|
2232
|
-
}
|
|
2233
|
-
function commandSpelling(command) {
|
|
2234
|
-
return isTopLevel(command) ? command.subject : `${command.subject} ${command.action}`;
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
2147
|
// src/effort/store/fix-schema.ts
|
|
2238
2148
|
import { existsSync as existsSync8 } from "fs";
|
|
2239
2149
|
import { Database as Database4 } from "bun:sqlite";
|
|
@@ -2285,18 +2195,18 @@ function vacuumInto(source, target) {
|
|
|
2285
2195
|
db.close();
|
|
2286
2196
|
}
|
|
2287
2197
|
}
|
|
2288
|
-
function copyOne(
|
|
2289
|
-
if (
|
|
2290
|
-
vacuumInto(join10(source,
|
|
2198
|
+
function copyOne(name2, source, target) {
|
|
2199
|
+
if (name2 === SQLITE_STORE_FILE_NAME) {
|
|
2200
|
+
vacuumInto(join10(source, name2), join10(target, name2));
|
|
2291
2201
|
return;
|
|
2292
2202
|
}
|
|
2293
|
-
copyFileSync(join10(source,
|
|
2203
|
+
copyFileSync(join10(source, name2), join10(target, name2), constants.COPYFILE_EXCL);
|
|
2294
2204
|
}
|
|
2295
2205
|
function undo(target, written, madeDir) {
|
|
2296
2206
|
try {
|
|
2297
2207
|
if (madeDir === undefined) {
|
|
2298
|
-
for (const
|
|
2299
|
-
rmSync5(join10(target,
|
|
2208
|
+
for (const name2 of written)
|
|
2209
|
+
rmSync5(join10(target, name2), { force: true });
|
|
2300
2210
|
} else {
|
|
2301
2211
|
rmSync5(madeDir, { recursive: true, force: true });
|
|
2302
2212
|
}
|
|
@@ -2310,7 +2220,7 @@ function copyEffortStore(request) {
|
|
|
2310
2220
|
const target = resolve3(request.target);
|
|
2311
2221
|
guardTestProcess(source);
|
|
2312
2222
|
guardTestProcess(target);
|
|
2313
|
-
const files = EFFORT_COPY_FILE_NAMES.filter((
|
|
2223
|
+
const files = EFFORT_COPY_FILE_NAMES.filter((name2) => existsSync6(join10(source, name2)));
|
|
2314
2224
|
if (files.length === 0) {
|
|
2315
2225
|
throw new EffortCopyRefusal(`no effort store at ${source}: it holds none of ${EFFORT_COPY_FILE_NAMES.join(", ")}. Nothing to copy.`);
|
|
2316
2226
|
}
|
|
@@ -2319,9 +2229,9 @@ function copyEffortStore(request) {
|
|
|
2319
2229
|
let madeDir;
|
|
2320
2230
|
try {
|
|
2321
2231
|
madeDir = mkdirSync6(target, { recursive: true });
|
|
2322
|
-
for (const
|
|
2323
|
-
written.push(
|
|
2324
|
-
copyOne(
|
|
2232
|
+
for (const name2 of files) {
|
|
2233
|
+
written.push(name2);
|
|
2234
|
+
copyOne(name2, source, target);
|
|
2325
2235
|
}
|
|
2326
2236
|
} catch (error) {
|
|
2327
2237
|
const left = undo(target, written, madeDir);
|
|
@@ -2337,8 +2247,8 @@ class RebuildRefusal extends Error {
|
|
|
2337
2247
|
this.name = "RebuildRefusal";
|
|
2338
2248
|
}
|
|
2339
2249
|
}
|
|
2340
|
-
function quoted(
|
|
2341
|
-
return `"${
|
|
2250
|
+
function quoted(name2) {
|
|
2251
|
+
return `"${name2.replaceAll('"', '""')}"`;
|
|
2342
2252
|
}
|
|
2343
2253
|
function count(db, sql) {
|
|
2344
2254
|
return db.query(sql).get()?.n ?? 0;
|
|
@@ -2544,8 +2454,8 @@ function buildParallel(path, parallelPath, migrations, stamp) {
|
|
|
2544
2454
|
db.close();
|
|
2545
2455
|
}
|
|
2546
2456
|
}
|
|
2547
|
-
function asidePaths(path,
|
|
2548
|
-
return { parallelPath: `${path}.fix-${stamp}`, backupPath: `${path}.v${String(
|
|
2457
|
+
function asidePaths(path, version2, stamp) {
|
|
2458
|
+
return { parallelPath: `${path}.fix-${stamp}`, backupPath: `${path}.v${String(version2)}-${stamp}.bak` };
|
|
2549
2459
|
}
|
|
2550
2460
|
function isRepairable(reason) {
|
|
2551
2461
|
return REPAIRABLE_REASONS.includes(reason);
|
|
@@ -2578,8 +2488,8 @@ function fixStoreSchema(options) {
|
|
|
2578
2488
|
const plan = planSchema(store, sqliteCatalogue(migrations), "write", "open");
|
|
2579
2489
|
if (plan.verdict === "use") {
|
|
2580
2490
|
const pending = writeNames(plan);
|
|
2581
|
-
const
|
|
2582
|
-
return { ...found, ...nothing, status, reason: null, refusal: null, pending };
|
|
2491
|
+
const status2 = pending.length === 0 ? "current" : "behind";
|
|
2492
|
+
return { ...found, ...nothing, status: status2, reason: null, refusal: null, pending };
|
|
2583
2493
|
}
|
|
2584
2494
|
if (!isRepairable(plan.reason)) {
|
|
2585
2495
|
throw new RebuildRefusal(`a rebuild at this rafa's migrations does not repair ${plan.reason}. ${plan.message}`);
|
|
@@ -2611,8 +2521,8 @@ import { resolve as resolve4 } from "path";
|
|
|
2611
2521
|
// src/config-load.ts
|
|
2612
2522
|
import { existsSync as existsSync9, readFileSync as readFileSync8, realpathSync as realpathSync5 } from "fs";
|
|
2613
2523
|
import { isAbsolute as isAbsolute6 } from "path";
|
|
2614
|
-
function
|
|
2615
|
-
const path =
|
|
2524
|
+
function readConfigFile(root) {
|
|
2525
|
+
const path = configFilePath(root);
|
|
2616
2526
|
if (!existsSync9(path))
|
|
2617
2527
|
return null;
|
|
2618
2528
|
let text;
|
|
@@ -2620,9 +2530,9 @@ function readConfigFile2(root) {
|
|
|
2620
2530
|
text = readFileSync8(path, "utf8");
|
|
2621
2531
|
} catch (error) {
|
|
2622
2532
|
const problem = `${path}: cannot be read (${messageOf(error)})`;
|
|
2623
|
-
throw new
|
|
2533
|
+
throw new ConfigError([problem], { cause: error });
|
|
2624
2534
|
}
|
|
2625
|
-
return
|
|
2535
|
+
return parseConfigText(text, path);
|
|
2626
2536
|
}
|
|
2627
2537
|
function isSameDirectory(first, second) {
|
|
2628
2538
|
try {
|
|
@@ -2633,19 +2543,19 @@ function isSameDirectory(first, second) {
|
|
|
2633
2543
|
}
|
|
2634
2544
|
function readUserConfigFile({ root, home }) {
|
|
2635
2545
|
if (!isAbsolute6(home)) {
|
|
2636
|
-
throw new
|
|
2546
|
+
throw new ConfigError([
|
|
2637
2547
|
`home directory is ${describeValue(home)}, expected an absolute path`
|
|
2638
2548
|
]);
|
|
2639
2549
|
}
|
|
2640
|
-
return isSameDirectory(root, home) ? null :
|
|
2550
|
+
return isSameDirectory(root, home) ? null : readConfigFile(home);
|
|
2641
2551
|
}
|
|
2642
2552
|
function printWarning(message) {
|
|
2643
2553
|
activeOutput().warn(message);
|
|
2644
2554
|
}
|
|
2645
|
-
function
|
|
2555
|
+
function loadConfig(roots, cli = {}, warn = printWarning) {
|
|
2646
2556
|
const user = readUserConfigFile(roots);
|
|
2647
|
-
const file =
|
|
2648
|
-
const resolved =
|
|
2557
|
+
const file = readConfigFile(roots.root);
|
|
2558
|
+
const resolved = resolveConfig({ cli, file, user });
|
|
2649
2559
|
for (const warning of resolved.warnings)
|
|
2650
2560
|
warn(warning);
|
|
2651
2561
|
setActiveStoreSettings({ busyTimeoutMs: resolved.config.effortBusyTimeoutMs });
|
|
@@ -2666,9 +2576,9 @@ function resolvePlansDir(project, command, warn) {
|
|
|
2666
2576
|
}
|
|
2667
2577
|
function resolveProjectConfig(project, command, warn) {
|
|
2668
2578
|
try {
|
|
2669
|
-
return
|
|
2579
|
+
return loadConfig({ root: project.root, home: project.home }, {}, warn).config;
|
|
2670
2580
|
} catch (error) {
|
|
2671
|
-
if (!(error instanceof
|
|
2581
|
+
if (!(error instanceof ConfigError))
|
|
2672
2582
|
throw error;
|
|
2673
2583
|
throw new CommandExit(1, [
|
|
2674
2584
|
`\u274C ${command}: the config cannot be used:`,
|
|
@@ -2696,8 +2606,8 @@ function checkbox(status) {
|
|
|
2696
2606
|
function planFileName(stub, tracker) {
|
|
2697
2607
|
return tracker ? `PLAN_TRACKER-${stub}.md` : `PLAN-${stub}.md`;
|
|
2698
2608
|
}
|
|
2699
|
-
function stubOfPlanFile(
|
|
2700
|
-
const stub = /^PLAN-(.+)\.md$/.exec(
|
|
2609
|
+
function stubOfPlanFile(name2) {
|
|
2610
|
+
const stub = /^PLAN-(.+)\.md$/.exec(name2)?.[1];
|
|
2701
2611
|
return stub !== undefined && isStampableStub(stub) ? stub : null;
|
|
2702
2612
|
}
|
|
2703
2613
|
function isFile(path) {
|
|
@@ -2706,8 +2616,8 @@ function isFile(path) {
|
|
|
2706
2616
|
function issueLine(file, issue) {
|
|
2707
2617
|
return `${file}:${issue.line}: ${issue.reason}: ${issue.text}`;
|
|
2708
2618
|
}
|
|
2709
|
-
function plural(
|
|
2710
|
-
return
|
|
2619
|
+
function plural(count2, noun) {
|
|
2620
|
+
return count2 === 1 ? `${count2} ${noun}` : `${count2} ${noun}s`;
|
|
2711
2621
|
}
|
|
2712
2622
|
function expectNoArgument(args, usage) {
|
|
2713
2623
|
if (args.length === 0)
|
|
@@ -2732,12 +2642,12 @@ function expectAtMostOneArgument(args, usage) {
|
|
|
2732
2642
|
throw new CommandExit(1, `\u274C Expected at most one argument, got ${args.length}: ${args.join(" ")}
|
|
2733
2643
|
Usage: ${usage}`);
|
|
2734
2644
|
}
|
|
2735
|
-
function readSwitch(
|
|
2645
|
+
function readSwitch(name2, value, hint) {
|
|
2736
2646
|
if (value === undefined || value === false || value === "false")
|
|
2737
2647
|
return false;
|
|
2738
2648
|
if (value === true || value === "true")
|
|
2739
2649
|
return true;
|
|
2740
|
-
throw new CommandExit(1, `\u274C --${
|
|
2650
|
+
throw new CommandExit(1, `\u274C --${name2} takes no value, and read "${value}" as one. ${hint}`);
|
|
2741
2651
|
}
|
|
2742
2652
|
|
|
2743
2653
|
// src/commands/effort/fix-schema.ts
|
|
@@ -2899,8 +2809,8 @@ function normaliseBranch(source) {
|
|
|
2899
2809
|
return null;
|
|
2900
2810
|
return source.startsWith("refs/heads/") ? source.slice("refs/heads/".length) : source;
|
|
2901
2811
|
}
|
|
2902
|
-
function parseStatCount(
|
|
2903
|
-
return /^\d+$/.test(
|
|
2812
|
+
function parseStatCount(field2) {
|
|
2813
|
+
return /^\d+$/.test(field2) ? Number(field2) : null;
|
|
2904
2814
|
}
|
|
2905
2815
|
function parseCommitHeader(line) {
|
|
2906
2816
|
const parts = line.split(FIELD_SEPARATOR);
|
|
@@ -3128,7 +3038,7 @@ function mergedTables() {
|
|
|
3128
3038
|
return Object.entries(MERGE_RULES).flatMap(([table, rule]) => rule.scope === "merged" ? [{ table, identity: rule.identity }] : []);
|
|
3129
3039
|
}
|
|
3130
3040
|
function carriedColumns(db, table) {
|
|
3131
|
-
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name }) =>
|
|
3041
|
+
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name: name2 }) => name2).filter((name2) => name2 !== "seq");
|
|
3132
3042
|
}
|
|
3133
3043
|
function refuseColumnMismatch(local, other, tables) {
|
|
3134
3044
|
const faults = tables.flatMap(({ table }) => {
|
|
@@ -3219,29 +3129,29 @@ function sameValue(left, right) {
|
|
|
3219
3129
|
return Buffer.from(left).equals(right);
|
|
3220
3130
|
return (left ?? null) === (right ?? null);
|
|
3221
3131
|
}
|
|
3222
|
-
function fillStatement(table,
|
|
3223
|
-
const statement = SET_ONCE_FILLS[`${table}.${
|
|
3132
|
+
function fillStatement(table, field2) {
|
|
3133
|
+
const statement = SET_ONCE_FILLS[`${table}.${field2}`];
|
|
3224
3134
|
if (statement === undefined) {
|
|
3225
|
-
throw new Error(`${table}.${
|
|
3135
|
+
throw new Error(`${table}.${field2} is set-once in MERGE_RULES and has no statement in SET_ONCE_FILLS`);
|
|
3226
3136
|
}
|
|
3227
3137
|
return statement;
|
|
3228
3138
|
}
|
|
3229
|
-
function settleSetOnce(local, table,
|
|
3230
|
-
const incoming = there[
|
|
3139
|
+
function settleSetOnce(local, table, field2, here, there) {
|
|
3140
|
+
const incoming = there[field2] ?? null;
|
|
3231
3141
|
if (incoming === null)
|
|
3232
3142
|
return { skipped: true, filled: [], conflictFields: [] };
|
|
3233
|
-
if ((here[
|
|
3234
|
-
return { skipped: false, filled: [], conflictFields: [
|
|
3235
|
-
local.query(fillStatement(table,
|
|
3236
|
-
return { skipped: true, filled: [
|
|
3143
|
+
if ((here[field2] ?? null) !== null)
|
|
3144
|
+
return { skipped: false, filled: [], conflictFields: [field2] };
|
|
3145
|
+
local.query(fillStatement(table, field2)).run(incoming, Number(here.seq));
|
|
3146
|
+
return { skipped: true, filled: [field2], conflictFields: [] };
|
|
3237
3147
|
}
|
|
3238
|
-
function settleRecomputed(table,
|
|
3239
|
-
const comparable = RECOMPUTED_COMPARISONS[`${table}.${
|
|
3148
|
+
function settleRecomputed(table, field2, here, there) {
|
|
3149
|
+
const comparable = RECOMPUTED_COMPARISONS[`${table}.${field2}`];
|
|
3240
3150
|
if (comparable === undefined) {
|
|
3241
|
-
throw new Error(`${table}.${
|
|
3151
|
+
throw new Error(`${table}.${field2} is recomputed in MERGE_RULES and has no entry in RECOMPUTED_COMPARISONS`);
|
|
3242
3152
|
}
|
|
3243
|
-
const hereValue = here[
|
|
3244
|
-
const thereValue = there[
|
|
3153
|
+
const hereValue = here[field2];
|
|
3154
|
+
const thereValue = there[field2];
|
|
3245
3155
|
const same = typeof hereValue === "string" && typeof thereValue === "string" && comparable(hereValue) === comparable(thereValue);
|
|
3246
3156
|
return same ? { skipped: true, filled: [], conflictFields: [] } : { skipped: false, filled: [], conflictFields: [null] };
|
|
3247
3157
|
}
|
|
@@ -3251,12 +3161,12 @@ function settlePair(local, table, edited, here, there) {
|
|
|
3251
3161
|
return { skipped: true, filled: [], conflictFields: [] };
|
|
3252
3162
|
if (differing.some((column) => !Object.hasOwn(edited, column)))
|
|
3253
3163
|
return { skipped: false, filled: [], conflictFields: [null] };
|
|
3254
|
-
const outcomes = differing.map((
|
|
3255
|
-
if (edited[
|
|
3256
|
-
return settleSetOnce(local, table,
|
|
3257
|
-
if (edited[
|
|
3258
|
-
return settleRecomputed(table,
|
|
3259
|
-
throw new Error(`${table}.${
|
|
3164
|
+
const outcomes = differing.map((field2) => {
|
|
3165
|
+
if (edited[field2] === SET_ONCE)
|
|
3166
|
+
return settleSetOnce(local, table, field2, here, there);
|
|
3167
|
+
if (edited[field2] === RECOMPUTED)
|
|
3168
|
+
return settleRecomputed(table, field2, here, there);
|
|
3169
|
+
throw new Error(`${table}.${field2} has the rule ${edited[field2]}, which the merge does not apply`);
|
|
3260
3170
|
});
|
|
3261
3171
|
return {
|
|
3262
3172
|
skipped: outcomes.every((outcome) => outcome.skipped),
|
|
@@ -3273,16 +3183,16 @@ function settleTable(local, other, union, trail) {
|
|
|
3273
3183
|
const here = rowAt(local, union.table, columns, match.localSeq, "here");
|
|
3274
3184
|
const there = rowAt(other, union.table, columns, match.incomingSeq, "in the other store");
|
|
3275
3185
|
const outcome = settlePair(local, union.table, rule.edited, here, there);
|
|
3276
|
-
const conflicts = outcome.conflictFields.map((
|
|
3277
|
-
local.query(INSERT_CONFLICT).run(trail.mergeId, union.table, match.localSeq,
|
|
3278
|
-
return { localSeq: match.localSeq, incomingSeq: match.incomingSeq, field };
|
|
3186
|
+
const conflicts = outcome.conflictFields.map((field2) => {
|
|
3187
|
+
local.query(INSERT_CONFLICT).run(trail.mergeId, union.table, match.localSeq, field2, JSON.stringify(there), trail.recordedAt);
|
|
3188
|
+
return { localSeq: match.localSeq, incomingSeq: match.incomingSeq, field: field2 };
|
|
3279
3189
|
});
|
|
3280
3190
|
return { match, outcome, conflicts };
|
|
3281
3191
|
});
|
|
3282
3192
|
return {
|
|
3283
3193
|
table: union.table,
|
|
3284
3194
|
skipped: settled.flatMap(({ match, outcome }) => outcome.skipped ? [match.localSeq] : []),
|
|
3285
|
-
filled: settled.flatMap(({ match, outcome }) => outcome.filled.map((
|
|
3195
|
+
filled: settled.flatMap(({ match, outcome }) => outcome.filled.map((field2) => ({ localSeq: match.localSeq, field: field2 }))),
|
|
3286
3196
|
conflicts: settled.flatMap(({ conflicts }) => conflicts)
|
|
3287
3197
|
};
|
|
3288
3198
|
}
|
|
@@ -3492,7 +3402,7 @@ function mergeInto(db, other, mergeId, otherStore, mergedAt) {
|
|
|
3492
3402
|
collided: union.collided.length
|
|
3493
3403
|
};
|
|
3494
3404
|
});
|
|
3495
|
-
const sum = (
|
|
3405
|
+
const sum = (field2) => tables.reduce((total, entry) => total + entry[field2], 0);
|
|
3496
3406
|
const totals = { rowsAdded: sum("added"), rowsSkipped: sum("skipped"), rowsInConflict: sum("inConflict") };
|
|
3497
3407
|
db.query(INSERT_MERGE).run(mergeId, otherStore, mergedAt, totals.rowsAdded, totals.rowsSkipped, totals.rowsInConflict);
|
|
3498
3408
|
return { tables, ...totals, gapsRewritten: gaps.length };
|
|
@@ -3505,18 +3415,18 @@ function addedRows(merged) {
|
|
|
3505
3415
|
merge_conflicts: merged.rowsInConflict
|
|
3506
3416
|
};
|
|
3507
3417
|
}
|
|
3508
|
-
function buildMerged(path, parallelPath, other,
|
|
3418
|
+
function buildMerged(path, parallelPath, other, merge2) {
|
|
3509
3419
|
vacuumInto(path, parallelPath);
|
|
3510
3420
|
const db = new Database6(parallelPath, { readwrite: true });
|
|
3511
3421
|
try {
|
|
3512
|
-
bringForward(db, parallelPath, "write", "open", { ...
|
|
3513
|
-
const merged = mergeInto(db, other,
|
|
3422
|
+
bringForward(db, parallelPath, "write", "open", { ...merge2.stamp, builtAside: true });
|
|
3423
|
+
const merged = mergeInto(db, other, merge2.id, merge2.otherStore, merge2.stamp.now().toISOString());
|
|
3514
3424
|
db.run(`ATTACH DATABASE ? AS ${LIVE3}`, [path]);
|
|
3515
3425
|
const live = new Set(tableNames(db, LIVE3));
|
|
3516
3426
|
checkedCounts(db, tableNames(db, "main").filter((table) => live.has(table)), LIVE3, addedRows(merged));
|
|
3517
3427
|
db.run(`DETACH DATABASE ${LIVE3}`);
|
|
3518
3428
|
refuseCorrupt(db);
|
|
3519
|
-
refuseUnusable(db, parallelPath,
|
|
3429
|
+
refuseUnusable(db, parallelPath, merge2.stamp.migrations);
|
|
3520
3430
|
return merged;
|
|
3521
3431
|
} finally {
|
|
3522
3432
|
db.close();
|
|
@@ -3539,7 +3449,7 @@ function refuseUnmergeable(options, migrations) {
|
|
|
3539
3449
|
}
|
|
3540
3450
|
return { otherPlan, otherStore };
|
|
3541
3451
|
}
|
|
3542
|
-
function
|
|
3452
|
+
function mergeStore(options) {
|
|
3543
3453
|
const { path, otherPath, dryRun, stamp } = options;
|
|
3544
3454
|
guardTestProcess(path);
|
|
3545
3455
|
guardTestProcess(otherPath);
|
|
@@ -3574,10 +3484,10 @@ var NOTHING_COPIED = "Nothing was copied.";
|
|
|
3574
3484
|
class FileSyncRefusal extends Error {
|
|
3575
3485
|
name = "FileSyncRefusal";
|
|
3576
3486
|
}
|
|
3577
|
-
function requirePath(
|
|
3487
|
+
function requirePath(field2, value) {
|
|
3578
3488
|
if (typeof value === "string" && value !== "")
|
|
3579
3489
|
return value;
|
|
3580
|
-
throw new TypeError(`${REFUSAL}: ${
|
|
3490
|
+
throw new TypeError(`${REFUSAL}: ${field2} is ${describeValue(value)}, expected a path`);
|
|
3581
3491
|
}
|
|
3582
3492
|
function refuseUncarriable(backend, source) {
|
|
3583
3493
|
if (backend === "ndjson") {
|
|
@@ -3607,8 +3517,8 @@ function createFileSync(options) {
|
|
|
3607
3517
|
const otherPath = resolve5(repoRoot, requirePath("from", from));
|
|
3608
3518
|
const path = join12(storeDir(), SQLITE_STORE_FILE_NAME);
|
|
3609
3519
|
const stamp = fileStamp(now());
|
|
3610
|
-
const
|
|
3611
|
-
return Object.freeze({ status: "pulled", merge });
|
|
3520
|
+
const merge2 = mergeStore({ ...mergeSeams(options), path, otherPath, backend, dryRun, stamp, now, sessionId });
|
|
3521
|
+
return Object.freeze({ status: "pulled", merge: merge2 });
|
|
3612
3522
|
};
|
|
3613
3523
|
return Object.freeze({ kind: "file", push, pull });
|
|
3614
3524
|
}
|
|
@@ -3659,7 +3569,7 @@ function contextOf(repoRoot, config) {
|
|
|
3659
3569
|
...hub === undefined ? {} : { hub }
|
|
3660
3570
|
};
|
|
3661
3571
|
}
|
|
3662
|
-
function selectSync(repoRoot, config, registry =
|
|
3572
|
+
function selectSync(repoRoot, config, registry = CORE_ADAPTER_REGISTRY) {
|
|
3663
3573
|
const kind = config.effortSync;
|
|
3664
3574
|
const adapter = typeof kind === "string" ? registry.find("sync", kind) : undefined;
|
|
3665
3575
|
if (adapter !== undefined)
|
|
@@ -3667,8 +3577,8 @@ function selectSync(repoRoot, config, registry = CORE_ADAPTER_REGISTRY2) {
|
|
|
3667
3577
|
const registered = registry.kinds("sync");
|
|
3668
3578
|
if (isModuleStrategy(kind))
|
|
3669
3579
|
throw new SyncModuleMissing(kind, registered);
|
|
3670
|
-
const known =
|
|
3671
|
-
const expected = known ? `registered: ${listed(registered)}` : `expected one of: ${
|
|
3580
|
+
const known = SYNC_STRATEGIES.includes(kind);
|
|
3581
|
+
const expected = known ? `registered: ${listed(registered)}` : `expected one of: ${SYNC_STRATEGIES.join(", ")}`;
|
|
3672
3582
|
throw new TypeError(`${REFUSAL2}: effort.sync is ${describeValue(kind)}, and no sync adapter is registered under it; ${expected}`);
|
|
3673
3583
|
}
|
|
3674
3584
|
|
|
@@ -3683,7 +3593,7 @@ import { join as join14 } from "path";
|
|
|
3683
3593
|
// src/check/layout.ts
|
|
3684
3594
|
import { readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
3685
3595
|
import { join as join13, relative as relative2 } from "path";
|
|
3686
|
-
var
|
|
3596
|
+
var SKILL_FILE = "SKILL.md";
|
|
3687
3597
|
var MARKDOWN_EXTENSION = ".md";
|
|
3688
3598
|
var LAYOUT_SEVERITY = Object.freeze({
|
|
3689
3599
|
"flat-grouped-layout": "failure",
|
|
@@ -3711,13 +3621,13 @@ function entryKind(path) {
|
|
|
3711
3621
|
}
|
|
3712
3622
|
}
|
|
3713
3623
|
function visibleNames(dir) {
|
|
3714
|
-
return readdirSync3(dir).filter((
|
|
3624
|
+
return readdirSync3(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
3715
3625
|
}
|
|
3716
|
-
function isMarkdown(
|
|
3717
|
-
return
|
|
3626
|
+
function isMarkdown(name2) {
|
|
3627
|
+
return name2.endsWith(MARKDOWN_EXTENSION);
|
|
3718
3628
|
}
|
|
3719
|
-
function markdownStem(
|
|
3720
|
-
return
|
|
3629
|
+
function markdownStem(name2) {
|
|
3630
|
+
return name2.slice(0, -MARKDOWN_EXTENSION.length);
|
|
3721
3631
|
}
|
|
3722
3632
|
function shown(root, path) {
|
|
3723
3633
|
return relative2(root, path) || ".";
|
|
@@ -3742,37 +3652,37 @@ function unreadableEntry(kind, root, path) {
|
|
|
3742
3652
|
issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
|
|
3743
3653
|
};
|
|
3744
3654
|
}
|
|
3745
|
-
function skillFileEntry(path,
|
|
3746
|
-
return { kind: "skill", path, isFile: true, shape: "skill-file", name, issues: [] };
|
|
3655
|
+
function skillFileEntry(path, name2) {
|
|
3656
|
+
return { kind: "skill", path, isFile: true, shape: "skill-file", name: name2, issues: [] };
|
|
3747
3657
|
}
|
|
3748
|
-
function flatGroupedEntry(root, path,
|
|
3658
|
+
function flatGroupedEntry(root, path, name2) {
|
|
3749
3659
|
return {
|
|
3750
3660
|
kind: "skill",
|
|
3751
3661
|
path,
|
|
3752
3662
|
isFile: true,
|
|
3753
3663
|
shape: "flat-grouped",
|
|
3754
|
-
name,
|
|
3755
|
-
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>/${
|
|
3664
|
+
name: name2,
|
|
3665
|
+
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}`)]
|
|
3756
3666
|
};
|
|
3757
3667
|
}
|
|
3758
|
-
function nestedGroupEntry(root, path,
|
|
3668
|
+
function nestedGroupEntry(root, path, name2) {
|
|
3759
3669
|
return {
|
|
3760
3670
|
kind: "skill",
|
|
3761
3671
|
path,
|
|
3762
3672
|
isFile: true,
|
|
3763
3673
|
shape: "nested-group",
|
|
3764
|
-
name,
|
|
3765
|
-
issues: [layoutIssue("nested-group-layout", `${shown(root, path)} is the nested layout <dir>/<group>/<name>/${
|
|
3674
|
+
name: name2,
|
|
3675
|
+
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}`)]
|
|
3766
3676
|
};
|
|
3767
3677
|
}
|
|
3768
|
-
function looseFileEntry(root, path,
|
|
3678
|
+
function looseFileEntry(root, path, name2) {
|
|
3769
3679
|
return {
|
|
3770
3680
|
kind: "skill",
|
|
3771
3681
|
path,
|
|
3772
3682
|
isFile: true,
|
|
3773
3683
|
shape: "loose-file",
|
|
3774
|
-
name,
|
|
3775
|
-
issues: [layoutIssue("loose-file", `${shown(root, path)} sits directly under the skills directory, which registers` + ` only <dir>/<name>/${
|
|
3684
|
+
name: name2,
|
|
3685
|
+
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}`)]
|
|
3776
3686
|
};
|
|
3777
3687
|
}
|
|
3778
3688
|
function noSkillFileEntry(root, dir) {
|
|
@@ -3782,26 +3692,26 @@ function noSkillFileEntry(root, dir) {
|
|
|
3782
3692
|
isFile: false,
|
|
3783
3693
|
shape: "no-skill-file",
|
|
3784
3694
|
name: null,
|
|
3785
|
-
issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${
|
|
3695
|
+
issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
|
|
3786
3696
|
};
|
|
3787
3697
|
}
|
|
3788
|
-
function nestedInstinctEntry(root, path,
|
|
3698
|
+
function nestedInstinctEntry(root, path, name2) {
|
|
3789
3699
|
return {
|
|
3790
3700
|
kind: "instinct",
|
|
3791
3701
|
path,
|
|
3792
3702
|
isFile: true,
|
|
3793
3703
|
shape: "nested-instinct",
|
|
3794
|
-
name,
|
|
3795
|
-
issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${
|
|
3704
|
+
name: name2,
|
|
3705
|
+
issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name2}${MARKDOWN_EXTENSION}`)]
|
|
3796
3706
|
};
|
|
3797
3707
|
}
|
|
3798
|
-
function scanGroupMember(root, dir,
|
|
3708
|
+
function scanGroupMember(root, dir, name2) {
|
|
3799
3709
|
const children = visibleNames(dir);
|
|
3800
3710
|
if (children.length === 0)
|
|
3801
3711
|
return [dotfileOnlyEntry("skill", root, dir)];
|
|
3802
|
-
const skillPath = join13(dir,
|
|
3803
|
-
if (children.includes(
|
|
3804
|
-
return [nestedGroupEntry(root, skillPath,
|
|
3712
|
+
const skillPath = join13(dir, SKILL_FILE);
|
|
3713
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
3714
|
+
return [nestedGroupEntry(root, skillPath, name2)];
|
|
3805
3715
|
}
|
|
3806
3716
|
return [noSkillFileEntry(root, dir)];
|
|
3807
3717
|
}
|
|
@@ -3825,31 +3735,31 @@ function scanSkillGroup(root, dir, children) {
|
|
|
3825
3735
|
return [noSkillFileEntry(root, dir)];
|
|
3826
3736
|
return entries;
|
|
3827
3737
|
}
|
|
3828
|
-
function scanSkillDirectory(root, dir,
|
|
3738
|
+
function scanSkillDirectory(root, dir, name2) {
|
|
3829
3739
|
const children = visibleNames(dir);
|
|
3830
3740
|
if (children.length === 0)
|
|
3831
3741
|
return [dotfileOnlyEntry("skill", root, dir)];
|
|
3832
|
-
const skillPath = join13(dir,
|
|
3833
|
-
if (children.includes(
|
|
3834
|
-
return [skillFileEntry(skillPath,
|
|
3742
|
+
const skillPath = join13(dir, SKILL_FILE);
|
|
3743
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
3744
|
+
return [skillFileEntry(skillPath, name2)];
|
|
3835
3745
|
}
|
|
3836
3746
|
return scanSkillGroup(root, dir, children);
|
|
3837
3747
|
}
|
|
3838
3748
|
function scanSkillRoot(root) {
|
|
3839
3749
|
const entries = [];
|
|
3840
|
-
for (const
|
|
3841
|
-
const path = join13(root,
|
|
3750
|
+
for (const name2 of visibleNames(root)) {
|
|
3751
|
+
const path = join13(root, name2);
|
|
3842
3752
|
const found = entryKind(path);
|
|
3843
3753
|
if (found === null) {
|
|
3844
3754
|
entries.push(unreadableEntry("skill", root, path));
|
|
3845
3755
|
continue;
|
|
3846
3756
|
}
|
|
3847
3757
|
if (found === "directory") {
|
|
3848
|
-
entries.push(...scanSkillDirectory(root, path,
|
|
3758
|
+
entries.push(...scanSkillDirectory(root, path, name2));
|
|
3849
3759
|
continue;
|
|
3850
3760
|
}
|
|
3851
|
-
if (isMarkdown(
|
|
3852
|
-
entries.push(looseFileEntry(root, path, markdownStem(
|
|
3761
|
+
if (isMarkdown(name2))
|
|
3762
|
+
entries.push(looseFileEntry(root, path, markdownStem(name2)));
|
|
3853
3763
|
}
|
|
3854
3764
|
return entries;
|
|
3855
3765
|
}
|
|
@@ -3876,8 +3786,8 @@ function scanInstinctSubdirectory(root, dir) {
|
|
|
3876
3786
|
}
|
|
3877
3787
|
function scanInstinctRoot(root) {
|
|
3878
3788
|
const entries = [];
|
|
3879
|
-
for (const
|
|
3880
|
-
const path = join13(root,
|
|
3789
|
+
for (const name2 of visibleNames(root)) {
|
|
3790
|
+
const path = join13(root, name2);
|
|
3881
3791
|
const found = entryKind(path);
|
|
3882
3792
|
if (found === null) {
|
|
3883
3793
|
entries.push(unreadableEntry("instinct", root, path));
|
|
@@ -3887,14 +3797,14 @@ function scanInstinctRoot(root) {
|
|
|
3887
3797
|
entries.push(...scanInstinctSubdirectory(root, path));
|
|
3888
3798
|
continue;
|
|
3889
3799
|
}
|
|
3890
|
-
if (!isMarkdown(
|
|
3800
|
+
if (!isMarkdown(name2))
|
|
3891
3801
|
continue;
|
|
3892
3802
|
entries.push({
|
|
3893
3803
|
kind: "instinct",
|
|
3894
3804
|
path,
|
|
3895
3805
|
isFile: true,
|
|
3896
3806
|
shape: "instinct-file",
|
|
3897
|
-
name: markdownStem(
|
|
3807
|
+
name: markdownStem(name2),
|
|
3898
3808
|
issues: []
|
|
3899
3809
|
});
|
|
3900
3810
|
}
|
|
@@ -3914,9 +3824,9 @@ function scanLayout(root, kind) {
|
|
|
3914
3824
|
function declaredNameIssue(entry, declared) {
|
|
3915
3825
|
if (entry.name === null || declared === null || declared === entry.name)
|
|
3916
3826
|
return null;
|
|
3917
|
-
const
|
|
3827
|
+
const field2 = entry.kind === "skill" ? "name" : "id";
|
|
3918
3828
|
const source = entry.kind === "skill" ? "directory name" : "file name";
|
|
3919
|
-
return layoutIssue("name-mismatch", `frontmatter ${
|
|
3829
|
+
return layoutIssue("name-mismatch", `frontmatter ${field2} "${declared}" is not the ${source} "${entry.name}"`);
|
|
3920
3830
|
}
|
|
3921
3831
|
|
|
3922
3832
|
// src/schema/frontmatter.ts
|
|
@@ -4005,8 +3915,8 @@ function writeFrontmatter(document, data) {
|
|
|
4005
3915
|
}
|
|
4006
3916
|
|
|
4007
3917
|
// src/schema/instinct.ts
|
|
4008
|
-
var INSTINCT_KINDS =
|
|
4009
|
-
var INSTINCT_SIGNALS =
|
|
3918
|
+
var INSTINCT_KINDS = FINDING_KINDS;
|
|
3919
|
+
var INSTINCT_SIGNALS = FINDING_SIGNALS;
|
|
4010
3920
|
var INSTINCT_DOMAINS = FINDING_DOMAINS;
|
|
4011
3921
|
var INSTINCT_SCOPES = ["project", "user"];
|
|
4012
3922
|
var INSTINCT_SOURCES = ["task-report", "loop-observed", "imported", "demoted"];
|
|
@@ -4036,8 +3946,8 @@ var INSTINCT_KEY_ORDER = [
|
|
|
4036
3946
|
"created_at",
|
|
4037
3947
|
"updated_at"
|
|
4038
3948
|
];
|
|
4039
|
-
function issue(code,
|
|
4040
|
-
return { code, field, message };
|
|
3949
|
+
function issue(code, field2, message) {
|
|
3950
|
+
return { code, field: field2, message };
|
|
4041
3951
|
}
|
|
4042
3952
|
function typeName(value) {
|
|
4043
3953
|
if (value === null)
|
|
@@ -4052,25 +3962,25 @@ function typeName(value) {
|
|
|
4052
3962
|
return "a number";
|
|
4053
3963
|
return `a ${typeof value}`;
|
|
4054
3964
|
}
|
|
4055
|
-
function requiredString(data,
|
|
4056
|
-
const value = data[
|
|
3965
|
+
function requiredString(data, field2) {
|
|
3966
|
+
const value = data[field2];
|
|
4057
3967
|
if (value === undefined || value === null) {
|
|
4058
|
-
return [issue("missing-field",
|
|
3968
|
+
return [issue("missing-field", field2, `${field2} is required and is absent`)];
|
|
4059
3969
|
}
|
|
4060
3970
|
if (typeof value !== "string") {
|
|
4061
|
-
return [issue("wrong-type",
|
|
3971
|
+
return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(value)}`)];
|
|
4062
3972
|
}
|
|
4063
3973
|
if (value.trim() === "") {
|
|
4064
|
-
return [issue("missing-field",
|
|
3974
|
+
return [issue("missing-field", field2, `${field2} is required and is empty`)];
|
|
4065
3975
|
}
|
|
4066
3976
|
return [];
|
|
4067
3977
|
}
|
|
4068
|
-
function requiredChoice(data,
|
|
4069
|
-
const issues = requiredString(data,
|
|
3978
|
+
function requiredChoice(data, field2, allowed, code) {
|
|
3979
|
+
const issues = requiredString(data, field2);
|
|
4070
3980
|
if (issues.length > 0)
|
|
4071
3981
|
return issues;
|
|
4072
|
-
const value = data[
|
|
4073
|
-
return allowed.includes(value) ? [] : [issue(code,
|
|
3982
|
+
const value = data[field2];
|
|
3983
|
+
return allowed.includes(value) ? [] : [issue(code, field2, `${field2} "${value}" is not one of ${allowed.join(", ")}`)];
|
|
4074
3984
|
}
|
|
4075
3985
|
function checkId(data) {
|
|
4076
3986
|
const issues = requiredString(data, "id");
|
|
@@ -4106,11 +4016,11 @@ function checkSources(data) {
|
|
|
4106
4016
|
return [issue("wrong-type", "sources", `sources must be a list, not ${typeName(value)}`)];
|
|
4107
4017
|
}
|
|
4108
4018
|
return value.flatMap((entry, index) => {
|
|
4109
|
-
const
|
|
4019
|
+
const field2 = `sources[${index}]`;
|
|
4110
4020
|
if (typeof entry !== "string") {
|
|
4111
|
-
return [issue("wrong-type",
|
|
4021
|
+
return [issue("wrong-type", field2, `sources entries must be strings, and this one is ${typeName(entry)}`)];
|
|
4112
4022
|
}
|
|
4113
|
-
return entry.trim() === "" ? [issue("wrong-type",
|
|
4023
|
+
return entry.trim() === "" ? [issue("wrong-type", field2, "sources entries must name a source, and this one is blank")] : [];
|
|
4114
4024
|
});
|
|
4115
4025
|
}
|
|
4116
4026
|
function checkPromotedTo(data) {
|
|
@@ -4139,26 +4049,26 @@ function checkProjectId(data) {
|
|
|
4139
4049
|
}
|
|
4140
4050
|
return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
|
|
4141
4051
|
}
|
|
4142
|
-
function checkTimestamp(data,
|
|
4143
|
-
const issues = requiredString(data,
|
|
4052
|
+
function checkTimestamp(data, field2) {
|
|
4053
|
+
const issues = requiredString(data, field2);
|
|
4144
4054
|
if (issues.length > 0)
|
|
4145
4055
|
return issues;
|
|
4146
|
-
const value = data[
|
|
4147
|
-
return TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) ? [] : [issue("invalid-timestamp",
|
|
4056
|
+
const value = data[field2];
|
|
4057
|
+
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`)];
|
|
4148
4058
|
}
|
|
4149
4059
|
function isScalar(value) {
|
|
4150
4060
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
4151
4061
|
}
|
|
4152
4062
|
function checkEvidenceEntry(entry, index) {
|
|
4153
|
-
const
|
|
4063
|
+
const field2 = `evidence[${index}]`;
|
|
4154
4064
|
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
4155
|
-
return [issue("wrong-type",
|
|
4065
|
+
return [issue("wrong-type", field2, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
|
|
4156
4066
|
}
|
|
4157
4067
|
const entries = Object.entries(entry);
|
|
4158
4068
|
if (entries.length === 0) {
|
|
4159
|
-
return [issue("wrong-type",
|
|
4069
|
+
return [issue("wrong-type", field2, "evidence entries must say something, and this one is empty")];
|
|
4160
4070
|
}
|
|
4161
|
-
return entries.flatMap(([key, value]) => isScalar(value) ? [] : [issue("wrong-type", `${
|
|
4071
|
+
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)}`)]);
|
|
4162
4072
|
}
|
|
4163
4073
|
function checkEvidence(data) {
|
|
4164
4074
|
const value = data["evidence"];
|
|
@@ -4271,7 +4181,7 @@ function buildInstinct(data, body) {
|
|
|
4271
4181
|
updatedAt: data["updated_at"],
|
|
4272
4182
|
action,
|
|
4273
4183
|
cause: sectionText(body, CAUSE_HEADING) ?? "",
|
|
4274
|
-
actionHash:
|
|
4184
|
+
actionHash: actionHash(action)
|
|
4275
4185
|
};
|
|
4276
4186
|
}
|
|
4277
4187
|
function instinctFrontmatter(instinct) {
|
|
@@ -4318,11 +4228,11 @@ function writeInstinct(instinct, newline = `
|
|
|
4318
4228
|
}
|
|
4319
4229
|
|
|
4320
4230
|
// src/commands/instinct/instinct-records.ts
|
|
4321
|
-
function isRecordFile(
|
|
4322
|
-
return !
|
|
4231
|
+
function isRecordFile(name2) {
|
|
4232
|
+
return !name2.startsWith(".") && name2.endsWith(MARKDOWN_EXTENSION) && name2 !== MARKDOWN_EXTENSION;
|
|
4323
4233
|
}
|
|
4324
|
-
function recordId(
|
|
4325
|
-
return
|
|
4234
|
+
function recordId(name2) {
|
|
4235
|
+
return name2.slice(0, -MARKDOWN_EXTENSION.length);
|
|
4326
4236
|
}
|
|
4327
4237
|
function unreadableIssue(message) {
|
|
4328
4238
|
return { code: "missing-frontmatter", field: "file", message: `the file could not be read: ${message}` };
|
|
@@ -4346,7 +4256,7 @@ function isFile2(path) {
|
|
|
4346
4256
|
}
|
|
4347
4257
|
}
|
|
4348
4258
|
function recordFiles(dir) {
|
|
4349
|
-
return readdirSync4(dir).filter((
|
|
4259
|
+
return readdirSync4(dir).filter((name2) => isRecordFile(name2) && isFile2(join14(dir, name2))).sort((left, right) => left.localeCompare(right));
|
|
4350
4260
|
}
|
|
4351
4261
|
function readScope(scope, dir) {
|
|
4352
4262
|
if (!tierExists(dir))
|
|
@@ -4355,7 +4265,7 @@ function readScope(scope, dir) {
|
|
|
4355
4265
|
scope,
|
|
4356
4266
|
dir,
|
|
4357
4267
|
exists: true,
|
|
4358
|
-
records: recordFiles(dir).map((
|
|
4268
|
+
records: recordFiles(dir).map((name2) => readRecord(join14(dir, name2), scope, recordId(name2)))
|
|
4359
4269
|
};
|
|
4360
4270
|
}
|
|
4361
4271
|
function readScopes(seams) {
|
|
@@ -4367,9 +4277,9 @@ function allRecords(listings) {
|
|
|
4367
4277
|
function findRecords(listings, id) {
|
|
4368
4278
|
return allRecords(listings).filter((entry) => entry.id === id);
|
|
4369
4279
|
}
|
|
4370
|
-
function instinctProject(context,
|
|
4280
|
+
function instinctProject(context, name2) {
|
|
4371
4281
|
if (context.project === null)
|
|
4372
|
-
throw new Error(`${
|
|
4282
|
+
throw new Error(`${name2} runs inside a project, and was handed none`);
|
|
4373
4283
|
return context.project;
|
|
4374
4284
|
}
|
|
4375
4285
|
|
|
@@ -4406,17 +4316,17 @@ function toHeldRecord(instinct) {
|
|
|
4406
4316
|
function toHeldRecords(held) {
|
|
4407
4317
|
const actions = new Map;
|
|
4408
4318
|
for (const instinct of held) {
|
|
4409
|
-
const key =
|
|
4319
|
+
const key = triggerKey(instinct.trigger);
|
|
4410
4320
|
actions.set(key, (actions.get(key) ?? new Set).add(instinct.actionHash));
|
|
4411
4321
|
}
|
|
4412
4322
|
return held.map((instinct) => {
|
|
4413
4323
|
const record = toHeldRecord(instinct);
|
|
4414
|
-
return actions.get(
|
|
4324
|
+
return actions.get(triggerKey(instinct.trigger)).size > 1 ? { ...record, status: "flagged" } : record;
|
|
4415
4325
|
});
|
|
4416
4326
|
}
|
|
4417
4327
|
function earliestHeldMember(record, held) {
|
|
4418
|
-
const key =
|
|
4419
|
-
const members = held.filter((instinct) => instinct.actionHash === record.action_hash &&
|
|
4328
|
+
const key = triggerKey(record.trigger);
|
|
4329
|
+
const members = held.filter((instinct) => instinct.actionHash === record.action_hash && triggerKey(instinct.trigger) === key);
|
|
4420
4330
|
return [...members].sort(byAge)[0] ?? null;
|
|
4421
4331
|
}
|
|
4422
4332
|
function toHeldInstinct(record, held, fallback) {
|
|
@@ -4443,7 +4353,7 @@ function toHeldInstinct(record, held, fallback) {
|
|
|
4443
4353
|
updatedAt: record.updated_at,
|
|
4444
4354
|
action: record.action,
|
|
4445
4355
|
cause: description.cause,
|
|
4446
|
-
actionHash:
|
|
4356
|
+
actionHash: actionHash(record.action)
|
|
4447
4357
|
};
|
|
4448
4358
|
}
|
|
4449
4359
|
|
|
@@ -4525,7 +4435,7 @@ function recordProblem(record, index) {
|
|
|
4525
4435
|
if (problem !== null)
|
|
4526
4436
|
return problem;
|
|
4527
4437
|
const { action, action_hash: hash, description } = record;
|
|
4528
|
-
if (hash !==
|
|
4438
|
+
if (hash !== actionHash(action)) {
|
|
4529
4439
|
return `action_hash is ${describeValue(hash)} in ${at}, expected the sha256 of its trimmed, lower-cased action`;
|
|
4530
4440
|
}
|
|
4531
4441
|
return description === undefined ? null : fieldsProblem(description, DESCRIPTION_CHECKS, `${at}.description`);
|
|
@@ -4537,7 +4447,7 @@ function payloadProblem(payload) {
|
|
|
4537
4447
|
const { instincts } = payload;
|
|
4538
4448
|
return instincts.map(recordProblem).find((found) => found !== null) ?? null;
|
|
4539
4449
|
}
|
|
4540
|
-
function
|
|
4450
|
+
function flagProblem(value) {
|
|
4541
4451
|
return fieldsProblem(value, FLAG_CHECKS, "the flag");
|
|
4542
4452
|
}
|
|
4543
4453
|
function copyInstinct(record) {
|
|
@@ -4614,9 +4524,9 @@ function unflagged(records, flagged) {
|
|
|
4614
4524
|
return records.filter((record) => !flagged.has(record.id));
|
|
4615
4525
|
}
|
|
4616
4526
|
function userAdditions(userBlessed, projectHeld, flagged) {
|
|
4617
|
-
const heldTriggers = new Set(projectHeld.map((record) =>
|
|
4527
|
+
const heldTriggers = new Set(projectHeld.map((record) => triggerKey(record.trigger)));
|
|
4618
4528
|
const heldIds = new Set(projectHeld.map((record) => record.id));
|
|
4619
|
-
return unflagged(userBlessed, flagged).filter((record) => !heldTriggers.has(
|
|
4529
|
+
return unflagged(userBlessed, flagged).filter((record) => !heldTriggers.has(triggerKey(record.trigger)) && !heldIds.has(record.id));
|
|
4620
4530
|
}
|
|
4621
4531
|
function byAge2(left, right) {
|
|
4622
4532
|
if (left.created_at !== right.created_at) {
|
|
@@ -4627,8 +4537,8 @@ function byAge2(left, right) {
|
|
|
4627
4537
|
return left.id < right.id ? -1 : 1;
|
|
4628
4538
|
}
|
|
4629
4539
|
function payloadDescription(record, described) {
|
|
4630
|
-
const key =
|
|
4631
|
-
const members = described.filter((candidate) => candidate.description !== undefined && candidate.action_hash === record.action_hash &&
|
|
4540
|
+
const key = triggerKey(record.trigger);
|
|
4541
|
+
const members = described.filter((candidate) => candidate.description !== undefined && candidate.action_hash === record.action_hash && triggerKey(candidate.trigger) === key);
|
|
4632
4542
|
return [...members].sort(byAge2)[0]?.description;
|
|
4633
4543
|
}
|
|
4634
4544
|
function producedRecords(result) {
|
|
@@ -4654,35 +4564,35 @@ function heldChanges(dir, held, result, described) {
|
|
|
4654
4564
|
throw new Error(`${PUSH_REFUSAL}: ${file} has no held member and no payload description to be written with`);
|
|
4655
4565
|
}
|
|
4656
4566
|
const text = writeInstinct(instinct);
|
|
4657
|
-
const [
|
|
4658
|
-
if (
|
|
4659
|
-
throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${
|
|
4567
|
+
const [issue2] = parseInstinct(text).issues;
|
|
4568
|
+
if (issue2 !== undefined) {
|
|
4569
|
+
throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${issue2.field}: ${issue2.message}`);
|
|
4660
4570
|
}
|
|
4661
4571
|
const path = join15(dir, file);
|
|
4662
4572
|
if (held.unreadable.has(path)) {
|
|
4663
4573
|
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which could not be read`);
|
|
4664
4574
|
}
|
|
4665
4575
|
const before = heldAt.get(path);
|
|
4666
|
-
if (before !== undefined &&
|
|
4576
|
+
if (before !== undefined && triggerKey(before.trigger) !== triggerKey(record.trigger)) {
|
|
4667
4577
|
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which holds another trigger`);
|
|
4668
4578
|
}
|
|
4669
4579
|
kept.add(path);
|
|
4670
4580
|
if (before === undefined || writeInstinct(before) !== text)
|
|
4671
4581
|
writes.push({ path, text });
|
|
4672
4582
|
}
|
|
4673
|
-
const touched = new Set(result.decisions.map((decision) =>
|
|
4674
|
-
const removals = held.files.filter((file) => touched.has(
|
|
4583
|
+
const touched = new Set(result.decisions.map((decision) => triggerKey(decision.incoming.trigger)));
|
|
4584
|
+
const removals = held.files.filter((file) => touched.has(triggerKey(file.instinct.trigger)) && !kept.has(file.path)).map((file) => file.path);
|
|
4675
4585
|
return { writes, removals };
|
|
4676
4586
|
}
|
|
4677
4587
|
function pushLogLines(sourceId, described, result) {
|
|
4678
4588
|
return result.decisions.map((decision, index) => {
|
|
4679
|
-
const key =
|
|
4589
|
+
const key = triggerKey(decision.incoming.trigger);
|
|
4680
4590
|
const line = {
|
|
4681
4591
|
source_id: sourceId,
|
|
4682
4592
|
rule: decision.rule,
|
|
4683
4593
|
incoming: loggedIncoming(described[index]),
|
|
4684
4594
|
produced: decision.produced.map((record) => record.id),
|
|
4685
|
-
discarded: result.discarded.filter((record) =>
|
|
4595
|
+
discarded: result.discarded.filter((record) => triggerKey(record.trigger) === key).map((record) => record.id)
|
|
4686
4596
|
};
|
|
4687
4597
|
return JSON.stringify(line);
|
|
4688
4598
|
});
|
|
@@ -4707,7 +4617,7 @@ function createLocalLearning(options) {
|
|
|
4707
4617
|
for (const entry of records) {
|
|
4708
4618
|
if (entry.instinct === null) {
|
|
4709
4619
|
unreadable.add(entry.path);
|
|
4710
|
-
const problems = entry.issues.map((
|
|
4620
|
+
const problems = entry.issues.map((issue2) => `${issue2.field}: ${issue2.message}`).join("; ");
|
|
4711
4621
|
warn(`${PREFIX}: ${entry.path} is not a held instinct, so it was skipped: ${problems}`);
|
|
4712
4622
|
} else {
|
|
4713
4623
|
files.push({ path: entry.path, instinct: entry.instinct });
|
|
@@ -4724,7 +4634,7 @@ function createLocalLearning(options) {
|
|
|
4724
4634
|
async function flaggedIds() {
|
|
4725
4635
|
const lines = numberedLines(await readIfPresent(flagsPath));
|
|
4726
4636
|
return new Set(lines.map(([number, line]) => {
|
|
4727
|
-
const reading = readLine(line,
|
|
4637
|
+
const reading = readLine(line, flagProblem);
|
|
4728
4638
|
if (reading.ok)
|
|
4729
4639
|
return reading.value.id;
|
|
4730
4640
|
throw new Error(`${PREFIX}: line ${number} of ${flagsPath} is not a flag,` + ` and a bundle skipping it could hold an instinct it flags: ${reading.problem}`);
|
|
@@ -4738,7 +4648,7 @@ function createLocalLearning(options) {
|
|
|
4738
4648
|
}
|
|
4739
4649
|
const described = payload.instincts;
|
|
4740
4650
|
const held = heldSet();
|
|
4741
|
-
const result =
|
|
4651
|
+
const result = merge(toHeldRecords(held.files.map((file) => file.instinct)), { source_id: payload.source_id, instincts: described.map(copyInstinct) }, now());
|
|
4742
4652
|
const changes = heldChanges(instinctsDir, held, result, described);
|
|
4743
4653
|
const lines = pushLogLines(payload.source_id, described, result);
|
|
4744
4654
|
if (lines.length > 0) {
|
|
@@ -4756,13 +4666,13 @@ function createLocalLearning(options) {
|
|
|
4756
4666
|
pullBlessed: async () => {
|
|
4757
4667
|
const flagged = await flaggedIds();
|
|
4758
4668
|
const projectHeld = heldRecords(heldSet());
|
|
4759
|
-
const project =
|
|
4760
|
-
const user =
|
|
4669
|
+
const project = bless(unflagged(projectHeld, flagged), { minConfidence }).instincts;
|
|
4670
|
+
const user = bless(heldRecords(readHeld("user", userDir)), { minConfidence }).instincts;
|
|
4761
4671
|
return { version: now(), instincts: [...project, ...userAdditions(user, projectHeld, flagged)] };
|
|
4762
4672
|
},
|
|
4763
4673
|
flag: async (id, reason) => {
|
|
4764
4674
|
const flag = { id, reason, flagged_at: now() };
|
|
4765
|
-
const problem =
|
|
4675
|
+
const problem = flagProblem(flag);
|
|
4766
4676
|
if (problem !== null) {
|
|
4767
4677
|
throw new TypeError(`${PREFIX}: refused to write an invalid flag: ${problem}`);
|
|
4768
4678
|
}
|
|
@@ -4882,8 +4792,8 @@ function describeValue3(value) {
|
|
|
4882
4792
|
function messageOf2(error) {
|
|
4883
4793
|
return error instanceof Error ? error.message : String(error);
|
|
4884
4794
|
}
|
|
4885
|
-
function counted(
|
|
4886
|
-
return
|
|
4795
|
+
function counted(count2, noun) {
|
|
4796
|
+
return count2 === 1 ? `1 ${noun}` : `${String(count2)} ${noun}s`;
|
|
4887
4797
|
}
|
|
4888
4798
|
function valueAt(mapping, key) {
|
|
4889
4799
|
return Object.hasOwn(mapping, key) ? mapping[key] ?? null : null;
|
|
@@ -4901,39 +4811,39 @@ function verdictOf(value) {
|
|
|
4901
4811
|
const word = spelled.replace(VERDICT_BREAK, "-");
|
|
4902
4812
|
return SPEC_REVIEW_VERDICTS.includes(word) ? word : null;
|
|
4903
4813
|
}
|
|
4904
|
-
function issueAt(
|
|
4905
|
-
return { field, text };
|
|
4814
|
+
function issueAt(field2, text) {
|
|
4815
|
+
return { field: field2, text };
|
|
4906
4816
|
}
|
|
4907
|
-
function readBlocking(item,
|
|
4817
|
+
function readBlocking(item, field2, issues) {
|
|
4908
4818
|
const value = valueAt(item, "blocking");
|
|
4909
4819
|
if (typeof value === "boolean")
|
|
4910
4820
|
return value;
|
|
4911
|
-
const text = `${
|
|
4912
|
-
issues.push(issueAt(`${
|
|
4821
|
+
const text = `${field2}.blocking is ${describeValue3(value)}, not true or false; ` + "the gap is read as blocking";
|
|
4822
|
+
issues.push(issueAt(`${field2}.blocking`, text));
|
|
4913
4823
|
return true;
|
|
4914
4824
|
}
|
|
4915
|
-
function readGap(item,
|
|
4825
|
+
function readGap(item, field2, issues) {
|
|
4916
4826
|
if (!isMapping2(item)) {
|
|
4917
|
-
const text = `${
|
|
4918
|
-
issues.push(issueAt(
|
|
4827
|
+
const text = `${field2} is ${describeValue3(item)}, not a mapping of heading and what; dropped`;
|
|
4828
|
+
issues.push(issueAt(field2, text));
|
|
4919
4829
|
return null;
|
|
4920
4830
|
}
|
|
4921
4831
|
const what = stringAt(item, "what");
|
|
4922
4832
|
if (what === null) {
|
|
4923
|
-
const text = `${
|
|
4924
|
-
issues.push(issueAt(`${
|
|
4833
|
+
const text = `${field2}.what is ${describeValue3(valueAt(item, "what"))}, ` + "not a sentence; the gap is dropped";
|
|
4834
|
+
issues.push(issueAt(`${field2}.what`, text));
|
|
4925
4835
|
return null;
|
|
4926
4836
|
}
|
|
4927
4837
|
const heading = stringAt(item, "heading");
|
|
4928
4838
|
if (heading === null) {
|
|
4929
|
-
const text = `${
|
|
4930
|
-
issues.push(issueAt(`${
|
|
4839
|
+
const text = `${field2}.heading is ${describeValue3(valueAt(item, "heading"))}, ` + `not a heading; the gap is filed under ${REVIEW_HEADING}`;
|
|
4840
|
+
issues.push(issueAt(`${field2}.heading`, text));
|
|
4931
4841
|
}
|
|
4932
|
-
const blocking = readBlocking(item,
|
|
4842
|
+
const blocking = readBlocking(item, field2, issues);
|
|
4933
4843
|
const assumption = stringAt(item, "assumption");
|
|
4934
4844
|
if (!blocking && assumption === null) {
|
|
4935
|
-
const text = `${
|
|
4936
|
-
issues.push(issueAt(`${
|
|
4845
|
+
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";
|
|
4846
|
+
issues.push(issueAt(`${field2}.assumption`, text));
|
|
4937
4847
|
return { heading: heading ?? REVIEW_HEADING, what, blocking: true, assumption: null };
|
|
4938
4848
|
}
|
|
4939
4849
|
return { heading: heading ?? REVIEW_HEADING, what, blocking, assumption };
|
|
@@ -4982,7 +4892,7 @@ function judged(verdict, gaps, block, issues) {
|
|
|
4982
4892
|
};
|
|
4983
4893
|
}
|
|
4984
4894
|
function parseSpecReview(output) {
|
|
4985
|
-
const reviews =
|
|
4895
|
+
const reviews = readRafaBlocks(output).filter((found) => found.kind === SPEC_REVIEW_KIND);
|
|
4986
4896
|
const block = reviews.at(-1);
|
|
4987
4897
|
if (block === undefined) {
|
|
4988
4898
|
return unreadable("absent", null, "the session output holds no rafa:spec-review block");
|
|
@@ -5051,16 +4961,16 @@ class UndeclaredSpendError extends Error {
|
|
|
5051
4961
|
name = "UndeclaredSpendError";
|
|
5052
4962
|
}
|
|
5053
4963
|
function carriesFlag(flags, flag) {
|
|
5054
|
-
const
|
|
5055
|
-
const value = flags[
|
|
4964
|
+
const name2 = flag.replace(/^--/, "");
|
|
4965
|
+
const value = flags[name2];
|
|
5056
4966
|
if (value !== undefined && value !== false)
|
|
5057
4967
|
return true;
|
|
5058
|
-
return
|
|
4968
|
+
return name2.startsWith("no-") && flags[name2.slice(3)] === false;
|
|
5059
4969
|
}
|
|
5060
|
-
function spendRefusal(
|
|
5061
|
-
if (
|
|
4970
|
+
function spendRefusal(running2) {
|
|
4971
|
+
if (running2 === null)
|
|
5062
4972
|
return null;
|
|
5063
|
-
const { command, flags } =
|
|
4973
|
+
const { command, flags } = running2;
|
|
5064
4974
|
const spend = command.spends;
|
|
5065
4975
|
const named = `rafa ${commandSpelling(command)}`;
|
|
5066
4976
|
const remedy = "declare spends on the command to cover this run";
|
|
@@ -5218,7 +5128,8 @@ var TYPE_MEMBERS = {
|
|
|
5218
5128
|
adr: true,
|
|
5219
5129
|
chore: true,
|
|
5220
5130
|
"package-api": true,
|
|
5221
|
-
epic: true
|
|
5131
|
+
epic: true,
|
|
5132
|
+
spec: true
|
|
5222
5133
|
};
|
|
5223
5134
|
var PRIORITY_MEMBERS = {
|
|
5224
5135
|
urgent: true,
|
|
@@ -5246,7 +5157,8 @@ var GITHUB_LABELS = Object.freeze({
|
|
|
5246
5157
|
typePrefix: "type:",
|
|
5247
5158
|
priorityPrefix: "priority:",
|
|
5248
5159
|
needsTriage: "needs-triage",
|
|
5249
|
-
blockedByPrefix: "blocked-by:OPT-"
|
|
5160
|
+
blockedByPrefix: "blocked-by:OPT-",
|
|
5161
|
+
specBlocked: "spec:blocked"
|
|
5250
5162
|
});
|
|
5251
5163
|
var VIEW_FIELDS = "number,title,body,state,stateReason,labels,url";
|
|
5252
5164
|
var LIST_COMMAND = "gh issue list";
|
|
@@ -5305,8 +5217,8 @@ async function withDeadline(work, timeoutMs) {
|
|
|
5305
5217
|
if (timeoutMs === undefined)
|
|
5306
5218
|
return work;
|
|
5307
5219
|
let timer;
|
|
5308
|
-
const deadline = new Promise((
|
|
5309
|
-
timer = setTimeout(() =>
|
|
5220
|
+
const deadline = new Promise((resolve7) => {
|
|
5221
|
+
timer = setTimeout(() => resolve7(TIMED_OUT), timeoutMs);
|
|
5310
5222
|
});
|
|
5311
5223
|
try {
|
|
5312
5224
|
return await Promise.race([work, deadline]);
|
|
@@ -5333,8 +5245,8 @@ function isRepoName(value) {
|
|
|
5333
5245
|
function isLabelPart(value) {
|
|
5334
5246
|
return typeof value === "string" && !value.includes(",");
|
|
5335
5247
|
}
|
|
5336
|
-
function optionalFlag(
|
|
5337
|
-
return value === undefined ? [] : [
|
|
5248
|
+
function optionalFlag(name2, value) {
|
|
5249
|
+
return value === undefined ? [] : [name2, value];
|
|
5338
5250
|
}
|
|
5339
5251
|
function detailOf(result) {
|
|
5340
5252
|
return result.stderr.trim() || result.stdout.trim() || "it exited non-zero and wrote nothing";
|
|
@@ -5355,7 +5267,8 @@ var DRAFT_CHECKS = [
|
|
|
5355
5267
|
"blockedBy",
|
|
5356
5268
|
(value) => Array.isArray(value) && value.every((item) => Number.isSafeInteger(item) && item > 0),
|
|
5357
5269
|
"a list of positive whole numbers"
|
|
5358
|
-
]
|
|
5270
|
+
],
|
|
5271
|
+
["specBlocked", (value) => value === undefined || typeof value === "boolean", "absent or a boolean"]
|
|
5359
5272
|
];
|
|
5360
5273
|
function draftProblem(draft) {
|
|
5361
5274
|
if (!isMapping(draft))
|
|
@@ -5369,6 +5282,7 @@ function labelsFor(draft) {
|
|
|
5369
5282
|
`${GITHUB_LABELS.modulePrefix}${draft.module}`,
|
|
5370
5283
|
`${GITHUB_LABELS.typePrefix}${draft.type}`,
|
|
5371
5284
|
...draft.blockedBy.map((opt) => `${GITHUB_LABELS.blockedByPrefix}${opt}`),
|
|
5285
|
+
...draft.specBlocked === true ? [GITHUB_LABELS.specBlocked] : [],
|
|
5372
5286
|
priority
|
|
5373
5287
|
];
|
|
5374
5288
|
}
|
|
@@ -5397,7 +5311,7 @@ function labelNames(value) {
|
|
|
5397
5311
|
if (!Array.isArray(value))
|
|
5398
5312
|
return null;
|
|
5399
5313
|
const names = value.map((label) => isMapping(label) && typeof label["name"] === "string" ? label["name"] : null);
|
|
5400
|
-
return names.every((
|
|
5314
|
+
return names.every((name2) => name2 !== null) ? names : null;
|
|
5401
5315
|
}
|
|
5402
5316
|
function viewProblem(payload, number) {
|
|
5403
5317
|
if (!isMapping(payload))
|
|
@@ -5455,13 +5369,13 @@ function listedIssues(stdout) {
|
|
|
5455
5369
|
}
|
|
5456
5370
|
function listedOpenIssues(stdout) {
|
|
5457
5371
|
return listedRows(stdout).map((row, index) => {
|
|
5458
|
-
const
|
|
5372
|
+
const listed2 = listedRow(row, index);
|
|
5459
5373
|
const { title, body } = row;
|
|
5460
5374
|
if (typeof title !== "string")
|
|
5461
5375
|
throw rowRefusal(index, `with title ${describeValue(title)}, expected a string`);
|
|
5462
5376
|
if (typeof body !== "string")
|
|
5463
5377
|
throw rowRefusal(index, `with body ${describeValue(body)}, expected a string`);
|
|
5464
|
-
return { ...
|
|
5378
|
+
return { ...listed2, title, body };
|
|
5465
5379
|
});
|
|
5466
5380
|
}
|
|
5467
5381
|
function refOfRow(row) {
|
|
@@ -5519,9 +5433,9 @@ function createGithubTracker(options) {
|
|
|
5519
5433
|
return result.stdout;
|
|
5520
5434
|
}
|
|
5521
5435
|
async function ensureLabels(names) {
|
|
5522
|
-
const missing = [...new Set(names)].filter((
|
|
5523
|
-
for (const
|
|
5524
|
-
await run(["label", "create",
|
|
5436
|
+
const missing = [...new Set(names)].filter((name2) => !knownLabels.has(name2));
|
|
5437
|
+
for (const name2 of missing) {
|
|
5438
|
+
await run(["label", "create", name2, "--force"], `gh label create ${describeValue(name2)}`);
|
|
5525
5439
|
}
|
|
5526
5440
|
knownLabels = new Set([...knownLabels, ...missing]);
|
|
5527
5441
|
}
|
|
@@ -5664,6 +5578,12 @@ function oneOf2(members) {
|
|
|
5664
5578
|
function orNull(accepts) {
|
|
5665
5579
|
return (value) => value === null || accepts(value);
|
|
5666
5580
|
}
|
|
5581
|
+
function orAbsent(accepts) {
|
|
5582
|
+
return (value) => value === undefined || accepts(value);
|
|
5583
|
+
}
|
|
5584
|
+
function isBoolean(value) {
|
|
5585
|
+
return typeof value === "boolean";
|
|
5586
|
+
}
|
|
5667
5587
|
function listOf(accepts) {
|
|
5668
5588
|
return (value) => Array.isArray(value) && value.every(accepts);
|
|
5669
5589
|
}
|
|
@@ -5675,6 +5595,7 @@ var FIELD_CHECKS = {
|
|
|
5675
5595
|
project: [orNull(isString2), "a string or null"],
|
|
5676
5596
|
title: [isString2, "a string"],
|
|
5677
5597
|
blockedBy: [listOf(isFiniteNumber), "a list of numbers"],
|
|
5598
|
+
specBlocked: [orAbsent(isBoolean), "absent or a boolean"],
|
|
5678
5599
|
state: [oneOf2(ISSUE_STATES), `one of: ${ISSUE_STATES.join(", ")}`],
|
|
5679
5600
|
fallbackReason: [orNull(isString2), "a string or null"],
|
|
5680
5601
|
capturedAt: [isString2, "a string"],
|
|
@@ -5700,8 +5621,8 @@ function parseIssueNumber(text) {
|
|
|
5700
5621
|
const number = Number(text);
|
|
5701
5622
|
return Number.isSafeInteger(number) ? number : null;
|
|
5702
5623
|
}
|
|
5703
|
-
function issueNumberOfFile(
|
|
5704
|
-
return
|
|
5624
|
+
function issueNumberOfFile(name2) {
|
|
5625
|
+
return name2.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name2.slice(0, -ISSUE_EXTENSION.length)) : null;
|
|
5705
5626
|
}
|
|
5706
5627
|
function issuePath(issuesDir, number) {
|
|
5707
5628
|
return join16(issuesDir, `${number}${ISSUE_EXTENSION}`);
|
|
@@ -5730,6 +5651,7 @@ function renderLocalIssue(record) {
|
|
|
5730
5651
|
project: draft.project,
|
|
5731
5652
|
title: draft.title,
|
|
5732
5653
|
blockedBy: draft.blockedBy,
|
|
5654
|
+
...draft.specBlocked === undefined ? {} : { specBlocked: draft.specBlocked },
|
|
5733
5655
|
state: record.state,
|
|
5734
5656
|
fallbackReason: record.fallbackReason,
|
|
5735
5657
|
capturedAt: record.capturedAt,
|
|
@@ -5771,7 +5693,8 @@ function parseLocalIssue(contents, sourcePath) {
|
|
|
5771
5693
|
module: fields.module,
|
|
5772
5694
|
priority: fields.priority,
|
|
5773
5695
|
project: fields.project,
|
|
5774
|
-
blockedBy: fields.blockedBy
|
|
5696
|
+
blockedBy: fields.blockedBy,
|
|
5697
|
+
...fields.specBlocked === undefined ? {} : { specBlocked: fields.specBlocked }
|
|
5775
5698
|
},
|
|
5776
5699
|
fallbackReason: fields.fallbackReason,
|
|
5777
5700
|
capturedAt: fields.capturedAt,
|
|
@@ -5906,7 +5829,7 @@ function createLocalTracker(options) {
|
|
|
5906
5829
|
|
|
5907
5830
|
// src/adapters/registry.ts
|
|
5908
5831
|
var REFUSAL3 = "adapter registry";
|
|
5909
|
-
var
|
|
5832
|
+
var PORT_VERSIONS = Object.freeze({
|
|
5910
5833
|
tracker: 1,
|
|
5911
5834
|
store: 1,
|
|
5912
5835
|
learning: 1,
|
|
@@ -5914,7 +5837,7 @@ var PORT_VERSIONS2 = Object.freeze({
|
|
|
5914
5837
|
planner: 1,
|
|
5915
5838
|
sync: 1
|
|
5916
5839
|
});
|
|
5917
|
-
var SERVED_VERSIONS = new Map(Object.entries(
|
|
5840
|
+
var SERVED_VERSIONS = new Map(Object.entries(PORT_VERSIONS));
|
|
5918
5841
|
var PORT_TYPE_LIST = [...SERVED_VERSIONS.keys()].join(", ");
|
|
5919
5842
|
function checkPortType(port) {
|
|
5920
5843
|
if (!SERVED_VERSIONS.has(port)) {
|
|
@@ -5954,11 +5877,11 @@ function indexByPort(adapters) {
|
|
|
5954
5877
|
sync: only("sync")
|
|
5955
5878
|
};
|
|
5956
5879
|
}
|
|
5957
|
-
function
|
|
5880
|
+
function createAdapterRegistry(adapters = []) {
|
|
5958
5881
|
const held = adapters.reduce((accepted, adapter) => [...accepted, accept(adapter, accepted)], []);
|
|
5959
5882
|
const byPort = indexByPort(held);
|
|
5960
5883
|
const registry = {
|
|
5961
|
-
register: (adapter) =>
|
|
5884
|
+
register: (adapter) => createAdapterRegistry([...held, adapter]),
|
|
5962
5885
|
find: (port, kind) => SERVED_VERSIONS.has(port) ? byPort[port].get(kind) : undefined,
|
|
5963
5886
|
resolve: (port, kind) => {
|
|
5964
5887
|
checkPortType(port);
|
|
@@ -5966,8 +5889,8 @@ function createAdapterRegistry2(adapters = []) {
|
|
|
5966
5889
|
if (adapter !== undefined)
|
|
5967
5890
|
return adapter;
|
|
5968
5891
|
const registered = [...byPort[port].keys()];
|
|
5969
|
-
const
|
|
5970
|
-
throw new Error(`${REFUSAL3}: no ${port} adapter is registered as ${describeValue(kind)};` + ` registered: ${
|
|
5892
|
+
const listed2 = registered.length === 0 ? "none" : registered.join(", ");
|
|
5893
|
+
throw new Error(`${REFUSAL3}: no ${port} adapter is registered as ${describeValue(kind)};` + ` registered: ${listed2}`);
|
|
5971
5894
|
},
|
|
5972
5895
|
kinds: (port) => {
|
|
5973
5896
|
checkPortType(port);
|
|
@@ -5977,38 +5900,38 @@ function createAdapterRegistry2(adapters = []) {
|
|
|
5977
5900
|
return Object.freeze(registry);
|
|
5978
5901
|
}
|
|
5979
5902
|
var STORE_OPENERS = {
|
|
5980
|
-
sqlite:
|
|
5981
|
-
ndjson:
|
|
5903
|
+
sqlite: openSqliteStore,
|
|
5904
|
+
ndjson: openNdjsonStore
|
|
5982
5905
|
};
|
|
5983
5906
|
var CORE_ADAPTERS = [
|
|
5984
|
-
...
|
|
5907
|
+
...STORE_BACKENDS.map((backend) => ({
|
|
5985
5908
|
port: "store",
|
|
5986
5909
|
kind: backend,
|
|
5987
|
-
portVersion:
|
|
5910
|
+
portVersion: PORT_VERSIONS.store,
|
|
5988
5911
|
create: ({ repoRoot }) => STORE_OPENERS[backend](repoRoot)
|
|
5989
5912
|
})),
|
|
5990
5913
|
{
|
|
5991
5914
|
port: "output",
|
|
5992
5915
|
kind: "text",
|
|
5993
|
-
portVersion:
|
|
5916
|
+
portVersion: PORT_VERSIONS.output,
|
|
5994
5917
|
create: ({ stream = process.stdout, verbosity = 0 }) => createTextOutput({ verbosity, stream })
|
|
5995
5918
|
},
|
|
5996
5919
|
{
|
|
5997
5920
|
port: "output",
|
|
5998
5921
|
kind: "json",
|
|
5999
|
-
portVersion:
|
|
5922
|
+
portVersion: PORT_VERSIONS.output,
|
|
6000
5923
|
create: ({ stream = process.stdout }) => createJsonOutput({ stream })
|
|
6001
5924
|
},
|
|
6002
5925
|
{
|
|
6003
5926
|
port: "output",
|
|
6004
5927
|
kind: "events",
|
|
6005
|
-
portVersion:
|
|
5928
|
+
portVersion: PORT_VERSIONS.output,
|
|
6006
5929
|
create: ({ stream = process.stdout }) => createEventsOutput({ stream })
|
|
6007
5930
|
},
|
|
6008
5931
|
{
|
|
6009
5932
|
port: "tracker",
|
|
6010
5933
|
kind: "local",
|
|
6011
|
-
portVersion:
|
|
5934
|
+
portVersion: PORT_VERSIONS.tracker,
|
|
6012
5935
|
create: ({ repoRoot, fallbackReason = null }) => createLocalTracker({
|
|
6013
5936
|
issuesDir: localIssuesDir(repoRoot),
|
|
6014
5937
|
fallbackReason
|
|
@@ -6017,17 +5940,17 @@ var CORE_ADAPTERS = [
|
|
|
6017
5940
|
{
|
|
6018
5941
|
port: "tracker",
|
|
6019
5942
|
kind: "github",
|
|
6020
|
-
portVersion:
|
|
5943
|
+
portVersion: PORT_VERSIONS.tracker,
|
|
6021
5944
|
create: ({ repoRoot, gh }) => createGithubTracker({ gh: gh ?? createGhRunner({ cwd: repoRoot }) })
|
|
6022
5945
|
},
|
|
6023
5946
|
{
|
|
6024
5947
|
port: "learning",
|
|
6025
5948
|
kind: "local",
|
|
6026
|
-
portVersion:
|
|
5949
|
+
portVersion: PORT_VERSIONS.learning,
|
|
6027
5950
|
create: ({
|
|
6028
5951
|
repoRoot,
|
|
6029
5952
|
home = homedir(),
|
|
6030
|
-
learningBlessMinConfidence =
|
|
5953
|
+
learningBlessMinConfidence = CONFIG_DEFAULTS.learningBlessMinConfidence
|
|
6031
5954
|
}) => createLocalLearning({
|
|
6032
5955
|
instinctsDir: localInstinctsDir(repoRoot),
|
|
6033
5956
|
home,
|
|
@@ -6037,7 +5960,7 @@ var CORE_ADAPTERS = [
|
|
|
6037
5960
|
{
|
|
6038
5961
|
port: "planner",
|
|
6039
5962
|
kind: "claude",
|
|
6040
|
-
portVersion:
|
|
5963
|
+
portVersion: PORT_VERSIONS.planner,
|
|
6041
5964
|
create: ({ repoRoot, settingSources, planPrompt, planDir, claude }) => {
|
|
6042
5965
|
if (!Array.isArray(settingSources)) {
|
|
6043
5966
|
throw new TypeError(`${REFUSAL3}: planner/claude has settingSources ${describeValue(settingSources)} in its context,` + " expected a list of setting sources");
|
|
@@ -6054,26 +5977,26 @@ var CORE_ADAPTERS = [
|
|
|
6054
5977
|
{
|
|
6055
5978
|
port: "sync",
|
|
6056
5979
|
kind: "local",
|
|
6057
|
-
portVersion:
|
|
5980
|
+
portVersion: PORT_VERSIONS.sync,
|
|
6058
5981
|
create: () => createLocalSync()
|
|
6059
5982
|
},
|
|
6060
5983
|
{
|
|
6061
5984
|
port: "sync",
|
|
6062
5985
|
kind: "file",
|
|
6063
|
-
portVersion:
|
|
5986
|
+
portVersion: PORT_VERSIONS.sync,
|
|
6064
5987
|
create: ({ repoRoot, store }) => {
|
|
6065
|
-
if (!
|
|
6066
|
-
throw new TypeError(`${REFUSAL3}: sync/file has store ${describeValue(store)} in its context,` + ` expected one of: ${
|
|
5988
|
+
if (!STORE_BACKENDS.includes(store)) {
|
|
5989
|
+
throw new TypeError(`${REFUSAL3}: sync/file has store ${describeValue(store)} in its context,` + ` expected one of: ${STORE_BACKENDS.join(", ")}`);
|
|
6067
5990
|
}
|
|
6068
5991
|
return createFileSync({ repoRoot, backend: store });
|
|
6069
5992
|
}
|
|
6070
5993
|
}
|
|
6071
5994
|
];
|
|
6072
|
-
var
|
|
5995
|
+
var CORE_ADAPTER_REGISTRY = createAdapterRegistry(CORE_ADAPTERS);
|
|
6073
5996
|
|
|
6074
5997
|
// src/board/trust.ts
|
|
6075
5998
|
var REPO_PATH = "{owner}/{repo}";
|
|
6076
|
-
var
|
|
5999
|
+
var TRUSTED_PERMISSIONS = ["admin", "maintain", "write"];
|
|
6077
6000
|
var TRUST_REFUSAL_EXIT = 2;
|
|
6078
6001
|
var REMEDY = Object.freeze({
|
|
6079
6002
|
issue: "a member must open the spec",
|
|
@@ -6085,7 +6008,7 @@ function failureDetail(reading) {
|
|
|
6085
6008
|
return detail === "" ? UNREPORTED : detail;
|
|
6086
6009
|
}
|
|
6087
6010
|
function isTrustedPermission(value) {
|
|
6088
|
-
return
|
|
6011
|
+
return TRUSTED_PERMISSIONS.some((permission) => permission === value);
|
|
6089
6012
|
}
|
|
6090
6013
|
function stringOrNull(value) {
|
|
6091
6014
|
return typeof value === "string" ? value : null;
|
|
@@ -6135,8 +6058,8 @@ async function readAuthorTrust(options) {
|
|
|
6135
6058
|
};
|
|
6136
6059
|
}
|
|
6137
6060
|
const wanted = login.toLowerCase();
|
|
6138
|
-
const
|
|
6139
|
-
if (
|
|
6061
|
+
const listed2 = trustedAuthors.some((author) => author.toLowerCase() === wanted);
|
|
6062
|
+
if (listed2) {
|
|
6140
6063
|
return { login, trusted: true, source: "allow-list", refusal: null, permission: null };
|
|
6141
6064
|
}
|
|
6142
6065
|
const permission = await permissions(login);
|
|
@@ -6192,14 +6115,14 @@ import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, rmSync as rmSy
|
|
|
6192
6115
|
import { tmpdir as tmpdir4 } from "os";
|
|
6193
6116
|
import { join as join17 } from "path";
|
|
6194
6117
|
import { Database as Database7, SQLiteError as SQLiteError3 } from "bun:sqlite";
|
|
6195
|
-
var
|
|
6196
|
-
var
|
|
6118
|
+
var WIRE_FORMAT = "rafa-effort-wire";
|
|
6119
|
+
var WIRE_VERSION = 1;
|
|
6197
6120
|
|
|
6198
|
-
class
|
|
6121
|
+
class WireFormatError extends Error {
|
|
6199
6122
|
name = "WireFormatError";
|
|
6200
6123
|
}
|
|
6201
6124
|
|
|
6202
|
-
class
|
|
6125
|
+
class WireExportRefusal extends Error {
|
|
6203
6126
|
name = "WireExportRefusal";
|
|
6204
6127
|
}
|
|
6205
6128
|
var MERGED_TABLES = Object.entries(MERGE_RULES).filter(([, rule]) => rule.scope === "merged").map(([table]) => table);
|
|
@@ -6210,17 +6133,17 @@ function isRecord4(value) {
|
|
|
6210
6133
|
function isCount(value) {
|
|
6211
6134
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
6212
6135
|
}
|
|
6213
|
-
function isMergedTable(
|
|
6214
|
-
return MERGED_TABLES.includes(
|
|
6136
|
+
function isMergedTable(name2) {
|
|
6137
|
+
return MERGED_TABLES.includes(name2);
|
|
6215
6138
|
}
|
|
6216
6139
|
function checkCursor(value) {
|
|
6217
6140
|
if (!isRecord4(value))
|
|
6218
|
-
throw new
|
|
6141
|
+
throw new WireFormatError("wire: cursor is not an object");
|
|
6219
6142
|
const entries = Object.entries(value).map(([table, seq]) => {
|
|
6220
6143
|
if (!isMergedTable(table))
|
|
6221
|
-
throw new
|
|
6144
|
+
throw new WireFormatError(`wire: cursor names ${JSON.stringify(table)}, which is no merged table`);
|
|
6222
6145
|
if (!isCount(seq))
|
|
6223
|
-
throw new
|
|
6146
|
+
throw new WireFormatError(`wire: cursor for ${table} is ${JSON.stringify(seq)}, expected a whole number of 0 or more`);
|
|
6224
6147
|
return [table, seq];
|
|
6225
6148
|
});
|
|
6226
6149
|
return Object.freeze(Object.fromEntries(entries));
|
|
@@ -6228,67 +6151,67 @@ function checkCursor(value) {
|
|
|
6228
6151
|
function checkRow(table, value, index) {
|
|
6229
6152
|
const where = `wire: ${table} row ${String(index)}`;
|
|
6230
6153
|
if (!isRecord4(value))
|
|
6231
|
-
throw new
|
|
6154
|
+
throw new WireFormatError(`${where} is not an object`);
|
|
6232
6155
|
for (const [column, cell] of Object.entries(value)) {
|
|
6233
6156
|
const carried = cell === null || typeof cell === "string" || typeof cell === "number" && Number.isFinite(cell);
|
|
6234
6157
|
if (!carried)
|
|
6235
|
-
throw new
|
|
6158
|
+
throw new WireFormatError(`${where} holds ${JSON.stringify(cell) ?? typeof cell} in ${column}, expected a string, a number or null`);
|
|
6236
6159
|
}
|
|
6237
6160
|
const seq = value.seq;
|
|
6238
6161
|
if (!isCount(seq) || seq === 0)
|
|
6239
|
-
throw new
|
|
6162
|
+
throw new WireFormatError(`${where} has seq ${JSON.stringify(seq) ?? "undefined"}, expected a whole number above 0`);
|
|
6240
6163
|
return Object.freeze({ ...value });
|
|
6241
6164
|
}
|
|
6242
6165
|
function checkMigrations(value) {
|
|
6243
6166
|
if (!Array.isArray(value))
|
|
6244
|
-
throw new
|
|
6167
|
+
throw new WireFormatError("wire: migrations is not a list");
|
|
6245
6168
|
const ids = value.map((id) => {
|
|
6246
6169
|
if (typeof id !== "string" || id === "")
|
|
6247
|
-
throw new
|
|
6170
|
+
throw new WireFormatError(`wire: migration id ${JSON.stringify(id)} is not a name`);
|
|
6248
6171
|
return id;
|
|
6249
6172
|
});
|
|
6250
6173
|
const twice = ids.filter((id, index) => ids.indexOf(id) !== index);
|
|
6251
6174
|
if (twice.length > 0)
|
|
6252
|
-
throw new
|
|
6175
|
+
throw new WireFormatError(`wire: migrations name ${twice.join(", ")} more than once`);
|
|
6253
6176
|
return Object.freeze(ids);
|
|
6254
6177
|
}
|
|
6255
6178
|
function checkTables(value) {
|
|
6256
6179
|
if (!isRecord4(value))
|
|
6257
|
-
throw new
|
|
6180
|
+
throw new WireFormatError("wire: tables is not an object");
|
|
6258
6181
|
const entries = Object.entries(value).map(([table, rows]) => {
|
|
6259
6182
|
if (!isMergedTable(table))
|
|
6260
|
-
throw new
|
|
6183
|
+
throw new WireFormatError(`wire: tables names ${JSON.stringify(table)}, which is no merged table`);
|
|
6261
6184
|
if (!Array.isArray(rows))
|
|
6262
|
-
throw new
|
|
6185
|
+
throw new WireFormatError(`wire: tables.${table} is not a list`);
|
|
6263
6186
|
return [table, Object.freeze(rows.map((row, index) => checkRow(table, row, index)))];
|
|
6264
6187
|
});
|
|
6265
6188
|
return Object.freeze(Object.fromEntries(entries));
|
|
6266
6189
|
}
|
|
6267
6190
|
function checkPayload(value) {
|
|
6268
6191
|
if (!isRecord4(value))
|
|
6269
|
-
throw new
|
|
6270
|
-
if (value.format !==
|
|
6271
|
-
throw new
|
|
6272
|
-
if (value.version !==
|
|
6273
|
-
throw new
|
|
6192
|
+
throw new WireFormatError("wire: the payload is not an object");
|
|
6193
|
+
if (value.format !== WIRE_FORMAT)
|
|
6194
|
+
throw new WireFormatError(`wire: format is ${JSON.stringify(value.format) ?? "undefined"}, expected ${JSON.stringify(WIRE_FORMAT)}`);
|
|
6195
|
+
if (value.version !== WIRE_VERSION)
|
|
6196
|
+
throw new WireFormatError(`wire: version is ${JSON.stringify(value.version) ?? "undefined"}; this rafa reads version ${String(WIRE_VERSION)}`);
|
|
6274
6197
|
return Object.freeze({
|
|
6275
|
-
format:
|
|
6276
|
-
version:
|
|
6198
|
+
format: WIRE_FORMAT,
|
|
6199
|
+
version: WIRE_VERSION,
|
|
6277
6200
|
migrations: checkMigrations(value.migrations),
|
|
6278
6201
|
tables: checkTables(value.tables),
|
|
6279
6202
|
cursor: checkCursor(value.cursor)
|
|
6280
6203
|
});
|
|
6281
6204
|
}
|
|
6282
|
-
function
|
|
6205
|
+
function encodeWirePayload(payload) {
|
|
6283
6206
|
return JSON.stringify(payload);
|
|
6284
6207
|
}
|
|
6285
|
-
function
|
|
6208
|
+
function decodeWirePayload(text) {
|
|
6286
6209
|
let parsed;
|
|
6287
6210
|
try {
|
|
6288
6211
|
parsed = JSON.parse(text);
|
|
6289
6212
|
} catch (error) {
|
|
6290
6213
|
const reason = error instanceof Error ? error.message : String(error);
|
|
6291
|
-
throw new
|
|
6214
|
+
throw new WireFormatError(`wire: the payload is not JSON (${reason})`);
|
|
6292
6215
|
}
|
|
6293
6216
|
return checkPayload(parsed);
|
|
6294
6217
|
}
|
|
@@ -6299,17 +6222,17 @@ function readLoggedIds(db) {
|
|
|
6299
6222
|
return db.query(`SELECT id FROM ${MIGRATION_LOG_TABLE} ORDER BY seq`).all().map(({ id }) => id);
|
|
6300
6223
|
}
|
|
6301
6224
|
function columnsOf(db, table) {
|
|
6302
|
-
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name }) =>
|
|
6225
|
+
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name: name2 }) => name2);
|
|
6303
6226
|
}
|
|
6304
6227
|
function wireValue(value, where) {
|
|
6305
6228
|
if (typeof value === "bigint") {
|
|
6306
6229
|
const number = Number(value);
|
|
6307
6230
|
if (Number.isSafeInteger(number))
|
|
6308
6231
|
return number;
|
|
6309
|
-
throw new
|
|
6232
|
+
throw new WireExportRefusal(`wire: ${where} holds ${String(value)}, past the whole numbers JSON carries exactly`);
|
|
6310
6233
|
}
|
|
6311
6234
|
if (value instanceof Uint8Array)
|
|
6312
|
-
throw new
|
|
6235
|
+
throw new WireExportRefusal(`wire: ${where} holds a BLOB, which the wire does not carry`);
|
|
6313
6236
|
return value;
|
|
6314
6237
|
}
|
|
6315
6238
|
function readRows(db, table, columns, after, path) {
|
|
@@ -6322,7 +6245,7 @@ function readRows(db, table, columns, after, path) {
|
|
|
6322
6245
|
function readPayload(db, path, cursor) {
|
|
6323
6246
|
const migrations = readLoggedIds(db);
|
|
6324
6247
|
if (migrations === null) {
|
|
6325
|
-
throw new
|
|
6248
|
+
throw new WireExportRefusal(`wire: ${path} has no migration log; any rafa command that writes the store brings it forward`);
|
|
6326
6249
|
}
|
|
6327
6250
|
const tables = {};
|
|
6328
6251
|
const next = { ...cursor };
|
|
@@ -6332,7 +6255,7 @@ function readPayload(db, path, cursor) {
|
|
|
6332
6255
|
continue;
|
|
6333
6256
|
const missing = ORIGIN_COLUMNS.filter((column) => !columns.includes(column));
|
|
6334
6257
|
if (missing.length > 0) {
|
|
6335
|
-
throw new
|
|
6258
|
+
throw new WireExportRefusal(`wire: ${path} holds no ${missing.map((column) => `${table}.${column}`).join(", ")}, so its rows carry no origin pair`);
|
|
6336
6259
|
}
|
|
6337
6260
|
const rows = readRows(db, table, columns, cursor[table] ?? 0, path);
|
|
6338
6261
|
tables[table] = Object.freeze(rows);
|
|
@@ -6340,19 +6263,19 @@ function readPayload(db, path, cursor) {
|
|
|
6340
6263
|
next[table] = last === undefined ? cursor[table] ?? 0 : Number(last.seq);
|
|
6341
6264
|
}
|
|
6342
6265
|
return Object.freeze({
|
|
6343
|
-
format:
|
|
6344
|
-
version:
|
|
6266
|
+
format: WIRE_FORMAT,
|
|
6267
|
+
version: WIRE_VERSION,
|
|
6345
6268
|
migrations: Object.freeze([...migrations]),
|
|
6346
6269
|
tables: Object.freeze(tables),
|
|
6347
6270
|
cursor: Object.freeze(next)
|
|
6348
6271
|
});
|
|
6349
6272
|
}
|
|
6350
|
-
function
|
|
6273
|
+
function exportWirePayload(options) {
|
|
6351
6274
|
const { path } = options;
|
|
6352
6275
|
const cursor = checkCursor(options.cursor ?? {});
|
|
6353
6276
|
guardTestProcess(path);
|
|
6354
6277
|
if (!existsSync14(path))
|
|
6355
|
-
throw new
|
|
6278
|
+
throw new WireExportRefusal(`wire: ${path} is not there`);
|
|
6356
6279
|
const db = new Database7(path, { readonly: true, safeIntegers: true });
|
|
6357
6280
|
try {
|
|
6358
6281
|
db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
|
|
@@ -6365,28 +6288,28 @@ function namedMigrations(ids, catalogue) {
|
|
|
6365
6288
|
const known = new Set(catalogue.map(({ id }) => id));
|
|
6366
6289
|
const unknown = ids.filter((id) => !known.has(id));
|
|
6367
6290
|
if (unknown.length > 0) {
|
|
6368
|
-
throw new
|
|
6291
|
+
throw new WireFormatError(`wire: the payload's store holds migration ${unknown.join(", ")}, which this rafa does not know; update rafa to take its rows`);
|
|
6369
6292
|
}
|
|
6370
6293
|
return catalogue.filter(({ id }) => ids.includes(id));
|
|
6371
6294
|
}
|
|
6372
6295
|
function insertRows(db, table, rows) {
|
|
6373
6296
|
const columns = columnsOf(db, table);
|
|
6374
6297
|
if (columns.length === 0)
|
|
6375
|
-
throw new
|
|
6298
|
+
throw new WireFormatError(`wire: the payload carries ${table}, which its migrations do not make`);
|
|
6376
6299
|
const statement = db.query(`INSERT INTO ${quoted(table)} (${columns.map(quoted).join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`);
|
|
6377
6300
|
const expected = [...columns].sort().join(",");
|
|
6378
6301
|
rows.forEach((row, index) => {
|
|
6379
6302
|
const where = `wire: ${table} row ${String(index)}`;
|
|
6380
6303
|
const held = Object.keys(row);
|
|
6381
6304
|
if ([...held].sort().join(",") !== expected) {
|
|
6382
|
-
throw new
|
|
6305
|
+
throw new WireFormatError(`${where} holds columns ${held.join(", ")}; its migrations make ${columns.join(", ")}`);
|
|
6383
6306
|
}
|
|
6384
6307
|
try {
|
|
6385
6308
|
statement.run(...columns.map((column) => row[column] ?? null));
|
|
6386
6309
|
} catch (error) {
|
|
6387
6310
|
if (!(error instanceof SQLiteError3))
|
|
6388
6311
|
throw error;
|
|
6389
|
-
throw new
|
|
6312
|
+
throw new WireFormatError(`${where} (seq ${String(row.seq)}) is refused by the table: ${error.message}`);
|
|
6390
6313
|
}
|
|
6391
6314
|
});
|
|
6392
6315
|
}
|
|
@@ -6408,7 +6331,7 @@ function buildStore(path, payload, options) {
|
|
|
6408
6331
|
db.close();
|
|
6409
6332
|
}
|
|
6410
6333
|
}
|
|
6411
|
-
function
|
|
6334
|
+
function materialiseWirePayload(payload, options = {}) {
|
|
6412
6335
|
const checked = checkPayload(payload);
|
|
6413
6336
|
const directory = mkdtempSync2(join17(options.tempDir ?? tmpdir4(), "rafa-wire-"));
|
|
6414
6337
|
const release = () => rmSync8(directory, { recursive: true, force: true });
|
|
@@ -6431,13 +6354,13 @@ function describeValue4(value) {
|
|
|
6431
6354
|
return "an object";
|
|
6432
6355
|
return String(value);
|
|
6433
6356
|
}
|
|
6434
|
-
function
|
|
6435
|
-
const adapter =
|
|
6357
|
+
function selectEffortStore(repoRoot, config) {
|
|
6358
|
+
const adapter = CORE_ADAPTER_REGISTRY.find("store", config.store);
|
|
6436
6359
|
if (adapter === undefined) {
|
|
6437
|
-
const expected =
|
|
6360
|
+
const expected = CORE_ADAPTER_REGISTRY.kinds("store").join(", ");
|
|
6438
6361
|
throw new TypeError(`effort store: store is ${describeValue4(config.store)},` + ` expected one of: ${expected}`);
|
|
6439
6362
|
}
|
|
6440
6363
|
return adapter.create({ repoRoot });
|
|
6441
6364
|
}
|
|
6442
6365
|
|
|
6443
|
-
export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess,
|
|
6366
|
+
export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess, EFFORT_KEY_PROJECTIONS, openNdjsonStore, name, version, RAFA_VERSION, versionLine, createGitRunner, gitSaid, NOT_A_REPOSITORY, mainCheckoutOf, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, POSITION_FILE, positionFilePath, readPositionFile, writePositionFile, positionAt, rehome, hop, goHome, HOP_FILE, hopFilePath, readHopRecord, writeHopRecord, staleAgainst, SessionRecordError, isSessionId, sessionPhase, SessionConflictError, SessionStateError, runsDir, sessionFilePath, errorCode, isPidAlive, readSessions, readSession, beginSession, updateSession, DEVELOPMENT_NEXT_STEP, DEVELOPMENT_MARK, DevelopmentBuildRefusedError, refuseUnownedDevelopmentWrite, LEGACY_GATE_OPEN, LEGACY_GATE_CLOSED, SQLITE_MIGRATIONS, sqliteCatalogue, planSchema, SchemaRefusedError, readStoreSchema, writeNames, ACROSS_STORES_ORDER, STAMPED_COLUMNS, stampedValues, activeStoreSettings, normalizeRemote, gitRemoteUrl, readStoreMeta, SQLITE_STORE_FILE_NAME, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, EffortCopyRefusal, EffortCopyFailure, copyEffortStore, RebuildRefusal, readConfigFile, loadConfig, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, stubOfPlanFile, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, fileStamp, keepsIdLine, fix_schema_default, minutesBetween, readCommitLog, UnionSchemaMismatch, migrateStore, MOVE_TO_SQLITE, MergeRefusal, mergeStore, createFileSync, SyncModuleMissing, selectSync, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, EVENT_PREFIX, padKind, oneLine, sessionSpawnEnv, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, spawnClaude, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, TRUSTED_PERMISSIONS, createGhPermissions, readAuthorTrust, ghBoardTrust, readBoardTrust, requireTrustedBoardAuthor, trustRefusalClause, requireTrustedAuthor, WIRE_FORMAT, WIRE_VERSION, WireFormatError, WireExportRefusal, encodeWirePayload, decodeWirePayload, exportWirePayload, materialiseWirePayload, selectEffortStore };
|