W spec-memo Wiki
Home Wiki Index GitHub

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 h2 sections, 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> --regenerate to 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.md in the vault root.
  • Only an existing project directory can be read or regenerated.
  • assertSafeProjectId rejects null, empty, the literal all, ids containing /, \, .., or NUL, and any id that resolves outside projects/.
  • Rejection returns 400 INVALID_PROJECT with the message A 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 call ensureVaultStructure and create no config.json, projects/, or telemetry files.
  • The CLI get path is treated as read-only.

Template and deterministic render [edit]

  • A shipped src/wiki/template.md defines the ordered sections: Overview, Architecture & decisions, Active traps, Specs & plans, Sessions, and Structured links.
  • loadWikiTemplate resolves the template from the packaged location or the source tree and errors if it is missing.
  • renderWikiMarkdown replaces 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: trap with status: active.
  • Decisions exclude archived and superseded.
  • Specs and plans exclude archived and superseded.
  • 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 because listProjectRecords filters them out.
  • 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.md are 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 that parseLastGenerated extracts.

AI polish (optional, fail-open) [edit]

  • Polish runs only when config.json wiki.aiEnabled is true.
  • When aiEnabled is unset, the env var SPEC_MEMO_WIKI_AI=1 enables polish.
  • wiki.timeoutMs overrides 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: ok stays true, aiPolished is false, and aiError is a short sanitized string (max 180 chars).

Persistence side effects and safety [edit]

  • persistWikiMarkdown writes under a vault lock.
  • It then calls commitVaultChange (vault-git, fail-open) and scheduleHybridPush (hybrid, fail-open).
  • Regeneration does not increment record hits or occurrences.
  • 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: WikiError with codes INVALID_PROJECT (400) and NOT_FOUND (404).
  • Functions: assertSafeProjectId, wikiProjectExists, wikiFilePath, collectWikiSources, loadWikiTemplate, renderWikiMarkdown, parseLastGenerated, persistWikiMarkdown, readWikiFile, splitWikiSections, readWikiSection, regenerateWiki.

Data shapes [edit]

  • WikiSources: projectId, projectTitle (project displayName or id), gitRemote, trapsActive, decisions, specsPlans, sessions, and inventory counts (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.md yields exists: false with empty markdown.
  • HTML is rendered with renderPromptMarkdownHtml wrapped by wrapWikiH2Html for 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/regenerate with { "projectId": "<id>" } → 200 result.
  • Missing/empty/all projectId → 400 with no write.
  • Unknown project → 404 with 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/vaults array (Array.isArray first).
  • It has a markdown view region and a Regenerate button disabled while in flight.
  • h2 sections 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> --regenerate runs regenerateWiki and exits 0.
  • With no --project and no bound cwd, the CLI exits non-zero requiring --project and never regenerates all projects.
  • In remote mode the command is rejected because data lives on the daemon.

Tests and provenance [edit]

  • src/wiki.test.ts covers template fill, missing wiki, 400 without project, regenerate persistence, AI fail-open, deep links, and path sanitization.
  • Wiki cases also live in src/status.test.ts and src/cli.test.ts.
  • Provenance: 0046-project-wiki.spec.md.
  • Related compiled views live in src/compiler.ts.