From 187e7fc8d7d96760fb1f89437f61852634c067ef Mon Sep 17 00:00:00 2001 From: prode Date: Sat, 1 Aug 2026 00:19:41 -0300 Subject: [PATCH 1/3] igonre claude setup --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 872d5f6..e13ef87 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,6 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +.claude/ +CLAUDE.md \ No newline at end of file From f70e36b21d8eb252d0c03497671cc4e2b07adfcf Mon Sep 17 00:00:00 2001 From: prode Date: Sat, 1 Aug 2026 01:38:11 -0300 Subject: [PATCH 2/3] docs: make the project directory the unit, and MCP a read-only window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three decision records, and the plan they rewrite. 0013 replaces the workspace container with a plain project directory opened by `ow` in its scope, moves wiki reads onto the filesystem the harness already has open, and leaves MCP one job: consulting another project, read-only, over stdio, with no port and no token. It supersedes 0003 — four of that record's seven operative clauses are void — and narrows 0002, which keeps everything but the container. 0014 puts everything except audio capture in TypeScript and makes the CLI the product's spine, resolving 0010 by applying its own reasoning: that record disclaimed its performance argument, and what remains of it is agnostic of language. 0015 settles where the page convention lives — skills scaffolded by `ow init`, not a generated CLAUDE.md — and states the ageing that scaffolding reintroduces. Reviewed before opening: a first draft claimed a PreToolUse hook cannot see the content it is about to allow, which made the write gate look unrebuildable. It receives the complete tool_input and can deny with a reason, so refusal survives for the file tools; what does not survive is coverage of writes made through the shell. Four documents were corrected. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01C9Vv5jJqcGxrStXhLwbf9b --- .../0003-mcp-as-the-only-bridge-to-the-llm.md | 3 +- ...010-a-derived-index-engine-behind-a-cli.md | 3 +- .../0013-the-project-directory-is-the-unit.md | 196 ++++++++++++++ ...escript-everywhere-except-audio-capture.md | 70 +++++ .../0015-the-convention-ships-as-skills.md | 68 +++++ docs/glossary.md | 27 +- docs/stack.md | 23 +- docs/wiki/changelog.md | 13 + docs/wiki/claude-code-plugins.md | 84 ++++-- docs/wiki/index.md | 5 +- plans/open-wiki.md | 254 +++++++++--------- 11 files changed, 574 insertions(+), 172 deletions(-) create mode 100644 docs/adr/0013-the-project-directory-is-the-unit.md create mode 100644 docs/adr/0014-typescript-everywhere-except-audio-capture.md create mode 100644 docs/adr/0015-the-convention-ships-as-skills.md diff --git a/docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md b/docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md index a96d801..ea5c6b0 100644 --- a/docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md +++ b/docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md @@ -1,5 +1,6 @@ --- -status: accepted +status: superseded +superseded-by: 0013-the-project-directory-is-the-unit --- # 0003 · MCP is the only bridge between the wiki and the LLM diff --git a/docs/adr/0010-a-derived-index-engine-behind-a-cli.md b/docs/adr/0010-a-derived-index-engine-behind-a-cli.md index d7743ac..3478e42 100644 --- a/docs/adr/0010-a-derived-index-engine-behind-a-cli.md +++ b/docs/adr/0010-a-derived-index-engine-behind-a-cli.md @@ -1,5 +1,6 @@ --- -status: proposed +status: superseded +superseded-by: 0014-typescript-everywhere-except-audio-capture --- # 0010 · A derived-index engine as a second Rust binary, behind a CLI diff --git a/docs/adr/0013-the-project-directory-is-the-unit.md b/docs/adr/0013-the-project-directory-is-the-unit.md new file mode 100644 index 0000000..22c9f3f --- /dev/null +++ b/docs/adr/0013-the-project-directory-is-the-unit.md @@ -0,0 +1,196 @@ +--- +status: accepted +--- + +# 0013 · The project directory is the unit, and MCP becomes cross-project consultation + +## Context + +`adr:0002-workspace-as-a-local-markdown-folder` made the workspace a container folder with +one directory per project, and `adr:0003-mcp-as-the-only-bridge-to-the-llm` made MCP the +only way an agent reached any of it. Both were written before anyone checked what the +harness does with a folder that sits outside the project it has open. + +Checked against Claude Code v2.1.x, it does badly: + +- Reaching the folder at all takes `--add-dir` or an `additionalDirectories` setting. +- A `CLAUDE.md` in such a directory **does not load** by default — it needs an environment + variable, and through the `additionalDirectories` setting it loads no configuration at + all. The plan generated exactly that file. +- A plugin cannot ship permission rules, so a deny rule protecting the folder has to be + written into the user's own settings — which, for a folder outside the project, there is + nothing in the product positioned to do. + +Inside the project the harness has the opposite problem, which is to say none. `CLAUDE.md` +and `.claude/rules/*.md` load at session start. `.mcp.json` and `.claude/settings.json` are +picked up from the repository root, so they can be committed and reach everyone who clones. +`Edit(path/**)` deny rules are enforced by the harness rather than requested of the model. +And `Read` and `Grep` already work, over a ripgrep better than any search this product would +ship. + +So MCP was carrying reads that the filesystem carries better, and the container folder was +buying a project switcher nobody needed. + +## Decision + +**A project is a directory.** `ow` invoked inside it opens the application scoped to that +directory, the way `code .` and `idea .` do. There is no workspace container and no +directory of projects owned by the application. A registry of known project paths replaces +it, for the launcher and for name resolution — and it is a cache, never a source of truth: +a moved or deleted directory degrades, it does not corrupt. + +**The harness reads the wiki through the filesystem.** No tool, no protocol, no +configuration. + +**MCP stops being the bridge and becomes the window into other projects.** It is read-only, +runs over stdio, and is spawned by the harness rather than by the application: + +``` +ow mcp --project fenix --read-only +``` + +Four constraints are the decision: + +**One process, one project, chosen by the caller's configuration.** The process is launched +for a single project and cannot reach another. This replaces 0003's "chosen by the +application" with confinement by process. The project is still named — `--project fenix` is +a parameter — but it is fixed for the process's whole life and comes from configuration +rather than from a tool call, so an agent cannot pivot to another base mid-session. That is +the whole of the improvement, and it is smaller than "no parameter at all": the entrypoint +must therefore not import the write path, so that `--read-only` describes what the process +can do rather than what it agrees to do. + +**Stdio, so there is no port and no token.** The consulted project's application is almost +never running — nobody has `fenix` open while working in `payments-api` — so a server the +application starts is a server that is not there. The harness spawning the process removes +the port, the loopback exposure, the mandatory token and the `headersHelper` that was to +deliver it, in one move. + +That win is only real if nothing puts an unauthenticated local listener back. A CLI that +talks to the running application over a socket to pay down cold start is exactly that +listener, so it is a security boundary and not a performance detail: it carries read and +validate, never write, and where the platform offers a named pipe with a restrictive ACL it +uses one. + +**The project is named, not pathed.** A committed `.mcp.json` carrying `C:\dev\fenix` breaks +for everyone who clones it. The argument is the registry name, resolved locally, so the +configuration is both committed and portable. + +**Read tools return whole pages.** A wiki page is a few kilobytes; the index says what +exists and the agent reads what it picked. No passage extraction and no ranking — and with +them, this record closes the embeddings question the plan had left ajar: there are none, and +no vector store, no reranking and no inverted index. + +**The gate's own configuration is outside what the gate lets an agent write.** `.claude/**`, +`.mcp.json` and `CLAUDE.md` sit inside the project directory, and they are executable +configuration: a command to spawn, permission rules, and prompt text loaded into every +collaborator's agent. A write path that reaches them is a write path that edits away its own +restraint — and it does it through a change that reads as documentation in review. Path +confinement is to the project *minus* those, and the check resolves the real path before +comparing, because on Windows a directory junction needs no privilege and is not a symlink. + +A project's `.mcp.json` lists *other* projects, never itself. Locally the filesystem reaches +everything, and structural queries go through the CLI. + +## Consequences + +Of `adr:0003-mcp-as-the-only-bridge-to-the-llm`, three clauses survive intact and are the +substance of that record: **the application does not call an LLM**, **the agent writes the +pages**, and **write-time validation is what replaces the writer**. Four are void: HTTP on +the loopback started and stopped by the application; MCP exposing ingest and write; the +served project being chosen by the application; and the mandatory token. That is why this +record supersedes it rather than narrowing it. + +`adr:0002-workspace-as-a-local-markdown-folder` is narrowed, not superseded. It loses the +container — a workspace with one directory per project — and keeps everything that made it: +files rather than a database, the application never touching git, and atomic writes, +snapshots, the operation log and undo as the net. + +Because the token is gone, the application holds **exactly one secret again**, the +transcription credential. That is what 0003 claimed and what +`adr:0007-plaintext-credentials-in-the-config` had to walk back to two. + +That same record now carries weight it was not written to carry. Its clause "never inside +the workspace" was mild advice when the workspace was a folder the application owned; with +the project directory likely being a git repository, a secret written there is a secret in +history forever. The configuration therefore splits: project settings — the content +language, the conventions — committed and shared with the team, and every secret in the +application's own data directory, keyed by project path. + +Three things about that split are decided here rather than left to taste. **It is +unconditional**: a secret is never written into a project directory, whether or not one is a +repository today, because `git init` a week later turns a conditional rule into a leak. +**The committed half carries no local path**, for the same reason `.mcp.json` names a +project rather than a directory — a path is both a portability bug and someone's username. +And **the committed half is a closed schema**: an unknown key is refused rather than +tolerated, so the first feature that wants a per-project token cannot quietly put one there. + +`adr:0007-plaintext-credentials-in-the-config` keeps its decision — plaintext, in the +application's data directory, with DPAPI as the named successor — but the file it sketches +is now historical in two places: there is no `mcp` section, because the port and the token +are gone, and `workspace_path` becomes the registry of +`adr:0013-the-project-directory-is-the-unit`. Its two operational rules gain a reader it did +not have: the CLI's stderr is consumed by an agent and travels to a model provider, so the +entrypoints that run under a hook or as `ow mcp` must not read the credential section at +all. + +Where the project is in git, the user's git becomes a recovery path the product does not +have to build, and the application still knows nothing about it. Where it is not, `.state/` +is the whole net, so nothing here may be read as delegating recovery to a tool this project +declares out of scope. + +`adr:0008-content-language-is-a-setting-english-by-default` loses one clause and keeps its +decision. The content language was "workspace-wide, not per project", on the reasoning that +a workspace holding projects in different languages was an axis nobody had asked for; with +no workspace, the setting is necessarily per project and lives in the committed half of that +project's configuration. What it reaches is unchanged — the transcription hint, and the +convention text the agent reads. + +**The gate moved, and it is rebuildable — for the file tools.** With writes gone from MCP, +the agent writes the wiki with its own tools. A `PreToolUse` hook receives the complete +`tool_input` — for `Write` that includes `content`, for `Edit` the strings — and can answer +`permissionDecision: deny` with a reason the agent reads. So refusal survives intact: the +malformed page never reaches the disk, and 0002's corollary that the defence has to be at +the entrance still holds. This is the single most load-bearing fact in the record, and an +earlier draft had it backwards. + +What does *not* survive is the completeness of that gate. A hook matches a tool, so a write +that arrives some other way is not gated: `echo > wiki/page.md` through Bash carries a +command string, not page content, and no content-validating hook can fire on it. Denying +`Edit(wiki/**)` does not constrain Bash either — permission rules are per tool. Whatever +9.5 chooses therefore has to answer for the shell, not only for `Edit` and `Write`, and +`PostToolUse` validation plus group 7's checks are what cover what the entrance misses. + +Hooks are also Claude Code's mechanism and nobody else's. In a harness without them the +gate is whatever the CLI verb enforces, and where neither is in place group 7 is the net of +record. + +The operation log changes meaning with it. It was *operations we performed*; it becomes +*changes we observed*, and undo covers only what the application's editor or a hook saw. A +page edited in Obsidian falls outside it. + +## The two questions this record does not answer + +**Which write gate, and how does it cover the shell?** A `PreToolUse` hook that validates +`tool_input` and denies is the strongest of the options and the cheapest, but it is Claude +Code only and it does not see a write made through Bash. A CLI verb that is the only way to +write covers any harness that has a shell, but only if something stops the shell writing the +file directly, which a per-tool deny rule does not. They compose — and the composition, not +either half, is what has to be chosen. It cannot ship open. + +**What of `raw/` and `.state/` enters git, and who reads it then?** The cost side is easy: +an hour of Opus is ~11 MB, written once and never modified, and committing `text.md`, +`timeline.*` and `manifest.json` keeps a cited passage readable by anyone who clones, while +refusing the media means a provenance link opens nothing for everyone except the person who +recorded it — a real loss against `adr:0006-opus-as-the-provenance-format`. + +The side that matters more is disclosure, and it is the reason this question is the more +irreversible of the two. `raw/` holds recorded meeting audio and the verbatim text rendered +from its timeline; +`.state/` holds a snapshot of every page before every write, which means a redaction lives +on there in its unredacted form. Committing either puts that content in every clone, every +fork and every CI checkout, permanently and beyond recall — and it dissolves the promise +`docs/stack.md` makes for whisper.cpp, that the audio never leaves the machine. The default +therefore has to be deny: `ow init` writes the ignore entries and the user opts *in*, rather +than discovering after a push. What is still open is exactly which paths, and whether +committing the media is offered at all. diff --git a/docs/adr/0014-typescript-everywhere-except-audio-capture.md b/docs/adr/0014-typescript-everywhere-except-audio-capture.md new file mode 100644 index 0000000..bda5f9b --- /dev/null +++ b/docs/adr/0014-typescript-everywhere-except-audio-capture.md @@ -0,0 +1,70 @@ +--- +status: accepted +--- + +# 0014 · TypeScript everywhere except audio capture + +## Context + +`adr:0010-a-derived-index-engine-behind-a-cli` proposed a second Rust binary, `ow.exe`, +owning the derived view of a project behind a command line. It was never accepted, and it +left two questions open. + +Meanwhile `adr:0013-the-project-directory-is-the-unit` turned that CLI from an optimisation +into the product's spine: it is the launcher, the project registry, the MCP process, the +integrity checker and — depending on which write gate is chosen — the way a page is written +at all. + +The repository is already a pnpm workspace with strict TypeScript, Vitest and a coverage +floor enforced per package. + +## Decision + +**TypeScript and Node for everything except audio capture.** Rust keeps exactly the +recorder: COM against WASAPI, a clock of its own, no GC pause for an hour. + +The CLI is a TypeScript package. It is published to npm, so `npx open-wiki init` works with +nothing installed, and the desktop installer puts an `ow` shim on `PATH` that invokes the +installed application — which is how `code` works, and it ships no second runtime. + +## Consequences + +This does not overrule 0010's reasoning; it applies it. That record disclaimed its own +performance argument in as many words — *"anyone claiming this engine is needed for speed at +MVP scale is wrong. What justifies it is having one owner for the derived view and a +contract narrow enough to test"* — and that justification is agnostic of language. What it +loses is the second binary, the two artifacts whose versions must agree, and the mismatched +pair that fails looking like corrupted data. + +`adr:0005-wasapi-capture-in-a-minimal-sidecar` becomes literally true again. Its sentence +"everything that is not audio capture lives on the JavaScript side" was being narrowed by +0010; it is not any more. + +One language means one test runner, one lint, one coverage story, and the core — validation, +frontmatter, wikilinks, markdown — is a package imported by the Electron main process, the +CLI and the MCP process rather than a contract between two languages. The plan's rule that +there is one implementation of project access, used by every surface, stops being a +discipline and becomes a fact of the build. The MCP SDK is TypeScript-first, which the Rust +version would have given up. + +**Cold start is the real cost, and it is the one 0010 named.** A CLI carrying a markdown +parser, a YAML parser and the MCP SDK reaches several hundred milliseconds per invocation, +and under a hook that fires on every page write that is a tax on the agent's edit loop. +Two things pay it down: bundling to a single file, which removes module resolution, and +talking to the running application over a local socket when there is one — which there +usually is, because `ow` is what opened it. + +That socket is a local listener, and this project has already paid once for treating one as +plumbing. `adr:0013-the-project-directory-is-the-unit` removed an authenticated loopback +port and would be giving the exposure straight back if a cold-start optimisation quietly +reintroduced an unauthenticated one carrying the write path. It carries read and validate +only, and the constraint belongs to that record rather than to this one. + +That second path cannot be the only one. `npx open-wiki init` runs where nothing is +installed, so the CLI has to work standalone as well, and both paths have to produce the +same answer. + +Publishing to npm is a distribution channel `adr:0009-distribution-through-github-releases` +does not cover. There are now two artifacts with two release paths — an installer from a +`v*` tag and a package on npm — and a version skew between them is a real failure mode, not +a hypothetical one. diff --git a/docs/adr/0015-the-convention-ships-as-skills.md b/docs/adr/0015-the-convention-ships-as-skills.md new file mode 100644 index 0000000..1dac11d --- /dev/null +++ b/docs/adr/0015-the-convention-ships-as-skills.md @@ -0,0 +1,68 @@ +--- +status: accepted +--- + +# 0015 · The convention ships as skills scaffolded into the project + +## Context + +`adr:0003-mcp-as-the-only-bridge-to-the-llm` moved the page convention out of code and into +prose, and named the consequence: *"if it is vague, two agents write two different wikis in +the same folder and nothing breaks."* Where that prose lives has been open since: the plan +generated a `CLAUDE.md` inside each project and also said a skill was the rival home, with +one of the two to become a pointer to the other. + +`adr:0013-the-project-directory-is-the-unit` changed the evidence twice over. It removed the +argument that a generated `CLAUDE.md` might never load — inside the project it always loads. +And it made the same directory the place a harness already reads `.claude/skills/`. + +What decides it is context. A `CLAUDE.md` is loaded into every session whether or not the +session is about writing a wiki page; a skill loads when the concern is live. This project +holds that principle about its own methodology, in its own `CLAUDE.md`: *"every session pays +for it in context, and model accuracy degrades as context grows — non-uniformly, well before +any documented limit."* Applying it to the product and not to ourselves would be incoherent. + +## Decision + +`ow init` scaffolds the convention as skills in the project's `.claude/skills/` — one for +the **wiki** and one for **codewiki** — and writes neither if it is already there. + +This is what `scc` does to this repository, and the product does the same thing to the +user's. + +The generated `CLAUDE.md` of task 9.14 is not a second home. Where it says anything about +the page schema or the method, it points at the skill. + +## Consequences + +The plan's open question is answered, and the drift +`adr:0003-mcp-as-the-only-bridge-to-the-llm` warned about has one home instead of two. + +One home means one, which rules out something the distribution plan was reaching for: a +Claude Code plugin that also carries these skills. A plugin-installed skill and a +project-scaffolded skill are two copies of one convention, updated by different mechanisms, +and neither is obviously the pointer — the same drift, with both halves now skills. What a +plugin may carry is the hooks, and the scaffolding command that writes the skills; not the +skills themselves. + +Scaffolding **codewiki** into a user's project widens what the product is for. The wiki was +sources, meetings and decisions; it now also narrates the project's code, with sections +citing exact line ranges. Those citations go stale loudly rather than quietly — which is the +point of them — but it means the integrity checks of group 7 need a code-citation variant +beside the provenance one, and the page types grow beyond `projects/`, `people/` and +`topics/`. + +**Generating into the project reintroduces the ageing the skill was supposed to avoid.** +The plan's argument against the generated `CLAUDE.md` was that it is "a copy per folder that +ages from the moment it is written", and a skill written by `ow init` is a copy per folder +that ages from the moment it is written. Refusing to overwrite is right — the user edits +these files and losing that is worse — but it means a project scaffolded at v0.3 keeps a v0.3 +convention forever, silently, while the validators enforce v0.7. + +## The question this record does not answer + +**How a scaffolded skill learns it is old.** A version marker in the generated file plus an +`ow init` that reports staleness instead of overwriting is the obvious shape, and there are +others: a skill thin enough to be a pointer at a CLI command that carries the authoritative +text, or an upgrade path that diffs and asks. Nothing here chooses, and until something +does, the ageing above is unmitigated rather than accepted. diff --git a/docs/glossary.md b/docs/glossary.md index 1f38242..f950831 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -4,23 +4,36 @@ One canonical term per concept, and the synonyms to avoid. These terms appear in file names, in JSON schemas, in the MCP server's tools and in the pages the agent writes — which is why each one is listed in the exact form it takes in the schemas. -- **workspace** — the root folder chosen by the user, with one directory per project. Avoid: vault, library -- **project** — a project inside the workspace, with its own `raw/`, `wiki/`, `.state/` and `CLAUDE.md`. The MCP server serves exactly one at a time. Avoid: namespace +- **project** — a directory holding `raw/`, `wiki/`, `.state/` and its scaffolded skills, opened by `ow` in its scope — `adr:0013-the-project-directory-is-the-unit`. There is no folder above it that the application owns. Avoid: vault, namespace +- **registry** — the list of known project paths, used by the launcher and to resolve a project name in `.mcp.json`. A cache, never a source of truth: a directory that moved degrades the entry, it does not corrupt the project. Avoid: catalogue - **source** — any entry in `raw/`: an uploaded file or a recording. Immutable once written, and named for what it is — `adr:0011-sources-are-named-by-what-they-are`. Avoid: attachment - **source id** — a source's directory name, and what a provenance link points at. Derived from the source's name when it is written, and never changed after. - **title** — a source's readable name, held in its `manifest.json` and correctable at any time. It is not the source id and may drift from it. -- **recording** — one audio capture, named for the occasion and the date it happened, as in `fenix-weekly-2026-07-31`. Avoid: session +- **recording** — one audio capture, named for the occasion and the date it happened, as in `fenix-weekly-2026-07-31`. - **track** — one of the two captured streams, `mic` or `system`. Avoid: feed - **timeline** — the two tracks merged and ordered by real time, in `timeline.json`, and written out as `timeline.vtt` for anything that reads WebVTT. Avoid: transcript - **transcription journal** — the per-chunk record of a transcription in progress, in the recording's directory, that makes an interrupted run resumable — `adr:0012-transcription-is-a-journalled-serial-pipeline`. It is not the operation log of `.state/` and not the wiki's `log.md`. - **time map** — the table converting an instant of the compressed audio into a real instant, in `timemap.json`. Avoid: offset table - **chunk** — a ~10-minute piece cut at a silence point; the unit of transcription and of retry. Avoid: slice - **ingest** — the path from a source to being available as `text.md` in the project. It ends there: writing pages is the agent's job. Avoid: sync -- **entity** — a person, project or topic with a page of its own, identified by `id` in the form `type:slug`. Avoid: subject +- **entity** — a person, project, topic or narrated code area with a page of its own, identified by `id` in the form `type:slug`. Avoid: subject - **claim** — a statement recorded on a page, of type `decision`, `fact`, `action_item` or `open_question`, always with a citation. Avoid: insight - **supersession** — marking an earlier decision as replaced, preserving it struck through with a date and a link to the one replacing it. Avoid: override - **provenance link** — the link that opens the source where a claim came from: an instant for audio, a page for a PDF. Avoid: backlink +- **write gate** — whatever makes an agent's write to `wiki/` pass through the group 5 validations now that MCP no longer writes. Which mechanism it is has not been chosen — `adr:0013-the-project-directory-is-the-unit`. -> **`workspace` has another sense in `docs/stack.md`**, where "pnpm workspaces" names the -> way the source monorepo is divided. They are different things: one is the user's folder, -> the other is a tool for whoever develops the application. +> **`session` stopped being a synonym to avoid.** It was listed against `recording`, from +> when the only sessions in this product were audio ones. +> `adr:0013-the-project-directory-is-the-unit` moved the product inside the harness's +> working directory, and "session" now regularly and correctly means one run of Claude +> Code — in the ADRs, in the hook mechanics, in what a skill costs to load. A rule that +> fires on every sentence about the harness gets ignored within a week, and an ignored +> check is worse than none, so `recording` keeps the canonical name and gives up the +> synonym. + +> **`workspace` is no longer a term of this domain.** The user-facing sense — a folder above +> the projects — was removed by `adr:0013-the-project-directory-is-the-unit`. It stays in +> live use in `docs/stack.md`, where "pnpm workspaces" names the way the source monorepo is +> divided, and it stays throughout the ADRs written before the change, including the title +> and filename of `adr:0002-workspace-as-a-local-markdown-folder`, which several records +> cite. Those are history and are not to be edited. New writing does not use the word. diff --git a/docs/stack.md b/docs/stack.md index 7f1d47c..dedbb51 100644 --- a/docs/stack.md +++ b/docs/stack.md @@ -9,50 +9,53 @@ adding each one a deliberate act. ## Capture -- **Rust** — the recorder has to speak COM to WASAPI and hold a clock of its own for an hour with no GC pause. Standalone binary, no runtime to install. +- **Rust** — the recorder has to speak COM to WASAPI and hold a clock of its own for an hour with no GC pause. Standalone binary, no runtime to install. It is the **only** thing in this product written in Rust — see `adr:0014-typescript-everywhere-except-audio-capture`. - **`wasapi` crate** — direct access to WASAPI, including loopback of the render device, which is exactly what ffmpeg on Windows does not have. See `adr:0005-wasapi-capture-in-a-minimal-sidecar`. ## Pipeline -- **TypeScript** — the pipeline lives in Electron's main process; one language across UI and orchestration avoids a process boundary that does not pay for itself. -- **Node.js** — Electron's runtime, already present; no extra process. +- **TypeScript** — everything that is not audio capture: the pipeline, the UI, the CLI and the MCP process. One language means one core imported by all three rather than a contract between two languages — see `adr:0014-typescript-everywhere-except-audio-capture`. +- **Node.js** — Electron's runtime, already present; and what the CLI runs on when it is invoked with no application installed. - **ffmpeg (vendored)** — downmix, VAD and Opus encode in a single tool. Bundled with hash verification, never downloaded at run time. - **Opus 24 kbps** — the only encoding that puts an hour of meeting under the 25 MB upload limit. A requirement, not an optimisation. See `adr:0006-opus-as-the-provenance-format`. -- **Groq `whisper-large-v3-turbo`** — the default STT provider: ~US$ 0.04 per hour and ~228x real time, multilingual, which is what lets the content language be a setting rather than a fixed choice — see `adr:0008-content-language-is-a-setting-english-by-default`. It is the **only** credential the application holds — see `adr:0003-mcp-as-the-only-bridge-to-the-llm`. +- **Groq `whisper-large-v3-turbo`** — the default STT provider: ~US$ 0.04 per hour and ~228x real time, multilingual, which is what lets the content language be a setting rather than a fixed choice — see `adr:0008-content-language-is-a-setting-english-by-default`. It is the **only** credential the application holds, now that the MCP token went away with the port — see `adr:0013-the-project-directory-is-the-unit`. - **whisper.cpp** — optional local provider, for anyone who requires that the audio never leave the machine. It is what holds up the privacy argument without rewriting the pipeline. ## Application -- **Electron** — desktop UI in the same TypeScript as the pipeline, with filesystem and child-process access and no native bridge. +- **Electron** — desktop UI in the same TypeScript as the pipeline, with filesystem and child-process access and no native bridge. Opened scoped to one project directory, by `ow`. - **React** — the UI has real state (a recording in progress, sources crossing the flow, pages changing while the agent writes); the ecosystem around Electron is larger than any alternative's, and that matters more than preference. - **Vite** — renderer build and reload, fast enough that there is no temptation to skip the UI while iterating. - **markdown-it** — renders the wiki pages in the embedded browser; its plugin model is what allows teaching it `[[wikilink]]` and `rec://` without rewriting the parser. - **pnpm workspaces** — a monorepo of several TS packages with unhoisted dependencies, which is what stops a package from importing what it never declared. +- **esbuild** — bundles the CLI to a single file. Not a preference: an unbundled CLI pays module resolution on every invocation, and a hook fires it on every page write — see `adr:0014-typescript-everywhere-except-audio-capture`. -The workspace does not use git: `adr:0002-workspace-as-a-local-markdown-folder`. This -project's source does, and that is not a technology adopted by the product. +The application neither reads nor writes a git repository — +`adr:0002-workspace-as-a-local-markdown-folder`. The project directory usually *is* one, +and that is the user's business, not a technology this product adopted. ## Text extraction from sources Each source adapter has a single responsibility — becoming `text.md` with provenance anchors, and the path stops writing there — see -`adr:0003-mcp-as-the-only-bridge-to-the-llm`. +`adr:0013-the-project-directory-is-the-unit`. - **pdf-parse** — text and page boundaries of a PDF; it is the page number that makes the citation possible, and without it the source is of no use. - **mammoth** — DOCX to markdown preserving the heading hierarchy, which is the anchor equivalent to a PDF's page. ## MCP server -- **MCP TypeScript SDK** — the product's interface, not an accessory: it is how the agent reads, ingests and writes. It does not couple the product to a vendor, and we are not building a search engine. See `adr:0003-mcp-as-the-only-bridge-to-the-llm`. +- **MCP TypeScript SDK** — how a project with no wiki of its own consults one that has: read-only, over stdio, spawned by the harness. It is not how the local wiki is reached, because the harness already has the directory open. See `adr:0013-the-project-directory-is-the-unit`. ## Testing and verification - **Vitest** — runner for the TS packages: it runs a single file fast enough for the per-task loop, which is what scoped verification demands. - **`@vitest/coverage-v8`** — Vitest's V8 coverage provider, and the source of the `coverage-summary.json` that CI reads to enforce the 76% floor per package. V8 rather than Istanbul because it needs no instrumentation step. -- **`cargo test`** — what already ships with Rust; adding a second runner buys nothing. +- **`cargo test`** — what already ships with Rust; adding a second runner buys nothing. It covers the recorder and nothing else. - **GitHub Actions** — CI on `windows-latest`, which is the only platform the product supports. One job per workspace package, so a package below the coverage floor fails on its own instead of hiding behind a well-tested neighbour. It also builds and publishes the release — see `adr:0009-distribution-through-github-releases`. ## Distribution - **electron-builder** — packs the Electron application, ffmpeg and `recorder.exe` into one NSIS installer, and is what reads `CSC_LINK` to sign it the day there is a certificate. See `adr:0009-distribution-through-github-releases`. - **GitHub Releases** — where the installer is downloaded from. No host of ours to run, and a stable URL with a published hash is exactly what a winget or Scoop manifest needs. +- **npm** — a second channel, carrying the CLI alone, so `npx open-wiki init` scaffolds a project with nothing installed. It is what makes the convention reachable without the desktop application, and it is the reason two artifacts now ship from one tag — see `adr:0014-typescript-everywhere-except-audio-capture`. diff --git a/docs/wiki/changelog.md b/docs/wiki/changelog.md index 10efffd..cf79904 100644 --- a/docs/wiki/changelog.md +++ b/docs/wiki/changelog.md @@ -2,6 +2,19 @@ What changed in the wiki, newest first. +## 2026-08-01 + +- Revised [[claude-code-plugins]] against `adr:0013-the-project-directory-is-the-unit`. + Its `headersHelper` finding no longer applies to this product — a stdio server has no + headers and no token — and it gains what a plugin *cannot* carry: permission rules and + `additionalDirectories`. That absence is what stops the product from shipping the deny + rule its write gate may need. +- The page also records the hook mechanics the write gate rests on: a `PreToolUse` hook + receives the tool's complete `tool_input` and can deny with a reason, so a malformed page + can be refused before it lands. A first pass through this recorded the opposite, and + `adr:0013-the-project-directory-is-the-unit` was drafted against the wrong version before + it was checked against the hooks reference. + ## 2026-07-31 - The project is **open-wiki**, not project-wiki. Renamed throughout, including diff --git a/docs/wiki/claude-code-plugins.md b/docs/wiki/claude-code-plugins.md index 6861212..271ac4a 100644 --- a/docs/wiki/claude-code-plugins.md +++ b/docs/wiki/claude-code-plugins.md @@ -2,11 +2,13 @@ A plugin is a self-contained directory that adds skills, agents, hooks, MCP servers and LSP servers to Claude Code, installed with one command instead of assembled by hand. It -matters here because this product's whole interface is an MCP server plus a written -convention, and a plugin is the only shipping format that carries both at once. +matters here because this product's interface is a written convention plus a read-only MCP +server, and a plugin is the only shipping format that carries both at once. -Read against `adr:0003-mcp-as-the-only-bridge-to-the-llm`, which is what makes the MCP -server the product rather than an accessory. +Read against `adr:0013-the-project-directory-is-the-unit`, which moved the local wiki out +of MCP's reach and left the protocol one job: consulting *another* project. Much of what +follows was distilled when the plan still had an HTTP server with a token, and the sections +below say where that no longer applies. ## The shape on disk @@ -47,7 +49,7 @@ servers only in specific fields — `command`, `args` and `env` for stdio server Installation copies the plugin into a cache, so nothing may reference a path outside its own directory. `../shared` does not survive the copy. -## `headersHelper` — the finding that changes a task +## `headersHelper` — a finding this project no longer needs An HTTP MCP server in a plugin can name a command that produces its headers: @@ -68,14 +70,12 @@ ten-second timeout, and for a plugin-provided server runs with its working direc to the plugin root. It runs fresh on every connection, and since v2.1.193 Claude Code re-runs it and retries once when a tool call comes back `401` or `403`. -**This removes the pasted configuration.** Task 9.13 exists because the MCP token is -generated per workspace and has to reach the harness somehow, and pasting a JSON block -containing a bearer token is the current answer. With a helper, the plugin ships a static -file that contains no secret; the helper reads the token from the application's own -`config.json` — the same file `adr:0007-plaintext-credentials-in-the-config` already -puts it in — and hands it over at connection time. Rotating the token then needs no edit -anywhere, and a token that changed while a harness was connected recovers on the retry -instead of failing until someone notices. +**This solved a problem the product then stopped having.** It was distilled to remove the +pasted bearer token of what was task 9.13, back when the server ran over HTTP on the +loopback. `adr:0013-the-project-directory-is-the-unit` replaced that with stdio, spawned by +the harness — and a stdio server has no headers, no port and no token to deliver. The +mechanism is recorded here because it is a real Claude Code capability and the next person +to reach for an HTTP MCP server will want it; it is not something this plan uses. One constraint to design around: a plugin's `headersHelper` cannot read the plugin's own `${user_config.*}` values, because the command goes through a shell. The helper has to @@ -118,28 +118,56 @@ it is checked into the repository and reaches everyone who clones it — and the servers it declares go through per-server approval, LSP servers wait for workspace trust, and background monitors do not load at all. -For this product the marketplace route is the one that matters, because the audience -installs a desktop application and is not cloning anything. +Both routes now matter, which was not true when this page was written. The audience installs +a desktop application, so the marketplace reaches them — but after +`adr:0013-the-project-directory-is-the-unit` they also work in repositories where +`.claude/` and `.mcp.json` are committed and reach everyone who clones, and +`npx open-wiki init` targets someone with nothing installed at all. -## What this does not solve +## What a plugin carries, and the two things it does not -A plugin distributes the skill and the server *configuration*. It does not distribute the -server: the MCP endpoint is the desktop application, which the user still installs from -`adr:0009-distribution-through-github-releases`. So the plugin is worth exactly one thing -— removing the paste step and the copy of the token that comes with it. - -It also raises a question this project has not answered. `adr:0003-mcp-as-the-only-bridge-to-the-llm` -puts the page convention in a `CLAUDE.md` generated inside each project folder, and a -plugin skill is a second place the same convention could live — versioned with the -product and updated by an upgrade, rather than regenerated per folder. Two homes for one -convention is the drift that ADR warned about, so one of them has to become a pointer to -the other. +| Component | In a plugin? | +|---|---| +| Skills, agents, hooks, LSP servers, MCP servers | yes | +| Executables in `bin/`, added to the Bash `PATH` | yes | +| Background monitors | yes | +| Default settings | only the `agent` and `subagentStatusLine` keys | +| **Permission rules — `allow`, `ask`, `deny`** | **no** | +| **`additionalDirectories`** | **no** | + +The two absences are the ones that reach this plan. + +**A plugin cannot ship the deny rule**, so nothing the product distributes can be what +stops an agent writing `wiki/` outside the validations. If the write gate of task 9.5 turns +out to be `Edit(wiki/**)` in `deny`, that rule has to be written into the user's own +settings — by `ow init`, or by the user — and the plugin can only carry the hooks beside +it. Hooks it can carry, which is why the hook-based gate is the one a plugin could deliver +whole. + +**`bin/` on the `PATH` is worth more than it looks.** A plugin can put a CLI in front of +the agent without the desktop application being installed, which is the same reach +`npx open-wiki` has and the reason +`adr:0014-typescript-everywhere-except-audio-capture` cares that the CLI runs standalone. + +What a plugin still does not distribute is the wiki itself: an MCP server consulting +`fenix` needs `fenix` checked out somewhere on that machine, and the registry is what turns +a committed project *name* into that local path. + +The question this page used to leave open — whether the convention lives in a generated +`CLAUDE.md` or in a skill — was closed by `adr:0015-the-convention-ships-as-skills`. It is +a skill, scaffolded by `ow init`, and the `CLAUDE.md` points at it. ## Sources - - - +- - -Read 2026-07-31, against Claude Code v2.1.x. +Read 2026-07-31, against Claude Code v2.1.x. Revisited 2026-08-01 for what a plugin cannot +carry, and for the hook mechanics the write gate rests on: a `PreToolUse` hook receives the +tool's complete `tool_input` — including `content` for `Write` and the strings for `Edit` — +and can answer `permissionDecision: deny` with a reason the agent reads, so a write can be +refused before it lands. A first pass recorded the opposite, and three documents were +written against it before it was checked. diff --git a/docs/wiki/index.md b/docs/wiki/index.md index 5bbdeff..3246726 100644 --- a/docs/wiki/index.md +++ b/docs/wiki/index.md @@ -5,5 +5,6 @@ from here; a page that is not is a page nobody will find again. ## Distribution and integration -- [[claude-code-plugins]] — how a Claude Code plugin is built, installed and distributed, - and the one thing shipping one would buy this product. +- [[claude-code-plugins]] — how a Claude Code plugin is built, installed and distributed; + the two things it cannot carry, which is why the product cannot ship its own write gate + whole; and the hook mechanics that gate rests on. diff --git a/plans/open-wiki.md b/plans/open-wiki.md index 8e911bf..e75c78b 100644 --- a/plans/open-wiki.md +++ b/plans/open-wiki.md @@ -6,8 +6,8 @@ ci: no-wait # Open Wiki — desktop An open source desktop application (Windows 10/11, Apache-2.0) that **centralises a -project's documentation sources in a local folder and serves that folder over MCP as a -wiki the AI agent reads and writes.** +project's documentation sources inside the project's own directory, as a wiki the AI agent +already has open.** Today the user has a project's documentation scattered: an architecture PDF, a requirements `.docx`, decisions that exist only in a recorded meeting. None of it answers @@ -16,13 +16,16 @@ by an agent without someone pasting it all into a prompt by hand. The application does three things and refuses the rest: it **takes in sources** (a file or a recording) and reduces them to text with provenance anchors; it **stores the wiki** as -validated markdown; and it **serves one project over MCP** to Claude Code, Cursor or any -harness. +validated markdown; and it **lives inside the project directory**, which is where the +harness is already working — so reading it needs no protocol at all. + +`ow` invoked in a directory opens the application scoped to it, the way `code .` does — +`adr:0013-the-project-directory-is-the-unit`. MCP is not how the local wiki is read; it is +how **another** project is consulted, read-only, by a project that has no wiki of its own. **The application calls no LLM.** Reading the source text, applying the LLM-Wiki -methodology and writing the pages is the agent's job, over MCP — the only bridge between -the wiki and a model. The application does not write content; it validates what comes in -and records everything that changes. See `adr:0003-mcp-as-the-only-bridge-to-the-llm`. +methodology and writing the pages is the agent's job. The application does not write +content; it validates what comes in and records everything that changes. ## Out of scope @@ -31,76 +34,81 @@ and records everything that changes. See `adr:0003-mcp-as-the-only-bridge-to-the - A hosted service, accounts, authentication of our own, multi-tenancy or telemetry — `adr:0001-no-backend-byok`. - A Notion-style block editor — `adr:0004-markdown-editing-without-blocks`. - Real-time collaboration, comments, permissions. -- Embeddings or a vector store. Full-text search over the files, yes. -- An inverted index and a graph of the project, **under review** — `adr:0010-a-derived-index-engine-behind-a-cli` proposes both, in a second Rust binary driven by a CLI. Until that record is accepted or rejected, this line still refuses them. -- Versioning of the workspace — `adr:0002-workspace-as-a-local-markdown-folder`. +- Embeddings, a vector store, reranking or an inverted index. Lexical search over the files, yes; the agent's own grep inside the project, mostly. +- Versioning done by the application. The project directory is often a git repository and the user's git is welcome to it — the application still knows nothing about it, per `adr:0002-workspace-as-a-local-markdown-folder`. - macOS and Linux. - Real-time transcription, ML diarisation, a bot that joins the meeting. - Pasting loose text as a source. In the MVP there are two kinds of source: a file and a recording. ## Done when -The user opens the application on an empty folder, creates the project, uploads a PDF and -records an hour-long meeting — pausing halfway — and clicks transcribe. The sources screen -shows both, with the text ready in `raw/`. They start the MCP server for that project, -paste the configuration into Claude Code, and ask for the wiki to be built from the -sources. The pages appear in the application while the agent writes; a write that departs -from the schema is refused with a reason; and a follow-up question about the state of the +The user runs `ow` inside a project they already work in, uploads a PDF and records an +hour-long meeting — pausing halfway — and clicks transcribe. The sources screen shows both, +with the text ready in `raw/`. They ask Claude Code, in that same directory, to build the +wiki from the sources; the agent reads `raw/` and writes `wiki/` with its own tools, a +write that departs from the schema is refused or reverted with a reason, and the pages +appear in the application while it works. A follow-up question about the state of the project is answered by citing the pages, with a link that opens the source at the right -instant. +instant. In a second repository with no wiki of its own, an agent answers the same question +by consulting the first over MCP. ## Decided and not up for discussion -Apache-2.0 · Windows only in the MVP · no backend · the application calls no LLM · the -workspace is a local folder, no git and no remote · sources stay immutable in `raw/` · -MCP over local HTTP, with read, ingest and write, serving one project at a time chosen by -the application · the application's only credential is the transcription one · the content -language is a setting, English by default, with Brazilian Portuguese and Spanish available +Apache-2.0 · Windows only in the MVP · no backend · the application calls no LLM · a +project is a directory, opened by `ow` in its scope · sources stay immutable in `raw/` · +MCP is read-only, over stdio, spawned by the harness, and serves one project that is never +the one the harness has open (`adr:0013-the-project-directory-is-the-unit`) · TypeScript +everywhere except audio capture (`adr:0014-typescript-everywhere-except-audio-capture`) · +the convention ships as skills (`adr:0015-the-convention-ships-as-skills`) · the +application's only credential is the transcription one · the content language is a setting, +English by default, with Brazilian Portuguese and Spanish available (`adr:0008-content-language-is-a-setting-english-by-default`) · audio capture through WASAPI directly, with pause · Opus 24 kbps as the provenance format. -**Git belongs to the code, not to the product.** This repository is versioned; the user's -workspace is not. +**Git belongs to the user, not to the product.** The project directory is usually +versioned, and that is the user's business; the application neither reads nor writes a +repository. -## The workspace +## The project directory ``` -/ - fenix/ one project - raw/ sources, immutable once written and named for what they are - fenix-weekly-2026-07-31/ a recording, named for the occasion and its date - manifest.json · mic.opus · system.opus - timeline.json · timeline.vtt · text.md - journal.json · *.wav only until transcription seals the source - arquitetura-fenix.pdf/ an uploaded file, keeping its filename - manifest.json · source.pdf · text.md - wiki/ primary content, written by the agent and by the user - index.md · changelog.md · log.md - projects/*.md · people/*.md · topics/*.md - .state/ snapshots and operation log; not content - CLAUDE.md schema and methodology, for the agent operating the folder - atlas/ - ... +fenix/ a project — usually a repository the user already has + raw/ sources, immutable once written and named for what they are + fenix-weekly-2026-07-31/ a recording, named for the occasion and its date + manifest.json · mic.opus · system.opus + timeline.json · timeline.vtt · text.md + journal.json · *.wav only until transcription seals the source + arquitetura-fenix.pdf/ an uploaded file, keeping its filename + manifest.json · source.pdf · text.md + wiki/ primary content, written by the agent and by the user + index.md · changelog.md · log.md + projects/*.md · people/*.md · topics/*.md · codewiki/*.md + .state/ snapshots and operation log; not content + .claude/skills/ the wiki and codewiki conventions, scaffolded by `ow init` + .mcp.json other projects this one consults; never itself + CLAUDE.md short, and pointing at the skills ``` --- ## 1 — Foundation -- [ ] 1.1 (Unit) Set up the monorepo: a pnpm workspace for `apps/desktop` and `packages/*`, a cargo workspace for `crates/recorder`, shared strict TypeScript +- [ ] 1.1 (Unit) Set up the monorepo: a pnpm workspace for `apps/desktop`, `packages/*` and `packages/cli`, a cargo workspace for `crates/recorder`, shared strict TypeScript - [ ] 1.2 (Unit) Fill in `.claude/rules/project.md` with the real build, test, scoped test, lint and format commands - [ ] 1.3 (Unit) CI on GitHub Actions on `windows-latest`: Rust and TS build, tests with a coverage floor of 76% per package, lint - [ ] 1.4 (Unit) Remove `.claude/` and `CLAUDE.md` from `.gitignore` — the methodology is versioned with the code, and today it exists only on this machine - [ ] 1.5 (Unit) Bundle `vendor/ffmpeg` through a download script with hash verification -## 2 — Workspace, projects and safe writing +## 2 — Project directory and safe writing -- [ ] 2.1 (Unit) Open or create a workspace: choose the folder and refuse one already occupied by something else -- [ ] 2.2 (Unit) Create, list and rename projects, each with its own `raw/`, `wiki/`, `.state/` and `CLAUDE.md` +- [ ] 2.1 (Unit) Open or create a project in a directory: scaffold `raw/`, `wiki/` and `.state/`, and refuse a directory already occupied by something else +- [ ] 2.2 (TDD) A registry of known project paths, resolving a name to a directory for the launcher and for `.mcp.json` — a name is never a path segment, an unknown name is refused rather than guessed at, and a directory that moved degrades to a refusal, never to a search or to the current directory. It is a cache, never truth, and the names in it come from committed files - [ ] 2.3 (TDD) Write a page atomically — temporary file plus rename — snapshotting the touched pages into `.state/` before any write -- [ ] 2.4 (TDD) Record every write operation in a log in `.state/`, with its origin (editor, MCP), the affected pages and the time +- [ ] 2.4 (TDD) Record every write in a log in `.state/`, with its origin (editor, CLI, hook), the affected pages and the time — it records what was observed, not only what this application performed - [ ] 2.5 (TDD) Undo an operation by its id, restoring the snapshot and removing what it created -- [ ] 2.6 (TDD) Refuse a write that resolves outside the served project, including through a relative path or a symbolic link +- [ ] 2.6 (TDD) Refuse a write that resolves outside the project — resolving the real path before comparing, and covering a relative path, a symbolic link and a Windows directory junction, which needs no privilege and is not a symlink +- [ ] 2.7 (TDD) Split the configuration: project settings committed inside the project under a closed schema that refuses an unknown key and carries no local path, and every secret only in the application's data directory keyed by project path — never in the project directory, unconditionally, because `git init` a week later turns a conditional rule into a leak +- [ ] 2.8 (TDD) Write the ignore entries at `ow init` so that recorded audio and `.state/` are out by default and committing them is opting in — `.state/` holds every page as it was before each write, which is where a redaction survives the redaction ## 3 — Sources: files @@ -108,8 +116,9 @@ workspace is not. - [ ] 3.2 (Unit) Upload Markdown and plain text: copy into `raw/` and normalise to `text.md` - [ ] 3.3 (Unit) Upload a PDF: extract the text to `text.md`, keeping the page number as a provenance anchor - [ ] 3.4 (Unit) Upload a DOCX: extract the text and the heading hierarchy to `text.md` -- [ ] 3.5 (Unit) Drag files onto the window, choose the project, and see what was recognised and what was not +- [ ] 3.5 (Unit) Drag files onto the window and see what was recognised and what was not - [ ] 3.6 (TDD) Derive the id: lowercase, accents folded, anything outside `[a-z0-9]` collapsed to one `-`, and refuse a filename already taken in this project instead of inventing a suffix +- [ ] 3.7 (Unit) Watch `raw/_inbox/` and ingest what lands there through the same path as 3.1 — the way an agent hands over material it fetched, now that no MCP tool ingests ## 4 — Sources: audio recording @@ -135,14 +144,14 @@ workspace is not. ## 5 — The wiki as a validated store What replaces the code that used to write the pages: the application does not guarantee -the content is good, it guarantees it is **well formed**. Every write — from the editor or -from MCP — goes through here. +the content is good, it guarantees it is **well formed**. Every write — from the editor, +from the CLI, or caught by a hook — goes through here. - [ ] 5.1 (TDD) Validate the page frontmatter against the schema (`id`, `type`, `title`, `status`, `aliases`, `updated`, `sources`) and refuse the write with a reason, instead of storing something malformed - [ ] 5.2 (TDD) Refuse a write whose wikilink does not resolve to an existing page, saying which link broke - [ ] 5.3 (TDD) Refuse a write whose provenance citation does not point at an existing source and, for audio, at an instant inside the recording - [ ] 5.4 (Unit) Fill in `updated` and append the source to `sources` automatically, so that it does not depend on the agent remembering -- [ ] 5.5 (Unit) Append a line to `log.md` and an entry to `changelog.md` on every write operation, with its origin +- [ ] 5.5 (Unit) Append a line to `log.md` and an entry to `changelog.md` on every write, with its origin - [ ] 5.6 (Unit) Maintain the index: register a new page in `index.md` and flag a page that became unreachable ## 6 — Source flow @@ -157,67 +166,68 @@ from MCP — goes through here. ## 7 — Integrity -With the agent writing, this stops being hygiene and becomes the defence against drift. +With the agent writing through the filesystem, this stops being hygiene and becomes the net +of record. - [ ] 7.1 (Unit) Report broken wikilinks and orphan pages - [ ] 7.2 (Unit) Report a desynchronised changelog and a source never cited - [ ] 7.3 (Unit) Report a provenance link that does not resolve to an existing source or instant - [ ] 7.4 (Unit) Report a synonym used where the project has a canonical term -- [ ] 7.5 (Unit) Expose the checks in the UI, with the correction path described per finding -- [ ] 7.6 (Unit) Expose the same checks as an MCP tool, so the agent can check its own work before finishing +- [ ] 7.5 (Unit) Report a codewiki citation that no longer resolves or runs past the end of its file — the check that comes with scaffolding codewiki, per `adr:0015-the-convention-ships-as-skills` +- [ ] 7.6 (Unit) Expose the checks in the UI, with the correction path described per finding +- [ ] 7.7 (Unit) Expose the same checks as `ow check`, so an agent and a CI job can run them without the application ## 8 — Application - [ ] 8.1 (Unit) Design system: dense dark-theme tokens, a compact type scale, focus and error states, a recording indicator -- [ ] 8.2 (Unit) Electron shell: navigation across wiki, sources and MCP, a project selector, record, pause and stop, a persistent indicator while recording +- [ ] 8.2 (Unit) Electron shell scoped to the directory it was opened in: navigation across wiki and sources, record, pause and stop, a persistent indicator while recording - [ ] 8.3 (Unit) Transcription credential: a Groq key typed and validated on the spot, or local whisper.cpp with no credential at all — stored as per `adr:0007-plaintext-credentials-in-the-config` -- [ ] 8.4 (Unit) Onboarding: choose the workspace folder, create the first project, and start the MCP server with the configuration ready to paste +- [ ] 8.4 (Unit) A launcher for when `ow` was run outside a project: the registry of known projects, and creating a new one - [ ] 8.5 (Unit) Browse the rendered wiki: follow wikilinks, see the page with its frontmatter, go back - [ ] 8.6 (Unit) Open the source at the right instant when a provenance link is clicked — audio at the timestamp, a document at the page - [ ] 8.7 (Unit) Edit a page's markdown with preview and save, going through the group 5 validations - [ ] 8.8 (Unit) Refuse to overwrite a page changed on disk since it was loaded, instead of losing the change silently - [ ] 8.9 (Unit) Create, rename and delete a page from the UI, fixing the wikilinks that pointed at it -- [ ] 8.10 (Unit) Reflect on screen, live, the pages the agent writes over MCP -- [ ] 8.11 (Unit) An operation history with undo, fed by 2.4 — the only way back there is -- [ ] 8.12 (Unit) Choose the content language at onboarding and change it afterwards — English by default, Brazilian Portuguese and Spanish alongside it — reaching the transcription hint and the generated `CLAUDE.md`, and nothing else - -## 9 — MCP server - -- [ ] 9.1 (Unit) A project access module — list, read, search, ingest, write — one implementation, used by the UI and by the server -- [ ] 9.2 (Unit) An MCP server over HTTP, bound to the loopback only, started and stopped by the application, serving exactly one project chosen by the application, always at the same address -- [ ] 9.3 (TDD) Require a token on every request, generated per workspace, and refuse a request without it — any local process reaches that port -- [ ] 9.4 (TDD) No tool takes a project parameter, and none reaches a path outside the served project -- [ ] 9.5 (Unit) Switch the served project by dropping the open connections, so that the harness never goes on talking to the previous project -- [ ] 9.6 (Unit) Announce the active project in the server's name and description, so the agent says which base it is working on -- [ ] 9.7 (Unit) Read tools: list pages, read a page, search full text returning passages -- [ ] 9.8 (Unit) Source tools: list sources with their state and read the `text.md` of one of them -- [ ] 9.9 (Unit) An ingest tool: accept a document, write it into `raw/` and reduce it to text through the same path as group 3 -- [ ] 9.10 (TDD) Write tools — create, update, rename and delete a page — going through the group 5 validations, the atomic path of 2.3 and the log of 2.4 -- [ ] 9.11 (Unit) Return a validation error readable enough for the agent to fix it on its own and try again -- [ ] 9.12 (Unit) Show in the UI, unambiguously, which project is being served, the active connections and the latest operations that came in over MCP -- [ ] 9.13 (Unit) Generate the configuration ready to paste into the harness, with the address and the token -- [ ] 9.14 (TDD) Generate the project's `CLAUDE.md` with the page schema, the LLM-Wiki methodology and the configured content language — it is the only place either convention exists, now that neither exists in code -- [ ] 9.15 (Unit) Verify end to end that Claude Code, pointed at the server and starting from a single source, builds valid pages and then answers by citing them -- [ ] 9.16 (Unit) Hand the MCP token to the harness through a `headersHelper` reading the application's `config.json`, so the token is never pasted and never stored a second time — see [[claude-code-plugins]] -- [ ] 9.17 (Unit) Write the agent-facing skill **from the tool list 9.7–9.10 actually shipped**, and settle in the same task whether it or the `CLAUDE.md` of 9.14 is the single home of the convention +- [ ] 8.10 (Unit) Watch the folder and reflect changes on screen live, whichever wrote them — the agent, a hook, or the user in another editor +- [ ] 8.11 (Unit) An operation history with undo, fed by 2.4, and honest about covering only what was observed +- [ ] 8.12 (Unit) Choose the content language at onboarding and change it afterwards — English by default, Brazilian Portuguese and Spanish alongside it — reaching the transcription hint and the scaffolded skills, and nothing else + +## 9 — The CLI, MCP and the agent's contract + +- [ ] 9.1 (Unit) A project access module — read, search, validate, write — one implementation, imported by the application, the CLI and the MCP process +- [ ] 9.2 (Unit) `ow` in a directory opens the application scoped to it, and with a subcommand runs headless: the shim the installer puts on `PATH`, and the same package `npx open-wiki` reaches +- [ ] 9.3 (Unit) `ow init`: scaffold the project, and write the wiki and codewiki skills into `.claude/skills/` without overwriting anything already there — `adr:0015-the-convention-ships-as-skills` +- [ ] 9.4 (Unit) Generate a short project `CLAUDE.md` that points at the skills and duplicates nothing they say +- [ ] 9.5 (TDD) Settle and build the write gate: a `PreToolUse` hook that validates the content it is handed and denies with a reason, a CLI verb for harnesses without hooks, and an answer for a write made through the shell, which no per-tool rule reaches — `adr:0013-the-project-directory-is-the-unit` leaves the composition open and it cannot ship open +- [ ] 9.6 (TDD) Refuse an agent-mediated write that lands in `.claude/`, `.mcp.json` or `CLAUDE.md` — the gate's own configuration is inside the project, and a write path that reaches it edits away its own restraint through a change that reads as documentation in review +- [ ] 9.7 (Unit) `ow mcp --project --read-only`: an MCP server over stdio, spawned by the harness, serving exactly the one project it was launched for +- [ ] 9.8 (TDD) The MCP entrypoint does not import the write path at all, so read-only is what the process can do rather than what it agrees to do, and no tool resolves a path outside the launched project +- [ ] 9.9 (Unit) Read tools: the index as structure, a page returned whole, and the sources with their state and their `text.md` +- [ ] 9.10 (Unit) Announce the project in the server's name and description, so an agent with several configured says which base it answered from +- [ ] 9.11 (Unit) `ow search` and `ow graph`: the lexical and structural queries over the local project that `adr:0013-the-project-directory-is-the-unit` sends to the CLI rather than to MCP, printing JSON +- [ ] 9.12 (Unit) A validation error readable enough for the agent to fix it on its own and try again — the same text whether it came from the CLI, a hook or the editor +- [ ] 9.13 (TDD) Pay down cold start: bundle the CLI to a single file, and talk to the running application over a local socket when there is one — the socket carries read and validate and never write, and the standalone path produces the same answer +- [ ] 9.14 (Unit) Verify end to end that Claude Code, working inside a project and starting from a single source, builds valid pages and then answers by citing them +- [ ] 9.15 (Unit) Verify that a second project with no wiki of its own consults the first through a committed `.mcp.json` naming it, and answers citing its pages ## 10 — Distribution -- [ ] 10.1 (Unit) A single NSIS installer with ffmpeg and `recorder.exe` embedded, written to `apps/desktop/release/`, with no external dependency to install — `adr:0009-distribution-through-github-releases` +- [ ] 10.1 (Unit) A single NSIS installer with ffmpeg and `recorder.exe` embedded, written to `apps/desktop/release/`, with no external dependency to install, and the `ow` shim on `PATH` — `adr:0009-distribution-through-github-releases` - [ ] 10.2 (Unit) Release from a `v*` tag: CI builds the installer, refuses a tag that disagrees with the app version or that already has a release, and publishes it to GitHub Releases with its `SHA256SUMS.txt` -- [ ] 10.3 (Unit) Publish to winget and Scoop, with the manifests pointing at the release URL and quoting its hash -- [ ] 10.4 (Unit) A README with the recording notice, the responsibility to inform participants, and what the SmartScreen warning on an unsigned installer means -- [ ] 10.5 (Unit) Package the skill and the MCP configuration as an installable Claude Code plugin, with `.claude-plugin/marketplace.json` at the repository root so `/plugin marketplace add` reaches it, and `claude plugin validate --strict` in CI — see [[claude-code-plugins]] +- [ ] 10.3 (Unit) Publish the CLI to npm from the same tag, so `npx open-wiki init` works with nothing installed, and fail the release when the two artifacts disagree on version — `adr:0014-typescript-everywhere-except-audio-capture` +- [ ] 10.4 (Unit) Publish to winget and Scoop, with the manifests pointing at the release URL and quoting its hash +- [ ] 10.5 (Unit) A README with the recording notice, the responsibility to inform participants, what the SmartScreen warning on an unsigned installer means, and what committing a wiki puts in front of everyone with repository access +- [ ] 10.6 (Unit) Package the hooks and the scaffolding command as an installable Claude Code plugin — never the skills themselves, which would be a second copy of the convention (`adr:0015-the-convention-ships-as-skills`), and never a `.mcp.json`, whose contents differ per user — with `.claude-plugin/marketplace.json` at the repository root so `/plugin marketplace add` reaches it, and `claude plugin validate --strict` in CI — see [[claude-code-plugins]] --- ## Notes -**Order.** The minimum product is 2 + 3 + 5 + 9: one project, one uploaded markdown file, a -store that validates, and a server Claude Code drives. With that the whole cycle already -runs, with no audio, no PDF and no pretty UI. Close that path first — it answers the only -question that matters, which is whether an agent can build and maintain the wiki through -the tools you exposed. +**Order.** The minimum product is 2 + 3 + 5 + 9: one project directory, one uploaded +markdown file, a store that validates, and the CLI plus the gate that makes an agent's +writes go through it. With that the whole cycle already runs, with no audio, no PDF and no +pretty UI. Close that path first — it answers the only question that matters, which is +whether an agent can build and maintain the wiki under the convention you gave it. Group 8 comes after: a wiki Claude Code already operates has value with an ugly UI, and the reverse is not true. @@ -231,28 +241,24 @@ to wait, it is this group. *There is no recompilation any more.* With distillation out of the application, `wiki/` stopped being derivable from `raw/` and became primary content. No task rebuilds the wiki, -and none can. The pair 2.3–2.5 is the only net there is, which is why all three are `(TDD)` -and come before anything that writes. - -*The convention lives in a prose file.* 9.14 generates the `CLAUDE.md` carrying the schema -and the methodology; if it is vague, different agents write differently and the wiki drifts -without anything breaking. Group 5 is what stops the drift from becoming corruption — but -it checks form, not meaning. A well-formed and wrong page passes. - -A skill is the rival home for it — versioned with the product and updated by an upgrade, -where a generated `CLAUDE.md` is a copy per folder that ages from the moment it is -written. 9.17 writes that skill and picks which of the two is a pointer to the other. - -**It is deliberately last in the group, and one was already written and thrown away.** -A skill teaching an agent to call `list_pages` and `read_page` is fiction until 9.7–9.10 -decide those names, and fiction in a file that loads into an agent's context is worse than -an absent file: whoever picks the work up next reads it as a decision somebody made and honours it. The -convention — one page per entity, every claim cited, supersede rather than overwrite — was -true before any tool existed and belongs in the plan and in group 5's validations. The -calling sequence is not, and waiting costs nothing. - -*The port is local, not private.* Any process on the machine reaches the loopback. With -ingest and write exposed, 9.3 and 9.4 are the difference between a tool and a vector. +and none can. The pair 2.3–2.5 is the only net the product itself has, which is why all +three are `(TDD)` and come before anything that writes. + +*The gate moved, and 9.5 is where it lands.* MCP no longer writes, so the agent writes with +its own tools. Refusal is rebuildable — a `PreToolUse` hook receives the content about to be +written and can deny it with a reason — so the store keeps its promise for `Edit` and +`Write`. What it does not keep is coverage: a hook matches a tool, and a page written +through Bash carries a command string rather than page content. Denying `Edit(wiki/**)` does +not constrain Bash either, since permission rules are per tool. 9.5 has to answer for the +shell, not only for the file tools, and where neither a hook nor a CLI verb is in place — +another harness, a bare checkout — group 7 is the only thing between a wrong page and a +permanent one. + +*The convention lives in prose, now in a skill.* `adr:0015-the-convention-ships-as-skills` +gives it one home, which removes the drift of having two. It does not remove the other +failure: group 5 checks form, not meaning, so a well-formed and wrong page passes. And a +skill scaffolded into a project ages there — that record leaves the version marker open, +and until it is closed a project set up today keeps today's convention forever. *The time map lies with confidence.* It runs through 4.7, 4.11, 4.13, 5.3 and 7.3. If it is wrong, provenance points at the wrong instant — worse than not existing. Three manual checks @@ -265,17 +271,21 @@ the confirmation is checked against, so 4.9 and 4.17 come before 4.14 does anyth irreversible — and a run abandoned at chunk four keeps its WAV forever, which makes surfacing a stalled recording (6.2) part of the retention story rather than a nicety. -*The validation error is an interface.* 9.11 looks cosmetic and is not: with the agent +*The validation error is an interface.* 9.12 looks cosmetic and is not: with the agent writing, a refusal it cannot understand becomes an attempt it repeats verbatim. The message -is what closes the loop. +is what closes the loop, and it now has three mouths — the CLI, the hook and the editor — +which have to say the same thing. + +*Two artifacts, one version.* The installer and the npm package ship from the same tag +(10.3). A skew between them fails looking like corrupted state rather than a bad install, +which is the cost `adr:0014-typescript-everywhere-except-audio-capture` accepted. -**Research, later, and why it costs almost nothing.** Nothing in this plan asks the +**Research, later, and why it still costs almost nothing.** Nothing in this plan asks the application to go and find material — sources arrive because a person uploaded a file or -recorded a meeting. But 9.9 exposes ingest as an MCP tool, which means an agent that can -already read the web, a ticket tracker or another repository can hand what it found to the -workspace and then write pages citing it. No new component: the application still calls no -LLM, the researcher is the agent, and the material lands in `raw/` beside a PDF and a -recording. +recorded a meeting. But 3.7 watches an inbox, which means an agent that can already read +the web, a ticket tracker or another repository can drop what it found into the project and +then write pages citing it. No new component, and no MCP tool either: the agent is already +inside the directory. The one thing that has to be right is what gets ingested. **Store the retrieved text, not the URL.** A link rots, is edited, or sits behind a login by the time someone follows the @@ -284,10 +294,8 @@ citation, and a citation that opens nothing is worse than none — which is the Ingesting the fetched text makes an external source immutable in exactly the way a recording already is, and the URL becomes a note on it rather than the evidence. -This is deliberately not scheduled. It is written down because it is the cheapest thing on -the horizon and the design decision inside it is easy to get wrong once, permanently. - **Methods.** The `(TDD)` ones are the tasks where being wrong produces no symptom: track alignment, pause, the time map, atomic writing, the operation log, undo, confinement to the -project, the three write validations, the server token, the write tools and the generated -`CLAUDE.md`. They are also the ones to surface before landing, even in an automatic run. +project, the configuration split, the three write validations, the write gate and the MCP +process's confinement. They are also the ones to surface before landing, even in an +automatic run. From e2e8787564d6cae5b10d2c80e87471fe64d7fda0 Mon Sep 17 00:00:00 2001 From: prode Date: Sat, 1 Aug 2026 02:01:48 -0300 Subject: [PATCH 3/3] docs: close the holes a review found in the plan, and correct the hook contract again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A validation pass over plans/open-wiki.md, then a review of the fixes. The architecture hole was group 5: the preamble said every write goes through the validated store, but nothing said which caller does what, so the automatic fields, the log and the index had no executor on the path the agent actually uses. The preamble now divides one module across three callers — editor and CLI verb, the hook pair, the folder observer — and 9.5 builds both hooks rather than only PreToolUse. Second correction to the same fact in two commits: a PreToolUse hook can return updatedInput and rewrite the arguments before the tool runs. So the store does not merely refuse on the direct path, it completes — the fields filled on the agent's behalf are written into the input rather than demanded back in an error. The first commit said the hook cannot see the content; a draft of this one said it cannot change it. Both were reasoned about rather than read, and the plan now says so where the claim lives. Other holes closed: supersession was prose-only, so `ow graph superseded` had nothing to walk (new 5.2, and the glossary entry it contradicted); 8.12 and 9.3 disagreed about whether changing the content language rewrites a scaffolded file, and the first fix traded that contradiction for a hole where nothing put the language in front of the agent at all; three tasks scaffolded a project and produced different projects depending on the door; nothing wrote the consulting .mcp.json, leaving the paste step the pivot claimed to have removed (new 9.8); raw/_inbox sat inside the immutable tree with no exemption; index.md, changelog.md and log.md were about to be validated against the entity schema. ADR 0009 stops citing a task number that had already rotted twice, 0014 says why 0010 is marked superseded rather than rejected and answers the ordering question 0010 left open, and the Methods note stops being a roll-call that was already missing six tasks. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01C9Vv5jJqcGxrStXhLwbf9b --- ...09-distribution-through-github-releases.md | 6 +- .../0013-the-project-directory-is-the-unit.md | 13 +- ...escript-everywhere-except-audio-capture.md | 8 ++ .../0015-the-convention-ships-as-skills.md | 6 +- docs/glossary.md | 2 +- docs/wiki/changelog.md | 14 ++- docs/wiki/claude-code-plugins.md | 8 +- plans/open-wiki.md | 113 +++++++++++------- 8 files changed, 111 insertions(+), 59 deletions(-) diff --git a/docs/adr/0009-distribution-through-github-releases.md b/docs/adr/0009-distribution-through-github-releases.md index 41c3a28..4384c77 100644 --- a/docs/adr/0009-distribution-through-github-releases.md +++ b/docs/adr/0009-distribution-through-github-releases.md @@ -11,7 +11,7 @@ no server of our own, so there is no update endpoint and no download host to run distributes the binary is somebody else's infrastructure. The repository is already on GitHub, the tag is already the thing that says "this is a -version", and the winget and Scoop manifests of task 10.2 both work by pointing at a stable +version", and the winget and Scoop manifests both work by pointing at a stable download URL with a known hash. GitHub Releases is that URL, produced by the tag we are already pushing. @@ -36,8 +36,8 @@ is a settings change and not a workflow rewrite. ## Consequences Distribution costs nothing to run and nothing to operate. The download URL is stable and -predictable, which is the only property task 10.2 needs from it, and every release carries -the hash that manifest has to state. +predictable, which is the only property a winget or Scoop manifest needs from it, and every +release carries the hash that manifest has to state. **An unsigned installer means Microsoft SmartScreen warns on it**, with a dialog whose default button is "Don't run". This lands worst in exactly the environment this product is diff --git a/docs/adr/0013-the-project-directory-is-the-unit.md b/docs/adr/0013-the-project-directory-is-the-unit.md index 22c9f3f..ded7ae0 100644 --- a/docs/adr/0013-the-project-directory-is-the-unit.md +++ b/docs/adr/0013-the-project-directory-is-the-unit.md @@ -149,10 +149,17 @@ convention text the agent reads. **The gate moved, and it is rebuildable — for the file tools.** With writes gone from MCP, the agent writes the wiki with its own tools. A `PreToolUse` hook receives the complete `tool_input` — for `Write` that includes `content`, for `Edit` the strings — and can answer -`permissionDecision: deny` with a reason the agent reads. So refusal survives intact: the +`permissionDecision: deny` with a reason the agent reads. It can also return `updatedInput`, +which replaces the arguments before the tool runs. So refusal survives intact — the malformed page never reaches the disk, and 0002's corollary that the defence has to be at -the entrance still holds. This is the single most load-bearing fact in the record, and an -earlier draft had it backwards. +the entrance still holds — and so does everything the store used to do *for* the agent +rather than *to* it: the fields filled in on its behalf are written into the input, not +requested of it in an error message. The whole service survives, not a weakened half of it. + +This is the most load-bearing fact in the record and it was got wrong twice while drafting, +in both directions, each time by reasoning about the hook contract instead of reading it. +Nothing about what a harness can or cannot do belongs in these records unless it was +checked against the reference. What does *not* survive is the completeness of that gate. A hook matches a tool, so a write that arrives some other way is not gated: `echo > wiki/page.md` through Bash carries a diff --git a/docs/adr/0014-typescript-everywhere-except-audio-capture.md b/docs/adr/0014-typescript-everywhere-except-audio-capture.md index bda5f9b..d6e8b4c 100644 --- a/docs/adr/0014-typescript-everywhere-except-audio-capture.md +++ b/docs/adr/0014-typescript-everywhere-except-audio-capture.md @@ -36,6 +36,14 @@ contract narrow enough to test"* — and that justification is agnostic of langu loses is the second binary, the two artifacts whose versions must agree, and the mismatched pair that fails looking like corrupted data. +0010 is marked `superseded` rather than `rejected`, which deserves a word because it was +never accepted. `rejected` would say the proposal was refused and nothing came of it; what +happened is that its contract was adopted and its implementation language was not. This +record is where the surviving half now lives, so pointing at it is more useful to a reader +than a status that would send them nowhere. The two questions 0010 left open go with it: +the inverted index is refused outright, and the graph comes before the search because the +search is what a scan already does. + `adr:0005-wasapi-capture-in-a-minimal-sidecar` becomes literally true again. Its sentence "everything that is not audio capture lives on the JavaScript side" was being narrowed by 0010; it is not any more. diff --git a/docs/adr/0015-the-convention-ships-as-skills.md b/docs/adr/0015-the-convention-ships-as-skills.md index 1dac11d..a7b8311 100644 --- a/docs/adr/0015-the-convention-ships-as-skills.md +++ b/docs/adr/0015-the-convention-ships-as-skills.md @@ -30,8 +30,10 @@ the **wiki** and one for **codewiki** — and writes neither if it is already th This is what `scc` does to this repository, and the product does the same thing to the user's. -The generated `CLAUDE.md` of task 9.14 is not a second home. Where it says anything about -the page schema or the method, it points at the skill. +The generated project `CLAUDE.md` is not a second home. Where it would say anything about +the page schema or the method, it points at the skill instead; what it carries on its own is +only what varies per project and cannot sit in a shared skill, which today is the configured +content language. ## Consequences diff --git a/docs/glossary.md b/docs/glossary.md index f950831..2a5102a 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -18,7 +18,7 @@ which is why each one is listed in the exact form it takes in the schemas. - **ingest** — the path from a source to being available as `text.md` in the project. It ends there: writing pages is the agent's job. Avoid: sync - **entity** — a person, project, topic or narrated code area with a page of its own, identified by `id` in the form `type:slug`. Avoid: subject - **claim** — a statement recorded on a page, of type `decision`, `fact`, `action_item` or `open_question`, always with a citation. Avoid: insight -- **supersession** — marking an earlier decision as replaced, preserving it struck through with a date and a link to the one replacing it. Avoid: override +- **supersession** — marking an earlier decision as replaced, never deleting it. It is carried twice: as data, in the replaced page's `status`, `superseded-by` and the date, which is what a traversal can answer from; and as prose, struck through with the same date and a link, which is what a reader sees. The prose alone is not supersession — nothing can walk it. Avoid: override - **provenance link** — the link that opens the source where a claim came from: an instant for audio, a page for a PDF. Avoid: backlink - **write gate** — whatever makes an agent's write to `wiki/` pass through the group 5 validations now that MCP no longer writes. Which mechanism it is has not been chosen — `adr:0013-the-project-directory-is-the-unit`. diff --git a/docs/wiki/changelog.md b/docs/wiki/changelog.md index cf79904..4fbc348 100644 --- a/docs/wiki/changelog.md +++ b/docs/wiki/changelog.md @@ -10,10 +10,13 @@ What changed in the wiki, newest first. `additionalDirectories`. That absence is what stops the product from shipping the deny rule its write gate may need. - The page also records the hook mechanics the write gate rests on: a `PreToolUse` hook - receives the tool's complete `tool_input` and can deny with a reason, so a malformed page - can be refused before it lands. A first pass through this recorded the opposite, and - `adr:0013-the-project-directory-is-the-unit` was drafted against the wrong version before - it was checked against the hooks reference. + receives the tool's complete `tool_input`, can deny with a reason, and can replace the + arguments with `updatedInput` — so a malformed page can be refused before it lands, and a + page missing an automatic field can be completed before it lands. +- Both of those were recorded wrongly first, in opposite directions, and + `adr:0013-the-project-directory-is-the-unit` was drafted against each wrong version in + turn. What the hook contract permits is now a thing to read rather than infer, and the + hooks reference joins this page's sources. ## 2026-07-31 @@ -22,5 +25,6 @@ What changed in the wiki, newest first. - Added [[claude-code-plugins]], distilled from the Claude Code plugin, marketplace and MCP references. It carries one finding that reaches the plan: an HTTP MCP server in a plugin can generate its own auth headers at connection time, which removes the pasted - token of task 9.13. + token of what was then task 9.13. (That finding was voided a day later, along with the + token itself — see the entry above.) - Started the wiki with `index.md` and this file. diff --git a/docs/wiki/claude-code-plugins.md b/docs/wiki/claude-code-plugins.md index 271ac4a..dd14b7d 100644 --- a/docs/wiki/claude-code-plugins.md +++ b/docs/wiki/claude-code-plugins.md @@ -168,6 +168,8 @@ a skill, scaffolded by `ow init`, and the `CLAUDE.md` points at it. Read 2026-07-31, against Claude Code v2.1.x. Revisited 2026-08-01 for what a plugin cannot carry, and for the hook mechanics the write gate rests on: a `PreToolUse` hook receives the tool's complete `tool_input` — including `content` for `Write` and the strings for `Edit` — -and can answer `permissionDecision: deny` with a reason the agent reads, so a write can be -refused before it lands. A first pass recorded the opposite, and three documents were -written against it before it was checked. +and can answer `permissionDecision: deny` with a reason the agent reads, or `updatedInput` +to replace the arguments before the tool runs. So a write can be refused before it lands, +and it can also be *completed* before it lands. Both facts were recorded wrongly on a first +pass — first as "the hook cannot see the content", then as "the hook cannot change it" — and +several documents were written against each before either was checked. diff --git a/plans/open-wiki.md b/plans/open-wiki.md index e75c78b..8b71519 100644 --- a/plans/open-wiki.md +++ b/plans/open-wiki.md @@ -56,8 +56,10 @@ by consulting the first over MCP. Apache-2.0 · Windows only in the MVP · no backend · the application calls no LLM · a project is a directory, opened by `ow` in its scope · sources stay immutable in `raw/` · -MCP is read-only, over stdio, spawned by the harness, and serves one project that is never -the one the harness has open (`adr:0013-the-project-directory-is-the-unit`) · TypeScript +MCP is read-only, over stdio, spawned by the harness, and serves a project other than the +one the harness has open — not because anything forbids it, but because consulting through +a protocol what is already on disk buys nothing +(`adr:0013-the-project-directory-is-the-unit`) · TypeScript everywhere except audio capture (`adr:0014-typescript-everywhere-except-audio-capture`) · the convention ships as skills (`adr:0015-the-convention-ships-as-skills`) · the application's only credential is the transcription one · the content language is a setting, @@ -93,7 +95,7 @@ fenix/ a project — usually a repository the user alre ## 1 — Foundation -- [ ] 1.1 (Unit) Set up the monorepo: a pnpm workspace for `apps/desktop`, `packages/*` and `packages/cli`, a cargo workspace for `crates/recorder`, shared strict TypeScript +- [ ] 1.1 (Unit) Set up the monorepo: a pnpm workspace for `apps/desktop` and `packages/*` — the access module, the CLI and the MCP process among them — a cargo workspace for `crates/recorder`, shared strict TypeScript - [ ] 1.2 (Unit) Fill in `.claude/rules/project.md` with the real build, test, scoped test, lint and format commands - [ ] 1.3 (Unit) CI on GitHub Actions on `windows-latest`: Rust and TS build, tests with a coverage floor of 76% per package, lint - [ ] 1.4 (Unit) Remove `.claude/` and `CLAUDE.md` from `.gitignore` — the methodology is versioned with the code, and today it exists only on this machine @@ -101,9 +103,9 @@ fenix/ a project — usually a repository the user alre ## 2 — Project directory and safe writing -- [ ] 2.1 (Unit) Open or create a project in a directory: scaffold `raw/`, `wiki/` and `.state/`, and refuse a directory already occupied by something else +- [ ] 2.1 (Unit) One scaffolder, in the access module of 9.1: it creates `raw/`, `wiki/` and `.state/`, refuses a directory already occupied by something else, and calls the settings of 2.7, the ignore entries of 2.8 and the skills of 9.3 rather than reimplementing any of them. `ow init`, the launcher and the first run all go through it, so a project is the same project whichever door it came through - [ ] 2.2 (TDD) A registry of known project paths, resolving a name to a directory for the launcher and for `.mcp.json` — a name is never a path segment, an unknown name is refused rather than guessed at, and a directory that moved degrades to a refusal, never to a search or to the current directory. It is a cache, never truth, and the names in it come from committed files -- [ ] 2.3 (TDD) Write a page atomically — temporary file plus rename — snapshotting the touched pages into `.state/` before any write +- [ ] 2.3 (TDD) Write a page atomically — temporary file plus rename — snapshotting the touched pages into `.state/` before any write, with the snapshot callable on its own: on the hook path the agent's own tool does the writing, so the snapshot has to happen without this module performing the write it is protecting - [ ] 2.4 (TDD) Record every write in a log in `.state/`, with its origin (editor, CLI, hook), the affected pages and the time — it records what was observed, not only what this application performed - [ ] 2.5 (TDD) Undo an operation by its id, restoring the snapshot and removing what it created - [ ] 2.6 (TDD) Refuse a write that resolves outside the project — resolving the real path before comparing, and covering a relative path, a symbolic link and a Windows directory junction, which needs no privilege and is not a symlink @@ -118,7 +120,7 @@ fenix/ a project — usually a repository the user alre - [ ] 3.4 (Unit) Upload a DOCX: extract the text and the heading hierarchy to `text.md` - [ ] 3.5 (Unit) Drag files onto the window and see what was recognised and what was not - [ ] 3.6 (TDD) Derive the id: lowercase, accents folded, anything outside `[a-z0-9]` collapsed to one `-`, and refuse a filename already taken in this project instead of inventing a suffix -- [ ] 3.7 (Unit) Watch `raw/_inbox/` and ingest what lands there through the same path as 3.1 — the way an agent hands over material it fetched, now that no MCP tool ingests +- [ ] 3.7 (Unit) Watch `raw/_inbox/` and ingest what lands there through the same path as 3.1 — the way an agent hands over material it fetched, now that no MCP tool ingests. The inbox is the one mutable thing under `raw/`: it is a doorway, emptied by ingestion, and it is not a source, so nothing enumerates it, cites it or reports it uncited ## 4 — Sources: audio recording @@ -144,15 +146,31 @@ fenix/ a project — usually a repository the user alre ## 5 — The wiki as a validated store What replaces the code that used to write the pages: the application does not guarantee -the content is good, it guarantees it is **well formed**. Every write — from the editor, -from the CLI, or caught by a hook — goes through here. - -- [ ] 5.1 (TDD) Validate the page frontmatter against the schema (`id`, `type`, `title`, `status`, `aliases`, `updated`, `sources`) and refuse the write with a reason, instead of storing something malformed -- [ ] 5.2 (TDD) Refuse a write whose wikilink does not resolve to an existing page, saying which link broke -- [ ] 5.3 (TDD) Refuse a write whose provenance citation does not point at an existing source and, for audio, at an instant inside the recording -- [ ] 5.4 (Unit) Fill in `updated` and append the source to `sources` automatically, so that it does not depend on the agent remembering -- [ ] 5.5 (Unit) Append a line to `log.md` and an entry to `changelog.md` on every write, with its origin -- [ ] 5.6 (Unit) Maintain the index: register a new page in `index.md` and flag a page that became unreachable +the content is good, it guarantees it is **well formed**. Three paths reach a page, and they +divide one module rather than each inventing its own: + +- **The editor and the CLI verb** call it directly and get the whole service — snapshot, + validation, the automatic fields, the atomic write, the log. +- **A `PreToolUse` hook**, when the agent writes with its own tools, gets the same service a + different way: it is handed the content before the file exists, so it snapshots, + validates, returns the completed frontmatter as `updatedInput`, and denies with a reason + when the page cannot be fixed by filling a field in. `PostToolUse` is where the log, the + changelog and the index go, because those describe a write that has actually happened. +- **The folder observer** catches what neither saw — a page edited in Obsidian. It cannot + refuse anything: it records the change in the log of 2.4, redraws the screen through 8.10, + and whatever is wrong with the page surfaces later as a group 7 finding. + +One implementation, three callers. What no path covers is a write made through the shell, +which is 9.5's problem. + +- [ ] 5.1 (TDD) Validate the page frontmatter against the schema (`id`, `type`, `title`, `status`, `aliases`, `updated`, `sources`, `superseded-by`) and refuse the write with a reason, instead of storing something malformed — `index.md`, `changelog.md` and `log.md` are not entity pages and are validated as themselves, not against this schema +- [ ] 5.2 (TDD) Record supersession as data, not only as struck-through prose: `status`, `superseded-by` and the date it happened, on the page that was replaced, so that "what replaced this, and when" is answerable by a traversal rather than by reading — without it 9.12's `ow graph superseded` has nothing to walk +- [ ] 5.3 (TDD) Refuse a write whose wikilink does not resolve to an existing page, saying which link broke +- [ ] 5.4 (TDD) Refuse a write whose provenance citation does not point at an existing source and, for audio, at an instant inside the recording +- [ ] 5.5 (Unit) Fill in `updated` and append the source to `sources` automatically — returned as `updatedInput` on the hook path, so that it does not depend on the agent remembering rather than merely telling it to remember +- [ ] 5.6 (Unit) Append a line to `log.md` and an entry to `changelog.md` after every write, with its origin +- [ ] 5.7 (Unit) Maintain the index: register a new page in `index.md` and flag a page that became unreachable +- [ ] 5.8 (TDD) Do not let a correction the store itself made read as somebody else's edit. Two paths produce it: a hook that rewrote the content through `updatedInput` leaves the agent holding a copy that no longer matches the disk, and the editor filling `updated` on save makes the next save from the same buffer look stale to 8.8. Both have to resolve without asking the writer to reconcile a change it did not make ## 6 — Source flow @@ -190,31 +208,32 @@ of record. - [ ] 8.9 (Unit) Create, rename and delete a page from the UI, fixing the wikilinks that pointed at it - [ ] 8.10 (Unit) Watch the folder and reflect changes on screen live, whichever wrote them — the agent, a hook, or the user in another editor - [ ] 8.11 (Unit) An operation history with undo, fed by 2.4, and honest about covering only what was observed -- [ ] 8.12 (Unit) Choose the content language at onboarding and change it afterwards — English by default, Brazilian Portuguese and Spanish alongside it — reaching the transcription hint and the scaffolded skills, and nothing else +- [ ] 8.12 (Unit) Choose the content language at onboarding and change it afterwards — English by default, Brazilian Portuguese and Spanish alongside it — held in the project settings of 2.7 and reaching exactly two places: the transcription hint of 4.15, and the generated `CLAUDE.md` of 9.4, which is regenerated on change because it is generated and the skills are not ## 9 — The CLI, MCP and the agent's contract -- [ ] 9.1 (Unit) A project access module — read, search, validate, write — one implementation, imported by the application, the CLI and the MCP process +- [ ] 9.1 (Unit) A project access module — scaffold, read, search, validate, write — one implementation, imported by the application, the CLI and the MCP process - [ ] 9.2 (Unit) `ow` in a directory opens the application scoped to it, and with a subcommand runs headless: the shim the installer puts on `PATH`, and the same package `npx open-wiki` reaches -- [ ] 9.3 (Unit) `ow init`: scaffold the project, and write the wiki and codewiki skills into `.claude/skills/` without overwriting anything already there — `adr:0015-the-convention-ships-as-skills` -- [ ] 9.4 (Unit) Generate a short project `CLAUDE.md` that points at the skills and duplicates nothing they say -- [ ] 9.5 (TDD) Settle and build the write gate: a `PreToolUse` hook that validates the content it is handed and denies with a reason, a CLI verb for harnesses without hooks, and an answer for a write made through the shell, which no per-tool rule reaches — `adr:0013-the-project-directory-is-the-unit` leaves the composition open and it cannot ship open +- [ ] 9.3 (Unit) Write the wiki and codewiki skills into `.claude/skills/`, overwriting nothing already there, and expose the scaffolder of 2.1 as `ow init` — `adr:0015-the-convention-ships-as-skills` +- [ ] 9.4 (Unit) Generate a short project `CLAUDE.md` that points at the skills, duplicates nothing they say, and carries the one thing they cannot hold because it varies per project: the configured content language — regenerated when 8.12 changes it +- [ ] 9.5 (TDD) Settle and build the write gate as a pair plus a fallback: a `PreToolUse` hook that snapshots, validates, completes the frontmatter through `updatedInput` and denies with a reason; a `PostToolUse` hook that appends the log, the changelog and the index entry, which describe a write that has actually happened; a CLI verb for harnesses with no hooks; and an answer for a write made through the shell, which no per-tool rule reaches — `adr:0013-the-project-directory-is-the-unit` leaves the composition open and it cannot ship open - [ ] 9.6 (TDD) Refuse an agent-mediated write that lands in `.claude/`, `.mcp.json` or `CLAUDE.md` — the gate's own configuration is inside the project, and a write path that reaches it edits away its own restraint through a change that reads as documentation in review - [ ] 9.7 (Unit) `ow mcp --project --read-only`: an MCP server over stdio, spawned by the harness, serving exactly the one project it was launched for -- [ ] 9.8 (TDD) The MCP entrypoint does not import the write path at all, so read-only is what the process can do rather than what it agrees to do, and no tool resolves a path outside the launched project -- [ ] 9.9 (Unit) Read tools: the index as structure, a page returned whole, and the sources with their state and their `text.md` -- [ ] 9.10 (Unit) Announce the project in the server's name and description, so an agent with several configured says which base it answered from -- [ ] 9.11 (Unit) `ow search` and `ow graph`: the lexical and structural queries over the local project that `adr:0013-the-project-directory-is-the-unit` sends to the CLI rather than to MCP, printing JSON -- [ ] 9.12 (Unit) A validation error readable enough for the agent to fix it on its own and try again — the same text whether it came from the CLI, a hook or the editor -- [ ] 9.13 (TDD) Pay down cold start: bundle the CLI to a single file, and talk to the running application over a local socket when there is one — the socket carries read and validate and never write, and the standalone path produces the same answer -- [ ] 9.14 (Unit) Verify end to end that Claude Code, working inside a project and starting from a single source, builds valid pages and then answers by citing them -- [ ] 9.15 (Unit) Verify that a second project with no wiki of its own consults the first through a committed `.mcp.json` naming it, and answers citing its pages +- [ ] 9.8 (Unit) `ow consult add `: write the consulting entry into this project's `.mcp.json`, naming the other project rather than its path, so the file is committable and portable — nobody hand-writes the stdio invocation, which is the paste step the pivot was supposed to have removed +- [ ] 9.9 (TDD) The MCP entrypoint does not import the write path at all, so read-only is what the process can do rather than what it agrees to do, and no tool resolves a path outside the launched project +- [ ] 9.10 (Unit) Read tools: the index as structure, a page returned whole, and the sources with their state and their `text.md` +- [ ] 9.11 (Unit) Announce the project in the server's name and description, so an agent with several configured says which base it answered from +- [ ] 9.12 (Unit) `ow graph` first and `ow search` after — the structural queries have no other owner and the supersession walk depends on the fields 5.2 records, where lexical search is what a scan over a few megabytes already does. Both are the local queries `adr:0013-the-project-directory-is-the-unit` sends to the CLI rather than to MCP, printing JSON +- [ ] 9.13 (Unit) A validation error readable enough for the agent to fix it on its own and try again — the same text whether it came from the CLI, a hook or the editor +- [ ] 9.14 (TDD) Pay down cold start: bundle the CLI to a single file, and talk to the running application over a local socket when there is one — the socket carries read and validate and never write, so the write verb always pays the standalone path, and both paths produce the same answer +- [ ] 9.15 (Unit) Verify end to end that Claude Code, working inside a project and starting from a single source, builds valid pages and then answers by citing them +- [ ] 9.16 (Unit) Verify that a second project with no wiki of its own consults the first through a committed `.mcp.json` naming it, and answers citing its pages ## 10 — Distribution - [ ] 10.1 (Unit) A single NSIS installer with ffmpeg and `recorder.exe` embedded, written to `apps/desktop/release/`, with no external dependency to install, and the `ow` shim on `PATH` — `adr:0009-distribution-through-github-releases` - [ ] 10.2 (Unit) Release from a `v*` tag: CI builds the installer, refuses a tag that disagrees with the app version or that already has a release, and publishes it to GitHub Releases with its `SHA256SUMS.txt` -- [ ] 10.3 (Unit) Publish the CLI to npm from the same tag, so `npx open-wiki init` works with nothing installed, and fail the release when the two artifacts disagree on version — `adr:0014-typescript-everywhere-except-audio-capture` +- [ ] 10.3 (Unit) Publish the CLI to npm from the same tag, so `npx open-wiki init` works with nothing installed, and fail the release when the two artifacts disagree on version — claiming the package name before anyone else does, and publishing with provenance, because a product that verifies a hash on its own ffmpeg cannot ship a fetch-and-execute that verifies nothing — `adr:0014-typescript-everywhere-except-audio-capture` - [ ] 10.4 (Unit) Publish to winget and Scoop, with the manifests pointing at the release URL and quoting its hash - [ ] 10.5 (Unit) A README with the recording notice, the responsibility to inform participants, what the SmartScreen warning on an unsigned installer means, and what committing a wiki puts in front of everyone with repository access - [ ] 10.6 (Unit) Package the hooks and the scaffolding command as an installable Claude Code plugin — never the skills themselves, which would be a second copy of the convention (`adr:0015-the-convention-ships-as-skills`), and never a `.mcp.json`, whose contents differ per user — with `.claude-plugin/marketplace.json` at the repository root so `/plugin marketplace add` reaches it, and `claude plugin validate --strict` in CI — see [[claude-code-plugins]] @@ -245,22 +264,29 @@ and none can. The pair 2.3–2.5 is the only net the product itself has, which i three are `(TDD)` and come before anything that writes. *The gate moved, and 9.5 is where it lands.* MCP no longer writes, so the agent writes with -its own tools. Refusal is rebuildable — a `PreToolUse` hook receives the content about to be -written and can deny it with a reason — so the store keeps its promise for `Edit` and -`Write`. What it does not keep is coverage: a hook matches a tool, and a page written -through Bash carries a command string rather than page content. Denying `Edit(wiki/**)` does -not constrain Bash either, since permission rules are per tool. 9.5 has to answer for the -shell, not only for the file tools, and where neither a hook nor a CLI verb is in place — +its own tools — and the entrance survives that intact. A `PreToolUse` hook is handed the +content before the file exists, can return the completed frontmatter as `updatedInput`, and +can deny with a reason. Refusal *and* the automatic fields both work on that path, so for +`Edit` and `Write` the store keeps its whole promise, not a weakened one. + +What it does not keep is coverage. A hook matches a tool, so a page written through Bash +arrives as a command string with no page content to inspect, and denying `Edit(wiki/**)` +does not constrain Bash either, since permission rules are per tool. 9.5 has to answer for +the shell, not only for the file tools. Where neither a hook nor a CLI verb is in place — another harness, a bare checkout — group 7 is the only thing between a wrong page and a permanent one. +**This paragraph was wrong twice before it was right**, in both directions, because the hook +contract was reasoned about rather than read. Anything here that asserts what a harness can +or cannot do is a claim to check against the reference, not to infer. + *The convention lives in prose, now in a skill.* `adr:0015-the-convention-ships-as-skills` gives it one home, which removes the drift of having two. It does not remove the other failure: group 5 checks form, not meaning, so a well-formed and wrong page passes. And a skill scaffolded into a project ages there — that record leaves the version marker open, and until it is closed a project set up today keeps today's convention forever. -*The time map lies with confidence.* It runs through 4.7, 4.11, 4.13, 5.3 and 7.3. If it is +*The time map lies with confidence.* It runs through 4.7, 4.11, 4.13, 5.4 and 7.3. If it is wrong, provenance points at the wrong instant — worse than not existing. Three manual checks on an hour-long recording are an acceptance criterion for group 4. @@ -271,7 +297,7 @@ the confirmation is checked against, so 4.9 and 4.17 come before 4.14 does anyth irreversible — and a run abandoned at chunk four keeps its WAV forever, which makes surfacing a stalled recording (6.2) part of the retention story rather than a nicety. -*The validation error is an interface.* 9.12 looks cosmetic and is not: with the agent +*The validation error is an interface.* 9.13 looks cosmetic and is not: with the agent writing, a refusal it cannot understand becomes an attempt it repeats verbatim. The message is what closes the loop, and it now has three mouths — the CLI, the hook and the editor — which have to say the same thing. @@ -294,8 +320,11 @@ citation, and a citation that opens nothing is worse than none — which is the Ingesting the fetched text makes an external source immutable in exactly the way a recording already is, and the URL becomes a note on it rather than the evidence. -**Methods.** The `(TDD)` ones are the tasks where being wrong produces no symptom: track -alignment, pause, the time map, atomic writing, the operation log, undo, confinement to the -project, the configuration split, the three write validations, the write gate and the MCP -process's confinement. They are also the ones to surface before landing, even in an -automatic run. +**Methods.** A task is `(TDD)` here when being wrong about it produces no symptom — when the +code runs, the tests pass, and the damage shows up somewhere else or much later. That covers +three families: **time and alignment**, where a number is plausible and wrong; **the paths +that write**, where the loss is silent and there is nothing to roll back to; and **the +boundaries**, where the failure is that something got through. The annotation on each line +is the record of that call, not this paragraph — do not read a list here as the roll. + +They are also the tasks to surface before landing, even in an automatic run.