Per-Project Vault Wiki
From spec-memo Wiki, the living architecture & domain knowledge base
Feature Overview [edit]
The project wiki is one persisted narrative Markdown page per vault project that reads like a project README but is generated entirely from collected vault memory (traps, decisions, specs, plans, sessions, and inventory counts) rather than from product git. It complements the inventory-style compiled views (INDEX.md, TRAPS.md, DECISIONS.md, PROMPTS.md, SESSIONS.md) with a progressive, templated synthesis.
Primary journeys:
- An operator opens the status monitor Wiki tab, selects a project, reads the page with collapsed
h2sections, and clicks Regenerate to rebuild it. - A maintainer runs CLI
memo wiki [--project <id>] [--json]to print the current page. - A maintainer runs
memo wiki --project <id> --regenerateto collect, render, and persist the page. - An agent uses the CLI or status HTTP because there is deliberately no 12th MCP tool; the 11-tool contract stays frozen.
Boundaries:
- The wiki is vault-side only; it never writes or modifies the consumer product
README.md. - Generation is on demand, not automatic on every upsert.
Business Rules & Logic [edit]
Path and project validation [edit]
- The page is written to
projects/{projectId}/WIKI.mdin the vault root. - Only an existing project directory can be read or regenerated.
assertSafeProjectIdrejectsnull, empty, the literalall, ids containing/,\,.., or NUL, and any id that resolves outsideprojects/.- Rejection returns
400 INVALID_PROJECTwith the messageA specific projectId is required. - An unknown but safe project returns
404 NOT_FOUND(Not found). - Regeneration never creates a new project directory.
- Read-only getters (
readWikiFile,readWikiSection) do not callensureVaultStructureand create noconfig.json,projects/, or telemetry files. - The CLI get path is treated as read-only.
Template and deterministic render [edit]
- A shipped
src/wiki/template.mddefines the ordered sections: Overview, Architecture & decisions, Active traps, Specs & plans, Sessions, and Structured links. loadWikiTemplateresolves the template from the packaged location or the source tree and errors if it is missing.renderWikiMarkdownreplaces every named placeholder with collected data or an explicit empty-state sentence.- Empty-state sentences:
*No active traps recorded for this project yet.*,*No architecture decisions recorded for this project yet.*,*No specs or plans recorded for this project yet.*,*No sessions recorded for this project yet.*. - Any residual
{{...}}token is stripped so no raw placeholder reaches disk.
Source selection [edit]
- Active traps are
kind: trapwithstatus: active. - Decisions exclude
archivedandsuperseded. - Specs and plans exclude
archivedandsuperseded. - Sessions include all
kind: session. - Lists are sorted newest-first.
- Lists are capped at 25 bullets with a
*N more in compiled views / inventory.*line. *.conflict.*sidecars are skipped becauselistProjectRecordsfilters them out.
Links, marker, and timestamp [edit]
- Structured links are repo-relative Markdown into the project vault:
./traps/{id}.md,./decisions/…,./specs/…,./plans/…. - The compiled-view links
./INDEX.md,./TRAPS.md, and./DECISIONS.mdare also emitted. - Link style matches compiled views and is viewer-compatible.
- Each listed item also carries a
[[id]]wikilink. - Every regenerated page includes the marker
<!-- AUTO-GENERATED BY spec-memo wiki — DO NOT EDIT DIRECTLY -->. - Every page includes a
*Last generated: <ISO>*line thatparseLastGeneratedextracts.
AI polish (optional, fail-open) [edit]
- Polish runs only when
config.jsonwiki.aiEnabledistrue. - When
aiEnabledis unset, the env varSPEC_MEMO_WIKI_AI=1enables polish. wiki.timeoutMsoverrides the 15000 ms default timeout.- When enabled but no provider is injected, the result reports
aiError: 'no provider'. - Any throw or timeout falls back to the deterministic page:
okstaystrue,aiPolishedisfalse, andaiErroris a short sanitized string (max 180 chars).
Persistence side effects and safety [edit]
persistWikiMarkdownwrites under a vault lock.- It then calls
commitVaultChange(vault-git, fail-open) andscheduleHybridPush(hybrid, fail-open). - Regeneration does not increment record
hitsoroccurrences. - Status GET/POST wiki routes require the configured bearer token like other
/api/*routes. - All responses pass through
sanitizeToolOutput, so absolute vault paths are never leaked. - No MCP tool is added; creation and regeneration are CLI/HTTP only.
Technical Architecture [edit]
Core module src/wiki.ts [edit]
- Constants/markers:
WIKI_AUTO_MARKER,WIKI_PROJECT_REQUIRED. - Error type:
WikiErrorwith codesINVALID_PROJECT(400) andNOT_FOUND(404). - Functions:
assertSafeProjectId,wikiProjectExists,wikiFilePath,collectWikiSources,loadWikiTemplate,renderWikiMarkdown,parseLastGenerated,persistWikiMarkdown,readWikiFile,splitWikiSections,readWikiSection,regenerateWiki.
Data shapes [edit]
WikiSources:projectId,projectTitle(projectdisplayNameor id),gitRemote,trapsActive,decisions,specsPlans,sessions, andinventorycounts (traps,trapsActive,decisions,specs,plans,sessions,prompts).WikiFilePayload:{ projectId, markdown, lastGenerated, exists }.WikiSectionPayload:{ id, title, markdown }.WikiRegenerateResult:{ ok: true, projectId, lastGenerated, aiPolished, aiError? }.
Status HTTP (status listener :3124) [edit]
GET /api/wiki?project={id}→{ projectId, markdown, lastGenerated, exists, renderedHtml }.- Missing
WIKI.mdyieldsexists: falsewith empty markdown. - HTML is rendered with
renderPromptMarkdownHtmlwrapped bywrapWikiH2Htmlfor collapsible sections. GET /api/wiki/section?project={id}&id={sectionId}→{ id, title, markdown }for a heading whose slugified title matches; unknown id →404 { error: "Not found" }.POST /api/wiki/regeneratewith{ "projectId": "<id>" }→200result.- Missing/empty/
allprojectId →400with no write. - Unknown project →
404with no directory creation. - The activity bus captures the regenerate write with
path: "/api/wiki/regenerate".
UI (src/status.ts generateStatusHtml) [edit]
<section id="tab-wiki">is reached by?tab=wiki.?tab=wiki&project={id}selects that project when present in/api/vaults.- The tab has a project selector parsed from the raw
/api/vaultsarray (Array.isArrayfirst). - It has a markdown view region and a Regenerate button disabled while in flight.
h2sections are collapsed by default.
CLI (src/cli.ts) [edit]
- Read-only:
memo wiki [--project <id>] [--json]prints sanitized Markdown. - When the page is missing, the CLI writes a notice to stderr and exits 0 when the project exists.
- Write:
memo wiki --project <id> --regeneraterunsregenerateWikiand exits 0. - With no
--projectand no bound cwd, the CLI exits non-zero requiring--projectand never regenerates all projects. - In
remotemode the command is rejected because data lives on the daemon.
Tests and provenance [edit]
src/wiki.test.tscovers template fill, missing wiki,400without project, regenerate persistence, AI fail-open, deep links, and path sanitization.- Wiki cases also live in
src/status.test.tsandsrc/cli.test.ts. - Provenance:
0046-project-wiki.spec.md. - Related compiled views live in
src/compiler.ts.