@bartolli/kmd 0.6.0 → 0.7.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 +392 -253
- package/dist/kmd.mjs.map +4 -4
- package/package.json +1 -1
package/dist/kmd.mjs
CHANGED
|
@@ -116,7 +116,7 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
// ../
|
|
119
|
+
// ../db/src/vault-config.ts
|
|
120
120
|
import { readFile } from "node:fs/promises";
|
|
121
121
|
import { join as join2 } from "node:path";
|
|
122
122
|
import { parse } from "yaml";
|
|
@@ -131,6 +131,9 @@ function isValidRegex(pattern) {
|
|
|
131
131
|
function kindName(entry) {
|
|
132
132
|
return typeof entry === "string" ? entry : entry.name;
|
|
133
133
|
}
|
|
134
|
+
function configJsonSchema() {
|
|
135
|
+
return z.toJSONSchema(VaultConfigSchema, { target: "draft-7" });
|
|
136
|
+
}
|
|
134
137
|
async function loadVaultConfig(vaultRoot2) {
|
|
135
138
|
const path = join2(vaultRoot2, "vault.yaml");
|
|
136
139
|
let raw;
|
|
@@ -148,42 +151,46 @@ ${issues}`);
|
|
|
148
151
|
return parsed.data;
|
|
149
152
|
}
|
|
150
153
|
var ScopeSchema, KindEntrySchema, WhenSchema, TriggerSchema, TriggersSchema, VaultConfigSchema, BUILT_IN_KINDS;
|
|
151
|
-
var
|
|
152
|
-
"../
|
|
154
|
+
var init_vault_config = __esm({
|
|
155
|
+
"../db/src/vault-config.ts"() {
|
|
153
156
|
"use strict";
|
|
154
|
-
ScopeSchema = z.
|
|
155
|
-
repo: z.string().optional()
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
ScopeSchema = z.strictObject({
|
|
158
|
+
repo: z.string().optional().describe(
|
|
159
|
+
"Consumer repo path (~ expands). Load-bearing for kmd hook: the active scope resolves by matching the session cwd against it."
|
|
160
|
+
),
|
|
161
|
+
methodology: z.string().optional().describe("Must appear in the methodologies list."),
|
|
162
|
+
status: z.string().describe("Free string; keep within statuses by convention.")
|
|
158
163
|
});
|
|
159
164
|
KindEntrySchema = z.union([
|
|
160
165
|
z.string(),
|
|
161
|
-
z.
|
|
166
|
+
z.strictObject({
|
|
162
167
|
name: z.string(),
|
|
163
|
-
signal: z.string(),
|
|
164
|
-
where: z.string()
|
|
168
|
+
signal: z.string().describe("When to pick this kind."),
|
|
169
|
+
where: z.string().describe("Path pattern pages of this kind follow.")
|
|
165
170
|
})
|
|
166
171
|
]);
|
|
167
172
|
WhenSchema = z.union([
|
|
168
173
|
z.string(),
|
|
169
|
-
z.
|
|
174
|
+
z.strictObject({
|
|
170
175
|
name: z.enum(["newer-than"]),
|
|
171
176
|
fresh: z.array(z.string().min(1)).min(1),
|
|
172
177
|
than: z.array(z.string().min(1)).min(1)
|
|
173
178
|
})
|
|
174
179
|
]);
|
|
175
|
-
TriggerSchema = z.
|
|
176
|
-
id: z.string().min(1),
|
|
180
|
+
TriggerSchema = z.strictObject({
|
|
181
|
+
id: z.string().min(1).describe("Unique per scope list; duplicates keep the first occurrence."),
|
|
177
182
|
on: z.enum(["prompt", "pretool"]),
|
|
178
|
-
enforce: z.enum(["inject", "warn", "block"]),
|
|
179
|
-
keywords: z.array(z.string().min(1)).optional(),
|
|
180
|
-
intent: z.array(z.string()).optional(),
|
|
181
|
-
tool: z.string().optional(),
|
|
182
|
-
args_match: z.string().optional(),
|
|
183
|
-
files: z.array(z.string().min(1)).optional(),
|
|
184
|
-
when: WhenSchema.optional()
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
enforce: z.enum(["inject", "warn", "block"]).describe("inject: context line \xB7 warn: stderr \xB7 block: deny with reason."),
|
|
184
|
+
keywords: z.array(z.string().min(1)).optional().describe("Word-boundary, porter-stemmed match. Prompt triggers need keywords or intent."),
|
|
185
|
+
intent: z.array(z.string()).optional().describe("Case-insensitive regexes over the raw prompt \u2014 the stemming escape hatch."),
|
|
186
|
+
tool: z.string().optional().describe("Exact tool name; pretool matchers AND-compose."),
|
|
187
|
+
args_match: z.string().optional().describe("Regex over the serialized tool input."),
|
|
188
|
+
files: z.array(z.string().min(1)).optional().describe("Globs against the paths the tool touches; pretool triggers only."),
|
|
189
|
+
when: WhenSchema.optional().describe(
|
|
190
|
+
"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."
|
|
191
|
+
),
|
|
192
|
+
text: z.string().optional().describe("Required for inject and warn \u2014 the line emitted."),
|
|
193
|
+
reason: z.string().optional().describe("Required for block \u2014 the denial the agent reads.")
|
|
187
194
|
}).superRefine((trigger, ctx) => {
|
|
188
195
|
if (trigger.on === "prompt" && !trigger.keywords?.length && !trigger.intent?.length) {
|
|
189
196
|
ctx.addIssue({
|
|
@@ -221,22 +228,28 @@ var init_config = __esm({
|
|
|
221
228
|
}
|
|
222
229
|
});
|
|
223
230
|
TriggersSchema = z.record(z.string(), z.array(TriggerSchema));
|
|
224
|
-
VaultConfigSchema = z.
|
|
225
|
-
scopes: z.record(z.string(), ScopeSchema),
|
|
226
|
-
kinds: z.array(KindEntrySchema)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
VaultConfigSchema = z.strictObject({
|
|
232
|
+
scopes: z.record(z.string(), ScopeSchema).describe("Scope name \u2192 entry; key = directory name under projects/."),
|
|
233
|
+
kinds: z.array(KindEntrySchema).describe(
|
|
234
|
+
"Page kind vocabulary; validate-enforced. Object form adds a kind-selector row to wiki://authoring."
|
|
235
|
+
),
|
|
236
|
+
statuses: z.array(z.string()).describe("Page status vocabulary; validate-enforced."),
|
|
237
|
+
methodologies: z.array(z.string()).describe("Methodology vocabulary for pages and scope entries."),
|
|
238
|
+
tags: z.strictObject({
|
|
239
|
+
canonical: z.array(z.string()).describe("Approved tags."),
|
|
240
|
+
aliases: z.record(z.string(), z.string()).describe("Alias \u2192 canonical; validate warns on alias use.")
|
|
232
241
|
}),
|
|
233
|
-
authoring_rules: z.string().optional(),
|
|
234
|
-
authoring_rules_extra: z.string().optional(),
|
|
235
|
-
sync_protocol: z.string().optional(),
|
|
236
|
-
sync_protocol_extra: z.string().optional(),
|
|
237
|
-
triggers: TriggersSchema.optional()
|
|
238
|
-
|
|
239
|
-
|
|
242
|
+
authoring_rules: z.string().optional().describe("Replaces the served \xA7 Authoring rules entirely \u2014 escape hatch."),
|
|
243
|
+
authoring_rules_extra: z.string().optional().describe("Appended after the served \xA7 Authoring rules."),
|
|
244
|
+
sync_protocol: z.string().optional().describe("Replaces the served \xA7 Resync protocol entirely \u2014 escape hatch."),
|
|
245
|
+
sync_protocol_extra: z.string().optional().describe("Appended after the served \xA7 Resync protocol."),
|
|
246
|
+
triggers: TriggersSchema.optional().describe(
|
|
247
|
+
'Full-replace of the trigger base per scope \u2014 escape hatch. "_all" is reserved for triggers_extra.'
|
|
248
|
+
),
|
|
249
|
+
triggers_extra: TriggersSchema.optional().describe(
|
|
250
|
+
'Appended per scope after the engine defaults; the reserved "_all" key fires in every session.'
|
|
251
|
+
)
|
|
252
|
+
}).describe("kmd vault.yaml \u2014 controlled vocabulary and gate triggers.").superRefine((config, ctx) => {
|
|
240
253
|
for (const [name, scope] of Object.entries(config.scopes)) {
|
|
241
254
|
if (scope.methodology !== void 0 && !config.methodologies.includes(scope.methodology)) {
|
|
242
255
|
ctx.addIssue({
|
|
@@ -310,11 +323,287 @@ var init_frontmatter = __esm({
|
|
|
310
323
|
}
|
|
311
324
|
});
|
|
312
325
|
|
|
326
|
+
// ../cli/src/init-templates.ts
|
|
327
|
+
var VAULT_TEMPLATES;
|
|
328
|
+
var init_init_templates = __esm({
|
|
329
|
+
"../cli/src/init-templates.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
VAULT_TEMPLATES = {
|
|
332
|
+
"note.md": '---\ntitle: {{title}}\ntags: []\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n',
|
|
333
|
+
"project-adr.md": '---\ntitle: {{title}}\nkind: adr\nscope:\nstatus: active\nsummary:\ntags: []\nsupersedes:\nsuperseded_by:\nsources: []\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n\n## Status\n\n> [!info] Active\n\n## Context\n\nThe forces in tension \u2014 what made this a decision rather than an\nobvious step. Name the constraints; skip the history recap.\n\n## Decision\n\nWhat was decided, precisely. Predicates only \u2014 falsifiable, not\naspirational.\n\n## Rationale\n\nWhy this side won: which alternatives were live, and what killed each.\nAn ADR with no rejected alternative is a spec in disguise.\n\n## Consequences\n\nWhat this commits the project to going forward \u2014 including the costs\naccepted, not just the benefits claimed.\n\n## Links\n\n-\n',
|
|
334
|
+
"project-index.md": `---
|
|
335
|
+
title: {{title}}
|
|
336
|
+
kind: project
|
|
337
|
+
methodology: sdd
|
|
338
|
+
phase: 1
|
|
339
|
+
repo:
|
|
340
|
+
scope:
|
|
341
|
+
status: active
|
|
342
|
+
tags: []
|
|
343
|
+
summary:
|
|
344
|
+
created: "{{date}}"
|
|
345
|
+
updated: {{date}}
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
# {{title}}
|
|
349
|
+
|
|
350
|
+
## Summary
|
|
351
|
+
|
|
352
|
+
One paragraph. What this project is and what it delivers.
|
|
353
|
+
|
|
354
|
+
## Current Phase
|
|
355
|
+
|
|
356
|
+
What's in flight right now. 1\u20133 sentences.
|
|
357
|
+
|
|
358
|
+
## Links
|
|
359
|
+
|
|
360
|
+
- [[projects/{{title}}/primer]]
|
|
361
|
+
|
|
362
|
+
## Sources
|
|
363
|
+
|
|
364
|
+
-
|
|
365
|
+
`,
|
|
366
|
+
"project-ops.md": `---
|
|
367
|
+
title: {{title}}
|
|
368
|
+
kind: ops
|
|
369
|
+
scope:
|
|
370
|
+
status: active
|
|
371
|
+
summary:
|
|
372
|
+
tags: []
|
|
373
|
+
sources: []
|
|
374
|
+
created: "{{date}}"
|
|
375
|
+
updated: {{date}}
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
# {{title}}
|
|
379
|
+
|
|
380
|
+
## Summary
|
|
381
|
+
|
|
382
|
+
What this runbook operates and when it runs.
|
|
383
|
+
|
|
384
|
+
## Context
|
|
385
|
+
|
|
386
|
+
What triggers this flow; what breaks when it's skipped.
|
|
387
|
+
|
|
388
|
+
## Details
|
|
389
|
+
|
|
390
|
+
Numbered steps executable without asking: exact commands, expected
|
|
391
|
+
output, failure modes and their recovery. Predicates only \u2014 if a step
|
|
392
|
+
needs judgment, say whose.
|
|
393
|
+
|
|
394
|
+
## Links
|
|
395
|
+
|
|
396
|
+
-
|
|
397
|
+
|
|
398
|
+
## Sources
|
|
399
|
+
|
|
400
|
+
-
|
|
401
|
+
`,
|
|
402
|
+
"project-plan.md": `---
|
|
403
|
+
title: {{title}}
|
|
404
|
+
kind: plan
|
|
405
|
+
scope:
|
|
406
|
+
status: active
|
|
407
|
+
summary:
|
|
408
|
+
tags: []
|
|
409
|
+
created: "{{date}}"
|
|
410
|
+
updated: {{date}}
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
# {{title}}
|
|
414
|
+
|
|
415
|
+
## Goal
|
|
416
|
+
|
|
417
|
+
One sentence: what this phase delivers. If it needs two, it's two
|
|
418
|
+
plans.
|
|
419
|
+
|
|
420
|
+
## Scope
|
|
421
|
+
|
|
422
|
+
What's in and \u2014 more important \u2014 what's out. Out-of-scope lines stop
|
|
423
|
+
scope creep better than in-scope lines define it.
|
|
424
|
+
|
|
425
|
+
## Milestones
|
|
426
|
+
|
|
427
|
+
Checkable outcomes, not activities. Tick here; don't cascade ticks to
|
|
428
|
+
index.md unless phase or status changed.
|
|
429
|
+
|
|
430
|
+
1.
|
|
431
|
+
2.
|
|
432
|
+
|
|
433
|
+
## Dependencies
|
|
434
|
+
|
|
435
|
+
-
|
|
436
|
+
|
|
437
|
+
## Status Log
|
|
438
|
+
|
|
439
|
+
Dated one-liners, newest first. This is the only history surface \u2014
|
|
440
|
+
primer and index stay clean.
|
|
441
|
+
|
|
442
|
+
- {{date}}: Phase started.
|
|
443
|
+
`,
|
|
444
|
+
"project-primer.md": "---\ncreated: \"{{date}}\"\nupdated: {{date}}\n---\n\n# Primer\n\n## Current Focus\n\nWhat we're working on right now. 1\u20133 sentences. Not a history log \u2014\nprogress lives in plan checkboxes; history lives in git.\n\n## Open Questions\n\nCurrently-open questions only. Strip on resolution; reflect the\nresolution in the relevant ADR or spec, not here.\n\n-\n\n## Blocked On\n\nNothing currently. List only items genuinely blocking forward\nprogress.\n\n## Load-bearing invariants (optional)\n\nFacts not derivable from code or commits \u2014 pipeline orderings, sync\nconstraints, file-location rationales. The kind of fact a fresh-context\nagent would otherwise re-derive incorrectly. Durable across sessions.\n\n## Read Order (optional)\n\n\u22645 entries. Wiki/doc pointers \u2014 what to read to orient. Each entry\none line: `[[wikilink]] \u2014 why`.\n\n1.\n\n## Working set (optional)\n\n\u226410 entries. File pointers (code, fixtures, configs) for the\nimmediate Next Steps, with one-line context. Lets a fresh-context\nagent skip the discovery loop. Task-scoped \u2014 refresh when focus\nshifts. Most valuable when transitioning phases or starting a new\nfeature; can be skipped mid-slice when the immediate context already\ncarries it.\n\n- `path/to/file` \u2014 why it's relevant for the next steps.\n",
|
|
445
|
+
"project-spec.md": `---
|
|
446
|
+
title: {{title}}
|
|
447
|
+
kind: spec
|
|
448
|
+
scope:
|
|
449
|
+
status: active
|
|
450
|
+
summary:
|
|
451
|
+
tags: []
|
|
452
|
+
sources: []
|
|
453
|
+
created: "{{date}}"
|
|
454
|
+
updated: {{date}}
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
# {{title}}
|
|
458
|
+
|
|
459
|
+
## Summary
|
|
460
|
+
|
|
461
|
+
One paragraph, in predicates \u2014 the system's current shape, not its
|
|
462
|
+
history. If you're recording a *choice* between alternatives, stop:
|
|
463
|
+
that's an ADR.
|
|
464
|
+
|
|
465
|
+
## Context
|
|
466
|
+
|
|
467
|
+
The question this spec answers and who asks it. One paragraph, no
|
|
468
|
+
narrative arc.
|
|
469
|
+
|
|
470
|
+
## Details
|
|
471
|
+
|
|
472
|
+
The actual contract: inputs, outputs, invariants, failure modes. Use
|
|
473
|
+
subsections per concern. Present tense \u2014 the spec describes what IS
|
|
474
|
+
and must match current code at every commit.
|
|
475
|
+
|
|
476
|
+
## Links
|
|
477
|
+
|
|
478
|
+
-
|
|
479
|
+
|
|
480
|
+
## Sources
|
|
481
|
+
|
|
482
|
+
-
|
|
483
|
+
`,
|
|
484
|
+
"project-story.md": '---\ntitle: {{title}}\nkind: story\nscope:\nparent:\nstatus: active\ntriage_state: needs-triage\ncategory: enhancement\nblocked_by: []\ntags: []\nsources: []\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n\n## User Story\n\nAs a {{actor}}, I want {{capability}}, so that {{benefit}}.\n\n## Scenarios\n\nScenarios are the test specification \u2014 each becomes a failing test\nbefore implementation. Write observable outcomes, not implementation\nsteps.\n\n**Scenario: {{scenario name}}**\n- Given {{precondition}}\n- When {{action}}\n- Then {{expected outcome}}\n\n**Scenario: {{edge case name}}**\n- Given {{precondition}}\n- When {{action}}\n- Then {{expected outcome}}\n\n## Slices\n\nVertical tracer bullets: each slice ships signature + implementation +\ntests + wiring, independently committable. `AFK` = agent works alone;\n`HITL` = human in the loop.\n\n- [ ] **Slice 1** \u2014 {{description}} \xB7 `AFK` \xB7 [[spec-{{topic}}]]\n- [ ] **Slice 2** \u2014 {{description}} \xB7 `HITL` \xB7 [[adr-{{decision}}]]\n\n## References\n\n- [[spec-{{related-spec}}]]\n- [[adr-{{related-adr}}]]\n',
|
|
485
|
+
"research-article.md": '---\ntitle: {{title}}\nkind: article\ntopic:\nstatus: active\nsummary:\ntags: []\nsources: []\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n\n## Summary\n\nOne paragraph. What this page covers and why it matters.\n\n## Context\n\nWhy this page exists. The problem or question it addresses.\n\n## Details\n\nThe actual content. Use subsections as needed.\n\n## Links\n\n-\n\n## Sources\n\n-\n',
|
|
486
|
+
"research-index.md": '---\ntitle: {{title}}\nkind: topic\nstatus: active\nsummary:\nconfidence: medium\nsource_count: 0\ntags: []\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n\n## Summary\n\nOne paragraph. What this research topic covers.\n\n## Confidence\n\nWhy the confidence level is what it is. What would raise or lower it.\n\n## Pages\n\n-\n\n## Sources\n\n-\n',
|
|
487
|
+
"research-src.md": '---\ntitle: {{title}}\nkind: src\ntopic:\nstatus: active\nsummary:\ntags: []\nsource_url:\ncreated: "{{date}}"\nupdated: {{date}}\n---\n\n# {{title}}\n\n## Summary\n\nWhat this source is. Author, origin, year.\n\n## Key Points\n\n-\n\n## Quotes\n\n>\n\n## Relevance\n\nWhy this source matters to the topic.\n\n## Citation\n\n`{author, year, title, url}`\n'
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// ../cli/src/init.ts
|
|
493
|
+
import { mkdir, readdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
494
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
495
|
+
import { stringify } from "yaml";
|
|
496
|
+
async function refreshSchemaFile(root) {
|
|
497
|
+
const path = join3(root, SCHEMA_FILE);
|
|
498
|
+
const next = `${JSON.stringify(configJsonSchema(), null, 2)}
|
|
499
|
+
`;
|
|
500
|
+
try {
|
|
501
|
+
if (await readFile2(path, "utf8") === next) return false;
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
await writeFile(path, next);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
async function scaffoldVault(dir) {
|
|
508
|
+
const root = resolve2(dir);
|
|
509
|
+
let entries = [];
|
|
510
|
+
try {
|
|
511
|
+
entries = await readdir(root);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
if (err.code !== "ENOENT") throw err;
|
|
514
|
+
}
|
|
515
|
+
if (entries.includes("vault.yaml")) {
|
|
516
|
+
throw new Error(`already a vault: ${root} (vault.yaml exists)`);
|
|
517
|
+
}
|
|
518
|
+
if (entries.length > 0) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
`target is not empty: ${root}
|
|
521
|
+
found: ${entries.join(", ")}
|
|
522
|
+
delete it or pick another directory`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
for (const domain of DOMAIN_DIRS) {
|
|
526
|
+
await mkdir(join3(root, domain), { recursive: true });
|
|
527
|
+
}
|
|
528
|
+
await mkdir(join3(root, "templates"), { recursive: true });
|
|
529
|
+
for (const [file, content] of Object.entries(VAULT_TEMPLATES)) {
|
|
530
|
+
await writeFile(join3(root, "templates", file), content);
|
|
531
|
+
}
|
|
532
|
+
await refreshSchemaFile(root);
|
|
533
|
+
await writeFile(join3(root, "vault.yaml"), SCHEMA_MODELINE + stringify(STARTER_CONFIG));
|
|
534
|
+
return root;
|
|
535
|
+
}
|
|
536
|
+
async function promptYesNo(question, input = process.stdin, output = process.stderr) {
|
|
537
|
+
const { createInterface } = await import("node:readline/promises");
|
|
538
|
+
const rl = createInterface({ input, output });
|
|
539
|
+
try {
|
|
540
|
+
const answer = await rl.question(question);
|
|
541
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
542
|
+
} finally {
|
|
543
|
+
rl.close();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function runInit(dir, yes = false) {
|
|
547
|
+
let target = dir;
|
|
548
|
+
if (!target) {
|
|
549
|
+
if (yes) {
|
|
550
|
+
target = ".";
|
|
551
|
+
} else if (process.stdin.isTTY) {
|
|
552
|
+
const ok = await promptYesNo(`initialize a vault in ${resolve2(".")}? [y/N] `);
|
|
553
|
+
if (!ok) {
|
|
554
|
+
console.error("init: aborted");
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
target = ".";
|
|
558
|
+
} else {
|
|
559
|
+
console.error("usage: kmd init <dir> (or --yes to scaffold the current directory)");
|
|
560
|
+
process.exit(2);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
let root;
|
|
564
|
+
try {
|
|
565
|
+
root = await scaffoldVault(target);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
console.error(`init: ${err instanceof Error ? err.message : err}`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
const templateCount = Object.keys(VAULT_TEMPLATES).length;
|
|
571
|
+
console.log(`initialized empty vault at ${root}
|
|
572
|
+
|
|
573
|
+
vault.yaml starter vocabulary \u2014 add your first scope under scopes:
|
|
574
|
+
vault.schema.json IDE validation via the yaml-language-server modeline
|
|
575
|
+
templates/ ${templateCount} built-in templates (served at wiki://template/...)
|
|
576
|
+
projects/ research/ notes/
|
|
577
|
+
|
|
578
|
+
next steps:
|
|
579
|
+
export WIKI_VAULT=${root}
|
|
580
|
+
kmd mcp ${root} # stdio MCP server (prime, search)`);
|
|
581
|
+
}
|
|
582
|
+
var SCHEMA_FILE, SCHEMA_MODELINE, STARTER_CONFIG, DOMAIN_DIRS;
|
|
583
|
+
var init_init = __esm({
|
|
584
|
+
"../cli/src/init.ts"() {
|
|
585
|
+
"use strict";
|
|
586
|
+
init_vault_config();
|
|
587
|
+
init_init_templates();
|
|
588
|
+
SCHEMA_FILE = "vault.schema.json";
|
|
589
|
+
SCHEMA_MODELINE = `# yaml-language-server: $schema=./${SCHEMA_FILE}
|
|
590
|
+
`;
|
|
591
|
+
STARTER_CONFIG = {
|
|
592
|
+
scopes: {},
|
|
593
|
+
kinds: ["project", "spec", "adr", "plan", "story", "ops", "topic", "article", "src", "note"],
|
|
594
|
+
statuses: ["draft", "active", "superseded", "archived"],
|
|
595
|
+
methodologies: ["sdd", "tdd", "hybrid"],
|
|
596
|
+
tags: { canonical: [], aliases: {} }
|
|
597
|
+
};
|
|
598
|
+
DOMAIN_DIRS = ["projects", "research", "notes"];
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
|
|
313
602
|
// ../cli/src/sync.ts
|
|
314
603
|
import { createHash as createHash2 } from "node:crypto";
|
|
315
604
|
import { mkdirSync } from "node:fs";
|
|
316
|
-
import { readdir, readFile as
|
|
317
|
-
import { dirname, join as
|
|
605
|
+
import { readdir as readdir2, readFile as readFile3 } from "node:fs/promises";
|
|
606
|
+
import { dirname, join as join4, relative, sep } from "node:path";
|
|
318
607
|
import { z as z2 } from "zod";
|
|
319
608
|
function loadEnv() {
|
|
320
609
|
const parsed = EnvSchema.safeParse({
|
|
@@ -330,18 +619,18 @@ function loadEnv() {
|
|
|
330
619
|
async function walkMarkdown(root, domain) {
|
|
331
620
|
const out = [];
|
|
332
621
|
async function recurse(dir) {
|
|
333
|
-
const entries = await
|
|
622
|
+
const entries = await readdir2(dir, { withFileTypes: true }).catch(() => null);
|
|
334
623
|
if (!entries) return;
|
|
335
624
|
for (const entry of entries) {
|
|
336
625
|
if (entry.name.startsWith(".")) continue;
|
|
337
626
|
if (entry.isDirectory()) {
|
|
338
|
-
await recurse(
|
|
627
|
+
await recurse(join4(dir, entry.name));
|
|
339
628
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
340
|
-
out.push(
|
|
629
|
+
out.push(join4(dir, entry.name));
|
|
341
630
|
}
|
|
342
631
|
}
|
|
343
632
|
}
|
|
344
|
-
await recurse(
|
|
633
|
+
await recurse(join4(root, domain));
|
|
345
634
|
return out;
|
|
346
635
|
}
|
|
347
636
|
function toRelativePath(root, absolute) {
|
|
@@ -480,7 +769,7 @@ async function syncVault(vaultRoot2) {
|
|
|
480
769
|
let skipped = 0;
|
|
481
770
|
for (const file of files) {
|
|
482
771
|
const path = toRelativePath(vaultRoot2, file);
|
|
483
|
-
const raw = await
|
|
772
|
+
const raw = await readFile3(file, "utf8");
|
|
484
773
|
const parsed = parseFrontmatter(raw);
|
|
485
774
|
const fields = buildPageFields(path, raw, parsed, scopes);
|
|
486
775
|
if (!fields) {
|
|
@@ -507,6 +796,9 @@ async function syncVault(vaultRoot2) {
|
|
|
507
796
|
db.exec("INSERT INTO pages_fts(pages_fts) VALUES('rebuild')");
|
|
508
797
|
setMeta(db, "vault_root", canonicalVaultRoot(vaultRoot2));
|
|
509
798
|
setMeta(db, "last_synced", (/* @__PURE__ */ new Date()).toISOString());
|
|
799
|
+
if (await refreshSchemaFile(vaultRoot2)) {
|
|
800
|
+
console.error("sync: vault.schema.json refreshed to the running engine");
|
|
801
|
+
}
|
|
510
802
|
return {
|
|
511
803
|
changed,
|
|
512
804
|
unchanged,
|
|
@@ -535,8 +827,9 @@ var init_sync = __esm({
|
|
|
535
827
|
"../cli/src/sync.ts"() {
|
|
536
828
|
"use strict";
|
|
537
829
|
init_database();
|
|
538
|
-
|
|
830
|
+
init_vault_config();
|
|
539
831
|
init_frontmatter();
|
|
832
|
+
init_init();
|
|
540
833
|
EnvSchema = z2.object({
|
|
541
834
|
WIKI_VAULT: z2.string().min(1)
|
|
542
835
|
});
|
|
@@ -556,8 +849,8 @@ var init_sync = __esm({
|
|
|
556
849
|
});
|
|
557
850
|
|
|
558
851
|
// ../cli/src/validate.ts
|
|
559
|
-
import { readFile as
|
|
560
|
-
import { join as
|
|
852
|
+
import { readFile as readFile4, stat } from "node:fs/promises";
|
|
853
|
+
import { join as join5 } from "node:path";
|
|
561
854
|
function hasIndexableTitle(data) {
|
|
562
855
|
return typeof data.title === "string" && data.title.trim() !== "";
|
|
563
856
|
}
|
|
@@ -915,7 +1208,7 @@ async function validateVault(root) {
|
|
|
915
1208
|
const pages = [];
|
|
916
1209
|
const linkPages = [];
|
|
917
1210
|
for (const { relPath, abs } of files) {
|
|
918
|
-
const raw = await
|
|
1211
|
+
const raw = await readFile4(abs, "utf8");
|
|
919
1212
|
findings.push(...validatePage(relPath, raw, cfg, refIndex));
|
|
920
1213
|
try {
|
|
921
1214
|
const parsed = parseFrontmatter(raw);
|
|
@@ -929,7 +1222,7 @@ async function validateVault(root) {
|
|
|
929
1222
|
for (const name of customKindNames(cfg)) {
|
|
930
1223
|
const file = `templates/${name}.md`;
|
|
931
1224
|
try {
|
|
932
|
-
await stat(
|
|
1225
|
+
await stat(join5(root, file));
|
|
933
1226
|
} catch {
|
|
934
1227
|
findings.push({
|
|
935
1228
|
path: file,
|
|
@@ -945,7 +1238,7 @@ var REQUIRED_FIELDS, TAG_OPTIONAL_KINDS, FOLDER_PATTERNS, UNIVERSAL_FLOOR;
|
|
|
945
1238
|
var init_validate = __esm({
|
|
946
1239
|
"../cli/src/validate.ts"() {
|
|
947
1240
|
"use strict";
|
|
948
|
-
|
|
1241
|
+
init_vault_config();
|
|
949
1242
|
init_frontmatter();
|
|
950
1243
|
init_sync();
|
|
951
1244
|
REQUIRED_FIELDS = {
|
|
@@ -1003,12 +1296,13 @@ __export(cli_exports, {
|
|
|
1003
1296
|
resolveCli: () => resolveCli,
|
|
1004
1297
|
runConfig: () => runConfig,
|
|
1005
1298
|
runDbReset: () => runDbReset,
|
|
1299
|
+
runInit: () => runInit,
|
|
1006
1300
|
runSyncCommand: () => runSyncCommand,
|
|
1007
1301
|
runValidate: () => runValidate,
|
|
1008
1302
|
vaultRoot: () => vaultRoot
|
|
1009
1303
|
});
|
|
1010
1304
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
|
1011
|
-
import { dirname as dirname2, join as
|
|
1305
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
1012
1306
|
import { parseArgs } from "node:util";
|
|
1013
1307
|
function resolveCli(argv) {
|
|
1014
1308
|
const { positionals: positionals2 } = parseArgs({ args: argv, allowPositionals: true, strict: false });
|
|
@@ -1078,7 +1372,7 @@ function knownVaults() {
|
|
|
1078
1372
|
const known = [];
|
|
1079
1373
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
1080
1374
|
if (!entry.isDirectory()) continue;
|
|
1081
|
-
const index =
|
|
1375
|
+
const index = join6(root, entry.name, "index.db");
|
|
1082
1376
|
if (!existsSync(index)) continue;
|
|
1083
1377
|
const db = openDatabase(index);
|
|
1084
1378
|
try {
|
|
@@ -1141,6 +1435,7 @@ var init_cli = __esm({
|
|
|
1141
1435
|
init_database();
|
|
1142
1436
|
init_sync();
|
|
1143
1437
|
init_validate();
|
|
1438
|
+
init_init();
|
|
1144
1439
|
}
|
|
1145
1440
|
});
|
|
1146
1441
|
|
|
@@ -1161,7 +1456,7 @@ ${issues}`);
|
|
|
1161
1456
|
};
|
|
1162
1457
|
}
|
|
1163
1458
|
var EnvSchema2;
|
|
1164
|
-
var
|
|
1459
|
+
var init_config = __esm({
|
|
1165
1460
|
"../mcp/src/config.ts"() {
|
|
1166
1461
|
"use strict";
|
|
1167
1462
|
EnvSchema2 = z3.object({
|
|
@@ -1193,7 +1488,7 @@ var init_db = __esm({
|
|
|
1193
1488
|
// ../mcp/src/lib/diag.ts
|
|
1194
1489
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1195
1490
|
import { homedir as homedir2 } from "node:os";
|
|
1196
|
-
import { join as
|
|
1491
|
+
import { join as join7 } from "node:path";
|
|
1197
1492
|
function diag(msg, data) {
|
|
1198
1493
|
try {
|
|
1199
1494
|
const line = data ? `${(/* @__PURE__ */ new Date()).toISOString()} pid=${process.pid} ${msg} ${JSON.stringify(data)}
|
|
@@ -1207,8 +1502,8 @@ var DIAG_DIR, DIAG_LOG_PATH;
|
|
|
1207
1502
|
var init_diag = __esm({
|
|
1208
1503
|
"../mcp/src/lib/diag.ts"() {
|
|
1209
1504
|
"use strict";
|
|
1210
|
-
DIAG_DIR =
|
|
1211
|
-
DIAG_LOG_PATH =
|
|
1505
|
+
DIAG_DIR = join7(homedir2(), ".local", "state", "wiki-mcp");
|
|
1506
|
+
DIAG_LOG_PATH = join7(DIAG_DIR, "server.log");
|
|
1212
1507
|
try {
|
|
1213
1508
|
mkdirSync3(DIAG_DIR, { recursive: true });
|
|
1214
1509
|
} catch {
|
|
@@ -1235,176 +1530,6 @@ var init_logger = __esm({
|
|
|
1235
1530
|
}
|
|
1236
1531
|
});
|
|
1237
1532
|
|
|
1238
|
-
// ../mcp/src/vault-config.ts
|
|
1239
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
1240
|
-
import { join as join7 } from "node:path";
|
|
1241
|
-
import { parse as parse2 } from "yaml";
|
|
1242
|
-
import { z as z4 } from "zod";
|
|
1243
|
-
function isValidRegex2(pattern) {
|
|
1244
|
-
try {
|
|
1245
|
-
return Boolean(new RegExp(pattern));
|
|
1246
|
-
} catch {
|
|
1247
|
-
return false;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
function kindName2(entry) {
|
|
1251
|
-
return typeof entry === "string" ? entry : entry.name;
|
|
1252
|
-
}
|
|
1253
|
-
async function loadVaultConfig2(vaultRoot2) {
|
|
1254
|
-
const path = join7(vaultRoot2, "vault.yaml");
|
|
1255
|
-
let raw;
|
|
1256
|
-
try {
|
|
1257
|
-
raw = await readFile4(path, "utf8");
|
|
1258
|
-
} catch (err) {
|
|
1259
|
-
throw new Error(`vault.yaml not found at ${path}`, { cause: err });
|
|
1260
|
-
}
|
|
1261
|
-
const parsed = VaultConfigSchema2.safeParse(parse2(raw));
|
|
1262
|
-
if (!parsed.success) {
|
|
1263
|
-
const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
1264
|
-
throw new Error(`Invalid vault.yaml at ${path}:
|
|
1265
|
-
${issues}`);
|
|
1266
|
-
}
|
|
1267
|
-
return parsed.data;
|
|
1268
|
-
}
|
|
1269
|
-
var ScopeSchema2, KindEntrySchema2, WhenSchema2, TriggerSchema2, TriggersSchema2, VaultConfigSchema2, BUILT_IN_KINDS2;
|
|
1270
|
-
var init_vault_config = __esm({
|
|
1271
|
-
"../mcp/src/vault-config.ts"() {
|
|
1272
|
-
"use strict";
|
|
1273
|
-
ScopeSchema2 = z4.object({
|
|
1274
|
-
repo: z4.string().optional(),
|
|
1275
|
-
methodology: z4.string().optional(),
|
|
1276
|
-
status: z4.string()
|
|
1277
|
-
});
|
|
1278
|
-
KindEntrySchema2 = z4.union([
|
|
1279
|
-
z4.string(),
|
|
1280
|
-
z4.object({
|
|
1281
|
-
name: z4.string(),
|
|
1282
|
-
signal: z4.string(),
|
|
1283
|
-
where: z4.string()
|
|
1284
|
-
})
|
|
1285
|
-
]);
|
|
1286
|
-
WhenSchema2 = z4.union([
|
|
1287
|
-
z4.string(),
|
|
1288
|
-
z4.object({
|
|
1289
|
-
name: z4.enum(["newer-than"]),
|
|
1290
|
-
fresh: z4.array(z4.string().min(1)).min(1),
|
|
1291
|
-
than: z4.array(z4.string().min(1)).min(1)
|
|
1292
|
-
})
|
|
1293
|
-
]);
|
|
1294
|
-
TriggerSchema2 = z4.object({
|
|
1295
|
-
id: z4.string().min(1),
|
|
1296
|
-
on: z4.enum(["prompt", "pretool"]),
|
|
1297
|
-
enforce: z4.enum(["inject", "warn", "block"]),
|
|
1298
|
-
keywords: z4.array(z4.string().min(1)).optional(),
|
|
1299
|
-
intent: z4.array(z4.string()).optional(),
|
|
1300
|
-
tool: z4.string().optional(),
|
|
1301
|
-
args_match: z4.string().optional(),
|
|
1302
|
-
files: z4.array(z4.string().min(1)).optional(),
|
|
1303
|
-
when: WhenSchema2.optional(),
|
|
1304
|
-
text: z4.string().optional(),
|
|
1305
|
-
reason: z4.string().optional()
|
|
1306
|
-
}).superRefine((trigger, ctx) => {
|
|
1307
|
-
if (trigger.on === "prompt" && !trigger.keywords?.length && !trigger.intent?.length) {
|
|
1308
|
-
ctx.addIssue({
|
|
1309
|
-
code: "custom",
|
|
1310
|
-
message: `prompt trigger "${trigger.id}" needs keywords or intent`
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
if (trigger.on === "pretool" && trigger.tool === void 0 && trigger.args_match === void 0 && !trigger.files?.length) {
|
|
1314
|
-
ctx.addIssue({
|
|
1315
|
-
code: "custom",
|
|
1316
|
-
message: `pretool trigger "${trigger.id}" needs a tool, args_match, or files matcher`
|
|
1317
|
-
});
|
|
1318
|
-
}
|
|
1319
|
-
if (trigger.on === "prompt" && trigger.files !== void 0) {
|
|
1320
|
-
ctx.addIssue({
|
|
1321
|
-
code: "custom",
|
|
1322
|
-
message: `trigger "${trigger.id}": files applies to pretool triggers only`
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
if (trigger.enforce === "block" ? trigger.reason === void 0 : trigger.text === void 0) {
|
|
1326
|
-
ctx.addIssue({
|
|
1327
|
-
code: "custom",
|
|
1328
|
-
message: trigger.enforce === "block" ? `block trigger "${trigger.id}" needs a reason` : `${trigger.enforce} trigger "${trigger.id}" needs a text`
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
const patterns = [...trigger.intent ?? []];
|
|
1332
|
-
if (trigger.args_match !== void 0) patterns.push(trigger.args_match);
|
|
1333
|
-
for (const pattern of patterns) {
|
|
1334
|
-
if (!isValidRegex2(pattern)) {
|
|
1335
|
-
ctx.addIssue({
|
|
1336
|
-
code: "custom",
|
|
1337
|
-
message: `trigger "${trigger.id}" has an invalid regex: ${pattern}`
|
|
1338
|
-
});
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
});
|
|
1342
|
-
TriggersSchema2 = z4.record(z4.string(), z4.array(TriggerSchema2));
|
|
1343
|
-
VaultConfigSchema2 = z4.object({
|
|
1344
|
-
scopes: z4.record(z4.string(), ScopeSchema2),
|
|
1345
|
-
kinds: z4.array(KindEntrySchema2),
|
|
1346
|
-
statuses: z4.array(z4.string()),
|
|
1347
|
-
methodologies: z4.array(z4.string()),
|
|
1348
|
-
tags: z4.object({
|
|
1349
|
-
canonical: z4.array(z4.string()),
|
|
1350
|
-
aliases: z4.record(z4.string(), z4.string())
|
|
1351
|
-
}),
|
|
1352
|
-
authoring_rules: z4.string().optional(),
|
|
1353
|
-
authoring_rules_extra: z4.string().optional(),
|
|
1354
|
-
sync_protocol: z4.string().optional(),
|
|
1355
|
-
sync_protocol_extra: z4.string().optional(),
|
|
1356
|
-
triggers: TriggersSchema2.optional(),
|
|
1357
|
-
triggers_extra: TriggersSchema2.optional()
|
|
1358
|
-
}).superRefine((config, ctx) => {
|
|
1359
|
-
for (const [name, scope] of Object.entries(config.scopes)) {
|
|
1360
|
-
if (scope.methodology !== void 0 && !config.methodologies.includes(scope.methodology)) {
|
|
1361
|
-
ctx.addIssue({
|
|
1362
|
-
code: "custom",
|
|
1363
|
-
path: ["scopes", name, "methodology"],
|
|
1364
|
-
message: `"${scope.methodology}" is not in the methodologies list`
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
if (config.triggers?._all !== void 0) {
|
|
1369
|
-
ctx.addIssue({
|
|
1370
|
-
code: "custom",
|
|
1371
|
-
path: ["triggers", "_all"],
|
|
1372
|
-
message: '"_all" is reserved for triggers_extra'
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
for (const field of ["triggers", "triggers_extra"]) {
|
|
1376
|
-
for (const [scope, list] of Object.entries(config[field] ?? {})) {
|
|
1377
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1378
|
-
for (const trigger of list) {
|
|
1379
|
-
if (seen.has(trigger.id)) {
|
|
1380
|
-
ctx.addIssue({
|
|
1381
|
-
code: "custom",
|
|
1382
|
-
path: [field, scope],
|
|
1383
|
-
message: `duplicate trigger id "${trigger.id}"`
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
seen.add(trigger.id);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
});
|
|
1391
|
-
BUILT_IN_KINDS2 = /* @__PURE__ */ new Set([
|
|
1392
|
-
"project",
|
|
1393
|
-
"spec",
|
|
1394
|
-
"adr",
|
|
1395
|
-
"plan",
|
|
1396
|
-
"story",
|
|
1397
|
-
"ops",
|
|
1398
|
-
"topic",
|
|
1399
|
-
"article",
|
|
1400
|
-
"src",
|
|
1401
|
-
"note",
|
|
1402
|
-
"artifact",
|
|
1403
|
-
"prompt"
|
|
1404
|
-
]);
|
|
1405
|
-
}
|
|
1406
|
-
});
|
|
1407
|
-
|
|
1408
1533
|
// ../mcp/src/resources/authoring.ts
|
|
1409
1534
|
function buildAuthoringRules(config) {
|
|
1410
1535
|
const parts = [(config.authoring_rules ?? DEFAULT_AUTHORING_RULES).trim()];
|
|
@@ -1419,13 +1544,13 @@ function buildSyncProtocol(config) {
|
|
|
1419
1544
|
function buildKindSelector(kinds) {
|
|
1420
1545
|
const lines = ["## Kind selector", "", "| Signal | Kind | Where |", "|---|---|---|"];
|
|
1421
1546
|
for (const entry of kinds) {
|
|
1422
|
-
const name =
|
|
1547
|
+
const name = kindName(entry);
|
|
1423
1548
|
const pedagogy = typeof entry === "string" ? KIND_PEDAGOGY.get(entry) : entry;
|
|
1424
1549
|
const signal = pedagogy?.signal ?? "\u2014";
|
|
1425
1550
|
const where = pedagogy?.where ?? "\u2014";
|
|
1426
1551
|
lines.push(`| ${signal} | **${name}** | ${where} |`);
|
|
1427
1552
|
}
|
|
1428
|
-
const names = kinds.map(
|
|
1553
|
+
const names = kinds.map(kindName);
|
|
1429
1554
|
const hasNote = names.includes("note");
|
|
1430
1555
|
const hasAdrAndSpec = names.includes("adr") && names.includes("spec");
|
|
1431
1556
|
if (hasNote || hasAdrAndSpec) {
|
|
@@ -1448,7 +1573,7 @@ function buildVocabulary(config) {
|
|
|
1448
1573
|
const lines = [
|
|
1449
1574
|
"## Controlled vocabulary",
|
|
1450
1575
|
"",
|
|
1451
|
-
`**Kinds:** ${config.kinds.map(
|
|
1576
|
+
`**Kinds:** ${config.kinds.map(kindName).join(", ")}`,
|
|
1452
1577
|
buildStatusLine(config.statuses),
|
|
1453
1578
|
`**Methodologies:** ${config.methodologies.join(", ")}`,
|
|
1454
1579
|
`**Canonical tags:** ${config.tags.canonical.join(", ")}`
|
|
@@ -1624,7 +1749,7 @@ import { join as join8 } from "node:path";
|
|
|
1624
1749
|
function customTemplates(config) {
|
|
1625
1750
|
const specs = [];
|
|
1626
1751
|
for (const entry of config.kinds) {
|
|
1627
|
-
if (typeof entry === "string" ||
|
|
1752
|
+
if (typeof entry === "string" || BUILT_IN_KINDS.has(entry.name)) continue;
|
|
1628
1753
|
specs.push({
|
|
1629
1754
|
uri: `wiki://template/${entry.name}`,
|
|
1630
1755
|
name: entry.name.charAt(0).toUpperCase() + entry.name.slice(1),
|
|
@@ -1825,7 +1950,7 @@ var init_toolResponse = __esm({
|
|
|
1825
1950
|
});
|
|
1826
1951
|
|
|
1827
1952
|
// ../mcp/src/tools/search.ts
|
|
1828
|
-
import { z as
|
|
1953
|
+
import { z as z4 } from "zod";
|
|
1829
1954
|
function search(deps, input) {
|
|
1830
1955
|
const ftsQuery = sanitizeFtsQuery(input.query);
|
|
1831
1956
|
if (!ftsQuery) return { results: [] };
|
|
@@ -1872,15 +1997,15 @@ var init_search = __esm({
|
|
|
1872
1997
|
"use strict";
|
|
1873
1998
|
init_fts();
|
|
1874
1999
|
init_toolResponse();
|
|
1875
|
-
SearchInputSchema =
|
|
1876
|
-
query:
|
|
2000
|
+
SearchInputSchema = z4.object({
|
|
2001
|
+
query: z4.string().min(1).describe(
|
|
1877
2002
|
"Natural-language search query. Matched against title, summary, and body via SQLite FTS5."
|
|
1878
2003
|
),
|
|
1879
|
-
scope:
|
|
1880
|
-
kind:
|
|
2004
|
+
scope: z4.string().optional().describe('Optional: restrict to a project scope (e.g. "ontology", "sotto").'),
|
|
2005
|
+
kind: z4.string().optional().describe(
|
|
1881
2006
|
"Optional: restrict to a kind. Project kinds: spec, adr, plan, ops. Research kinds: article, src. Misc: note."
|
|
1882
2007
|
),
|
|
1883
|
-
limit:
|
|
2008
|
+
limit: z4.number().int().min(1).max(50).default(5).describe("Maximum number of ranked results to return. Default 5.")
|
|
1884
2009
|
});
|
|
1885
2010
|
FTS_RANK = "bm25(pages_fts, 10.0, 5.0, 1.0)";
|
|
1886
2011
|
}
|
|
@@ -1889,7 +2014,7 @@ var init_search = __esm({
|
|
|
1889
2014
|
// ../mcp/src/tools/prime.ts
|
|
1890
2015
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
1891
2016
|
import { basename as basename3, join as join9 } from "node:path";
|
|
1892
|
-
import { z as
|
|
2017
|
+
import { z as z5 } from "zod";
|
|
1893
2018
|
function pathSlug(p) {
|
|
1894
2019
|
return basename3(p).replace(/\.md$/, "");
|
|
1895
2020
|
}
|
|
@@ -2032,7 +2157,7 @@ function renderMarkdown(d, config, task) {
|
|
|
2032
2157
|
lines.push(countEntries.map(([k, n]) => `${k}: ${n}`).join(" | "));
|
|
2033
2158
|
}
|
|
2034
2159
|
lines.push("", "## Vocabulary");
|
|
2035
|
-
lines.push(`kinds: ${config.kinds.map(
|
|
2160
|
+
lines.push(`kinds: ${config.kinds.map(kindName).join(", ")}`);
|
|
2036
2161
|
lines.push(`statuses: ${config.statuses.join(", ")}`);
|
|
2037
2162
|
lines.push(`tags: ${config.tags.canonical.join(", ")}`);
|
|
2038
2163
|
if (d.top_tags.length > 0) {
|
|
@@ -2093,14 +2218,14 @@ var init_prime = __esm({
|
|
|
2093
2218
|
"../mcp/src/tools/prime.ts"() {
|
|
2094
2219
|
"use strict";
|
|
2095
2220
|
init_database();
|
|
2221
|
+
init_vault_config();
|
|
2096
2222
|
init_frontmatter2();
|
|
2097
2223
|
init_fts();
|
|
2098
2224
|
init_toolResponse();
|
|
2099
|
-
init_vault_config();
|
|
2100
2225
|
init_search();
|
|
2101
|
-
PrimeInputSchema =
|
|
2102
|
-
scope:
|
|
2103
|
-
task:
|
|
2226
|
+
PrimeInputSchema = z5.object({
|
|
2227
|
+
scope: z5.string().min(1).describe("Project scope to prime (matches projects/{scope}/ in the vault)."),
|
|
2228
|
+
task: z5.string().optional().describe("Optional task description; surfaces top-3 tsvector-ranked relevant pages.")
|
|
2104
2229
|
});
|
|
2105
2230
|
}
|
|
2106
2231
|
});
|
|
@@ -2152,7 +2277,7 @@ async function startMcpServer() {
|
|
|
2152
2277
|
diag("main entered");
|
|
2153
2278
|
const config = loadConfig();
|
|
2154
2279
|
diag("config loaded", { vault: config.wikiVault, level: config.logLevel });
|
|
2155
|
-
const vaultConfig = await
|
|
2280
|
+
const vaultConfig = await loadVaultConfig(config.wikiVault);
|
|
2156
2281
|
diag("vault config loaded", {
|
|
2157
2282
|
scopes: Object.keys(vaultConfig.scopes).length,
|
|
2158
2283
|
kinds: vaultConfig.kinds.length,
|
|
@@ -2195,12 +2320,12 @@ async function startMcpServer() {
|
|
|
2195
2320
|
var init_start = __esm({
|
|
2196
2321
|
"../mcp/src/start.ts"() {
|
|
2197
2322
|
"use strict";
|
|
2198
|
-
|
|
2323
|
+
init_vault_config();
|
|
2324
|
+
init_config();
|
|
2199
2325
|
init_db();
|
|
2200
2326
|
init_diag();
|
|
2201
2327
|
init_logger();
|
|
2202
2328
|
init_server();
|
|
2203
|
-
init_vault_config();
|
|
2204
2329
|
}
|
|
2205
2330
|
});
|
|
2206
2331
|
|
|
@@ -2228,11 +2353,11 @@ __export(hook_exports, {
|
|
|
2228
2353
|
});
|
|
2229
2354
|
import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync, rmSync as rmSync2, statSync, writeFileSync } from "node:fs";
|
|
2230
2355
|
import { homedir as homedir3 } from "node:os";
|
|
2231
|
-
import { join as join10, resolve as
|
|
2356
|
+
import { join as join10, resolve as resolve3, sep as sep2 } from "node:path";
|
|
2232
2357
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
2233
2358
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
2234
2359
|
import { parse as parseYaml3 } from "yaml";
|
|
2235
|
-
import { z as
|
|
2360
|
+
import { z as z6 } from "zod";
|
|
2236
2361
|
function eventFields(raw) {
|
|
2237
2362
|
let data;
|
|
2238
2363
|
try {
|
|
@@ -2258,7 +2383,7 @@ function kiroIdePromptEvent(now = Date.now()) {
|
|
|
2258
2383
|
}
|
|
2259
2384
|
function loadTriggerFile(path) {
|
|
2260
2385
|
try {
|
|
2261
|
-
const result =
|
|
2386
|
+
const result = z6.array(TriggerSchema).safeParse(parseYaml3(readFileSync(path, "utf8")));
|
|
2262
2387
|
return result.success ? result.data : null;
|
|
2263
2388
|
} catch {
|
|
2264
2389
|
return null;
|
|
@@ -2506,10 +2631,10 @@ function patchPaths(toolInput) {
|
|
|
2506
2631
|
return paths;
|
|
2507
2632
|
}
|
|
2508
2633
|
function vaultPathTouched(toolInput, vaultRoot2, cwd) {
|
|
2509
|
-
const root =
|
|
2634
|
+
const root = resolve3(vaultRoot2);
|
|
2510
2635
|
const candidates = [...pathCandidates(toolInput, cwd), ...patchPaths(toolInput)];
|
|
2511
2636
|
return candidates.some((candidate) => {
|
|
2512
|
-
const absolute =
|
|
2637
|
+
const absolute = resolve3(cwd ?? ".", candidate);
|
|
2513
2638
|
return absolute === root || absolute.startsWith(`${root}/`);
|
|
2514
2639
|
});
|
|
2515
2640
|
}
|
|
@@ -2739,7 +2864,7 @@ var init_hook = __esm({
|
|
|
2739
2864
|
"../cli/src/hook.ts"() {
|
|
2740
2865
|
"use strict";
|
|
2741
2866
|
init_database();
|
|
2742
|
-
|
|
2867
|
+
init_vault_config();
|
|
2743
2868
|
init_frontmatter();
|
|
2744
2869
|
init_sync();
|
|
2745
2870
|
init_validate();
|
|
@@ -2762,6 +2887,7 @@ process.on("warning", (warning) => {
|
|
|
2762
2887
|
var USAGE = `usage: kmd <command> [options]
|
|
2763
2888
|
|
|
2764
2889
|
commands:
|
|
2890
|
+
init [<dir>] [-y] scaffold a fresh vault (no dir: current directory \u2014 TTY prompt, or -y)
|
|
2765
2891
|
sync vault \u2192 index sync (runs validate first)
|
|
2766
2892
|
validate [<path>] deterministic vault checker (default: $WIKI_VAULT)
|
|
2767
2893
|
mcp [<vault-root>] start the stdio MCP server (default: $WIKI_VAULT)
|
|
@@ -2780,7 +2906,8 @@ var { positionals, values } = parseArgs3({
|
|
|
2780
2906
|
strict: false,
|
|
2781
2907
|
options: {
|
|
2782
2908
|
version: { type: "boolean", short: "v" },
|
|
2783
|
-
help: { type: "boolean", short: "h" }
|
|
2909
|
+
help: { type: "boolean", short: "h" },
|
|
2910
|
+
yes: { type: "boolean", short: "y" }
|
|
2784
2911
|
}
|
|
2785
2912
|
});
|
|
2786
2913
|
var command = values.version ? "--version" : values.help ? "--help" : positionals[0];
|
|
@@ -2792,6 +2919,11 @@ function applyVaultRoot(positionalIndex) {
|
|
|
2792
2919
|
}
|
|
2793
2920
|
async function run() {
|
|
2794
2921
|
switch (command) {
|
|
2922
|
+
case "init": {
|
|
2923
|
+
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
2924
|
+
await runInit2(positionals[1], Boolean(values.yes));
|
|
2925
|
+
break;
|
|
2926
|
+
}
|
|
2795
2927
|
case "sync": {
|
|
2796
2928
|
const { runSyncCommand: runSyncCommand2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
2797
2929
|
await runSyncCommand2();
|
|
@@ -2838,9 +2970,11 @@ async function run() {
|
|
|
2838
2970
|
} else if (sub === "posttool") {
|
|
2839
2971
|
const { runHookPosttool: runHookPosttool2 } = await Promise.resolve().then(() => (init_hook(), hook_exports));
|
|
2840
2972
|
await runHookPosttool2();
|
|
2973
|
+
} else if (sub) {
|
|
2974
|
+
console.error(`kmd hook: unknown event: ${sub}`);
|
|
2841
2975
|
} else {
|
|
2842
2976
|
console.error(
|
|
2843
|
-
|
|
2977
|
+
"usage: kmd hook <prompt|pretool|posttool> [<vault-root>] [--scope <scope>] [--harness <claude|kiro-ide>]"
|
|
2844
2978
|
);
|
|
2845
2979
|
process.exit(2);
|
|
2846
2980
|
}
|
|
@@ -2864,6 +2998,11 @@ async function run() {
|
|
|
2864
2998
|
break;
|
|
2865
2999
|
}
|
|
2866
3000
|
default: {
|
|
3001
|
+
const tail = positionals[1];
|
|
3002
|
+
if (tail === "prompt" || tail === "pretool" || tail === "posttool") {
|
|
3003
|
+
console.error(`kmd: unknown command: ${command}`);
|
|
3004
|
+
break;
|
|
3005
|
+
}
|
|
2867
3006
|
console.error(`unknown command: ${command}
|
|
2868
3007
|
|
|
2869
3008
|
${USAGE}`);
|