@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/README.md
CHANGED
|
@@ -408,8 +408,8 @@ Worked flows:
|
|
|
408
408
|
|
|
409
409
|
**Placement is cache economics.** `load`'s output belongs in the stable
|
|
410
410
|
prefix — system prompt or first turn; `query` and `pack` results belong at
|
|
411
|
-
the tail. `digest` is the base's content stamp:
|
|
412
|
-
|
|
411
|
+
the tail. `digest` is the base's content stamp: `kb_stamp` and the opt-in
|
|
412
|
+
reload hook compare it to detect change, not the model. A
|
|
413
413
|
prompt cache matches a byte-for-byte prefix, so one volatile result ahead of
|
|
414
414
|
a stable load prices the base at full rate thereafter. Mechanism and digest
|
|
415
415
|
caveats: <https://saasontools.github.io/strauss-agent-tools/mcp-reference>.
|
|
@@ -451,6 +451,11 @@ carry no numbers:
|
|
|
451
451
|
}
|
|
452
452
|
```
|
|
453
453
|
|
|
454
|
+
A pinned base can also change mid-session — a `git pull`, a sub-agent's write.
|
|
455
|
+
`strauss-kb stamp` reports each pinned base's content digest. Comparing it for
|
|
456
|
+
you is opt-in, see the
|
|
457
|
+
[plugin README](../../plugins/strauss-kb/README.md#opt-in-workspace-hooks).
|
|
458
|
+
|
|
454
459
|
`sync-instructions <file>` keeps that block between
|
|
455
460
|
`<!-- strauss-kb:begin/end -->` sentinels in AGENTS.md or CLAUDE.md; it is
|
|
456
461
|
idempotent, and covers runtimes without a reliable post-compaction hook.
|
|
@@ -458,15 +463,16 @@ idempotent, and covers runtimes without a reliable post-compaction hook.
|
|
|
458
463
|
What each runtime gets (configs in the
|
|
459
464
|
[plugin's adapters](../../plugins/strauss-kb/adapters/)):
|
|
460
465
|
|
|
461
|
-
| Layer | Claude Code
|
|
462
|
-
| ------------------------- |
|
|
463
|
-
| MCP tool descriptions | ✓
|
|
464
|
-
| Session-start injection | SessionStart hook
|
|
465
|
-
| Post-compact re-injection | ✓ `compact` source
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
|
|
|
469
|
-
|
|
|
466
|
+
| Layer | Claude Code | Codex CLI | Antigravity CLI |
|
|
467
|
+
| ------------------------- | -------------------------------- | ------------------------------------------- | -------------------------- |
|
|
468
|
+
| MCP tool descriptions | ✓ | ✓ | ✓ |
|
|
469
|
+
| Session-start injection | SessionStart hook | SessionStart hook | PreInvocation, per turn |
|
|
470
|
+
| Post-compact re-injection | ✓ `compact` source | ✓ client-side; instruction-only when hosted | moot — injected every turn |
|
|
471
|
+
| Reload after a pull | opt-in PostToolUse, SubagentStop | opt-in PostToolUse on `shell` | moot — injected every turn |
|
|
472
|
+
| File-read blocking | opt-in PreToolUse | ✗ (shell is the side door) | opt-in PreToolUse, JSON |
|
|
473
|
+
| Manual-edit validation | opt-in PostToolUse | ✗ | ✗ |
|
|
474
|
+
| Generated-file edit guard | opt-in PreToolUse | ✗ | ✗ |
|
|
475
|
+
| Instruction file | CLAUDE.md | AGENTS.md | AGENTS.md + rules/ |
|
|
470
476
|
|
|
471
477
|
Never read record files directly — read through the tools; a raw read bypasses
|
|
472
478
|
standing, and a superseded record reads exactly like a current one. Enforce it
|
|
@@ -1273,6 +1273,8 @@ var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
|
1273
1273
|
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
1274
1274
|
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
1275
1275
|
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
1276
|
+
ErrorTypes2["KbStampBaselineUnreadable"] = "KbStampBaselineUnreadable";
|
|
1277
|
+
ErrorTypes2["KbStampDigestBaselineAmbiguous"] = "KbStampDigestBaselineAmbiguous";
|
|
1276
1278
|
ErrorTypes2["KbUnknownLinkRel"] = "KbUnknownLinkRel";
|
|
1277
1279
|
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
1278
1280
|
return ErrorTypes2;
|
|
@@ -1427,6 +1429,36 @@ var KbInvalidConceptIdError = class extends BaseError {
|
|
|
1427
1429
|
});
|
|
1428
1430
|
}
|
|
1429
1431
|
};
|
|
1432
|
+
var KbStampBaselineError = class extends BaseError {
|
|
1433
|
+
constructor(since) {
|
|
1434
|
+
super({
|
|
1435
|
+
message: `kb: --since ${since} is neither a 64-character digest nor a readable stamp file`,
|
|
1436
|
+
errorType: "KbStampBaselineUnreadable" /* KbStampBaselineUnreadable */,
|
|
1437
|
+
code: 400,
|
|
1438
|
+
fault: "User" /* User */,
|
|
1439
|
+
retriable: false,
|
|
1440
|
+
reportToUser: true,
|
|
1441
|
+
details: { since }
|
|
1442
|
+
});
|
|
1443
|
+
this.since = since;
|
|
1444
|
+
}
|
|
1445
|
+
since;
|
|
1446
|
+
};
|
|
1447
|
+
var KbStampDigestBaselineError = class extends BaseError {
|
|
1448
|
+
constructor(since) {
|
|
1449
|
+
super({
|
|
1450
|
+
message: `kb: --since ${since} is a digest, which needs --bundle (one base) \u2014 a file baseline works for many`,
|
|
1451
|
+
errorType: "KbStampDigestBaselineAmbiguous" /* KbStampDigestBaselineAmbiguous */,
|
|
1452
|
+
code: 400,
|
|
1453
|
+
fault: "User" /* User */,
|
|
1454
|
+
retriable: false,
|
|
1455
|
+
reportToUser: true,
|
|
1456
|
+
details: { since }
|
|
1457
|
+
});
|
|
1458
|
+
this.since = since;
|
|
1459
|
+
}
|
|
1460
|
+
since;
|
|
1461
|
+
};
|
|
1430
1462
|
|
|
1431
1463
|
// src/kb-pins/budgets.ts
|
|
1432
1464
|
function asBudgets(value) {
|
|
@@ -3611,17 +3643,115 @@ var schemaCommand = define({
|
|
|
3611
3643
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
3612
3644
|
});
|
|
3613
3645
|
|
|
3614
|
-
// src/commands/
|
|
3646
|
+
// src/commands/stamp.ts
|
|
3647
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
3615
3648
|
import { z as z25 } from "zod";
|
|
3649
|
+
var DIGEST = /^[0-9a-f]{64}$/;
|
|
3650
|
+
var stampCommand = define({
|
|
3651
|
+
name: "stamp",
|
|
3652
|
+
tool: "kb_stamp",
|
|
3653
|
+
usage: "stamp [--bundle PATH] [--since DIGEST|FILE]",
|
|
3654
|
+
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.",
|
|
3655
|
+
input: z25.object({
|
|
3656
|
+
bundlePath: z25.string().min(1).optional().describe(
|
|
3657
|
+
"Absolute path to one knowledge base. Omit to stamp every pinned base."
|
|
3658
|
+
),
|
|
3659
|
+
since: z25.string().min(1).optional().describe(
|
|
3660
|
+
"Prior digest, or path to a prior `stamp --json`; only moved bases return, with changed ids when the baseline is a file."
|
|
3661
|
+
)
|
|
3662
|
+
}),
|
|
3663
|
+
fromArgv: (argv, path, _stdin, bundleExplicit) => {
|
|
3664
|
+
const since = argvFlag(argv, "--since");
|
|
3665
|
+
return {
|
|
3666
|
+
...bundleExplicit ? { bundlePath: path } : {},
|
|
3667
|
+
...since !== void 0 ? { since } : {}
|
|
3668
|
+
};
|
|
3669
|
+
},
|
|
3670
|
+
run: async ({ store }, { bundlePath: bundlePath2, since }) => {
|
|
3671
|
+
const targets = bundlePath2 ? [bundlePath2] : (await readMergedPins(process.cwd())).pins.map(
|
|
3672
|
+
(pin) => pin.absolutePath
|
|
3673
|
+
);
|
|
3674
|
+
if (since !== void 0 && DIGEST.test(since) && targets.length > 1) {
|
|
3675
|
+
throw new KbStampDigestBaselineError(since);
|
|
3676
|
+
}
|
|
3677
|
+
const stamps = await Promise.all(
|
|
3678
|
+
targets.map((target) => store.stamp(target))
|
|
3679
|
+
);
|
|
3680
|
+
if (since === void 0) {
|
|
3681
|
+
return stamps.map((stamp) => ({ ...stamp, changed: null }));
|
|
3682
|
+
}
|
|
3683
|
+
const baseline = await readBaseline(since);
|
|
3684
|
+
const reports = [];
|
|
3685
|
+
for (const stamp of stamps) {
|
|
3686
|
+
const before = baseline.byPath.get(stamp.path);
|
|
3687
|
+
if (baseline.digest !== null) {
|
|
3688
|
+
if (baseline.digest === stamp.digest) continue;
|
|
3689
|
+
reports.push({ ...stamp, changed: null });
|
|
3690
|
+
continue;
|
|
3691
|
+
}
|
|
3692
|
+
if (before && before.digest === stamp.digest) continue;
|
|
3693
|
+
reports.push({ ...stamp, changed: changedIds(before?.records, stamp) });
|
|
3694
|
+
}
|
|
3695
|
+
return reports;
|
|
3696
|
+
},
|
|
3697
|
+
render: (result) => result.map((report) => {
|
|
3698
|
+
const counts = `${report.recordCount} record(s), ${report.superseded} superseded`;
|
|
3699
|
+
const head = `${report.path} ${report.digest} ${counts}${report.newestAt ? ` newest ${report.newestAt}` : ""}`;
|
|
3700
|
+
return report.changed?.length ? `${head}
|
|
3701
|
+
changed: ${report.changed.join(", ")}` : head;
|
|
3702
|
+
}).join("\n")
|
|
3703
|
+
});
|
|
3704
|
+
function changedIds(before, stamp) {
|
|
3705
|
+
const now = new Map(
|
|
3706
|
+
stamp.records.map((record) => [record.conceptId, record.digest])
|
|
3707
|
+
);
|
|
3708
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3709
|
+
for (const [conceptId2, digest] of now) {
|
|
3710
|
+
if (before?.get(conceptId2) !== digest) ids.add(conceptId2);
|
|
3711
|
+
}
|
|
3712
|
+
for (const conceptId2 of before?.keys() ?? []) {
|
|
3713
|
+
if (!now.has(conceptId2)) ids.add(conceptId2);
|
|
3714
|
+
}
|
|
3715
|
+
return [...ids].sort();
|
|
3716
|
+
}
|
|
3717
|
+
async function readBaseline(since) {
|
|
3718
|
+
if (DIGEST.test(since)) return { digest: since, byPath: /* @__PURE__ */ new Map() };
|
|
3719
|
+
let parsed;
|
|
3720
|
+
try {
|
|
3721
|
+
parsed = JSON.parse(await readFile4(since, "utf8"));
|
|
3722
|
+
} catch {
|
|
3723
|
+
throw new KbStampBaselineError(since);
|
|
3724
|
+
}
|
|
3725
|
+
const entries = Array.isArray(parsed) ? parsed : parsed?.stamps ?? [];
|
|
3726
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
3727
|
+
for (const entry of entries) {
|
|
3728
|
+
if (typeof entry?.path !== "string" || typeof entry?.digest !== "string") {
|
|
3729
|
+
continue;
|
|
3730
|
+
}
|
|
3731
|
+
byPath.set(entry.path, {
|
|
3732
|
+
digest: entry.digest,
|
|
3733
|
+
records: new Map(
|
|
3734
|
+
(entry.records ?? []).map((record) => [
|
|
3735
|
+
record.conceptId,
|
|
3736
|
+
record.digest
|
|
3737
|
+
])
|
|
3738
|
+
)
|
|
3739
|
+
});
|
|
3740
|
+
}
|
|
3741
|
+
return { digest: null, byPath };
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// src/commands/status.ts
|
|
3745
|
+
import { z as z26 } from "zod";
|
|
3616
3746
|
var statusCommand = define({
|
|
3617
3747
|
name: "status",
|
|
3618
3748
|
tool: "kb_status",
|
|
3619
3749
|
usage: "status <concept-id> <status>",
|
|
3620
3750
|
description: "Move a record's status. Compare-and-swap: a concurrent change fails instead of being overwritten.",
|
|
3621
|
-
input:
|
|
3751
|
+
input: z26.object({
|
|
3622
3752
|
bundlePath,
|
|
3623
3753
|
conceptId,
|
|
3624
|
-
status:
|
|
3754
|
+
status: z26.enum(KB_RECORD_STATUSES)
|
|
3625
3755
|
}),
|
|
3626
3756
|
fromArgv: (argv, path) => ({
|
|
3627
3757
|
bundlePath: path,
|
|
@@ -3636,13 +3766,13 @@ var statusCommand = define({
|
|
|
3636
3766
|
});
|
|
3637
3767
|
|
|
3638
3768
|
// src/commands/supersede.ts
|
|
3639
|
-
import { z as
|
|
3769
|
+
import { z as z27 } from "zod";
|
|
3640
3770
|
var supersedeCommand = define({
|
|
3641
3771
|
name: "supersede",
|
|
3642
3772
|
tool: "kb_supersede",
|
|
3643
3773
|
usage: "supersede <concept-id> <replacement-id>",
|
|
3644
3774
|
description: "Mark a record superseded by another, linked in both directions. Use instead of editing a record whose meaning changed.",
|
|
3645
|
-
input:
|
|
3775
|
+
input: z27.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
3646
3776
|
fromArgv: (argv, path) => ({
|
|
3647
3777
|
bundlePath: path,
|
|
3648
3778
|
conceptId: argv[1],
|
|
@@ -3656,16 +3786,16 @@ var supersedeCommand = define({
|
|
|
3656
3786
|
});
|
|
3657
3787
|
|
|
3658
3788
|
// src/commands/sync-instructions.ts
|
|
3659
|
-
import { z as
|
|
3789
|
+
import { z as z28 } from "zod";
|
|
3660
3790
|
var syncInstructionsCommand = define({
|
|
3661
3791
|
name: "sync-instructions",
|
|
3662
3792
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
3663
3793
|
description: "CLI-only: plant the kb_context block between sentinel comments in AGENTS.md or CLAUDE.md, idempotently.",
|
|
3664
|
-
input:
|
|
3665
|
-
file:
|
|
3666
|
-
budgetTokens:
|
|
3667
|
-
fullUnderTokens:
|
|
3668
|
-
profile:
|
|
3794
|
+
input: z28.object({
|
|
3795
|
+
file: z28.string().min(1).describe("The instruction file to edit in place."),
|
|
3796
|
+
budgetTokens: z28.number().int().positive().optional(),
|
|
3797
|
+
fullUnderTokens: z28.number().int().positive().optional(),
|
|
3798
|
+
profile: z28.string().optional()
|
|
3669
3799
|
}),
|
|
3670
3800
|
fromArgv: (argv) => {
|
|
3671
3801
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -3691,17 +3821,17 @@ var syncInstructionsCommand = define({
|
|
|
3691
3821
|
});
|
|
3692
3822
|
|
|
3693
3823
|
// src/commands/trace.ts
|
|
3694
|
-
import { z as
|
|
3824
|
+
import { z as z29 } from "zod";
|
|
3695
3825
|
var traceCommand = define({
|
|
3696
3826
|
name: "trace",
|
|
3697
3827
|
tool: "kb_trace",
|
|
3698
3828
|
usage: "trace <concept-id> [edges...]",
|
|
3699
3829
|
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".',
|
|
3700
|
-
input:
|
|
3830
|
+
input: z29.object({
|
|
3701
3831
|
bundlePath,
|
|
3702
3832
|
conceptId,
|
|
3703
|
-
edges:
|
|
3704
|
-
depth:
|
|
3833
|
+
edges: z29.array(z29.enum(TRACE_EDGES)).optional(),
|
|
3834
|
+
depth: z29.number().int().positive().optional()
|
|
3705
3835
|
}),
|
|
3706
3836
|
fromArgv: (argv, path) => ({
|
|
3707
3837
|
bundlePath: path,
|
|
@@ -3723,37 +3853,37 @@ var traceCommand = define({
|
|
|
3723
3853
|
});
|
|
3724
3854
|
|
|
3725
3855
|
// src/commands/types.ts
|
|
3726
|
-
import { z as
|
|
3856
|
+
import { z as z30 } from "zod";
|
|
3727
3857
|
var typesCommand = define({
|
|
3728
3858
|
name: "types",
|
|
3729
3859
|
tool: "kb_types",
|
|
3730
3860
|
usage: "types",
|
|
3731
3861
|
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.",
|
|
3732
|
-
input:
|
|
3862
|
+
input: z30.object({}),
|
|
3733
3863
|
fromArgv: () => ({}),
|
|
3734
3864
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
3735
3865
|
});
|
|
3736
3866
|
|
|
3737
3867
|
// src/commands/unpin.ts
|
|
3738
|
-
import { z as
|
|
3868
|
+
import { z as z31 } from "zod";
|
|
3739
3869
|
var unpinCommand = define({
|
|
3740
3870
|
name: "unpin",
|
|
3741
3871
|
tool: "kb_unpin",
|
|
3742
3872
|
usage: "unpin [bundle-path]",
|
|
3743
3873
|
description: "Remove a base from every manifest layer that holds it. Reports the layers touched.",
|
|
3744
|
-
input:
|
|
3874
|
+
input: z31.object({ bundlePath }),
|
|
3745
3875
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
3746
3876
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
3747
3877
|
});
|
|
3748
3878
|
|
|
3749
3879
|
// src/commands/validate.ts
|
|
3750
|
-
import { z as
|
|
3880
|
+
import { z as z32 } from "zod";
|
|
3751
3881
|
var validateCommand = define({
|
|
3752
3882
|
name: "validate",
|
|
3753
3883
|
tool: "kb_validate",
|
|
3754
3884
|
usage: "validate",
|
|
3755
3885
|
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.",
|
|
3756
|
-
input:
|
|
3886
|
+
input: z32.object({ bundlePath }),
|
|
3757
3887
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
3758
3888
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
3759
3889
|
// Warnings never fail the exit code; every other severity does.
|
|
@@ -3763,16 +3893,16 @@ var validateCommand = define({
|
|
|
3763
3893
|
});
|
|
3764
3894
|
|
|
3765
3895
|
// src/commands/verify.ts
|
|
3766
|
-
import { z as
|
|
3896
|
+
import { z as z33 } from "zod";
|
|
3767
3897
|
var verifyCommand = define({
|
|
3768
3898
|
name: "verify",
|
|
3769
3899
|
tool: "kb_verify",
|
|
3770
3900
|
usage: "verify <concept-id> --note <text>",
|
|
3771
3901
|
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.",
|
|
3772
|
-
input:
|
|
3902
|
+
input: z33.object({
|
|
3773
3903
|
bundlePath,
|
|
3774
3904
|
conceptId,
|
|
3775
|
-
note:
|
|
3905
|
+
note: z33.string().refine((s) => s.trim().length > 0, {
|
|
3776
3906
|
message: "note must say what the check found"
|
|
3777
3907
|
})
|
|
3778
3908
|
}),
|
|
@@ -3792,15 +3922,15 @@ var verifyCommand = define({
|
|
|
3792
3922
|
});
|
|
3793
3923
|
|
|
3794
3924
|
// src/commands/write.ts
|
|
3795
|
-
import { z as
|
|
3925
|
+
import { z as z34 } from "zod";
|
|
3796
3926
|
var writeCommand = define({
|
|
3797
3927
|
name: "write",
|
|
3798
3928
|
tool: "kb_write",
|
|
3799
3929
|
usage: "write <type> < record.json",
|
|
3800
3930
|
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.",
|
|
3801
|
-
input:
|
|
3931
|
+
input: z34.object({
|
|
3802
3932
|
bundlePath,
|
|
3803
|
-
type:
|
|
3933
|
+
type: z34.enum(KB_RECORD_TYPES),
|
|
3804
3934
|
input: composeInputSchema
|
|
3805
3935
|
}),
|
|
3806
3936
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -3824,13 +3954,13 @@ var writeCommand = define({
|
|
|
3824
3954
|
});
|
|
3825
3955
|
|
|
3826
3956
|
// src/commands/write-decision.ts
|
|
3827
|
-
import { z as
|
|
3957
|
+
import { z as z35 } from "zod";
|
|
3828
3958
|
var writeDecisionCommand = define({
|
|
3829
3959
|
name: "write-decision",
|
|
3830
3960
|
tool: "kb_write_decision",
|
|
3831
3961
|
usage: "write-decision < decision.json",
|
|
3832
3962
|
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.",
|
|
3833
|
-
input:
|
|
3963
|
+
input: z35.object({ bundlePath, input: decisionInputSchema }),
|
|
3834
3964
|
fromArgv: async (_argv, path, stdin) => ({
|
|
3835
3965
|
bundlePath: path,
|
|
3836
3966
|
input: JSON.parse(await stdin())
|
|
@@ -3870,6 +4000,7 @@ var KB_COMMANDS = [
|
|
|
3870
4000
|
listCommand,
|
|
3871
4001
|
readIndexCommand,
|
|
3872
4002
|
logCommand,
|
|
4003
|
+
stampCommand,
|
|
3873
4004
|
validateCommand,
|
|
3874
4005
|
doctorCommand,
|
|
3875
4006
|
schemaCommand,
|
|
@@ -3999,19 +4130,50 @@ async function loadQmd(logger) {
|
|
|
3999
4130
|
}
|
|
4000
4131
|
|
|
4001
4132
|
// src/kb-store.ts
|
|
4002
|
-
import { createHash as createHash2 } from "crypto";
|
|
4003
4133
|
import {
|
|
4004
4134
|
appendFile,
|
|
4005
4135
|
link,
|
|
4006
4136
|
mkdir as mkdir3,
|
|
4007
4137
|
readdir,
|
|
4008
|
-
readFile as
|
|
4138
|
+
readFile as readFile5,
|
|
4009
4139
|
rename,
|
|
4010
4140
|
unlink,
|
|
4011
4141
|
writeFile as writeFile3
|
|
4012
4142
|
} from "fs/promises";
|
|
4013
4143
|
import { join as join5, resolve as resolve5, sep as sep3 } from "path";
|
|
4014
4144
|
|
|
4145
|
+
// src/kb-stamp.ts
|
|
4146
|
+
import { createHash as createHash2 } from "crypto";
|
|
4147
|
+
function sha256(contents) {
|
|
4148
|
+
return createHash2("sha256").update(contents).digest("hex");
|
|
4149
|
+
}
|
|
4150
|
+
function bundleStamp(records, superseded) {
|
|
4151
|
+
const entries = [
|
|
4152
|
+
...records.map((hit) => ({
|
|
4153
|
+
conceptId: hit.record.conceptId,
|
|
4154
|
+
digest: `current:${sha256(
|
|
4155
|
+
stringifyMarkdownWithFrontmatter(
|
|
4156
|
+
hit.record.body,
|
|
4157
|
+
hit.record.frontmatter
|
|
4158
|
+
)
|
|
4159
|
+
)}`
|
|
4160
|
+
})),
|
|
4161
|
+
...superseded.map((entry) => ({
|
|
4162
|
+
conceptId: entry.conceptId,
|
|
4163
|
+
digest: `superseded:${sha256(JSON.stringify(entry))}`
|
|
4164
|
+
}))
|
|
4165
|
+
].sort((a, b) => a.conceptId < b.conceptId ? -1 : 1);
|
|
4166
|
+
return {
|
|
4167
|
+
digest: sha256(
|
|
4168
|
+
entries.map((entry) => `${entry.conceptId}:${entry.digest}`).join("\n")
|
|
4169
|
+
),
|
|
4170
|
+
records: entries
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
function bundleDigest(records, superseded) {
|
|
4174
|
+
return bundleStamp(records, superseded).digest;
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4015
4177
|
// src/kb-links/inbound.ts
|
|
4016
4178
|
function inboundIndex(bundle) {
|
|
4017
4179
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -4242,7 +4404,7 @@ var KbStore = class {
|
|
|
4242
4404
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
4243
4405
|
let raw;
|
|
4244
4406
|
try {
|
|
4245
|
-
raw = await
|
|
4407
|
+
raw = await readFile5(target, "utf8");
|
|
4246
4408
|
} catch {
|
|
4247
4409
|
return null;
|
|
4248
4410
|
}
|
|
@@ -4267,7 +4429,7 @@ var KbStore = class {
|
|
|
4267
4429
|
const records = await mapLimit(
|
|
4268
4430
|
wanted,
|
|
4269
4431
|
DEFAULT_IO_CONCURRENCY,
|
|
4270
|
-
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await
|
|
4432
|
+
async ({ name, conceptId: conceptId2 }) => this.parse(conceptId2, await readFile5(join5(root, name), "utf8"))
|
|
4271
4433
|
);
|
|
4272
4434
|
return records.filter((record) => record !== null);
|
|
4273
4435
|
}
|
|
@@ -4542,6 +4704,28 @@ ${answer}
|
|
|
4542
4704
|
digest: bundleDigestValue
|
|
4543
4705
|
};
|
|
4544
4706
|
}
|
|
4707
|
+
/**
|
|
4708
|
+
* `load`'s digest without `load`'s bodies — the same records, adjudicated
|
|
4709
|
+
* the same way, handed back as a stamp. Skips the anchor drift pass, which
|
|
4710
|
+
* reads source files and only ever adds warnings: no warning reaches the
|
|
4711
|
+
* digest, so the value is identical to the one `load` returns.
|
|
4712
|
+
*/
|
|
4713
|
+
async stamp(bundlePath2) {
|
|
4714
|
+
const bundle = await this.list(bundlePath2);
|
|
4715
|
+
const adjudicated = adjudicate(bundle, bundle, /* @__PURE__ */ new Date());
|
|
4716
|
+
const current = adjudicated.filter((hit) => hit.standing !== "superseded");
|
|
4717
|
+
const superseded = adjudicated.filter((hit) => hit.standing === "superseded").map(stub);
|
|
4718
|
+
const stamped = bundleStamp(current, superseded);
|
|
4719
|
+
const dates = bundle.map((record) => record.frontmatter.generated?.at ?? null).filter((at) => typeof at === "string").sort();
|
|
4720
|
+
return {
|
|
4721
|
+
path: bundlePath2,
|
|
4722
|
+
digest: stamped.digest,
|
|
4723
|
+
recordCount: bundle.length,
|
|
4724
|
+
superseded: superseded.length,
|
|
4725
|
+
newestAt: dates.at(-1) ?? null,
|
|
4726
|
+
records: stamped.records
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4545
4729
|
/** How a position was arrived at, as a timeline. See `trace.ts`. */
|
|
4546
4730
|
async trace(bundlePath2, seedId, options = {}) {
|
|
4547
4731
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
@@ -4572,7 +4756,7 @@ ${answer}
|
|
|
4572
4756
|
async readIndex(bundlePath2) {
|
|
4573
4757
|
const root = this.root(bundlePath2);
|
|
4574
4758
|
const expected = renderIndex(await this.list(bundlePath2));
|
|
4575
|
-
const stored = await
|
|
4759
|
+
const stored = await readFile5(join5(root, INDEX_FILE), "utf8").catch(
|
|
4576
4760
|
() => null
|
|
4577
4761
|
);
|
|
4578
4762
|
if (indexIsStale(stored, expected)) {
|
|
@@ -4593,7 +4777,7 @@ ${answer}
|
|
|
4593
4777
|
* knows which agent touched what. So a bad line is surfaced and left alone.
|
|
4594
4778
|
*/
|
|
4595
4779
|
async readLog(bundlePath2) {
|
|
4596
|
-
const raw = await
|
|
4780
|
+
const raw = await readFile5(
|
|
4597
4781
|
join5(this.root(bundlePath2), LOG_FILE),
|
|
4598
4782
|
"utf8"
|
|
4599
4783
|
).catch(() => "");
|
|
@@ -4645,15 +4829,15 @@ ${answer}
|
|
|
4645
4829
|
}
|
|
4646
4830
|
async mutate(bundlePath2, conceptId2, change, entry, changeBody = (body) => body) {
|
|
4647
4831
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
4648
|
-
const before = await
|
|
4832
|
+
const before = await readFile5(target, "utf8").catch(() => null);
|
|
4649
4833
|
if (before === null) throw new KbRecordNotFoundError(conceptId2);
|
|
4650
4834
|
const parsed = this.parse(conceptId2, before);
|
|
4651
4835
|
if (!parsed) throw new KbRecordNotFoundError(conceptId2);
|
|
4652
4836
|
const frontmatter = change(parsed.frontmatter);
|
|
4653
4837
|
const body = changeBody(parsed.body);
|
|
4654
4838
|
const contents = stringifyMarkdownWithFrontmatter(body, frontmatter);
|
|
4655
|
-
const witness = await
|
|
4656
|
-
if (witness === null ||
|
|
4839
|
+
const witness = await readFile5(target, "utf8").catch(() => null);
|
|
4840
|
+
if (witness === null || sha256(witness) !== sha256(before)) {
|
|
4657
4841
|
throw new KbWriteConflictError(conceptId2);
|
|
4658
4842
|
}
|
|
4659
4843
|
await this.publish(target, contents, true, conceptId2);
|
|
@@ -4739,7 +4923,7 @@ ${answer}
|
|
|
4739
4923
|
try {
|
|
4740
4924
|
let existing;
|
|
4741
4925
|
try {
|
|
4742
|
-
existing = await
|
|
4926
|
+
existing = await readFile5(target, "utf8");
|
|
4743
4927
|
} catch (error) {
|
|
4744
4928
|
if (error.code !== "ENOENT") throw error;
|
|
4745
4929
|
existing = null;
|
|
@@ -4861,25 +5045,6 @@ function normalizeActor(id) {
|
|
|
4861
5045
|
if (colon === -1) return id.toLowerCase();
|
|
4862
5046
|
return id.slice(0, colon + 1).toLowerCase() + id.slice(colon + 1);
|
|
4863
5047
|
}
|
|
4864
|
-
function digest(contents) {
|
|
4865
|
-
return createHash2("sha256").update(contents).digest("hex");
|
|
4866
|
-
}
|
|
4867
|
-
function bundleDigest(records, superseded) {
|
|
4868
|
-
const entries = [
|
|
4869
|
-
...records.map(
|
|
4870
|
-
(hit) => `${hit.record.conceptId}:current:${digest(
|
|
4871
|
-
stringifyMarkdownWithFrontmatter(
|
|
4872
|
-
hit.record.body,
|
|
4873
|
-
hit.record.frontmatter
|
|
4874
|
-
)
|
|
4875
|
-
)}`
|
|
4876
|
-
),
|
|
4877
|
-
...superseded.map(
|
|
4878
|
-
(entry) => `${entry.conceptId}:superseded:${digest(JSON.stringify(entry))}`
|
|
4879
|
-
)
|
|
4880
|
-
].sort();
|
|
4881
|
-
return digest(entries.join("\n"));
|
|
4882
|
-
}
|
|
4883
5048
|
|
|
4884
5049
|
// src/pack.ts
|
|
4885
5050
|
var DEFAULT_PACK_HOPS = 2;
|
|
@@ -4966,7 +5131,7 @@ function typeRank(record) {
|
|
|
4966
5131
|
}
|
|
4967
5132
|
|
|
4968
5133
|
// src/version.ts
|
|
4969
|
-
var VERSION = true ? "0.1.
|
|
5134
|
+
var VERSION = true ? "0.1.16" : "0.0.0-dev";
|
|
4970
5135
|
|
|
4971
5136
|
export {
|
|
4972
5137
|
kbSourceSchema,
|
|
@@ -5082,4 +5247,4 @@ export {
|
|
|
5082
5247
|
KbStore,
|
|
5083
5248
|
VERSION
|
|
5084
5249
|
};
|
|
5085
|
-
//# sourceMappingURL=chunk-
|
|
5250
|
+
//# sourceMappingURL=chunk-H5W53NVU.js.map
|