RFD 127: In-Repo Project Wiki
Summary
This document establishes the project wiki: a third in-repo documentation workflow alongside RFDs and tickets, managed by a jp-wiki command plugin and rendered by jp-vite. Wiki pages live in docs/wiki/<id>-<slug>.md with collision-resistant W-<id> identifiers, distinguish maintained system references (Reference) from point-in-time investigations (Snapshot), support section-level linking and transclusion, and validate links to pages, headings, and workspace source lines in jp wiki check.
Motivation
RFD 100 introduced in-repo tickets because RFDs had become the only place to write anything down. With jp-ticket and jp-rfd in place (RFD 116), a third kind of artifact now falls into the gap between them: project knowledge that is neither a design proposal nor a unit of work.
In practice, contributors and assistants produce:
- Literature and prior-art notes (such as paper evaluations or comparative studies of other systems)
- Domain glossaries and ubiquitous language definitions
- Subsystem architecture overviews that describe how the code works today
- Operational and qualification procedures
These keep landing in jp-ticket (often under a type=note or type=task label) because jp-ticket offers five ergonomics that plain markdown files in docs/ lack:
- Zero-friction capture (
jp ticket add/ticket_create) without choosing a directory path or editing.vitepress/config.mts. - A short handle (
T-0n0nwz3) that prose in other tickets and RFDs cites andjp-viteauto-links. - Structured metadata and validated labels (
relevant-rfd=006,domain=conversation) backed byjp_label. - Bidirectional backlinks (
ReferencesandReferenced bybadges on the site, plus RFD link counts). - A filterable index and dev-server editing (
<DocIndex />and/__ticketmiddleware).
Once filed as a ticket, however, a note sits on a kanban board whose columns are Todo, In Progress, and Done. Leaving a research note in Todo pollutes the prioritized work queue; marking it Done buries active reference material in the completed-work archive and misstates its nature.
Meanwhile, the existing docs/architecture/ files have the opposite problem: they sit outside the VitePress navbar and sidebar, have a manually maintained index.md that already omits newer files, carry no backlinks to the RFDs and tickets that cite them, and do not fail the build when #section anchors or relative links to source files break.
Traditional wikis solve capture but fail on maintenance: without ordering, freshness contracts, and link checking, a wiki degrades into orphan pages, stale claims, and broken links. A repo-native wiki integrated into jp's toolchain can prevent those failure modes structurally.
Design
What a Wiki Page Is
The three workflows answer distinct questions and have distinct lifecycles:
| RFD | Ticket | Wiki Page | |
|---|---|---|---|
| Question | What should we build, and why? | What needs doing? | What do we know, and how does X work? |
| Contains | A design and its rationale | A description and a discussion | Reference documentation or research |
| Lifetime | Frozen once implemented | Closed when the work is done | Maintained (Reference) or dated (Snapshot) |
| Primary view | Priority board | Kanban work queue | Hierarchical sidebar and filterable index |
User-facing product documentation (installation, getting started, feature guides for end users of the binary) stays in ordinary VitePress markdown pages under docs/. Contributor- and assistant-facing project knowledge (architecture, ubiquitous language, research notes, qualification runbooks) belongs in the wiki.
Page Format and Identity
A wiki page is a single markdown file at docs/wiki/<id>-<slug>.md. Every page is plain CommonMark, readable in any file explorer, text editor, or Git forge without jp installed.
The <id> is a 7-character collision-resistant identifier allocated from the wiki directory's .epoch file using the same algorithm as RFD 102. The canonical reference form is W-02wt0kx; CLI commands and tools accept W02wt0kx, the bare 7-character body, and any case.
Keeping identity in a flat <id>-<slug>.md filename rather than a nested directory path gives two properties:
- Zero filing friction. Creating a page requires a title, and optionally the page it sits under, not a decision about directory taxonomy.
- Rename and reorganization safety. Renaming a page renames its
<slug>and rewrites file links, while everyW-02wt0kxcitation in RFDs, tickets, commits, and conversations continues to resolve.
# Ubiquitous Language: Conversation
- **Status**: Reference
- **Authors**: Jean Mertz <git@jeanmertz.com>
- **Date**: 2026-10-30
- **Parent**: W-0a1b2c3
- **Label**: domain=conversation
- **Label**: relevant-rfd=064
- **Summary**: Canonical definitions for Conversation, Turn, Thread, and Event
Stream.
## Turn
A single request-response cycle initiated by the user...Metadata
Wiki pages use the same - **Key**: Value metadata header as RFDs and tickets, formatted at 80 columns by comfort.
| Field | Required | Values |
|---|---|---|
Status | yes | Reference, Snapshot, Archived |
Authors | yes | |
Date | yes | YYYY-MM-DD |
Parent | no | W-<id> of the parent wiki page |
Superseded by | no | W-<id> or RFD NNN when Status: Archived |
Label | no | One key=value pair, repeatable |
Summary | yes | One sentence, last in the metadata block |
Epistemic Status (Reference, Snapshot, Archived)
Wikis lose trust when point-in-time notes and living system specifications sit side by side without distinction. Status declares the freshness contract of the page:
Reference: Authoritative documentation of the current system (such as subsystem architecture, glossary definitions, or runbooks). If the code and aReferencepage disagree, one of them is wrong.Snapshot: Point-in-time research, literature synthesis, benchmark analysis, or external prior-art study (such as paper notes or game design studies). ASnapshotis dated evidence; it is not expected to change when the codebase evolves.Archived: Retired material kept so historical links still resolve.Archivedpages are omitted from the site's sidebar, omitted from defaultjp wiki listandwiki_listoutput unless--archivedor--allis passed, and may carry- **Superseded by**: W-<id>orRFD NNN.
Parent Hierarchy and Ordering
Organization is decoupled from filenames through two mechanisms:
Parent: A page may name another wiki page as its parent (- **Parent**: W-0a1b2c3), forming a tree of arbitrary depth without moving files into subdirectories. A page without one is a top page, which introduces an area, such as the architecture or the glossary, and leads to the pages under it. Cycles inParentlinks are rejected byjp wiki check.Ordering (
.order.json): As injp-ticket(.board.json) andjp-rfd(.priority.json), relational ordering lives indocs/wiki/.order.jsonrather than inside individual files. The file holds the order of the top pages, and maps each parent page ID to the order of the pages under it:json{ "top": [ "W-0b42fs5", "W-0a1b2c3" ], "children": { "W-0a1b2c3": [ "W-0a1b2c4" ] } }Pages not listed in
.order.jsonsort after ranked pages by ID.
Linking and Section Addressing
Cross-Document Citations
jp-vite's mention linker (references.mjs) and document registry (theme/documents.mjs) add wiki as a third document kind alongside rfd and ticket:
- Mentioning
W-02wt0kxin prose or inline code in any RFD, ticket, or wiki page links to/wiki/02wt0kx-slugand records a bidirectional edge in<DocReferences />(ReferencesandReferenced by). redirects.mjsemits short-link redirects so/wiki/W-02wt0kxand/wiki/02wt0kxbounce to/wiki/02wt0kx-slug.- Mention syntax supports an optional section fragment:
W-0a1b2c3#pinned-conversation,RFD 100#labels, andT-0n0nwz3#swotlink directly to that heading anchor on the target page.
Heading Slugs and Section Addressing
Every ATX heading (# through ######) has an anchor slug computed the way the site computes it, so a #fragment that jp wiki check accepts is one the site renders.
jp-vite owns the slug function: withJpVite sets markdown.anchor.slugify to its own copy of VitePress's default (slugify from @mdit-vue/shared), so no existing anchor changes and a VitePress upgrade can't change one underneath. The Rust side implements the same function, and a fixture of headings and their slugs, shared by the JavaScript and Rust tests, holds the two in step.
The function normalizes Unicode (NFKD) and drops combining accents, replaces runs of whitespace and punctuation (_ included) with -, keeps other letters, lowercases, and prefixes a leading digit with _:
| Heading | Slug |
|---|---|
## Pinned Conversation | pinned-conversation |
### 1. Query Command (`jp_cli::cmd::query`) | _1-query-command-jp-cli-cmd-query |
Slugs are unique within a document: the second ## Examples is examples-1, the third examples-2. Suffixes are counted over the whole source document, so reading or embedding one section never changes them. Custom heading anchors (## Turn {#turn}) are rejected by jp wiki check: Git forges and plain-text readers show them as literal text.
Commands and tools address sections by anchor slug or heading title. An argument that matches a slug selects that section; otherwise it is matched against heading titles, and a title shared by several headings is refused with their slugs, so a section edit never guesses which section to replace:
jp wiki show W-0a1b2c3prints the metadata header, linked RFDs/tickets/wiki pages, and the page's table of contents (indented headings with their#sluganchors), omitting the body unless--bodyis passed.jp wiki show W-0a1b2c3 --section pinned-conversationprints only that section (from the matched heading up to the next heading of equal or higher level).jp wiki edit W-0a1b2c3 --section pinned-conversation --body "..."replaces only that section in place.
Transclusion (Reusing Snippets Across Pages)
A Reference page can embed a section of another document or a line range of a workspace source file using standard CommonMark image syntax on its own line:

Using  preserves plain-text readability:
- In
$EDITORand Git forges: It is valid CommonMark with a standard relative path that editor go-to-file commands follow directly. - In
jp-vite: Amarkdown-itrule replaces a standalone transclusion link pointing to.md#sectionwith the rendered AST of that section (shifting child heading levels to sit below the enclosing heading) inside a container that links back to the canonical source. A transclusion link pointing to a workspace file with#L<start>-L<end>expands into a fenced code block with the file's extension as its language tag and a header linking to the source file. - In
jp wiki show --body, andwiki_showwithexpand_embeds: The Rust reader expands transclusions inline so the reader receives the embedded text in a single read.wiki_showreturns the text as written unless asked, since that text is whatwiki_editreplaces, and writing expanded text back would turn each embed into a copy. - In
<DocReferences />: Transcluding a document counts as referencing it, so the source page lists every page that embeds it underReferenced by.
An embedded section is parsed as part of its whole source document, so the site and the Rust reader give it the same meaning wherever it appears:
- Reference-style links resolve against the source document's definitions, which
comfortputs at the end of the file, outside any section. - Relative targets resolve against the source document's directory and are rewritten to be valid from the embedding page.
- A
#fragmentpointing into the embedded section keeps pointing at the source page, not at a heading of the embedding page. - A nested embed resolves against the document that contains it.
wiki_show wraps each expanded embed in markers naming its source (<!-- embed W-0a1b2c3#pinned-conversation --> and <!-- /embed -->), so the assistant can tell embedded text from the page's own. On the site, a  pointing at a markdown document or a workspace file that isn't a standalone embed of a section or line range fails the build rather than rendering as a broken image.
Snapshot pages don't embed. An embed shows the source's current text, so a page that must not change as the code evolves would change on every build. A Snapshot copies the excerpt it relies on and cites its source as described in Snapshot Citations.
Deep-Linking to Workspace Source Files
Links from wiki pages, RFDs, and tickets to files in the repository use normal relative paths, optionally carrying a 1-based line or line-range fragment:
See [`Links::by_rfd`](../../crates/plugins/command/rfd/src/links.rs#L88-L125).Line fragments accept #L12, #L12-L24, #L12-24, and #12-24, normalizing to #L12-L24 when rewritten by jp-vite.
To catch silent line drift when a source file is edited above the linked range, when a link's label is an inline code span (such as [`Links::by_rfd`] or [`by_rfd`]), jp wiki check takes the trailing identifier (by_rfd) as an expected symbol guard and verifies that the substring still appears within the linked line range (or within the file when no line range is given).
Snapshot Citations
A relative source link follows the working tree. That is what a Reference page wants and what a Snapshot must not do: once the code moves, the link breaks or points at code the snapshot never saw. Snapshot pages therefore cite source code with a permalink to a commit:
[`Links::by_rfd`](https://github.com/dcdpr/jp/blob/9a43a5e2dc077909a9b8fca9e58abe541b3d8cda/crates/plugins/command/rfd/src/links.rs#L88-L125)A permalink is a link in every editor, on every forge, and on the site, and what it points at never changes. A link counts as a permalink when it names a full commit hash under the repository jp vite links to (plugins.command.vite.options.repository, or git's origin remote); any other URL is an ordinary external link.
The commit has to be reachable from the default branch (origin/HEAD). A branch commit stops being reachable once its pull request is squash-merged, and a fresh clone never fetches it. Code that exists only on the current branch can't be pinned yet, so the page names its path and lines as code text (`crates/foo/src/bar.rs` L12-24) beside the copied excerpt, and is pinned once the code lands.
jp wiki pin <ID> rewrites a page's relative source links into permalinks. Each link is pinned to the newest default-branch commit whose copy of the file matches the working tree; a link with no such commit is left alone and reported. Creating a Snapshot page, or changing a page's status to Snapshot, pins it the same way, through the CLI or the tools.
Validation (jp wiki check)
jp wiki check runs all structural and anti-rot checks over docs/wiki/ and exits 1 on any error:
- Metadata and formatting: Every page has a
# Title, required metadata fields (Status,Authors,Date,Summary), and no duplicate IDs. - Label vocabulary: Every
Labelsatisfiesplugins.command.wiki.options.labels(values,min,max,unique), using the same validator asjp ticket check. - Hierarchy and order integrity: The
Parentof every non-archived page, and every.order.jsonentry, names an existing, non-archived wiki page, with no cycles inParent. An archived page may keep its ownParent. - Internal page and
#sectionanchor links: Every link or transclusion to another wiki page, RFD, or ticket resolves to an existing file, every#fragmenton a markdown link matches a heading slug in the target file, and no heading carries a custom anchor. - Workspace source file, line-range, and symbol links: In a
Referencepage, every relative link or transclusion that resolves outsidedocs/into the workspace must point to an existing file or directory on disk. If a#L<start>-L<end>fragment is present,1 <= start <= end <= line_countmust hold. If the link text is a code span, the identifier must appear within that line range; if it moved elsewhere in the file, the error reports the new line number. In aSnapshotpage, a relative link into the workspace outsidedocs/is an error namingjp wiki pin. A permalink's commit must exist locally and be reachable from the default branch, and the path, line range, and symbol guard are checked against that commit's copy of the file, which never changes: these checks fail on a mistake, never on later code changes. - Transclusions: Rejects any cycle of
embeds, and any embed in aSnapshotpage. - Advisory anti-rot warnings (printed to stderr, promoted to errors with
--strict):- Orphan pages: A non-archived page with no
Parent, no incoming links from any RFD, ticket, or wiki page, and norelevant-rfdlabel. - Superseded RFD citations: A
Referencewiki page that cites an RFD whose status isSupersededorAbandoned.
- Orphan pages: A non-archived page with no
Configuration
jp-wiki follows RFD 116's plugin configuration pattern under plugins.command.wiki:
[plugins.command.wiki]
run = "allow"
[plugins.command.wiki.options]
dir = "docs/wiki"
assistant = "jp"
# The label key a wiki page uses to link to an RFD.
relevant.key = "relevant-rfd"
[plugins.command.wiki.options.labels.domain]
description = "The subsystem covered by this page"
values = ["conversation", "llm", "mcp", "plugins", "storage", "tooling"]
[plugins.command.wiki.options.labels.relevant-rfd]
description = "An RFD this page relates to"
values = []CLI Commands and Assistant Tools
| Verb | jp wiki CLI | Assistant tool (plugin.command.wiki.*) |
|---|---|---|
| Create | add <TITLE> [--parent ...] [--status ...] [--body ...] | wiki_create |
| Show | show <ID> [--body] [--section <SLUG>] [--raw] | wiki_show |
| List | list [--under ...] [--status ...] [--label ...] [--query ...] | wiki_list |
| Edit | edit <ID> [--title ...] [--body ...] [--section ...] | wiki_edit |
| Rename | rename <ID> <TITLE> | wiki_edit (via title) |
| Rank | rank <ID> (--before OTHER | --after OTHER) | not a tool |
| Archive | archive <ID> [--superseded-by <TARGET>] | wiki_edit (via status) |
| Pin | pin <ID> | wiki_edit (via status) |
| Check | check [--strict] | not a tool |
| Migrate ticket | from-ticket <TICKET-ID> [--parent ...] [--status ...] | not a tool |
| Tool definitions | tools init <PATH>, tools sync <PATH> | not a tool |
| Init | init | not a tool |
With no mutation flags, jp wiki edit <ID> opens the markdown file in the user's $EDITOR via protocol 13's edit_files and runs the page validator when the editor closes.
archive refuses a page that still has non-archived children, naming them, so no active page is left under a parent the sidebar hides. Archiving removes the page's entries from .order.json. Both hold when wiki_edit sets the status to Archived.
jp wiki from-ticket <TICKET-ID> moves a note ticket into the wiki the way jp rfd draft --from-ticket moves one into an RFD (RFD 116). It creates a page from the ticket's title and description, carrying over Authors, Date, and the labels the wiki vocabulary accepts (naming the rest), then closes the ticket as Done with Promoted to naming the page. The ticket stays, so every T- citation in tickets, RFDs, commits, and conversations still resolves and leads on to the page. Its comments stay with it, with their attribution; whoever migrates the ticket folds what matters into the page.
The page gets a new id rather than the ticket's. An id's first five characters count time from its own directory's .epoch (RFD 102), so the same characters under the wiki's epoch would name a different time and break creation order. Like draft --from-ticket, the command is safe to repeat: a ticket whose Promoted to already names an existing page reports it.
Token-Conscious Assistant Tools
To keep per-turn schema overhead small, jp-wiki exposes four tools:
wiki_list: Lists non-archived wiki pages in.order.jsonorder, each top page above the pages under it, optionally filtered byunder(the pages below one page),status,labels, orquery. Withfull: true, includes each page's one-lineSummary.wiki_show: Returns a page's metadata, incoming/outgoing references, and its heading table of contents (#slugand title) by default. Passingbody: truereturns the full markdown body as written; passingsection: "<slug>"returns only that section;expand_embeds: trueexpands the transclusions in either.wiki_create: Creates a new wiki page withtitle,summary, and optionalbody(a page without one is a hub),status(defaulting toReference),parent, andlabels.wiki_edit: Single mutation tool acceptingidand any combination oftitle(which also renames the file and rewrites relative links),summary,status,parent,beforeorafter(a sibling to place it directly above or below, asjp wiki rankdoes),superseded_by,body,section(scopingbodyreplacement to one heading section),add_labels, andremove_labels.
Every tool holds to the call's access policy (RFD 116) file by file, not only at the wiki directory:
wiki_showchecks the policy for every file an embed reads, nested embeds included. Being allowed to read a page grants nothing about what it embeds. A denied embed is replaced by a marker naming the target and the refusal, and the rest of the page is returned.wiki_editupdates only files the policy lets it update. A rename that leaves references in files it can't reach names them, and says so when the policy doesn't let it search the workspace, asrfd_renamedoes.
Site Integration (jp-vite)
jp-vite adds wiki to settings.json alongside rfd and ticket when the configured wiki directory exists:
- Auto-generated hierarchical sidebar: Unlike RFDs and tickets, which hide the VitePress sidebar,
withJpVitegeneratesthemeConfig.sidebar['/wiki/']from the wiki'sParenttree and.order.json, each top page above the pages under it, using each page's# Titleas the link text. Prev/Next navigation at the bottom of each wiki page follows this reading order. - A front page and hub pages:
/wiki/is the wiki's own front page, whichjp vite synccreates once and never rewrites: an introduction, links to the pages to read first, and embeds. Each area opens with a top page introducing it, with the detailed pages under it throughParent.<WikiChildren />lists the pages one level down, with their summaries, in reading order: under the page it is on, underof="W-0a1b2c3", or with no page around it, the top pages. A page with no text of its own, only a title and metadata, shows this list. A hub can be just that.jp wiki checkcounts a link from the front page as reaching a page, so a hub is no orphan. <WikiIndex />on/wiki/all: Wraps<DocIndex />with a filter button per top page, narrowing the table to the pages under it, status badges (Reference,Snapshot,Archived), label badges, search, summary toggles, and dev-server+ New page,Edit, andArchiveactions backed by/__wikimiddleware callingjp wiki.- Editing in place: On a dev server that accepts edits, each section heading of a wiki page has an edit button, and the page an
Edit pagebutton. Either opens the text as written, throughjp wiki show --raw. Saving runsjp wiki edit --section, which checks the page before keeping it. On the published site, every page links to the forge's editor for its file on the default branch, whichjp vitereads from git'sorigin/HEAD. - Compact metadata: On RFD, ticket, and wiki pages, the metadata block renders as one line of labels grouped by key (
domain: conversation, llm), and each other field on a short line. A wiki page's status is a letter after its title (R,S, orA), its summary, authors, and date sit at the end of the page in small print, above the references, and its parent is a trail above the title (Wiki › Glossary › Ubiquitous Language), on the line with theEdit pagebutton. An RFD or ticket keeps a line of status badge, category, date, and authors, and leaves its summary to the index pages. The file keeps the list. - Cross-references:
<DocReferences />lists, below each RFD, ticket, and wiki page, what it references and what references it, grouped by kind and named by title, folded when long. RFD and ticket pages show wiki pages that cite them or carryrelevant-rfd=NNN. - Moved pages: A
redirectsmap inplugins.command.vite.optionssends an old route to a new one, and may name a wiki page by id ("architecture/ubiquitous-language" = "W-0a1b2c3"), so a later rename doesn't break the redirect.redirects.mjswrites the stubs, which keep the#fragment.
Shared Workspace Substrate
RFD 100 deferred extracting shared infrastructure until a third consumer existed. This RFD extracts crates/internal/doc (or expands site-revision) with the primitives shared by ticket, jp-rfd, and jp-wiki:
- **Key**: Valuemetadata block parsing andcomfort-compatible rendering- 7-character epoch ID allocation and parsing parameterized by prefix (
T-,W-) - Workspace-wide reference walking and link rewriting (
references.rs) - Markdown ATX heading extraction and
#anchorslug computation - Workspace relative-path, line-range, and code-span symbol verification
jp-wiki links the ticket library for from-ticket, as jp-rfd does for draft --from-ticket, so the ticket format keeps one implementation.
Drawbacks
- Four more tool definitions when enabled. Offering
wiki_list,wiki_show,wiki_create, andwiki_editadds four schemas to the assistant's tool list in workspaces that enable the wiki. Consolidating all mutations intowiki_editcaps this at four rather than eight. - Flat filenames on disk.
docs/wiki/0a1b2c3-ubiquitous-language.mdputs the 7-character ID at the front of every filename, sols docs/wiki/orders by creation time rather than by topic.jp wiki listand the VitePress sidebar provide the topical hierarchy. - Embeds look like images on Git forges. A forge preview treats
as an image, so where the site shows an embedded section, the forge shows a broken image. The source file stays readable and names the target. - Snapshots cite longer links, and wait for merges. A permalink is long, and code that exists only on a branch is cited as text until it lands on the default branch.
Alternatives
Add a Note status to jp-ticket
Adding Status::Note to crates/internal/ticket and hiding Note tickets from the kanban board takes roughly 50 lines of code. It was rejected because a literature note or architecture reference is not a work item: ticket affordances (append-only From/Date comment threads, flat listing, T- identifiers) are the wrong model for living documentation, and it cannot absorb docs/architecture/ or provide hierarchical navigation and section transclusion.
Add a status-exempt category to jp-rfd
Allowing a note category in jp-rfd that skips Draft -> Implemented would reuse jp-rfd, but consumes permanent sequential RFD numbers (001..999) for informal notes and mixes historical decision records with living reference pages.
Directory hierarchy instead of short IDs
Storing wiki pages as docs/wiki/<area>/<slug>.md without IDs produces clean filenames on disk, but reintroduces taxonomy friction on creation and breaks prose citations whenever a page is moved to another area or renamed.
Configured categories beside the tree
A Category field naming one of a configured list (architecture, glossary, research, guide) would group the sidebar and filter the index. It was rejected because the Parent tree already does both, and a page's category could disagree with its parent's, an error state nothing about the page could resolve. A top page is the category, and the pages under it are its members.
VitePress @include for transclusion
VitePress supports HTML-comment includes natively, but HTML comments are invisible in GitHub markdown previews and plain-text reading, and do not participate in jp-vite's <DocReferences /> graph or jp wiki show.
Non-Goals
- Prompt injection and
learntool. Automatically injecting wiki topics into the system prompt is covered by the Knowledge Base design, which can point atdocs/wikias a topic directory once implemented. - Replacing user-facing product documentation. End-user guides (
/usage,/installation,/features/*) remain regular VitePress pages. - Hosted source code browsing on the website. Validating relative source links (
../../crates/...#L12-L24) happens injp wiki check, while rendering workspace source files inside the VitePress site is a separatejp-viteextension.
Risks and Open Questions
- Heading-level shifting during transclusion. When a
### Termsection is transcluded under a## Contextheading in another page, shifting its headings so the outline stays valid requires care with fenced code blocks and nested subheadings. - Symbol guard false positives on refactored code. Checking that the identifier in
[`foo`](path.rs#L10-L20)appears in lines10..=20catches line shifts, but a link whose label is prose ([the parser](path.rs#L10-L20)) only gets bounds checking, not content checking. - Slug drift between the site and Rust. The two implementations of the slug function agree as far as the shared fixture covers. A heading shape it misses can yield an anchor
checkaccepts and the site renders differently. - Shallow checkouts. Checking a permalink needs its commit locally.
docs.ymlandrust.ymlcheck out full history (fetch-depth: 0), butplugins.ymluses the default depth of 1, so whichever CI job runsjp wiki checkneeds full history. Without it,checkreports the commit as missing rather than passing it. - Source changes that skip the check.
jp wiki checkvalidates links into workspace source, so a pull request that changes, renames, or deletes a linked file has to run it, though it touches nothing underdocs/. A job triggered only by documentation changes lets such a pull request pass and fails the next one that does touchdocs/.
Implementation Plan
Phase 1: Shared document substrate
Extract metadata parsing/rendering, parameterized 7-character epoch IDs, reference rewriting, and heading/anchor extraction into a shared internal crate used by ticket and jp-rfd.
Mergeable independently with no behavioral change.
Phase 2: jp-wiki core and CLI
Create crates/plugins/command/wiki (jp-wiki) with init, add, show (including --section and transclusion expansion), list, edit, rename, rank, archive, pin, from-ticket, and check (metadata, labels, #section anchors, workspace file/line/symbol links, and permalinks).
Depends on Phase 1.
Phase 3: Assistant tools (wiki_*)
Add wiki_list, wiki_show, wiki_create, and wiki_edit, plus jp wiki tools init and jp wiki tools sync.
Depends on Phase 2.
Phase 4: jp-vite integration
Extend jp-vite (settings.rs, references.mjs, redirects.mjs and the redirects map, theme/documents.mjs, middleware.mjs, <WikiIndex />, the owned slug function, the transclusion markdown rule, and the auto-generated /wiki/ sidebar).
Depends on Phase 2.
Phase 5: Migrating JP's documents
Move each docs/architecture/ file into docs/wiki/ as one page, keeping its headings so anchors carry over; split pages afterwards, not during the move.
Rewrite references to the old paths across the whole workspace, not only docs/. Persona and knowledge configuration under .jp/config/ and doc comments in jp_cli name docs/architecture/ files, and a missed one points the assistant at a file that no longer exists. Report every reference left unrewritten. Redirect old /architecture/* routes to their pages through the redirects map.
Move research-note tickets such as T-0n0nwz3 with jp wiki from-ticket.
Depends on Phase 4.