The knowledge base is currently a strictly flat, two-level taxonomy — kb/<section>/<slug>.md — with no way to nest.
Current behavior (why nesting is impossible today)
KbStore.write() runs both section and slug through normSeg():
function normSeg(s: string): string {
return String(s || "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
}
[^a-z0-9]+ -> "-" collapses any / to a hyphen, so section: "runbooks/infra" silently becomes the flat section runbooks-infra. rel_path is path.posix.join("kb", section, slug + ".md") and sections() returns a flat list — one folder level, full stop.
The problem (motivation)
A single section grows unwieldy fast. In one workspace the runbooks section already holds 15 pages mixing infra / migration / security / support (vs. 1 each for the other sections). There is no way to group runbooks/infra/*, runbooks/migration/*, runbooks/support/* — they all live as siblings in one flat runbooks/ bucket, which hurts both the console tree and human/git browsing of the on-disk mirror.
Proposal
Allow hierarchical sections (arbitrary, bounded depth):
- Path-aware section — permit
/ in section and normalize per segment (each segment still normSeg-clean), so runbooks/infra → kb/runbooks/infra/<slug>.md. Reject .. and leading/trailing / (the on-disk mirror already guards containment via contained() — keep that as the security backstop against traversal).
sections() returns a tree (or the console derives one by splitting on /) so the KB view can render nested folders.
- Search
section filter becomes a prefix match (section = "runbooks" still returns everything under it; section = "runbooks/support" scopes to that subtree). FTS/rel_path/revisions are unaffected — only the section segmenting changes.
Backward compatibility
Existing flat pages are untouched (a single-segment section is just a depth-1 path). No migration needed; normSeg still applies to each segment, so url-safety and the 64-char cap per segment hold.
Nice-to-haves (out of scope, but enabled by this)
- Console breadcrumb / collapsible tree per subtree.
- Per-subtree ownership/tags conventions (e.g.
runbooks/support/* owned by the support agent).
Happy to send a PR if this direction is acceptable.
🤖 Filed with Claude Code · https://claude.ai/code/session_01HiUDC14XfeTQJZHf2wswma
The knowledge base is currently a strictly flat, two-level taxonomy —
kb/<section>/<slug>.md— with no way to nest.Current behavior (why nesting is impossible today)
KbStore.write()runs bothsectionandslugthroughnormSeg():[^a-z0-9]+ -> "-"collapses any/to a hyphen, sosection: "runbooks/infra"silently becomes the flat sectionrunbooks-infra.rel_pathispath.posix.join("kb", section, slug + ".md")andsections()returns a flat list — one folder level, full stop.The problem (motivation)
A single section grows unwieldy fast. In one workspace the
runbookssection already holds 15 pages mixing infra / migration / security / support (vs. 1 each for the other sections). There is no way to grouprunbooks/infra/*,runbooks/migration/*,runbooks/support/*— they all live as siblings in one flatrunbooks/bucket, which hurts both the console tree and human/git browsing of the on-disk mirror.Proposal
Allow hierarchical sections (arbitrary, bounded depth):
/insectionand normalize per segment (each segment stillnormSeg-clean), sorunbooks/infra→kb/runbooks/infra/<slug>.md. Reject..and leading/trailing/(the on-disk mirror already guards containment viacontained()— keep that as the security backstop against traversal).sections()returns a tree (or the console derives one by splitting on/) so the KB view can render nested folders.sectionfilter becomes a prefix match (section = "runbooks"still returns everything under it;section = "runbooks/support"scopes to that subtree). FTS/rel_path/revisions are unaffected — only the section segmenting changes.Backward compatibility
Existing flat pages are untouched (a single-segment section is just a depth-1 path). No migration needed;
normSegstill applies to each segment, so url-safety and the 64-char cap per segment hold.Nice-to-haves (out of scope, but enabled by this)
runbooks/support/*owned by the support agent).Happy to send a PR if this direction is acceptable.
🤖 Filed with Claude Code · https://claude.ai/code/session_01HiUDC14XfeTQJZHf2wswma