@bartolli/kmd 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kmd.mjs +303 -107
- package/dist/kmd.mjs.map +4 -4
- package/package.json +1 -1
package/dist/kmd.mjs
CHANGED
|
@@ -82,6 +82,9 @@ import { readFile } from "node:fs/promises";
|
|
|
82
82
|
import { join } from "node:path";
|
|
83
83
|
import { parse } from "yaml";
|
|
84
84
|
import { z } from "zod";
|
|
85
|
+
function kindName(entry) {
|
|
86
|
+
return typeof entry === "string" ? entry : entry.name;
|
|
87
|
+
}
|
|
85
88
|
async function loadVaultConfig(vaultRoot2) {
|
|
86
89
|
const path = join(vaultRoot2, "vault.yaml");
|
|
87
90
|
let raw;
|
|
@@ -98,25 +101,61 @@ ${issues}`);
|
|
|
98
101
|
}
|
|
99
102
|
return parsed.data;
|
|
100
103
|
}
|
|
101
|
-
var ScopeSchema, VaultConfigSchema;
|
|
104
|
+
var ScopeSchema, KindEntrySchema, VaultConfigSchema, BUILT_IN_KINDS;
|
|
102
105
|
var init_config = __esm({
|
|
103
106
|
"../cli/src/config.ts"() {
|
|
104
107
|
"use strict";
|
|
105
108
|
ScopeSchema = z.object({
|
|
106
109
|
repo: z.string().optional(),
|
|
107
|
-
methodology: z.
|
|
110
|
+
methodology: z.string().optional(),
|
|
108
111
|
status: z.string()
|
|
109
112
|
});
|
|
113
|
+
KindEntrySchema = z.union([
|
|
114
|
+
z.string(),
|
|
115
|
+
z.object({
|
|
116
|
+
name: z.string(),
|
|
117
|
+
signal: z.string(),
|
|
118
|
+
where: z.string()
|
|
119
|
+
})
|
|
120
|
+
]);
|
|
110
121
|
VaultConfigSchema = z.object({
|
|
111
122
|
scopes: z.record(z.string(), ScopeSchema),
|
|
112
|
-
kinds: z.array(
|
|
123
|
+
kinds: z.array(KindEntrySchema),
|
|
113
124
|
statuses: z.array(z.string()),
|
|
114
125
|
methodologies: z.array(z.string()),
|
|
115
126
|
tags: z.object({
|
|
116
127
|
canonical: z.array(z.string()),
|
|
117
128
|
aliases: z.record(z.string(), z.string())
|
|
118
|
-
})
|
|
129
|
+
}),
|
|
130
|
+
authoring_rules: z.string().optional(),
|
|
131
|
+
authoring_rules_extra: z.string().optional(),
|
|
132
|
+
sync_protocol: z.string().optional(),
|
|
133
|
+
sync_protocol_extra: z.string().optional()
|
|
134
|
+
}).superRefine((config, ctx) => {
|
|
135
|
+
for (const [name, scope] of Object.entries(config.scopes)) {
|
|
136
|
+
if (scope.methodology !== void 0 && !config.methodologies.includes(scope.methodology)) {
|
|
137
|
+
ctx.addIssue({
|
|
138
|
+
code: "custom",
|
|
139
|
+
path: ["scopes", name, "methodology"],
|
|
140
|
+
message: `"${scope.methodology}" is not in the methodologies list`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
119
144
|
});
|
|
145
|
+
BUILT_IN_KINDS = /* @__PURE__ */ new Set([
|
|
146
|
+
"project",
|
|
147
|
+
"spec",
|
|
148
|
+
"adr",
|
|
149
|
+
"plan",
|
|
150
|
+
"story",
|
|
151
|
+
"ops",
|
|
152
|
+
"topic",
|
|
153
|
+
"article",
|
|
154
|
+
"src",
|
|
155
|
+
"note",
|
|
156
|
+
"artifact",
|
|
157
|
+
"prompt"
|
|
158
|
+
]);
|
|
120
159
|
}
|
|
121
160
|
});
|
|
122
161
|
|
|
@@ -378,9 +417,13 @@ var init_sync = __esm({
|
|
|
378
417
|
});
|
|
379
418
|
|
|
380
419
|
// ../cli/src/validate.ts
|
|
381
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
420
|
+
import { readFile as readFile3, stat } from "node:fs/promises";
|
|
421
|
+
import { join as join3 } from "node:path";
|
|
422
|
+
function hasIndexableTitle(data) {
|
|
423
|
+
return typeof data.title === "string" && data.title.trim() !== "";
|
|
424
|
+
}
|
|
382
425
|
function isIndexed(relPath, data) {
|
|
383
|
-
if (
|
|
426
|
+
if (!hasIndexableTitle(data)) return false;
|
|
384
427
|
if (typeof data.kind === "string" && data.kind !== "") return true;
|
|
385
428
|
return relPath.startsWith("notes/");
|
|
386
429
|
}
|
|
@@ -402,6 +445,45 @@ function checkRequiredFields(relPath, data) {
|
|
|
402
445
|
});
|
|
403
446
|
}
|
|
404
447
|
}
|
|
448
|
+
if (Object.hasOwn(data, "title") && !hasIndexableTitle(data)) {
|
|
449
|
+
findings.push({
|
|
450
|
+
path: relPath,
|
|
451
|
+
rule: "required-fields",
|
|
452
|
+
severity: "error",
|
|
453
|
+
message: `"title" must be a non-empty string for kind "${kind}" \u2014 sync skips the page otherwise`
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return findings;
|
|
457
|
+
}
|
|
458
|
+
function customKindNames(cfg) {
|
|
459
|
+
const names = /* @__PURE__ */ new Set();
|
|
460
|
+
for (const entry of cfg.kinds) {
|
|
461
|
+
if (typeof entry !== "string" && !BUILT_IN_KINDS.has(entry.name)) names.add(entry.name);
|
|
462
|
+
}
|
|
463
|
+
return names;
|
|
464
|
+
}
|
|
465
|
+
function checkCustomKindFloor(relPath, data, cfg) {
|
|
466
|
+
const kind = data.kind;
|
|
467
|
+
if (typeof kind !== "string" || !customKindNames(cfg).has(kind)) return [];
|
|
468
|
+
const findings = [];
|
|
469
|
+
for (const field of UNIVERSAL_FLOOR) {
|
|
470
|
+
if (!Object.hasOwn(data, field)) {
|
|
471
|
+
findings.push({
|
|
472
|
+
path: relPath,
|
|
473
|
+
rule: "custom-kind-floor",
|
|
474
|
+
severity: "warning",
|
|
475
|
+
message: `custom kind "${kind}": missing "${field}" \u2014 the universal floor (title, summary, updated) keeps the page retrievable`
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (Object.hasOwn(data, "title") && !hasIndexableTitle(data)) {
|
|
480
|
+
findings.push({
|
|
481
|
+
path: relPath,
|
|
482
|
+
rule: "custom-kind-floor",
|
|
483
|
+
severity: "warning",
|
|
484
|
+
message: `custom kind "${kind}": "title" is empty \u2014 sync skips title-less pages; this page will not be indexed`
|
|
485
|
+
});
|
|
486
|
+
}
|
|
405
487
|
return findings;
|
|
406
488
|
}
|
|
407
489
|
function checkTagsRequired(relPath, data) {
|
|
@@ -433,7 +515,7 @@ function checkFolderSlug(relPath, data) {
|
|
|
433
515
|
function checkVocabulary(relPath, data, cfg) {
|
|
434
516
|
const findings = [];
|
|
435
517
|
const kind = data.kind;
|
|
436
|
-
if (typeof kind === "string" && !cfg.kinds.
|
|
518
|
+
if (typeof kind === "string" && !cfg.kinds.some((k) => kindName(k) === kind)) {
|
|
437
519
|
findings.push({
|
|
438
520
|
path: relPath,
|
|
439
521
|
rule: "kind-vocabulary",
|
|
@@ -572,7 +654,6 @@ function checkSupersededLink(relPath, data) {
|
|
|
572
654
|
function checkIndexedPage(relPath, data, body, cfg, refIndex) {
|
|
573
655
|
if (!isIndexed(relPath, data)) return [];
|
|
574
656
|
return [
|
|
575
|
-
...checkRequiredFields(relPath, data),
|
|
576
657
|
...checkTagsRequired(relPath, data),
|
|
577
658
|
...checkFolderSlug(relPath, data),
|
|
578
659
|
...checkVocabulary(relPath, data, cfg),
|
|
@@ -599,7 +680,11 @@ function validatePage(relPath, raw, cfg, refIndex) {
|
|
|
599
680
|
if (isPrimer(relPath)) {
|
|
600
681
|
return checkBodyLinks(relPath, parsed.content, refIndex);
|
|
601
682
|
}
|
|
602
|
-
return
|
|
683
|
+
return [
|
|
684
|
+
...checkRequiredFields(relPath, parsed.data),
|
|
685
|
+
...checkCustomKindFloor(relPath, parsed.data, cfg),
|
|
686
|
+
...checkIndexedPage(relPath, parsed.data, parsed.content, cfg, refIndex)
|
|
687
|
+
];
|
|
603
688
|
}
|
|
604
689
|
function hasErrors(findings) {
|
|
605
690
|
return findings.some((f) => f.severity === "error");
|
|
@@ -702,9 +787,22 @@ async function validateVault(root) {
|
|
|
702
787
|
}
|
|
703
788
|
findings.push(...validateSupersession(pages));
|
|
704
789
|
findings.push(...validateAmbiguousLinks(linkPages, basenameToPaths));
|
|
790
|
+
for (const name of customKindNames(cfg)) {
|
|
791
|
+
const file = `templates/${name}.md`;
|
|
792
|
+
try {
|
|
793
|
+
await stat(join3(root, file));
|
|
794
|
+
} catch {
|
|
795
|
+
findings.push({
|
|
796
|
+
path: file,
|
|
797
|
+
rule: "custom-kind-template",
|
|
798
|
+
severity: "warning",
|
|
799
|
+
message: `custom kind "${name}" declared in vault.yaml has no template file`
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
705
803
|
return findings;
|
|
706
804
|
}
|
|
707
|
-
var REQUIRED_FIELDS, TAG_OPTIONAL_KINDS, FOLDER_PATTERNS;
|
|
805
|
+
var REQUIRED_FIELDS, TAG_OPTIONAL_KINDS, FOLDER_PATTERNS, UNIVERSAL_FLOOR;
|
|
708
806
|
var init_validate = __esm({
|
|
709
807
|
"../cli/src/validate.ts"() {
|
|
710
808
|
"use strict";
|
|
@@ -755,6 +853,7 @@ var init_validate = __esm({
|
|
|
755
853
|
topic: /^research\/[^/]+\/index\.md$/,
|
|
756
854
|
src: /^research\/[^/]+\/src-[^/]+\.md$/
|
|
757
855
|
};
|
|
856
|
+
UNIVERSAL_FLOOR = ["title", "summary", "updated"];
|
|
758
857
|
}
|
|
759
858
|
});
|
|
760
859
|
|
|
@@ -863,10 +962,10 @@ var init_config2 = __esm({
|
|
|
863
962
|
// ../mcp/src/db.ts
|
|
864
963
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
865
964
|
import { homedir as homedir2 } from "node:os";
|
|
866
|
-
import { join as
|
|
965
|
+
import { join as join4 } from "node:path";
|
|
867
966
|
function createDatabase() {
|
|
868
|
-
const dbDir =
|
|
869
|
-
const dbPath =
|
|
967
|
+
const dbDir = join4(homedir2(), ".kmd", "db");
|
|
968
|
+
const dbPath = join4(dbDir, "index.db");
|
|
870
969
|
mkdirSync2(dbDir, { recursive: true });
|
|
871
970
|
return openDatabase(dbPath);
|
|
872
971
|
}
|
|
@@ -880,7 +979,7 @@ var init_db = __esm({
|
|
|
880
979
|
// ../mcp/src/lib/diag.ts
|
|
881
980
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
882
981
|
import { homedir as homedir3 } from "node:os";
|
|
883
|
-
import { join as
|
|
982
|
+
import { join as join5 } from "node:path";
|
|
884
983
|
function diag(msg, data) {
|
|
885
984
|
try {
|
|
886
985
|
const line = data ? `${(/* @__PURE__ */ new Date()).toISOString()} pid=${process.pid} ${msg} ${JSON.stringify(data)}
|
|
@@ -894,8 +993,8 @@ var DIAG_DIR, DIAG_LOG_PATH;
|
|
|
894
993
|
var init_diag = __esm({
|
|
895
994
|
"../mcp/src/lib/diag.ts"() {
|
|
896
995
|
"use strict";
|
|
897
|
-
DIAG_DIR =
|
|
898
|
-
DIAG_LOG_PATH =
|
|
996
|
+
DIAG_DIR = join5(homedir3(), ".local", "state", "wiki-mcp");
|
|
997
|
+
DIAG_LOG_PATH = join5(DIAG_DIR, "server.log");
|
|
899
998
|
try {
|
|
900
999
|
mkdirSync3(DIAG_DIR, { recursive: true });
|
|
901
1000
|
} catch {
|
|
@@ -922,31 +1021,111 @@ var init_logger = __esm({
|
|
|
922
1021
|
}
|
|
923
1022
|
});
|
|
924
1023
|
|
|
925
|
-
// ../mcp/src/
|
|
1024
|
+
// ../mcp/src/vault-config.ts
|
|
926
1025
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
927
|
-
import { join as
|
|
928
|
-
|
|
1026
|
+
import { join as join6 } from "node:path";
|
|
1027
|
+
import { parse as parse2 } from "yaml";
|
|
1028
|
+
import { z as z4 } from "zod";
|
|
1029
|
+
function kindName2(entry) {
|
|
1030
|
+
return typeof entry === "string" ? entry : entry.name;
|
|
1031
|
+
}
|
|
1032
|
+
async function loadVaultConfig2(vaultRoot2) {
|
|
1033
|
+
const path = join6(vaultRoot2, "vault.yaml");
|
|
1034
|
+
let raw;
|
|
929
1035
|
try {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
|
|
1036
|
+
raw = await readFile4(path, "utf8");
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
throw new Error(`vault.yaml not found at ${path}`, { cause: err });
|
|
1039
|
+
}
|
|
1040
|
+
const parsed = VaultConfigSchema2.safeParse(parse2(raw));
|
|
1041
|
+
if (!parsed.success) {
|
|
1042
|
+
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1043
|
+
throw new Error(`Invalid vault.yaml at ${path}:
|
|
1044
|
+
${issues}`);
|
|
938
1045
|
}
|
|
1046
|
+
return parsed.data;
|
|
1047
|
+
}
|
|
1048
|
+
var ScopeSchema2, KindEntrySchema2, VaultConfigSchema2, BUILT_IN_KINDS2;
|
|
1049
|
+
var init_vault_config = __esm({
|
|
1050
|
+
"../mcp/src/vault-config.ts"() {
|
|
1051
|
+
"use strict";
|
|
1052
|
+
ScopeSchema2 = z4.object({
|
|
1053
|
+
repo: z4.string().optional(),
|
|
1054
|
+
methodology: z4.string().optional(),
|
|
1055
|
+
status: z4.string()
|
|
1056
|
+
});
|
|
1057
|
+
KindEntrySchema2 = z4.union([
|
|
1058
|
+
z4.string(),
|
|
1059
|
+
z4.object({
|
|
1060
|
+
name: z4.string(),
|
|
1061
|
+
signal: z4.string(),
|
|
1062
|
+
where: z4.string()
|
|
1063
|
+
})
|
|
1064
|
+
]);
|
|
1065
|
+
VaultConfigSchema2 = z4.object({
|
|
1066
|
+
scopes: z4.record(z4.string(), ScopeSchema2),
|
|
1067
|
+
kinds: z4.array(KindEntrySchema2),
|
|
1068
|
+
statuses: z4.array(z4.string()),
|
|
1069
|
+
methodologies: z4.array(z4.string()),
|
|
1070
|
+
tags: z4.object({
|
|
1071
|
+
canonical: z4.array(z4.string()),
|
|
1072
|
+
aliases: z4.record(z4.string(), z4.string())
|
|
1073
|
+
}),
|
|
1074
|
+
authoring_rules: z4.string().optional(),
|
|
1075
|
+
authoring_rules_extra: z4.string().optional(),
|
|
1076
|
+
sync_protocol: z4.string().optional(),
|
|
1077
|
+
sync_protocol_extra: z4.string().optional()
|
|
1078
|
+
}).superRefine((config, ctx) => {
|
|
1079
|
+
for (const [name, scope] of Object.entries(config.scopes)) {
|
|
1080
|
+
if (scope.methodology !== void 0 && !config.methodologies.includes(scope.methodology)) {
|
|
1081
|
+
ctx.addIssue({
|
|
1082
|
+
code: "custom",
|
|
1083
|
+
path: ["scopes", name, "methodology"],
|
|
1084
|
+
message: `"${scope.methodology}" is not in the methodologies list`
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
BUILT_IN_KINDS2 = /* @__PURE__ */ new Set([
|
|
1090
|
+
"project",
|
|
1091
|
+
"spec",
|
|
1092
|
+
"adr",
|
|
1093
|
+
"plan",
|
|
1094
|
+
"story",
|
|
1095
|
+
"ops",
|
|
1096
|
+
"topic",
|
|
1097
|
+
"article",
|
|
1098
|
+
"src",
|
|
1099
|
+
"note",
|
|
1100
|
+
"artifact",
|
|
1101
|
+
"prompt"
|
|
1102
|
+
]);
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
// ../mcp/src/resources/authoring.ts
|
|
1107
|
+
function buildAuthoringRules(config) {
|
|
1108
|
+
const parts = [(config.authoring_rules ?? DEFAULT_AUTHORING_RULES).trim()];
|
|
1109
|
+
if (config.authoring_rules_extra) parts.push(config.authoring_rules_extra.trim());
|
|
1110
|
+
return ["## Authoring rules", "", parts.join("\n\n")].join("\n");
|
|
1111
|
+
}
|
|
1112
|
+
function buildSyncProtocol(config) {
|
|
1113
|
+
const parts = [(config.sync_protocol ?? DEFAULT_SYNC_PROTOCOL).trim()];
|
|
1114
|
+
if (config.sync_protocol_extra) parts.push(config.sync_protocol_extra.trim());
|
|
1115
|
+
return ["## Resync protocol", "", parts.join("\n\n")].join("\n");
|
|
939
1116
|
}
|
|
940
1117
|
function buildKindSelector(kinds) {
|
|
941
1118
|
const lines = ["## Kind selector", "", "| Signal | Kind | Where |", "|---|---|---|"];
|
|
942
|
-
for (const
|
|
943
|
-
const
|
|
1119
|
+
for (const entry of kinds) {
|
|
1120
|
+
const name = kindName2(entry);
|
|
1121
|
+
const pedagogy = typeof entry === "string" ? KIND_PEDAGOGY.get(entry) : entry;
|
|
944
1122
|
const signal = pedagogy?.signal ?? "\u2014";
|
|
945
1123
|
const where = pedagogy?.where ?? "\u2014";
|
|
946
|
-
lines.push(`| ${signal} | **${
|
|
1124
|
+
lines.push(`| ${signal} | **${name}** | ${where} |`);
|
|
947
1125
|
}
|
|
948
|
-
const
|
|
949
|
-
const
|
|
1126
|
+
const names = kinds.map(kindName2);
|
|
1127
|
+
const hasNote = names.includes("note");
|
|
1128
|
+
const hasAdrAndSpec = names.includes("adr") && names.includes("spec");
|
|
950
1129
|
if (hasNote || hasAdrAndSpec) {
|
|
951
1130
|
const hints = [];
|
|
952
1131
|
if (hasNote) hints.push("If none fits \u2192 note.");
|
|
@@ -959,12 +1138,16 @@ function buildKindSelector(kinds) {
|
|
|
959
1138
|
}
|
|
960
1139
|
return lines.join("\n");
|
|
961
1140
|
}
|
|
1141
|
+
function buildStatusLine(statuses) {
|
|
1142
|
+
const isCanonical = statuses.length === CANONICAL_STATUS_FLOW.length && statuses.every((s, i) => s === CANONICAL_STATUS_FLOW[i]);
|
|
1143
|
+
return isCanonical ? `**Statuses:** ${statuses.join(" \u2192 ")} (one-directional; superseded requires superseded_by link)` : `**Statuses:** ${statuses.join(", ")}`;
|
|
1144
|
+
}
|
|
962
1145
|
function buildVocabulary(config) {
|
|
963
1146
|
const lines = [
|
|
964
1147
|
"## Controlled vocabulary",
|
|
965
1148
|
"",
|
|
966
|
-
`**Kinds:** ${config.kinds.join(", ")}`,
|
|
967
|
-
|
|
1149
|
+
`**Kinds:** ${config.kinds.map(kindName2).join(", ")}`,
|
|
1150
|
+
buildStatusLine(config.statuses),
|
|
968
1151
|
`**Methodologies:** ${config.methodologies.join(", ")}`,
|
|
969
1152
|
`**Canonical tags:** ${config.tags.canonical.join(", ")}`
|
|
970
1153
|
];
|
|
@@ -974,16 +1157,15 @@ function buildVocabulary(config) {
|
|
|
974
1157
|
}
|
|
975
1158
|
return lines.join("\n");
|
|
976
1159
|
}
|
|
977
|
-
function registerAuthoringResource(mcp,
|
|
1160
|
+
function registerAuthoringResource(mcp, _vaultRoot, vaultConfig) {
|
|
978
1161
|
mcp.registerResource(
|
|
979
1162
|
"Authoring guide",
|
|
980
1163
|
"wiki://authoring",
|
|
981
1164
|
{
|
|
982
|
-
description: "Wiki authoring pedagogy: kind selector, controlled vocabulary, authoring rules, and template URIs. Read before creating or editing wiki pages.",
|
|
1165
|
+
description: "Wiki authoring pedagogy: kind selector, controlled vocabulary, authoring rules, resync protocol, and template URIs. Read before creating or editing wiki pages.",
|
|
983
1166
|
mimeType: "text/markdown"
|
|
984
1167
|
},
|
|
985
1168
|
async (uri) => {
|
|
986
|
-
const rules = await readAuthoringRules(vaultRoot2);
|
|
987
1169
|
const sections = [
|
|
988
1170
|
"# Wiki authoring guide",
|
|
989
1171
|
"",
|
|
@@ -993,11 +1175,12 @@ function registerAuthoringResource(mcp, vaultRoot2, vaultConfig) {
|
|
|
993
1175
|
"",
|
|
994
1176
|
"## Templates",
|
|
995
1177
|
"",
|
|
996
|
-
"Full index with URIs and descriptions: `wiki://templates`"
|
|
1178
|
+
"Full index with URIs and descriptions: `wiki://templates`",
|
|
1179
|
+
"",
|
|
1180
|
+
buildAuthoringRules(vaultConfig),
|
|
1181
|
+
"",
|
|
1182
|
+
buildSyncProtocol(vaultConfig)
|
|
997
1183
|
];
|
|
998
|
-
if (rules) {
|
|
999
|
-
sections.push("", rules);
|
|
1000
|
-
}
|
|
1001
1184
|
return {
|
|
1002
1185
|
contents: [
|
|
1003
1186
|
{
|
|
@@ -1010,10 +1193,11 @@ function registerAuthoringResource(mcp, vaultRoot2, vaultConfig) {
|
|
|
1010
1193
|
}
|
|
1011
1194
|
);
|
|
1012
1195
|
}
|
|
1013
|
-
var KIND_PEDAGOGY;
|
|
1196
|
+
var KIND_PEDAGOGY, DEFAULT_AUTHORING_RULES, DEFAULT_SYNC_PROTOCOL, CANONICAL_STATUS_FLOW;
|
|
1014
1197
|
var init_authoring = __esm({
|
|
1015
1198
|
"../mcp/src/resources/authoring.ts"() {
|
|
1016
1199
|
"use strict";
|
|
1200
|
+
init_vault_config();
|
|
1017
1201
|
KIND_PEDAGOGY = /* @__PURE__ */ new Map([
|
|
1018
1202
|
[
|
|
1019
1203
|
"project",
|
|
@@ -1094,21 +1278,74 @@ var init_authoring = __esm({
|
|
|
1094
1278
|
}
|
|
1095
1279
|
]
|
|
1096
1280
|
]);
|
|
1281
|
+
DEFAULT_AUTHORING_RULES = [
|
|
1282
|
+
"**Where things go**",
|
|
1283
|
+
"",
|
|
1284
|
+
"- **Use the matching template** via `wiki://template/{domain}/{kind}` (MCP) or from `templates/` (filesystem). Don't hand-roll frontmatter.",
|
|
1285
|
+
"- **Folder name = slug prefix in `projects/`.** `spec/spec-x.md`, `adr/adr-y.md`, `plan/plan-z.md`, `ops/ops-w.md`. Stories use `story-` prefix under `plan/{plan-name}/`.",
|
|
1286
|
+
"- **Research is flat.** Articles are `{subject}.md`; sources are `src-{slug}.md`. The topic folder names a *frame*, the article slug names a *subject*. Avoid generic slugs (`architecture.md`, `notes.md`) and slugs that collide with project scopes.",
|
|
1287
|
+
"- **Extend before you split.** Prefer sharpening the existing spec, ADR, or article over creating a near-duplicate page. A new page needs a new subject, not a new session.",
|
|
1288
|
+
"",
|
|
1289
|
+
"**Frontmatter**",
|
|
1290
|
+
"",
|
|
1291
|
+
'- **`summary` is the retrieval contract.** One sentence stating the page\'s decision or claim, not its topic. Search and `prime` rank by it \u2014 "Chose X over Y because Z" surfaces; "About the sync pipeline" sinks.',
|
|
1292
|
+
'- **Quote prose-bearing scalars.** `summary: "..."` \u2014 unquoted `Word: phrase` patterns break the YAML parser.',
|
|
1293
|
+
"- **On any edit, update `updated`.** Never change `created` \u2014 it is write-once.",
|
|
1294
|
+
"- **Notes have no `kind` field** \u2014 implied by location. Sync sets `kind: note`.",
|
|
1295
|
+
"- **Reuse existing tags** (visible in `prime` response `top_tags`). No synonyms.",
|
|
1296
|
+
"",
|
|
1297
|
+
"**Content**",
|
|
1298
|
+
"",
|
|
1299
|
+
"- **ADR and ops pages are predicate-only.** No definitional preambles for established vocabulary, no narrative, no marketing. The audience is the project team \u2014 assume fluency.",
|
|
1300
|
+
"- **Spec / ADR edits land inline with the change that surfaces them.** Don't queue corrections in plans \u2014 the spec reflects current code at every commit.",
|
|
1301
|
+
"",
|
|
1302
|
+
"**Linking**",
|
|
1303
|
+
"",
|
|
1304
|
+
"- **Cross-reference with `[[wikilinks]]`, and link every mention of another vault page** \u2014 backlinks are the navigation graph for humans and agents alike. Don't link pages that don't exist yet: dangling links fail validation.",
|
|
1305
|
+
"- **ADR supersession is bidirectional**: `superseded_by` on the old ADR + `supersedes` on the new one.",
|
|
1306
|
+
"- **Sources convention**: external paths/URLs go inline in body text. Vault-internal `raw/` paths go in frontmatter `sources:` array. Don't mix the two surfaces."
|
|
1307
|
+
].join("\n");
|
|
1308
|
+
DEFAULT_SYNC_PROTOCOL = [
|
|
1309
|
+
"Edit the smallest set of files that reflects the change. A milestone tick is plan-only; don't cascade to index.md unless phase or status changed. Controlled-vocabulary edits (`vault.yaml`) need explicit user approval.",
|
|
1310
|
+
"After editing wiki pages, run `kmd validate` and fix findings before `kmd sync` \u2014 it checks frontmatter shape, vocabulary membership, and link integrity."
|
|
1311
|
+
].join("\n");
|
|
1312
|
+
CANONICAL_STATUS_FLOW = ["draft", "active", "superseded", "archived"];
|
|
1097
1313
|
}
|
|
1098
1314
|
});
|
|
1099
1315
|
|
|
1100
1316
|
// ../mcp/src/resources/templates.ts
|
|
1101
1317
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
1102
|
-
import { join as
|
|
1103
|
-
function
|
|
1104
|
-
const
|
|
1105
|
-
for (const
|
|
1318
|
+
import { join as join7 } from "node:path";
|
|
1319
|
+
function customTemplates(config) {
|
|
1320
|
+
const specs = [];
|
|
1321
|
+
for (const entry of config.kinds) {
|
|
1322
|
+
if (typeof entry === "string" || BUILT_IN_KINDS2.has(entry.name)) continue;
|
|
1323
|
+
specs.push({
|
|
1324
|
+
uri: `wiki://template/${entry.name}`,
|
|
1325
|
+
name: entry.name.charAt(0).toUpperCase() + entry.name.slice(1),
|
|
1326
|
+
file: `${entry.name}.md`,
|
|
1327
|
+
description: entry.signal
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
return specs;
|
|
1331
|
+
}
|
|
1332
|
+
function registerTemplateResources(mcp, vaultRoot2, vaultConfig) {
|
|
1333
|
+
const dir = join7(vaultRoot2, "templates");
|
|
1334
|
+
const templates = [...TEMPLATES, ...customTemplates(vaultConfig)];
|
|
1335
|
+
for (const tmpl of templates) {
|
|
1106
1336
|
mcp.registerResource(
|
|
1107
1337
|
tmpl.name,
|
|
1108
1338
|
tmpl.uri,
|
|
1109
1339
|
{ description: tmpl.description, mimeType: "text/markdown" },
|
|
1110
1340
|
async (uri) => {
|
|
1111
|
-
|
|
1341
|
+
let text;
|
|
1342
|
+
try {
|
|
1343
|
+
text = await readFile5(join7(dir, tmpl.file), "utf8");
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
throw new Error(`template file missing: templates/${tmpl.file} (${tmpl.uri})`, {
|
|
1346
|
+
cause: err
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1112
1349
|
return {
|
|
1113
1350
|
contents: [
|
|
1114
1351
|
{
|
|
@@ -1122,7 +1359,7 @@ function registerTemplateResources(mcp, vaultRoot2) {
|
|
|
1122
1359
|
);
|
|
1123
1360
|
}
|
|
1124
1361
|
const indexLines = ["# Wiki Templates", ""];
|
|
1125
|
-
for (const tmpl of
|
|
1362
|
+
for (const tmpl of templates) {
|
|
1126
1363
|
indexLines.push(`- **${tmpl.name}** \u2014 \`${tmpl.uri}\` `);
|
|
1127
1364
|
indexLines.push(` ${tmpl.description}`);
|
|
1128
1365
|
}
|
|
@@ -1143,6 +1380,7 @@ var TEMPLATES;
|
|
|
1143
1380
|
var init_templates = __esm({
|
|
1144
1381
|
"../mcp/src/resources/templates.ts"() {
|
|
1145
1382
|
"use strict";
|
|
1383
|
+
init_vault_config();
|
|
1146
1384
|
TEMPLATES = [
|
|
1147
1385
|
{
|
|
1148
1386
|
uri: "wiki://template/project/index",
|
|
@@ -1283,14 +1521,14 @@ var init_toolResponse = __esm({
|
|
|
1283
1521
|
|
|
1284
1522
|
// ../mcp/src/tools/prime.ts
|
|
1285
1523
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
1286
|
-
import { basename as basename2, join as
|
|
1287
|
-
import { z as
|
|
1524
|
+
import { basename as basename2, join as join8 } from "node:path";
|
|
1525
|
+
import { z as z5 } from "zod";
|
|
1288
1526
|
function pathSlug(p) {
|
|
1289
1527
|
return basename2(p).replace(/\.md$/, "");
|
|
1290
1528
|
}
|
|
1291
1529
|
async function readIndexFm(vaultRoot2, scope) {
|
|
1292
1530
|
try {
|
|
1293
|
-
const raw = await readFile6(
|
|
1531
|
+
const raw = await readFile6(join8(vaultRoot2, "projects", scope, "index.md"), "utf8");
|
|
1294
1532
|
return parseFrontmatter2(raw).data;
|
|
1295
1533
|
} catch {
|
|
1296
1534
|
return {};
|
|
@@ -1298,7 +1536,7 @@ async function readIndexFm(vaultRoot2, scope) {
|
|
|
1298
1536
|
}
|
|
1299
1537
|
async function readPrimer(vaultRoot2, scope) {
|
|
1300
1538
|
try {
|
|
1301
|
-
const raw = await readFile6(
|
|
1539
|
+
const raw = await readFile6(join8(vaultRoot2, "projects", scope, "primer.md"), "utf8");
|
|
1302
1540
|
return parseFrontmatter2(raw).content.trim().replace(/^#\s+[^\n]+\n+/, "");
|
|
1303
1541
|
} catch {
|
|
1304
1542
|
return "";
|
|
@@ -1425,7 +1663,7 @@ function renderMarkdown(d, config, task) {
|
|
|
1425
1663
|
lines.push(countEntries.map(([k, n]) => `${k}: ${n}`).join(" | "));
|
|
1426
1664
|
}
|
|
1427
1665
|
lines.push("", "## Vocabulary");
|
|
1428
|
-
lines.push(`kinds: ${config.kinds.join(", ")}`);
|
|
1666
|
+
lines.push(`kinds: ${config.kinds.map(kindName2).join(", ")}`);
|
|
1429
1667
|
lines.push(`statuses: ${config.statuses.join(", ")}`);
|
|
1430
1668
|
lines.push(`tags: ${config.tags.canonical.join(", ")}`);
|
|
1431
1669
|
if (d.top_tags.length > 0) {
|
|
@@ -1459,7 +1697,7 @@ function renderMarkdown(d, config, task) {
|
|
|
1459
1697
|
lines.push(
|
|
1460
1698
|
"",
|
|
1461
1699
|
"---",
|
|
1462
|
-
"Authoring wiki content? Read `wiki://authoring` for kind selector, rules, and templates."
|
|
1700
|
+
"Authoring wiki content? Read `wiki://authoring` for kind selector, rules, and templates. After edits: `kmd validate`, then `kmd sync`."
|
|
1463
1701
|
);
|
|
1464
1702
|
return lines.join("\n");
|
|
1465
1703
|
}
|
|
@@ -1488,15 +1726,16 @@ var init_prime = __esm({
|
|
|
1488
1726
|
init_frontmatter2();
|
|
1489
1727
|
init_fts();
|
|
1490
1728
|
init_toolResponse();
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1729
|
+
init_vault_config();
|
|
1730
|
+
PrimeInputSchema = z5.object({
|
|
1731
|
+
scope: z5.string().min(1).describe("Project scope to prime (matches projects/{scope}/ in the vault)."),
|
|
1732
|
+
task: z5.string().optional().describe("Optional task description; surfaces top-3 tsvector-ranked relevant pages.")
|
|
1494
1733
|
});
|
|
1495
1734
|
}
|
|
1496
1735
|
});
|
|
1497
1736
|
|
|
1498
1737
|
// ../mcp/src/tools/search.ts
|
|
1499
|
-
import { z as
|
|
1738
|
+
import { z as z6 } from "zod";
|
|
1500
1739
|
function search(deps, input) {
|
|
1501
1740
|
const ftsQuery = sanitizeFtsQuery(input.query);
|
|
1502
1741
|
if (!ftsQuery) return { results: [] };
|
|
@@ -1543,15 +1782,15 @@ var init_search = __esm({
|
|
|
1543
1782
|
"use strict";
|
|
1544
1783
|
init_fts();
|
|
1545
1784
|
init_toolResponse();
|
|
1546
|
-
SearchInputSchema =
|
|
1547
|
-
query:
|
|
1785
|
+
SearchInputSchema = z6.object({
|
|
1786
|
+
query: z6.string().min(1).describe(
|
|
1548
1787
|
"Natural-language search query. Matched against title, summary, and body via SQLite FTS5."
|
|
1549
1788
|
),
|
|
1550
|
-
scope:
|
|
1551
|
-
kind:
|
|
1789
|
+
scope: z6.string().optional().describe('Optional: restrict to a project scope (e.g. "ontology", "sotto").'),
|
|
1790
|
+
kind: z6.string().optional().describe(
|
|
1552
1791
|
"Optional: restrict to a kind. Project kinds: spec, adr, plan, ops. Research kinds: article, src. Misc: note."
|
|
1553
1792
|
),
|
|
1554
|
-
limit:
|
|
1793
|
+
limit: z6.number().int().min(1).max(50).default(5).describe("Maximum number of ranked results to return. Default 5.")
|
|
1555
1794
|
});
|
|
1556
1795
|
}
|
|
1557
1796
|
});
|
|
@@ -1579,7 +1818,7 @@ function buildServer(args) {
|
|
|
1579
1818
|
return handleSearch({ db }, input);
|
|
1580
1819
|
}
|
|
1581
1820
|
);
|
|
1582
|
-
registerTemplateResources(mcp, vaultRoot2);
|
|
1821
|
+
registerTemplateResources(mcp, vaultRoot2, vaultConfig);
|
|
1583
1822
|
registerAuthoringResource(mcp, vaultRoot2, vaultConfig);
|
|
1584
1823
|
return mcp;
|
|
1585
1824
|
}
|
|
@@ -1593,49 +1832,6 @@ var init_server = __esm({
|
|
|
1593
1832
|
}
|
|
1594
1833
|
});
|
|
1595
1834
|
|
|
1596
|
-
// ../mcp/src/vault-config.ts
|
|
1597
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
1598
|
-
import { join as join8 } from "node:path";
|
|
1599
|
-
import { parse as parse2 } from "yaml";
|
|
1600
|
-
import { z as z6 } from "zod";
|
|
1601
|
-
async function loadVaultConfig2(vaultRoot2) {
|
|
1602
|
-
const path = join8(vaultRoot2, "vault.yaml");
|
|
1603
|
-
let raw;
|
|
1604
|
-
try {
|
|
1605
|
-
raw = await readFile7(path, "utf8");
|
|
1606
|
-
} catch (err) {
|
|
1607
|
-
throw new Error(`vault.yaml not found at ${path}`, { cause: err });
|
|
1608
|
-
}
|
|
1609
|
-
const parsed = VaultConfigSchema2.safeParse(parse2(raw));
|
|
1610
|
-
if (!parsed.success) {
|
|
1611
|
-
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1612
|
-
throw new Error(`Invalid vault.yaml at ${path}:
|
|
1613
|
-
${issues}`);
|
|
1614
|
-
}
|
|
1615
|
-
return parsed.data;
|
|
1616
|
-
}
|
|
1617
|
-
var ScopeSchema2, VaultConfigSchema2;
|
|
1618
|
-
var init_vault_config = __esm({
|
|
1619
|
-
"../mcp/src/vault-config.ts"() {
|
|
1620
|
-
"use strict";
|
|
1621
|
-
ScopeSchema2 = z6.object({
|
|
1622
|
-
repo: z6.string().optional(),
|
|
1623
|
-
methodology: z6.enum(["sdd", "tdd", "hybrid"]).optional(),
|
|
1624
|
-
status: z6.string()
|
|
1625
|
-
});
|
|
1626
|
-
VaultConfigSchema2 = z6.object({
|
|
1627
|
-
scopes: z6.record(z6.string(), ScopeSchema2),
|
|
1628
|
-
kinds: z6.array(z6.string()),
|
|
1629
|
-
statuses: z6.array(z6.string()),
|
|
1630
|
-
methodologies: z6.array(z6.string()),
|
|
1631
|
-
tags: z6.object({
|
|
1632
|
-
canonical: z6.array(z6.string()),
|
|
1633
|
-
aliases: z6.record(z6.string(), z6.string())
|
|
1634
|
-
})
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
});
|
|
1638
|
-
|
|
1639
1835
|
// ../mcp/src/start.ts
|
|
1640
1836
|
var start_exports = {};
|
|
1641
1837
|
__export(start_exports, {
|