@bartolli/kmd 0.10.0 → 0.12.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 +906 -321
- package/dist/kmd.mjs.map +4 -4
- package/package.json +1 -1
package/dist/kmd.mjs
CHANGED
|
@@ -16,9 +16,9 @@ var __export = (target, all) => {
|
|
|
16
16
|
|
|
17
17
|
// ../db/src/database.ts
|
|
18
18
|
import { createHash } from "node:crypto";
|
|
19
|
-
import { realpathSync } from "node:fs";
|
|
19
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
|
-
import { basename, join, resolve } from "node:path";
|
|
21
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
22
22
|
import { DatabaseSync } from "node:sqlite";
|
|
23
23
|
function openDatabase(dbPath) {
|
|
24
24
|
const db = new DatabaseSync(dbPath);
|
|
@@ -33,20 +33,35 @@ function kmdHome() {
|
|
|
33
33
|
function indexRootDir() {
|
|
34
34
|
return join(kmdHome(), "db");
|
|
35
35
|
}
|
|
36
|
-
function canonicalVaultRoot(
|
|
36
|
+
function canonicalVaultRoot(vaultRoot) {
|
|
37
37
|
try {
|
|
38
|
-
return realpathSync(
|
|
38
|
+
return realpathSync(vaultRoot);
|
|
39
39
|
} catch {
|
|
40
|
-
return resolve(
|
|
40
|
+
return resolve(vaultRoot);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
function vaultKey(
|
|
44
|
-
const canonical = canonicalVaultRoot(
|
|
43
|
+
function vaultKey(vaultRoot) {
|
|
44
|
+
const canonical = canonicalVaultRoot(vaultRoot);
|
|
45
45
|
const hash = createHash("sha256").update(canonical).digest("hex").slice(0, 8);
|
|
46
46
|
return `${basename(canonical)}-${hash}`;
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
48
|
+
function tierKmdDir(canonical) {
|
|
49
|
+
for (const stateHome of [join(canonical, ".kmd"), join(dirname(canonical), ".kmd")]) {
|
|
50
|
+
if (existsSync(stateHome)) return stateHome;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function resolveIndexPath(vaultRoot) {
|
|
55
|
+
const canonical = canonicalVaultRoot(vaultRoot);
|
|
56
|
+
const tier = tierKmdDir(canonical);
|
|
57
|
+
if (tier !== null) return join(tier, "db", "index.db");
|
|
58
|
+
return join(indexRootDir(), vaultKey(canonical), "index.db");
|
|
59
|
+
}
|
|
60
|
+
function resolveStateDir(vaultRoot) {
|
|
61
|
+
const canonical = canonicalVaultRoot(vaultRoot);
|
|
62
|
+
const tier = tierKmdDir(canonical);
|
|
63
|
+
if (tier !== null) return join(tier, "state", "hook");
|
|
64
|
+
return join(kmdHome(), "state", "hook");
|
|
50
65
|
}
|
|
51
66
|
function getMeta(db, key) {
|
|
52
67
|
const row = db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
@@ -116,11 +131,159 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
116
131
|
}
|
|
117
132
|
});
|
|
118
133
|
|
|
134
|
+
// ../db/src/kmd-config.ts
|
|
135
|
+
var kmd_config_exports = {};
|
|
136
|
+
__export(kmd_config_exports, {
|
|
137
|
+
expandVars: () => expandVars,
|
|
138
|
+
findProjectTier: () => findProjectTier,
|
|
139
|
+
globalConfigPath: () => globalConfigPath,
|
|
140
|
+
loadGlobalConfig: () => loadGlobalConfig,
|
|
141
|
+
resolveVaultRoot: () => resolveVaultRoot,
|
|
142
|
+
setGlobalConfigValue: () => setGlobalConfigValue,
|
|
143
|
+
unsetGlobalConfigValue: () => unsetGlobalConfigValue
|
|
144
|
+
});
|
|
145
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
146
|
+
import { homedir as homedir2 } from "node:os";
|
|
147
|
+
import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "node:path";
|
|
148
|
+
import { Document, parse, parseDocument } from "yaml";
|
|
149
|
+
import { z } from "zod";
|
|
150
|
+
function expandVars(value, env = process.env) {
|
|
151
|
+
return value.replace(
|
|
152
|
+
/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,
|
|
153
|
+
(_match, name, fallback) => {
|
|
154
|
+
const resolved = env[name];
|
|
155
|
+
if (resolved !== void 0 && resolved !== "") return resolved;
|
|
156
|
+
if (fallback !== void 0) return fallback;
|
|
157
|
+
throw new Error(`unresolved \${${name}} (variable unset, no :-default)`);
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
function expandHome(value) {
|
|
162
|
+
if (value === "~" || value.startsWith("~/")) return join2(homedir2(), value.slice(1));
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function loadYamlFile(path, schema) {
|
|
166
|
+
let raw;
|
|
167
|
+
try {
|
|
168
|
+
raw = readFileSync(path, "utf8");
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const parsed = schema.safeParse(parse(raw) ?? {});
|
|
173
|
+
if (!parsed.success) {
|
|
174
|
+
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
175
|
+
throw new Error(`invalid config: ${path}
|
|
176
|
+
${issues}`);
|
|
177
|
+
}
|
|
178
|
+
return parsed.data;
|
|
179
|
+
}
|
|
180
|
+
function globalConfigPath() {
|
|
181
|
+
return join2(kmdHome(), "config.yaml");
|
|
182
|
+
}
|
|
183
|
+
function loadGlobalConfig(env = process.env) {
|
|
184
|
+
const config = loadYamlFile(globalConfigPath(), GlobalConfigSchema);
|
|
185
|
+
if (config === null || config.default_vault === void 0) return {};
|
|
186
|
+
const expanded = expandHome(expandVars(config.default_vault, env));
|
|
187
|
+
if (!isAbsolute(expanded)) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`invalid config: ${globalConfigPath()}
|
|
190
|
+
- default_vault: must be absolute or ~/ (got "${config.default_vault}")`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return { default_vault: expanded };
|
|
194
|
+
}
|
|
195
|
+
function loadGlobalDocument() {
|
|
196
|
+
try {
|
|
197
|
+
return parseDocument(readFileSync(globalConfigPath(), "utf8"));
|
|
198
|
+
} catch {
|
|
199
|
+
return new Document({});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function flushGlobalDocument(doc) {
|
|
203
|
+
mkdirSync(kmdHome(), { recursive: true });
|
|
204
|
+
writeFileSync(globalConfigPath(), doc.toString());
|
|
205
|
+
}
|
|
206
|
+
function setGlobalConfigValue(key, value) {
|
|
207
|
+
const doc = loadGlobalDocument();
|
|
208
|
+
doc.set(key, value);
|
|
209
|
+
flushGlobalDocument(doc);
|
|
210
|
+
}
|
|
211
|
+
function unsetGlobalConfigValue(key) {
|
|
212
|
+
const doc = loadGlobalDocument();
|
|
213
|
+
if (!doc.has(key)) return false;
|
|
214
|
+
doc.delete(key);
|
|
215
|
+
flushGlobalDocument(doc);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function projectVaultAt(level, env, onSkip) {
|
|
219
|
+
for (const [file, via] of TIER_CONFIG_FILES) {
|
|
220
|
+
const path = join2(level, ".kmd", file);
|
|
221
|
+
const config = loadYamlFile(path, ProjectConfigSchema);
|
|
222
|
+
if (config === null || config.vault === void 0) continue;
|
|
223
|
+
const expanded = expandHome(expandVars(config.vault, env));
|
|
224
|
+
return {
|
|
225
|
+
tierRoot: level,
|
|
226
|
+
vaultRoot: isAbsolute(expanded) ? expanded : resolve2(level, expanded),
|
|
227
|
+
via
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (existsSync2(join2(level, "vault", "vault.yaml"))) {
|
|
231
|
+
return { tierRoot: level, vaultRoot: join2(level, "vault"), via: "convention" };
|
|
232
|
+
}
|
|
233
|
+
if (existsSync2(join2(level, "vault.yaml"))) {
|
|
234
|
+
if (existsSync2(join2(level, ".kmd"))) {
|
|
235
|
+
return { tierRoot: level, vaultRoot: level, via: "convention" };
|
|
236
|
+
}
|
|
237
|
+
onSkip?.(join2(level, "vault.yaml"));
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function findProjectTier(fromDir, env = process.env, onSkip) {
|
|
242
|
+
let level = canonicalVaultRoot(fromDir);
|
|
243
|
+
for (; ; ) {
|
|
244
|
+
const hit = projectVaultAt(level, env, onSkip);
|
|
245
|
+
if (hit !== null) return hit;
|
|
246
|
+
const parent = dirname2(level);
|
|
247
|
+
if (parent === level) return null;
|
|
248
|
+
level = parent;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function resolveVaultRoot(input) {
|
|
252
|
+
if (input.positional) return { root: input.positional, source: "positional" };
|
|
253
|
+
if (input.projectDir) {
|
|
254
|
+
const tier = findProjectTier(input.projectDir, input.env ?? process.env, input.onSkip);
|
|
255
|
+
if (tier !== null) {
|
|
256
|
+
return { root: tier.vaultRoot, source: `project-${tier.via}`, tierRoot: tier.tierRoot };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (input.defaultRoot) return { root: input.defaultRoot, source: "default-root" };
|
|
260
|
+
if (input.envVault) return { root: input.envVault, source: "env" };
|
|
261
|
+
if (input.globalDefault) return { root: input.globalDefault, source: "global-config" };
|
|
262
|
+
return { root: null, source: "none" };
|
|
263
|
+
}
|
|
264
|
+
var GlobalConfigSchema, ProjectConfigSchema, TIER_CONFIG_FILES;
|
|
265
|
+
var init_kmd_config = __esm({
|
|
266
|
+
"../db/src/kmd-config.ts"() {
|
|
267
|
+
"use strict";
|
|
268
|
+
init_database();
|
|
269
|
+
GlobalConfigSchema = z.strictObject({
|
|
270
|
+
default_vault: z.string().min(1).optional()
|
|
271
|
+
});
|
|
272
|
+
ProjectConfigSchema = z.strictObject({
|
|
273
|
+
vault: z.string().min(1).optional()
|
|
274
|
+
});
|
|
275
|
+
TIER_CONFIG_FILES = [
|
|
276
|
+
["config.local.yaml", "local-config"],
|
|
277
|
+
["config.yaml", "config"]
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
119
282
|
// ../db/src/vault-config.ts
|
|
120
283
|
import { readFile } from "node:fs/promises";
|
|
121
|
-
import { join as
|
|
122
|
-
import { parse } from "yaml";
|
|
123
|
-
import { z } from "zod";
|
|
284
|
+
import { join as join3 } from "node:path";
|
|
285
|
+
import { parse as parse2 } from "yaml";
|
|
286
|
+
import { z as z2 } from "zod";
|
|
124
287
|
function isValidRegex(pattern) {
|
|
125
288
|
try {
|
|
126
289
|
return Boolean(new RegExp(pattern));
|
|
@@ -132,17 +295,17 @@ function kindName(entry) {
|
|
|
132
295
|
return typeof entry === "string" ? entry : entry.name;
|
|
133
296
|
}
|
|
134
297
|
function configJsonSchema() {
|
|
135
|
-
return
|
|
298
|
+
return z2.toJSONSchema(VaultConfigSchema, { target: "draft-7" });
|
|
136
299
|
}
|
|
137
|
-
async function loadVaultConfig(
|
|
138
|
-
const path =
|
|
300
|
+
async function loadVaultConfig(vaultRoot) {
|
|
301
|
+
const path = join3(vaultRoot, "vault.yaml");
|
|
139
302
|
let raw;
|
|
140
303
|
try {
|
|
141
304
|
raw = await readFile(path, "utf8");
|
|
142
305
|
} catch (err) {
|
|
143
306
|
throw new Error(`vault.yaml not found at ${path}`, { cause: err });
|
|
144
307
|
}
|
|
145
|
-
const parsed = VaultConfigSchema.safeParse(
|
|
308
|
+
const parsed = VaultConfigSchema.safeParse(parse2(raw));
|
|
146
309
|
if (!parsed.success) {
|
|
147
310
|
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
148
311
|
throw new Error(`Invalid vault.yaml at ${path}:
|
|
@@ -154,47 +317,47 @@ var ScopeSchema, KindEntrySchema, WhenSchema, DedupSchema, TriggerSchema, Trigge
|
|
|
154
317
|
var init_vault_config = __esm({
|
|
155
318
|
"../db/src/vault-config.ts"() {
|
|
156
319
|
"use strict";
|
|
157
|
-
ScopeSchema =
|
|
158
|
-
repo:
|
|
320
|
+
ScopeSchema = z2.strictObject({
|
|
321
|
+
repo: z2.string().optional().describe(
|
|
159
322
|
"Consumer repo path (~ expands). Load-bearing for kmd hook: the active scope resolves by matching the session cwd against it."
|
|
160
323
|
),
|
|
161
|
-
methodology:
|
|
162
|
-
status:
|
|
324
|
+
methodology: z2.string().optional().describe("Must appear in the methodologies list."),
|
|
325
|
+
status: z2.string().describe("Free string; keep within statuses by convention.")
|
|
163
326
|
});
|
|
164
|
-
KindEntrySchema =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
name:
|
|
168
|
-
signal:
|
|
169
|
-
where:
|
|
327
|
+
KindEntrySchema = z2.union([
|
|
328
|
+
z2.string(),
|
|
329
|
+
z2.strictObject({
|
|
330
|
+
name: z2.string(),
|
|
331
|
+
signal: z2.string().describe("When to pick this kind."),
|
|
332
|
+
where: z2.string().describe("Path pattern pages of this kind follow.")
|
|
170
333
|
})
|
|
171
334
|
]);
|
|
172
|
-
WhenSchema =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
name:
|
|
176
|
-
fresh:
|
|
177
|
-
than:
|
|
335
|
+
WhenSchema = z2.union([
|
|
336
|
+
z2.string(),
|
|
337
|
+
z2.strictObject({
|
|
338
|
+
name: z2.enum(["newer-than"]),
|
|
339
|
+
fresh: z2.array(z2.string().min(1)).min(1),
|
|
340
|
+
than: z2.array(z2.string().min(1)).min(1)
|
|
178
341
|
})
|
|
179
342
|
]);
|
|
180
|
-
DedupSchema =
|
|
181
|
-
|
|
182
|
-
|
|
343
|
+
DedupSchema = z2.union([
|
|
344
|
+
z2.enum(["session", "never"]),
|
|
345
|
+
z2.strictObject({ minutes: z2.number().int().positive() })
|
|
183
346
|
]);
|
|
184
|
-
TriggerSchema =
|
|
185
|
-
id:
|
|
186
|
-
on:
|
|
187
|
-
enforce:
|
|
188
|
-
keywords:
|
|
189
|
-
intent:
|
|
190
|
-
tool:
|
|
191
|
-
args_match:
|
|
192
|
-
files:
|
|
347
|
+
TriggerSchema = z2.strictObject({
|
|
348
|
+
id: z2.string().min(1).describe("Unique per scope list; duplicates keep the first occurrence."),
|
|
349
|
+
on: z2.enum(["prompt", "pretool"]),
|
|
350
|
+
enforce: z2.enum(["inject", "warn", "block"]).describe("inject: context line \xB7 warn: stderr \xB7 block: deny with reason."),
|
|
351
|
+
keywords: z2.array(z2.string().min(1)).optional().describe("Word-boundary, porter-stemmed match. Prompt triggers need keywords or intent."),
|
|
352
|
+
intent: z2.array(z2.string()).optional().describe("Case-insensitive regexes over the raw prompt \u2014 the stemming escape hatch."),
|
|
353
|
+
tool: z2.string().optional().describe("Exact tool name; pretool matchers AND-compose."),
|
|
354
|
+
args_match: z2.string().optional().describe("Regex over the serialized tool input."),
|
|
355
|
+
files: z2.array(z2.string().min(1)).optional().describe("Globs against the paths the tool touches; pretool triggers only."),
|
|
193
356
|
when: WhenSchema.optional().describe(
|
|
194
357
|
"Precondition \u2014 the gate fires only when it is UNMET. newer-than: the newest page matching fresh must carry frontmatter updated at or after the newest matching than."
|
|
195
358
|
),
|
|
196
|
-
text:
|
|
197
|
-
reason:
|
|
359
|
+
text: z2.string().optional().describe("Required for inject and warn \u2014 the line emitted."),
|
|
360
|
+
reason: z2.string().optional().describe("Required for block \u2014 the denial the agent reads."),
|
|
198
361
|
dedup: DedupSchema.optional().describe(
|
|
199
362
|
"Re-fire policy: session (default, once per session), never, or {minutes: N} for at most once per bucket. Rejected on block triggers \u2014 blocks are dedup-exempt."
|
|
200
363
|
)
|
|
@@ -240,36 +403,42 @@ var init_vault_config = __esm({
|
|
|
240
403
|
}
|
|
241
404
|
}
|
|
242
405
|
});
|
|
243
|
-
TriggersSchema =
|
|
244
|
-
BuiltinHooksSchema =
|
|
245
|
-
resync:
|
|
246
|
-
reason:
|
|
247
|
-
text:
|
|
406
|
+
TriggersSchema = z2.record(z2.string(), z2.array(TriggerSchema));
|
|
407
|
+
BuiltinHooksSchema = z2.strictObject({
|
|
408
|
+
resync: z2.strictObject({
|
|
409
|
+
reason: z2.string().min(1).optional().describe("Validate-errors preamble; the engine appends the error lines."),
|
|
410
|
+
text: z2.string().min(1).optional().describe("Sync-failed note.")
|
|
248
411
|
}).optional(),
|
|
249
|
-
"handoff-gate":
|
|
250
|
-
reason:
|
|
412
|
+
"handoff-gate": z2.strictObject({
|
|
413
|
+
reason: z2.string().min(1).optional().describe("Stop-block preamble; the engine appends the error lines.")
|
|
414
|
+
}).optional(),
|
|
415
|
+
orient: z2.strictObject({
|
|
416
|
+
text: z2.string().min(1).optional().describe("Session-start prime instruction; the engine prepends the resolved scope.")
|
|
417
|
+
}).optional(),
|
|
418
|
+
reorient: z2.strictObject({
|
|
419
|
+
text: z2.string().min(1).optional().describe("Post-compaction re-orientation; the engine prepends the resolved scope.")
|
|
251
420
|
}).optional()
|
|
252
421
|
});
|
|
253
|
-
VaultConfigSchema =
|
|
254
|
-
scopes:
|
|
255
|
-
kinds:
|
|
422
|
+
VaultConfigSchema = z2.strictObject({
|
|
423
|
+
scopes: z2.record(z2.string(), ScopeSchema).describe("Scope name \u2192 entry; key = directory name under projects/."),
|
|
424
|
+
kinds: z2.array(KindEntrySchema).describe(
|
|
256
425
|
"Page kind vocabulary; validate-enforced. Object form adds a kind-selector row to wiki://authoring."
|
|
257
426
|
),
|
|
258
|
-
statuses:
|
|
259
|
-
methodologies:
|
|
260
|
-
tags:
|
|
261
|
-
canonical:
|
|
262
|
-
aliases:
|
|
427
|
+
statuses: z2.array(z2.string()).describe("Page status vocabulary; validate-enforced."),
|
|
428
|
+
methodologies: z2.array(z2.string()).describe("Methodology vocabulary for pages and scope entries."),
|
|
429
|
+
tags: z2.strictObject({
|
|
430
|
+
canonical: z2.array(z2.string()).describe("Approved tags."),
|
|
431
|
+
aliases: z2.record(z2.string(), z2.string()).describe("Alias \u2192 canonical; validate warns on alias use.")
|
|
263
432
|
}),
|
|
264
|
-
authoring_rules:
|
|
265
|
-
authoring_rules_extra:
|
|
266
|
-
sync_protocol:
|
|
267
|
-
sync_protocol_extra:
|
|
433
|
+
authoring_rules: z2.string().optional().describe("Replaces the served \xA7 Authoring rules entirely \u2014 escape hatch."),
|
|
434
|
+
authoring_rules_extra: z2.string().optional().describe("Appended after the served \xA7 Authoring rules."),
|
|
435
|
+
sync_protocol: z2.string().optional().describe("Replaces the served \xA7 Resync protocol entirely \u2014 escape hatch."),
|
|
436
|
+
sync_protocol_extra: z2.string().optional().describe("Appended after the served \xA7 Resync protocol."),
|
|
268
437
|
triggers: TriggersSchema.optional().describe(
|
|
269
438
|
'Full-replace of the trigger base per scope \u2014 escape hatch. "_all" is reserved for triggers_extra.'
|
|
270
439
|
),
|
|
271
440
|
builtin_hooks: BuiltinHooksSchema.optional().describe(
|
|
272
|
-
"Message overrides for the fixed-function hooks (resync, handoff-gate) by public id."
|
|
441
|
+
"Message overrides for the fixed-function hooks (resync, handoff-gate, orient, reorient) by public id."
|
|
273
442
|
),
|
|
274
443
|
triggers_extra: TriggersSchema.optional().describe(
|
|
275
444
|
'Appended per scope after the engine defaults; the reserved "_all" key fires in every session.'
|
|
@@ -515,11 +684,12 @@ and must match current code at every commit.
|
|
|
515
684
|
});
|
|
516
685
|
|
|
517
686
|
// ../cli/src/init.ts
|
|
687
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
518
688
|
import { mkdir, readdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
519
|
-
import { join as
|
|
689
|
+
import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
520
690
|
import { stringify } from "yaml";
|
|
521
691
|
async function refreshSchemaFile(root) {
|
|
522
|
-
const path =
|
|
692
|
+
const path = join4(root, SCHEMA_FILE);
|
|
523
693
|
const next = `${JSON.stringify(configJsonSchema(), null, 2)}
|
|
524
694
|
`;
|
|
525
695
|
try {
|
|
@@ -530,7 +700,7 @@ async function refreshSchemaFile(root) {
|
|
|
530
700
|
return true;
|
|
531
701
|
}
|
|
532
702
|
async function scaffoldVault(dir) {
|
|
533
|
-
const root =
|
|
703
|
+
const root = resolve3(dir);
|
|
534
704
|
let entries = [];
|
|
535
705
|
try {
|
|
536
706
|
entries = await readdir(root);
|
|
@@ -548,33 +718,97 @@ async function scaffoldVault(dir) {
|
|
|
548
718
|
);
|
|
549
719
|
}
|
|
550
720
|
for (const domain of DOMAIN_DIRS) {
|
|
551
|
-
await mkdir(
|
|
721
|
+
await mkdir(join4(root, domain), { recursive: true });
|
|
552
722
|
}
|
|
553
|
-
await mkdir(
|
|
723
|
+
await mkdir(join4(root, "templates"), { recursive: true });
|
|
554
724
|
for (const [file, content] of Object.entries(VAULT_TEMPLATES)) {
|
|
555
|
-
await writeFile(
|
|
725
|
+
await writeFile(join4(root, "templates", file), content);
|
|
556
726
|
}
|
|
557
727
|
await refreshSchemaFile(root);
|
|
558
|
-
await writeFile(
|
|
728
|
+
await writeFile(join4(root, "vault.yaml"), SCHEMA_MODELINE + stringify(STARTER_CONFIG));
|
|
559
729
|
return root;
|
|
560
730
|
}
|
|
561
|
-
async function promptYesNo(question, input = process.stdin, output = process.stderr) {
|
|
731
|
+
async function promptYesNo(question, input = process.stdin, output = process.stderr, defaultYes = false) {
|
|
562
732
|
const { createInterface } = await import("node:readline/promises");
|
|
563
733
|
const rl = createInterface({ input, output });
|
|
564
734
|
try {
|
|
565
|
-
const answer = await rl.question(question);
|
|
566
|
-
|
|
735
|
+
const answer = (await rl.question(question)).trim();
|
|
736
|
+
if (answer === "") return defaultYes;
|
|
737
|
+
return /^y(es)?$/i.test(answer);
|
|
567
738
|
} finally {
|
|
568
739
|
rl.close();
|
|
569
740
|
}
|
|
570
741
|
}
|
|
571
|
-
|
|
742
|
+
function findGitRoot(from) {
|
|
743
|
+
let level = resolve3(from);
|
|
744
|
+
for (; ; ) {
|
|
745
|
+
if (existsSync3(join4(level, ".git"))) return level;
|
|
746
|
+
const parent = dirname3(level);
|
|
747
|
+
if (parent === level) return null;
|
|
748
|
+
level = parent;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async function runInitLocal(yes) {
|
|
752
|
+
const root = findGitRoot(process.cwd()) ?? process.cwd();
|
|
753
|
+
const vaultTarget = join4(root, "vault");
|
|
754
|
+
const kmdDir = join4(root, ".kmd");
|
|
755
|
+
if (!yes) {
|
|
756
|
+
if (process.stdin.isTTY) {
|
|
757
|
+
const ok = await promptYesNo(
|
|
758
|
+
`initialize project vault at ${vaultTarget} (state in ${kmdDir})? [Y/n] `,
|
|
759
|
+
process.stdin,
|
|
760
|
+
process.stderr,
|
|
761
|
+
true
|
|
762
|
+
);
|
|
763
|
+
if (!ok) {
|
|
764
|
+
console.error("init: aborted");
|
|
765
|
+
process.exit(1);
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
console.error("usage: kmd init --local -y (piped stdin cannot confirm the target)");
|
|
769
|
+
process.exit(2);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
let vaultRoot;
|
|
773
|
+
try {
|
|
774
|
+
vaultRoot = await scaffoldVault(vaultTarget);
|
|
775
|
+
} catch (err) {
|
|
776
|
+
console.error(`init: ${err instanceof Error ? err.message : err}`);
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
await mkdir(kmdDir, { recursive: true });
|
|
780
|
+
const gitignore = join4(kmdDir, ".gitignore");
|
|
781
|
+
if (!existsSync3(gitignore)) await writeFile(gitignore, TIER_GITIGNORE);
|
|
782
|
+
console.log(`initialized project vault at ${vaultRoot}
|
|
783
|
+
|
|
784
|
+
${kmdDir}/ state home \u2014 index and hook state live with the repo
|
|
785
|
+
${kmdDir}/.gitignore db/, state/, config.local.yaml stay untracked
|
|
786
|
+
|
|
787
|
+
every kmd command run inside ${root} now resolves this vault (project tier).
|
|
788
|
+
next steps:
|
|
789
|
+
kmd sync # builds the index at .kmd/db/index.db`);
|
|
790
|
+
}
|
|
791
|
+
async function runInit(dir, yes = false, local = false, setDefaultFlag = false) {
|
|
792
|
+
if (local) {
|
|
793
|
+
if (dir !== void 0) {
|
|
794
|
+
console.error("usage: kmd init --local (the root is the nearest .git ancestor, not chosen)");
|
|
795
|
+
process.exit(2);
|
|
796
|
+
}
|
|
797
|
+
if (setDefaultFlag) {
|
|
798
|
+
console.error(
|
|
799
|
+
"kmd init: --set-default applies to global init only (a project vault resolves by location)"
|
|
800
|
+
);
|
|
801
|
+
process.exit(2);
|
|
802
|
+
}
|
|
803
|
+
await runInitLocal(yes);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
572
806
|
let target = dir;
|
|
573
807
|
if (!target) {
|
|
574
808
|
if (yes) {
|
|
575
809
|
target = ".";
|
|
576
810
|
} else if (process.stdin.isTTY) {
|
|
577
|
-
const ok = await promptYesNo(`initialize a vault in ${
|
|
811
|
+
const ok = await promptYesNo(`initialize a vault in ${resolve3(".")}? [y/N] `);
|
|
578
812
|
if (!ok) {
|
|
579
813
|
console.error("init: aborted");
|
|
580
814
|
process.exit(1);
|
|
@@ -598,16 +832,34 @@ async function runInit(dir, yes = false) {
|
|
|
598
832
|
vault.yaml starter vocabulary \u2014 add your first scope under scopes:
|
|
599
833
|
vault.schema.json IDE validation via the yaml-language-server modeline
|
|
600
834
|
templates/ ${templateCount} built-in templates (served at wiki://template/...)
|
|
601
|
-
projects/ research/ notes
|
|
602
|
-
|
|
835
|
+
projects/ research/ notes/`);
|
|
836
|
+
let setDefault = setDefaultFlag;
|
|
837
|
+
if (!setDefault && !yes && process.stdin.isTTY) {
|
|
838
|
+
setDefault = await promptYesNo(
|
|
839
|
+
`set as default vault in ~/.kmd/config.yaml? [Y/n] `,
|
|
840
|
+
process.stdin,
|
|
841
|
+
process.stderr,
|
|
842
|
+
true
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
if (setDefault) {
|
|
846
|
+
setGlobalConfigValue("default_vault", root);
|
|
847
|
+
console.log(`
|
|
848
|
+
default_vault: ${root}
|
|
603
849
|
next steps:
|
|
604
|
-
|
|
850
|
+
kmd mcp # stdio MCP server (prime, search) \u2014 resolves the default`);
|
|
851
|
+
} else {
|
|
852
|
+
console.log(`
|
|
853
|
+
next steps:
|
|
854
|
+
kmd config set default_vault ${root} # make it the machine default
|
|
605
855
|
kmd mcp ${root} # stdio MCP server (prime, search)`);
|
|
856
|
+
}
|
|
606
857
|
}
|
|
607
|
-
var SCHEMA_FILE, SCHEMA_MODELINE, STARTER_CONFIG, DOMAIN_DIRS;
|
|
858
|
+
var SCHEMA_FILE, SCHEMA_MODELINE, STARTER_CONFIG, DOMAIN_DIRS, TIER_GITIGNORE;
|
|
608
859
|
var init_init = __esm({
|
|
609
860
|
"../cli/src/init.ts"() {
|
|
610
861
|
"use strict";
|
|
862
|
+
init_kmd_config();
|
|
611
863
|
init_vault_config();
|
|
612
864
|
init_init_templates();
|
|
613
865
|
SCHEMA_FILE = "vault.schema.json";
|
|
@@ -621,15 +873,16 @@ var init_init = __esm({
|
|
|
621
873
|
tags: { canonical: [], aliases: {} }
|
|
622
874
|
};
|
|
623
875
|
DOMAIN_DIRS = ["projects", "research", "notes"];
|
|
876
|
+
TIER_GITIGNORE = "db/\nstate/\nconfig.local.yaml\n";
|
|
624
877
|
}
|
|
625
878
|
});
|
|
626
879
|
|
|
627
880
|
// ../cli/src/sync.ts
|
|
628
881
|
import { createHash as createHash2 } from "node:crypto";
|
|
629
|
-
import { mkdirSync } from "node:fs";
|
|
882
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
630
883
|
import { readdir as readdir2, readFile as readFile3 } from "node:fs/promises";
|
|
631
|
-
import { dirname, join as
|
|
632
|
-
import { z as
|
|
884
|
+
import { dirname as dirname4, join as join5, relative, sep } from "node:path";
|
|
885
|
+
import { z as z3 } from "zod";
|
|
633
886
|
function loadEnv() {
|
|
634
887
|
const parsed = EnvSchema.safeParse({
|
|
635
888
|
WIKI_VAULT: process.env.WIKI_VAULT
|
|
@@ -649,13 +902,13 @@ async function walkMarkdown(root, domain) {
|
|
|
649
902
|
for (const entry of entries) {
|
|
650
903
|
if (entry.name.startsWith(".")) continue;
|
|
651
904
|
if (entry.isDirectory()) {
|
|
652
|
-
await recurse(
|
|
905
|
+
await recurse(join5(dir, entry.name));
|
|
653
906
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
654
|
-
out.push(
|
|
907
|
+
out.push(join5(dir, entry.name));
|
|
655
908
|
}
|
|
656
909
|
}
|
|
657
910
|
}
|
|
658
|
-
await recurse(
|
|
911
|
+
await recurse(join5(root, domain));
|
|
659
912
|
return out;
|
|
660
913
|
}
|
|
661
914
|
function toRelativePath(root, absolute) {
|
|
@@ -777,23 +1030,23 @@ function syncPage(db, fields) {
|
|
|
777
1030
|
}
|
|
778
1031
|
return "changed";
|
|
779
1032
|
}
|
|
780
|
-
async function syncVault(
|
|
781
|
-
const dbPath = resolveIndexPath(
|
|
782
|
-
const vaultConfig = await loadVaultConfig(
|
|
1033
|
+
async function syncVault(vaultRoot) {
|
|
1034
|
+
const dbPath = resolveIndexPath(vaultRoot);
|
|
1035
|
+
const vaultConfig = await loadVaultConfig(vaultRoot);
|
|
783
1036
|
const scopes = new Set(Object.keys(vaultConfig.scopes));
|
|
784
|
-
|
|
1037
|
+
mkdirSync2(dirname4(dbPath), { recursive: true });
|
|
785
1038
|
const db = openDatabase(dbPath);
|
|
786
1039
|
try {
|
|
787
1040
|
const files = [];
|
|
788
1041
|
for (const domain of SCAN_DOMAINS) {
|
|
789
|
-
files.push(...await walkMarkdown(
|
|
1042
|
+
files.push(...await walkMarkdown(vaultRoot, domain));
|
|
790
1043
|
}
|
|
791
1044
|
const indexedPaths = [];
|
|
792
1045
|
let changed = 0;
|
|
793
1046
|
let unchanged = 0;
|
|
794
1047
|
let skipped = 0;
|
|
795
1048
|
for (const file of files) {
|
|
796
|
-
const path = toRelativePath(
|
|
1049
|
+
const path = toRelativePath(vaultRoot, file);
|
|
797
1050
|
const raw = await readFile3(file, "utf8");
|
|
798
1051
|
const parsed = parseFrontmatter(raw);
|
|
799
1052
|
const fields = buildPageFields(path, raw, parsed, scopes);
|
|
@@ -815,9 +1068,9 @@ async function syncVault(vaultRoot2) {
|
|
|
815
1068
|
const linkResult = db.prepare("DELETE FROM links WHERE source_path NOT IN (SELECT path FROM pages)").run();
|
|
816
1069
|
const linksDeleted = Number(linkResult.changes);
|
|
817
1070
|
db.exec("INSERT INTO pages_fts(pages_fts) VALUES('rebuild')");
|
|
818
|
-
setMeta(db, "vault_root", canonicalVaultRoot(
|
|
1071
|
+
setMeta(db, "vault_root", canonicalVaultRoot(vaultRoot));
|
|
819
1072
|
setMeta(db, "last_synced", (/* @__PURE__ */ new Date()).toISOString());
|
|
820
|
-
if (await refreshSchemaFile(
|
|
1073
|
+
if (await refreshSchemaFile(vaultRoot)) {
|
|
821
1074
|
console.error("sync: vault.schema.json refreshed to the running engine");
|
|
822
1075
|
}
|
|
823
1076
|
return {
|
|
@@ -851,8 +1104,8 @@ var init_sync = __esm({
|
|
|
851
1104
|
init_vault_config();
|
|
852
1105
|
init_frontmatter();
|
|
853
1106
|
init_init();
|
|
854
|
-
EnvSchema =
|
|
855
|
-
WIKI_VAULT:
|
|
1107
|
+
EnvSchema = z3.object({
|
|
1108
|
+
WIKI_VAULT: z3.string().min(1)
|
|
856
1109
|
});
|
|
857
1110
|
SCAN_DOMAINS = ["projects", "research", "notes"];
|
|
858
1111
|
WIKILINK_RE = /\[\[([^\]|#^]+)(?:[#^][^\]|]*)?(?:\|([^\]]+))?\]\]/g;
|
|
@@ -871,7 +1124,7 @@ var init_sync = __esm({
|
|
|
871
1124
|
|
|
872
1125
|
// ../cli/src/validate.ts
|
|
873
1126
|
import { readFile as readFile4, stat } from "node:fs/promises";
|
|
874
|
-
import { join as
|
|
1127
|
+
import { join as join6 } from "node:path";
|
|
875
1128
|
function hasIndexableTitle(data) {
|
|
876
1129
|
return typeof data.title === "string" && data.title.trim() !== "";
|
|
877
1130
|
}
|
|
@@ -1243,7 +1496,7 @@ async function validateVault(root) {
|
|
|
1243
1496
|
for (const name of customKindNames(cfg)) {
|
|
1244
1497
|
const file = `templates/${name}.md`;
|
|
1245
1498
|
try {
|
|
1246
|
-
await stat(
|
|
1499
|
+
await stat(join6(root, file));
|
|
1247
1500
|
} catch {
|
|
1248
1501
|
findings.push({
|
|
1249
1502
|
path: file,
|
|
@@ -1315,15 +1568,18 @@ var cli_exports = {};
|
|
|
1315
1568
|
__export(cli_exports, {
|
|
1316
1569
|
main: () => main,
|
|
1317
1570
|
resolveCli: () => resolveCli,
|
|
1571
|
+
resolveCliVault: () => resolveCliVault,
|
|
1318
1572
|
runConfig: () => runConfig,
|
|
1573
|
+
runConfigGet: () => runConfigGet,
|
|
1574
|
+
runConfigSet: () => runConfigSet,
|
|
1575
|
+
runConfigUnset: () => runConfigUnset,
|
|
1319
1576
|
runDbReset: () => runDbReset,
|
|
1320
1577
|
runInit: () => runInit,
|
|
1321
1578
|
runSyncCommand: () => runSyncCommand,
|
|
1322
|
-
runValidate: () => runValidate
|
|
1323
|
-
vaultRoot: () => vaultRoot
|
|
1579
|
+
runValidate: () => runValidate
|
|
1324
1580
|
});
|
|
1325
|
-
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
1326
|
-
import { dirname as
|
|
1581
|
+
import { existsSync as existsSync4, readdirSync, rmSync } from "node:fs";
|
|
1582
|
+
import { dirname as dirname5, join as join7, resolve as resolve4 } from "node:path";
|
|
1327
1583
|
import { parseArgs } from "node:util";
|
|
1328
1584
|
function resolveCli(argv) {
|
|
1329
1585
|
const { positionals: positionals2 } = parseArgs({ args: argv, allowPositionals: true, strict: false });
|
|
@@ -1339,27 +1595,42 @@ function resolveCli(argv) {
|
|
|
1339
1595
|
}
|
|
1340
1596
|
return { kind: "error", message: `unknown command: ${command2}` };
|
|
1341
1597
|
}
|
|
1342
|
-
function
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1598
|
+
function resolveCliVault(positional, defaultRoot) {
|
|
1599
|
+
return resolveVaultRoot({
|
|
1600
|
+
positional,
|
|
1601
|
+
defaultRoot,
|
|
1602
|
+
projectDir: process.env.KMD_PROJECT_DIR ?? process.cwd(),
|
|
1603
|
+
envVault: process.env.WIKI_VAULT,
|
|
1604
|
+
globalDefault: loadGlobalConfig().default_vault,
|
|
1605
|
+
onSkip: (candidate) => console.error(
|
|
1606
|
+
`kmd: ignoring ${candidate} \u2014 no .kmd marker beside it; mkdir ${dirname5(candidate)}/.kmd to bind it as the project vault`
|
|
1607
|
+
)
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
function requireCliVault(positional) {
|
|
1611
|
+
const resolution = resolveCliVault(positional);
|
|
1612
|
+
if (resolution.root === null) {
|
|
1613
|
+
console.error(NO_VAULT_HINT);
|
|
1346
1614
|
process.exit(1);
|
|
1347
1615
|
}
|
|
1348
|
-
|
|
1616
|
+
process.env.WIKI_VAULT = resolution.root;
|
|
1617
|
+
return { root: resolution.root, resolution };
|
|
1349
1618
|
}
|
|
1350
1619
|
function reportFindings(findings) {
|
|
1351
1620
|
for (const f of findings) {
|
|
1352
1621
|
console.error(`${f.severity}: ${f.path} [${f.rule}] ${f.message}`);
|
|
1353
1622
|
}
|
|
1354
1623
|
}
|
|
1355
|
-
async function runValidate() {
|
|
1356
|
-
const
|
|
1624
|
+
async function runValidate(positional) {
|
|
1625
|
+
const { root } = requireCliVault(positional);
|
|
1626
|
+
const findings = await validateVault(root);
|
|
1357
1627
|
reportFindings(findings);
|
|
1358
1628
|
console.log(`validate: ${findings.length} finding(s)`);
|
|
1359
1629
|
process.exit(hasErrors(findings) ? 1 : 0);
|
|
1360
1630
|
}
|
|
1361
|
-
async function runSyncCommand() {
|
|
1362
|
-
const
|
|
1631
|
+
async function runSyncCommand(positional) {
|
|
1632
|
+
const { root } = requireCliVault(positional);
|
|
1633
|
+
const findings = await validateVault(root);
|
|
1363
1634
|
reportFindings(findings);
|
|
1364
1635
|
if (hasErrors(findings)) {
|
|
1365
1636
|
const errors = findings.filter((f) => f.severity === "error").length;
|
|
@@ -1372,7 +1643,7 @@ function describeVault(root) {
|
|
|
1372
1643
|
const vault = canonicalVaultRoot(root);
|
|
1373
1644
|
const index = resolveIndexPath(vault);
|
|
1374
1645
|
let synced = "never";
|
|
1375
|
-
if (
|
|
1646
|
+
if (existsSync4(index)) {
|
|
1376
1647
|
const db = openDatabase(index);
|
|
1377
1648
|
try {
|
|
1378
1649
|
synced = getMeta(db, "last_synced") ?? "never";
|
|
@@ -1389,12 +1660,12 @@ function printVault(d) {
|
|
|
1389
1660
|
}
|
|
1390
1661
|
function knownVaults() {
|
|
1391
1662
|
const root = indexRootDir();
|
|
1392
|
-
if (!
|
|
1663
|
+
if (!existsSync4(root)) return [];
|
|
1393
1664
|
const known = [];
|
|
1394
1665
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
1395
1666
|
if (!entry.isDirectory()) continue;
|
|
1396
|
-
const index =
|
|
1397
|
-
if (!
|
|
1667
|
+
const index = join7(root, entry.name, "index.db");
|
|
1668
|
+
if (!existsSync4(index)) continue;
|
|
1398
1669
|
const db = openDatabase(index);
|
|
1399
1670
|
try {
|
|
1400
1671
|
const vault = getMeta(db, "vault_root");
|
|
@@ -1406,17 +1677,16 @@ function knownVaults() {
|
|
|
1406
1677
|
}
|
|
1407
1678
|
return known;
|
|
1408
1679
|
}
|
|
1409
|
-
async function runConfig() {
|
|
1410
|
-
const
|
|
1411
|
-
if (root) {
|
|
1412
|
-
printVault(describeVault(root));
|
|
1680
|
+
async function runConfig(positional) {
|
|
1681
|
+
const resolution = resolveCliVault(positional);
|
|
1682
|
+
if (resolution.root !== null) {
|
|
1683
|
+
printVault(describeVault(resolution.root));
|
|
1684
|
+
console.log(`source: ${SOURCE_LABELS[resolution.source]}`);
|
|
1413
1685
|
return;
|
|
1414
1686
|
}
|
|
1415
1687
|
const known = knownVaults();
|
|
1416
1688
|
if (known.length === 0) {
|
|
1417
|
-
console.error(
|
|
1418
|
-
"no vault specified and none known \u2014 pass a vault root, set WIKI_VAULT, or run `kmd sync <vault-root>` once"
|
|
1419
|
-
);
|
|
1689
|
+
console.error(NO_VAULT_HINT);
|
|
1420
1690
|
process.exit(1);
|
|
1421
1691
|
}
|
|
1422
1692
|
known.forEach((d, i) => {
|
|
@@ -1424,14 +1694,44 @@ async function runConfig() {
|
|
|
1424
1694
|
printVault(d);
|
|
1425
1695
|
});
|
|
1426
1696
|
}
|
|
1427
|
-
async function
|
|
1428
|
-
|
|
1429
|
-
|
|
1697
|
+
async function runConfigSet(key, value) {
|
|
1698
|
+
if (key !== "default_vault" || !value) {
|
|
1699
|
+
console.error("usage: kmd config set default_vault <path>");
|
|
1700
|
+
process.exit(2);
|
|
1701
|
+
}
|
|
1702
|
+
const vault = resolve4(value);
|
|
1703
|
+
await loadVaultConfig(vault);
|
|
1704
|
+
setGlobalConfigValue("default_vault", vault);
|
|
1705
|
+
console.log(`default_vault: ${vault}`);
|
|
1706
|
+
}
|
|
1707
|
+
async function runConfigGet(key) {
|
|
1708
|
+
if (key !== "default_vault") {
|
|
1709
|
+
console.error("usage: kmd config get default_vault");
|
|
1710
|
+
process.exit(2);
|
|
1711
|
+
}
|
|
1712
|
+
const value = loadGlobalConfig().default_vault;
|
|
1713
|
+
if (value === void 0) process.exit(1);
|
|
1714
|
+
console.log(value);
|
|
1715
|
+
}
|
|
1716
|
+
async function runConfigUnset(key) {
|
|
1717
|
+
if (key !== "default_vault") {
|
|
1718
|
+
console.error("usage: kmd config unset default_vault");
|
|
1719
|
+
process.exit(2);
|
|
1720
|
+
}
|
|
1721
|
+
if (!unsetGlobalConfigValue("default_vault")) {
|
|
1722
|
+
console.error("default_vault is not set");
|
|
1723
|
+
process.exit(1);
|
|
1724
|
+
}
|
|
1725
|
+
console.log("default_vault unset");
|
|
1726
|
+
}
|
|
1727
|
+
async function runDbReset(positional) {
|
|
1728
|
+
const resolution = resolveCliVault(positional);
|
|
1729
|
+
if (resolution.root === null) {
|
|
1430
1730
|
console.error("usage: kmd db reset [<vault-root>] (or set WIKI_VAULT)");
|
|
1431
1731
|
process.exit(2);
|
|
1432
1732
|
}
|
|
1433
|
-
const dir =
|
|
1434
|
-
if (!
|
|
1733
|
+
const dir = dirname5(resolveIndexPath(resolution.root));
|
|
1734
|
+
if (!existsSync4(dir)) {
|
|
1435
1735
|
console.log(`${dir} does not exist \u2014 nothing to reset`);
|
|
1436
1736
|
return;
|
|
1437
1737
|
}
|
|
@@ -1450,53 +1750,126 @@ async function main() {
|
|
|
1450
1750
|
await runValidate();
|
|
1451
1751
|
}
|
|
1452
1752
|
}
|
|
1753
|
+
var SOURCE_LABELS, NO_VAULT_HINT;
|
|
1453
1754
|
var init_cli = __esm({
|
|
1454
1755
|
"../cli/src/cli.ts"() {
|
|
1455
1756
|
"use strict";
|
|
1456
1757
|
init_database();
|
|
1758
|
+
init_kmd_config();
|
|
1759
|
+
init_vault_config();
|
|
1457
1760
|
init_sync();
|
|
1458
1761
|
init_validate();
|
|
1459
1762
|
init_init();
|
|
1763
|
+
SOURCE_LABELS = {
|
|
1764
|
+
positional: "positional",
|
|
1765
|
+
"project-local-config": "project tier (local config)",
|
|
1766
|
+
"project-config": "project tier (config)",
|
|
1767
|
+
"project-convention": "project tier (convention)",
|
|
1768
|
+
"default-root": "--default-root",
|
|
1769
|
+
env: "$WIKI_VAULT",
|
|
1770
|
+
"global-config": "global config (default_vault)",
|
|
1771
|
+
none: "none"
|
|
1772
|
+
};
|
|
1773
|
+
NO_VAULT_HINT = "no vault resolvable \u2014 pass a vault root, run inside a project vault, set WIKI_VAULT, or `kmd config set default_vault <path>`";
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
// ../mcp/src/binding.ts
|
|
1778
|
+
import { fileURLToPath } from "node:url";
|
|
1779
|
+
function projectDirsFromRoots(roots) {
|
|
1780
|
+
const dirs = [];
|
|
1781
|
+
for (const root of roots) {
|
|
1782
|
+
if (!root.uri.startsWith("file://")) continue;
|
|
1783
|
+
try {
|
|
1784
|
+
dirs.push(fileURLToPath(root.uri));
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
return dirs;
|
|
1789
|
+
}
|
|
1790
|
+
function resolveDeferredVault(input) {
|
|
1791
|
+
const env = input.env ?? process.env;
|
|
1792
|
+
if (input.rootDirs === null) {
|
|
1793
|
+
return resolveVaultRoot({
|
|
1794
|
+
projectDir: input.cwd,
|
|
1795
|
+
defaultRoot: input.defaultRoot,
|
|
1796
|
+
envVault: input.envVault,
|
|
1797
|
+
globalDefault: input.globalDefault,
|
|
1798
|
+
env,
|
|
1799
|
+
onSkip: input.onSkip
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
for (const dir of input.rootDirs) {
|
|
1803
|
+
const tier = findProjectTier(dir, env, input.onSkip);
|
|
1804
|
+
if (tier !== null) {
|
|
1805
|
+
return { root: tier.vaultRoot, source: `project-${tier.via}`, tierRoot: tier.tierRoot };
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return resolveVaultRoot({
|
|
1809
|
+
defaultRoot: input.defaultRoot,
|
|
1810
|
+
envVault: input.envVault,
|
|
1811
|
+
globalDefault: input.globalDefault,
|
|
1812
|
+
env
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
var init_binding = __esm({
|
|
1816
|
+
"../mcp/src/binding.ts"() {
|
|
1817
|
+
"use strict";
|
|
1818
|
+
init_kmd_config();
|
|
1460
1819
|
}
|
|
1461
1820
|
});
|
|
1462
1821
|
|
|
1463
1822
|
// ../mcp/src/config.ts
|
|
1464
|
-
import { z as
|
|
1465
|
-
function
|
|
1466
|
-
const parsed =
|
|
1823
|
+
import { z as z4 } from "zod";
|
|
1824
|
+
function parseEnv(schema, env) {
|
|
1825
|
+
const parsed = schema.safeParse(env);
|
|
1467
1826
|
if (!parsed.success) {
|
|
1468
1827
|
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1469
1828
|
throw new Error(`Invalid environment configuration:
|
|
1470
1829
|
${issues}`);
|
|
1471
1830
|
}
|
|
1831
|
+
return parsed.data;
|
|
1832
|
+
}
|
|
1833
|
+
function loadConfig(env = process.env) {
|
|
1834
|
+
const data = parseEnv(EnvSchema2, env);
|
|
1835
|
+
return {
|
|
1836
|
+
wikiVault: data.WIKI_VAULT,
|
|
1837
|
+
logLevel: data.LOG_LEVEL,
|
|
1838
|
+
serverName: data.SERVER_NAME,
|
|
1839
|
+
serverVersion: data.SERVER_VERSION
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
function loadServerEnv(env = process.env) {
|
|
1843
|
+
const data = parseEnv(BaseEnvSchema, env);
|
|
1472
1844
|
return {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
serverVersion: parsed.data.SERVER_VERSION
|
|
1845
|
+
logLevel: data.LOG_LEVEL,
|
|
1846
|
+
serverName: data.SERVER_NAME,
|
|
1847
|
+
serverVersion: data.SERVER_VERSION
|
|
1477
1848
|
};
|
|
1478
1849
|
}
|
|
1479
|
-
var EnvSchema2;
|
|
1850
|
+
var BaseEnvSchema, EnvSchema2;
|
|
1480
1851
|
var init_config = __esm({
|
|
1481
1852
|
"../mcp/src/config.ts"() {
|
|
1482
1853
|
"use strict";
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1854
|
+
BaseEnvSchema = z4.object({
|
|
1855
|
+
LOG_LEVEL: z4.enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"]).default("info").describe("Pino log level. Logs go to stderr to keep the stdio JSON-RPC stream clean."),
|
|
1856
|
+
SERVER_NAME: z4.string().default("wiki-mcp"),
|
|
1857
|
+
SERVER_VERSION: z4.string().default("0.0.0")
|
|
1858
|
+
});
|
|
1859
|
+
EnvSchema2 = BaseEnvSchema.extend({
|
|
1860
|
+
WIKI_VAULT: z4.string().min(1).describe("Absolute path to the Obsidian vault root")
|
|
1488
1861
|
});
|
|
1489
1862
|
}
|
|
1490
1863
|
});
|
|
1491
1864
|
|
|
1492
1865
|
// ../mcp/src/db.ts
|
|
1493
|
-
import { mkdirSync as
|
|
1494
|
-
import { dirname as
|
|
1495
|
-
function createDatabase(
|
|
1496
|
-
const dbPath = resolveIndexPath(
|
|
1497
|
-
|
|
1866
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
1867
|
+
import { dirname as dirname6 } from "node:path";
|
|
1868
|
+
function createDatabase(vaultRoot) {
|
|
1869
|
+
const dbPath = resolveIndexPath(vaultRoot);
|
|
1870
|
+
mkdirSync3(dirname6(dbPath), { recursive: true });
|
|
1498
1871
|
const db = openDatabase(dbPath);
|
|
1499
|
-
setMeta(db, "vault_root", canonicalVaultRoot(
|
|
1872
|
+
setMeta(db, "vault_root", canonicalVaultRoot(vaultRoot));
|
|
1500
1873
|
return db;
|
|
1501
1874
|
}
|
|
1502
1875
|
var init_db = __esm({
|
|
@@ -1507,9 +1880,9 @@ var init_db = __esm({
|
|
|
1507
1880
|
});
|
|
1508
1881
|
|
|
1509
1882
|
// ../mcp/src/lib/diag.ts
|
|
1510
|
-
import { appendFileSync, mkdirSync as
|
|
1511
|
-
import { homedir as
|
|
1512
|
-
import { join as
|
|
1883
|
+
import { appendFileSync, mkdirSync as mkdirSync4 } from "node:fs";
|
|
1884
|
+
import { homedir as homedir3 } from "node:os";
|
|
1885
|
+
import { join as join8 } from "node:path";
|
|
1513
1886
|
function diag(msg, data) {
|
|
1514
1887
|
try {
|
|
1515
1888
|
const line = data ? `${(/* @__PURE__ */ new Date()).toISOString()} pid=${process.pid} ${msg} ${JSON.stringify(data)}
|
|
@@ -1523,10 +1896,10 @@ var DIAG_DIR, DIAG_LOG_PATH;
|
|
|
1523
1896
|
var init_diag = __esm({
|
|
1524
1897
|
"../mcp/src/lib/diag.ts"() {
|
|
1525
1898
|
"use strict";
|
|
1526
|
-
DIAG_DIR =
|
|
1527
|
-
DIAG_LOG_PATH =
|
|
1899
|
+
DIAG_DIR = join8(homedir3(), ".local", "state", "wiki-mcp");
|
|
1900
|
+
DIAG_LOG_PATH = join8(DIAG_DIR, "server.log");
|
|
1528
1901
|
try {
|
|
1529
|
-
|
|
1902
|
+
mkdirSync4(DIAG_DIR, { recursive: true });
|
|
1530
1903
|
} catch {
|
|
1531
1904
|
}
|
|
1532
1905
|
}
|
|
@@ -1605,7 +1978,7 @@ function buildVocabulary(config) {
|
|
|
1605
1978
|
}
|
|
1606
1979
|
return lines.join("\n");
|
|
1607
1980
|
}
|
|
1608
|
-
function registerAuthoringResource(mcp,
|
|
1981
|
+
function registerAuthoringResource(mcp, binding) {
|
|
1609
1982
|
mcp.registerResource(
|
|
1610
1983
|
"Authoring guide",
|
|
1611
1984
|
"wiki://authoring",
|
|
@@ -1614,10 +1987,11 @@ function registerAuthoringResource(mcp, vaultRoot2, vaultConfig) {
|
|
|
1614
1987
|
mimeType: "text/markdown"
|
|
1615
1988
|
},
|
|
1616
1989
|
async (uri) => {
|
|
1990
|
+
const { vaultRoot, vaultConfig } = await binding;
|
|
1617
1991
|
const sections = [
|
|
1618
1992
|
"# Wiki authoring guide",
|
|
1619
1993
|
"",
|
|
1620
|
-
`Vault root: \`${canonicalVaultRoot(
|
|
1994
|
+
`Vault root: \`${canonicalVaultRoot(vaultRoot)}\` \u2014 every page path below is relative to it; write files and run \`kmd validate\` / \`kmd sync\` against it.`,
|
|
1621
1995
|
"",
|
|
1622
1996
|
buildKindSelector(vaultConfig.kinds),
|
|
1623
1997
|
"",
|
|
@@ -1766,7 +2140,7 @@ var init_authoring = __esm({
|
|
|
1766
2140
|
|
|
1767
2141
|
// ../mcp/src/resources/templates.ts
|
|
1768
2142
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
1769
|
-
import { join as
|
|
2143
|
+
import { join as join9 } from "node:path";
|
|
1770
2144
|
function customTemplates(config) {
|
|
1771
2145
|
const specs = [];
|
|
1772
2146
|
for (const entry of config.kinds) {
|
|
@@ -1780,41 +2154,52 @@ function customTemplates(config) {
|
|
|
1780
2154
|
}
|
|
1781
2155
|
return specs;
|
|
1782
2156
|
}
|
|
1783
|
-
function
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
cause: err
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
return {
|
|
1801
|
-
contents: [
|
|
1802
|
-
{
|
|
1803
|
-
uri: uri.toString(),
|
|
1804
|
-
mimeType: "text/markdown",
|
|
1805
|
-
text
|
|
1806
|
-
}
|
|
1807
|
-
]
|
|
1808
|
-
};
|
|
2157
|
+
function registerTemplate(mcp, tmpl, binding) {
|
|
2158
|
+
mcp.registerResource(
|
|
2159
|
+
tmpl.name,
|
|
2160
|
+
tmpl.uri,
|
|
2161
|
+
{ description: tmpl.description, mimeType: "text/markdown" },
|
|
2162
|
+
async (uri) => {
|
|
2163
|
+
const { vaultRoot } = await binding;
|
|
2164
|
+
let text;
|
|
2165
|
+
try {
|
|
2166
|
+
text = await readFile5(join9(vaultRoot, "templates", tmpl.file), "utf8");
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
throw new Error(`template file missing: templates/${tmpl.file} (${tmpl.uri})`, {
|
|
2169
|
+
cause: err
|
|
2170
|
+
});
|
|
1809
2171
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
2172
|
+
return {
|
|
2173
|
+
contents: [
|
|
2174
|
+
{
|
|
2175
|
+
uri: uri.toString(),
|
|
2176
|
+
mimeType: "text/markdown",
|
|
2177
|
+
text
|
|
2178
|
+
}
|
|
2179
|
+
]
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
function buildIndexText(vaultConfig) {
|
|
1812
2185
|
const indexLines = ["# Wiki Templates", ""];
|
|
1813
|
-
for (const tmpl of
|
|
2186
|
+
for (const tmpl of [...TEMPLATES, ...customTemplates(vaultConfig)]) {
|
|
1814
2187
|
indexLines.push(`- **${tmpl.name}** \u2014 \`${tmpl.uri}\` `);
|
|
1815
2188
|
indexLines.push(` ${tmpl.description}`);
|
|
1816
2189
|
}
|
|
1817
|
-
|
|
2190
|
+
return indexLines.join("\n");
|
|
2191
|
+
}
|
|
2192
|
+
function registerTemplateResources(mcp, binding) {
|
|
2193
|
+
for (const tmpl of TEMPLATES) {
|
|
2194
|
+
registerTemplate(mcp, tmpl, binding);
|
|
2195
|
+
}
|
|
2196
|
+
const registerCustom = (bound) => {
|
|
2197
|
+
for (const tmpl of customTemplates(bound.vaultConfig)) {
|
|
2198
|
+
registerTemplate(mcp, tmpl, binding);
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
if (binding instanceof Promise) void binding.then(registerCustom);
|
|
2202
|
+
else registerCustom(binding);
|
|
1818
2203
|
mcp.registerResource(
|
|
1819
2204
|
"Template index",
|
|
1820
2205
|
"wiki://templates",
|
|
@@ -1822,9 +2207,18 @@ function registerTemplateResources(mcp, vaultRoot2, vaultConfig) {
|
|
|
1822
2207
|
description: "Index of all wiki templates with URIs and descriptions. Read a specific template via its URI.",
|
|
1823
2208
|
mimeType: "text/markdown"
|
|
1824
2209
|
},
|
|
1825
|
-
async (uri) =>
|
|
1826
|
-
|
|
1827
|
-
|
|
2210
|
+
async (uri) => {
|
|
2211
|
+
const { vaultConfig } = await binding;
|
|
2212
|
+
return {
|
|
2213
|
+
contents: [
|
|
2214
|
+
{
|
|
2215
|
+
uri: uri.toString(),
|
|
2216
|
+
mimeType: "text/markdown",
|
|
2217
|
+
text: buildIndexText(vaultConfig)
|
|
2218
|
+
}
|
|
2219
|
+
]
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
1828
2222
|
);
|
|
1829
2223
|
}
|
|
1830
2224
|
var TEMPLATES;
|
|
@@ -1971,7 +2365,7 @@ var init_toolResponse = __esm({
|
|
|
1971
2365
|
});
|
|
1972
2366
|
|
|
1973
2367
|
// ../mcp/src/tools/search.ts
|
|
1974
|
-
import { z as
|
|
2368
|
+
import { z as z5 } from "zod";
|
|
1975
2369
|
function search(deps, input) {
|
|
1976
2370
|
const ftsQuery = sanitizeFtsQuery(input.query);
|
|
1977
2371
|
if (!ftsQuery) return { results: [] };
|
|
@@ -2018,15 +2412,15 @@ var init_search = __esm({
|
|
|
2018
2412
|
"use strict";
|
|
2019
2413
|
init_fts();
|
|
2020
2414
|
init_toolResponse();
|
|
2021
|
-
SearchInputSchema =
|
|
2022
|
-
query:
|
|
2415
|
+
SearchInputSchema = z5.object({
|
|
2416
|
+
query: z5.string().min(1).describe(
|
|
2023
2417
|
"Natural-language search query. Matched against title, summary, and body via SQLite FTS5."
|
|
2024
2418
|
),
|
|
2025
|
-
scope:
|
|
2026
|
-
kind:
|
|
2419
|
+
scope: z5.string().optional().describe('Optional: restrict to a project scope (e.g. "ontology", "sotto").'),
|
|
2420
|
+
kind: z5.string().optional().describe(
|
|
2027
2421
|
"Optional: restrict to a kind. Project kinds: spec, adr, plan, ops. Research kinds: article, src. Misc: note."
|
|
2028
2422
|
),
|
|
2029
|
-
limit:
|
|
2423
|
+
limit: z5.number().int().min(1).max(50).default(5).describe("Maximum number of ranked results to return. Default 5.")
|
|
2030
2424
|
});
|
|
2031
2425
|
FTS_RANK = "bm25(pages_fts, 10.0, 5.0, 1.0)";
|
|
2032
2426
|
}
|
|
@@ -2034,33 +2428,33 @@ var init_search = __esm({
|
|
|
2034
2428
|
|
|
2035
2429
|
// ../mcp/src/tools/prime.ts
|
|
2036
2430
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
2037
|
-
import { basename as basename3, join as
|
|
2038
|
-
import { z as
|
|
2431
|
+
import { basename as basename3, join as join10 } from "node:path";
|
|
2432
|
+
import { z as z6 } from "zod";
|
|
2039
2433
|
function pathSlug(p) {
|
|
2040
2434
|
return basename3(p).replace(/\.md$/, "");
|
|
2041
2435
|
}
|
|
2042
|
-
async function readIndexFm(
|
|
2436
|
+
async function readIndexFm(vaultRoot, scope) {
|
|
2043
2437
|
try {
|
|
2044
|
-
const raw = await readFile6(
|
|
2438
|
+
const raw = await readFile6(join10(vaultRoot, "projects", scope, "index.md"), "utf8");
|
|
2045
2439
|
return parseFrontmatter2(raw).data;
|
|
2046
2440
|
} catch {
|
|
2047
2441
|
return {};
|
|
2048
2442
|
}
|
|
2049
2443
|
}
|
|
2050
|
-
async function readPrimer(
|
|
2444
|
+
async function readPrimer(vaultRoot, scope) {
|
|
2051
2445
|
try {
|
|
2052
|
-
const raw = await readFile6(
|
|
2446
|
+
const raw = await readFile6(join10(vaultRoot, "projects", scope, "primer.md"), "utf8");
|
|
2053
2447
|
return parseFrontmatter2(raw).content.trim().replace(/^#\s+[^\n]+\n+/, "");
|
|
2054
2448
|
} catch {
|
|
2055
2449
|
return "";
|
|
2056
2450
|
}
|
|
2057
2451
|
}
|
|
2058
2452
|
async function prime(deps, input) {
|
|
2059
|
-
const { db, vaultRoot
|
|
2453
|
+
const { db, vaultRoot, vaultConfig } = deps;
|
|
2060
2454
|
const { scope, task } = input;
|
|
2061
2455
|
const [fm, primer] = await Promise.all([
|
|
2062
|
-
readIndexFm(
|
|
2063
|
-
readPrimer(
|
|
2456
|
+
readIndexFm(vaultRoot, scope),
|
|
2457
|
+
readPrimer(vaultRoot, scope)
|
|
2064
2458
|
]);
|
|
2065
2459
|
const counts = db.prepare("SELECT kind, count(*) AS count FROM pages WHERE scope = ? GROUP BY kind").all(scope);
|
|
2066
2460
|
const adrs = db.prepare(
|
|
@@ -2114,7 +2508,7 @@ async function prime(deps, input) {
|
|
|
2114
2508
|
for (const row of counts) countsRecord[row.kind] = Number(row.count);
|
|
2115
2509
|
const data = {
|
|
2116
2510
|
scope,
|
|
2117
|
-
vault_root: canonicalVaultRoot(
|
|
2511
|
+
vault_root: canonicalVaultRoot(vaultRoot),
|
|
2118
2512
|
title: fm.title ?? null,
|
|
2119
2513
|
methodology: fm.methodology ?? null,
|
|
2120
2514
|
phase: typeof fm.phase === "number" ? fm.phase : null,
|
|
@@ -2244,9 +2638,9 @@ var init_prime = __esm({
|
|
|
2244
2638
|
init_fts();
|
|
2245
2639
|
init_toolResponse();
|
|
2246
2640
|
init_search();
|
|
2247
|
-
PrimeInputSchema =
|
|
2248
|
-
scope:
|
|
2249
|
-
task:
|
|
2641
|
+
PrimeInputSchema = z6.object({
|
|
2642
|
+
scope: z6.string().min(1).describe("Project scope to prime (matches projects/{scope}/ in the vault)."),
|
|
2643
|
+
task: z6.string().optional().describe("Optional task description; surfaces top-3 tsvector-ranked relevant pages.")
|
|
2250
2644
|
});
|
|
2251
2645
|
}
|
|
2252
2646
|
});
|
|
@@ -2254,15 +2648,20 @@ var init_prime = __esm({
|
|
|
2254
2648
|
// ../mcp/src/server.ts
|
|
2255
2649
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2256
2650
|
function buildServer(args) {
|
|
2257
|
-
const { name, version,
|
|
2258
|
-
const
|
|
2651
|
+
const { name, version, logger, binding } = args;
|
|
2652
|
+
const deferred = binding instanceof Promise;
|
|
2653
|
+
const mcp = new McpServer(
|
|
2654
|
+
{ name, version },
|
|
2655
|
+
{ capabilities: { tools: {}, resources: deferred ? { listChanged: true } : {} } }
|
|
2656
|
+
);
|
|
2259
2657
|
mcp.tool(
|
|
2260
2658
|
"prime",
|
|
2261
2659
|
"Orient on a project. Returns a markdown briefing with: identity (scope, phase, methodology, summary), the human-authored primer.md inlined, active ADRs, current plan, page counts, top tags, hub pages (most-linked-to), recent events, cross-scope references, and \u2014 when `task` is provided \u2014 the top 3 tsvector-ranked relevant pages. Call once at session start. Empty sections are omitted to keep the surface lean.",
|
|
2262
2660
|
PrimeInputSchema.shape,
|
|
2263
2661
|
async (input) => {
|
|
2264
2662
|
logger.debug({ tool: "prime", input }, "tool call");
|
|
2265
|
-
|
|
2663
|
+
const { db, vaultRoot, vaultConfig } = await binding;
|
|
2664
|
+
return handlePrime({ db, vaultRoot, vaultConfig }, input);
|
|
2266
2665
|
}
|
|
2267
2666
|
);
|
|
2268
2667
|
mcp.tool(
|
|
@@ -2271,11 +2670,12 @@ function buildServer(args) {
|
|
|
2271
2670
|
SearchInputSchema.shape,
|
|
2272
2671
|
async (input) => {
|
|
2273
2672
|
logger.debug({ tool: "search", input }, "tool call");
|
|
2673
|
+
const { db } = await binding;
|
|
2274
2674
|
return handleSearch({ db }, input);
|
|
2275
2675
|
}
|
|
2276
2676
|
);
|
|
2277
|
-
registerTemplateResources(mcp,
|
|
2278
|
-
registerAuthoringResource(mcp,
|
|
2677
|
+
registerTemplateResources(mcp, binding);
|
|
2678
|
+
registerAuthoringResource(mcp, binding);
|
|
2279
2679
|
return mcp;
|
|
2280
2680
|
}
|
|
2281
2681
|
var init_server = __esm({
|
|
@@ -2294,8 +2694,25 @@ __export(start_exports, {
|
|
|
2294
2694
|
startMcpServer: () => startMcpServer
|
|
2295
2695
|
});
|
|
2296
2696
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2297
|
-
|
|
2697
|
+
import { RootsListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2698
|
+
function installShutdown(mcp, logger, getDb) {
|
|
2699
|
+
const shutdown = async (signal) => {
|
|
2700
|
+
logger.info({ signal }, "shutting down");
|
|
2701
|
+
diag("shutting down", { signal });
|
|
2702
|
+
try {
|
|
2703
|
+
await mcp.close();
|
|
2704
|
+
getDb()?.close();
|
|
2705
|
+
} catch (err) {
|
|
2706
|
+
logger.error({ err }, "error during shutdown");
|
|
2707
|
+
}
|
|
2708
|
+
process.exit(0);
|
|
2709
|
+
};
|
|
2710
|
+
process.once("SIGINT", (s) => void shutdown(s));
|
|
2711
|
+
process.once("SIGTERM", (s) => void shutdown(s));
|
|
2712
|
+
}
|
|
2713
|
+
async function startMcpServer(deferred) {
|
|
2298
2714
|
diag("main entered");
|
|
2715
|
+
if (deferred) return startDeferred(deferred);
|
|
2299
2716
|
const config = loadConfig();
|
|
2300
2717
|
diag("config loaded", { vault: config.wikiVault, level: config.logLevel });
|
|
2301
2718
|
const vaultConfig = await loadVaultConfig(config.wikiVault);
|
|
@@ -2314,34 +2731,89 @@ async function startMcpServer() {
|
|
|
2314
2731
|
const mcp = buildServer({
|
|
2315
2732
|
name: config.serverName,
|
|
2316
2733
|
version: config.serverVersion,
|
|
2317
|
-
vaultRoot: config.wikiVault,
|
|
2318
|
-
db,
|
|
2319
2734
|
logger,
|
|
2320
|
-
vaultConfig
|
|
2735
|
+
binding: { vaultRoot: config.wikiVault, db, vaultConfig }
|
|
2321
2736
|
});
|
|
2322
2737
|
diag("server built");
|
|
2323
|
-
|
|
2324
|
-
logger.info({ signal }, "shutting down");
|
|
2325
|
-
diag("shutting down", { signal });
|
|
2326
|
-
try {
|
|
2327
|
-
await mcp.close();
|
|
2328
|
-
db.close();
|
|
2329
|
-
} catch (err) {
|
|
2330
|
-
logger.error({ err }, "error during shutdown");
|
|
2331
|
-
}
|
|
2332
|
-
process.exit(0);
|
|
2333
|
-
};
|
|
2334
|
-
process.once("SIGINT", (s) => void shutdown(s));
|
|
2335
|
-
process.once("SIGTERM", (s) => void shutdown(s));
|
|
2738
|
+
installShutdown(mcp, logger, () => db);
|
|
2336
2739
|
const transport = new StdioServerTransport();
|
|
2337
2740
|
await mcp.connect(transport);
|
|
2338
2741
|
logger.info("wiki-mcp ready");
|
|
2339
2742
|
diag("ready and connected to transport");
|
|
2340
2743
|
}
|
|
2744
|
+
async function startDeferred(input) {
|
|
2745
|
+
const env = loadServerEnv();
|
|
2746
|
+
diag("config loaded, vault binding deferred", { level: env.logLevel, cwd: input.cwd });
|
|
2747
|
+
const logger = createLogger(env.logLevel, env.serverName);
|
|
2748
|
+
logger.info(
|
|
2749
|
+
{ serverName: env.serverName, serverVersion: env.serverVersion },
|
|
2750
|
+
"starting wiki-mcp on stdio; vault binding deferred to after initialization"
|
|
2751
|
+
);
|
|
2752
|
+
let resolveBinding;
|
|
2753
|
+
const binding = new Promise((resolve6) => {
|
|
2754
|
+
resolveBinding = resolve6;
|
|
2755
|
+
});
|
|
2756
|
+
const mcp = buildServer({
|
|
2757
|
+
name: env.serverName,
|
|
2758
|
+
version: env.serverVersion,
|
|
2759
|
+
logger,
|
|
2760
|
+
binding
|
|
2761
|
+
});
|
|
2762
|
+
diag("server built (deferred binding)");
|
|
2763
|
+
let bound = null;
|
|
2764
|
+
const bind = async () => {
|
|
2765
|
+
let rootDirs = null;
|
|
2766
|
+
if (mcp.server.getClientCapabilities()?.roots) {
|
|
2767
|
+
const { roots } = await mcp.server.listRoots();
|
|
2768
|
+
rootDirs = projectDirsFromRoots(roots);
|
|
2769
|
+
diag("client roots received", { uris: roots.map((r) => r.uri), dirs: rootDirs });
|
|
2770
|
+
} else {
|
|
2771
|
+
diag("client declares no roots capability; falling back to the cwd-fed chain");
|
|
2772
|
+
}
|
|
2773
|
+
const resolution = resolveDeferredVault({
|
|
2774
|
+
rootDirs,
|
|
2775
|
+
cwd: input.cwd,
|
|
2776
|
+
defaultRoot: input.defaultRoot,
|
|
2777
|
+
envVault: input.envVault,
|
|
2778
|
+
globalDefault: input.globalDefault,
|
|
2779
|
+
onSkip: (candidate) => logger.warn({ candidate }, "ignoring unmarked vault.yaml \u2014 no .kmd sibling")
|
|
2780
|
+
});
|
|
2781
|
+
if (resolution.root === null) {
|
|
2782
|
+
throw new Error(
|
|
2783
|
+
"no vault resolvable \u2014 no client root maps to a vault; pass <vault-root>, set KMD_PROJECT_DIR or WIKI_VAULT, use --default-root, or `kmd config set default_vault <path>`"
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
process.env.WIKI_VAULT = resolution.root;
|
|
2787
|
+
const vaultConfig = await loadVaultConfig(resolution.root);
|
|
2788
|
+
const db = createDatabase(resolution.root);
|
|
2789
|
+
bound = { vaultRoot: resolution.root, db, vaultConfig };
|
|
2790
|
+
diag("vault bound", { vault: resolution.root, source: resolution.source });
|
|
2791
|
+
logger.info({ vault: resolution.root, source: resolution.source }, "vault bound");
|
|
2792
|
+
resolveBinding(bound);
|
|
2793
|
+
};
|
|
2794
|
+
mcp.server.oninitialized = () => {
|
|
2795
|
+
void bind().catch((err) => {
|
|
2796
|
+
const msg = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
2797
|
+
diag("FATAL at vault bind", { err: msg });
|
|
2798
|
+
process.stderr.write(`fatal: vault bind failed: ${msg}
|
|
2799
|
+
`);
|
|
2800
|
+
process.exit(1);
|
|
2801
|
+
});
|
|
2802
|
+
};
|
|
2803
|
+
mcp.server.setNotificationHandler(RootsListChangedNotificationSchema, () => {
|
|
2804
|
+
logger.debug("roots/list_changed received; rebinding declined \u2014 vault binds once per server");
|
|
2805
|
+
});
|
|
2806
|
+
installShutdown(mcp, logger, () => bound?.db ?? null);
|
|
2807
|
+
const transport = new StdioServerTransport();
|
|
2808
|
+
await mcp.connect(transport);
|
|
2809
|
+
logger.info("wiki-mcp ready; awaiting initialization to bind the vault");
|
|
2810
|
+
diag("ready and connected to transport; vault binds after initialize");
|
|
2811
|
+
}
|
|
2341
2812
|
var init_start = __esm({
|
|
2342
2813
|
"../mcp/src/start.ts"() {
|
|
2343
2814
|
"use strict";
|
|
2344
2815
|
init_vault_config();
|
|
2816
|
+
init_binding();
|
|
2345
2817
|
init_config();
|
|
2346
2818
|
init_db();
|
|
2347
2819
|
init_diag();
|
|
@@ -2366,25 +2838,28 @@ __export(hook_exports, {
|
|
|
2366
2838
|
matchPromptTriggers: () => matchPromptTriggers,
|
|
2367
2839
|
parsePretoolEvent: () => parsePretoolEvent,
|
|
2368
2840
|
parsePromptEvent: () => parsePromptEvent,
|
|
2841
|
+
parseSessionStartEvent: () => parseSessionStartEvent,
|
|
2369
2842
|
parseStopEvent: () => parseStopEvent,
|
|
2370
2843
|
renderPosttool: () => renderPosttool,
|
|
2371
2844
|
renderPretool: () => renderPretool,
|
|
2372
2845
|
renderPrompt: () => renderPrompt,
|
|
2846
|
+
renderSessionStart: () => renderSessionStart,
|
|
2373
2847
|
renderStop: () => renderStop,
|
|
2374
2848
|
resolveScope: () => resolveScope,
|
|
2375
2849
|
runHookPosttool: () => runHookPosttool,
|
|
2376
2850
|
runHookPretool: () => runHookPretool,
|
|
2377
2851
|
runHookPrompt: () => runHookPrompt,
|
|
2852
|
+
runHookSessionStart: () => runHookSessionStart,
|
|
2378
2853
|
runHookStop: () => runHookStop,
|
|
2379
2854
|
vaultPathTouched: () => vaultPathTouched
|
|
2380
2855
|
});
|
|
2381
|
-
import { mkdirSync as
|
|
2382
|
-
import { homedir as
|
|
2383
|
-
import { join as
|
|
2856
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2857
|
+
import { homedir as homedir4 } from "node:os";
|
|
2858
|
+
import { join as join11, resolve as resolve5, sep as sep2 } from "node:path";
|
|
2384
2859
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
2385
2860
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
2386
2861
|
import { parse as parseYaml3 } from "yaml";
|
|
2387
|
-
import { z as
|
|
2862
|
+
import { z as z7 } from "zod";
|
|
2388
2863
|
function eventFields(raw) {
|
|
2389
2864
|
let data;
|
|
2390
2865
|
try {
|
|
@@ -2410,7 +2885,7 @@ function kiroIdePromptEvent(now = Date.now()) {
|
|
|
2410
2885
|
}
|
|
2411
2886
|
function loadTriggerFile(path) {
|
|
2412
2887
|
try {
|
|
2413
|
-
const result =
|
|
2888
|
+
const result = z7.array(TriggerSchema).safeParse(parseYaml3(readFileSync2(path, "utf8")));
|
|
2414
2889
|
return result.success ? result.data : null;
|
|
2415
2890
|
} catch {
|
|
2416
2891
|
return null;
|
|
@@ -2434,9 +2909,9 @@ function effectiveTriggers(config, scope, fileTriggers = []) {
|
|
|
2434
2909
|
}
|
|
2435
2910
|
return { triggers, duplicates };
|
|
2436
2911
|
}
|
|
2437
|
-
function
|
|
2438
|
-
if (path === "~") return
|
|
2439
|
-
return path.startsWith("~/") ?
|
|
2912
|
+
function expandHome2(path) {
|
|
2913
|
+
if (path === "~") return homedir4();
|
|
2914
|
+
return path.startsWith("~/") ? join11(homedir4(), path.slice(2)) : path;
|
|
2440
2915
|
}
|
|
2441
2916
|
function resolveScope(config, cwd) {
|
|
2442
2917
|
if (cwd === void 0 || cwd === "") return void 0;
|
|
@@ -2444,7 +2919,7 @@ function resolveScope(config, cwd) {
|
|
|
2444
2919
|
let bestLength = -1;
|
|
2445
2920
|
for (const [name, scope] of Object.entries(config.scopes)) {
|
|
2446
2921
|
if (scope.repo === void 0) continue;
|
|
2447
|
-
const repo =
|
|
2922
|
+
const repo = expandHome2(scope.repo).replace(/\/+$/, "");
|
|
2448
2923
|
if (!repo.startsWith("/")) continue;
|
|
2449
2924
|
if (cwd !== repo && !cwd.startsWith(`${repo}/`)) continue;
|
|
2450
2925
|
if (repo.length > bestLength) {
|
|
@@ -2593,7 +3068,7 @@ function matchPretoolTriggers(toolName, toolInput, triggers, cwd) {
|
|
|
2593
3068
|
}
|
|
2594
3069
|
return matches;
|
|
2595
3070
|
}
|
|
2596
|
-
function evaluateMatches(matches,
|
|
3071
|
+
function evaluateMatches(matches, vaultRoot) {
|
|
2597
3072
|
const fired = [];
|
|
2598
3073
|
const skipped = [];
|
|
2599
3074
|
for (const match of matches) {
|
|
@@ -2601,38 +3076,38 @@ function evaluateMatches(matches, vaultRoot2) {
|
|
|
2601
3076
|
fired.push(match);
|
|
2602
3077
|
continue;
|
|
2603
3078
|
}
|
|
2604
|
-
const verdict = evaluateWhen(match.when,
|
|
3079
|
+
const verdict = evaluateWhen(match.when, vaultRoot);
|
|
2605
3080
|
if (verdict === null) skipped.push(match.id);
|
|
2606
3081
|
else if (!verdict) fired.push(match);
|
|
2607
3082
|
}
|
|
2608
3083
|
return { fired, skipped };
|
|
2609
3084
|
}
|
|
2610
|
-
function evaluateWhenVerdict(when,
|
|
3085
|
+
function evaluateWhenVerdict(when, vaultRoot) {
|
|
2611
3086
|
if (typeof when === "string") return "unknown";
|
|
2612
3087
|
try {
|
|
2613
|
-
const than = newestUpdated(
|
|
3088
|
+
const than = newestUpdated(vaultRoot, when.than);
|
|
2614
3089
|
if (than === null) return "vacuous";
|
|
2615
|
-
const fresh = newestUpdated(
|
|
3090
|
+
const fresh = newestUpdated(vaultRoot, when.fresh);
|
|
2616
3091
|
if (fresh === null) return "unmet";
|
|
2617
3092
|
return fresh >= than ? "satisfied" : "unmet";
|
|
2618
3093
|
} catch {
|
|
2619
3094
|
return "unknown";
|
|
2620
3095
|
}
|
|
2621
3096
|
}
|
|
2622
|
-
function evaluateWhen(when,
|
|
2623
|
-
const verdict = evaluateWhenVerdict(when,
|
|
3097
|
+
function evaluateWhen(when, vaultRoot) {
|
|
3098
|
+
const verdict = evaluateWhenVerdict(when, vaultRoot);
|
|
2624
3099
|
if (verdict === "unknown") return null;
|
|
2625
3100
|
return verdict !== "unmet";
|
|
2626
3101
|
}
|
|
2627
|
-
function newestUpdated(
|
|
3102
|
+
function newestUpdated(vaultRoot, globs) {
|
|
2628
3103
|
const regexes = globs.map(globToRegExp);
|
|
2629
3104
|
let newest = null;
|
|
2630
|
-
for (const entry of readdirSync2(
|
|
3105
|
+
for (const entry of readdirSync2(vaultRoot, { recursive: true })) {
|
|
2631
3106
|
const rel = entry.split(sep2).join("/");
|
|
2632
3107
|
if (!rel.endsWith(".md")) continue;
|
|
2633
3108
|
if (rel.startsWith(".") || rel.includes("/.")) continue;
|
|
2634
3109
|
if (!regexes.some((regex) => regex.test(rel))) continue;
|
|
2635
|
-
const updated = readUpdated(
|
|
3110
|
+
const updated = readUpdated(join11(vaultRoot, entry));
|
|
2636
3111
|
if (updated !== null && (newest === null || updated > newest)) {
|
|
2637
3112
|
newest = updated;
|
|
2638
3113
|
}
|
|
@@ -2641,7 +3116,7 @@ function newestUpdated(vaultRoot2, globs) {
|
|
|
2641
3116
|
}
|
|
2642
3117
|
function readUpdated(path) {
|
|
2643
3118
|
try {
|
|
2644
|
-
const { data } = parseFrontmatter(
|
|
3119
|
+
const { data } = parseFrontmatter(readFileSync2(path, "utf8"));
|
|
2645
3120
|
const updated = data.updated;
|
|
2646
3121
|
if (typeof updated === "string") return updated;
|
|
2647
3122
|
if (updated instanceof Date) return updated.toISOString().slice(0, 10);
|
|
@@ -2704,15 +3179,15 @@ function commandPaths(toolInput) {
|
|
|
2704
3179
|
}
|
|
2705
3180
|
return paths;
|
|
2706
3181
|
}
|
|
2707
|
-
function vaultPathTouched(toolInput,
|
|
2708
|
-
const root =
|
|
3182
|
+
function vaultPathTouched(toolInput, vaultRoot, cwd) {
|
|
3183
|
+
const root = resolve5(vaultRoot);
|
|
2709
3184
|
const candidates = [
|
|
2710
3185
|
...pathCandidates(toolInput, cwd),
|
|
2711
3186
|
...patchPaths(toolInput),
|
|
2712
3187
|
...commandPaths(toolInput)
|
|
2713
3188
|
];
|
|
2714
3189
|
return candidates.some((candidate) => {
|
|
2715
|
-
const absolute =
|
|
3190
|
+
const absolute = resolve5(cwd ?? ".", candidate);
|
|
2716
3191
|
return absolute === root || absolute.startsWith(`${root}/`);
|
|
2717
3192
|
});
|
|
2718
3193
|
}
|
|
@@ -2762,12 +3237,12 @@ function dedupePretoolMatches(stateDir, sessionId, matches, persist = true) {
|
|
|
2762
3237
|
const fresh = dedupeMatches(stateDir, sessionId, rest, Date.now(), persist);
|
|
2763
3238
|
return matches.filter((match) => blocks.includes(match) || fresh.includes(match));
|
|
2764
3239
|
}
|
|
2765
|
-
function hookStateDir() {
|
|
2766
|
-
return
|
|
3240
|
+
function hookStateDir(vaultRoot) {
|
|
3241
|
+
return resolveStateDir(vaultRoot);
|
|
2767
3242
|
}
|
|
2768
3243
|
function explainPrompt(options) {
|
|
2769
3244
|
const now = options.now ?? Date.now();
|
|
2770
|
-
const fired = readFired(
|
|
3245
|
+
const fired = readFired(join11(options.stateDir, safeName(options.sessionId)));
|
|
2771
3246
|
const entries = [];
|
|
2772
3247
|
const rendered = [];
|
|
2773
3248
|
const state = { db: null };
|
|
@@ -2804,7 +3279,7 @@ function explainPrompt(options) {
|
|
|
2804
3279
|
}
|
|
2805
3280
|
function explainPretool(options) {
|
|
2806
3281
|
const now = options.now ?? Date.now();
|
|
2807
|
-
const fired = readFired(
|
|
3282
|
+
const fired = readFired(join11(options.stateDir, safeName(options.sessionId)));
|
|
2808
3283
|
const entries = [];
|
|
2809
3284
|
const rendered = [];
|
|
2810
3285
|
for (const trigger of options.triggers) {
|
|
@@ -2845,7 +3320,7 @@ function explainPretool(options) {
|
|
|
2845
3320
|
}
|
|
2846
3321
|
function dedupeMatches(stateDir, sessionId, matches, now = Date.now(), persist = true) {
|
|
2847
3322
|
if (matches.length === 0) return [];
|
|
2848
|
-
const dir =
|
|
3323
|
+
const dir = join11(stateDir, safeName(sessionId));
|
|
2849
3324
|
const fired = readFired(dir);
|
|
2850
3325
|
const fresh = [];
|
|
2851
3326
|
const record = [];
|
|
@@ -2861,10 +3336,10 @@ function dedupeMatches(stateDir, sessionId, matches, now = Date.now(), persist =
|
|
|
2861
3336
|
}
|
|
2862
3337
|
if (persist && record.length > 0) {
|
|
2863
3338
|
try {
|
|
2864
|
-
|
|
3339
|
+
mkdirSync5(dir, { recursive: true });
|
|
2865
3340
|
for (const key of record) {
|
|
2866
3341
|
try {
|
|
2867
|
-
|
|
3342
|
+
writeFileSync2(join11(dir, key), "", { flag: "wx" });
|
|
2868
3343
|
} catch (err) {
|
|
2869
3344
|
if (err.code !== "EEXIST") throw err;
|
|
2870
3345
|
}
|
|
@@ -2896,7 +3371,7 @@ function pruneStale(stateDir, keep) {
|
|
|
2896
3371
|
try {
|
|
2897
3372
|
const cutoff = Date.now() - SESSION_STATE_MAX_AGE_MS;
|
|
2898
3373
|
for (const entry of readdirSync2(stateDir)) {
|
|
2899
|
-
const path =
|
|
3374
|
+
const path = join11(stateDir, entry);
|
|
2900
3375
|
if (path !== keep && statSync(path).mtimeMs < cutoff) {
|
|
2901
3376
|
rmSync2(path, { recursive: true, force: true });
|
|
2902
3377
|
}
|
|
@@ -2913,17 +3388,14 @@ function hookInvocation() {
|
|
|
2913
3388
|
scope: { type: "string" },
|
|
2914
3389
|
harness: { type: "string" },
|
|
2915
3390
|
triggers: { type: "string" },
|
|
3391
|
+
"default-root": { type: "string" },
|
|
2916
3392
|
"dry-run": { type: "boolean" },
|
|
2917
3393
|
explain: { type: "boolean" }
|
|
2918
3394
|
}
|
|
2919
3395
|
});
|
|
2920
|
-
const vaultRoot2 = positionals2[2] ?? process.env.WIKI_VAULT;
|
|
2921
|
-
if (vaultRoot2 === void 0 || vaultRoot2 === "") {
|
|
2922
|
-
diag2("no vault root (positional or $WIKI_VAULT)");
|
|
2923
|
-
return null;
|
|
2924
|
-
}
|
|
2925
3396
|
return {
|
|
2926
|
-
|
|
3397
|
+
positional: positionals2[2],
|
|
3398
|
+
defaultRoot: typeof values2["default-root"] === "string" ? values2["default-root"] : void 0,
|
|
2927
3399
|
scope: typeof values2.scope === "string" ? values2.scope : process.env.WIKI_SCOPE,
|
|
2928
3400
|
harness: values2.harness,
|
|
2929
3401
|
triggersFile: values2.triggers,
|
|
@@ -2931,6 +3403,28 @@ function hookInvocation() {
|
|
|
2931
3403
|
explain: values2.explain === true
|
|
2932
3404
|
};
|
|
2933
3405
|
}
|
|
3406
|
+
function resolveHookVault(invocation, eventCwd) {
|
|
3407
|
+
if (invocation.positional !== void 0 && invocation.defaultRoot !== void 0) {
|
|
3408
|
+
diag2("<vault-root> and --default-root are mutually exclusive \u2014 using the positional");
|
|
3409
|
+
}
|
|
3410
|
+
try {
|
|
3411
|
+
const resolution = resolveVaultRoot({
|
|
3412
|
+
positional: invocation.positional,
|
|
3413
|
+
projectDir: eventCwd,
|
|
3414
|
+
defaultRoot: invocation.defaultRoot,
|
|
3415
|
+
envVault: process.env.WIKI_VAULT,
|
|
3416
|
+
globalDefault: loadGlobalConfig().default_vault
|
|
3417
|
+
});
|
|
3418
|
+
if (resolution.root === null) {
|
|
3419
|
+
diag2("no vault root resolvable (positional, event cwd, --default-root, or $WIKI_VAULT)");
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
return resolution.root;
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
diag2(`vault resolution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3425
|
+
return null;
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
2934
3428
|
function resolveFileTriggers(invocation) {
|
|
2935
3429
|
if (typeof invocation.triggersFile !== "string") return [];
|
|
2936
3430
|
const loaded = loadTriggerFile(invocation.triggersFile);
|
|
@@ -2943,8 +3437,6 @@ function resolveFileTriggers(invocation) {
|
|
|
2943
3437
|
async function runHookPrompt() {
|
|
2944
3438
|
try {
|
|
2945
3439
|
const invocation = hookInvocation();
|
|
2946
|
-
if (invocation === null) return;
|
|
2947
|
-
const { vaultRoot: vaultRoot2 } = invocation;
|
|
2948
3440
|
let event = null;
|
|
2949
3441
|
if (invocation.harness === "kiro-ide") {
|
|
2950
3442
|
event = kiroIdePromptEvent();
|
|
@@ -2956,7 +3448,9 @@ async function runHookPrompt() {
|
|
|
2956
3448
|
diag2("stdin is not a prompt event ({session_id, prompt})");
|
|
2957
3449
|
return;
|
|
2958
3450
|
}
|
|
2959
|
-
const
|
|
3451
|
+
const vaultRoot = resolveHookVault(invocation, event.cwd);
|
|
3452
|
+
if (vaultRoot === null) return;
|
|
3453
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
2960
3454
|
const scope = invocation.scope ?? resolveScope(config, event.cwd);
|
|
2961
3455
|
const { triggers, duplicates } = effectiveTriggers(
|
|
2962
3456
|
config,
|
|
@@ -2970,7 +3464,7 @@ async function runHookPrompt() {
|
|
|
2970
3464
|
const trace = explainPrompt({
|
|
2971
3465
|
prompt: event.prompt,
|
|
2972
3466
|
triggers,
|
|
2973
|
-
stateDir: hookStateDir(),
|
|
3467
|
+
stateDir: hookStateDir(vaultRoot),
|
|
2974
3468
|
sessionId: event.session_id
|
|
2975
3469
|
});
|
|
2976
3470
|
console.log(JSON.stringify({ event: "prompt", scope: scope ?? null, duplicates, ...trace }));
|
|
@@ -2978,7 +3472,7 @@ async function runHookPrompt() {
|
|
|
2978
3472
|
}
|
|
2979
3473
|
const matches = matchPromptTriggers(event.prompt, triggers);
|
|
2980
3474
|
const fresh = dedupeMatches(
|
|
2981
|
-
hookStateDir(),
|
|
3475
|
+
hookStateDir(vaultRoot),
|
|
2982
3476
|
event.session_id,
|
|
2983
3477
|
matches,
|
|
2984
3478
|
Date.now(),
|
|
@@ -2994,8 +3488,6 @@ async function runHookPrompt() {
|
|
|
2994
3488
|
async function runHookPretool() {
|
|
2995
3489
|
try {
|
|
2996
3490
|
const invocation = hookInvocation();
|
|
2997
|
-
if (invocation === null) return;
|
|
2998
|
-
const { vaultRoot: vaultRoot2 } = invocation;
|
|
2999
3491
|
let format = "neutral";
|
|
3000
3492
|
if (invocation.harness === "claude") {
|
|
3001
3493
|
format = "claude";
|
|
@@ -3007,7 +3499,9 @@ async function runHookPretool() {
|
|
|
3007
3499
|
diag2("stdin is not a pretool event ({session_id, tool_name})");
|
|
3008
3500
|
return;
|
|
3009
3501
|
}
|
|
3010
|
-
const
|
|
3502
|
+
const vaultRoot = resolveHookVault(invocation, event.cwd);
|
|
3503
|
+
if (vaultRoot === null) return;
|
|
3504
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
3011
3505
|
const scope = invocation.scope ?? resolveScope(config, event.cwd);
|
|
3012
3506
|
const { triggers, duplicates } = effectiveTriggers(
|
|
3013
3507
|
config,
|
|
@@ -3022,8 +3516,8 @@ async function runHookPretool() {
|
|
|
3022
3516
|
toolName: event.tool_name,
|
|
3023
3517
|
toolInput: event.tool_input,
|
|
3024
3518
|
triggers,
|
|
3025
|
-
vaultRoot
|
|
3026
|
-
stateDir: hookStateDir(),
|
|
3519
|
+
vaultRoot,
|
|
3520
|
+
stateDir: hookStateDir(vaultRoot),
|
|
3027
3521
|
sessionId: event.session_id,
|
|
3028
3522
|
...event.cwd !== void 0 && { cwd: event.cwd },
|
|
3029
3523
|
format
|
|
@@ -3032,12 +3526,12 @@ async function runHookPretool() {
|
|
|
3032
3526
|
return;
|
|
3033
3527
|
}
|
|
3034
3528
|
const matches = matchPretoolTriggers(event.tool_name, event.tool_input, triggers, event.cwd);
|
|
3035
|
-
const { fired, skipped } = evaluateMatches(matches,
|
|
3529
|
+
const { fired, skipped } = evaluateMatches(matches, vaultRoot);
|
|
3036
3530
|
for (const id of skipped) {
|
|
3037
3531
|
diag2(`trigger "${id}": unknown or unevaluable predicate \u2014 skipped`);
|
|
3038
3532
|
}
|
|
3039
3533
|
const rendered = renderPretool(
|
|
3040
|
-
dedupePretoolMatches(hookStateDir(), event.session_id, fired, !invocation.dryRun),
|
|
3534
|
+
dedupePretoolMatches(hookStateDir(vaultRoot), event.session_id, fired, !invocation.dryRun),
|
|
3041
3535
|
format
|
|
3042
3536
|
);
|
|
3043
3537
|
for (const line of rendered.stderr) {
|
|
@@ -3053,7 +3547,6 @@ async function runHookPretool() {
|
|
|
3053
3547
|
async function runHookPosttool() {
|
|
3054
3548
|
try {
|
|
3055
3549
|
const invocation = hookInvocation();
|
|
3056
|
-
if (invocation === null) return;
|
|
3057
3550
|
if (invocation.dryRun) {
|
|
3058
3551
|
diag2("--dry-run/--explain support prompt and pretool events only");
|
|
3059
3552
|
return;
|
|
@@ -3069,13 +3562,15 @@ async function runHookPosttool() {
|
|
|
3069
3562
|
diag2("stdin is not a posttool event ({session_id, tool_name})");
|
|
3070
3563
|
return;
|
|
3071
3564
|
}
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3565
|
+
const vaultRoot = resolveHookVault(invocation, event.cwd);
|
|
3566
|
+
if (vaultRoot === null) return;
|
|
3567
|
+
if (!vaultPathTouched(event.tool_input, vaultRoot, event.cwd)) return;
|
|
3568
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
3569
|
+
const findings = await validateVault(vaultRoot);
|
|
3075
3570
|
let synced = false;
|
|
3076
3571
|
if (!hasErrors(findings)) {
|
|
3077
3572
|
try {
|
|
3078
|
-
await syncVault(
|
|
3573
|
+
await syncVault(vaultRoot);
|
|
3079
3574
|
synced = true;
|
|
3080
3575
|
} catch (err) {
|
|
3081
3576
|
diag2(`sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3092,7 +3587,6 @@ async function runHookPosttool() {
|
|
|
3092
3587
|
async function runHookStop() {
|
|
3093
3588
|
try {
|
|
3094
3589
|
const invocation = hookInvocation();
|
|
3095
|
-
if (invocation === null) return;
|
|
3096
3590
|
if (invocation.dryRun) {
|
|
3097
3591
|
diag2("--dry-run/--explain support prompt and pretool events only");
|
|
3098
3592
|
return;
|
|
@@ -3103,21 +3597,62 @@ async function runHookStop() {
|
|
|
3103
3597
|
return;
|
|
3104
3598
|
}
|
|
3105
3599
|
if (event.stop_hook_active === true) return;
|
|
3106
|
-
const
|
|
3600
|
+
const vaultRoot = resolveHookVault(invocation, event.cwd);
|
|
3601
|
+
if (vaultRoot === null) return;
|
|
3602
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
3107
3603
|
const scope = invocation.scope ?? resolveScope(config, event.cwd);
|
|
3108
3604
|
if (scope === void 0) return;
|
|
3109
3605
|
const rendered = renderStop(
|
|
3110
|
-
await validateVault(
|
|
3606
|
+
await validateVault(vaultRoot),
|
|
3111
3607
|
config.builtin_hooks?.["handoff-gate"]?.reason
|
|
3112
3608
|
);
|
|
3113
3609
|
if (rendered === null) return;
|
|
3114
|
-
const fired = dedupeMatches(hookStateDir(), event.session_id, [
|
|
3610
|
+
const fired = dedupeMatches(hookStateDir(vaultRoot), event.session_id, [
|
|
3611
|
+
{ id: "handoff-gate" }
|
|
3612
|
+
]);
|
|
3115
3613
|
if (fired.length === 0) return;
|
|
3116
3614
|
console.log(rendered);
|
|
3117
3615
|
} catch (err) {
|
|
3118
3616
|
diag2(err instanceof Error ? err.message : String(err));
|
|
3119
3617
|
}
|
|
3120
3618
|
}
|
|
3619
|
+
function parseSessionStartEvent(raw) {
|
|
3620
|
+
const fields = eventFields(raw);
|
|
3621
|
+
if (fields === null) return null;
|
|
3622
|
+
const { session_id, cwd, source } = fields;
|
|
3623
|
+
if (typeof session_id !== "string") return null;
|
|
3624
|
+
return {
|
|
3625
|
+
session_id,
|
|
3626
|
+
...typeof cwd === "string" && { cwd },
|
|
3627
|
+
...typeof source === "string" && { source }
|
|
3628
|
+
};
|
|
3629
|
+
}
|
|
3630
|
+
function renderSessionStart(scope, source, messages = {}) {
|
|
3631
|
+
const text = source === "compact" ? messages.reorient?.text ?? REORIENT_TEXT : messages.orient?.text ?? ORIENT_TEXT;
|
|
3632
|
+
return `Wiki scope "${scope}": ${text}`;
|
|
3633
|
+
}
|
|
3634
|
+
async function runHookSessionStart() {
|
|
3635
|
+
try {
|
|
3636
|
+
const invocation = hookInvocation();
|
|
3637
|
+
if (invocation.dryRun) {
|
|
3638
|
+
diag2("--dry-run/--explain support prompt and pretool events only");
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
const event = parseSessionStartEvent(await readStdin());
|
|
3642
|
+
if (event === null) {
|
|
3643
|
+
diag2("stdin is not a session-start event ({session_id})");
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const vaultRoot = resolveHookVault(invocation, event.cwd);
|
|
3647
|
+
if (vaultRoot === null) return;
|
|
3648
|
+
const config = await loadVaultConfig(vaultRoot);
|
|
3649
|
+
const scope = invocation.scope ?? resolveScope(config, event.cwd);
|
|
3650
|
+
if (scope === void 0) return;
|
|
3651
|
+
console.log(renderSessionStart(scope, event.source, config.builtin_hooks ?? {}));
|
|
3652
|
+
} catch (err) {
|
|
3653
|
+
diag2(err instanceof Error ? err.message : String(err));
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3121
3656
|
async function readStdin() {
|
|
3122
3657
|
process.stdin.setEncoding("utf8");
|
|
3123
3658
|
let input = "";
|
|
@@ -3129,11 +3664,12 @@ async function readStdin() {
|
|
|
3129
3664
|
function diag2(message) {
|
|
3130
3665
|
console.error(`kmd hook: ${message}`);
|
|
3131
3666
|
}
|
|
3132
|
-
var DEFAULT_TRIGGERS, SESSION_STATE_MAX_AGE_MS, KIRO_IDE_BUCKET_MS, ALL_SCOPES_KEY, PATCH_FILE_RE, COMMAND_TOKEN_RE, PATHISH_RE, RESYNC_REASON, RESYNC_TEXT, HANDOFF_GATE_REASON;
|
|
3667
|
+
var DEFAULT_TRIGGERS, SESSION_STATE_MAX_AGE_MS, KIRO_IDE_BUCKET_MS, ALL_SCOPES_KEY, PATCH_FILE_RE, COMMAND_TOKEN_RE, PATHISH_RE, RESYNC_REASON, RESYNC_TEXT, HANDOFF_GATE_REASON, ORIENT_TEXT, REORIENT_TEXT;
|
|
3133
3668
|
var init_hook = __esm({
|
|
3134
3669
|
"../cli/src/hook.ts"() {
|
|
3135
3670
|
"use strict";
|
|
3136
3671
|
init_database();
|
|
3672
|
+
init_kmd_config();
|
|
3137
3673
|
init_vault_config();
|
|
3138
3674
|
init_frontmatter();
|
|
3139
3675
|
init_sync();
|
|
@@ -3148,6 +3684,8 @@ var init_hook = __esm({
|
|
|
3148
3684
|
RESYNC_REASON = "Edit landed; the index sync is held until these validate errors are fixed";
|
|
3149
3685
|
RESYNC_TEXT = "kmd sync failed \u2014 index not updated; see hook stderr";
|
|
3150
3686
|
HANDOFF_GATE_REASON = "Validate errors are outstanding and the index sync is held \u2014 fix them, let the resync run, then finish";
|
|
3687
|
+
ORIENT_TEXT = "prime via the wiki MCP prime tool before substantive work \u2014 the primer carries current focus, book of work, and invariants.";
|
|
3688
|
+
REORIENT_TEXT = "context was compacted and transcript detail is lost \u2014 re-read the primer via the wiki MCP prime tool and route uncaptured findings into the wiki before continuing.";
|
|
3151
3689
|
}
|
|
3152
3690
|
});
|
|
3153
3691
|
|
|
@@ -3161,16 +3699,33 @@ process.on("warning", (warning) => {
|
|
|
3161
3699
|
});
|
|
3162
3700
|
var USAGE = `usage: kmd <command> [options]
|
|
3163
3701
|
|
|
3702
|
+
vault resolution (every command): positional > project tier (.kmd/config.local.yaml >
|
|
3703
|
+
.kmd/config.yaml > vault/vault.yaml > vault.yaml with a .kmd marker beside it,
|
|
3704
|
+
nearest ancestor of $KMD_PROJECT_DIR or cwd) > --default-root > $WIKI_VAULT >
|
|
3705
|
+
~/.kmd/config.yaml default_vault
|
|
3706
|
+
|
|
3164
3707
|
commands:
|
|
3165
|
-
init [<dir>] [-y]
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3708
|
+
init [<dir>] [-y] [--set-default]
|
|
3709
|
+
scaffold a fresh vault (no dir: current directory \u2014 TTY prompt, or -y);
|
|
3710
|
+
TTY offers to record it as default_vault, --set-default records it
|
|
3711
|
+
without asking (-y alone never touches the machine default)
|
|
3712
|
+
init --local [-y] scaffold a project vault: <git-root>/vault + <git-root>/.kmd state home
|
|
3713
|
+
sync [<vault-root>] vault \u2192 index sync (runs validate first)
|
|
3714
|
+
validate [<path>] deterministic vault checker
|
|
3715
|
+
mcp [<vault-root>] [--default-root <path>]
|
|
3716
|
+
start the stdio MCP server; --default-root is the plugin form
|
|
3717
|
+
(config default the project tier may beat); flags are mutually exclusive;
|
|
3718
|
+
no positional and no $KMD_PROJECT_DIR: the vault binds after
|
|
3719
|
+
initialization, from the client's roots/list when it declares the
|
|
3720
|
+
roots capability
|
|
3721
|
+
config [<vault-root>] print resolved vault + winning source; with nothing resolvable, list known vaults
|
|
3722
|
+
config <set|get|unset> default_vault [<path>]
|
|
3723
|
+
read/write the global default in ~/.kmd/config.yaml
|
|
3724
|
+
db reset [<vault-root>] delete the vault's index
|
|
3725
|
+
hook <prompt|pretool|posttool|stop|session-start> [<vault-root>] [--default-root <path>] [--scope <s>] [--harness <claude|kiro-ide>] [--triggers <file>]
|
|
3172
3726
|
harness gate engine: JSON event on stdin, decision/context on stdout;
|
|
3173
|
-
|
|
3727
|
+
the vault resolves through the chain with the event cwd as project
|
|
3728
|
+
signal; posttool auto-runs validate + sync after a vault write;
|
|
3174
3729
|
stop blocks the handoff once while validate errors hold the sync
|
|
3175
3730
|
|
|
3176
3731
|
options:
|
|
@@ -3183,52 +3738,79 @@ var { positionals, values } = parseArgs3({
|
|
|
3183
3738
|
options: {
|
|
3184
3739
|
version: { type: "boolean", short: "v" },
|
|
3185
3740
|
help: { type: "boolean", short: "h" },
|
|
3186
|
-
yes: { type: "boolean", short: "y" }
|
|
3741
|
+
yes: { type: "boolean", short: "y" },
|
|
3742
|
+
"default-root": { type: "string" },
|
|
3743
|
+
"set-default": { type: "boolean" },
|
|
3744
|
+
local: { type: "boolean" }
|
|
3187
3745
|
}
|
|
3188
3746
|
});
|
|
3189
3747
|
var command = values.version ? "--version" : values.help ? "--help" : positionals[0];
|
|
3190
|
-
function applyVaultRoot(positionalIndex) {
|
|
3191
|
-
const arg = positionals[positionalIndex];
|
|
3192
|
-
if (arg) {
|
|
3193
|
-
process.env.WIKI_VAULT = arg;
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
3748
|
async function run() {
|
|
3197
3749
|
switch (command) {
|
|
3198
3750
|
case "init": {
|
|
3199
3751
|
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3200
|
-
await runInit2(
|
|
3752
|
+
await runInit2(
|
|
3753
|
+
positionals[1],
|
|
3754
|
+
Boolean(values.yes),
|
|
3755
|
+
Boolean(values.local),
|
|
3756
|
+
Boolean(values["set-default"])
|
|
3757
|
+
);
|
|
3201
3758
|
break;
|
|
3202
3759
|
}
|
|
3203
3760
|
case "sync": {
|
|
3204
3761
|
const { runSyncCommand: runSyncCommand2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3205
|
-
await runSyncCommand2();
|
|
3762
|
+
await runSyncCommand2(positionals[1]);
|
|
3206
3763
|
break;
|
|
3207
3764
|
}
|
|
3208
3765
|
case "validate": {
|
|
3209
|
-
applyVaultRoot(1);
|
|
3210
3766
|
const { runValidate: runValidate2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3211
|
-
await runValidate2();
|
|
3767
|
+
await runValidate2(positionals[1]);
|
|
3212
3768
|
break;
|
|
3213
3769
|
}
|
|
3214
3770
|
case "mcp": {
|
|
3215
|
-
|
|
3771
|
+
const positional = positionals[1];
|
|
3772
|
+
const defaultRoot = typeof values["default-root"] === "string" ? values["default-root"] : void 0;
|
|
3773
|
+
if (positional && defaultRoot !== void 0) {
|
|
3774
|
+
console.error("kmd mcp: <vault-root> and --default-root are mutually exclusive");
|
|
3775
|
+
process.exit(2);
|
|
3776
|
+
}
|
|
3216
3777
|
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_start(), start_exports));
|
|
3778
|
+
if (!positional && !process.env.KMD_PROJECT_DIR) {
|
|
3779
|
+
const { loadGlobalConfig: loadGlobalConfig2 } = await Promise.resolve().then(() => (init_kmd_config(), kmd_config_exports));
|
|
3780
|
+
await startMcpServer2({
|
|
3781
|
+
cwd: process.cwd(),
|
|
3782
|
+
defaultRoot,
|
|
3783
|
+
envVault: process.env.WIKI_VAULT,
|
|
3784
|
+
globalDefault: loadGlobalConfig2().default_vault
|
|
3785
|
+
});
|
|
3786
|
+
break;
|
|
3787
|
+
}
|
|
3788
|
+
const { resolveCliVault: resolveCliVault2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3789
|
+
const resolved = resolveCliVault2(positional, defaultRoot);
|
|
3790
|
+
if (resolved.root === null) {
|
|
3791
|
+
console.error(
|
|
3792
|
+
"kmd mcp: no vault resolvable \u2014 pass <vault-root>, run inside a project vault, set WIKI_VAULT, or `kmd config set default_vault <path>`"
|
|
3793
|
+
);
|
|
3794
|
+
process.exit(1);
|
|
3795
|
+
}
|
|
3796
|
+
process.env.WIKI_VAULT = resolved.root;
|
|
3217
3797
|
await startMcpServer2();
|
|
3218
3798
|
break;
|
|
3219
3799
|
}
|
|
3220
3800
|
case "config": {
|
|
3221
|
-
|
|
3222
|
-
const { runConfig: runConfig2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3223
|
-
await
|
|
3801
|
+
const sub = positionals[1];
|
|
3802
|
+
const { runConfig: runConfig2, runConfigGet: runConfigGet2, runConfigSet: runConfigSet2, runConfigUnset: runConfigUnset2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3803
|
+
if (sub === "set") await runConfigSet2(positionals[2], positionals[3]);
|
|
3804
|
+
else if (sub === "get") await runConfigGet2(positionals[2]);
|
|
3805
|
+
else if (sub === "unset") await runConfigUnset2(positionals[2]);
|
|
3806
|
+
else await runConfig2(sub);
|
|
3224
3807
|
break;
|
|
3225
3808
|
}
|
|
3226
3809
|
case "db": {
|
|
3227
3810
|
const sub = positionals[1];
|
|
3228
3811
|
if (sub === "reset") {
|
|
3229
|
-
applyVaultRoot(2);
|
|
3230
3812
|
const { runDbReset: runDbReset2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
3231
|
-
await runDbReset2();
|
|
3813
|
+
await runDbReset2(positionals[2]);
|
|
3232
3814
|
} else {
|
|
3233
3815
|
console.error(sub ? `unknown db subcommand: ${sub}` : "usage: kmd db reset [<vault-root>]");
|
|
3234
3816
|
process.exit(2);
|
|
@@ -3249,11 +3831,14 @@ async function run() {
|
|
|
3249
3831
|
} else if (sub === "stop") {
|
|
3250
3832
|
const { runHookStop: runHookStop2 } = await Promise.resolve().then(() => (init_hook(), hook_exports));
|
|
3251
3833
|
await runHookStop2();
|
|
3834
|
+
} else if (sub === "session-start") {
|
|
3835
|
+
const { runHookSessionStart: runHookSessionStart2 } = await Promise.resolve().then(() => (init_hook(), hook_exports));
|
|
3836
|
+
await runHookSessionStart2();
|
|
3252
3837
|
} else if (sub) {
|
|
3253
3838
|
console.error(`kmd hook: unknown event: ${sub}`);
|
|
3254
3839
|
} else {
|
|
3255
3840
|
console.error(
|
|
3256
|
-
"usage: kmd hook <prompt|pretool|posttool|stop> [<vault-root>] [--scope <scope>] [--harness <claude|kiro-ide>]"
|
|
3841
|
+
"usage: kmd hook <prompt|pretool|posttool|stop|session-start> [<vault-root>] [--scope <scope>] [--harness <claude|kiro-ide>]"
|
|
3257
3842
|
);
|
|
3258
3843
|
process.exit(2);
|
|
3259
3844
|
}
|
|
@@ -3261,11 +3846,11 @@ async function run() {
|
|
|
3261
3846
|
}
|
|
3262
3847
|
case "--version":
|
|
3263
3848
|
case "-v": {
|
|
3264
|
-
const { readFileSync:
|
|
3265
|
-
const { join:
|
|
3266
|
-
const { fileURLToPath } = await import("node:url");
|
|
3267
|
-
const pkgDir =
|
|
3268
|
-
const pkg = JSON.parse(
|
|
3849
|
+
const { readFileSync: readFileSync3 } = await import("node:fs");
|
|
3850
|
+
const { join: join12, dirname: dirname7 } = await import("node:path");
|
|
3851
|
+
const { fileURLToPath: fileURLToPath2 } = await import("node:url");
|
|
3852
|
+
const pkgDir = dirname7(dirname7(fileURLToPath2(import.meta.url)));
|
|
3853
|
+
const pkg = JSON.parse(readFileSync3(join12(pkgDir, "package.json"), "utf8"));
|
|
3269
3854
|
console.log(pkg.version);
|
|
3270
3855
|
break;
|
|
3271
3856
|
}
|