Skip to content

open-wiki: the rename, the UI draft, and three product decisions - #3

Merged
protonspy merged 2 commits into
mainfrom
docs/open-wiki-decisions
Aug 1, 2026
Merged

open-wiki: the rename, the UI draft, and three product decisions#3
protonspy merged 2 commits into
mainfrom
docs/open-wiki-decisions

Conversation

@protonspy

@protonspy protonspy commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Follows #2, which merged while these two commits were being written. New branch because the old name still said project-wiki.

The project is open-wiki

Renamed across 18 places, including plans/project-wiki.mdplans/open-wiki.md (with git mv, so history follows) and the @open-wiki/* package scope in CI and the release workflow.

A skill was written and removed again

skills/open-wiki/SKILL.md taught an agent to call list_pages and read_page — tool names invented for a server nobody has built. A SKILL.md loads into an agent's context, so this is not merely rework: it is fiction that the next reader honours as a decision somebody made, and tasks 9.7–9.10 would then either obey names nobody chose or contradict a file that already existed.

What survives is the part that was never fiction. The convention — one page per entity, every claim cited, supersede rather than overwrite — was true before any tool existed and already belongs to group 5's validations and task 9.14. The calling sequence was not. Task 9.17 now writes the skill from the tool list group 9 actually shipped, and the plan records why it is last in its group so this is not repeated.

That also dissolves the two-homes-for-one-convention conflict the earlier version had opened.

Three decisions

Record State What it says
0010 proposed A derived-index engine as a second Rust binary, ow.exe, behind a CLI
0011 accepted A source is named by what it is, and that name is frozen
0012 accepted Transcription is journalled, serial and resumable

0010 is proposed, not accepted. It contradicts the plan's out-of-scope line and touches the boundary adr:0005 said to defend — which that record predicted in as many words: "a new method deserves an ADR that supersedes this one, not one more line in an enum."

Its argument is that group 7's checks and group 9's search are the same traversal written twice, and that a derived view with one owner and a printed contract is worth a process boundary. It is explicitly not a performance argument: a workspace of two thousand pages is a few megabytes, Node scans that faster than a person notices, and the record says so rather than inventing a benchmark.

Three constraints are the whole proposal — the index is derived and never a source of truth (or editing a page in Obsidian starts corrupting state); the query surface is structural and never natural language (or it is an LLM in the application by another name, against adr:0003); and the contract is the CLI.

Two questions are left open for a person: does an inverted index enter scope, and is the graph or the search built first.

0011 — a file keeps its filename and a duplicate is refused rather than silently suffixed. A recording is named for the occasion and the date, fenix-weekly-2026-07-31. The date is part of the shape rather than a tiebreak, because recurring meetings are the normal case and a scheme where the first has no date and the second gets -2 produces ids whose meaning depends on creation order.

The extension worth reviewing: the id freezes and the title stays editable. Citations embed the id and raw/ is immutable, so fixing a typo in a title must never break a citation or move a frozen directory. The two names can drift, deliberately — the same relationship a page's id has to its title.

0012 — each chunk is written to a journal before the next starts, so a run killed at chunk five loses one chunk rather than four. Serial replaces parallel, and the reason is that neither provider gains: Groq returns a ten-minute chunk in about three seconds, so an hour finishes in under twenty either way, and whisper.cpp already saturates every core so two at once makes both slower.

A journal whose chunk boundaries, provider or model changed is refused rather than reused. Stitching two segmentations produces a readable, plausible, wrong timeline with correct-looking timestamps, which is the worst failure available in this area.

timeline.vtt is written beside timeline.json so a recording opens in any player. The JSON stays the truth; the VTT is derived and regenerable. It is named timeline.vtt rather than transcript.vtt because the glossary makes "transcript" a synonym to avoid.

This completes the deletion ordering adr:0006 demanded but had nothing durable to check against.

Consequence a reviewer should notice: immutability now has a start time. A source in raw/ is immutable once sealed, and a recording is not sealed until transcription completes — until then its directory holds a journal and a 690 MB WAV. Anything treating everything under raw/ as frozen is wrong about that window. A run abandoned at chunk four keeps that WAV forever, which makes surfacing a stalled recording (6.2) part of the retention story rather than a nicety.

design/desktop-draft.html

Every screen group 8 asks for, in one self-contained file with no network access — the application is offline, so a font or icon CDN in the draft would be a lie. The shell and its four panes work; so do starting a recording and changing the palette.

Amber is provenance rather than a brand colour: a citation, a source, a page that has one. The reading surface is warm while the rest is cold slate — the content is paper, the application is the instrument holding it.

Where colour would lie, form carries the state. A source nothing cites gets a dashed, dimmed pill rather than a colour, because its problem is disappearing from view and the state should look like disappearing. The chunk strip shows which chunk failed rather than a percentage, because the chunk is the retry unit and a percentage erases the thing that decides the next click.

Tokens use shadcn/ui names so the palette copies into globals.css; icons are Lucide inlined as a sprite so they become lucide-react imports one for one.

A third commit fixes a rendering defect in that sprite: it drew each icon inside <g>, which establishes no viewport and carries no viewBox, and no consuming <svg> had one either — so every icon rendered as the top-left corner of a 24-unit drawing clipped into a 13px box. <g> becomes <symbol viewBox="0 0 24 24">, which fixes all 81 usages at once. It also adds flex: 0 0 auto to .icon-sm and .icon-lg, which only .icon had, and which would have let icons compress into ellipses in tight flex rows.

docs/wiki/

Started, with the Claude Code plugin research. The finding that reaches the plan is headersHelper: a plugin can name a command that produces its auth headers at connection time, so the plugin ships a static file containing no secret and the helper reads the token from the application's own config.json — the file adr:0007 already puts it in. That removes the pasted token of task 9.13, and became task 9.16.

How this was verified

No test suite exists yet — the docs tests were removed in #2 and no package exists.

  • The draft parses: no unclosed tags, no duplicate ids, no icon referenced that is not defined, every symbol carrying a viewBox.
  • Those are structural checks, and they did not catch the icon defect. Nothing here renders the page, so the missing viewBox passed every check and was found by a person looking at it. A reviewer should open the file rather than trust this list.
  • Checked by hand in place of scc validate (scc is not installed here): ADR numbering contiguous 0001–0012, every adr: citation resolves, every ADR cited from outside docs/adr/, every wikilink resolves, no orphan wiki page, 88 tasks all carrying exactly one (Unit) or (TDD) with no repeated number, and no Avoid: synonym used as a whole word in prose.

code-review and security-review subagents were not run — this session is configured not to dispatch agents unless asked.

.gitignore is left alone: it carries an unrelated local change adding .claude/ and CLAUDE.md, which is the opposite of what task 1.4 asks for.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe

protonspy and others added 2 commits July 31, 2026 15:14
A single self-contained HTML file, no network, so it opens the same on a
machine with no internet — which is what the application is. The shell and its
four panes work: switch pane, start a recording, watch the palette move when a
colour changes.

Two arguments the draft is making. Amber is provenance, not a brand colour: a
citation, a source, a page that has one. It is the accent because tracing a
claim back to the minute someone said it is the only thing this product does
that nothing else does. And the reading surface is warm while the rest is cold
slate — the content is paper, the application is the instrument holding it.

Form carries meaning where colour would lie. A source nothing cites gets a
dashed, dimmed pill rather than a colour, because its problem is disappearing
from view and the state should look like disappearing. The chunk strip shows
which chunk failed rather than a percentage, because the chunk is the retry
unit and a percentage erases the thing that decides the next click.

Tokens use shadcn/ui names so the palette copies into globals.css, and icons
are Lucide inlined as a sprite so they become lucide-react imports one for one.
Dark only, deliberately: task 8.1 asks for it, and an application open beside
an hour-long meeting should not glow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe
The project is open-wiki, not project-wiki. Renamed across 18 places including
plans/open-wiki.md and the @open-wiki/* package scope.

A skill teaching an agent to call list_pages and read_page was written and is
removed again. The tool names were invented for a server nobody has built, and
a SKILL.md loads into an agent's context — so it is not merely rework, it is
fiction the next reader honours as a decision. The convention it carried was
already real and already belongs to group 5 and 9.14; the calling sequence was
not. Task 9.17 now writes it from the tool list group 9 actually shipped, and
the plan says why it is last.

ADR 0010 — a derived-index engine as a second Rust binary behind a CLI.
Proposed, not accepted: it contradicts the plan's out-of-scope line and touches
the boundary ADR 0005 said to defend, so a person answers first. The argument
is that group 7's checks and group 9's search are the same traversal written
twice, not that anything is slow — at MVP scale nothing is, and the record says
so rather than inventing a benchmark.

ADR 0011 — a source is named by what it is, and that name is frozen. A file
keeps its filename and a duplicate is refused; a recording is named for the
occasion and the date. The date is part of the shape rather than a tiebreak,
because recurring meetings are the normal case. The id freezes and the title
stays editable: citations embed the id and raw/ is immutable, so fixing a typo
must not break a citation.

ADR 0012 — transcription is journalled, serial and resumable. Each chunk is
written before the next starts, so a run killed at chunk five loses one chunk
rather than four. Serial replaces parallel because neither provider gains:
Groq returns a ten-minute chunk in about three seconds, and whisper.cpp already
saturates every core. A journal whose chunk boundaries, provider or model
changed is refused rather than reused — stitching two segmentations produces a
readable, plausible, wrong timeline. timeline.vtt is written beside
timeline.json so the recording opens in any player. This completes the deletion
ordering ADR 0006 demanded but had nothing durable to check against.

Also adds docs/wiki/ with the Claude Code plugin research, whose one finding
that reaches the plan is headersHelper: a plugin can generate its own auth
headers at connection time, which removes the pasted token of task 9.13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a standalone Open Wiki desktop prototype, records architecture and data-processing decisions, updates the product plan, applies the package rename, and adds wiki documentation for project distribution and Claude Code plugin integration.

Changes

Desktop prototype

Layer / File(s) Summary
Prototype visual foundation
design/desktop-draft.html
Adds the dark visual system, reusable styles, inline icons, component specimens, accessibility behavior, and explanatory footer.
Application views and workflows
design/desktop-draft.html
Adds the application shell, onboarding, editor refusal states, provenance view, settings, dialogs, history, and source and recording states.
Prototype interaction wiring
design/desktop-draft.html
Adds pane navigation, recording controls, MCP controls, onboarding navigation, waveform rendering, token editing, and clipboard export behavior.

Architecture and product planning

Layer / File(s) Summary
Index and source contracts
docs/adr/0010-a-derived-index-engine-behind-a-cli.md, docs/adr/0011-sources-are-named-by-what-they-are.md, plans/open-wiki.md
Defines the derived-index CLI, immutable source naming, editable titles, duplicate rejection, and related plan updates.
Journalled transcription contract
docs/adr/0012-transcription-is-a-journalled-serial-pipeline.md, docs/glossary.md, plans/open-wiki.md
Defines durable chunk journals, serial resumable transcription, timeline outputs, WAV retention, and related terminology.
Open Wiki implementation plan
plans/open-wiki.md
Updates workspace artifacts, MCP token handling, skill and plugin packaging, retention rules, and future text ingestion notes.

Rename and distribution documentation

Layer / File(s) Summary
Package rename wiring
package.json, .github/workflows/release.yml, scripts/ci/workspace-packages.mjs
Changes package metadata and package references from project-wiki to open-wiki.
Wiki and distribution documentation
docs/wiki/*, docs/stack.md
Adds wiki navigation, changelog content, Claude Code plugin documentation, and the renamed plan link.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: the project rename, desktop UI draft, and three product decisions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/open-wiki-decisions

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (1)
design/desktop-draft.html (1)

1867-1880: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use roving tabindex in the radio groups.

Every role="radio" element carries tabindex="0", and the script binds only Enter and Space. The ARIA radiogroup pattern gives tabindex="0" to the checked radio and tabindex="-1" to the rest, and moves selection with the arrow keys. The same pattern appears in the provider group at Lines 1897-1912. Fix it here so the React port inherits the correct behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/desktop-draft.html` around lines 1867 - 1880, Update the
content-language radio group around the checked and unchecked role="radio"
elements to use roving tabindex: keep tabindex="0" only on the selected English
option and set tabindex="-1" on the other choices. Add arrow-key navigation so
moving between options updates selection and transfers tabindex="0" to the newly
selected radio, matching the provider group behavior and preserving Enter/Space
activation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yml:
- Line 77: Add the missing apps/desktop package manifest with the package name
set to `@open-wiki/desktop` before the release workflow invokes the pnpm filter in
the desktop packaging step, so the workspace can resolve that filter
successfully.

In `@design/desktop-draft.html`:
- Around line 1403-1437: Update the citation and wikilink placeholders in the
draft so every .cite and .wikilink control has a real navigable target via href,
or use a keyboard-operable button where no destination exists yet. Preserve the
existing labels and styling while ensuring these controls are focusable and
actionable for keyboard users.
- Line 2111: Update the canvas identified by id="wave" so its backing-store
dimensions match the CSS-rendered box, at minimum changing the height attribute
from 112 to 56 while preserving the rendered 100% width behavior. If drawing
code resizes the canvas, instead derive its dimensions from the client rect and
devicePixelRatio before rendering to maintain the correct aspect ratio.
- Around line 2891-2902: Update the btnCopyTokens click handler to check whether
navigator.clipboard and its writeText API are available before calling them, and
preserve the existing success and failure feedback for supported contexts. When
unavailable, provide the generated css through a selectable text fallback and
update copyState so file:// users receive the CSS and clear guidance instead of
an exception.
- Around line 1344-1361: Update the rail markup around the tablist and pane
elements: add unique tab IDs with matching aria-controls values for the Wiki,
Sources, MCP, and Checks tabs, and add corresponding aria-labelledby values to
each panel including `#pane-wiki`. Close the role="tablist" nav before the
rail-spacer, placing the EN button outside the tablist while preserving its
existing behavior.
- Around line 1192-1265: Replace every sprite entry’s <g id="i-…"> wrapper with
a <symbol id="i-…" viewBox="0 0 24 24"> wrapper, preserving each icon’s existing
attributes and paths. Apply this consistently to all definitions inside the SVG
sprite so every <use href="`#i-`…"> scales correctly within its referencing icon
element.

In `@docs/adr/0010-a-derived-index-engine-behind-a-cli.md`:
- Line 45: Update the CLI example fence in the ADR document to specify the text
language identifier, adding “text” to the opening fence on the indicated example
while preserving its contents.
- Around line 46-53: Update the graph command definitions shown in the CLI usage
examples so every graph subcommand accepts a <project> selector, including
neighbors, sources, superseded, and orphans. Keep the existing graph arguments
and descriptions intact while making project scope explicit for all graph
operations.
- Around line 100-103: Update the stale-index discussion in ADR 0010 to define
the CLI query contract: specify how freshness is detected after external edits
and whether queries refresh the index, reject results, or return an explicit
stale marker. Ensure the documented behavior applies consistently to MCP and UI
provenance queries.

In `@docs/adr/0011-sources-are-named-by-what-they-are.md`:
- Around line 50-56: Define a single source-ID contract in
docs/adr/0011-sources-are-named-by-what-they-are.md:50-56, explicitly stating
whether file extensions are retained and what fallback or rejection applies when
normalization yields an empty ID. Update the citations and workspace examples
there to follow that contract, then revise plans/open-wiki.md:70-76, including
the arquitetura-fenix.pdf/ example, to use the same resulting directory names;
both sites require changes.

In `@docs/adr/0012-transcription-is-a-journalled-serial-pipeline.md`:
- Around line 45-49: Update the journal identity and resume refusal check to
fingerprint and compare every transcription-affecting input, including content
language and vocabulary seeded from project pages, in addition to chunk
boundaries, provider, and model. Ensure resuming is rejected with a clean
restart whenever any fingerprinted setting differs.
- Around line 25-31: Update the journal persistence requirements in the ADR to
specify crash-safe atomic writes: write the complete journal to a temporary
file, durably flush it, then atomically replace the existing journal and ensure
the replacement is durable. Define startup recovery for an incomplete or corrupt
journal, preserving the last valid committed state so completed chunks can
resume safely.
- Around line 57-59: The WAV deletion plan must use one durable seal gate: in
docs/adr/0012-transcription-is-a-journalled-serial-pipeline.md lines 57-59,
define that the journal is durably committed with every chunk successful and all
required derived artifacts durably on disk before deletion; in
plans/open-wiki.md line 129, replace the provider-success deletion condition
with this same gate.
- Around line 42-44: The resumption rules must define a consistent recovery path
for chunks left in flight by an application crash. In
docs/adr/0012-transcription-is-a-journalled-serial-pipeline.md lines 42-44,
specify that stale in-flight chunks are retryable on resume alongside failed and
never-attempted chunks; in plans/open-wiki.md line 132, require reopening to
retry those stale in-flight entries.

In `@docs/glossary.md`:
- Around line 9-15: Update the source definition in the glossary to say sources
are “immutable once sealed” instead of “immutable once written,” and specify
that uploaded files are sealed when written while recordings are sealed after
transcription completes and their journal and WAV files are removed. Preserve
the existing naming guidance and source terminology.

In `@docs/wiki/claude-code-plugins.md`:
- Around line 71-78: Update the documentation around headersHelper to explicitly
state that it executes arbitrary shell commands with user filesystem access and
must only be used by trusted plugins. Document least-privilege, workspace-scoped
token rotation and revocation procedures, and instruct users not to log the
token or helper output.
- Around line 58-69: Update the Claude Code version guidance in the plugin
documentation: state that relative headersHelper paths require Claude Code
2.1.195 or later, and that versions 2.1.207 and later reject ${user_config.*}
while earlier versions substitute it. Replace the broad “Claude Code v2.1.x”
wording and revise the guidance around the headersHelper configuration
accordingly.

In `@docs/wiki/index.md`:
- Around line 3-9: Update the wiki index near the existing Distribution and
integration links to add a link to the changelog page, ensuring the new
changelog page is reachable from the index as stated by the introductory text.

In `@plans/open-wiki.md`:
- Around line 263-266: Update the retention plan around the abandoned-run
behavior to define a user-controlled policy for stalled WAV files: provide a
visible discard action and specify how long abandoned recordings are retained
before automatic cleanup, while preserving recovery for runs still eligible for
confirmation.

---

Nitpick comments:
In `@design/desktop-draft.html`:
- Around line 1867-1880: Update the content-language radio group around the
checked and unchecked role="radio" elements to use roving tabindex: keep
tabindex="0" only on the selected English option and set tabindex="-1" on the
other choices. Add arrow-key navigation so moving between options updates
selection and transfers tabindex="0" to the newly selected radio, matching the
provider group behavior and preserving Enter/Space activation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f957428-c35e-49c6-83e1-a4de55697361

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba5da0 and d8abfdf.

📒 Files selected for processing (13)
  • .github/workflows/release.yml
  • design/desktop-draft.html
  • docs/adr/0010-a-derived-index-engine-behind-a-cli.md
  • docs/adr/0011-sources-are-named-by-what-they-are.md
  • docs/adr/0012-transcription-is-a-journalled-serial-pipeline.md
  • docs/glossary.md
  • docs/stack.md
  • docs/wiki/changelog.md
  • docs/wiki/claude-code-plugins.md
  • docs/wiki/index.md
  • package.json
  • plans/open-wiki.md
  • scripts/ci/workspace-packages.mjs

# Publishing is this workflow's job, not electron-builder's.
GH_TOKEN: ""
run: pnpm --filter "@project-wiki/desktop" run package
run: pnpm --filter "@open-wiki/desktop" run package

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect apps/desktop/package.json to declare `@open-wiki/desktop`.
fd -t f package.json . | xargs -r rg -n '"name": "@(open-wiki|project-wiki)/desktop"'

# Review old-name hits. Historical changelog prose may remain; active metadata,
# workflows, scripts, and links must use the new names.
rg -n --hidden -g '!.git' \
  '"name": "project-wiki"|`@project-wiki/`|plans/project-wiki\.md' .

Repository: protonspy/open-wiki

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- desktop package manifests ---'
fd -t f -i 'package.json' . | while IFS= read -r f; do
  if rg -q '"name"[[:space:]]*:[[:space:]]*"(`@open-wiki`|`@project-wiki`)/desktop"' "$f"; then
    rg -n '"name"[[:space:]]*:[[:space:]]*"(`@open-wiki`|`@project-wiki`)/desktop"' "$f"
    printf 'file: %s\n' "$f"
  fi
done

printf '%s\n' '--- workspace package names and filter references ---'
rg -n --hidden -g '!.git' \
  '"name"[[:space:]]*:[[:space:]]*"(`@open-wiki`|`@project-wiki`)/desktop"|`@open-wiki/desktop`|`@project-wiki/desktop`|filter' .

printf '%s\n' '--- workflow context ---'
cat -n .github/workflows/release.yml | sed -n '65,85p'

Repository: protonspy/open-wiki

Length of output: 1861


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- repository files relevant to workspace metadata ---'
git ls-files | rg '(^|/)(package\.json|pnpm-workspace\.yaml|pnpm-lock\.yaml|release\.yml)$|desktop'

printf '%s\n' '--- all package manifest names ---'
fd -t f -i 'package.json' . | while IFS= read -r f; do
  printf 'file: %s\n' "$f"
  rg -n '"name"[[:space:]]*:' "$f" | head -n 5
done

printf '%s\n' '--- exact active old-scope references ---'
rg -n --hidden -g '!.git' '`@project-wiki/`|plans/project-wiki\.md|"name"[[:space:]]*:[[:space:]]*"project-wiki' . || true

Repository: protonspy/open-wiki

Length of output: 425


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- workspace configuration ---'
cat -n pnpm-workspace.yaml
printf '%s\n' '--- root package manifest ---'
cat -n package.json
printf '%s\n' '--- workspace package discovery script ---'
cat -n scripts/ci/workspace-packages.mjs | sed -n '1,115p'
printf '%s\n' '--- desktop paths tracked or present ---'
git ls-files apps/desktop
find apps/desktop -maxdepth 2 -type f -print 2>/dev/null | sort || true
printf '%s\n' '--- ignore rules relevant to desktop ---'
rg -n 'desktop|package\.json|apps' .gitignore .git/info/exclude 2>/dev/null || true

Repository: protonspy/open-wiki

Length of output: 5089


Add apps/desktop/package.json with "name": "@open-wiki/desktop" before using this filter. The workspace has no desktop package manifest, so pnpm cannot resolve the filter and the installer build fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 77, Add the missing apps/desktop
package manifest with the package name set to `@open-wiki/desktop` before the
release workflow invokes the pnpm filter in the desktop packaging step, so the
workspace can resolve that filter successfully.

Comment thread design/desktop-draft.html
Comment on lines +1192 to +1265
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<g id="i-book" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</g>
<g id="i-layers" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="m12 2 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5"/><path d="m3 17 9 5 9-5"/>
</g>
<g id="i-server" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/>
<path d="M6 6h.01"/><path d="M6 18h.01"/>
</g>
<g id="i-mic" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><path d="M12 19v3"/>
</g>
<g id="i-file" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>
<path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/>
</g>
<g id="i-waves" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v3"/>
</g>
<g id="i-search" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
</g>
<g id="i-plus" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"/><path d="M12 5v14"/>
</g>
<g id="i-pause" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="14" y="4" width="4" height="16" rx="1"/><rect x="6" y="4" width="4" height="16" rx="1"/>
</g>
<g id="i-stop" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="4" width="16" height="16" rx="2"/>
</g>
<g id="i-copy" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</g>
<g id="i-undo" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>
</g>
<g id="i-clock" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
</g>
<g id="i-alert" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/>
</g>
<g id="i-warn" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>
</g>
<g id="i-check" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="M22 4 12 14.01l-3-3"/>
</g>
<g id="i-chevron-down" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</g>
<g id="i-folder" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>
</g>
<g id="i-pencil" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
</g>
<g id="i-sliders" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 4h-7"/><path d="M10 4H3"/><path d="M21 12h-9"/><path d="M8 12H3"/><path d="M21 20h-5"/><path d="M12 20H3"/>
<path d="M14 2v4"/><path d="M8 10v4"/><path d="M16 18v4"/>
</g>
<g id="i-retry" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/>
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>
</g>
<g id="i-globe" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>
</g>
</defs>
</svg>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use <symbol viewBox="0 0 24 24"> instead of <g> in the sprite.

The Lucide paths use a 24×24 coordinate system. A <g> carries no viewport, so the referencing <svg class="icon"> element has no viewBox and renders the paths at 1:1 user units. Each icon is then clipped to the top-left corner of the 15×15 box. This affects every <use href="#i-…"> in the file.

Change each <g id="i-…"> to <symbol id="i-…" viewBox="0 0 24 24">. A <use> reference to a <symbol> establishes the viewport and scales the artwork into the icon box.

🐛 Proposed fix, applied to each sprite entry
-    <g id="i-book" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
+    <symbol id="i-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
       <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
-    </g>
+    </symbol>

Alternatively, add viewBox="0 0 24 24" to the .icon, .icon-sm, and .icon-lg elements. The <symbol> change is smaller.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/desktop-draft.html` around lines 1192 - 1265, Replace every sprite
entry’s <g id="i-…"> wrapper with a <symbol id="i-…" viewBox="0 0 24 24">
wrapper, preserving each icon’s existing attributes and paths. Apply this
consistently to all definitions inside the SVG sprite so every <use href="`#i-`…">
scales correctly within its referencing icon element.

Comment thread design/desktop-draft.html
Comment on lines +1344 to +1361
<nav class="rail" role="tablist" aria-label="Sections">
<button class="rail-btn" role="tab" aria-selected="true" data-pane="wiki">
<svg class="icon"><use href="#i-book"/></svg> Wiki
</button>
<button class="rail-btn" role="tab" aria-selected="false" data-pane="sources">
<svg class="icon"><use href="#i-layers"/></svg> Sources
</button>
<button class="rail-btn" role="tab" aria-selected="false" data-pane="mcp">
<svg class="icon"><use href="#i-server"/></svg> MCP
</button>
<button class="rail-btn" role="tab" aria-selected="false" data-pane="checks">
<svg class="icon"><use href="#i-check"/></svg> Checks
</button>
<span class="rail-spacer"></span>
<button class="rail-btn" type="button" title="Content language: English">
<svg class="icon"><use href="#i-globe"/></svg> EN
</button>
</nav>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete the tablist contract, and move the EN button out of the tablist.

Two problems exist in this rail:

  1. The EN button at Line 1358 is a child of role="tablist" but has no role="tab". A tablist accepts only tabs as owned children.
  2. No aria-controls links each tab to its panel, and no aria-labelledby links each panel back to its tab.

The React port copies this markup, so fix the pattern here.

🛠 Proposed fix
-        <div class="app-body">
-          <nav class="rail" role="tablist" aria-label="Sections">
-            <button class="rail-btn" role="tab" aria-selected="true" data-pane="wiki">
+        <div class="app-body">
+          <nav class="rail" aria-label="Sections">
+            <div class="rail-tabs" role="tablist" aria-label="Sections">
+            <button class="rail-btn" type="button" role="tab" id="tab-wiki" aria-controls="pane-wiki" aria-selected="true" data-pane="wiki">
               <svg class="icon"><use href="`#i-book`"/></svg> Wiki
             </button>

Repeat the id/aria-controls pair for the sources, mcp, and checks tabs, close the role="tablist" container before the rail-spacer, and add aria-labelledby="tab-wiki" to #pane-wiki and the matching value to the other panels.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/desktop-draft.html` around lines 1344 - 1361, Update the rail markup
around the tablist and pane elements: add unique tab IDs with matching
aria-controls values for the Wiki, Sources, MCP, and Checks tabs, and add
corresponding aria-labelledby values to each panel including `#pane-wiki`. Close
the role="tablist" nav before the rail-spacer, placing the EN button outside the
tablist while preserving its existing behavior.

Comment thread design/desktop-draft.html
Comment on lines +1403 to +1437
<p>
Fenix replaces the message broker behind order intake. It is deliberately not a
rewrite of <a class="wikilink">Billing rewrite</a> — the two share a
<a class="wikilink">Cutover window</a> and nothing else.
</p>

<h2>Decisions</h2>

<div class="claim decision">
<span class="claim-type">decision · 2026-07-31</span>
Cutover happens queue by queue, starting with <code class="inline-code">orders.intake</code>,
and the legacy broker stays running until the last one moves.<a class="cite"><svg class="icon-sm"><use href="#i-waves"/></svg>14:32</a>
</div>

<div class="claim decision">
<span class="claim-type">decision · 2026-07-24 · superseded</span>
<span class="superseded">A single big-bang cutover over one weekend.</span>
Replaced by the queue-by-queue plan above.<a class="cite"><svg class="icon-sm"><use href="#i-file"/></svg>p.12</a>
</div>

<h2>Open questions</h2>
<ul>
<li>
Who owns the rollback if a queue fails mid-cutover? Named nobody in the meeting.<a class="cite"><svg class="icon-sm"><use href="#i-waves"/></svg>41:07</a>
</li>
<li>
Retention on the replayed messages is still the legacy 30 days — see
<a class="wikilink">Data retention</a>.
</li>
</ul>

<h2>Action items</h2>
<ul>
<li><a class="wikilink">Renata Alves</a> writes the per-queue runbook before the next review.<a class="cite"><svg class="icon-sm"><use href="#i-waves"/></svg>52:18</a></li>
</ul>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document that .cite and .wikilink need a real target in the port.

Each <a class="cite"> and <a class="wikilink"> has no href. An anchor without href is not focusable and not keyboard-operable, but the CSS gives it cursor: pointer. The draft is stated to be the specification for the React build, so add a note or use <button> for these placeholders. Keyboard users must be able to follow a citation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/desktop-draft.html` around lines 1403 - 1437, Update the citation and
wikilink placeholders in the draft so every .cite and .wikilink control has a
real navigable target via href, or use a keyboard-operable button where no
destination exists yet. Preserve the existing labels and styling while ensuring
these controls are focusable and actionable for keyboard users.

Comment thread design/desktop-draft.html
</div>

<div class="wave-wrap">
<canvas id="wave" width="900" height="112" aria-label="Waveform of the recording, cited moment at 14:32"></canvas>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match the canvas backing store to its rendered box.

The width/height attributes set a 900×112 backing store. The CSS at Line 821 renders the canvas at width: 100% and height: 56px. The browser scales the bitmap to fit, so the drawn waveform is squashed vertically and stretched or compressed horizontally by an amount that depends on the pane width. Set height="56" to keep a 1:1 vertical mapping, or size the canvas from its client rect and multiply by devicePixelRatio before drawing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@design/desktop-draft.html` at line 2111, Update the canvas identified by
id="wave" so its backing-store dimensions match the CSS-rendered box, at minimum
changing the height attribute from 112 to 56 while preserving the rendered 100%
width behavior. If drawing code resizes the canvas, instead derive its
dimensions from the client rect and devicePixelRatio before rendering to
maintain the correct aspect ratio.

Comment thread docs/glossary.md
Comment on lines +9 to +15
- **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
- **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`. Avoid: transcript
- **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`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align source immutability with the accepted transcription lifecycle.

The glossary says every source is “immutable once written,” but ADR 0012 says a recording remains mutable until transcription completes and its journal and WAV files are removed. Change the definition to “immutable once sealed,” and state the seal point for uploaded files and recordings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/glossary.md` around lines 9 - 15, Update the source definition in the
glossary to say sources are “immutable once sealed” instead of “immutable once
written,” and specify that uploaded files are sealed when written while
recordings are sealed after transcription completes and their journal and WAV
files are removed. Preserve the existing naming guidance and source terminology.

Comment on lines +58 to +69
"type": "http",
"url": "http://127.0.0.1:7331/mcp",
"headersHelper": "node scripts/mcp-headers.mjs"
}
}
}
```

The command writes a JSON object of string pairs to stdout, runs in a shell with a
ten-second timeout, and for a plugin-provided server runs with its working directory set
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`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
git ls-files docs/wiki/claude-code-plugins.md
printf '%s\n' '--- relevant lines ---'
sed -n '45,90p;135,152p' docs/wiki/claude-code-plugins.md
printf '%s\n' '--- related references ---'
rg -n -C 3 'headersHelper|mcp-headers|2\.1\.(19[3-9]|20[0-9])|user_config|Claude Code v2\.1' .

Repository: protonspy/open-wiki

Length of output: 6777


🌐 Web query:

Claude Code MCP headersHelper plugin root minimum version 2.1.195 ${user_config.*} 2.1.207 official documentation

💡 Result:

In Claude Code, the headersHelper field is used to generate dynamic authentication headers for MCP servers at connection time [1]. Recent versions of Claude Code have introduced specific changes regarding how this helper functions, particularly concerning version requirements, path resolution, and configuration security [1][2]. Version 2.1.195 and later Claude Code v2.1.195 introduced support for expanding the ${CLAUDE_PLUGIN_ROOT} placeholder within the headersHelper configuration string [1]. Prior to this version, the placeholder was passed as a literal string, which caused issues when trying to reference bundled helper scripts [1]. Version 2.1.207 and later Claude Code v2.1.207 introduced a security fix to prevent shell injection [2][3]. As a result, the use of ${user_config.} placeholders within shell-form commands—including headersHelper—is now rejected [2][4]. If your configuration previously relied on ${user_config.} inside a headersHelper command, you must update your implementation [2][4]. Instead of passing these values directly in the command string, you should: 1. Read the configuration values directly within your helper script [2][4]. 2. Or, pass the necessary values through the MCP server's env block in your configuration, which is then accessible to your script [2][4]. General headersHelper behavior - Execution: headersHelper executes arbitrary shell commands [1]. - Path Resolution: The path for headersHelper is resolved relative to the plugin directory [1]. - Environment Variables: When the helper command executes, it is provided with the environment variables CLAUDE_CODE_MCP_SERVER_NAME and CLAUDE_CODE_MCP_SERVER_URL, which allow a single script to serve multiple MCP servers [5]. - Security: For project or local-scope plugins, headersHelper only executes after you have accepted the workspace trust dialog [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
p = Path("docs/wiki/claude-code-plugins.md")
text = p.read_text()
for needle in ("headersHelper", "mcp-headers.mjs", "user_config", "Claude Code"):
    print(f"--- {needle} ---")
    for i, line in enumerate(text.splitlines(), 1):
        if needle in line:
            print(f"{i}: {line}")
PY

Repository: protonspy/open-wiki

Length of output: 912


🌐 Web query:

site:code.claude.com/docs/en/mcp headersHelper plugin root 2.1.195 2.1.207

💡 Result:

In the context of Claude Code, the headersHelper is a feature used to generate request headers dynamically at connection time for MCP servers, particularly for authentication schemes like Kerberos or short-lived tokens [1]. The versions 2.1.195 and 2.1.207 mark specific functional changes regarding this feature: 1. Version 2.1.195: Introduced the ability for path placeholders (such as ${CLAUDE_PLUGIN_ROOT}) to be properly resolved within headersHelper, whereas previously they were passed as literal strings [1]. Additionally, for plugin-provided servers, the helper command's working directory is set to the plugin root, allowing relative paths to resolve within the plugin directory [1]. 2. Version 2.1.207: Changed the behavior regarding user configuration values. Prior to version 2.1.207, headersHelper would substitute ${user_config.*} values. This was changed because the command runs through a shell, and it is now recommended to place these values in the server's headers field instead to avoid misconfiguration [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from urllib.request import urlopen
urls = [
    "https://code.claude.com/docs/en/mcp",
    "https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md",
]
for url in urls:
    print(f"--- {url} ---")
    data = urlopen(url, timeout=20).read().decode("utf-8", "replace")
    terms = ("headersHelper", "CLAUDE_PLUGIN_ROOT", "user_config", "2.1.193", "2.1.195", "2.1.207")
    lines = data.splitlines()
    hits = [i for i, line in enumerate(lines) if any(t in line for t in terms)]
    for i in hits:
        lo, hi = max(0, i-3), min(len(lines), i+4)
        print("\n".join(f"{j+1}: {lines[j]}" for j in range(lo, hi)))
        print("---")
PY

Repository: protonspy/open-wiki

Length of output: 2487


🌐 Web query:

site:github.com/anthropics/claude-code/blob/main/CHANGELOG.md "2.1.195" "headersHelper"

💡 Result:

The term headersHelper appears as a component associated with plugin hooks and monitors within the changelog for claude-code [1]. Specifically, in version 2.1.195, a security update was implemented for headersHelper to address a shell-injection vulnerability [1]. This update rejects the use of ${user_config.*} syntax within shell-form commands [1]. Users are instead advised to read configuration values directly inside the script, such as from a configuration file or the environment block of the server [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
for url in \
  "https://code.claude.com/docs/en/mcp" \
  "https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md"; do
  echo "--- $url ---"
  curl -k -L --fail --silent "$url" |
    rg -n -C 4 'headersHelper|CLAUDE_PLUGIN_ROOT|user_config|2\.1\.193|2\.1\.195|2\.1\.207' || true
done

Repository: protonspy/open-wiki

Length of output: 50376


🌐 Web query:

Claude Code CHANGELOG 2.1.195 headersHelper user_config 2.1.207

💡 Result:

The Claude Code CHANGELOG entries for versions 2.1.195 and 2.1.207 relate to significant updates regarding MCP (Model Context Protocol) authentication and security, specifically concerning the use of headersHelper and ${user_config.*} variables [1][2][3]. Claude Code v2.1.195 In version 2.1.195, support for the headersHelper field was introduced (or notably updated) for MCP servers, allowing for dynamic generation of authentication headers [1]. This feature enables users to define an executable script that generates request headers at connection time, which is particularly useful for handling authentication schemes such as Kerberos, short-lived tokens, or internal SSO [1][4]. At this stage, headersHelper supported the substitution of ${user_config.*} values directly within the command string [1]. Claude Code v2.1.207 Version 2.1.207 introduced breaking changes to mitigate a shell-injection vulnerability [2][5]. Specifically: * Rejection of ${user_config.*}: The use of ${user_config.*} notation in shell-form commands for MCP headersHelper, as well as for plugin hooks and monitors, is now rejected [2][3]. * Migration Strategy: Instead of direct interpolation in the command string, plugin authors must read these configuration values inside the script itself (e.g., via a config file or the server's env block) [2][5]. * Project Settings Restriction: Plugin option values (pluginConfigs) are no longer read from project-level .claude/settings.json files; only user-level, --settings, and managed settings are honored, preventing repositories from potentially injecting malicious configurations [2][3]. These changes require authors of plugins and tools using headersHelper to update their implementation to move away from direct shell-based variable interpolation for security reasons [6][5].

Citations:


🌐 Web query:

Claude Code 2.1.195 headersHelper relative path plugin root

💡 Result:

As of Claude Code version 2.1.195, the behavior of headersHelper in MCP plugin configurations regarding relative paths and the plugin root is as follows: In version 2.1.195 and later, when using a plugin-provided MCP server, the headersHelper command runs with its working directory set to the plugin root [1]. This means a relative path defined in headersHelper (e.g., "scripts/get-headers.sh") will resolve correctly inside the plugin directory, rather than against the session's current working directory [1]. Prior to this version, relative paths for headersHelper were resolved against the session's current working directory, which often caused the helper script to be "not found" (ENOENT) if the Claude Code session was launched from a directory other than the plugin's root [2]. Important notes regarding usage: - Variable Substitution: The placeholder ${CLAUDE_PLUGIN_ROOT} is designed for use in MCP server configurations [1][3]. While documented as a way to reference plugin files, users have reported issues where this variable does not consistently expand within the headersHelper string itself [4][5]. - Best Practice: Due to the known limitations with variable expansion in certain fields, relying on the working directory behavior (where the helper runs in the plugin root) is the intended mechanism for relative paths [1]. - Environment Variables: When the headersHelper command executes, Claude Code provides the environment variables CLAUDE_CODE_MCP_SERVER_NAME and CLAUDE_CODE_MCP_SERVER_URL, which allow a single helper script to identify and serve multiple MCP servers [6]. If you continue to experience issues where the path is not resolved, ensure your plugin structure places your script at the plugin root level and use a simple relative path (e.g., "./scripts/your-script.sh") rather than relying on ${CLAUDE_PLUGIN_ROOT} expansion within the headersHelper value [1][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import subprocess
from pathlib import Path
log = Path("/tmp/coderabbit-shell-logs/shell-output-DUy83r").read_text(errors="replace")
terms = [
    "Before v2.1.195",
    "Before v2.1.207",
    "headersHelper",
    "user_config",
    "working directory set to the plugin root",
]
for term in terms:
    print(f"--- {term} ---")
    starts = [i for i in range(len(log)) if log.startswith(term, i)]
    for i in starts[:12]:
        print(log[max(0, i-500):i+900].replace("\\n", "\n"))
        print("---")
PY

Repository: protonspy/open-wiki

Length of output: 42413


🏁 Script executed:

#!/bin/bash
set -eu
tmp=$(mktemp)
curl -k -L --fail --silent https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md > "$tmp"
rg -n -C 8 '2\.1\.195|2\.1\.207|headersHelper|user_config' "$tmp" | head -n 240
rm -f "$tmp"

Repository: protonspy/open-wiki

Length of output: 17804


Document the Claude Code version boundaries.

Relative plugin headersHelper paths require Claude Code 2.1.195 or later. Claude Code 2.1.207 and later reject ${user_config.*} in headersHelper; earlier versions substituted it. Replace “Claude Code v2.1.x” and update the guidance at lines 80–83.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wiki/claude-code-plugins.md` around lines 58 - 69, Update the Claude
Code version guidance in the plugin documentation: state that relative
headersHelper paths require Claude Code 2.1.195 or later, and that versions
2.1.207 and later reject ${user_config.*} while earlier versions substitute it.
Replace the broad “Claude Code v2.1.x” wording and revise the guidance around
the headersHelper configuration accordingly.

Comment on lines +71 to +78
**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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- target excerpt ---'
sed -n '1,130p' docs/wiki/claude-code-plugins.md
printf '%s\n' '--- related references ---'
rg -n -i 'headersHelper|config\.json|MCP token|bearer|Task 9\.13|0007-plaintext-credentials|marketplace plugin|trusted plugin' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300
printf '%s\n' '--- candidate files ---'
git ls-files | rg -i '(claude|mcp|0007|config|plugin)'

Repository: protonspy/open-wiki

Length of output: 8537


🏁 Script executed:

set -e
sed -n '1,130p' docs/wiki/claude-code-plugins.md
printf '\n--- references ---\n'
rg -n -i 'headersHelper|config\.json|MCP token|bearer|Task 9\.13|0007-plaintext-credentials|marketplace plugin|trusted plugin' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300
printf '\n--- candidate files ---\n'
git ls-files | rg -i '(claude|mcp|0007|config|plugin)'

Repository: protonspy/open-wiki

Length of output: 8508


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- credential ADR ---'
sed -n '1,120p' docs/adr/0007-plaintext-credentials-in-the-config.md
printf '%s\n' '--- token and revocation references ---'
rg -n -i 'mcp.*token|token.*mcp|workspace.*token|revo|rotat|expire|scope|secret|credential' docs plans packages apps src 2>/dev/null | head -300

Repository: protonspy/open-wiki

Length of output: 7497


🌐 Web query:

Claude Code MCP headersHelper arbitrary shell commands plugin trust security documentation

💡 Result:

In Claude Code, headersHelper is a configuration option for Model Context Protocol (MCP) servers that allows for dynamic authentication by executing arbitrary shell commands to generate request headers [1][2]. Because this feature involves running external commands, it is governed by the tool's workspace trust security model [1][2]. Key technical and security details regarding headersHelper: Functionality and Scope: - Purpose: It is intended for authentication schemes that require short-lived tokens, dynamic headers, or custom SSO, where static headers fields are insufficient [1][3]. - Execution: The headersHelper command runs in a shell with a 10-second timeout and must output a JSON object containing string key-value pairs to standard output (stdout) [2][4]. - Context: When multiple MCP servers are configured, the helper script can use the environment variables CLAUDE_CODE_MCP_SERVER_NAME and CLAUDE_CODE_MCP_SERVER_URL to determine which server's headers to generate [2][4]. Security and Trust: - Workspace Trust: The headersHelper command only executes after a user has explicitly accepted the workspace trust dialog for the given project directory [1][2]. This is a critical security safeguard designed to prevent unauthorized code execution when opening untrusted repositories [5][6]. - Risk Mitigation: Because headersHelper runs arbitrary shell commands, it poses a potential vector for code execution if a user trusts a malicious project [1][6]. Claude Code uses a trust-per-workspace model to mitigate this, ensuring that configuration-based execution (including hooks and custom scripts) does not run until the user has reviewed and accepted the project's trust settings [5][7]. - Configuration: When defining a headersHelper in a plugin, it cannot directly reference ${user_config.*} values because the command is shell-parsed; instead, such values should be placed in the headers field or handled by the helper script reading its own environment [1]. Documentation: - Official documentation for headersHelper and MCP integration is located within the Claude Code documentation portal (specifically under the MCP integration section) and the project's repository documentation [1][3]. Users should also review the general security and permissions documentation to understand the broader context of tool and command execution within the Claude Code environment [5][7][8].

Citations:


Document the trust boundary before using headersHelper.

After workspace trust, headersHelper runs arbitrary shell commands with the user's filesystem access. A compromised trusted plugin can therefore read and exfiltrate the plaintext mcp.token from config.json. Document that only trusted plugins may use this helper. Define least-privilege, workspace-scoped token rotation and revocation. Do not log the token or helper output.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wiki/claude-code-plugins.md` around lines 71 - 78, Update the
documentation around headersHelper to explicitly state that it executes
arbitrary shell commands with user filesystem access and must only be used by
trusted plugins. Document least-privilege, workspace-scoped token rotation and
revocation procedures, and instruct users not to log the token or helper output.

Comment thread docs/wiki/index.md
Comment on lines +3 to +9
What this project knows that is not in the code and not in a spec. Every page is reachable
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Link the changelog from the index.

Lines 3-4 state that every page is reachable from the index. Lines 8-9 omit the new changelog page.

Proposed fix
 ## Distribution and integration
+- [[changelog]] — what changed in the wiki, newest first.
 - [[claude-code-plugins]] — how a Claude Code plugin is built, installed and distributed,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
What this project knows that is not in the code and not in a spec. Every page is reachable
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.
What this project knows that is not in the code and not in a spec. Every page is reachable
from here; a page that is not is a page nobody will find again.
## Distribution and integration
- [[changelog]] — what changed in the wiki, newest first.
- [[claude-code-plugins]] — how a Claude Code plugin is built, installed and distributed,
and the one thing shipping one would buy this product.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wiki/index.md` around lines 3 - 9, Update the wiki index near the
existing Distribution and integration links to add a link to the changelog page,
ensuring the new changelog page is reachable from the index as stated by the
introductory text.

Comment thread plans/open-wiki.md
Comment on lines +263 to +266
the recording; never deleting fills the disk in twenty meetings. The journal of 4.9 is what
the confirmation is checked against, so 4.9 and 4.17 come before 4.14 does anything
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Define user-controlled retention for abandoned WAV files.

The plan says an abandoned run keeps raw WAV data “forever.” Meeting audio can contain sensitive information, and each run can consume hundreds of megabytes. Add a visible discard action and a retention policy for stalled recordings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plans/open-wiki.md` around lines 263 - 266, Update the retention plan around
the abandoned-run behavior to define a user-controlled policy for stalled WAV
files: provide a visible discard action and specify how long abandoned
recordings are retained before automatic cleanup, while preserving recovery for
runs still eligible for confirmation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant