@open-tomato/rafa 0.35.0 → 0.37.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/README.md +5 -4
- 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 +13025 -11058
- package/dist/describe-5hqfwbjc.js +14 -0
- package/dist/effort/store/index.js +36 -33
- package/dist/{index-ckqfhynj.js → index-1mxv55ec.js} +588 -531
- package/dist/{index-dvrq9nfb.js → index-35ze56rz.js} +4991 -4683
- package/dist/index-3mzw3kzh.js +129 -0
- package/dist/{index-41vzahew.js → index-7bb0fdb7.js} +33 -31
- package/dist/{index-se13pvag.js → index-b16va6he.js} +3645 -2403
- package/dist/{index-hgcvjke7.js → index-d6d0942q.js} +17 -17
- 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-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.37.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";
|
|
@@ -5210,6 +5120,62 @@ function createClaudePlanner(options) {
|
|
|
5210
5120
|
return Object.freeze({ create });
|
|
5211
5121
|
}
|
|
5212
5122
|
|
|
5123
|
+
// src/adapters/tracker/github-edit.ts
|
|
5124
|
+
var PREFIX2 = "github tracker";
|
|
5125
|
+
var EDITABLE_FIELDS = "title,body,state,labels,author";
|
|
5126
|
+
function viewedEditable(payload, labelNames) {
|
|
5127
|
+
if (!isMapping(payload))
|
|
5128
|
+
return `${describeValue(payload)}, expected a mapping`;
|
|
5129
|
+
const { title, body, state, author } = payload;
|
|
5130
|
+
if (typeof title !== "string")
|
|
5131
|
+
return `title ${describeValue(title)}, expected a string`;
|
|
5132
|
+
if (typeof body !== "string")
|
|
5133
|
+
return `body ${describeValue(body)}, expected a string`;
|
|
5134
|
+
if (state !== "OPEN" && state !== "CLOSED")
|
|
5135
|
+
return `state ${describeValue(state)}, expected "OPEN" or "CLOSED"`;
|
|
5136
|
+
const labels = labelNames(payload["labels"]);
|
|
5137
|
+
if (labels === null)
|
|
5138
|
+
return "labels that are not a list of named labels";
|
|
5139
|
+
const login = isMapping(author) ? author["login"] : undefined;
|
|
5140
|
+
if (typeof login !== "string")
|
|
5141
|
+
return `author ${describeValue(author)}, expected a mapping holding a string login`;
|
|
5142
|
+
return { title, body, open: state === "OPEN", labels, author: login };
|
|
5143
|
+
}
|
|
5144
|
+
function editProblem(change) {
|
|
5145
|
+
if (!isMapping(change))
|
|
5146
|
+
return `the change is ${describeValue(change)}, expected a mapping`;
|
|
5147
|
+
const { body, title } = change;
|
|
5148
|
+
if (body === undefined && title === undefined)
|
|
5149
|
+
return "the change names neither a body nor a title";
|
|
5150
|
+
if (body !== undefined && typeof body !== "string")
|
|
5151
|
+
return `body is ${describeValue(body)}, expected a string`;
|
|
5152
|
+
return title !== undefined && typeof title !== "string" ? `title is ${describeValue(title)}, expected a string` : null;
|
|
5153
|
+
}
|
|
5154
|
+
function optionalFlag(name2, value) {
|
|
5155
|
+
return value === undefined ? [] : [name2, value];
|
|
5156
|
+
}
|
|
5157
|
+
function createGithubEditMembers(readers) {
|
|
5158
|
+
const { run, issueNumberOf, parseJson, labelNames } = readers;
|
|
5159
|
+
const editable = async (ref) => {
|
|
5160
|
+
const number = issueNumberOf(ref);
|
|
5161
|
+
const command = `gh issue view ${number}`;
|
|
5162
|
+
const stdout = await run(["issue", "view", number, ...optionalFlag("--repo", ref.repo), "--json", EDITABLE_FIELDS], command);
|
|
5163
|
+
const viewed = viewedEditable(parseJson(stdout, command), labelNames);
|
|
5164
|
+
if (typeof viewed === "string")
|
|
5165
|
+
throw new Error(`${PREFIX2}: ${command} answered ${viewed}`);
|
|
5166
|
+
return { ref, ...viewed };
|
|
5167
|
+
};
|
|
5168
|
+
const edit = async (ref, change) => {
|
|
5169
|
+
const number = issueNumberOf(ref);
|
|
5170
|
+
const problem = editProblem(change);
|
|
5171
|
+
if (problem !== null)
|
|
5172
|
+
throw new TypeError(`${PREFIX2}: refused an edit of issue #${number}: ${problem}`);
|
|
5173
|
+
const bodyFlag = change.body === undefined ? [] : ["--body-file", "-"];
|
|
5174
|
+
await run(["issue", "edit", number, ...optionalFlag("--repo", ref.repo), ...optionalFlag("--title", change.title), ...bodyFlag], `gh issue edit ${number}`, change.body);
|
|
5175
|
+
};
|
|
5176
|
+
return Object.freeze({ editable, edit });
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5213
5179
|
// src/adapters/tracker/issue-values.ts
|
|
5214
5180
|
var TYPE_MEMBERS = {
|
|
5215
5181
|
code: true,
|
|
@@ -5218,7 +5184,8 @@ var TYPE_MEMBERS = {
|
|
|
5218
5184
|
adr: true,
|
|
5219
5185
|
chore: true,
|
|
5220
5186
|
"package-api": true,
|
|
5221
|
-
epic: true
|
|
5187
|
+
epic: true,
|
|
5188
|
+
spec: true
|
|
5222
5189
|
};
|
|
5223
5190
|
var PRIORITY_MEMBERS = {
|
|
5224
5191
|
urgent: true,
|
|
@@ -5240,13 +5207,14 @@ var ISSUE_PRIORITIES = Object.freeze(Object.keys(PRIORITY_MEMBERS));
|
|
|
5240
5207
|
var ISSUE_STATES = Object.freeze(Object.keys(STATE_MEMBERS));
|
|
5241
5208
|
|
|
5242
5209
|
// src/adapters/tracker/github.ts
|
|
5243
|
-
var
|
|
5210
|
+
var PREFIX3 = "github tracker";
|
|
5244
5211
|
var GITHUB_LABELS = Object.freeze({
|
|
5245
5212
|
modulePrefix: "module:",
|
|
5246
5213
|
typePrefix: "type:",
|
|
5247
5214
|
priorityPrefix: "priority:",
|
|
5248
5215
|
needsTriage: "needs-triage",
|
|
5249
|
-
blockedByPrefix: "blocked-by:OPT-"
|
|
5216
|
+
blockedByPrefix: "blocked-by:OPT-",
|
|
5217
|
+
specBlocked: "spec:blocked"
|
|
5250
5218
|
});
|
|
5251
5219
|
var VIEW_FIELDS = "number,title,body,state,stateReason,labels,url";
|
|
5252
5220
|
var LIST_COMMAND = "gh issue list";
|
|
@@ -5269,7 +5237,7 @@ var REST_STATES = {
|
|
|
5269
5237
|
};
|
|
5270
5238
|
function createGhRunner(options) {
|
|
5271
5239
|
const { cwd, command = "gh", env, timeoutMs } = options;
|
|
5272
|
-
return async (args) => {
|
|
5240
|
+
return async (args, stdin) => {
|
|
5273
5241
|
const executable = env === undefined ? command : Bun.which(command, { PATH: env.PATH ?? "", cwd });
|
|
5274
5242
|
if (executable === null) {
|
|
5275
5243
|
return { ok: false, stdout: "", stderr: notOnPath(command, cwd, env?.PATH) };
|
|
@@ -5278,7 +5246,7 @@ function createGhRunner(options) {
|
|
|
5278
5246
|
const proc = Bun.spawn([executable, ...args], {
|
|
5279
5247
|
cwd,
|
|
5280
5248
|
...env === undefined ? {} : { env: { ...env } },
|
|
5281
|
-
stdin: "ignore",
|
|
5249
|
+
stdin: stdin === undefined ? "ignore" : new TextEncoder().encode(stdin),
|
|
5282
5250
|
stdout: "pipe",
|
|
5283
5251
|
stderr: "pipe"
|
|
5284
5252
|
});
|
|
@@ -5305,8 +5273,8 @@ async function withDeadline(work, timeoutMs) {
|
|
|
5305
5273
|
if (timeoutMs === undefined)
|
|
5306
5274
|
return work;
|
|
5307
5275
|
let timer;
|
|
5308
|
-
const deadline = new Promise((
|
|
5309
|
-
timer = setTimeout(() =>
|
|
5276
|
+
const deadline = new Promise((resolve7) => {
|
|
5277
|
+
timer = setTimeout(() => resolve7(TIMED_OUT), timeoutMs);
|
|
5310
5278
|
});
|
|
5311
5279
|
try {
|
|
5312
5280
|
return await Promise.race([work, deadline]);
|
|
@@ -5333,8 +5301,8 @@ function isRepoName(value) {
|
|
|
5333
5301
|
function isLabelPart(value) {
|
|
5334
5302
|
return typeof value === "string" && !value.includes(",");
|
|
5335
5303
|
}
|
|
5336
|
-
function
|
|
5337
|
-
return value === undefined ? [] : [
|
|
5304
|
+
function optionalFlag2(name2, value) {
|
|
5305
|
+
return value === undefined ? [] : [name2, value];
|
|
5338
5306
|
}
|
|
5339
5307
|
function detailOf(result) {
|
|
5340
5308
|
return result.stderr.trim() || result.stdout.trim() || "it exited non-zero and wrote nothing";
|
|
@@ -5355,7 +5323,8 @@ var DRAFT_CHECKS = [
|
|
|
5355
5323
|
"blockedBy",
|
|
5356
5324
|
(value) => Array.isArray(value) && value.every((item) => Number.isSafeInteger(item) && item > 0),
|
|
5357
5325
|
"a list of positive whole numbers"
|
|
5358
|
-
]
|
|
5326
|
+
],
|
|
5327
|
+
["specBlocked", (value) => value === undefined || typeof value === "boolean", "absent or a boolean"]
|
|
5359
5328
|
];
|
|
5360
5329
|
function draftProblem(draft) {
|
|
5361
5330
|
if (!isMapping(draft))
|
|
@@ -5369,18 +5338,19 @@ function labelsFor(draft) {
|
|
|
5369
5338
|
`${GITHUB_LABELS.modulePrefix}${draft.module}`,
|
|
5370
5339
|
`${GITHUB_LABELS.typePrefix}${draft.type}`,
|
|
5371
5340
|
...draft.blockedBy.map((opt) => `${GITHUB_LABELS.blockedByPrefix}${opt}`),
|
|
5341
|
+
...draft.specBlocked === true ? [GITHUB_LABELS.specBlocked] : [],
|
|
5372
5342
|
priority
|
|
5373
5343
|
];
|
|
5374
5344
|
}
|
|
5375
5345
|
function issueNumberOfRef(ref) {
|
|
5376
5346
|
if (ref.kind !== "github") {
|
|
5377
|
-
throw new Error(`${
|
|
5347
|
+
throw new Error(`${PREFIX3}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads github refs only`);
|
|
5378
5348
|
}
|
|
5379
5349
|
if (!isIssueNumber3(ref.externalId)) {
|
|
5380
|
-
throw new Error(`${
|
|
5350
|
+
throw new Error(`${PREFIX3}: externalId ${describeValue(ref.externalId)} is not a GitHub issue number,` + " expected a positive whole number with no leading zero");
|
|
5381
5351
|
}
|
|
5382
5352
|
if (ref.repo !== undefined && !isRepoName(ref.repo)) {
|
|
5383
|
-
throw new Error(`${
|
|
5353
|
+
throw new Error(`${PREFIX3}: repo ${describeValue(ref.repo)} is not a repository, expected owner/name`);
|
|
5384
5354
|
}
|
|
5385
5355
|
return ref.externalId;
|
|
5386
5356
|
}
|
|
@@ -5388,7 +5358,7 @@ function parseJson(stdout, command) {
|
|
|
5388
5358
|
try {
|
|
5389
5359
|
return JSON.parse(stdout);
|
|
5390
5360
|
} catch (error) {
|
|
5391
|
-
throw new Error(`${
|
|
5361
|
+
throw new Error(`${PREFIX3}: ${command} wrote output that is not JSON: ${messageOf(error)}`, {
|
|
5392
5362
|
cause: error
|
|
5393
5363
|
});
|
|
5394
5364
|
}
|
|
@@ -5397,7 +5367,7 @@ function labelNames(value) {
|
|
|
5397
5367
|
if (!Array.isArray(value))
|
|
5398
5368
|
return null;
|
|
5399
5369
|
const names = value.map((label) => isMapping(label) && typeof label["name"] === "string" ? label["name"] : null);
|
|
5400
|
-
return names.every((
|
|
5370
|
+
return names.every((name2) => name2 !== null) ? names : null;
|
|
5401
5371
|
}
|
|
5402
5372
|
function viewProblem(payload, number) {
|
|
5403
5373
|
if (!isMapping(payload))
|
|
@@ -5422,19 +5392,19 @@ function viewedIssue(stdout, number) {
|
|
|
5422
5392
|
const payload = parseJson(stdout, command);
|
|
5423
5393
|
const problem = viewProblem(payload, number);
|
|
5424
5394
|
if (problem !== null)
|
|
5425
|
-
throw new Error(`${
|
|
5395
|
+
throw new Error(`${PREFIX3}: ${command} answered ${problem}`);
|
|
5426
5396
|
const checked = payload;
|
|
5427
5397
|
return { ...checked, labels: labelNames(checked.labels) ?? [] };
|
|
5428
5398
|
}
|
|
5429
5399
|
function listedRows(stdout) {
|
|
5430
5400
|
const payload = parseJson(stdout, LIST_COMMAND);
|
|
5431
5401
|
if (!Array.isArray(payload)) {
|
|
5432
|
-
throw new Error(`${
|
|
5402
|
+
throw new Error(`${PREFIX3}: ${LIST_COMMAND} answered ${describeValue(payload)}, expected a list`);
|
|
5433
5403
|
}
|
|
5434
5404
|
return payload;
|
|
5435
5405
|
}
|
|
5436
5406
|
function rowRefusal(index, problem) {
|
|
5437
|
-
return new Error(`${
|
|
5407
|
+
return new Error(`${PREFIX3}: ${LIST_COMMAND} answered row ${index} ${problem}`);
|
|
5438
5408
|
}
|
|
5439
5409
|
function listedRow(row, index) {
|
|
5440
5410
|
if (!isMapping(row))
|
|
@@ -5455,13 +5425,13 @@ function listedIssues(stdout) {
|
|
|
5455
5425
|
}
|
|
5456
5426
|
function listedOpenIssues(stdout) {
|
|
5457
5427
|
return listedRows(stdout).map((row, index) => {
|
|
5458
|
-
const
|
|
5428
|
+
const listed2 = listedRow(row, index);
|
|
5459
5429
|
const { title, body } = row;
|
|
5460
5430
|
if (typeof title !== "string")
|
|
5461
5431
|
throw rowRefusal(index, `with title ${describeValue(title)}, expected a string`);
|
|
5462
5432
|
if (typeof body !== "string")
|
|
5463
5433
|
throw rowRefusal(index, `with body ${describeValue(body)}, expected a string`);
|
|
5464
|
-
return { ...
|
|
5434
|
+
return { ...listed2, title, body };
|
|
5465
5435
|
});
|
|
5466
5436
|
}
|
|
5467
5437
|
function refOfRow(row) {
|
|
@@ -5507,21 +5477,21 @@ function readBackState(state) {
|
|
|
5507
5477
|
function stateQueryRefusal(state) {
|
|
5508
5478
|
const bucket = REST_STATES[state];
|
|
5509
5479
|
const siblings = ISSUE_STATES.filter((other) => other !== state && REST_STATES[other] === bucket);
|
|
5510
|
-
return `${
|
|
5480
|
+
return `${PREFIX3}: find cannot narrow by state ${describeValue(state)}: GitHub holds an issue open or` + ` closed, and ${state} shares ${bucket} with ${siblings.join(", ")}, so a result would include` + " those too. Search without a state, then read each issue with get.";
|
|
5511
5481
|
}
|
|
5512
5482
|
function createGithubTracker(options) {
|
|
5513
5483
|
const { gh } = options;
|
|
5514
5484
|
let knownLabels = new Set;
|
|
5515
|
-
async function run(args, command) {
|
|
5516
|
-
const result = await gh(args);
|
|
5485
|
+
async function run(args, command, stdin) {
|
|
5486
|
+
const result = await gh(args, stdin);
|
|
5517
5487
|
if (!result.ok)
|
|
5518
|
-
throw new Error(`${
|
|
5488
|
+
throw new Error(`${PREFIX3}: ${command} failed: ${detailOf(result)}`);
|
|
5519
5489
|
return result.stdout;
|
|
5520
5490
|
}
|
|
5521
5491
|
async function ensureLabels(names) {
|
|
5522
|
-
const missing = [...new Set(names)].filter((
|
|
5523
|
-
for (const
|
|
5524
|
-
await run(["label", "create",
|
|
5492
|
+
const missing = [...new Set(names)].filter((name2) => !knownLabels.has(name2));
|
|
5493
|
+
for (const name2 of missing) {
|
|
5494
|
+
await run(["label", "create", name2, "--force"], `gh label create ${describeValue(name2)}`);
|
|
5525
5495
|
}
|
|
5526
5496
|
knownLabels = new Set([...knownLabels, ...missing]);
|
|
5527
5497
|
}
|
|
@@ -5548,7 +5518,7 @@ function createGithubTracker(options) {
|
|
|
5548
5518
|
create: async (draft) => {
|
|
5549
5519
|
const problem = draftProblem(draft);
|
|
5550
5520
|
if (problem !== null)
|
|
5551
|
-
throw new TypeError(`${
|
|
5521
|
+
throw new TypeError(`${PREFIX3}: refused to file an invalid draft: ${problem}`);
|
|
5552
5522
|
const labels = labelsFor(draft);
|
|
5553
5523
|
await ensureLabels(labels);
|
|
5554
5524
|
const stdout = await run([
|
|
@@ -5564,20 +5534,20 @@ function createGithubTracker(options) {
|
|
|
5564
5534
|
`).at(-1) ?? "";
|
|
5565
5535
|
const number = CREATED_ISSUE_URL.exec(url)?.[1];
|
|
5566
5536
|
if (number === undefined) {
|
|
5567
|
-
throw new Error(`${
|
|
5537
|
+
throw new Error(`${PREFIX3}: gh issue create exited 0 and printed no issue URL, so the issue may exist` + ` unrecorded; it wrote ${describeValue(stdout)}`);
|
|
5568
5538
|
}
|
|
5569
5539
|
return { opt: draft.opt, kind: "github", externalId: number, url, module: draft.module };
|
|
5570
5540
|
},
|
|
5571
5541
|
get: async (ref) => {
|
|
5572
5542
|
const number = issueNumberOfRef(ref);
|
|
5573
|
-
const stdout = await run(["issue", "view", number, ...
|
|
5543
|
+
const stdout = await run(["issue", "view", number, ...optionalFlag2("--repo", ref.repo), "--json", VIEW_FIELDS], `gh issue view ${number}`);
|
|
5574
5544
|
return issueOf(ref, viewedIssue(stdout, number));
|
|
5575
5545
|
},
|
|
5576
5546
|
find: async (query) => {
|
|
5577
5547
|
if (query.state !== undefined)
|
|
5578
5548
|
throw new Error(stateQueryRefusal(query.state));
|
|
5579
5549
|
if (query.module !== undefined && !isLabelPart(query.module)) {
|
|
5580
|
-
throw new TypeError(`${
|
|
5550
|
+
throw new TypeError(`${PREFIX3}: find refused module ${describeValue(query.module)}, expected a string holding no comma`);
|
|
5581
5551
|
}
|
|
5582
5552
|
const moduleLabel = query.module === undefined ? undefined : `${GITHUB_LABELS.modulePrefix}${query.module}`;
|
|
5583
5553
|
const typeLabel = query.type === undefined ? undefined : `${GITHUB_LABELS.typePrefix}${query.type}`;
|
|
@@ -5590,15 +5560,15 @@ function createGithubTracker(options) {
|
|
|
5590
5560
|
LIST_FIELDS,
|
|
5591
5561
|
"--limit",
|
|
5592
5562
|
String(query.limit ?? DEFAULT_FIND_LIMIT),
|
|
5593
|
-
...
|
|
5594
|
-
...
|
|
5595
|
-
...
|
|
5563
|
+
...optionalFlag2("--label", moduleLabel),
|
|
5564
|
+
...optionalFlag2("--label", typeLabel),
|
|
5565
|
+
...optionalFlag2("--search", query.text)
|
|
5596
5566
|
], LIST_COMMAND);
|
|
5597
5567
|
return listedIssues(stdout).map(refOfRow);
|
|
5598
5568
|
},
|
|
5599
5569
|
openIssues: async (type) => {
|
|
5600
5570
|
if (!isOneOf(ISSUE_TYPES, type)) {
|
|
5601
|
-
throw new TypeError(`${
|
|
5571
|
+
throw new TypeError(`${PREFIX3}: openIssues refused type ${describeValue(type)}, expected one of: ${ISSUE_TYPES.join(", ")}`);
|
|
5602
5572
|
}
|
|
5603
5573
|
const stdout = await run([
|
|
5604
5574
|
"issue",
|
|
@@ -5617,21 +5587,21 @@ function createGithubTracker(options) {
|
|
|
5617
5587
|
comment: async (ref, body) => {
|
|
5618
5588
|
const number = issueNumberOfRef(ref);
|
|
5619
5589
|
if (typeof body !== "string") {
|
|
5620
|
-
throw new TypeError(`${
|
|
5590
|
+
throw new TypeError(`${PREFIX3}: refused a comment body ${describeValue(body)}, expected a string`);
|
|
5621
5591
|
}
|
|
5622
|
-
await run(["issue", "comment", number, ...
|
|
5592
|
+
await run(["issue", "comment", number, ...optionalFlag2("--repo", ref.repo), "--body", body], `gh issue comment ${number}`);
|
|
5623
5593
|
},
|
|
5624
5594
|
transition: async (ref, state) => {
|
|
5625
5595
|
const number = issueNumberOfRef(ref);
|
|
5626
5596
|
if (!isOneOf(ISSUE_STATES, state)) {
|
|
5627
|
-
throw new TypeError(`${
|
|
5597
|
+
throw new TypeError(`${PREFIX3}: refused state ${describeValue(state)}, expected one of: ${ISSUE_STATES.join(", ")}`);
|
|
5628
5598
|
}
|
|
5629
5599
|
if (REST_STATES[state] === "closed") {
|
|
5630
5600
|
const path = `repos/${ref.repo ?? "{owner}/{repo}"}/issues/${number}`;
|
|
5631
5601
|
const reason = state === "cancelled" ? "not_planned" : "completed";
|
|
5632
5602
|
await run(["api", path, "-X", "PATCH", "-f", "state=closed", "-f", `state_reason=${reason}`], `gh api PATCH ${path}`);
|
|
5633
5603
|
} else {
|
|
5634
|
-
await run(["issue", "reopen", number, ...
|
|
5604
|
+
await run(["issue", "reopen", number, ...optionalFlag2("--repo", ref.repo)], `gh issue reopen ${number}`);
|
|
5635
5605
|
}
|
|
5636
5606
|
const readBack = readBackState(state);
|
|
5637
5607
|
if (readBack === state)
|
|
@@ -5639,7 +5609,8 @@ function createGithubTracker(options) {
|
|
|
5639
5609
|
return {
|
|
5640
5610
|
warning: `issue #${number} is now ${REST_STATES[state]}, but GitHub Issues without a board` + ` holds no ${state} state, so get reads it back as ${readBack}`
|
|
5641
5611
|
};
|
|
5642
|
-
}
|
|
5612
|
+
},
|
|
5613
|
+
...createGithubEditMembers({ run, issueNumberOf: issueNumberOfRef, parseJson, labelNames })
|
|
5643
5614
|
};
|
|
5644
5615
|
return Object.freeze(tracker);
|
|
5645
5616
|
}
|
|
@@ -5647,7 +5618,7 @@ function createGithubTracker(options) {
|
|
|
5647
5618
|
// src/adapters/tracker/local.ts
|
|
5648
5619
|
import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
5649
5620
|
import { join as join16 } from "path";
|
|
5650
|
-
var
|
|
5621
|
+
var PREFIX4 = "local tracker";
|
|
5651
5622
|
var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
5652
5623
|
var ISSUE_NUMBER2 = /^[1-9]\d*$/;
|
|
5653
5624
|
var CLOSED_STATES = new Set(["done", "released", "cancelled"]);
|
|
@@ -5664,6 +5635,12 @@ function oneOf2(members) {
|
|
|
5664
5635
|
function orNull(accepts) {
|
|
5665
5636
|
return (value) => value === null || accepts(value);
|
|
5666
5637
|
}
|
|
5638
|
+
function orAbsent(accepts) {
|
|
5639
|
+
return (value) => value === undefined || accepts(value);
|
|
5640
|
+
}
|
|
5641
|
+
function isBoolean(value) {
|
|
5642
|
+
return typeof value === "boolean";
|
|
5643
|
+
}
|
|
5667
5644
|
function listOf(accepts) {
|
|
5668
5645
|
return (value) => Array.isArray(value) && value.every(accepts);
|
|
5669
5646
|
}
|
|
@@ -5675,6 +5652,7 @@ var FIELD_CHECKS = {
|
|
|
5675
5652
|
project: [orNull(isString2), "a string or null"],
|
|
5676
5653
|
title: [isString2, "a string"],
|
|
5677
5654
|
blockedBy: [listOf(isFiniteNumber), "a list of numbers"],
|
|
5655
|
+
specBlocked: [orAbsent(isBoolean), "absent or a boolean"],
|
|
5678
5656
|
state: [oneOf2(ISSUE_STATES), `one of: ${ISSUE_STATES.join(", ")}`],
|
|
5679
5657
|
fallbackReason: [orNull(isString2), "a string or null"],
|
|
5680
5658
|
capturedAt: [isString2, "a string"],
|
|
@@ -5700,8 +5678,8 @@ function parseIssueNumber(text) {
|
|
|
5700
5678
|
const number = Number(text);
|
|
5701
5679
|
return Number.isSafeInteger(number) ? number : null;
|
|
5702
5680
|
}
|
|
5703
|
-
function issueNumberOfFile(
|
|
5704
|
-
return
|
|
5681
|
+
function issueNumberOfFile(name2) {
|
|
5682
|
+
return name2.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name2.slice(0, -ISSUE_EXTENSION.length)) : null;
|
|
5705
5683
|
}
|
|
5706
5684
|
function issuePath(issuesDir, number) {
|
|
5707
5685
|
return join16(issuesDir, `${number}${ISSUE_EXTENSION}`);
|
|
@@ -5730,6 +5708,7 @@ function renderLocalIssue(record) {
|
|
|
5730
5708
|
project: draft.project,
|
|
5731
5709
|
title: draft.title,
|
|
5732
5710
|
blockedBy: draft.blockedBy,
|
|
5711
|
+
...draft.specBlocked === undefined ? {} : { specBlocked: draft.specBlocked },
|
|
5733
5712
|
state: record.state,
|
|
5734
5713
|
fallbackReason: record.fallbackReason,
|
|
5735
5714
|
capturedAt: record.capturedAt,
|
|
@@ -5737,7 +5716,7 @@ function renderLocalIssue(record) {
|
|
|
5737
5716
|
};
|
|
5738
5717
|
const problem = frontmatterProblem(frontmatter) ?? (typeof draft.body === "string" ? null : `body is ${describeValue(draft.body)}, expected a string`);
|
|
5739
5718
|
if (problem !== null) {
|
|
5740
|
-
throw new TypeError(`${
|
|
5719
|
+
throw new TypeError(`${PREFIX4}: refused to write an invalid issue: ${problem}`);
|
|
5741
5720
|
}
|
|
5742
5721
|
return `---
|
|
5743
5722
|
${Bun.YAML.stringify(frontmatter, null, 2)}
|
|
@@ -5748,18 +5727,18 @@ function parseLocalIssue(contents, sourcePath) {
|
|
|
5748
5727
|
const at = sourcePath === undefined ? "" : ` at ${sourcePath}`;
|
|
5749
5728
|
const match = FRONTMATTER.exec(contents);
|
|
5750
5729
|
if (match === null) {
|
|
5751
|
-
throw new Error(`${
|
|
5730
|
+
throw new Error(`${PREFIX4}: the issue${at} has no YAML frontmatter block`);
|
|
5752
5731
|
}
|
|
5753
5732
|
const [, block = "", body = ""] = match;
|
|
5754
5733
|
let meta;
|
|
5755
5734
|
try {
|
|
5756
5735
|
meta = Bun.YAML.parse(block);
|
|
5757
5736
|
} catch (error) {
|
|
5758
|
-
throw new Error(`${
|
|
5737
|
+
throw new Error(`${PREFIX4}: invalid issue${at}: ${messageOf(error)}`, { cause: error });
|
|
5759
5738
|
}
|
|
5760
5739
|
const problem = frontmatterProblem(meta);
|
|
5761
5740
|
if (problem !== null) {
|
|
5762
|
-
throw new Error(`${
|
|
5741
|
+
throw new Error(`${PREFIX4}: invalid issue${at}: ${problem}`);
|
|
5763
5742
|
}
|
|
5764
5743
|
const fields = meta;
|
|
5765
5744
|
return {
|
|
@@ -5771,7 +5750,8 @@ function parseLocalIssue(contents, sourcePath) {
|
|
|
5771
5750
|
module: fields.module,
|
|
5772
5751
|
priority: fields.priority,
|
|
5773
5752
|
project: fields.project,
|
|
5774
|
-
blockedBy: fields.blockedBy
|
|
5753
|
+
blockedBy: fields.blockedBy,
|
|
5754
|
+
...fields.specBlocked === undefined ? {} : { specBlocked: fields.specBlocked }
|
|
5775
5755
|
},
|
|
5776
5756
|
fallbackReason: fields.fallbackReason,
|
|
5777
5757
|
capturedAt: fields.capturedAt,
|
|
@@ -5790,7 +5770,7 @@ async function listLocalIssues(issuesDir, warn = warnThroughActiveOutput2, reade
|
|
|
5790
5770
|
try {
|
|
5791
5771
|
contents = await readFile3(path, "utf8");
|
|
5792
5772
|
} catch (error) {
|
|
5793
|
-
throw new Error(`${
|
|
5773
|
+
throw new Error(`${PREFIX4}: could not read ${path}: ${messageOf(error)}`, { cause: error });
|
|
5794
5774
|
}
|
|
5795
5775
|
return { number, path, record: parseLocalIssue(contents, path) };
|
|
5796
5776
|
}));
|
|
@@ -5819,11 +5799,11 @@ function refFor(number, opt) {
|
|
|
5819
5799
|
}
|
|
5820
5800
|
function issueNumberOfRef2(ref) {
|
|
5821
5801
|
if (ref.kind !== "local") {
|
|
5822
|
-
throw new Error(`${
|
|
5802
|
+
throw new Error(`${PREFIX4}: refused a ref of kind ${describeValue(ref.kind)}; this tracker reads local refs only`);
|
|
5823
5803
|
}
|
|
5824
5804
|
const number = typeof ref.externalId === "string" ? parseIssueNumber(ref.externalId) : null;
|
|
5825
5805
|
if (number === null) {
|
|
5826
|
-
throw new Error(`${
|
|
5806
|
+
throw new Error(`${PREFIX4}: externalId ${describeValue(ref.externalId)} is not a local issue number,` + " expected a positive whole number with no leading zero");
|
|
5827
5807
|
}
|
|
5828
5808
|
return number;
|
|
5829
5809
|
}
|
|
@@ -5838,7 +5818,7 @@ function createLocalTracker(options) {
|
|
|
5838
5818
|
try {
|
|
5839
5819
|
contents = await readFile3(path, "utf8");
|
|
5840
5820
|
} catch (cause) {
|
|
5841
|
-
throw new Error(`${
|
|
5821
|
+
throw new Error(`${PREFIX4}: no issue ${number} under ${issuesDir}`, { cause });
|
|
5842
5822
|
}
|
|
5843
5823
|
return { number, record: parseLocalIssue(contents, path) };
|
|
5844
5824
|
}
|
|
@@ -5873,7 +5853,7 @@ function createLocalTracker(options) {
|
|
|
5873
5853
|
throw error;
|
|
5874
5854
|
}
|
|
5875
5855
|
}
|
|
5876
|
-
throw new Error(`${
|
|
5856
|
+
throw new Error(`${PREFIX4}: no issue number is left under ${issuesDir}`);
|
|
5877
5857
|
},
|
|
5878
5858
|
get: async (ref) => {
|
|
5879
5859
|
const { record } = await read(ref);
|
|
@@ -5899,6 +5879,29 @@ function createLocalTracker(options) {
|
|
|
5899
5879
|
const { number, record } = await read(ref);
|
|
5900
5880
|
await rewrite(number, { ...record, state });
|
|
5901
5881
|
return {};
|
|
5882
|
+
},
|
|
5883
|
+
editable: async (ref) => {
|
|
5884
|
+
const { number, record } = await read(ref);
|
|
5885
|
+
return {
|
|
5886
|
+
ref: refFor(number, record.draft.opt),
|
|
5887
|
+
title: record.draft.title,
|
|
5888
|
+
body: record.draft.body,
|
|
5889
|
+
open: !CLOSED_STATES.has(record.state),
|
|
5890
|
+
labels: [],
|
|
5891
|
+
author: ""
|
|
5892
|
+
};
|
|
5893
|
+
},
|
|
5894
|
+
edit: async (ref, change) => {
|
|
5895
|
+
if (change.body === undefined && change.title === undefined) {
|
|
5896
|
+
throw new TypeError(`${PREFIX4}: refused an edit naming neither a body nor a title`);
|
|
5897
|
+
}
|
|
5898
|
+
const { number, record } = await read(ref);
|
|
5899
|
+
const draft = {
|
|
5900
|
+
...record.draft,
|
|
5901
|
+
...change.body === undefined ? {} : { body: change.body },
|
|
5902
|
+
...change.title === undefined ? {} : { title: change.title }
|
|
5903
|
+
};
|
|
5904
|
+
await rewrite(number, { ...record, draft });
|
|
5902
5905
|
}
|
|
5903
5906
|
};
|
|
5904
5907
|
return Object.freeze(tracker);
|
|
@@ -5906,7 +5909,7 @@ function createLocalTracker(options) {
|
|
|
5906
5909
|
|
|
5907
5910
|
// src/adapters/registry.ts
|
|
5908
5911
|
var REFUSAL3 = "adapter registry";
|
|
5909
|
-
var
|
|
5912
|
+
var PORT_VERSIONS = Object.freeze({
|
|
5910
5913
|
tracker: 1,
|
|
5911
5914
|
store: 1,
|
|
5912
5915
|
learning: 1,
|
|
@@ -5914,7 +5917,7 @@ var PORT_VERSIONS2 = Object.freeze({
|
|
|
5914
5917
|
planner: 1,
|
|
5915
5918
|
sync: 1
|
|
5916
5919
|
});
|
|
5917
|
-
var SERVED_VERSIONS = new Map(Object.entries(
|
|
5920
|
+
var SERVED_VERSIONS = new Map(Object.entries(PORT_VERSIONS));
|
|
5918
5921
|
var PORT_TYPE_LIST = [...SERVED_VERSIONS.keys()].join(", ");
|
|
5919
5922
|
function checkPortType(port) {
|
|
5920
5923
|
if (!SERVED_VERSIONS.has(port)) {
|
|
@@ -5954,11 +5957,11 @@ function indexByPort(adapters) {
|
|
|
5954
5957
|
sync: only("sync")
|
|
5955
5958
|
};
|
|
5956
5959
|
}
|
|
5957
|
-
function
|
|
5960
|
+
function createAdapterRegistry(adapters = []) {
|
|
5958
5961
|
const held = adapters.reduce((accepted, adapter) => [...accepted, accept(adapter, accepted)], []);
|
|
5959
5962
|
const byPort = indexByPort(held);
|
|
5960
5963
|
const registry = {
|
|
5961
|
-
register: (adapter) =>
|
|
5964
|
+
register: (adapter) => createAdapterRegistry([...held, adapter]),
|
|
5962
5965
|
find: (port, kind) => SERVED_VERSIONS.has(port) ? byPort[port].get(kind) : undefined,
|
|
5963
5966
|
resolve: (port, kind) => {
|
|
5964
5967
|
checkPortType(port);
|
|
@@ -5966,8 +5969,8 @@ function createAdapterRegistry2(adapters = []) {
|
|
|
5966
5969
|
if (adapter !== undefined)
|
|
5967
5970
|
return adapter;
|
|
5968
5971
|
const registered = [...byPort[port].keys()];
|
|
5969
|
-
const
|
|
5970
|
-
throw new Error(`${REFUSAL3}: no ${port} adapter is registered as ${describeValue(kind)};` + ` registered: ${
|
|
5972
|
+
const listed2 = registered.length === 0 ? "none" : registered.join(", ");
|
|
5973
|
+
throw new Error(`${REFUSAL3}: no ${port} adapter is registered as ${describeValue(kind)};` + ` registered: ${listed2}`);
|
|
5971
5974
|
},
|
|
5972
5975
|
kinds: (port) => {
|
|
5973
5976
|
checkPortType(port);
|
|
@@ -5977,38 +5980,38 @@ function createAdapterRegistry2(adapters = []) {
|
|
|
5977
5980
|
return Object.freeze(registry);
|
|
5978
5981
|
}
|
|
5979
5982
|
var STORE_OPENERS = {
|
|
5980
|
-
sqlite:
|
|
5981
|
-
ndjson:
|
|
5983
|
+
sqlite: openSqliteStore,
|
|
5984
|
+
ndjson: openNdjsonStore
|
|
5982
5985
|
};
|
|
5983
5986
|
var CORE_ADAPTERS = [
|
|
5984
|
-
...
|
|
5987
|
+
...STORE_BACKENDS.map((backend) => ({
|
|
5985
5988
|
port: "store",
|
|
5986
5989
|
kind: backend,
|
|
5987
|
-
portVersion:
|
|
5990
|
+
portVersion: PORT_VERSIONS.store,
|
|
5988
5991
|
create: ({ repoRoot }) => STORE_OPENERS[backend](repoRoot)
|
|
5989
5992
|
})),
|
|
5990
5993
|
{
|
|
5991
5994
|
port: "output",
|
|
5992
5995
|
kind: "text",
|
|
5993
|
-
portVersion:
|
|
5996
|
+
portVersion: PORT_VERSIONS.output,
|
|
5994
5997
|
create: ({ stream = process.stdout, verbosity = 0 }) => createTextOutput({ verbosity, stream })
|
|
5995
5998
|
},
|
|
5996
5999
|
{
|
|
5997
6000
|
port: "output",
|
|
5998
6001
|
kind: "json",
|
|
5999
|
-
portVersion:
|
|
6002
|
+
portVersion: PORT_VERSIONS.output,
|
|
6000
6003
|
create: ({ stream = process.stdout }) => createJsonOutput({ stream })
|
|
6001
6004
|
},
|
|
6002
6005
|
{
|
|
6003
6006
|
port: "output",
|
|
6004
6007
|
kind: "events",
|
|
6005
|
-
portVersion:
|
|
6008
|
+
portVersion: PORT_VERSIONS.output,
|
|
6006
6009
|
create: ({ stream = process.stdout }) => createEventsOutput({ stream })
|
|
6007
6010
|
},
|
|
6008
6011
|
{
|
|
6009
6012
|
port: "tracker",
|
|
6010
6013
|
kind: "local",
|
|
6011
|
-
portVersion:
|
|
6014
|
+
portVersion: PORT_VERSIONS.tracker,
|
|
6012
6015
|
create: ({ repoRoot, fallbackReason = null }) => createLocalTracker({
|
|
6013
6016
|
issuesDir: localIssuesDir(repoRoot),
|
|
6014
6017
|
fallbackReason
|
|
@@ -6017,17 +6020,17 @@ var CORE_ADAPTERS = [
|
|
|
6017
6020
|
{
|
|
6018
6021
|
port: "tracker",
|
|
6019
6022
|
kind: "github",
|
|
6020
|
-
portVersion:
|
|
6023
|
+
portVersion: PORT_VERSIONS.tracker,
|
|
6021
6024
|
create: ({ repoRoot, gh }) => createGithubTracker({ gh: gh ?? createGhRunner({ cwd: repoRoot }) })
|
|
6022
6025
|
},
|
|
6023
6026
|
{
|
|
6024
6027
|
port: "learning",
|
|
6025
6028
|
kind: "local",
|
|
6026
|
-
portVersion:
|
|
6029
|
+
portVersion: PORT_VERSIONS.learning,
|
|
6027
6030
|
create: ({
|
|
6028
6031
|
repoRoot,
|
|
6029
6032
|
home = homedir(),
|
|
6030
|
-
learningBlessMinConfidence =
|
|
6033
|
+
learningBlessMinConfidence = CONFIG_DEFAULTS.learningBlessMinConfidence
|
|
6031
6034
|
}) => createLocalLearning({
|
|
6032
6035
|
instinctsDir: localInstinctsDir(repoRoot),
|
|
6033
6036
|
home,
|
|
@@ -6037,7 +6040,7 @@ var CORE_ADAPTERS = [
|
|
|
6037
6040
|
{
|
|
6038
6041
|
port: "planner",
|
|
6039
6042
|
kind: "claude",
|
|
6040
|
-
portVersion:
|
|
6043
|
+
portVersion: PORT_VERSIONS.planner,
|
|
6041
6044
|
create: ({ repoRoot, settingSources, planPrompt, planDir, claude }) => {
|
|
6042
6045
|
if (!Array.isArray(settingSources)) {
|
|
6043
6046
|
throw new TypeError(`${REFUSAL3}: planner/claude has settingSources ${describeValue(settingSources)} in its context,` + " expected a list of setting sources");
|
|
@@ -6054,26 +6057,26 @@ var CORE_ADAPTERS = [
|
|
|
6054
6057
|
{
|
|
6055
6058
|
port: "sync",
|
|
6056
6059
|
kind: "local",
|
|
6057
|
-
portVersion:
|
|
6060
|
+
portVersion: PORT_VERSIONS.sync,
|
|
6058
6061
|
create: () => createLocalSync()
|
|
6059
6062
|
},
|
|
6060
6063
|
{
|
|
6061
6064
|
port: "sync",
|
|
6062
6065
|
kind: "file",
|
|
6063
|
-
portVersion:
|
|
6066
|
+
portVersion: PORT_VERSIONS.sync,
|
|
6064
6067
|
create: ({ repoRoot, store }) => {
|
|
6065
|
-
if (!
|
|
6066
|
-
throw new TypeError(`${REFUSAL3}: sync/file has store ${describeValue(store)} in its context,` + ` expected one of: ${
|
|
6068
|
+
if (!STORE_BACKENDS.includes(store)) {
|
|
6069
|
+
throw new TypeError(`${REFUSAL3}: sync/file has store ${describeValue(store)} in its context,` + ` expected one of: ${STORE_BACKENDS.join(", ")}`);
|
|
6067
6070
|
}
|
|
6068
6071
|
return createFileSync({ repoRoot, backend: store });
|
|
6069
6072
|
}
|
|
6070
6073
|
}
|
|
6071
6074
|
];
|
|
6072
|
-
var
|
|
6075
|
+
var CORE_ADAPTER_REGISTRY = createAdapterRegistry(CORE_ADAPTERS);
|
|
6073
6076
|
|
|
6074
6077
|
// src/board/trust.ts
|
|
6075
6078
|
var REPO_PATH = "{owner}/{repo}";
|
|
6076
|
-
var
|
|
6079
|
+
var TRUSTED_PERMISSIONS = ["admin", "maintain", "write"];
|
|
6077
6080
|
var TRUST_REFUSAL_EXIT = 2;
|
|
6078
6081
|
var REMEDY = Object.freeze({
|
|
6079
6082
|
issue: "a member must open the spec",
|
|
@@ -6085,7 +6088,7 @@ function failureDetail(reading) {
|
|
|
6085
6088
|
return detail === "" ? UNREPORTED : detail;
|
|
6086
6089
|
}
|
|
6087
6090
|
function isTrustedPermission(value) {
|
|
6088
|
-
return
|
|
6091
|
+
return TRUSTED_PERMISSIONS.some((permission) => permission === value);
|
|
6089
6092
|
}
|
|
6090
6093
|
function stringOrNull(value) {
|
|
6091
6094
|
return typeof value === "string" ? value : null;
|
|
@@ -6135,8 +6138,8 @@ async function readAuthorTrust(options) {
|
|
|
6135
6138
|
};
|
|
6136
6139
|
}
|
|
6137
6140
|
const wanted = login.toLowerCase();
|
|
6138
|
-
const
|
|
6139
|
-
if (
|
|
6141
|
+
const listed2 = trustedAuthors.some((author) => author.toLowerCase() === wanted);
|
|
6142
|
+
if (listed2) {
|
|
6140
6143
|
return { login, trusted: true, source: "allow-list", refusal: null, permission: null };
|
|
6141
6144
|
}
|
|
6142
6145
|
const permission = await permissions(login);
|
|
@@ -6187,19 +6190,73 @@ function requireTrustedAuthor(item, reading) {
|
|
|
6187
6190
|
return;
|
|
6188
6191
|
throw new CommandExit(TRUST_REFUSAL_EXIT, trustRefusalMessage(item, reading));
|
|
6189
6192
|
}
|
|
6193
|
+
var USER_COMMAND = "gh api user";
|
|
6194
|
+
function noLogin(detail) {
|
|
6195
|
+
return { login: null, detail };
|
|
6196
|
+
}
|
|
6197
|
+
function createGhEditorLogin(options) {
|
|
6198
|
+
const { gh } = options;
|
|
6199
|
+
return async () => {
|
|
6200
|
+
const result = await gh(["api", "user"]);
|
|
6201
|
+
if (!result.ok)
|
|
6202
|
+
return noLogin(detailOf2(result, USER_COMMAND));
|
|
6203
|
+
let payload;
|
|
6204
|
+
try {
|
|
6205
|
+
payload = JSON.parse(result.stdout);
|
|
6206
|
+
} catch (error) {
|
|
6207
|
+
return noLogin(`${USER_COMMAND} wrote output that is not JSON: ${messageOf(error)}`);
|
|
6208
|
+
}
|
|
6209
|
+
if (!isMapping(payload)) {
|
|
6210
|
+
return noLogin(`${USER_COMMAND} answered ${describeValue(payload)}, expected a mapping`);
|
|
6211
|
+
}
|
|
6212
|
+
const login = payload["login"];
|
|
6213
|
+
return typeof login === "string" && isGitHubLogin(login) ? { login, detail: "" } : noLogin(`${USER_COMMAND} answered ${describeValue(login)} as its login, which is not a GitHub login`);
|
|
6214
|
+
};
|
|
6215
|
+
}
|
|
6216
|
+
async function readEditorTrust(trust, editor) {
|
|
6217
|
+
const read = await editor();
|
|
6218
|
+
if (read.login === null) {
|
|
6219
|
+
return { login: null, trusted: false, refusal: "ownership-unknown", reading: null, detail: read.detail };
|
|
6220
|
+
}
|
|
6221
|
+
const reading = await readBoardTrust(trust, read.login);
|
|
6222
|
+
return {
|
|
6223
|
+
login: read.login,
|
|
6224
|
+
trusted: reading.trusted,
|
|
6225
|
+
refusal: editorRefusalOf(reading),
|
|
6226
|
+
reading,
|
|
6227
|
+
detail: ""
|
|
6228
|
+
};
|
|
6229
|
+
}
|
|
6230
|
+
function editorRefusalOf(reading) {
|
|
6231
|
+
if (reading.trusted)
|
|
6232
|
+
return null;
|
|
6233
|
+
return reading.refusal === "lookup-failed" ? "ownership-unknown" : "ownership";
|
|
6234
|
+
}
|
|
6235
|
+
function editorRefusalMessage(item, reading) {
|
|
6236
|
+
const what = `${item.kind} #${String(item.number)}`;
|
|
6237
|
+
if (reading.reading !== null) {
|
|
6238
|
+
const because = trustRefusalClause(item.repo, reading.reading);
|
|
6239
|
+
return `${what} cannot be edited by ${reading.reading.login}, ${because}; a member must make the edit`;
|
|
6240
|
+
}
|
|
6241
|
+
if (reading.trusted) {
|
|
6242
|
+
throw new TypeError("board trust: a trusted editor has no refusal to name");
|
|
6243
|
+
}
|
|
6244
|
+
const detail = reading.detail === "" ? UNREPORTED : reading.detail;
|
|
6245
|
+
return `${what} cannot be edited: the account running the edit could not be read (${detail}); ` + "log gh in with `gh auth login` and run it again";
|
|
6246
|
+
}
|
|
6190
6247
|
// src/effort/sync/wire.ts
|
|
6191
6248
|
import { existsSync as existsSync14, mkdtempSync as mkdtempSync2, rmSync as rmSync8 } from "fs";
|
|
6192
6249
|
import { tmpdir as tmpdir4 } from "os";
|
|
6193
6250
|
import { join as join17 } from "path";
|
|
6194
6251
|
import { Database as Database7, SQLiteError as SQLiteError3 } from "bun:sqlite";
|
|
6195
|
-
var
|
|
6196
|
-
var
|
|
6252
|
+
var WIRE_FORMAT = "rafa-effort-wire";
|
|
6253
|
+
var WIRE_VERSION = 1;
|
|
6197
6254
|
|
|
6198
|
-
class
|
|
6255
|
+
class WireFormatError extends Error {
|
|
6199
6256
|
name = "WireFormatError";
|
|
6200
6257
|
}
|
|
6201
6258
|
|
|
6202
|
-
class
|
|
6259
|
+
class WireExportRefusal extends Error {
|
|
6203
6260
|
name = "WireExportRefusal";
|
|
6204
6261
|
}
|
|
6205
6262
|
var MERGED_TABLES = Object.entries(MERGE_RULES).filter(([, rule]) => rule.scope === "merged").map(([table]) => table);
|
|
@@ -6210,17 +6267,17 @@ function isRecord4(value) {
|
|
|
6210
6267
|
function isCount(value) {
|
|
6211
6268
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
6212
6269
|
}
|
|
6213
|
-
function isMergedTable(
|
|
6214
|
-
return MERGED_TABLES.includes(
|
|
6270
|
+
function isMergedTable(name2) {
|
|
6271
|
+
return MERGED_TABLES.includes(name2);
|
|
6215
6272
|
}
|
|
6216
6273
|
function checkCursor(value) {
|
|
6217
6274
|
if (!isRecord4(value))
|
|
6218
|
-
throw new
|
|
6275
|
+
throw new WireFormatError("wire: cursor is not an object");
|
|
6219
6276
|
const entries = Object.entries(value).map(([table, seq]) => {
|
|
6220
6277
|
if (!isMergedTable(table))
|
|
6221
|
-
throw new
|
|
6278
|
+
throw new WireFormatError(`wire: cursor names ${JSON.stringify(table)}, which is no merged table`);
|
|
6222
6279
|
if (!isCount(seq))
|
|
6223
|
-
throw new
|
|
6280
|
+
throw new WireFormatError(`wire: cursor for ${table} is ${JSON.stringify(seq)}, expected a whole number of 0 or more`);
|
|
6224
6281
|
return [table, seq];
|
|
6225
6282
|
});
|
|
6226
6283
|
return Object.freeze(Object.fromEntries(entries));
|
|
@@ -6228,67 +6285,67 @@ function checkCursor(value) {
|
|
|
6228
6285
|
function checkRow(table, value, index) {
|
|
6229
6286
|
const where = `wire: ${table} row ${String(index)}`;
|
|
6230
6287
|
if (!isRecord4(value))
|
|
6231
|
-
throw new
|
|
6288
|
+
throw new WireFormatError(`${where} is not an object`);
|
|
6232
6289
|
for (const [column, cell] of Object.entries(value)) {
|
|
6233
6290
|
const carried = cell === null || typeof cell === "string" || typeof cell === "number" && Number.isFinite(cell);
|
|
6234
6291
|
if (!carried)
|
|
6235
|
-
throw new
|
|
6292
|
+
throw new WireFormatError(`${where} holds ${JSON.stringify(cell) ?? typeof cell} in ${column}, expected a string, a number or null`);
|
|
6236
6293
|
}
|
|
6237
6294
|
const seq = value.seq;
|
|
6238
6295
|
if (!isCount(seq) || seq === 0)
|
|
6239
|
-
throw new
|
|
6296
|
+
throw new WireFormatError(`${where} has seq ${JSON.stringify(seq) ?? "undefined"}, expected a whole number above 0`);
|
|
6240
6297
|
return Object.freeze({ ...value });
|
|
6241
6298
|
}
|
|
6242
6299
|
function checkMigrations(value) {
|
|
6243
6300
|
if (!Array.isArray(value))
|
|
6244
|
-
throw new
|
|
6301
|
+
throw new WireFormatError("wire: migrations is not a list");
|
|
6245
6302
|
const ids = value.map((id) => {
|
|
6246
6303
|
if (typeof id !== "string" || id === "")
|
|
6247
|
-
throw new
|
|
6304
|
+
throw new WireFormatError(`wire: migration id ${JSON.stringify(id)} is not a name`);
|
|
6248
6305
|
return id;
|
|
6249
6306
|
});
|
|
6250
6307
|
const twice = ids.filter((id, index) => ids.indexOf(id) !== index);
|
|
6251
6308
|
if (twice.length > 0)
|
|
6252
|
-
throw new
|
|
6309
|
+
throw new WireFormatError(`wire: migrations name ${twice.join(", ")} more than once`);
|
|
6253
6310
|
return Object.freeze(ids);
|
|
6254
6311
|
}
|
|
6255
6312
|
function checkTables(value) {
|
|
6256
6313
|
if (!isRecord4(value))
|
|
6257
|
-
throw new
|
|
6314
|
+
throw new WireFormatError("wire: tables is not an object");
|
|
6258
6315
|
const entries = Object.entries(value).map(([table, rows]) => {
|
|
6259
6316
|
if (!isMergedTable(table))
|
|
6260
|
-
throw new
|
|
6317
|
+
throw new WireFormatError(`wire: tables names ${JSON.stringify(table)}, which is no merged table`);
|
|
6261
6318
|
if (!Array.isArray(rows))
|
|
6262
|
-
throw new
|
|
6319
|
+
throw new WireFormatError(`wire: tables.${table} is not a list`);
|
|
6263
6320
|
return [table, Object.freeze(rows.map((row, index) => checkRow(table, row, index)))];
|
|
6264
6321
|
});
|
|
6265
6322
|
return Object.freeze(Object.fromEntries(entries));
|
|
6266
6323
|
}
|
|
6267
6324
|
function checkPayload(value) {
|
|
6268
6325
|
if (!isRecord4(value))
|
|
6269
|
-
throw new
|
|
6270
|
-
if (value.format !==
|
|
6271
|
-
throw new
|
|
6272
|
-
if (value.version !==
|
|
6273
|
-
throw new
|
|
6326
|
+
throw new WireFormatError("wire: the payload is not an object");
|
|
6327
|
+
if (value.format !== WIRE_FORMAT)
|
|
6328
|
+
throw new WireFormatError(`wire: format is ${JSON.stringify(value.format) ?? "undefined"}, expected ${JSON.stringify(WIRE_FORMAT)}`);
|
|
6329
|
+
if (value.version !== WIRE_VERSION)
|
|
6330
|
+
throw new WireFormatError(`wire: version is ${JSON.stringify(value.version) ?? "undefined"}; this rafa reads version ${String(WIRE_VERSION)}`);
|
|
6274
6331
|
return Object.freeze({
|
|
6275
|
-
format:
|
|
6276
|
-
version:
|
|
6332
|
+
format: WIRE_FORMAT,
|
|
6333
|
+
version: WIRE_VERSION,
|
|
6277
6334
|
migrations: checkMigrations(value.migrations),
|
|
6278
6335
|
tables: checkTables(value.tables),
|
|
6279
6336
|
cursor: checkCursor(value.cursor)
|
|
6280
6337
|
});
|
|
6281
6338
|
}
|
|
6282
|
-
function
|
|
6339
|
+
function encodeWirePayload(payload) {
|
|
6283
6340
|
return JSON.stringify(payload);
|
|
6284
6341
|
}
|
|
6285
|
-
function
|
|
6342
|
+
function decodeWirePayload(text) {
|
|
6286
6343
|
let parsed;
|
|
6287
6344
|
try {
|
|
6288
6345
|
parsed = JSON.parse(text);
|
|
6289
6346
|
} catch (error) {
|
|
6290
6347
|
const reason = error instanceof Error ? error.message : String(error);
|
|
6291
|
-
throw new
|
|
6348
|
+
throw new WireFormatError(`wire: the payload is not JSON (${reason})`);
|
|
6292
6349
|
}
|
|
6293
6350
|
return checkPayload(parsed);
|
|
6294
6351
|
}
|
|
@@ -6299,17 +6356,17 @@ function readLoggedIds(db) {
|
|
|
6299
6356
|
return db.query(`SELECT id FROM ${MIGRATION_LOG_TABLE} ORDER BY seq`).all().map(({ id }) => id);
|
|
6300
6357
|
}
|
|
6301
6358
|
function columnsOf(db, table) {
|
|
6302
|
-
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name }) =>
|
|
6359
|
+
return db.query(`PRAGMA table_info(${quoted(table)})`).all().map(({ name: name2 }) => name2);
|
|
6303
6360
|
}
|
|
6304
6361
|
function wireValue(value, where) {
|
|
6305
6362
|
if (typeof value === "bigint") {
|
|
6306
6363
|
const number = Number(value);
|
|
6307
6364
|
if (Number.isSafeInteger(number))
|
|
6308
6365
|
return number;
|
|
6309
|
-
throw new
|
|
6366
|
+
throw new WireExportRefusal(`wire: ${where} holds ${String(value)}, past the whole numbers JSON carries exactly`);
|
|
6310
6367
|
}
|
|
6311
6368
|
if (value instanceof Uint8Array)
|
|
6312
|
-
throw new
|
|
6369
|
+
throw new WireExportRefusal(`wire: ${where} holds a BLOB, which the wire does not carry`);
|
|
6313
6370
|
return value;
|
|
6314
6371
|
}
|
|
6315
6372
|
function readRows(db, table, columns, after, path) {
|
|
@@ -6322,7 +6379,7 @@ function readRows(db, table, columns, after, path) {
|
|
|
6322
6379
|
function readPayload(db, path, cursor) {
|
|
6323
6380
|
const migrations = readLoggedIds(db);
|
|
6324
6381
|
if (migrations === null) {
|
|
6325
|
-
throw new
|
|
6382
|
+
throw new WireExportRefusal(`wire: ${path} has no migration log; any rafa command that writes the store brings it forward`);
|
|
6326
6383
|
}
|
|
6327
6384
|
const tables = {};
|
|
6328
6385
|
const next = { ...cursor };
|
|
@@ -6332,7 +6389,7 @@ function readPayload(db, path, cursor) {
|
|
|
6332
6389
|
continue;
|
|
6333
6390
|
const missing = ORIGIN_COLUMNS.filter((column) => !columns.includes(column));
|
|
6334
6391
|
if (missing.length > 0) {
|
|
6335
|
-
throw new
|
|
6392
|
+
throw new WireExportRefusal(`wire: ${path} holds no ${missing.map((column) => `${table}.${column}`).join(", ")}, so its rows carry no origin pair`);
|
|
6336
6393
|
}
|
|
6337
6394
|
const rows = readRows(db, table, columns, cursor[table] ?? 0, path);
|
|
6338
6395
|
tables[table] = Object.freeze(rows);
|
|
@@ -6340,19 +6397,19 @@ function readPayload(db, path, cursor) {
|
|
|
6340
6397
|
next[table] = last === undefined ? cursor[table] ?? 0 : Number(last.seq);
|
|
6341
6398
|
}
|
|
6342
6399
|
return Object.freeze({
|
|
6343
|
-
format:
|
|
6344
|
-
version:
|
|
6400
|
+
format: WIRE_FORMAT,
|
|
6401
|
+
version: WIRE_VERSION,
|
|
6345
6402
|
migrations: Object.freeze([...migrations]),
|
|
6346
6403
|
tables: Object.freeze(tables),
|
|
6347
6404
|
cursor: Object.freeze(next)
|
|
6348
6405
|
});
|
|
6349
6406
|
}
|
|
6350
|
-
function
|
|
6407
|
+
function exportWirePayload(options) {
|
|
6351
6408
|
const { path } = options;
|
|
6352
6409
|
const cursor = checkCursor(options.cursor ?? {});
|
|
6353
6410
|
guardTestProcess(path);
|
|
6354
6411
|
if (!existsSync14(path))
|
|
6355
|
-
throw new
|
|
6412
|
+
throw new WireExportRefusal(`wire: ${path} is not there`);
|
|
6356
6413
|
const db = new Database7(path, { readonly: true, safeIntegers: true });
|
|
6357
6414
|
try {
|
|
6358
6415
|
db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
|
|
@@ -6365,28 +6422,28 @@ function namedMigrations(ids, catalogue) {
|
|
|
6365
6422
|
const known = new Set(catalogue.map(({ id }) => id));
|
|
6366
6423
|
const unknown = ids.filter((id) => !known.has(id));
|
|
6367
6424
|
if (unknown.length > 0) {
|
|
6368
|
-
throw new
|
|
6425
|
+
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
6426
|
}
|
|
6370
6427
|
return catalogue.filter(({ id }) => ids.includes(id));
|
|
6371
6428
|
}
|
|
6372
6429
|
function insertRows(db, table, rows) {
|
|
6373
6430
|
const columns = columnsOf(db, table);
|
|
6374
6431
|
if (columns.length === 0)
|
|
6375
|
-
throw new
|
|
6432
|
+
throw new WireFormatError(`wire: the payload carries ${table}, which its migrations do not make`);
|
|
6376
6433
|
const statement = db.query(`INSERT INTO ${quoted(table)} (${columns.map(quoted).join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`);
|
|
6377
6434
|
const expected = [...columns].sort().join(",");
|
|
6378
6435
|
rows.forEach((row, index) => {
|
|
6379
6436
|
const where = `wire: ${table} row ${String(index)}`;
|
|
6380
6437
|
const held = Object.keys(row);
|
|
6381
6438
|
if ([...held].sort().join(",") !== expected) {
|
|
6382
|
-
throw new
|
|
6439
|
+
throw new WireFormatError(`${where} holds columns ${held.join(", ")}; its migrations make ${columns.join(", ")}`);
|
|
6383
6440
|
}
|
|
6384
6441
|
try {
|
|
6385
6442
|
statement.run(...columns.map((column) => row[column] ?? null));
|
|
6386
6443
|
} catch (error) {
|
|
6387
6444
|
if (!(error instanceof SQLiteError3))
|
|
6388
6445
|
throw error;
|
|
6389
|
-
throw new
|
|
6446
|
+
throw new WireFormatError(`${where} (seq ${String(row.seq)}) is refused by the table: ${error.message}`);
|
|
6390
6447
|
}
|
|
6391
6448
|
});
|
|
6392
6449
|
}
|
|
@@ -6408,7 +6465,7 @@ function buildStore(path, payload, options) {
|
|
|
6408
6465
|
db.close();
|
|
6409
6466
|
}
|
|
6410
6467
|
}
|
|
6411
|
-
function
|
|
6468
|
+
function materialiseWirePayload(payload, options = {}) {
|
|
6412
6469
|
const checked = checkPayload(payload);
|
|
6413
6470
|
const directory = mkdtempSync2(join17(options.tempDir ?? tmpdir4(), "rafa-wire-"));
|
|
6414
6471
|
const release = () => rmSync8(directory, { recursive: true, force: true });
|
|
@@ -6431,13 +6488,13 @@ function describeValue4(value) {
|
|
|
6431
6488
|
return "an object";
|
|
6432
6489
|
return String(value);
|
|
6433
6490
|
}
|
|
6434
|
-
function
|
|
6435
|
-
const adapter =
|
|
6491
|
+
function selectEffortStore(repoRoot, config) {
|
|
6492
|
+
const adapter = CORE_ADAPTER_REGISTRY.find("store", config.store);
|
|
6436
6493
|
if (adapter === undefined) {
|
|
6437
|
-
const expected =
|
|
6494
|
+
const expected = CORE_ADAPTER_REGISTRY.kinds("store").join(", ");
|
|
6438
6495
|
throw new TypeError(`effort store: store is ${describeValue4(config.store)},` + ` expected one of: ${expected}`);
|
|
6439
6496
|
}
|
|
6440
6497
|
return adapter.create({ repoRoot });
|
|
6441
6498
|
}
|
|
6442
6499
|
|
|
6443
|
-
export { EFFORT_STORE_DIR, effortStoreFileName, EFFORT_DIR_VARIABLE, effortStoreDir, guardTestProcess,
|
|
6500
|
+
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, TRUST_REFUSAL_EXIT, createGhPermissions, readAuthorTrust, ghBoardTrust, readBoardTrust, requireTrustedBoardAuthor, trustRefusalClause, trustRefusalMessage, requireTrustedAuthor, createGhEditorLogin, readEditorTrust, editorRefusalMessage, WIRE_FORMAT, WIRE_VERSION, WireFormatError, WireExportRefusal, encodeWirePayload, decodeWirePayload, exportWirePayload, materialiseWirePayload, selectEffortStore };
|