@saasontools/strauss-kb 0.1.8 → 0.1.10
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/ARCHITECTURE.md +89 -0
- package/README.md +112 -39
- package/dist/{chunk-YJK7KGHN.js → chunk-EJQPZWN5.js} +518 -234
- package/dist/chunk-EJQPZWN5.js.map +1 -0
- package/dist/{chunk-YYX6CX6V.js → chunk-NMTP7V7E.js} +2 -2
- package/dist/{chunk-MGPYUZOM.js → chunk-RGK3K6LN.js} +2 -2
- package/dist/cli-main.cjs +504 -222
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +374 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -5
- package/dist/index.d.ts +146 -5
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-main.cjs +504 -222
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-YJK7KGHN.js.map +0 -1
- /package/dist/{chunk-YYX6CX6V.js.map → chunk-NMTP7V7E.js.map} +0 -0
- /package/dist/{chunk-MGPYUZOM.js.map → chunk-RGK3K6LN.js.map} +0 -0
|
@@ -586,6 +586,157 @@ async function unpinBase(workspaceDir, bundlePath2) {
|
|
|
586
586
|
};
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
+
// src/errors.ts
|
|
590
|
+
var Fault = /* @__PURE__ */ ((Fault2) => {
|
|
591
|
+
Fault2["Configuration"] = "Configuration";
|
|
592
|
+
Fault2["System"] = "System";
|
|
593
|
+
Fault2["User"] = "User";
|
|
594
|
+
return Fault2;
|
|
595
|
+
})(Fault || {});
|
|
596
|
+
var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
597
|
+
ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
|
|
598
|
+
ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
|
|
599
|
+
ErrorTypes2["KbMissingFlagValue"] = "KbMissingFlagValue";
|
|
600
|
+
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
601
|
+
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
602
|
+
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
603
|
+
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
604
|
+
return ErrorTypes2;
|
|
605
|
+
})(ErrorTypes || {});
|
|
606
|
+
var BaseError = class extends Error {
|
|
607
|
+
code;
|
|
608
|
+
errorType;
|
|
609
|
+
fault;
|
|
610
|
+
retriable;
|
|
611
|
+
reportToUser;
|
|
612
|
+
details;
|
|
613
|
+
constructor(props) {
|
|
614
|
+
super(props.message);
|
|
615
|
+
this.name = props.name ?? this.constructor.name;
|
|
616
|
+
this.code = props.code ?? 500;
|
|
617
|
+
this.errorType = props.errorType;
|
|
618
|
+
this.fault = props.fault;
|
|
619
|
+
this.retriable = props.retriable ?? true;
|
|
620
|
+
this.reportToUser = props.reportToUser ?? false;
|
|
621
|
+
this.details = props.details;
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// src/kb-errors.ts
|
|
626
|
+
var KbRecordAlreadyExistsError = class extends BaseError {
|
|
627
|
+
constructor(conceptId2) {
|
|
628
|
+
super({
|
|
629
|
+
message: `kb: ${conceptId2} already exists \u2014 choose a more specific slug, or write with overwrite`,
|
|
630
|
+
errorType: "KbRecordAlreadyExists" /* KbRecordAlreadyExists */,
|
|
631
|
+
code: 409,
|
|
632
|
+
fault: "User" /* User */,
|
|
633
|
+
retriable: false,
|
|
634
|
+
reportToUser: true,
|
|
635
|
+
details: { conceptId: conceptId2, action: "refused" }
|
|
636
|
+
});
|
|
637
|
+
this.conceptId = conceptId2;
|
|
638
|
+
}
|
|
639
|
+
conceptId;
|
|
640
|
+
};
|
|
641
|
+
var KbRecordNotFoundError = class extends BaseError {
|
|
642
|
+
constructor(conceptId2) {
|
|
643
|
+
super({
|
|
644
|
+
message: `kb: ${conceptId2} does not exist`,
|
|
645
|
+
errorType: "KbRecordNotFound" /* KbRecordNotFound */,
|
|
646
|
+
code: 404,
|
|
647
|
+
fault: "User" /* User */,
|
|
648
|
+
retriable: false,
|
|
649
|
+
reportToUser: true,
|
|
650
|
+
details: { conceptId: conceptId2 }
|
|
651
|
+
});
|
|
652
|
+
this.conceptId = conceptId2;
|
|
653
|
+
}
|
|
654
|
+
conceptId;
|
|
655
|
+
};
|
|
656
|
+
var KbWriteConflictError = class extends BaseError {
|
|
657
|
+
constructor(conceptId2) {
|
|
658
|
+
super({
|
|
659
|
+
message: `kb: ${conceptId2} changed while it was being updated \u2014 re-read and retry`,
|
|
660
|
+
errorType: "KbWriteConflict" /* KbWriteConflict */,
|
|
661
|
+
code: 409,
|
|
662
|
+
fault: "System" /* System */,
|
|
663
|
+
retriable: true,
|
|
664
|
+
reportToUser: true,
|
|
665
|
+
details: { conceptId: conceptId2 }
|
|
666
|
+
});
|
|
667
|
+
this.conceptId = conceptId2;
|
|
668
|
+
}
|
|
669
|
+
conceptId;
|
|
670
|
+
};
|
|
671
|
+
var KbSelfVerificationError = class extends BaseError {
|
|
672
|
+
constructor(conceptId2, actor, generatedBy) {
|
|
673
|
+
super({
|
|
674
|
+
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
675
|
+
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
676
|
+
code: 400,
|
|
677
|
+
fault: "User" /* User */,
|
|
678
|
+
retriable: false,
|
|
679
|
+
reportToUser: true,
|
|
680
|
+
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
681
|
+
});
|
|
682
|
+
this.conceptId = conceptId2;
|
|
683
|
+
this.actor = actor;
|
|
684
|
+
this.generatedBy = generatedBy;
|
|
685
|
+
}
|
|
686
|
+
conceptId;
|
|
687
|
+
actor;
|
|
688
|
+
generatedBy;
|
|
689
|
+
};
|
|
690
|
+
var KbPackBudgetExceededError = class extends BaseError {
|
|
691
|
+
constructor(recordCount, approxTokens2, budgetTokens, excluded) {
|
|
692
|
+
super({
|
|
693
|
+
message: `kb: a pack of ${recordCount} records is ~${approxTokens2} tokens against a budget of ${budgetTokens} \u2014 lower hops or maxNodes, or raise the budget`,
|
|
694
|
+
errorType: "KbPackBudgetExceeded" /* KbPackBudgetExceeded */,
|
|
695
|
+
code: 400,
|
|
696
|
+
fault: "User" /* User */,
|
|
697
|
+
retriable: false,
|
|
698
|
+
reportToUser: true,
|
|
699
|
+
details: { recordCount, approxTokens: approxTokens2, budgetTokens, excluded }
|
|
700
|
+
});
|
|
701
|
+
this.recordCount = recordCount;
|
|
702
|
+
this.approxTokens = approxTokens2;
|
|
703
|
+
this.budgetTokens = budgetTokens;
|
|
704
|
+
this.excluded = excluded;
|
|
705
|
+
}
|
|
706
|
+
recordCount;
|
|
707
|
+
approxTokens;
|
|
708
|
+
budgetTokens;
|
|
709
|
+
excluded;
|
|
710
|
+
};
|
|
711
|
+
var KbMissingFlagValueError = class extends BaseError {
|
|
712
|
+
constructor(flag) {
|
|
713
|
+
super({
|
|
714
|
+
message: `kb: ${flag} needs a value \u2014 pass ${flag} <value> or ${flag}=<value>`,
|
|
715
|
+
errorType: "KbMissingFlagValue" /* KbMissingFlagValue */,
|
|
716
|
+
code: 400,
|
|
717
|
+
fault: "User" /* User */,
|
|
718
|
+
retriable: false,
|
|
719
|
+
reportToUser: true,
|
|
720
|
+
details: { flag }
|
|
721
|
+
});
|
|
722
|
+
this.flag = flag;
|
|
723
|
+
}
|
|
724
|
+
flag;
|
|
725
|
+
};
|
|
726
|
+
var KbInvalidConceptIdError = class extends BaseError {
|
|
727
|
+
constructor(message, details) {
|
|
728
|
+
super({
|
|
729
|
+
message: `kb: ${message}`,
|
|
730
|
+
errorType: "KbInvalidConceptId" /* KbInvalidConceptId */,
|
|
731
|
+
code: 400,
|
|
732
|
+
fault: "User" /* User */,
|
|
733
|
+
retriable: false,
|
|
734
|
+
reportToUser: true,
|
|
735
|
+
details
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
|
|
589
740
|
// src/adjudicate.ts
|
|
590
741
|
var STANDING = {
|
|
591
742
|
accepted: "current",
|
|
@@ -678,6 +829,52 @@ function successors(record, byId) {
|
|
|
678
829
|
return { records, missing };
|
|
679
830
|
}
|
|
680
831
|
|
|
832
|
+
// src/catalog.ts
|
|
833
|
+
var EMPTY_STANDINGS = {
|
|
834
|
+
current: 0,
|
|
835
|
+
superseded: 0,
|
|
836
|
+
rejected: 0,
|
|
837
|
+
unsettled: 0,
|
|
838
|
+
open: 0
|
|
839
|
+
};
|
|
840
|
+
function catalog(bundle, options = {}) {
|
|
841
|
+
const wanted = options.type ? bundle.filter((record) => record.frontmatter.type === options.type) : bundle;
|
|
842
|
+
const entries = adjudicate(wanted, bundle, options.now ?? /* @__PURE__ */ new Date()).map((hit) => ({
|
|
843
|
+
conceptId: hit.record.conceptId,
|
|
844
|
+
type: hit.record.frontmatter.type,
|
|
845
|
+
title: hit.record.frontmatter.title ?? null,
|
|
846
|
+
standing: hit.standing,
|
|
847
|
+
supersededBy: hit.heads.map((head) => head.conceptId),
|
|
848
|
+
stale: hit.warnings.some((warning) => warning.kind === "stale")
|
|
849
|
+
})).sort(byTypeThenTitle);
|
|
850
|
+
const standings = { ...EMPTY_STANDINGS };
|
|
851
|
+
for (const entry of entries) standings[entry.standing] += 1;
|
|
852
|
+
return {
|
|
853
|
+
entries,
|
|
854
|
+
recordCount: entries.length,
|
|
855
|
+
standings,
|
|
856
|
+
currentCount: standings.current,
|
|
857
|
+
supersededCount: standings.superseded,
|
|
858
|
+
staleCount: entries.filter((entry) => entry.stale).length
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function byTypeThenTitle(left, right) {
|
|
862
|
+
return byCodeUnit(left.type, right.type) || byCodeUnit(left.title ?? "", right.title ?? "") || byCodeUnit(left.conceptId, right.conceptId);
|
|
863
|
+
}
|
|
864
|
+
function byCodeUnit(left, right) {
|
|
865
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
866
|
+
}
|
|
867
|
+
function renderCatalogLine(entry) {
|
|
868
|
+
const parts = [
|
|
869
|
+
entry.conceptId,
|
|
870
|
+
entry.type,
|
|
871
|
+
entry.title ?? "(untitled)",
|
|
872
|
+
entry.standing === "superseded" ? `superseded \u2192 ${entry.supersededBy.join(", ") || "(no surviving head)"}` : entry.standing
|
|
873
|
+
];
|
|
874
|
+
if (entry.stale) parts.push("stale");
|
|
875
|
+
return `- ${parts.join(" \xB7 ")}`;
|
|
876
|
+
}
|
|
877
|
+
|
|
681
878
|
// src/kb-index.ts
|
|
682
879
|
var INDEX_FILE = "INDEX.md";
|
|
683
880
|
var HEADING = "# KB Index";
|
|
@@ -1301,7 +1498,15 @@ function ageInDays(record, now) {
|
|
|
1301
1498
|
import { z as z5 } from "zod";
|
|
1302
1499
|
var LOG_FILE = "log.jsonl";
|
|
1303
1500
|
var kbLogEntrySchema = z5.object({
|
|
1304
|
-
|
|
1501
|
+
// Validated, not just `min(1)`: `at` is a sort key (see `parseLog`
|
|
1502
|
+
// below), and a value that isn't actually chronological — a Unix
|
|
1503
|
+
// timestamp, a human-typed date, garbage — would sort wrong without
|
|
1504
|
+
// ever failing to parse. `z.iso.datetime()` accepts exactly what
|
|
1505
|
+
// `record()` writes (`Date#toISOString()`: full precision, `Z` offset)
|
|
1506
|
+
// and rejects everything else, including a non-`Z` offset — so a
|
|
1507
|
+
// malformed `at` is reported the same way a malformed line already is,
|
|
1508
|
+
// rather than silently sorting into the wrong place.
|
|
1509
|
+
at: z5.iso.datetime(),
|
|
1305
1510
|
by: z5.string().min(1),
|
|
1306
1511
|
operation: z5.string().min(1),
|
|
1307
1512
|
conceptId: z5.string().min(1),
|
|
@@ -1315,6 +1520,7 @@ function renderLogEntry(entry) {
|
|
|
1315
1520
|
function parseLog(raw) {
|
|
1316
1521
|
const entries = [];
|
|
1317
1522
|
const malformed = [];
|
|
1523
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1318
1524
|
raw.split("\n").forEach((text, index) => {
|
|
1319
1525
|
if (!text.trim()) return;
|
|
1320
1526
|
let value;
|
|
@@ -1329,8 +1535,14 @@ function parseLog(raw) {
|
|
|
1329
1535
|
malformed.push({ line: index + 1, text });
|
|
1330
1536
|
return;
|
|
1331
1537
|
}
|
|
1538
|
+
const key = JSON.stringify(parsed.data);
|
|
1539
|
+
if (seen.has(key)) return;
|
|
1540
|
+
seen.add(key);
|
|
1332
1541
|
entries.push(parsed.data);
|
|
1333
1542
|
});
|
|
1543
|
+
entries.sort(
|
|
1544
|
+
(left, right) => left.at < right.at ? -1 : left.at > right.at ? 1 : 0
|
|
1545
|
+
);
|
|
1334
1546
|
return { entries, malformed };
|
|
1335
1547
|
}
|
|
1336
1548
|
|
|
@@ -1395,8 +1607,19 @@ function define(command) {
|
|
|
1395
1607
|
return command;
|
|
1396
1608
|
}
|
|
1397
1609
|
function argvFlag(argv, name) {
|
|
1610
|
+
const joined = argv.find((arg) => arg.startsWith(`${name}=`));
|
|
1611
|
+
if (joined !== void 0) {
|
|
1612
|
+
const value2 = joined.slice(name.length + 1);
|
|
1613
|
+
if (!value2) throw new KbMissingFlagValueError(name);
|
|
1614
|
+
return value2;
|
|
1615
|
+
}
|
|
1398
1616
|
const at = argv.indexOf(name);
|
|
1399
|
-
|
|
1617
|
+
if (at === -1) return void 0;
|
|
1618
|
+
const value = argv[at + 1];
|
|
1619
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
1620
|
+
throw new KbMissingFlagValueError(name);
|
|
1621
|
+
}
|
|
1622
|
+
return value;
|
|
1400
1623
|
}
|
|
1401
1624
|
|
|
1402
1625
|
// src/commands/answer.ts
|
|
@@ -1418,27 +1641,90 @@ var answerCommand = define({
|
|
|
1418
1641
|
}
|
|
1419
1642
|
});
|
|
1420
1643
|
|
|
1421
|
-
// src/commands/
|
|
1644
|
+
// src/commands/catalog.ts
|
|
1422
1645
|
import { z as z9 } from "zod";
|
|
1646
|
+
var catalogCommand = define({
|
|
1647
|
+
name: "catalog",
|
|
1648
|
+
tool: "kb_catalog",
|
|
1649
|
+
usage: "catalog [type]",
|
|
1650
|
+
description: "Lists every record as one line \u2014 concept id, type, title, standing, and a stale flag \u2014 at roughly thirty tokens each. Pick this over kb_load once kb_load refuses: kb_catalog never refuses. Superseded records show only their replacement; fetch bodies with kb_load, kb_pack, kb_query, or kb_trace.",
|
|
1651
|
+
input: z9.object({
|
|
1652
|
+
bundlePath,
|
|
1653
|
+
type: z9.enum(KB_RECORD_TYPES).optional()
|
|
1654
|
+
}),
|
|
1655
|
+
fromArgv: (argv, path) => ({
|
|
1656
|
+
bundlePath: path,
|
|
1657
|
+
...argv[1] && !argv[1].startsWith("--") ? { type: argv[1] } : {}
|
|
1658
|
+
}),
|
|
1659
|
+
run: async ({ store }, { bundlePath: path, type }) => render(
|
|
1660
|
+
await store.catalog(path, { ...type ? { type } : {} }),
|
|
1661
|
+
path,
|
|
1662
|
+
type
|
|
1663
|
+
)
|
|
1664
|
+
});
|
|
1665
|
+
function render(result, bundle, type) {
|
|
1666
|
+
const lines = [
|
|
1667
|
+
`# KB Catalog${type ? ` \u2014 ${type}` : ""}`,
|
|
1668
|
+
`bundle: ${bundle}`,
|
|
1669
|
+
`${count(result.recordCount, "record")}: ${standingCounts(result)}`
|
|
1670
|
+
];
|
|
1671
|
+
if (result.staleCount) {
|
|
1672
|
+
lines.push(
|
|
1673
|
+
`${result.staleCount} stale \u2014 a flag over the standings above, not one of them`
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
lines.push("");
|
|
1677
|
+
if (!result.entries.length) {
|
|
1678
|
+
lines.push(
|
|
1679
|
+
type ? `(no records of type ${type})` : "(no records \u2014 this base is empty)"
|
|
1680
|
+
);
|
|
1681
|
+
} else {
|
|
1682
|
+
for (const entry of result.entries) lines.push(renderCatalogLine(entry));
|
|
1683
|
+
}
|
|
1684
|
+
lines.push(
|
|
1685
|
+
"",
|
|
1686
|
+
"Bodies are not here: kb_pack <conceptId> for the neighbourhood around one record, kb_load for the whole base when it fits the budget, kb_query for a lookup by wording, kb_trace <conceptId> for how a position was arrived at."
|
|
1687
|
+
);
|
|
1688
|
+
return lines.join("\n");
|
|
1689
|
+
}
|
|
1690
|
+
function standingCounts(result) {
|
|
1691
|
+
const ORDER = [
|
|
1692
|
+
"current",
|
|
1693
|
+
"open",
|
|
1694
|
+
"unsettled",
|
|
1695
|
+
"rejected",
|
|
1696
|
+
"superseded"
|
|
1697
|
+
];
|
|
1698
|
+
const parts = ORDER.filter((standing) => result.standings[standing]).map(
|
|
1699
|
+
(standing) => `${result.standings[standing]} ${standing}`
|
|
1700
|
+
);
|
|
1701
|
+
return parts.length ? parts.join(" \xB7 ") : "none";
|
|
1702
|
+
}
|
|
1703
|
+
function count(value, noun) {
|
|
1704
|
+
return `${value} ${value === 1 ? noun : `${noun}s`}`;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/commands/context.ts
|
|
1708
|
+
import { z as z10 } from "zod";
|
|
1423
1709
|
var contextCommand = define({
|
|
1424
1710
|
name: "context",
|
|
1425
1711
|
tool: "kb_context",
|
|
1426
1712
|
usage: "context [--profile NAME] [--budget N] [--full-under N] [--format json] [--event NAME]",
|
|
1427
1713
|
description: "The pinned-base index block, for injection at every context birth \u2014 startup, clear, resume, and after compaction. An index, not the content: concept ids, titles and standing, with the bodies left behind kb_load at the point of use. Emits nothing when nothing is pinned. Refuses with the list of bases and their sizes rather than truncating past its budget. Budgets resolve most-specific-first: explicit flags, then the workspace manifests' `context` tables (per profile, over their `default`), then the built-in profile (session-start, compact, turn), then package defaults \u2014 so a repo tunes its own numbers in .strauss/kb-pins.json without touching hook commands. Like kb_schema and kb_types this takes no bundlePath \u2014 it reads the workspace pin manifests, because which bases a session should see is workspace state, not a property of one base.",
|
|
1428
|
-
input:
|
|
1429
|
-
budgetTokens:
|
|
1714
|
+
input: z10.object({
|
|
1715
|
+
budgetTokens: z10.number().int().positive().optional().describe(
|
|
1430
1716
|
"Ceiling on the whole emitted block; past it the command refuses with a list of bases rather than truncating. Defaults to 4000."
|
|
1431
1717
|
),
|
|
1432
|
-
fullUnderTokens:
|
|
1718
|
+
fullUnderTokens: z10.number().int().positive().optional().describe(
|
|
1433
1719
|
"Per-base rendering threshold, applied before the budget: a base whose complete load fits under this arrives as full records instead of index lines, and the whole block still answers to budgetTokens. Off by default \u2014 index-only is the safe default at a context birth, because injected bodies outlive the qualifiers on them; the session-start profile opts tiny bases in at 1500."
|
|
1434
1720
|
),
|
|
1435
|
-
profile:
|
|
1721
|
+
profile: z10.string().optional().describe(
|
|
1436
1722
|
"Named budget set: built-ins are session-start (full-under 1500), compact and turn (budget 2500); the manifests' `context` tables override per repo. Unknown names fall through to defaults rather than failing."
|
|
1437
1723
|
),
|
|
1438
|
-
format:
|
|
1724
|
+
format: z10.enum(["markdown", "json"]).optional().describe(
|
|
1439
1725
|
"CLI envelope for hook protocols that require strict JSON on stdout. MCP callers omit this \u2014 the block itself is identical."
|
|
1440
1726
|
),
|
|
1441
|
-
event:
|
|
1727
|
+
event: z10.string().optional().describe(
|
|
1442
1728
|
"hookEventName stamped into the JSON envelope. Only meaningful with format=json."
|
|
1443
1729
|
)
|
|
1444
1730
|
}),
|
|
@@ -1474,14 +1760,14 @@ var contextCommand = define({
|
|
|
1474
1760
|
});
|
|
1475
1761
|
|
|
1476
1762
|
// src/commands/doctor.ts
|
|
1477
|
-
import { z as
|
|
1478
|
-
var days = (what, fallback) =>
|
|
1763
|
+
import { z as z11 } from "zod";
|
|
1764
|
+
var days = (what, fallback) => z11.number().int().positive().optional().describe(`${what} Defaults to ${fallback}.`);
|
|
1479
1765
|
var doctorCommand = define({
|
|
1480
1766
|
name: "doctor",
|
|
1481
1767
|
tool: "kb_doctor",
|
|
1482
1768
|
usage: "doctor [--expiring-days N] [--unverified-days N] [--aging-days N] [--strict]",
|
|
1483
1769
|
description: "A health sweep over a whole base: what the calendar has already retired, what nobody ever confirmed, what has been open or proposed long enough that the status is now the answer, and what the graph has dropped on the floor. Read-only \u2014 it never writes, never supersedes, and never re-dates anything; every finding names a record for a person to repair. Seven checks, grouped and counted: expired (past `stale_after`), expiring (inside the window), unverified (an empty `verified[]` on a record old enough to matter), aging (still `open` or `proposed`), orphaned (no other record links to it), broken supersession (a chain that does not resolve), and superseded-but-cited (a live record whose body links to a record that no longer holds). Every group is reported even when empty, because a check that found nothing and a check that never ran look identical in a report that only lists findings.\n\nThis is the question no reader thinks to ask, which is why it needs a command: decay is invisible from inside a single record \u2014 a stale one reads exactly like a live one, and a question nobody answered reads exactly like one nobody asked. Reach for it when picking up a base someone else kept, before trusting a base you have not touched in months, or on a schedule; kb_validate is the narrower neighbour, checking only whether pointers between records agree.",
|
|
1484
|
-
input:
|
|
1770
|
+
input: z11.object({
|
|
1485
1771
|
bundlePath,
|
|
1486
1772
|
expiringDays: days(
|
|
1487
1773
|
"How far ahead `expiring` looks, in days.",
|
|
@@ -1495,7 +1781,7 @@ var doctorCommand = define({
|
|
|
1495
1781
|
"How long a record may stay `open` or `proposed` before `aging` reports it, in days.",
|
|
1496
1782
|
DEFAULT_AGING_DAYS
|
|
1497
1783
|
),
|
|
1498
|
-
strict:
|
|
1784
|
+
strict: z11.boolean().optional().describe(
|
|
1499
1785
|
"Turn an expired record into a non-zero exit for the CLI. No effect on the report itself."
|
|
1500
1786
|
)
|
|
1501
1787
|
}),
|
|
@@ -1525,14 +1811,14 @@ var doctorCommand = define({
|
|
|
1525
1811
|
});
|
|
1526
1812
|
return { bundlePath: path, checkedAt, ...report };
|
|
1527
1813
|
},
|
|
1528
|
-
render: (result) =>
|
|
1814
|
+
render: (result) => render2(result),
|
|
1529
1815
|
// Only expiry, and only under --strict. The other six checks report debt a
|
|
1530
1816
|
// reader decides about; an expired record is the base asserting something it
|
|
1531
1817
|
// already said it would stop standing behind, which is the one finding a
|
|
1532
1818
|
// pipeline can act on without a judgment call.
|
|
1533
1819
|
failsWhen: (result, input) => input.strict === true && result.counts.expired > 0
|
|
1534
1820
|
});
|
|
1535
|
-
function
|
|
1821
|
+
function render2(result) {
|
|
1536
1822
|
const { thresholds } = result;
|
|
1537
1823
|
const lines = [
|
|
1538
1824
|
`# KB Doctor \u2014 ${result.bundlePath}`,
|
|
@@ -1564,13 +1850,13 @@ function render(result) {
|
|
|
1564
1850
|
}
|
|
1565
1851
|
|
|
1566
1852
|
// src/commands/list.ts
|
|
1567
|
-
import { z as
|
|
1853
|
+
import { z as z12 } from "zod";
|
|
1568
1854
|
var listCommand = define({
|
|
1569
1855
|
name: "list",
|
|
1570
1856
|
tool: "kb_list",
|
|
1571
1857
|
usage: "list [type]",
|
|
1572
1858
|
description: "Every record, optionally narrowed to one type. Use kb_query when you have a question; this is for enumerating.",
|
|
1573
|
-
input:
|
|
1859
|
+
input: z12.object({ bundlePath, type: z12.enum(KB_RECORD_TYPES).optional() }),
|
|
1574
1860
|
fromArgv: (argv, path) => ({ bundlePath: path, type: argv[1] }),
|
|
1575
1861
|
run: async ({ store }, { bundlePath: path, type }) => (await store.list(path, type)).map((record) => ({
|
|
1576
1862
|
conceptId: record.conceptId,
|
|
@@ -1582,21 +1868,21 @@ var listCommand = define({
|
|
|
1582
1868
|
});
|
|
1583
1869
|
|
|
1584
1870
|
// src/commands/load.ts
|
|
1585
|
-
import { z as
|
|
1871
|
+
import { z as z13 } from "zod";
|
|
1586
1872
|
var loadCommand = define({
|
|
1587
1873
|
name: "load",
|
|
1588
1874
|
tool: "kb_load",
|
|
1589
|
-
usage: "load [type] [--budget N
|
|
1590
|
-
description: "
|
|
1591
|
-
input:
|
|
1875
|
+
usage: "load [type] [--budget N] [--all]",
|
|
1876
|
+
description: "Loads the whole knowledge base at once, each record with its standing. Superseded records arrive as stubs (name, replacement, date); rejected and open records arrive whole. Refuses past the token budget rather than truncating \u2014 call kb_catalog, then kb_pack on the record that matters, or narrow with `type`; kb_query for a lookup by wording. `all` bypasses the budget.",
|
|
1877
|
+
input: z13.object({
|
|
1592
1878
|
bundlePath,
|
|
1593
|
-
type:
|
|
1594
|
-
budgetTokens:
|
|
1595
|
-
all:
|
|
1596
|
-
"
|
|
1879
|
+
type: z13.enum(KB_RECORD_TYPES).optional(),
|
|
1880
|
+
budgetTokens: z13.number().int().positive().optional().describe("Approximate token ceiling. Defaults to 25000."),
|
|
1881
|
+
all: z13.boolean().optional().describe(
|
|
1882
|
+
"Loads the entire base regardless of size, bypassing the token budget; mutually exclusive with budgetTokens."
|
|
1597
1883
|
)
|
|
1598
1884
|
}).refine((value) => !(value.all && value.budgetTokens !== void 0), {
|
|
1599
|
-
message: "all
|
|
1885
|
+
message: "all is mutually exclusive with budgetTokens: pass a ceiling or none, not both."
|
|
1600
1886
|
}),
|
|
1601
1887
|
fromArgv: (argv, path) => {
|
|
1602
1888
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -1630,25 +1916,25 @@ var loadCommand = define({
|
|
|
1630
1916
|
});
|
|
1631
1917
|
|
|
1632
1918
|
// src/commands/log.ts
|
|
1633
|
-
import { z as
|
|
1919
|
+
import { z as z14 } from "zod";
|
|
1634
1920
|
var logCommand = define({
|
|
1635
1921
|
name: "log",
|
|
1636
1922
|
tool: "kb_log",
|
|
1637
1923
|
usage: "log",
|
|
1638
1924
|
description: "What touched what, and when. The only artifact here that cannot be reconstructed from the records, so malformed lines are reported rather than repaired.",
|
|
1639
|
-
input:
|
|
1925
|
+
input: z14.object({ bundlePath }),
|
|
1640
1926
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1641
1927
|
run: ({ store }, { bundlePath: path }) => store.readLog(path)
|
|
1642
1928
|
});
|
|
1643
1929
|
|
|
1644
1930
|
// src/commands/no-decision.ts
|
|
1645
|
-
import { z as
|
|
1931
|
+
import { z as z15 } from "zod";
|
|
1646
1932
|
var noDecisionCommand = define({
|
|
1647
1933
|
name: "no-decision",
|
|
1648
1934
|
tool: "kb_no_decision",
|
|
1649
1935
|
usage: "no-decision <reason...>",
|
|
1650
1936
|
description: 'Claim in one sentence that there was nothing to decide. Gating on "did you write a decision?" rewards writing a junk one; gating on "did you answer?" does not, so silence has to be expressible. Idempotent \u2014 restating it is not a collision.',
|
|
1651
|
-
input:
|
|
1937
|
+
input: z15.object({ bundlePath, reason: z15.string().min(1) }),
|
|
1652
1938
|
fromArgv: (argv, path) => ({
|
|
1653
1939
|
bundlePath: path,
|
|
1654
1940
|
reason: argv.slice(1).join(" ").trim()
|
|
@@ -1665,20 +1951,20 @@ var noDecisionCommand = define({
|
|
|
1665
1951
|
});
|
|
1666
1952
|
|
|
1667
1953
|
// src/commands/pack.ts
|
|
1668
|
-
import { z as
|
|
1954
|
+
import { z as z16 } from "zod";
|
|
1669
1955
|
var packCommand = define({
|
|
1670
1956
|
name: "pack",
|
|
1671
1957
|
tool: "kb_pack",
|
|
1672
1958
|
usage: "pack <conceptId> [--hops N] [--max-nodes N] [--budget N]",
|
|
1673
1959
|
description: "The bounded neighbourhood around one record: everything within `hops` of the root, ranked and cut to `maxNodes`, with every cut record named under Excluded \u2014 a named gap is knowable, a silent one is not. Prefer this over kb_load when the base is too large to hold whole and the work centres on one record; prefer it over kb_query when the question needs the governed neighbourhood \u2014 what was settled and what binds near this record \u2014 rather than a lookup by wording. Superseded records arrive as name, replacement and date stubs exactly as kb_load emits them: their bodies no longer hold, and kb_trace has the history. Refuses outright rather than truncating when the pack would exceed its token budget \u2014 a partial pack is indistinguishable from a complete one \u2014 reporting the record count and every already-cut id so the caller can lower hops or maxNodes, or raise the budget. The header carries the bundle, root, budget and a timestamp; everything below the header is byte-identical across runs over an unchanged base, so two packs can be diffed and a changed byte means changed knowledge. This tool (with kb_load, kb_query and kb_trace) is the only supported way to read a base; a raw file read bypasses supersession resolution and returns replaced records as if current.",
|
|
1674
|
-
input:
|
|
1960
|
+
input: z16.object({
|
|
1675
1961
|
bundlePath,
|
|
1676
1962
|
conceptId,
|
|
1677
|
-
hops:
|
|
1678
|
-
maxNodes:
|
|
1963
|
+
hops: z16.number().int().positive().optional().describe("How far from the root the walk may reach. Defaults to 2."),
|
|
1964
|
+
maxNodes: z16.number().int().positive().optional().describe(
|
|
1679
1965
|
"How many records the pack may hold, root included. Defaults to 20."
|
|
1680
1966
|
),
|
|
1681
|
-
budgetTokens:
|
|
1967
|
+
budgetTokens: z16.number().int().positive().optional().describe(
|
|
1682
1968
|
"Approximate token ceiling over what is actually emitted. Defaults to 25000."
|
|
1683
1969
|
)
|
|
1684
1970
|
}),
|
|
@@ -1700,10 +1986,10 @@ var packCommand = define({
|
|
|
1700
1986
|
...maxNodes !== void 0 ? { maxNodes } : {},
|
|
1701
1987
|
...budgetTokens !== void 0 ? { budgetTokens } : {}
|
|
1702
1988
|
});
|
|
1703
|
-
return
|
|
1989
|
+
return render3(result, path, now());
|
|
1704
1990
|
}
|
|
1705
1991
|
});
|
|
1706
|
-
function
|
|
1992
|
+
function render3(result, bundle, at) {
|
|
1707
1993
|
const lines = [
|
|
1708
1994
|
`# KB Pack \u2014 ${result.root}`,
|
|
1709
1995
|
`bundle: ${bundle}`,
|
|
@@ -1765,22 +2051,22 @@ function warningLabel(warning) {
|
|
|
1765
2051
|
}
|
|
1766
2052
|
|
|
1767
2053
|
// src/commands/pin.ts
|
|
1768
|
-
import { z as
|
|
2054
|
+
import { z as z17 } from "zod";
|
|
1769
2055
|
var pinCommand = define({
|
|
1770
2056
|
name: "pin",
|
|
1771
2057
|
tool: "kb_pin",
|
|
1772
2058
|
usage: "pin [bundle-path] [--mode full|index] [--profiles a,b] [--local|--user] [--frozen|--unfreeze]",
|
|
1773
2059
|
description: "Pin a base into a workspace pin manifest, so `context` surfaces it at every context birth. Three layers, nearest wins: the committed project manifest (.strauss/kb-pins.json, the default), `--local` (.strauss/kb-pins.local.json, personal and gitignored), and `--user` (~/.strauss/kb-pins.json, every workspace). Idempotent \u2014 re-pinning changes nothing unless --mode, --profiles, or --frozen/--unfreeze are given, which update just those fields. `--mode full` preloads the whole base into the block regardless of the full-under threshold; `--mode index` never upgrades. `--profiles` scopes the pin to named context profiles. `--frozen` marks the base concluded: write commands against it refuse and `context` labels it read-only. A path with no records yet succeeds with a warning; bases are routinely pinned before they are populated. Pins are workspace state: the pinned base itself is never touched.",
|
|
1774
|
-
input:
|
|
2060
|
+
input: z17.object({
|
|
1775
2061
|
bundlePath,
|
|
1776
|
-
mode:
|
|
2062
|
+
mode: z17.enum(["full", "index"]).optional().describe(
|
|
1777
2063
|
"full: always emit this base's records whole (still under the block budget); index: never upgrade. Absent: the profile's full-under threshold decides."
|
|
1778
2064
|
),
|
|
1779
|
-
profiles:
|
|
1780
|
-
layer:
|
|
2065
|
+
profiles: z17.array(z17.string()).optional().describe("Context profiles this pin surfaces in. Absent: all of them."),
|
|
2066
|
+
layer: z17.enum(["project", "local", "user"]).optional().describe(
|
|
1781
2067
|
"Which manifest to write: project (committed, default), local (personal, gitignored), user (~/.strauss, every workspace)."
|
|
1782
2068
|
),
|
|
1783
|
-
frozen:
|
|
2069
|
+
frozen: z17.boolean().optional().describe(
|
|
1784
2070
|
"true: the base is concluded \u2014 writes against it refuse while pinned. false: lift a freeze."
|
|
1785
2071
|
)
|
|
1786
2072
|
}),
|
|
@@ -1809,29 +2095,29 @@ var pinCommand = define({
|
|
|
1809
2095
|
});
|
|
1810
2096
|
|
|
1811
2097
|
// src/commands/pins.ts
|
|
1812
|
-
import { z as
|
|
2098
|
+
import { z as z18 } from "zod";
|
|
1813
2099
|
var pinsCommand = define({
|
|
1814
2100
|
name: "pins",
|
|
1815
2101
|
tool: "kb_pins",
|
|
1816
2102
|
usage: "pins",
|
|
1817
2103
|
description: "Every pinned base across the manifest layers, each with its layer and whether it currently resolves to readable records. Reads the workspace manifests rather than any one base, like kb_context.",
|
|
1818
|
-
input:
|
|
2104
|
+
input: z18.object({}),
|
|
1819
2105
|
fromArgv: () => ({}),
|
|
1820
2106
|
run: ({ store }) => listPins(store, process.cwd())
|
|
1821
2107
|
});
|
|
1822
2108
|
|
|
1823
2109
|
// src/commands/query.ts
|
|
1824
|
-
import { z as
|
|
2110
|
+
import { z as z19 } from "zod";
|
|
1825
2111
|
var queryCommand = define({
|
|
1826
2112
|
name: "query",
|
|
1827
2113
|
tool: "kb_query",
|
|
1828
2114
|
usage: "query <text...>",
|
|
1829
|
-
description: "Search and return each match with its standing. Results are flagged, never filtered: a superseded record comes back alongside whatever replaced it, and a rejected one is marked as something explicitly not adopted.
|
|
1830
|
-
input:
|
|
2115
|
+
description: "Search and return each match with its standing. Results are flagged, never filtered: a superseded record comes back alongside whatever replaced it, and a rejected one is marked as something explicitly not adopted. This is the lookup-by-wording rung, and the narrowest of the three: use it when you know roughly what the record says. The decision rule around it \u2014 while the base fits kb_load's token budget, kb_load it whole, because on this package's measurements a reader holding the whole base answered eight of nine questions whose wording appears in no record where embedding search answered four; once kb_load refuses, kb_catalog for one line per record and then kb_pack on the record the work centres on; and kb_query when the question is a point lookup rather than a neighbourhood. A query cannot tell you that nothing was decided \u2014 it returns its nearest hit whatever the distance \u2014 so reach for kb_catalog when the question is what exists. Never read record files directly: this tool (with kb_load, kb_catalog, kb_pack and kb_trace) is the only supported way to read a base; a file read bypasses supersession resolution and returns replaced records as if current.",
|
|
2116
|
+
input: z19.object({
|
|
1831
2117
|
bundlePath,
|
|
1832
|
-
text:
|
|
1833
|
-
type:
|
|
1834
|
-
includeNonCurrent:
|
|
2118
|
+
text: z19.string().optional(),
|
|
2119
|
+
type: z19.enum(KB_RECORD_TYPES).optional(),
|
|
2120
|
+
includeNonCurrent: z19.boolean().optional()
|
|
1835
2121
|
}),
|
|
1836
2122
|
fromArgv: (argv, path) => ({
|
|
1837
2123
|
bundlePath: path,
|
|
@@ -1853,40 +2139,40 @@ var queryCommand = define({
|
|
|
1853
2139
|
});
|
|
1854
2140
|
|
|
1855
2141
|
// src/commands/read-index.ts
|
|
1856
|
-
import { z as
|
|
2142
|
+
import { z as z20 } from "zod";
|
|
1857
2143
|
var readIndexCommand = define({
|
|
1858
2144
|
name: "index",
|
|
1859
2145
|
tool: "kb_index",
|
|
1860
2146
|
usage: "index",
|
|
1861
2147
|
description: "The index, rebuilt if it disagrees with the records. One call gives the whole shape of the base: title, type, status, and description per record. The cheap re-orientation call after compaction or deep in a long session \u2014 a few hundred tokens; call it (or kb_context, when bases are pinned) first, then kb_load or fetch by concept id.",
|
|
1862
|
-
input:
|
|
2148
|
+
input: z20.object({ bundlePath }),
|
|
1863
2149
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
1864
2150
|
run: ({ store }, { bundlePath: path }) => store.readIndex(path)
|
|
1865
2151
|
});
|
|
1866
2152
|
|
|
1867
2153
|
// src/commands/schema.ts
|
|
1868
|
-
import { z as
|
|
2154
|
+
import { z as z21 } from "zod";
|
|
1869
2155
|
var schemaCommand = define({
|
|
1870
2156
|
name: "schema",
|
|
1871
2157
|
tool: "kb_schema",
|
|
1872
2158
|
usage: "schema",
|
|
1873
2159
|
description: "JSON Schema for the frontmatter, the write input, and log entries \u2014 generated from the code that enforces them, so it cannot drift from what a write will accept.",
|
|
1874
|
-
input:
|
|
2160
|
+
input: z21.object({}),
|
|
1875
2161
|
fromArgv: () => ({}),
|
|
1876
2162
|
run: () => Promise.resolve(kbJsonSchemas())
|
|
1877
2163
|
});
|
|
1878
2164
|
|
|
1879
2165
|
// src/commands/status.ts
|
|
1880
|
-
import { z as
|
|
2166
|
+
import { z as z22 } from "zod";
|
|
1881
2167
|
var statusCommand = define({
|
|
1882
2168
|
name: "status",
|
|
1883
2169
|
tool: "kb_status",
|
|
1884
2170
|
usage: "status <concept-id> <status>",
|
|
1885
2171
|
description: "Move a record's status, leaving everything else alone. Uses a compare-and-swap, so a concurrent change fails loudly rather than being overwritten.",
|
|
1886
|
-
input:
|
|
2172
|
+
input: z22.object({
|
|
1887
2173
|
bundlePath,
|
|
1888
2174
|
conceptId,
|
|
1889
|
-
status:
|
|
2175
|
+
status: z22.enum(KB_RECORD_STATUSES)
|
|
1890
2176
|
}),
|
|
1891
2177
|
fromArgv: (argv, path) => ({
|
|
1892
2178
|
bundlePath: path,
|
|
@@ -1901,13 +2187,13 @@ var statusCommand = define({
|
|
|
1901
2187
|
});
|
|
1902
2188
|
|
|
1903
2189
|
// src/commands/supersede.ts
|
|
1904
|
-
import { z as
|
|
2190
|
+
import { z as z23 } from "zod";
|
|
1905
2191
|
var supersedeCommand = define({
|
|
1906
2192
|
name: "supersede",
|
|
1907
2193
|
tool: "kb_supersede",
|
|
1908
2194
|
usage: "supersede <concept-id> <replacement-id>",
|
|
1909
2195
|
description: "Mark a record superseded by another, linking both directions. Use this rather than editing a record whose meaning changed \u2014 a record that quietly becomes something else invalidates every reference to it, and the earlier understanding is what a later trace needs.",
|
|
1910
|
-
input:
|
|
2196
|
+
input: z23.object({ bundlePath, conceptId, replacementId: conceptId }),
|
|
1911
2197
|
fromArgv: (argv, path) => ({
|
|
1912
2198
|
bundlePath: path,
|
|
1913
2199
|
conceptId: argv[1],
|
|
@@ -1921,16 +2207,16 @@ var supersedeCommand = define({
|
|
|
1921
2207
|
});
|
|
1922
2208
|
|
|
1923
2209
|
// src/commands/sync-instructions.ts
|
|
1924
|
-
import { z as
|
|
2210
|
+
import { z as z24 } from "zod";
|
|
1925
2211
|
var syncInstructionsCommand = define({
|
|
1926
2212
|
name: "sync-instructions",
|
|
1927
2213
|
usage: "sync-instructions <file> [--profile NAME] [--budget N] [--full-under N]",
|
|
1928
2214
|
description: "Idempotently plant the `context` block between sentinel comments in an instruction file (AGENTS.md, CLAUDE.md), creating the block when absent and leaving everything outside the sentinels alone. CLI-only: this is file plumbing for runtimes whose instruction files are re-read where their conversations are not, not an agent capability \u2014 the capability is kb_context.",
|
|
1929
|
-
input:
|
|
1930
|
-
file:
|
|
1931
|
-
budgetTokens:
|
|
1932
|
-
fullUnderTokens:
|
|
1933
|
-
profile:
|
|
2215
|
+
input: z24.object({
|
|
2216
|
+
file: z24.string().min(1).describe("The instruction file to edit in place."),
|
|
2217
|
+
budgetTokens: z24.number().int().positive().optional(),
|
|
2218
|
+
fullUnderTokens: z24.number().int().positive().optional(),
|
|
2219
|
+
profile: z24.string().optional()
|
|
1934
2220
|
}),
|
|
1935
2221
|
fromArgv: (argv) => {
|
|
1936
2222
|
const budget = argvFlag(argv, "--budget");
|
|
@@ -1956,17 +2242,17 @@ var syncInstructionsCommand = define({
|
|
|
1956
2242
|
});
|
|
1957
2243
|
|
|
1958
2244
|
// src/commands/trace.ts
|
|
1959
|
-
import { z as
|
|
2245
|
+
import { z as z25 } from "zod";
|
|
1960
2246
|
var traceCommand = define({
|
|
1961
2247
|
name: "trace",
|
|
1962
2248
|
tool: "kb_trace",
|
|
1963
2249
|
usage: "trace <concept-id> [edges...]",
|
|
1964
2250
|
description: 'How a position was arrived at, as a timeline ordered by when each record was written. Deliberately includes rejected, draft, and superseded records \u2014 in a history those are the content, not noise. Follows supersession, shared code anchors, and shared sources. Use when the question is "why is this the way it is" rather than "what do we hold now". This tool (with kb_load and kb_query) is the only supported way to read a base; a raw file read bypasses supersession resolution and returns replaced records as if current.',
|
|
1965
|
-
input:
|
|
2251
|
+
input: z25.object({
|
|
1966
2252
|
bundlePath,
|
|
1967
2253
|
conceptId,
|
|
1968
|
-
edges:
|
|
1969
|
-
depth:
|
|
2254
|
+
edges: z25.array(z25.enum(TRACE_EDGES)).optional(),
|
|
2255
|
+
depth: z25.number().int().positive().optional()
|
|
1970
2256
|
}),
|
|
1971
2257
|
fromArgv: (argv, path) => ({
|
|
1972
2258
|
bundlePath: path,
|
|
@@ -1988,53 +2274,53 @@ var traceCommand = define({
|
|
|
1988
2274
|
});
|
|
1989
2275
|
|
|
1990
2276
|
// src/commands/types.ts
|
|
1991
|
-
import { z as
|
|
2277
|
+
import { z as z26 } from "zod";
|
|
1992
2278
|
var typesCommand = define({
|
|
1993
2279
|
name: "types",
|
|
1994
2280
|
tool: "kb_types",
|
|
1995
2281
|
usage: "types",
|
|
1996
2282
|
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.",
|
|
1997
|
-
input:
|
|
2283
|
+
input: z26.object({}),
|
|
1998
2284
|
fromArgv: () => ({}),
|
|
1999
2285
|
run: () => Promise.resolve(RECORD_TYPES)
|
|
2000
2286
|
});
|
|
2001
2287
|
|
|
2002
2288
|
// src/commands/unpin.ts
|
|
2003
|
-
import { z as
|
|
2289
|
+
import { z as z27 } from "zod";
|
|
2004
2290
|
var unpinCommand = define({
|
|
2005
2291
|
name: "unpin",
|
|
2006
2292
|
tool: "kb_unpin",
|
|
2007
2293
|
usage: "unpin [bundle-path]",
|
|
2008
2294
|
description: "Remove a base from every pin manifest layer that holds it \u2014 project, local, and user \u2014 because unpinned means gone, not still injected from another file. Reports which layers were touched.",
|
|
2009
|
-
input:
|
|
2295
|
+
input: z27.object({ bundlePath }),
|
|
2010
2296
|
fromArgv: (argv, path) => ({ bundlePath: argv[1] ?? path }),
|
|
2011
2297
|
run: (_ctx, { bundlePath: path }) => unpinBase(process.cwd(), path)
|
|
2012
2298
|
});
|
|
2013
2299
|
|
|
2014
2300
|
// src/commands/validate.ts
|
|
2015
|
-
import { z as
|
|
2301
|
+
import { z as z28 } from "zod";
|
|
2016
2302
|
var validateCommand = define({
|
|
2017
2303
|
name: "validate",
|
|
2018
2304
|
tool: "kb_validate",
|
|
2019
2305
|
usage: "validate",
|
|
2020
2306
|
description: "Check pointers no single record can see: supersession links that disagree between the two records, and assumptions that cite sources. Per-record shape is enforced on every read, so a problem here means someone edited a file by hand.",
|
|
2021
|
-
input:
|
|
2307
|
+
input: z28.object({ bundlePath }),
|
|
2022
2308
|
fromArgv: (_argv, path) => ({ bundlePath: path }),
|
|
2023
2309
|
run: async ({ store }, { bundlePath: path }) => validateBundle(await store.list(path)),
|
|
2024
2310
|
failsWhen: (result) => Array.isArray(result) && result.length > 0
|
|
2025
2311
|
});
|
|
2026
2312
|
|
|
2027
2313
|
// src/commands/verify.ts
|
|
2028
|
-
import { z as
|
|
2314
|
+
import { z as z29 } from "zod";
|
|
2029
2315
|
var verifyCommand = define({
|
|
2030
2316
|
name: "verify",
|
|
2031
2317
|
tool: "kb_verify",
|
|
2032
2318
|
usage: "verify <concept-id> --note <text>",
|
|
2033
2319
|
description: "Append one verified[] event \u2014 who checked the record, when, and what the check found. Appends only; prior events are never rewritten. A record's own generator is refused unless the actor is human: re-reading your own output is not an independent check.",
|
|
2034
|
-
input:
|
|
2320
|
+
input: z29.object({
|
|
2035
2321
|
bundlePath,
|
|
2036
2322
|
conceptId,
|
|
2037
|
-
note:
|
|
2323
|
+
note: z29.string().refine((s) => s.trim().length > 0, {
|
|
2038
2324
|
message: "note must say what the check found"
|
|
2039
2325
|
})
|
|
2040
2326
|
}),
|
|
@@ -2054,7 +2340,7 @@ var verifyCommand = define({
|
|
|
2054
2340
|
});
|
|
2055
2341
|
|
|
2056
2342
|
// src/commands/write.ts
|
|
2057
|
-
import { z as
|
|
2343
|
+
import { z as z30 } from "zod";
|
|
2058
2344
|
var writeCommand = define({
|
|
2059
2345
|
name: "write",
|
|
2060
2346
|
tool: "kb_write",
|
|
@@ -2068,9 +2354,9 @@ var writeCommand = define({
|
|
|
2068
2354
|
"- Prefer a new record over overloading an existing one, and keep each short. A record nobody finishes reading is not durable memory.",
|
|
2069
2355
|
"- Records are never deleted; supersede instead, so the earlier reasoning stays inspectable."
|
|
2070
2356
|
].join("\n"),
|
|
2071
|
-
input:
|
|
2357
|
+
input: z30.object({
|
|
2072
2358
|
bundlePath,
|
|
2073
|
-
type:
|
|
2359
|
+
type: z30.enum(KB_RECORD_TYPES),
|
|
2074
2360
|
input: composeInputSchema
|
|
2075
2361
|
}),
|
|
2076
2362
|
fromArgv: async (argv, path, stdin) => ({
|
|
@@ -2094,7 +2380,7 @@ var writeCommand = define({
|
|
|
2094
2380
|
});
|
|
2095
2381
|
|
|
2096
2382
|
// src/commands/write-decision.ts
|
|
2097
|
-
import { z as
|
|
2383
|
+
import { z as z31 } from "zod";
|
|
2098
2384
|
var writeDecisionCommand = define({
|
|
2099
2385
|
name: "write-decision",
|
|
2100
2386
|
tool: "kb_write_decision",
|
|
@@ -2107,7 +2393,7 @@ var writeDecisionCommand = define({
|
|
|
2107
2393
|
"- `alternative` is what you turned down and why, not a list of everything considered.",
|
|
2108
2394
|
"- A reference to material you read goes in `sources`; a reference to code goes in `anchors`; a reference to another record goes in `relatedConceptIds`."
|
|
2109
2395
|
].join("\n"),
|
|
2110
|
-
input:
|
|
2396
|
+
input: z31.object({ bundlePath, input: decisionInputSchema }),
|
|
2111
2397
|
fromArgv: async (_argv, path, stdin) => ({
|
|
2112
2398
|
bundlePath: path,
|
|
2113
2399
|
input: JSON.parse(await stdin())
|
|
@@ -2137,6 +2423,7 @@ var KB_COMMANDS = [
|
|
|
2137
2423
|
answerCommand,
|
|
2138
2424
|
verifyCommand,
|
|
2139
2425
|
loadCommand,
|
|
2426
|
+
catalogCommand,
|
|
2140
2427
|
packCommand,
|
|
2141
2428
|
queryCommand,
|
|
2142
2429
|
traceCommand,
|
|
@@ -2182,141 +2469,6 @@ function parseMarkdownWithFrontmatter(text, schema) {
|
|
|
2182
2469
|
};
|
|
2183
2470
|
}
|
|
2184
2471
|
|
|
2185
|
-
// src/errors.ts
|
|
2186
|
-
var Fault = /* @__PURE__ */ ((Fault2) => {
|
|
2187
|
-
Fault2["Configuration"] = "Configuration";
|
|
2188
|
-
Fault2["System"] = "System";
|
|
2189
|
-
Fault2["User"] = "User";
|
|
2190
|
-
return Fault2;
|
|
2191
|
-
})(Fault || {});
|
|
2192
|
-
var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
|
|
2193
|
-
ErrorTypes2["KbRecordAlreadyExists"] = "KbRecordAlreadyExists";
|
|
2194
|
-
ErrorTypes2["KbInvalidConceptId"] = "KbInvalidConceptId";
|
|
2195
|
-
ErrorTypes2["KbPackBudgetExceeded"] = "KbPackBudgetExceeded";
|
|
2196
|
-
ErrorTypes2["KbRecordNotFound"] = "KbRecordNotFound";
|
|
2197
|
-
ErrorTypes2["KbSelfVerification"] = "KbSelfVerification";
|
|
2198
|
-
ErrorTypes2["KbWriteConflict"] = "KbWriteConflict";
|
|
2199
|
-
return ErrorTypes2;
|
|
2200
|
-
})(ErrorTypes || {});
|
|
2201
|
-
var BaseError = class extends Error {
|
|
2202
|
-
code;
|
|
2203
|
-
errorType;
|
|
2204
|
-
fault;
|
|
2205
|
-
retriable;
|
|
2206
|
-
reportToUser;
|
|
2207
|
-
details;
|
|
2208
|
-
constructor(props) {
|
|
2209
|
-
super(props.message);
|
|
2210
|
-
this.name = props.name ?? this.constructor.name;
|
|
2211
|
-
this.code = props.code ?? 500;
|
|
2212
|
-
this.errorType = props.errorType;
|
|
2213
|
-
this.fault = props.fault;
|
|
2214
|
-
this.retriable = props.retriable ?? true;
|
|
2215
|
-
this.reportToUser = props.reportToUser ?? false;
|
|
2216
|
-
this.details = props.details;
|
|
2217
|
-
}
|
|
2218
|
-
};
|
|
2219
|
-
|
|
2220
|
-
// src/kb-errors.ts
|
|
2221
|
-
var KbRecordAlreadyExistsError = class extends BaseError {
|
|
2222
|
-
constructor(conceptId2) {
|
|
2223
|
-
super({
|
|
2224
|
-
message: `kb: ${conceptId2} already exists \u2014 choose a more specific slug, or write with overwrite`,
|
|
2225
|
-
errorType: "KbRecordAlreadyExists" /* KbRecordAlreadyExists */,
|
|
2226
|
-
code: 409,
|
|
2227
|
-
fault: "User" /* User */,
|
|
2228
|
-
retriable: false,
|
|
2229
|
-
reportToUser: true,
|
|
2230
|
-
details: { conceptId: conceptId2, action: "refused" }
|
|
2231
|
-
});
|
|
2232
|
-
this.conceptId = conceptId2;
|
|
2233
|
-
}
|
|
2234
|
-
conceptId;
|
|
2235
|
-
};
|
|
2236
|
-
var KbRecordNotFoundError = class extends BaseError {
|
|
2237
|
-
constructor(conceptId2) {
|
|
2238
|
-
super({
|
|
2239
|
-
message: `kb: ${conceptId2} does not exist`,
|
|
2240
|
-
errorType: "KbRecordNotFound" /* KbRecordNotFound */,
|
|
2241
|
-
code: 404,
|
|
2242
|
-
fault: "User" /* User */,
|
|
2243
|
-
retriable: false,
|
|
2244
|
-
reportToUser: true,
|
|
2245
|
-
details: { conceptId: conceptId2 }
|
|
2246
|
-
});
|
|
2247
|
-
this.conceptId = conceptId2;
|
|
2248
|
-
}
|
|
2249
|
-
conceptId;
|
|
2250
|
-
};
|
|
2251
|
-
var KbWriteConflictError = class extends BaseError {
|
|
2252
|
-
constructor(conceptId2) {
|
|
2253
|
-
super({
|
|
2254
|
-
message: `kb: ${conceptId2} changed while it was being updated \u2014 re-read and retry`,
|
|
2255
|
-
errorType: "KbWriteConflict" /* KbWriteConflict */,
|
|
2256
|
-
code: 409,
|
|
2257
|
-
fault: "System" /* System */,
|
|
2258
|
-
retriable: true,
|
|
2259
|
-
reportToUser: true,
|
|
2260
|
-
details: { conceptId: conceptId2 }
|
|
2261
|
-
});
|
|
2262
|
-
this.conceptId = conceptId2;
|
|
2263
|
-
}
|
|
2264
|
-
conceptId;
|
|
2265
|
-
};
|
|
2266
|
-
var KbSelfVerificationError = class extends BaseError {
|
|
2267
|
-
constructor(conceptId2, actor, generatedBy) {
|
|
2268
|
-
super({
|
|
2269
|
-
message: `kb: ${conceptId2} was generated by ${generatedBy}, and a record's generator cannot verify it \u2014 only a human or a different actor can`,
|
|
2270
|
-
errorType: "KbSelfVerification" /* KbSelfVerification */,
|
|
2271
|
-
code: 400,
|
|
2272
|
-
fault: "User" /* User */,
|
|
2273
|
-
retriable: false,
|
|
2274
|
-
reportToUser: true,
|
|
2275
|
-
details: { conceptId: conceptId2, actor, generatedBy, action: "refused" }
|
|
2276
|
-
});
|
|
2277
|
-
this.conceptId = conceptId2;
|
|
2278
|
-
this.actor = actor;
|
|
2279
|
-
this.generatedBy = generatedBy;
|
|
2280
|
-
}
|
|
2281
|
-
conceptId;
|
|
2282
|
-
actor;
|
|
2283
|
-
generatedBy;
|
|
2284
|
-
};
|
|
2285
|
-
var KbPackBudgetExceededError = class extends BaseError {
|
|
2286
|
-
constructor(recordCount, approxTokens2, budgetTokens, excluded) {
|
|
2287
|
-
super({
|
|
2288
|
-
message: `kb: a pack of ${recordCount} records is ~${approxTokens2} tokens against a budget of ${budgetTokens} \u2014 lower hops or maxNodes, or raise the budget`,
|
|
2289
|
-
errorType: "KbPackBudgetExceeded" /* KbPackBudgetExceeded */,
|
|
2290
|
-
code: 400,
|
|
2291
|
-
fault: "User" /* User */,
|
|
2292
|
-
retriable: false,
|
|
2293
|
-
reportToUser: true,
|
|
2294
|
-
details: { recordCount, approxTokens: approxTokens2, budgetTokens, excluded }
|
|
2295
|
-
});
|
|
2296
|
-
this.recordCount = recordCount;
|
|
2297
|
-
this.approxTokens = approxTokens2;
|
|
2298
|
-
this.budgetTokens = budgetTokens;
|
|
2299
|
-
this.excluded = excluded;
|
|
2300
|
-
}
|
|
2301
|
-
recordCount;
|
|
2302
|
-
approxTokens;
|
|
2303
|
-
budgetTokens;
|
|
2304
|
-
excluded;
|
|
2305
|
-
};
|
|
2306
|
-
var KbInvalidConceptIdError = class extends BaseError {
|
|
2307
|
-
constructor(message, details) {
|
|
2308
|
-
super({
|
|
2309
|
-
message: `kb: ${message}`,
|
|
2310
|
-
errorType: "KbInvalidConceptId" /* KbInvalidConceptId */,
|
|
2311
|
-
code: 400,
|
|
2312
|
-
fault: "User" /* User */,
|
|
2313
|
-
retriable: false,
|
|
2314
|
-
reportToUser: true,
|
|
2315
|
-
details
|
|
2316
|
-
});
|
|
2317
|
-
}
|
|
2318
|
-
};
|
|
2319
|
-
|
|
2320
2472
|
// src/search-index.ts
|
|
2321
2473
|
import { stat } from "fs/promises";
|
|
2322
2474
|
import { join as join3 } from "path";
|
|
@@ -2416,6 +2568,32 @@ import {
|
|
|
2416
2568
|
writeFile as writeFile3
|
|
2417
2569
|
} from "fs/promises";
|
|
2418
2570
|
import { join as join4, resolve as resolve4, sep as sep2 } from "path";
|
|
2571
|
+
|
|
2572
|
+
// src/kb-gitattributes.ts
|
|
2573
|
+
var GITATTRIBUTES_FILE = ".gitattributes";
|
|
2574
|
+
var UNION_MERGE_LINE = `${LOG_FILE} text eol=lf merge=union`;
|
|
2575
|
+
function parseLine(line) {
|
|
2576
|
+
const trimmed = line.trim();
|
|
2577
|
+
if (!trimmed || trimmed.startsWith("#")) return null;
|
|
2578
|
+
const [pattern, ...attrs] = trimmed.split(/\s+/);
|
|
2579
|
+
return pattern === void 0 ? null : { pattern, attrs };
|
|
2580
|
+
}
|
|
2581
|
+
function hasMergeDeclaration(contents) {
|
|
2582
|
+
return contents.split("\n").some((line) => {
|
|
2583
|
+
const parsed = parseLine(line);
|
|
2584
|
+
if (!parsed || parsed.pattern !== LOG_FILE) return false;
|
|
2585
|
+
return parsed.attrs.some(
|
|
2586
|
+
(attr) => attr === "merge" || attr === "-merge" || attr.startsWith("merge=")
|
|
2587
|
+
);
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
function appendUnionMergeLine(contents) {
|
|
2591
|
+
const separator = contents.length === 0 || contents.endsWith("\n") ? "" : "\n";
|
|
2592
|
+
return `${separator}${UNION_MERGE_LINE}
|
|
2593
|
+
`;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
// src/kb-store.ts
|
|
2419
2597
|
var KB_DIR = join4(".strauss", "kb");
|
|
2420
2598
|
var STORE_OWNED = /* @__PURE__ */ new Set([INDEX_FILE, LOG_FILE, SEARCH_INDEX_FILE]);
|
|
2421
2599
|
var DEFAULT_LOAD_BUDGET = 25e3;
|
|
@@ -2671,9 +2849,16 @@ ${answer}
|
|
|
2671
2849
|
* is indistinguishable from a complete one, so a caller would answer "that
|
|
2672
2850
|
* was never decided" from a slice it did not know was a slice.
|
|
2673
2851
|
*
|
|
2674
|
-
*
|
|
2675
|
-
*
|
|
2676
|
-
*
|
|
2852
|
+
* A token budget decides that, measured over what is actually handed back.
|
|
2853
|
+
* The refusal names the estimate and the budget, because a caller told only
|
|
2854
|
+
* "too big" cannot tell whether to narrow the type filter, raise the budget,
|
|
2855
|
+
* or stop loading the base whole altogether. Past the budget the answer is
|
|
2856
|
+
* the catalog and then a pack, which is what the refusal says.
|
|
2857
|
+
*
|
|
2858
|
+
* That refusal is the default guardrail. `all` bypasses the budget outright
|
|
2859
|
+
* and always hands back the whole bundle: an explicit, never-accidental
|
|
2860
|
+
* escape hatch for an operator who has the budget to spend, not a wider
|
|
2861
|
+
* default.
|
|
2677
2862
|
*/
|
|
2678
2863
|
async load(bundlePath2, options = {}) {
|
|
2679
2864
|
const budgetTokens = options.budgetTokens ?? DEFAULT_LOAD_BUDGET;
|
|
@@ -2688,7 +2873,12 @@ ${answer}
|
|
|
2688
2873
|
loaded: false,
|
|
2689
2874
|
recordCount: wanted.length,
|
|
2690
2875
|
approxTokens: approxTokens2,
|
|
2691
|
-
budgetTokens
|
|
2876
|
+
budgetTokens,
|
|
2877
|
+
message: refusalMessage({
|
|
2878
|
+
approxTokens: approxTokens2,
|
|
2879
|
+
budgetTokens,
|
|
2880
|
+
type: options.type
|
|
2881
|
+
})
|
|
2692
2882
|
};
|
|
2693
2883
|
}
|
|
2694
2884
|
return {
|
|
@@ -2704,6 +2894,10 @@ ${answer}
|
|
|
2704
2894
|
async trace(bundlePath2, seedId, options = {}) {
|
|
2705
2895
|
return trace(seedId, await this.list(bundlePath2), options);
|
|
2706
2896
|
}
|
|
2897
|
+
/** Every record named in one line each. See `catalog.ts`. */
|
|
2898
|
+
async catalog(bundlePath2, options = {}) {
|
|
2899
|
+
return catalog(await this.list(bundlePath2), options);
|
|
2900
|
+
}
|
|
2707
2901
|
/** A bounded neighbourhood around one record. See `pack.ts`. */
|
|
2708
2902
|
async pack(bundlePath2, rootId, options = {}) {
|
|
2709
2903
|
return pack(await this.list(bundlePath2), rootId, options);
|
|
@@ -2840,8 +3034,87 @@ ${answer}
|
|
|
2840
3034
|
await unlink(staging).catch(() => void 0);
|
|
2841
3035
|
}
|
|
2842
3036
|
}
|
|
3037
|
+
/**
|
|
3038
|
+
* Declares union merge for the log, so two worktrees writing the same
|
|
3039
|
+
* bundle interleave their `log.jsonl` lines on merge rather than one
|
|
3040
|
+
* side's appends silently losing to git's ordinary line-level merge.
|
|
3041
|
+
*
|
|
3042
|
+
* Called from `record` — every path that appends a log line, not just
|
|
3043
|
+
* `write` — so a bundle only ever mutated through `setStatus`/`verify`/
|
|
3044
|
+
* `supersede` still gets it. There is no cheaper reliable signal for
|
|
3045
|
+
* "first write" than checking the file itself, and after the first call
|
|
3046
|
+
* the check is a no-op `readFile`.
|
|
3047
|
+
*
|
|
3048
|
+
* A missing `.gitattributes` is created outright, with `wx` (exclusive
|
|
3049
|
+
* create) rather than a plain write: if another process's `write()` won a
|
|
3050
|
+
* race and created the file between the `readFile` below and this call,
|
|
3051
|
+
* `wx` fails instead of truncating what that writer just wrote, and the
|
|
3052
|
+
* failure is swallowed by the catch below same as any other best-effort
|
|
3053
|
+
* miss. A file that exists but declares no merge strategy for the log
|
|
3054
|
+
* gets the line appended, never a wholesale rewrite; one that already
|
|
3055
|
+
* declares any merge strategy — this one or a user's own — is left alone
|
|
3056
|
+
* entirely (see `hasMergeDeclaration`).
|
|
3057
|
+
*
|
|
3058
|
+
* `readFile` failing is `existing === null` only for `ENOENT` — genuinely
|
|
3059
|
+
* missing. Any other error (a permission problem, a transient `EMFILE`,
|
|
3060
|
+
* the path being a directory) is *not* "missing" and must not fall into
|
|
3061
|
+
* the create branch, which would truncate whatever is actually there with
|
|
3062
|
+
* just the union-merge line: that is the file-destroying bug this
|
|
3063
|
+
* function exists to avoid, not commit. An unreadable existing file is
|
|
3064
|
+
* therefore left untouched and reported as a failure like any other.
|
|
3065
|
+
*
|
|
3066
|
+
* Two processes racing the append branch — both read a file without the
|
|
3067
|
+
* line, both append it — is possible and left unguarded: `appendFile` is
|
|
3068
|
+
* `O_APPEND`, so the result is two copies of the same line rather than a
|
|
3069
|
+
* torn write, and `hasMergeDeclaration` sees a duplicate declaration as
|
|
3070
|
+
* "already declared" on the next call. A cheap-to-detect, harmless-to-
|
|
3071
|
+
* leave residue, not a reason to add a cross-process lock (see
|
|
3072
|
+
* `ARCHITECTURE.md`'s rejection of one for the same trade on records).
|
|
3073
|
+
*
|
|
3074
|
+
* Best-effort, like the log append it precedes: failing to write this
|
|
3075
|
+
* file must not fail the mutation it guards.
|
|
3076
|
+
*/
|
|
3077
|
+
async ensureGitattributes(root) {
|
|
3078
|
+
const target = join4(root, GITATTRIBUTES_FILE);
|
|
3079
|
+
try {
|
|
3080
|
+
let existing;
|
|
3081
|
+
try {
|
|
3082
|
+
existing = await readFile3(target, "utf8");
|
|
3083
|
+
} catch (error) {
|
|
3084
|
+
if (error.code !== "ENOENT") throw error;
|
|
3085
|
+
existing = null;
|
|
3086
|
+
}
|
|
3087
|
+
if (existing === null) {
|
|
3088
|
+
await writeFile3(target, appendUnionMergeLine(""), {
|
|
3089
|
+
encoding: "utf8",
|
|
3090
|
+
flag: "wx"
|
|
3091
|
+
});
|
|
3092
|
+
this.logger.info?.({
|
|
3093
|
+
operation: "kb.gitattributes.ensure",
|
|
3094
|
+
bundlePath: root,
|
|
3095
|
+
outcome: "created"
|
|
3096
|
+
});
|
|
3097
|
+
return;
|
|
3098
|
+
}
|
|
3099
|
+
if (!hasMergeDeclaration(existing)) {
|
|
3100
|
+
await appendFile(target, appendUnionMergeLine(existing), "utf8");
|
|
3101
|
+
this.logger.info?.({
|
|
3102
|
+
operation: "kb.gitattributes.ensure",
|
|
3103
|
+
bundlePath: root,
|
|
3104
|
+
outcome: "appended"
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
} catch (error) {
|
|
3108
|
+
this.logger.warn?.({
|
|
3109
|
+
operation: "kb.gitattributes.ensure",
|
|
3110
|
+
outcome: "failed",
|
|
3111
|
+
error: error instanceof Error ? error.message : "unknown"
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
2843
3115
|
/** Appends one log line. Failing to log must not fail the mutation. */
|
|
2844
3116
|
async record(root, entry) {
|
|
3117
|
+
await this.ensureGitattributes(root);
|
|
2845
3118
|
const line = renderLogEntry({ at: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
2846
3119
|
await appendFile(join4(root, LOG_FILE), line, "utf8").catch((error) => {
|
|
2847
3120
|
this.logger.warn?.({
|
|
@@ -2891,6 +3164,14 @@ function estimateTokens(record) {
|
|
|
2891
3164
|
function estimateStubTokens(entry) {
|
|
2892
3165
|
return Math.ceil(JSON.stringify(entry).length / 4);
|
|
2893
3166
|
}
|
|
3167
|
+
function refusalMessage(refusal) {
|
|
3168
|
+
const scope = refusal.type ? ` of type ${refusal.type}` : "";
|
|
3169
|
+
return [
|
|
3170
|
+
`Refusing to load this base whole: ~${refusal.approxTokens} tokens is past the ${refusal.budgetTokens}-token budget.`,
|
|
3171
|
+
`Call kb_catalog for one line per record${scope} (id, type, title, standing), then kb_pack on the record that matters; kb_query works for a lookup by wording.`,
|
|
3172
|
+
`To load anyway: raise budgetTokens (currently ${refusal.budgetTokens}), or all=true to bypass the budget.`
|
|
3173
|
+
].join(" ");
|
|
3174
|
+
}
|
|
2894
3175
|
function stub(hit) {
|
|
2895
3176
|
return {
|
|
2896
3177
|
conceptId: hit.record.conceptId,
|
|
@@ -2999,7 +3280,7 @@ function typeRank(record) {
|
|
|
2999
3280
|
}
|
|
3000
3281
|
|
|
3001
3282
|
// src/version.ts
|
|
3002
|
-
var VERSION = true ? "0.1.
|
|
3283
|
+
var VERSION = true ? "0.1.10" : "0.0.0-dev";
|
|
3003
3284
|
|
|
3004
3285
|
export {
|
|
3005
3286
|
kbSourceSchema,
|
|
@@ -3039,8 +3320,20 @@ export {
|
|
|
3039
3320
|
listPins,
|
|
3040
3321
|
pinBase,
|
|
3041
3322
|
unpinBase,
|
|
3323
|
+
Fault,
|
|
3324
|
+
ErrorTypes,
|
|
3325
|
+
BaseError,
|
|
3326
|
+
KbRecordAlreadyExistsError,
|
|
3327
|
+
KbRecordNotFoundError,
|
|
3328
|
+
KbWriteConflictError,
|
|
3329
|
+
KbSelfVerificationError,
|
|
3330
|
+
KbPackBudgetExceededError,
|
|
3331
|
+
KbMissingFlagValueError,
|
|
3332
|
+
KbInvalidConceptIdError,
|
|
3042
3333
|
adjudicate,
|
|
3043
3334
|
resolveHeads,
|
|
3335
|
+
catalog,
|
|
3336
|
+
renderCatalogLine,
|
|
3044
3337
|
INDEX_FILE,
|
|
3045
3338
|
renderIndex,
|
|
3046
3339
|
renderIndexLine,
|
|
@@ -3072,15 +3365,6 @@ export {
|
|
|
3072
3365
|
stringifyMarkdownWithFrontmatter,
|
|
3073
3366
|
splitMarkdownFrontmatter,
|
|
3074
3367
|
parseMarkdownWithFrontmatter,
|
|
3075
|
-
Fault,
|
|
3076
|
-
ErrorTypes,
|
|
3077
|
-
BaseError,
|
|
3078
|
-
KbRecordAlreadyExistsError,
|
|
3079
|
-
KbRecordNotFoundError,
|
|
3080
|
-
KbWriteConflictError,
|
|
3081
|
-
KbSelfVerificationError,
|
|
3082
|
-
KbPackBudgetExceededError,
|
|
3083
|
-
KbInvalidConceptIdError,
|
|
3084
3368
|
SEARCH_INDEX_FILE,
|
|
3085
3369
|
searchBase,
|
|
3086
3370
|
resolveHits,
|
|
@@ -3093,4 +3377,4 @@ export {
|
|
|
3093
3377
|
KbStore,
|
|
3094
3378
|
VERSION
|
|
3095
3379
|
};
|
|
3096
|
-
//# sourceMappingURL=chunk-
|
|
3380
|
+
//# sourceMappingURL=chunk-EJQPZWN5.js.map
|