@saasontools/strauss-kb 0.1.15 → 0.1.16
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 +17 -11
- package/dist/{chunk-KNIUBCZY.js → chunk-H5W53NVU.js} +225 -60
- package/dist/chunk-H5W53NVU.js.map +1 -0
- package/dist/{chunk-LACCRB2Y.js → chunk-MEZCF646.js} +2 -2
- package/dist/{chunk-MK7GU4DX.js → chunk-RINBOAQZ.js} +15 -6
- package/dist/chunk-RINBOAQZ.js.map +1 -0
- package/dist/cli-main.cjs +247 -75
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +231 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +3 -3
- package/dist/mcp-main.cjs +234 -71
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-KNIUBCZY.js.map +0 -1
- package/dist/chunk-MK7GU4DX.js.map +0 -1
- /package/dist/{chunk-LACCRB2Y.js.map → chunk-MEZCF646.js.map} +0 -0
package/dist/cli-main.cjs
CHANGED
|
@@ -1439,6 +1439,36 @@ var KbInvalidConceptIdError = class extends BaseError {
|
|
|
1439
1439
|
});
|
|
1440
1440
|
}
|
|
1441
1441
|
};
|
|
1442
|
+
var KbStampBaselineError = class extends BaseError {
|
|
1443
|
+
constructor(since) {
|
|
1444
|
+
super({
|
|
1445
|
+
message: `kb: --since ${since} is neither a 64-character digest nor a readable stamp file`,
|
|
1446
|
+
errorType: "KbStampBaselineUnreadable" /* KbStampBaselineUnreadable */,
|
|
1447
|
+
code: 400,
|
|
1448
|
+
fault: "User" /* User */,
|
|
1449
|
+
retriable: false,
|
|
1450
|
+
reportToUser: true,
|
|
1451
|
+
details: { since }
|
|
1452
|
+
});
|
|
1453
|
+
this.since = since;
|
|
1454
|
+
}
|
|
1455
|
+
since;
|
|
1456
|
+
};
|
|
1457
|
+
var KbStampDigestBaselineError = class extends BaseError {
|
|
1458
|
+
constructor(since) {
|
|
1459
|
+
super({
|
|
1460
|
+
message: `kb: --since ${since} is a digest, which needs --bundle (one base) \u2014 a file baseline works for many`,
|
|
1461
|
+
errorType: "KbStampDigestBaselineAmbiguous" /* KbStampDigestBaselineAmbiguous */,
|
|
1462
|
+
code: 400,
|
|
1463
|
+
fault: "User" /* User */,
|
|
1464
|
+
retriable: false,
|
|
1465
|
+
reportToUser: true,
|
|
1466
|
+
details: { since }
|
|
1467
|
+
});
|
|
1468
|
+
this.since = since;
|
|
1469
|
+
}
|
|
1470
|
+
since;
|
|
1471
|
+
};
|
|
1442
1472
|
|
|
1443
1473
|
// src/kb-pins/budgets.ts
|
|
1444
1474
|
function asBudgets(value) {
|
|
@@ -3577,17 +3607,115 @@ var schemaCommand = define({
|
|
|
3577
3607
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
3578
3608
|
});
|
|
3579
3609
|
|
|
3580
|
-
// src/commands/
|
|
3610
|
+
// src/commands/stamp.ts
|
|
3611
|
+
var import_promises5 = require("fs/promises");
|
|
3581
3612
|
var import_zod25 = require("zod");
|
|
3613
|
+
var DIGEST = /^[0-9a-f]{64}$/;
|
|
3614
|
+
var stampCommand = define({
|
|
3615
|
+
name: "stamp",
|
|
3616
|
+
tool: "kb_stamp",
|
|
3617
|
+
usage: "stamp [--bundle PATH] [--since DIGEST|FILE]",
|
|
3618
|
+
description: "Content stamp of a base \u2014 `load`'s digest, record counts, per-record digests \u2014 without any bodies. Takes no bundlePath to stamp every pinned base. With `since`, reports only the bases that moved, naming the changed ids when the baseline is a prior stamp; silent when nothing changed. Reads, never writes.",
|
|
3619
|
+
input: import_zod25.z.object({
|
|
3620
|
+
bundlePath: import_zod25.z.string().min(1).optional().describe(
|
|
3621
|
+
"Absolute path to one knowledge base. Omit to stamp every pinned base."
|
|
3622
|
+
),
|
|
3623
|
+
since: import_zod25.z.string().min(1).optional().describe(
|
|
3624
|
+
"Prior digest, or path to a prior `stamp --json`; only moved bases return, with changed ids when the baseline is a file."
|
|
3625
|
+
)
|
|
3626
|
+
}),
|
|
3627
|
+
fromArgv: (argv, path, _stdin, bundleExplicit) => {
|
|
3628
|
+
const since = argvFlag(argv, "--since");
|
|
3629
|
+
return {
|
|
3630
|
+
...bundleExplicit ? { bundlePath: path } : {},
|
|
3631
|
+
...since !== void 0 ? { since } : {}
|
|
3632
|
+
};
|
|
3633
|
+
},
|
|
3634
|
+
run: async ({ store }, { bundlePath: bundlePath2, since }) => {
|
|
3635
|
+
const targets = bundlePath2 ? [bundlePath2] : (await readMergedPins(process.cwd())).pins.map(
|
|
3636
|
+
(pin) => pin.absolutePath
|
|
3637
|
+
);
|
|
3638
|
+
if (since !== void 0 && DIGEST.test(since) && targets.length > 1) {
|
|
3639
|
+
throw new KbStampDigestBaselineError(since);
|
|
3640
|
+
}
|
|
3641
|
+
const stamps = await Promise.all(
|
|
3642
|
+
targets.map((target) => store.stamp(target))
|
|
3643
|
+
);
|
|
3644
|
+
if (since === void 0) {
|
|
3645
|
+
return stamps.map((stamp) => ({ ...stamp, changed: null }));
|
|
3646
|
+
}
|
|
3647
|
+
const baseline = await readBaseline(since);
|
|
3648
|
+
const reports = [];
|
|
3649
|
+
for (const stamp of stamps) {
|
|
3650
|
+
const before = baseline.byPath.get(stamp.path);
|
|
3651
|
+
if (baseline.digest !== null) {
|
|
3652
|
+
if (baseline.digest === stamp.digest) continue;
|
|
3653
|
+
reports.push({ ...stamp, changed: null });
|
|
3654
|
+
continue;
|
|
3655
|
+
}
|
|
3656
|
+
if (before && before.digest === stamp.digest) continue;
|
|
3657
|
+
reports.push({ ...stamp, changed: changedIds(before?.records, stamp) });
|
|
3658
|
+
}
|
|
3659
|
+
return reports;
|
|
3660
|
+
},
|
|
3661
|
+
render: (result) => result.map((report) => {
|
|
3662
|
+
const counts = `${report.recordCount} record(s), ${report.superseded} superseded`;
|
|
3663
|
+
const head = `${report.path} ${report.digest} ${counts}${report.newestAt ? ` newest ${report.newestAt}` : ""}`;
|
|
3664
|
+
return report.changed?.length ? `${head}
|
|
3665
|
+
changed: ${report.changed.join(", ")}` : head;
|
|
3666
|
+
}).join("\n")
|
|
3667
|
+
});
|
|
3668
|
+
function changedIds(before, stamp) {
|
|
3669
|
+
const now = new Map(
|
|
3670
|
+
stamp.records.map((record) => [record.conceptId, record.digest])
|
|
3671
|
+
);
|
|
3672
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3673
|
+
for (const [conceptId2, digest] of now) {
|
|
3674
|
+
if (before?.get(conceptId2) !== digest) ids.add(conceptId2);
|
|
3675
|
+
}
|
|
3676
|
+
for (const conceptId2 of before?.keys() ?? []) {
|
|
3677
|
+
if (!now.has(conceptId2)) ids.add(conceptId2);
|
|
3678
|
+
}
|
|
3679
|
+
return [...ids].sort();
|
|
3680
|
+
}
|
|
3681
|
+
async function readBaseline(since) {
|
|
3682
|
+
if (DIGEST.test(since)) return { digest: since, byPath: /* @__PURE__ */ new Map() };
|
|
3683
|
+
let parsed;
|
|
3684
|
+
try {
|
|
3685
|
+
parsed = JSON.parse(await (0, import_promises5.readFile)(since, "utf8"));
|
|
3686
|
+
} catch {
|
|
3687
|
+
throw new KbStampBaselineError(since);
|
|
3688
|
+
}
|
|
3689
|
+
const entries = Array.isArray(parsed) ? parsed : parsed?.stamps ?? [];
|
|
3690
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
3691
|
+
for (const entry of entries) {
|
|
3692
|
+
if (typeof entry?.path !== "string" || typeof entry?.digest !== "string") {
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
byPath.set(entry.path, {
|
|
3696
|
+
digest: entry.digest,
|
|
3697
|
+
records: new Map(
|
|
3698
|
+
(entry.records ?? []).map((record) => [
|
|
3699
|
+
record.conceptId,
|
|
3700
|
+
record.digest
|
|
3701
|
+
])
|
|
3702
|
+
)
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
return { digest: null, byPath };
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
// src/commands/status.ts
|
|
3709
|
+
var import_zod26 = require("zod");
|
|
3582
3710
|
var statusCommand = define({
|
|
3583
3711
|
name: "status",
|
|
3584
3712
|
tool: "kb_status",
|
|
3585
3713
|
usage: "status <concept-id> <status>",
|
|
3586
3714
|
description: "Move a record's status. Compare-and-swap: a concurrent change fails instead of being overwritten.",
|
|
3587
|
-
input:
|
|
3715
|
+
input: import_zod26.z.object({
|
|
3588
3716
|
bundlePath,
|
|
3589
3717
|
conceptId,
|
|
3590
|
-
status:
|
|
3718
|
+
status: import_zod26.z.enum(KB_RECORD_STATUSES)
|
|
3591
3719
|
}),
|
|
3592
3720
|
fromArgv: (argv, path) => ({
|
|
3593
3721
|
bundlePath: path,
|
|
@@ -3602,13 +3730,13 @@ var statusCommand = define({
|
|
|
3602
3730
|
});
|
|
3603
3731
|
|
|
3604
3732
|
// src/commands/supersede.ts
|
|
3605
|
-
var
|
|
3733
|
+
var import_zod27 = require("zod");
|
|
3606
3734
|
var supersedeCommand = define({
|
|
3607
3735
|
name: "supersede",
|
|
3608
3736
|
tool: "kb_supersede",
|
|
3609
3737
|
usage: "supersede <concept-id> <replacement-id>",
|
|
3610
3738
|
description: "Mark a record superseded by another, linked in both directions. Use instead of editing a record whose meaning changed.",
|
|
3611
|
-
input:
|
|
3739
|
+
input: import_zod27.z.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
3612
3740
|
fromArgv: (argv, path) => ({
|
|
3613
3741
|
bundlePath: path,
|
|
3614
3742
|
conceptId: argv[1],
|
|
@@ -3622,16 +3750,16 @@ var supersedeCommand = define({
|
|
|
3622
3750
|
});
|
|
3623
3751
|
|
|
3624
3752
|
// src/commands/sync-instructions.ts
|
|
3625
|
-
var
|
|
3753
|
+
var import_zod28 = require("zod");
|
|
3626
3754
|
var syncInstructionsCommand = define({
|
|
3627
3755
|
name: "sync-instructions",
|
|
3628
3756
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
3629
3757
|
description: "CLI-only: plant the kb_context block between sentinel comments in AGENTS.md or CLAUDE.md, idempotently.",
|
|
3630
|
-
input:
|
|
3631
|
-
file:
|
|
3632
|
-
budgetTokens:
|
|
3633
|
-
fullUnderTokens:
|
|
3634
|
-
profile:
|
|
3758
|
+
input: import_zod28.z.object({
|
|
3759
|
+
file: import_zod28.z.string().min(1).describe("The instruction file to edit in place."),
|
|
3760
|
+
budgetTokens: import_zod28.z.number().int().positive().optional(),
|
|
3761
|
+
fullUnderTokens: import_zod28.z.number().int().positive().optional(),
|
|
3762
|
+
profile: import_zod28.z.string().optional()
|
|
3635
3763
|
}),
|
|
3636
3764
|
fromArgv: (argv) => {
|
|
3637
3765
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -3657,7 +3785,7 @@ var syncInstructionsCommand = define({
|
|
|
3657
3785
|
});
|
|
3658
3786
|
|
|
3659
3787
|
// src/commands/trace.ts
|
|
3660
|
-
var
|
|
3788
|
+
var import_zod29 = require("zod");
|
|
3661
3789
|
|
|
3662
3790
|
// src/trace.ts
|
|
3663
3791
|
var TRACE_EDGES = [
|
|
@@ -3713,11 +3841,11 @@ var traceCommand = define({
|
|
|
3713
3841
|
tool: "kb_trace",
|
|
3714
3842
|
usage: "trace <concept-id> [edges...]",
|
|
3715
3843
|
description: 'Timeline of how a position was reached, ordered by write time, following supersession, shared anchors and shared sources. Includes rejected, draft and superseded records \u2014 in a history they are the content. For "why is it like this"; kb_load answers "what holds now".',
|
|
3716
|
-
input:
|
|
3844
|
+
input: import_zod29.z.object({
|
|
3717
3845
|
bundlePath,
|
|
3718
3846
|
conceptId,
|
|
3719
|
-
edges:
|
|
3720
|
-
depth:
|
|
3847
|
+
edges: import_zod29.z.array(import_zod29.z.enum(TRACE_EDGES)).optional(),
|
|
3848
|
+
depth: import_zod29.z.number().int().positive().optional()
|
|
3721
3849
|
}),
|
|
3722
3850
|
fromArgv: (argv, path) => ({
|
|
3723
3851
|
bundlePath: path,
|
|
@@ -3739,37 +3867,37 @@ var traceCommand = define({
|
|
|
3739
3867
|
});
|
|
3740
3868
|
|
|
3741
3869
|
// src/commands/types.ts
|
|
3742
|
-
var
|
|
3870
|
+
var import_zod30 = require("zod");
|
|
3743
3871
|
var typesCommand = define({
|
|
3744
3872
|
name: "types",
|
|
3745
3873
|
tool: "kb_types",
|
|
3746
3874
|
usage: "types",
|
|
3747
3875
|
description: "The twelve record types with their purpose, body sections, and starting status. Read this before writing rather than guessing headings \u2014 a section the type does not define is rejected.",
|
|
3748
|
-
input:
|
|
3876
|
+
input: import_zod30.z.object({}),
|
|
3749
3877
|
fromArgv: () => ({}),
|
|
3750
3878
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
3751
3879
|
});
|
|
3752
3880
|
|
|
3753
3881
|
// src/commands/unpin.ts
|
|
3754
|
-
var
|
|
3882
|
+
var import_zod31 = require("zod");
|
|
3755
3883
|
var unpinCommand = define({
|
|
3756
3884
|
name: "unpin",
|
|
3757
3885
|
tool: "kb_unpin",
|
|
3758
3886
|
usage: "unpin [bundle-path]",
|
|
3759
3887
|
description: "Remove a base from every manifest layer that holds it. Reports the layers touched.",
|
|
3760
|
-
input:
|
|
3888
|
+
input: import_zod31.z.object({ bundlePath }),
|
|
3761
3889
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
3762
3890
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
3763
3891
|
});
|
|
3764
3892
|
|
|
3765
3893
|
// src/commands/validate.ts
|
|
3766
|
-
var
|
|
3894
|
+
var import_zod32 = require("zod");
|
|
3767
3895
|
var validateCommand = define({
|
|
3768
3896
|
name: "validate",
|
|
3769
3897
|
tool: "kb_validate",
|
|
3770
3898
|
usage: "validate",
|
|
3771
3899
|
description: "Check pointers no single record can see: supersession links that disagree between the two records, typed causal links, and assumptions that cite sources. Each finding carries a severity: errors fail the exit code, warnings do not.",
|
|
3772
|
-
input:
|
|
3900
|
+
input: import_zod32.z.object({ bundlePath }),
|
|
3773
3901
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3774
3902
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
3775
3903
|
// Warnings never fail the exit code; every other severity does.
|
|
@@ -3779,16 +3907,16 @@ var validateCommand = define({
|
|
|
3779
3907
|
});
|
|
3780
3908
|
|
|
3781
3909
|
// src/commands/verify.ts
|
|
3782
|
-
var
|
|
3910
|
+
var import_zod33 = require("zod");
|
|
3783
3911
|
var verifyCommand = define({
|
|
3784
3912
|
name: "verify",
|
|
3785
3913
|
tool: "kb_verify",
|
|
3786
3914
|
usage: "verify <concept-id> --note <text>",
|
|
3787
3915
|
description: "Append a verified[] event: who checked, when, and what was found. Append-only. A record's own generator is refused unless the actor is `human:`-prefixed.",
|
|
3788
|
-
input:
|
|
3916
|
+
input: import_zod33.z.object({
|
|
3789
3917
|
bundlePath,
|
|
3790
3918
|
conceptId,
|
|
3791
|
-
note:
|
|
3919
|
+
note: import_zod33.z.string().refine((s) => s.trim().length > 0, {
|
|
3792
3920
|
message: "note must say what the check found"
|
|
3793
3921
|
})
|
|
3794
3922
|
}),
|
|
@@ -3808,15 +3936,15 @@ var verifyCommand = define({
|
|
|
3808
3936
|
});
|
|
3809
3937
|
|
|
3810
3938
|
// src/commands/write.ts
|
|
3811
|
-
var
|
|
3939
|
+
var import_zod34 = require("zod");
|
|
3812
3940
|
var writeCommand = define({
|
|
3813
3941
|
name: "write",
|
|
3814
3942
|
tool: "kb_write",
|
|
3815
3943
|
usage: "write <type> < record.json",
|
|
3816
3944
|
description: "Write one record. Search first \u2014 a duplicate concept id is rejected, not overwritten; kb_types lists each type's sections. An unsourced claim is an `assumption` with assumption: true, never a vague `fact`. Conflicting records get a `risk`, `open-question`, or superseding `decision`. Prefer a new short record over overloading one. Never delete; supersede.",
|
|
3817
|
-
input:
|
|
3945
|
+
input: import_zod34.z.object({
|
|
3818
3946
|
bundlePath,
|
|
3819
|
-
type:
|
|
3947
|
+
type: import_zod34.z.enum(KB_RECORD_TYPES),
|
|
3820
3948
|
input: composeInputSchema
|
|
3821
3949
|
}),
|
|
3822
3950
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -3840,13 +3968,13 @@ var writeCommand = define({
|
|
|
3840
3968
|
});
|
|
3841
3969
|
|
|
3842
3970
|
// src/commands/write-decision.ts
|
|
3843
|
-
var
|
|
3971
|
+
var import_zod35 = require("zod");
|
|
3844
3972
|
var writeDecisionCommand = define({
|
|
3845
3973
|
name: "write-decision",
|
|
3846
3974
|
tool: "kb_write_decision",
|
|
3847
3975
|
usage: "write-decision < decision.json",
|
|
3848
3976
|
description: "Write a decision, with `alternative` (what was rejected and why) and `impact` as fields. Record one when a later reader would otherwise simplify the constraint away; skip when the diff already answers it. `sources` for material read, `anchors` for code, `relatedConceptIds` for records.",
|
|
3849
|
-
input:
|
|
3977
|
+
input: import_zod35.z.object({ bundlePath, input: decisionInputSchema }),
|
|
3850
3978
|
fromArgv: async (_argv, path, stdin) => ({
|
|
3851
3979
|
bundlePath: path,
|
|
3852
3980
|
input: JSON.parse(await stdin())
|
|
@@ -3886,6 +4014,7 @@ var KB_COMMANDS = [
|
|
|
3886
4014
|
listCommand,
|
|
3887
4015
|
readIndexCommand,
|
|
3888
4016
|
logCommand,
|
|
4017
|
+
stampCommand,
|
|
3889
4018
|
validateCommand,
|
|
3890
4019
|
doctorCommand,
|
|
3891
4020
|
schemaCommand,
|
|
@@ -3901,8 +4030,7 @@ var KB_COMMANDS_BY_NAME = new Map(
|
|
|
3901
4030
|
);
|
|
3902
4031
|
|
|
3903
4032
|
// src/kb-store.ts
|
|
3904
|
-
var
|
|
3905
|
-
var import_promises6 = require("fs/promises");
|
|
4033
|
+
var import_promises7 = require("fs/promises");
|
|
3906
4034
|
var import_node_path8 = require("path");
|
|
3907
4035
|
|
|
3908
4036
|
// src/markdown.ts
|
|
@@ -3930,8 +4058,40 @@ function parseMarkdownWithFrontmatter(text, schema) {
|
|
|
3930
4058
|
};
|
|
3931
4059
|
}
|
|
3932
4060
|
|
|
4061
|
+
// src/kb-stamp.ts
|
|
4062
|
+
var import_node_crypto2 = require("crypto");
|
|
4063
|
+
function sha256(contents) {
|
|
4064
|
+
return (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
|
|
4065
|
+
}
|
|
4066
|
+
function bundleStamp(records, superseded) {
|
|
4067
|
+
const entries = [
|
|
4068
|
+
...records.map((hit) => ({
|
|
4069
|
+
conceptId: hit.record.conceptId,
|
|
4070
|
+
digest: `current:${sha256(
|
|
4071
|
+
stringifyMarkdownWithFrontmatter(
|
|
4072
|
+
hit.record.body,
|
|
4073
|
+
hit.record.frontmatter
|
|
4074
|
+
)
|
|
4075
|
+
)}`
|
|
4076
|
+
})),
|
|
4077
|
+
...superseded.map((entry) => ({
|
|
4078
|
+
conceptId: entry.conceptId,
|
|
4079
|
+
digest: `superseded:${sha256(JSON.stringify(entry))}`
|
|
4080
|
+
}))
|
|
4081
|
+
].sort((a, b) => a.conceptId < b.conceptId ? -1 : 1);
|
|
4082
|
+
return {
|
|
4083
|
+
digest: sha256(
|
|
4084
|
+
entries.map((entry) => `${entry.conceptId}:${entry.digest}`).join("\n")
|
|
4085
|
+
),
|
|
4086
|
+
records: entries
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
function bundleDigest(records, superseded) {
|
|
4090
|
+
return bundleStamp(records, superseded).digest;
|
|
4091
|
+
}
|
|
4092
|
+
|
|
3933
4093
|
// src/search-index.ts
|
|
3934
|
-
var
|
|
4094
|
+
var import_promises6 = require("fs/promises");
|
|
3935
4095
|
var import_node_path7 = require("path");
|
|
3936
4096
|
var SEARCH_INDEX_FILE = ".index.sqlite";
|
|
3937
4097
|
var COLLECTION = "kb";
|
|
@@ -3976,7 +4136,7 @@ async function searchBase(bundlePath2, query, options = {}) {
|
|
|
3976
4136
|
}
|
|
3977
4137
|
}
|
|
3978
4138
|
async function isStale(bundlePath2) {
|
|
3979
|
-
const indexAt = await (0,
|
|
4139
|
+
const indexAt = await (0, import_promises6.stat)((0, import_node_path7.join)(bundlePath2, SEARCH_INDEX_FILE)).then((s) => s.mtimeMs).catch(() => 0);
|
|
3980
4140
|
if (!indexAt) return true;
|
|
3981
4141
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3982
4142
|
const names = (await readdir2(bundlePath2).catch(() => [])).filter(
|
|
@@ -3985,7 +4145,7 @@ async function isStale(bundlePath2) {
|
|
|
3985
4145
|
let stale = false;
|
|
3986
4146
|
await mapLimit(names, DEFAULT_IO_CONCURRENCY, async (name) => {
|
|
3987
4147
|
if (stale) return;
|
|
3988
|
-
const at = await (0,
|
|
4148
|
+
const at = await (0, import_promises6.stat)((0, import_node_path7.join)(bundlePath2, name)).then((s) => s.mtimeMs).catch(() => 0);
|
|
3989
4149
|
if (at > indexAt) stale = true;
|
|
3990
4150
|
});
|
|
3991
4151
|
return stale;
|
|
@@ -4294,7 +4454,7 @@ var KbStore = class {
|
|
|
4294
4454
|
const conceptId2 = `${input.type}.${input.slug}`;
|
|
4295
4455
|
const root = this.root(bundlePath2);
|
|
4296
4456
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
4297
|
-
await (0,
|
|
4457
|
+
await (0, import_promises7.mkdir)(root, { recursive: true });
|
|
4298
4458
|
await this.publish(
|
|
4299
4459
|
target,
|
|
4300
4460
|
stringifyMarkdownWithFrontmatter(input.body, frontmatter),
|
|
@@ -4333,7 +4493,7 @@ var KbStore = class {
|
|
|
4333
4493
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
4334
4494
|
let raw;
|
|
4335
4495
|
try {
|
|
4336
|
-
raw = await (0,
|
|
4496
|
+
raw = await (0, import_promises7.readFile)(target, "utf8");
|
|
4337
4497
|
} catch {
|
|
4338
4498
|
return null;
|
|
4339
4499
|
}
|
|
@@ -4350,7 +4510,7 @@ var KbStore = class {
|
|
|
4350
4510
|
const root = this.root(bundlePath2);
|
|
4351
4511
|
let names;
|
|
4352
4512
|
try {
|
|
4353
|
-
names = await (0,
|
|
4513
|
+
names = await (0, import_promises7.readdir)(root);
|
|
4354
4514
|
} catch {
|
|
4355
4515
|
return [];
|
|
4356
4516
|
}
|
|
@@ -4358,7 +4518,7 @@ var KbStore = class {
|
|
|
4358
4518
|
const records = await mapLimit(
|
|
4359
4519
|
wanted,
|
|
4360
4520
|
DEFAULT_IO_CONCURRENCY,
|
|
4361
|
-
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await (0,
|
|
4521
|
+
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await (0, import_promises7.readFile)((0, import_node_path8.join)(root, name), "utf8"))
|
|
4362
4522
|
);
|
|
4363
4523
|
return records.filter((record) => record !== null);
|
|
4364
4524
|
}
|
|
@@ -4633,6 +4793,28 @@ ${answer}
|
|
|
4633
4793
|
digest: bundleDigestValue
|
|
4634
4794
|
};
|
|
4635
4795
|
}
|
|
4796
|
+
/**
|
|
4797
|
+
* `load`'s digest without `load`'s bodies — the same records, adjudicated
|
|
4798
|
+
* the same way, handed back as a stamp. Skips the anchor drift pass, which
|
|
4799
|
+
* reads source files and only ever adds warnings: no warning reaches the
|
|
4800
|
+
* digest, so the value is identical to the one `load` returns.
|
|
4801
|
+
*/
|
|
4802
|
+
async stamp(bundlePath2) {
|
|
4803
|
+
const bundle = await this.list(bundlePath2);
|
|
4804
|
+
const adjudicated = adjudicate(bundle, bundle, /* @__PURE__ */ new Date());
|
|
4805
|
+
const current = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
4806
|
+
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
4807
|
+
const stamped = bundleStamp(current, superseded);
|
|
4808
|
+
const dates = bundle.map((record) => record.frontmatter.generated?.at ?? null).filter((at) => typeof at === "string").sort();
|
|
4809
|
+
return {
|
|
4810
|
+
path: bundlePath2,
|
|
4811
|
+
digest: stamped.digest,
|
|
4812
|
+
recordCount: bundle.length,
|
|
4813
|
+
superseded: superseded.length,
|
|
4814
|
+
newestAt: dates.at(-1) ?? null,
|
|
4815
|
+
records: stamped.records
|
|
4816
|
+
};
|
|
4817
|
+
}
|
|
4636
4818
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
4637
4819
|
async trace(bundlePath2, seedId, options = {}) {
|
|
4638
4820
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
@@ -4663,7 +4845,7 @@ ${answer}
|
|
|
4663
4845
|
async readIndex(bundlePath2) {
|
|
4664
4846
|
const root = this.root(bundlePath2);
|
|
4665
4847
|
const expected = renderIndex(await this.list(bundlePath2));
|
|
4666
|
-
const stored = await (0,
|
|
4848
|
+
const stored = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, INDEX_FILE), "utf8").catch(
|
|
4667
4849
|
() => null
|
|
4668
4850
|
);
|
|
4669
4851
|
if (indexIsStale(stored, expected)) {
|
|
@@ -4684,7 +4866,7 @@ ${answer}
|
|
|
4684
4866
|
* knows which agent touched what. So a bad line is surfaced and left alone.
|
|
4685
4867
|
*/
|
|
4686
4868
|
async readLog(bundlePath2) {
|
|
4687
|
-
const raw = await (0,
|
|
4869
|
+
const raw = await (0, import_promises7.readFile)(
|
|
4688
4870
|
(0, import_node_path8.join)(this.root(bundlePath2), LOG_FILE),
|
|
4689
4871
|
"utf8"
|
|
4690
4872
|
).catch(() => "");
|
|
@@ -4736,15 +4918,15 @@ ${answer}
|
|
|
4736
4918
|
}
|
|
4737
4919
|
async mutate(bundlePath2, conceptId2, change, entry, changeBody = (body) => body) {
|
|
4738
4920
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
4739
|
-
const before = await (0,
|
|
4921
|
+
const before = await (0, import_promises7.readFile)(target, "utf8").catch(() => null);
|
|
4740
4922
|
if (before === null) throw new KbRecordNotFoundError(conceptId2);
|
|
4741
4923
|
const parsed = this.parse(conceptId2, before);
|
|
4742
4924
|
if (!parsed) throw new KbRecordNotFoundError(conceptId2);
|
|
4743
4925
|
const frontmatter = change(parsed.frontmatter);
|
|
4744
4926
|
const body = changeBody(parsed.body);
|
|
4745
4927
|
const contents = stringifyMarkdownWithFrontmatter(body, frontmatter);
|
|
4746
|
-
const witness = await (0,
|
|
4747
|
-
if (witness === null ||
|
|
4928
|
+
const witness = await (0, import_promises7.readFile)(target, "utf8").catch(() => null);
|
|
4929
|
+
if (witness === null || sha256(witness) !== sha256(before)) {
|
|
4748
4930
|
throw new KbWriteConflictError(conceptId2);
|
|
4749
4931
|
}
|
|
4750
4932
|
await this.publish(target, contents, true, conceptId2);
|
|
@@ -4769,20 +4951,20 @@ ${answer}
|
|
|
4769
4951
|
*/
|
|
4770
4952
|
async publish(target, contents, overwrite, conceptId2) {
|
|
4771
4953
|
const staging = `${target}.${process.pid}.tmp`;
|
|
4772
|
-
await (0,
|
|
4954
|
+
await (0, import_promises7.writeFile)(staging, contents, "utf8");
|
|
4773
4955
|
try {
|
|
4774
4956
|
if (overwrite) {
|
|
4775
|
-
await (0,
|
|
4957
|
+
await (0, import_promises7.rename)(staging, target);
|
|
4776
4958
|
return;
|
|
4777
4959
|
}
|
|
4778
|
-
await (0,
|
|
4960
|
+
await (0, import_promises7.link)(staging, target);
|
|
4779
4961
|
} catch (error) {
|
|
4780
4962
|
if (error.code === "EEXIST") {
|
|
4781
4963
|
throw new KbRecordAlreadyExistsError(conceptId2);
|
|
4782
4964
|
}
|
|
4783
4965
|
throw error;
|
|
4784
4966
|
} finally {
|
|
4785
|
-
await (0,
|
|
4967
|
+
await (0, import_promises7.unlink)(staging).catch(() => void 0);
|
|
4786
4968
|
}
|
|
4787
4969
|
}
|
|
4788
4970
|
/**
|
|
@@ -4830,14 +5012,14 @@ ${answer}
|
|
|
4830
5012
|
try {
|
|
4831
5013
|
let existing;
|
|
4832
5014
|
try {
|
|
4833
|
-
existing = await (0,
|
|
5015
|
+
existing = await (0, import_promises7.readFile)(target, "utf8");
|
|
4834
5016
|
} catch (error) {
|
|
4835
5017
|
if (error.code !== "ENOENT") throw error;
|
|
4836
5018
|
existing = null;
|
|
4837
5019
|
}
|
|
4838
5020
|
if (existing === null) {
|
|
4839
5021
|
try {
|
|
4840
|
-
await (0,
|
|
5022
|
+
await (0, import_promises7.writeFile)(target, appendUnionMergeLine(""), {
|
|
4841
5023
|
encoding: "utf8",
|
|
4842
5024
|
flag: "wx"
|
|
4843
5025
|
});
|
|
@@ -4858,7 +5040,7 @@ ${answer}
|
|
|
4858
5040
|
return;
|
|
4859
5041
|
}
|
|
4860
5042
|
if (!hasMergeDeclaration(existing)) {
|
|
4861
|
-
await (0,
|
|
5043
|
+
await (0, import_promises7.appendFile)(target, appendUnionMergeLine(existing), "utf8");
|
|
4862
5044
|
this.logger.info?.({
|
|
4863
5045
|
operation: "kb.gitattributes.ensure",
|
|
4864
5046
|
bundlePath: root,
|
|
@@ -4877,7 +5059,7 @@ ${answer}
|
|
|
4877
5059
|
async record(root, entry) {
|
|
4878
5060
|
await this.ensureGitattributes(root);
|
|
4879
5061
|
const line = renderLogEntry({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
4880
|
-
await (0,
|
|
5062
|
+
await (0, import_promises7.appendFile)((0, import_node_path8.join)(root, LOG_FILE), line, "utf8").catch((error) => {
|
|
4881
5063
|
this.logger.warn?.({
|
|
4882
5064
|
operation: "kb.log.append",
|
|
4883
5065
|
outcome: "failed",
|
|
@@ -4952,33 +5134,18 @@ function normalizeActor(id) {
|
|
|
4952
5134
|
if (colon === -1) return id.toLowerCase();
|
|
4953
5135
|
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
4954
5136
|
}
|
|
4955
|
-
function digest(contents) {
|
|
4956
|
-
return (0, import_node_crypto2.createHash)("sha256").update(contents).digest("hex");
|
|
4957
|
-
}
|
|
4958
|
-
function bundleDigest(records, superseded) {
|
|
4959
|
-
const entries = [
|
|
4960
|
-
...records.map(
|
|
4961
|
-
(hit) => `${hit.record.conceptId}:current:${digest(
|
|
4962
|
-
stringifyMarkdownWithFrontmatter(
|
|
4963
|
-
hit.record.body,
|
|
4964
|
-
hit.record.frontmatter
|
|
4965
|
-
)
|
|
4966
|
-
)}`
|
|
4967
|
-
),
|
|
4968
|
-
...superseded.map(
|
|
4969
|
-
(entry) => `${entry.conceptId}:superseded:${digest(JSON.stringify(entry))}`
|
|
4970
|
-
)
|
|
4971
|
-
].sort();
|
|
4972
|
-
return digest(entries.join("\n"));
|
|
4973
|
-
}
|
|
4974
5137
|
|
|
4975
5138
|
// src/version.ts
|
|
4976
|
-
var VERSION = true ? "0.1.
|
|
5139
|
+
var VERSION = true ? "0.1.16" : "0.0.0-dev";
|
|
4977
5140
|
|
|
4978
5141
|
// src/cli.ts
|
|
4979
5142
|
async function runKbCli(argv) {
|
|
4980
5143
|
const { flags, literal } = takeLiteral(argv);
|
|
4981
|
-
const {
|
|
5144
|
+
const {
|
|
5145
|
+
bundle,
|
|
5146
|
+
explicit: bundleExplicit,
|
|
5147
|
+
rest: withFlags
|
|
5148
|
+
} = takeBundle(flags);
|
|
4982
5149
|
const name = withFlags[0] ?? "";
|
|
4983
5150
|
if (!name || name === "-h" || name === "--help") {
|
|
4984
5151
|
process.stdout.write(usage());
|
|
@@ -4999,7 +5166,7 @@ async function runKbCli(argv) {
|
|
|
4999
5166
|
...json ? withFlags.filter((argument) => argument !== "--json") : withFlags,
|
|
5000
5167
|
...literal
|
|
5001
5168
|
];
|
|
5002
|
-
const raw = await command.fromArgv(rest, bundle, readStdin);
|
|
5169
|
+
const raw = await command.fromArgv(rest, bundle, readStdin, bundleExplicit);
|
|
5003
5170
|
const parsed = command.input.safeParse(raw);
|
|
5004
5171
|
if (!parsed.success) {
|
|
5005
5172
|
die(
|
|
@@ -5021,6 +5188,7 @@ async function runKbCli(argv) {
|
|
|
5021
5188
|
if (command.failsWhen?.(result, parsed.data)) process.exitCode = 1;
|
|
5022
5189
|
if (result === "") return;
|
|
5023
5190
|
const text = command.render && !json ? command.render(result) : typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
5191
|
+
if (text === "") return;
|
|
5024
5192
|
process.stdout.write(text.endsWith("\n") ? text : `${text}
|
|
5025
5193
|
`);
|
|
5026
5194
|
}
|
|
@@ -5032,11 +5200,15 @@ function takeLiteral(argv) {
|
|
|
5032
5200
|
function takeBundle(argv) {
|
|
5033
5201
|
const at = argv.indexOf("--bundle");
|
|
5034
5202
|
if (at === -1) {
|
|
5035
|
-
return { bundle: (0, import_node_path9.join)(process.cwd(), KB_DIR), rest: argv };
|
|
5203
|
+
return { bundle: (0, import_node_path9.join)(process.cwd(), KB_DIR), explicit: false, rest: argv };
|
|
5036
5204
|
}
|
|
5037
5205
|
const bundle = argv[at + 1];
|
|
5038
5206
|
if (!bundle) die("--bundle requires a path");
|
|
5039
|
-
return {
|
|
5207
|
+
return {
|
|
5208
|
+
bundle,
|
|
5209
|
+
explicit: true,
|
|
5210
|
+
rest: [...argv.slice(0, at), ...argv.slice(at + 2)]
|
|
5211
|
+
};
|
|
5040
5212
|
}
|
|
5041
5213
|
function readStdin() {
|
|
5042
5214
|
return new Promise((resolve6, reject) => {
|