@bartolli/kmd 0.12.0 → 0.12.1
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/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/kmd.mjs +3 -1
- package/dist/kmd.mjs.map +2 -2
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Angel Bartolli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# kmd — knowledge-markdown
|
|
2
|
+
|
|
3
|
+
kmd is a knowledge system for AI agents, built on [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) primitives: plain markdown, YAML frontmatter, a controlled vocabulary. What it adds is timing. The right context reaches the agent at the right moment, and costs nothing the rest of the time.
|
|
4
|
+
|
|
5
|
+
A cold session gets its orientation in one `prime` call: current focus, invariants, what to read. `search` returns ranked candidates, never page dumps. Hooks steer the agent at the exact event where a rule applies: a reminder on the prompt that needs it, a gate on the tool call that would break it, validation on the write that touched the vault. Rules that would otherwise sit in instruction files, paying tokens on every request, fire only when they matter.
|
|
6
|
+
|
|
7
|
+
The vault stays plain markdown in git, Obsidian-compatible. Works with Claude Code, Codex, and Kiro. No database to run.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @bartolli/kmd --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Two setups. Pick one, or use both; they compose.
|
|
16
|
+
|
|
17
|
+
**A global vault.** One personal wiki serving all your projects:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @bartolli/kmd init ~/wiki-vault --set-default
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`--set-default` records the vault in `~/.kmd/config.yaml`. Every `kmd` command and MCP server on this machine now finds it without configuration.
|
|
24
|
+
|
|
25
|
+
**A project vault.** The repo carries its own knowledge:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my-repo
|
|
29
|
+
npx @bartolli/kmd init --local
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This scaffolds `my-repo/vault/` plus a `.kmd/` state home. Every command run inside the repo resolves this vault automatically, and it wins over the global default. Nothing else to configure.
|
|
33
|
+
|
|
34
|
+
Then, either way:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
kmd validate # deterministic checker, no LLM
|
|
38
|
+
kmd sync # vault → SQLite index
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Where things live
|
|
42
|
+
|
|
43
|
+
| Thing | Where | Nature |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Vault | wherever you put it (`~/wiki-vault`, `<repo>/vault/`) | yours: markdown + `vault.yaml`, git-tracked, canonical |
|
|
46
|
+
| Index | `~/.kmd/db/{vault-key}/index.db`, or `<repo>/.kmd/db/` for project vaults | disposable: `kmd db reset` deletes it, `kmd sync` rebuilds it |
|
|
47
|
+
| Machine config | `~/.kmd/config.yaml` | `default_vault`, written by `kmd init --set-default` or `kmd config set` |
|
|
48
|
+
| Project config | `<repo>/.kmd/config.yaml` | committed, repo-relative paths; `.kmd/config.local.yaml` is the personal, gitignored override |
|
|
49
|
+
| Hook state | `~/.kmd/state/` or `<repo>/.kmd/state/` | session dedup markers; survives `kmd db reset` |
|
|
50
|
+
|
|
51
|
+
A project vault's index and state live in the repo. Delete the repo and they go with it.
|
|
52
|
+
|
|
53
|
+
## How kmd finds your vault
|
|
54
|
+
|
|
55
|
+
One resolution order, used by the CLI, the MCP server, and the hooks. First hit wins:
|
|
56
|
+
|
|
57
|
+
1. An explicit path argument: pins that vault, beats everything.
|
|
58
|
+
2. The project: nearest ancestor with `.kmd/config.local.yaml`, `.kmd/config.yaml`, `vault/vault.yaml`, or a `vault.yaml` with a `.kmd/` directory beside it.
|
|
59
|
+
3. `--default-root <path>`: a configured default the project may beat.
|
|
60
|
+
4. `$WIKI_VAULT`.
|
|
61
|
+
5. `default_vault` from `~/.kmd/config.yaml`.
|
|
62
|
+
|
|
63
|
+
The project signal is the working directory: the shell's for CLI commands, the session's for hooks, `KMD_PROJECT_DIR` for MCP servers. When something resolves to the wrong vault, run `kmd config`. It prints the vault and which rank won.
|
|
64
|
+
|
|
65
|
+
Teams commit `.kmd/config.yaml` with a repo-relative `vault:` path or a `${VAR}` expansion. Absolute paths belong in the gitignored `config.local.yaml`.
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
kmd init [<dir>] [-y] [--set-default] scaffold a vault; --set-default records it as the machine default
|
|
71
|
+
kmd init --local [-y] project vault at <git-root>/vault/ with a .kmd/ state home
|
|
72
|
+
kmd validate [<path>] deterministic vault checker
|
|
73
|
+
kmd sync [<vault-root>] vault → SQLite index (validates first, aborts on errors)
|
|
74
|
+
kmd mcp [<vault-root>] [--default-root <path>]
|
|
75
|
+
stdio MCP server
|
|
76
|
+
kmd config [<vault-root>] print resolved vault, index path, winning rank
|
|
77
|
+
kmd config <set|get|unset> default_vault [<path>]
|
|
78
|
+
kmd db reset [<vault-root>] delete the vault's index
|
|
79
|
+
kmd hook <prompt|pretool|posttool|stop|session-start> [--default-root <path>] [--scope <s>] [--harness <h>] [--explain|--dry-run]
|
|
80
|
+
harness gate engine (see Hooks)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`kmd --help` is canonical for flags; this list names the surface.
|
|
84
|
+
|
|
85
|
+
## MCP server
|
|
86
|
+
|
|
87
|
+
Two tools, deliberately few:
|
|
88
|
+
|
|
89
|
+
- **`prime(scope, task?)`** returns an orientation briefing: identity, primer, active decision records, current plan, vocabulary, most-linked pages, recent activity, cross-scope links. Pass `task` and it adds the top-ranked relevant pages, weighted so a page naming the concept in its title outranks pages that merely mention it.
|
|
90
|
+
- **`search(query, scope?, kind?, limit?)`** returns ranked candidates `{path, title, kind, summary, score}`. Never page bodies; the agent opens files itself.
|
|
91
|
+
|
|
92
|
+
Templates are served as MCP resources at `wiki://template/{domain}/{kind}`, and the authoring guide at `wiki://authoring`.
|
|
93
|
+
|
|
94
|
+
Registration:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"wiki": {
|
|
100
|
+
"command": "npx",
|
|
101
|
+
"args": ["-y", "@bartolli/kmd", "mcp", "--default-root", "/absolute/path/to/vault"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`--default-root` keeps the resolution order live: a project that carries its own vault wins automatically. Passing the path as a bare positional instead pins that one vault unconditionally. Use it only when that is what you want.
|
|
108
|
+
|
|
109
|
+
## Skills
|
|
110
|
+
|
|
111
|
+
The `wiki-sdd` plugin ships nine skills: a complete spec-driven development loop that reads and writes the vault. Each is a slash command (`$name` on Codex):
|
|
112
|
+
|
|
113
|
+
| Skill | What it does |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `/wiki` | wires a project to the wiki; local-vs-global vault setup; the hub for everything below |
|
|
116
|
+
| `/grill-with-docs` | interview that sharpens intent and scaffolds a scope: index, primer, glossary, lazy ADRs |
|
|
117
|
+
| `/to-prd` | turns the working conversation into a thin plan plus user stories with Gherkin scenarios |
|
|
118
|
+
| `/triage` | moves stories through `needs-triage` → `ready-for-agent` / `ready-for-human` / `wontfix` |
|
|
119
|
+
| `/to-issues` | slices stories into vertical tracer bullets; mirrors to GitHub or GitLab when configured |
|
|
120
|
+
| `/tdd` | implements one `ready-for-agent` slice, red-green-refactor, and ticks its checkbox |
|
|
121
|
+
| `/retro` | two questions before every primer rewrite; converts the answers into wiki artifacts |
|
|
122
|
+
| `/to-triggers` | turns a prose rule into a tested vault trigger: you own the intent, it owns the regex |
|
|
123
|
+
| `/signal-dense` | canonical-vocabulary register for long agentic threads |
|
|
124
|
+
|
|
125
|
+
The working loop: `/grill-with-docs` → `/to-prd` → `/triage` → `/to-issues` → `/tdd` per slice → `/retro` before the session closes. `/wiki` is the on-ramp; `/to-triggers` joins whenever a rule proves it should be a gate. The same skills render for all three harnesses below.
|
|
126
|
+
|
|
127
|
+
## Harness integration
|
|
128
|
+
|
|
129
|
+
### Claude Code
|
|
130
|
+
|
|
131
|
+
The repo is a plugin marketplace. The [`wiki-sdd`](plugins/claude/wiki-sdd/README.md) plugin wires everything: the MCP server, all five hook events, and the [nine skills](#skills).
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
claude plugin marketplace add bartolli/kmd
|
|
135
|
+
claude plugin install wiki-sdd@kmd
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Project vaults resolve automatically: the plugin passes the project directory to the server. No per-project files.
|
|
139
|
+
|
|
140
|
+
### Codex
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
codex plugin marketplace add bartolli/kmd
|
|
144
|
+
codex plugin add wiki-sdd@kmd
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Hooks are project-aware out of the box. The MCP server needs one line of help, because Codex gives plugin MCP servers no workspace signal ([openai/codex#37903](https://github.com/openai/codex/issues/37903)). Add to your shell profile:
|
|
148
|
+
|
|
149
|
+
```zsh
|
|
150
|
+
codex() { KMD_PROJECT_DIR="$PWD" command codex "$@"; }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Now every Codex launch carries its project directory, and `prime`/`search` follow project vaults exactly as on Claude Code. Details: [codex adapter README](plugins/codex/wiki-sdd/README.md).
|
|
154
|
+
|
|
155
|
+
### Kiro
|
|
156
|
+
|
|
157
|
+
Kiro consumes the same skills as [Agent Skills](plugins/kiro/wiki-sdd/README.md). Copy the skill folders into `~/.kiro/skills/` or `.kiro/skills/`, and register the MCP server in `.kiro/settings/mcp.json` from the bundled template. Both Kiro seats (IDE and CLI) read the same layout.
|
|
158
|
+
|
|
159
|
+
### Manual wiring
|
|
160
|
+
|
|
161
|
+
No plugin, any harness that supports command hooks. Install globally first. Hooks spawn per event, and a global install keeps the spawn fast:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm i -g @bartolli/kmd
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"hooks": {
|
|
170
|
+
"UserPromptSubmit": [
|
|
171
|
+
{ "hooks": [{ "type": "command",
|
|
172
|
+
"command": "kmd hook prompt --default-root /absolute/path/to/vault" }] }
|
|
173
|
+
],
|
|
174
|
+
"PreToolUse": [
|
|
175
|
+
{ "hooks": [{ "type": "command",
|
|
176
|
+
"command": "kmd hook pretool --default-root /absolute/path/to/vault --harness claude" }] }
|
|
177
|
+
],
|
|
178
|
+
"PostToolUse": [
|
|
179
|
+
{ "matcher": "Write|Edit|Bash",
|
|
180
|
+
"hooks": [{ "type": "command",
|
|
181
|
+
"command": "kmd hook posttool --default-root /absolute/path/to/vault --harness claude" }] }
|
|
182
|
+
],
|
|
183
|
+
"Stop": [
|
|
184
|
+
{ "hooks": [{ "type": "command",
|
|
185
|
+
"command": "kmd hook stop --default-root /absolute/path/to/vault" }] }
|
|
186
|
+
],
|
|
187
|
+
"SessionStart": [
|
|
188
|
+
{ "hooks": [{ "type": "command",
|
|
189
|
+
"command": "kmd hook session-start --default-root /absolute/path/to/vault" }] }
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`--harness claude` emits Claude Code's decision JSON on tool events. Without it the output is a neutral JSON contract. Kiro IDE wires the prompt event with `--harness kiro-ide`.
|
|
196
|
+
|
|
197
|
+
## Hooks
|
|
198
|
+
|
|
199
|
+
Rules in instruction files cost tokens on every request. A trigger costs nothing until the moment it applies, then delivers the whole rule. Triggers are declared in `vault.yaml`; `kmd hook` evaluates them when the harness fires an event.
|
|
200
|
+
|
|
201
|
+
Five events:
|
|
202
|
+
|
|
203
|
+
- **`prompt`**: matching triggers inject one context line each, once per session.
|
|
204
|
+
- **`pretool`**: gates before a tool runs: inject, warn, or deny with a reason the agent reads.
|
|
205
|
+
- **`posttool`**: after a write inside the vault, `kmd validate` runs; findings return to the agent and the index holds until they are fixed. Clean writes sync silently.
|
|
206
|
+
- **`stop`**: a session ending with validation errors is sent back once with the fix list.
|
|
207
|
+
- **`session-start`**: a session opening inside a scope's repo gets one orientation line: prime first.
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
triggers_extra:
|
|
211
|
+
my-app:
|
|
212
|
+
- id: retro-before-tag
|
|
213
|
+
on: pretool
|
|
214
|
+
enforce: block
|
|
215
|
+
tool: Bash
|
|
216
|
+
args_match: "\\bgit tag\\b"
|
|
217
|
+
when: # precondition: the gate fires only while it is unmet
|
|
218
|
+
name: newer-than
|
|
219
|
+
fresh: ["notes/my-app-retro-*.md"]
|
|
220
|
+
than: ["projects/my-app/ops/release-*.md"]
|
|
221
|
+
reason: "Retro gate: run the retro before tagging."
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Prompt triggers match `keywords` with stemming on word boundaries; `intent` regexes are the escape hatch. Pretool matchers AND-compose: `tool` name, `args_match` regex, `files` globs. `when` predicates read your vault's files at call time. The example denies tagging until a retro note postdates the last release note.
|
|
225
|
+
|
|
226
|
+
You don't write this YAML by hand. The `/to-triggers` skill interviews your intent, authors the keyword and regex mechanics itself, proves fire and near-miss behavior with a dry run, and writes `vault.yaml` only after validation passes. You own the rule; it owns the regex.
|
|
227
|
+
|
|
228
|
+
Declare `repo:` on a scope and the engine resolves the active scope from the session's working directory:
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
scopes:
|
|
232
|
+
my-app:
|
|
233
|
+
status: active
|
|
234
|
+
repo: ~/Projects/my-app
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Everything fails open. A broken config means one stderr line and no gate work: never a blocked prompt, never a denied unrelated call. Test triggers by hand with `kmd hook prompt --explain` (a read-only trace; never wire probe flags into hook registrations).
|
|
238
|
+
|
|
239
|
+
If other Node tooling in your agent loop prints `ExperimentalWarning` noise, set `NODE_OPTIONS="--disable-warning=ExperimentalWarning"` in the harness env.
|
|
240
|
+
|
|
241
|
+
## The vault
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
vault/
|
|
245
|
+
├── vault.yaml # controlled vocabulary, the contract
|
|
246
|
+
├── templates/ # frontmatter templates, served as MCP resources
|
|
247
|
+
├── projects/{scope}/ # specs, ADRs, plans, stories
|
|
248
|
+
├── research/{topic}/ # articles, sources
|
|
249
|
+
└── notes/ # low-ceremony capture
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Every page carries YAML frontmatter validated against `vault.yaml`:
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
# projects/my-app/adr/adr-sqlite-index.md
|
|
256
|
+
---
|
|
257
|
+
title: "SQLite for the index"
|
|
258
|
+
kind: adr
|
|
259
|
+
status: active
|
|
260
|
+
tags: [storage]
|
|
261
|
+
created: "2025-03-15"
|
|
262
|
+
updated: 2025-06-01
|
|
263
|
+
---
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`kind` selects the template, `status` tracks lifecycle, and every value must appear in `vault.yaml` or validation fails. Loading is fail-loud: an invalid `vault.yaml` stops the server and blocks sync rather than serving drift.
|
|
267
|
+
|
|
268
|
+
`kmd validate` runs seventeen deterministic rules, no LLM involved. Among them: `dangling-link` (every `[[wikilink]]` resolves), `ambiguous-link` (a bare `[[name]]` owned by two files must disambiguate), `supersession-reciprocal` (an ADR superseding another requires the back-pointer), `path-authority` (the path, not frontmatter, decides scope and topic), and `tag-alias` (aliases normalize to canonical tags). Sync refuses to index a vault with errors.
|
|
269
|
+
|
|
270
|
+
`vault.yaml` also carries the served pedagogy: the authoring rules agents read at `wiki://authoring`, custom kinds with their own templates, your methodologies, and the trigger declarations. The full reference with every field and customization pattern: [docs/vault-config.md](docs/vault-config.md). A complete annotated example: [`vault.yaml.example`](vault.yaml.example).
|
|
271
|
+
|
|
272
|
+
## kmd vs OKF
|
|
273
|
+
|
|
274
|
+
Same primitives as [Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog/tree/main/okf) (markdown, YAML frontmatter, a directory tree), but opinionated where OKF is minimal:
|
|
275
|
+
|
|
276
|
+
| | OKF | kmd |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| Vocabulary | open: producer picks `type` values | controlled: `vault.yaml` defines kinds, scopes, statuses, tags; `kmd validate` enforces |
|
|
279
|
+
| Structure | flat: organize however | three domains: `projects/` · `research/` · `notes/` |
|
|
280
|
+
| Validation | none: format spec only | seventeen deterministic rules, LLM-free; gates sync |
|
|
281
|
+
| Cross-refs | bundle-relative `/path.md` | `[[wikilinks]]`: Obsidian-native, rename-safe, no dangling links |
|
|
282
|
+
| Agent surface | none | two MCP tools (`prime`, `search`) + template resources |
|
|
283
|
+
| Guardrails | none | prompt reminders and tool gates from `vault.yaml` |
|
|
284
|
+
| Infrastructure | n/a | `node:sqlite` FTS5, zero external services |
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
Node.js 22+ (`node:sqlite`) and pnpm 11+.
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
pnpm install
|
|
292
|
+
pnpm -r run typecheck && pnpm -r run test && pnpm lint
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The plugin adapters under `plugins/{claude,codex,kiro}` are build output. Edit the shared source in `plugins/src/wiki-sdd/`, then render and verify:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pnpm --filter @llm-wiki/render render && pnpm --filter @llm-wiki/render check
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
A hand-edited adapter copy diverges silently until the next render overwrites it. `check` asserts every copy matches the rendered output.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT
|
package/dist/kmd.mjs
CHANGED
|
@@ -3031,7 +3031,9 @@ function pathCandidates(toolInput, cwd) {
|
|
|
3031
3031
|
return candidates;
|
|
3032
3032
|
}
|
|
3033
3033
|
function pretoolStage(trigger, toolName, toolInput, cwd) {
|
|
3034
|
-
if (trigger.tool !== void 0 && trigger.tool !== toolName)
|
|
3034
|
+
if (trigger.tool !== void 0 && trigger.tool.toLowerCase() !== toolName.toLowerCase()) {
|
|
3035
|
+
return "tool";
|
|
3036
|
+
}
|
|
3035
3037
|
if (trigger.args_match !== void 0) {
|
|
3036
3038
|
const serialized = JSON.stringify(toolInput ?? {});
|
|
3037
3039
|
if (!new RegExp(trigger.args_match).test(serialized)) return "args";
|