open-wiki: English docs, CI with a 76% coverage floor, tagged releases, five decisions and a UI draft - #2
Conversation
…anguage a setting The project is English from here on. The seven ADRs, the glossary, the stack and the plan are translated, and each ADR slug is renamed with it: the slug is the id everything cites (`adr:0001-no-backend-byok`), so leaving it in Portuguese would translate the prose and not the reference. All 24 citations were updated. The content language stops being a fixed decision. ADR 0008 records it as a setting, English by default, with Brazilian Portuguese and Spanish alongside it, reaching exactly two places: the transcription hint and the `CLAUDE.md` generated in the project. The page schema stays English regardless — translating the frontmatter keys would mean a wiki written in Spanish is not readable by a tool that reads a wiki written in English. In the plan, that replaces "Brazilian Portuguese" in the decided list, adds tasks 4.15 and 8.12, and folds the language into the `CLAUDE.md` 9.14 generates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe
One `windows-latest` job per pnpm workspace package, discovered from the globs in pnpm-workspace.yaml so that adding a workspace root never means remembering to edit CI. `fail-fast: false`, because one package failing must not hide the state of the others — a single aggregate run would let a well-tested package cover for one with no tests at all. The floor is enforced twice on purpose. vitest.shared.ts carries the thresholds so the failure happens locally first; scripts/ci/check-coverage.mjs re-reads coverage-summary.json in CI, so a package that dropped `coverage.thresholds` from its own config still fails. A package that reports no coverage at all fails rather than passing silently. The docs test suite is removed with this: `tests/` and the vitest config that ran it are gone, and what was left of the root config became the shared bases the packages extend (tsconfig.base.json, vitest.shared.ts). Until task 1.1 creates the packages the matrix is empty, so the test, lint and Rust jobs are skipped and the `ci` gate passes on an empty workspace. Task 1.3 stays unticked for that reason — the workflow exists, but there is no Rust build, no linter and no coverage to run yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe
The tag is the trigger and CI is the only builder — nothing is packaged on a developer's machine and uploaded by hand. ADR 0009 records the rest of it: GitHub Releases as the download host, since adr:0001-no-backend-byok leaves us no server of our own and a stable URL with a published hash is all that the winget and Scoop manifests of task 10.3 need. Three refusals the workflow makes before building, because each one is a mistake that is public by the time it is noticed: a tag that disagrees with the app version, a tag that already has a release, and a package step that produced no .exe. A published release cannot be un-downloaded, so it is written once. Unsigned in the MVP, which means SmartScreen warns with "Don't run" as the default button — worst in exactly the corporate environment this product is built for. CSC_LINK and CSC_KEY_PASSWORD are read from secrets, so buying a certificate is a settings change rather than a workflow rewrite. Group 10 of the plan grows a task for the release itself; 10.1 gains the NSIS and output-directory convention the workflow globs for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe
📝 WalkthroughWalkthroughThe change adds workspace tooling, TypeScript and Vitest configuration, Windows CI and release workflows, and coverage enforcement. It also adds English architecture ADRs and translates or expands the glossary, stack documentation, and project roadmap. ChangesDelivery automation
Architecture documentation
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (11)
docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md-35-36 (1)
35-36: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse
well-formedas the compound adjective.Replace “well formed” with “well-formed” in Line 36.
🤖 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/adr/0003-mcp-as-the-only-bridge-to-the-llm.md` around lines 35 - 36, Update the wording in the ADR sentence to use the hyphenated compound adjective “well-formed” instead of “well formed,” without changing the surrounding text.Source: Linters/SAST tools
docs/adr/0002-workspace-as-a-local-markdown-folder.md-22-22 (1)
22-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCapitalize
Markdown.Use the proper name
Markdownin Line 22.🤖 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/adr/0002-workspace-as-a-local-markdown-folder.md` at line 22, Update the prose at the referenced Markdown documentation line to capitalize “Markdown,” while leaving the surrounding wording unchanged.Source: Linters/SAST tools
docs/stack.md-53-53 (1)
53-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAlign the CI description with the workflow behavior.
The workflow creates a matrix entry only for discovered workspace packages with tests. It skips empty workspaces. The release build and publication run in a separate workflow.
Update this sentence so the documentation does not imply one job for every workspace or that the CI test workflow publishes releases.
Proposed wording
-- **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`. +- **GitHub Actions** — CI on `windows-latest`, which is the only platform the product supports. One job per discovered workspace package with tests; empty workspaces are skipped. A separate release workflow builds and publishes the release — see `adr:0009-distribution-through-github-releases`.🤖 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/stack.md` at line 53, Update the GitHub Actions description in the stack documentation to state that the CI matrix creates jobs only for discovered workspace packages containing tests, skips empty workspaces, and does not build or publish releases; describe release build and publication as handled by a separate workflow.docs/stack.md-21-21 (1)
21-21: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUpdate the
whisper-large-v3-turbospeed figure.Groq documents a 216x real-time speed factor. Replace
~228xwith~216x, or remove the exact benchmark and record its method and date.🤖 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/stack.md` at line 21, Update the `whisper-large-v3-turbo` entry in the stack documentation to replace the outdated `~228x` speed figure with Groq’s documented `~216x` figure, keeping the surrounding provider description unchanged.docs/stack.md-20-20 (1)
20-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winQualify the bitrate and upload-limit claim.
Opus 24 kbpsis a selected quality/size trade-off, not the only encoding that keeps one hour below 25 MB. State the target tier and upload method: direct uploads are limited to 25 MB, while the 100 MB Developer Tier limit requires a URL.🤖 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/stack.md` at line 20, Update the Opus 24 kbps statement in the stack documentation to describe it as the selected quality/size target rather than the only encoding meeting the limit. Specify that direct uploads are capped at 25 MB, while files up to the 100 MB Developer Tier limit must be submitted via URL, and retain the ADR reference.plans/project-wiki.md-8-8 (1)
8-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winHyphenate the compound adjective.
Use
open-source desktop application.Proposed fix
-An open source desktop application (Windows 10/11, Apache-2.0) that **centralises a +An open-source desktop application (Windows 10/11, Apache-2.0) that **centralises a🤖 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/project-wiki.md` at line 8, Update the introductory description to hyphenate “open source” as “open-source” when it modifies “desktop application,” preserving the surrounding wording.Source: Linters/SAST tools
plans/project-wiki.md-89-89 (1)
89-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winSpecify the complete coverage acceptance criterion.
The PR objective requires 76% coverage for lines, statements, functions, and branches, with empty workspaces skipped. State these conditions in Task 1.3 so the roadmap defines an executable CI requirement.
Proposed wording
-- [ ] 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.3 (Unit) CI on GitHub Actions on `windows-latest`: Rust and TypeScript builds, lint, and tests with at least 76% lines, statements, functions, and branches per non-empty package; skip empty workspaces🤖 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/project-wiki.md` at line 89, Update Task 1.3 in the project roadmap to specify a 76% coverage floor for lines, statements, functions, and branches, and explicitly state that empty workspaces are skipped. Keep the existing Windows GitHub Actions requirements for Rust and TypeScript builds, tests, and linting.plans/project-wiki.md-66-66 (1)
66-66: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd a language to the fenced workspace tree.
markdownlintreports MD040 at this fence. Usetextas the language.Proposed fix
-``` +```text🤖 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/project-wiki.md` at line 66, Update the fenced workspace tree code block in project-wiki.md to specify text as its language by changing the fence opener to use the text language identifier.Source: Linters/SAST tools
.github/workflows/ci.yml-33-33 (1)
33-33: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winSet
persist-credentials: falseon checkout steps.zizmor flags all four
actions/checkout@v4steps (discover, test, checks, rust jobs) for credential persistence (artipacked): none of them need the checked-out git credentials afterward, since no job in this workflow pushes to the repository. Workflow-levelpermissions: contents: read(line 14) limits the blast radius, but disabling credential persistence is still the safer default, especially beforepnpm installruns third-party install scripts in the same job.🔒 Proposed fix
- uses: actions/checkout@v4 + with: + persist-credentials: falseApply the same change at all four
actions/checkout@v4steps.Also applies to: 54-54, 86-86, 110-110
🤖 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/ci.yml at line 33, Update all four actions/checkout@v4 steps in the discover, test, checks, and rust jobs to set persist-credentials to false. Keep the existing checkout behavior and other step configuration unchanged.Source: Linters/SAST tools
docs/adr/0008-content-language-is-a-setting-english-by-default.md-44-44 (1)
44-44: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse the hyphenated compound modifier.
Write
open-source projectat Line [44].🤖 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/adr/0008-content-language-is-a-setting-english-by-default.md` at line 44, Update the sentence in the ADR so the compound modifier uses the hyphenated form “open-source project.”Source: Linters/SAST tools
.github/workflows/release.yml-37-40 (1)
37-40: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCompare the complete tag with the version.
The
v*trigger acceptsvv1.2.3, andTrimStart("v")removes all leadingvcharacters. Compare${{ github.ref_name }}with"v$version"or remove exactly one leadingvafter validating the tag format.🤖 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 around lines 37 - 40, Update the version check in the release workflow to compare the complete github.ref_name against the expected “v” + package version, or validate and remove exactly one leading “v” before comparison. Do not use TrimStart("v"), which accepts tags with multiple leading v characters such as vv1.2.3.
🧹 Nitpick comments (6)
vitest.shared.ts (1)
1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCoverage threshold
76is hardcoded independently in three files. The value has no single source of truth, so a future update to one site can silently drift from the others, weakening or unexpectedly tightening the enforced floor.
vitest.shared.ts#L1-L9: keepCOVERAGE_THRESHOLDas the canonical definition, but consider making it consumable by the other two sites (e.g., a plain JSON/text file both the workflow and the Node script read, or a Node import if the toolchain supports running.tsdirectly) instead of a TypeScript-only export..github/workflows/ci.yml#L16-L20: rather than hardcoding"76"inenv.COVERAGE_THRESHOLD, source this value from the same placevitest.shared.tsandscripts/ci/check-coverage.mjsread it from.scripts/ci/check-coverage.mjs#L22-L27: rather than defaulting to a hardcoded76whenCOVERAGE_THRESHOLDis unset, read the same shared value asvitest.shared.tsso the fallback can't drift from the canonical constant.🤖 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 `@vitest.shared.ts` around lines 1 - 9, Make COVERAGE_THRESHOLD in vitest.shared.ts the single source of truth by exposing it through a format consumable by all tooling. Update .github/workflows/ci.yml lines 16-20 and scripts/ci/check-coverage.mjs lines 22-27 to read that shared value instead of hardcoding or defaulting to 76; preserve the existing CI and coverage-check behavior.scripts/ci/workspace-packages.mjs (1)
21-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a real YAML parser for future-proofing.
workspaceGlobs()parsespnpm-workspace.yamlwith a hand-rolled regex loop. It works for the current file, but it can silently mis-parse or under-parse the moment the file gains an inline comment after a list item, a flow-stylepackages: [...]list, or another top-level key likecatalog:placed beforepackages:. Using a small YAML library would remove this class of parsing edge cases.🤖 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 `@scripts/ci/workspace-packages.mjs` around lines 21 - 38, The workspaceGlobs function should stop parsing pnpm-workspace.yaml with the hand-rolled regex loop and use an established YAML parser instead. Read the parsed document’s packages field, return its glob list when present, and preserve the existing empty-array behavior when the file is missing or packages is absent.tsconfig.base.json (1)
2-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConfirm scoping
vitest/globalsto test files, not the shared production base.Line 7 adds
"vitest/globals"totsconfig.base.json, which every package is meant to extend (per the extension example invitest.shared.ts). This makes Vitest's global test API (describe,it,expect,vi, etc.) resolve as ambient types in every file that extends this base, including production source files, not only test files. Consider splitting this into a base config withoutvitest/globalsfor production code, and a narrower test-only config (ortsconfig.test.json) that adds it, since no packages exist yet to migrate later.♻️ Proposed refactor sketch
{ "compilerOptions": { "target": "ES2023", "lib": ["ES2023"], "module": "ESNext", "moduleResolution": "bundler", - "types": ["node", "vitest/globals"], + "types": ["node"], "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "noFallthroughCasesInSwitch": true, "verbatimModuleSyntax": true, "isolatedModules": true, "skipLibCheck": true, "noEmit": true } }Then add a
tsconfig.test.json(or per-package test tsconfig) that extends this base and adds"types": ["node", "vitest/globals"]for test file compilation only.🤖 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 `@tsconfig.base.json` around lines 2 - 16, Remove "vitest/globals" from the shared compilerOptions.types in tsconfig.base.json so production files do not receive Vitest ambient types. Add a test-only tsconfig.test.json extending the base and defining types for node and vitest/globals, preserving the existing compiler settings for test compilation.package.json (1)
15-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the dependency baseline. The repository uses Vitest 3.x and TypeScript 5.x, while newer major lines are available. State why these versions are required for the MVP, or plan a compatibility upgrade. Keep
vitestand@vitest/coverage-v8on the same major because CI parsescoverage-summary.json. Workspace packages must declare their own dependencies; they do not inherit rootdevDependencies.🤖 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 `@package.json` around lines 15 - 20, Document the MVP compatibility rationale for the TypeScript 5.x and Vitest 3.x versions in the repository dependency configuration, or update them through a compatibility upgrade. Keep vitest and `@vitest/coverage-v8` on the same major version, and ensure each workspace package explicitly declares any dependencies it uses instead of relying on root devDependencies..github/workflows/release.yml (2)
3-6: 🔒 Security & Privacy | 🔵 TrivialProtect release tags before enabling signing.
The
v*trigger can run repository code withCSC_LINKandCSC_KEY_PASSWORD. The workflow does not restrict who can create a matching tag. Verify that repository rules protectv*tags and require approval for release runs.Also applies to: 68-74
🤖 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 around lines 3 - 6, Update the release workflow’s v* tag trigger and repository configuration to ensure matching tags are protected from unauthorized creation and release runs require explicit approval before accessing CSC_LINK or CSC_KEY_PASSWORD. Preserve the existing workflow_dispatch path while enforcing these protections for all release executions.
21-21: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPin release actions to immutable commit SHAs.
This workflow uses mutable major tags for checkout, pnpm setup, Node setup, release publication, and artifact upload. A moved or compromised action tag can change a workflow with
contents: writeand signing secrets. Pin full-length commit SHAs and update them through dependency automation.Also applies to: 59-61, 97-97, 111-111
🤖 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 21, Pin every release workflow action currently referenced by mutable tags—including checkout, pnpm setup, Node setup, release publication, and artifact upload—to full-length immutable commit SHAs. Update the action references at the locations corresponding to actions/checkout@v4 and the additional reported lines, preserving their existing versions and configuration, and use dependency automation to maintain the pinned SHAs.
🤖 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/ci.yml:
- Around line 80-84: Update the checks job condition in the checks workflow job
so typecheck and lint run independently of needs.discover.outputs.any or
test-script discovery. Preserve the existing discover dependency only if
required for workflow ordering, and ensure the ci aggregator continues to
include the checks result.
In @.github/workflows/release.yml:
- Around line 37-40: Update both affected release workflow steps to define
RELEASE_TAG in their env blocks from github.ref_name, then replace direct
github.ref_name interpolation with $env:RELEASE_TAG for the PowerShell version
comparison, gh command, and related messages.
- Around line 45-57: Update the “Refuse to republish an existing release” step
to continue only when gh release view returns an explicit 404; fail the workflow
for authentication, rate-limit, network, or any other lookup error. Then
configure the subsequent softprops/action-gh-release@v2 publication step to use
create-only behavior and prevent updating existing releases or overwriting
matching assets.
- Around line 84-92: Update the installer validation and publishing flow around
the PowerShell installer discovery to require exactly one expected
electron-builder NSIS installer, rather than accepting every .exe in the release
directory. Fail when the NSIS target is missing or multiple installers are
found, then publish only that explicit installer path and generate its SHA256
checksum.
- Line 21: Update the actions/checkout@v4 step in the release workflow to set
persist-credentials to false, preventing the write-capable token from being
retained in the repository’s Git configuration.
In `@docs/adr/0001-no-backend-byok.md`:
- Around line 21-23: The ADRs use inconsistent authentication terminology. In
docs/adr/0001-no-backend-byok.md lines 21-23, revise the no-authentication
statement to limit it to hosted accounts and user authentication. In
docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md line 38, state that the
application stores the transcription credential and local MCP token, but no LLM
credential.
In `@docs/adr/0002-workspace-as-a-local-markdown-folder.md`:
- Around line 28-33: Define a shared transaction protocol in
docs/adr/0002-workspace-as-a-local-markdown-folder.md covering journal creation,
snapshotting, replacement commit order, operation-log recording, and crash
recovery. Update docs/adr/0004-markdown-editing-without-blocks.md so page
renames and wikilink repairs use this protocol and commit all-or-nothing.
In `@docs/adr/0005-wasapi-capture-in-a-minimal-sidecar.md`:
- Around line 28-30: Expand the sidecar protocol specification around the listed
methods (start, pause, resume, stop, status, devices) to define the JSON-RPC
version, stdio framing, parameters, results, errors, request IDs, and shutdown
behavior. Resolve the conflicting file-ownership statements at the writing
section and line 54 by explicitly listing permitted audio artifacts and
prohibiting workspace or wiki writes when they belong to JavaScript. Add shared
request/response fixtures for both Rust and TypeScript implementations before
implementation proceeds.
In `@docs/adr/0006-opus-as-the-provenance-format.md`:
- Around line 25-26: Align the WAV retention policy across both ADRs: in
docs/adr/0006-opus-as-the-provenance-format.md, define whether deletion is
mandatory or configurable, and in
docs/adr/0007-plaintext-credentials-in-the-config.md, remove the setting or
document the behavior when it is false. Ensure both files describe one
consistent retention rule.
- Around line 30-31: Update the storage estimate in the ADR to account for both
separately preserved microphone and system-output tracks: clarify that the
approximately 11 MB estimate and 24 kbps bitrate apply per track, then state the
resulting approximately 22 MB per meeting and 440 MB for twenty meetings.
In `@docs/adr/0007-plaintext-credentials-in-the-config.md`:
- Around line 22-30: Update the plaintext-config bootstrap guidance to treat an
empty mcp.token as unconfigured and prevent MCP startup until a high-entropy
token is supplied; also require user-only ACLs for config.json and its parent
application-data directory, consistent with the MCP authentication requirement.
In `@plans/project-wiki.md`:
- Line 31: Reconcile the plan’s credential model across the exclusion statement
and Sections 9.3 and 9.13: either define local MCP token authorization as an
in-scope mechanism with its lifecycle and harness handling, or remove the
per-workspace token requirement and its exposure. Ensure the plan consistently
identifies which application credentials and authentication controls exist.
- Around line 22-30: Clarify the application/agent boundary in the scope
section: state that the application performs mechanical parsing, transcription,
Markdown validation, persistence, and editor/MCP writes, while the agent
performs semantic extraction, summarisation, and page generation. Update the
claims around “calls no LLM” and “does not write content” so they do not
contradict these responsibilities, while preserving the MCP-only bridge to the
model.
---
Minor comments:
In @.github/workflows/ci.yml:
- Line 33: Update all four actions/checkout@v4 steps in the discover, test,
checks, and rust jobs to set persist-credentials to false. Keep the existing
checkout behavior and other step configuration unchanged.
In @.github/workflows/release.yml:
- Around line 37-40: Update the version check in the release workflow to compare
the complete github.ref_name against the expected “v” + package version, or
validate and remove exactly one leading “v” before comparison. Do not use
TrimStart("v"), which accepts tags with multiple leading v characters such as
vv1.2.3.
In `@docs/adr/0002-workspace-as-a-local-markdown-folder.md`:
- Line 22: Update the prose at the referenced Markdown documentation line to
capitalize “Markdown,” while leaving the surrounding wording unchanged.
In `@docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md`:
- Around line 35-36: Update the wording in the ADR sentence to use the
hyphenated compound adjective “well-formed” instead of “well formed,” without
changing the surrounding text.
In `@docs/adr/0008-content-language-is-a-setting-english-by-default.md`:
- Line 44: Update the sentence in the ADR so the compound modifier uses the
hyphenated form “open-source project.”
In `@docs/stack.md`:
- Line 53: Update the GitHub Actions description in the stack documentation to
state that the CI matrix creates jobs only for discovered workspace packages
containing tests, skips empty workspaces, and does not build or publish
releases; describe release build and publication as handled by a separate
workflow.
- Line 21: Update the `whisper-large-v3-turbo` entry in the stack documentation
to replace the outdated `~228x` speed figure with Groq’s documented `~216x`
figure, keeping the surrounding provider description unchanged.
- Line 20: Update the Opus 24 kbps statement in the stack documentation to
describe it as the selected quality/size target rather than the only encoding
meeting the limit. Specify that direct uploads are capped at 25 MB, while files
up to the 100 MB Developer Tier limit must be submitted via URL, and retain the
ADR reference.
In `@plans/project-wiki.md`:
- Line 8: Update the introductory description to hyphenate “open source” as
“open-source” when it modifies “desktop application,” preserving the surrounding
wording.
- Line 89: Update Task 1.3 in the project roadmap to specify a 76% coverage
floor for lines, statements, functions, and branches, and explicitly state that
empty workspaces are skipped. Keep the existing Windows GitHub Actions
requirements for Rust and TypeScript builds, tests, and linting.
- Line 66: Update the fenced workspace tree code block in project-wiki.md to
specify text as its language by changing the fence opener to use the text
language identifier.
---
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 3-6: Update the release workflow’s v* tag trigger and repository
configuration to ensure matching tags are protected from unauthorized creation
and release runs require explicit approval before accessing CSC_LINK or
CSC_KEY_PASSWORD. Preserve the existing workflow_dispatch path while enforcing
these protections for all release executions.
- Line 21: Pin every release workflow action currently referenced by mutable
tags—including checkout, pnpm setup, Node setup, release publication, and
artifact upload—to full-length immutable commit SHAs. Update the action
references at the locations corresponding to actions/checkout@v4 and the
additional reported lines, preserving their existing versions and configuration,
and use dependency automation to maintain the pinned SHAs.
In `@package.json`:
- Around line 15-20: Document the MVP compatibility rationale for the TypeScript
5.x and Vitest 3.x versions in the repository dependency configuration, or
update them through a compatibility upgrade. Keep vitest and `@vitest/coverage-v8`
on the same major version, and ensure each workspace package explicitly declares
any dependencies it uses instead of relying on root devDependencies.
In `@scripts/ci/workspace-packages.mjs`:
- Around line 21-38: The workspaceGlobs function should stop parsing
pnpm-workspace.yaml with the hand-rolled regex loop and use an established YAML
parser instead. Read the parsed document’s packages field, return its glob list
when present, and preserve the existing empty-array behavior when the file is
missing or packages is absent.
In `@tsconfig.base.json`:
- Around line 2-16: Remove "vitest/globals" from the shared
compilerOptions.types in tsconfig.base.json so production files do not receive
Vitest ambient types. Add a test-only tsconfig.test.json extending the base and
defining types for node and vitest/globals, preserving the existing compiler
settings for test compilation.
In `@vitest.shared.ts`:
- Around line 1-9: Make COVERAGE_THRESHOLD in vitest.shared.ts the single source
of truth by exposing it through a format consumable by all tooling. Update
.github/workflows/ci.yml lines 16-20 and scripts/ci/check-coverage.mjs lines
22-27 to read that shared value instead of hardcoding or defaulting to 76;
preserve the existing CI and coverage-check behavior.
🪄 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: 56bd45f3-e728-453b-a2d4-487e27935b97
📒 Files selected for processing (28)
.github/workflows/ci.yml.github/workflows/release.ymldocs/adr/0001-no-backend-byok.mddocs/adr/0001-sem-backend-byok.mddocs/adr/0002-workspace-as-a-local-markdown-folder.mddocs/adr/0002-workspace-como-pasta-local-de-markdown.mddocs/adr/0003-mcp-as-the-only-bridge-to-the-llm.mddocs/adr/0003-mcp-como-unica-ponte-com-o-llm.mddocs/adr/0004-edicao-de-markdown-sem-blocos.mddocs/adr/0004-markdown-editing-without-blocks.mddocs/adr/0005-captura-wasapi-num-sidecar-minimo.mddocs/adr/0005-wasapi-capture-in-a-minimal-sidecar.mddocs/adr/0006-opus-as-the-provenance-format.mddocs/adr/0006-opus-como-formato-de-proveniencia.mddocs/adr/0007-credenciais-em-texto-claro-no-config.mddocs/adr/0007-plaintext-credentials-in-the-config.mddocs/adr/0008-content-language-is-a-setting-english-by-default.mddocs/adr/0009-distribution-through-github-releases.mddocs/glossary.mddocs/stack.mdpackage.jsonplans/project-wiki.mdpnpm-workspace.yamlscripts/ci/check-coverage.mjsscripts/ci/workspace-packages.mjstsconfig.base.jsontsconfig.jsonvitest.shared.ts
💤 Files with no reviewable changes (7)
- docs/adr/0001-sem-backend-byok.md
- docs/adr/0002-workspace-como-pasta-local-de-markdown.md
- docs/adr/0005-captura-wasapi-num-sidecar-minimo.md
- docs/adr/0006-opus-como-formato-de-proveniencia.md
- docs/adr/0004-edicao-de-markdown-sem-blocos.md
- docs/adr/0007-credenciais-em-texto-claro-no-config.md
- docs/adr/0003-mcp-como-unica-ponte-com-o-llm.md
| checks: | ||
| name: typecheck & lint | ||
| needs: discover | ||
| if: needs.discover.outputs.any == 'true' | ||
| runs-on: windows-latest |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
checks job never runs while no package declares a test script.
if: needs.discover.outputs.any == 'true' gates the typecheck & lint job on whether any workspace package has a test/test:coverage script (per scripts/ci/workspace-packages.mjs). Per the PR's own status, no packages exist yet, so any is false today. This means the checks job — which runs pnpm run typecheck and pnpm run lint at the root, validating the very tsconfig.base.json/tsconfig.json/vitest.shared.ts files this PR adds — is skipped entirely, and the ci aggregator job (lines 124-134) treats a skipped job as passing. The root typecheck/lint commands already tolerate an empty workspace (--if-present), so this job doesn't need to depend on test-script presence at all.
🐛 Proposed fix
checks:
name: typecheck & lint
needs: discover
- if: needs.discover.outputs.any == 'true'
runs-on: windows-latest📝 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.
| checks: | |
| name: typecheck & lint | |
| needs: discover | |
| if: needs.discover.outputs.any == 'true' | |
| runs-on: windows-latest | |
| checks: | |
| name: typecheck & lint | |
| needs: discover | |
| runs-on: windows-latest |
🤖 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/ci.yml around lines 80 - 84, Update the checks job
condition in the checks workflow job so typecheck and lint run independently of
needs.discover.outputs.any or test-script discovery. Preserve the existing
discover dependency only if required for workflow ordering, and ensure the ci
aggregator continues to include the checks result.
| name: Build and publish the installer | ||
| runs-on: windows-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/release.yml
printf '%s\n' '--- package manifests and scripts ---'
git ls-files '*package.json' '*pnpm-lock.yaml' '*npmrc' '*yarnrc*' | head -80
rg -n --glob 'package.json' '"(scripts|packageManager|engines|prepare|preinstall|install|postinstall)"' .
printf '%s\n' '--- checkout and credential references ---'
rg -n 'actions/checkout|persist-credentials|github\.token|GITHUB_TOKEN|\.git/config|pnpm install|electron-builder|nsis|makensis' .github package.json pnpm-workspace.yaml 2>/dev/null || trueRepository: protonspy/open-wiki
Length of output: 6183
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- tracked workflow files ---'
git ls-files '.github/workflows/*'
printf '%s\n' '--- release-related files ---'
git ls-files | rg '(^|/)(package\.json|pnpm-workspace\.yaml|pnpm-lock\.yaml|.*release.*|.*package.*|.*electron.*|.*nsis.*)$' | head -120
printf '%s\n' '--- repository configuration ---'
git ls-files | rg '(^|/)(\.npmrc|\.pnpmfile\.cjs|\.gitignore|CODEOWNERS|README.*|CONTRIBUTING.*)$' | head -120Repository: protonspy/open-wiki
Length of output: 468
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- root package.json ---'
cat -n package.json
printf '%s\n' '--- workspace definition ---'
cat -n pnpm-workspace.yaml
printf '%s\n' '--- tracked desktop files ---'
git ls-files 'apps/desktop/*' | head -80
printf '%s\n' '--- distribution ADR ---'
cat -n docs/adr/0009-distribution-through-github-releases.mdRepository: protonspy/open-wiki
Length of output: 4839
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
workflow = Path(".github/workflows/release.yml").read_text()
checkout = workflow.index("actions/checkout@v4")
install = workflow.index("pnpm install --frozen-lockfile")
package = workflow.index('pnpm --filter "`@project-wiki/desktop`" run package')
assert checkout < install < package
assert "persist-credentials: false" not in workflow
assert "contents: write" in workflow
assert "GH_TOKEN: ${{ github.token }}" in workflow
print("checkout precedes install and packaging")
print("persist-credentials override: absent")
print("job permission: contents: write")
print("release check token: github.token")
PYRepository: protonspy/open-wiki
Length of output: 300
Disable checkout credential persistence.
This workflow grants contents: write. Installation and packaging run after checkout, so compromised code could use the persisted token from .git/config.
Set persist-credentials: false.
🧰 Tools
🪛 zizmor (1.28.0)
[warning] 21-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 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 21, Update the actions/checkout@v4
step in the release workflow to set persist-credentials to false, preventing the
write-capable token from being retained in the repository’s Git configuration.
Source: Linters/SAST tools
| $tag = "${{ github.ref_name }}".TrimStart("v") | ||
| $version = (Get-Content "apps/desktop/package.json" -Raw | ConvertFrom-Json).version | ||
| if ($tag -ne $version) { | ||
| Write-Error "tag v$tag does not match apps/desktop/package.json version $version" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/release.yml'
printf '%s\n' '--- relevant workflow sections ---'
sed -n '1,90p' .github/workflows/release.yml
printf '%s\n' '--- all github.ref_name uses ---'
rg -n -C 4 'github\.ref_name|RELEASE_TAG|ConvertFrom-Json|TrimStart' .github/workflows/release.ymlRepository: protonspy/open-wiki
Length of output: 4892
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import shutil
import subprocess
candidates = {
"tag-injection": 'v"; Write-Output "TAG_INJECTION',
"tag-injection-with-version-prefix": 'v1.2.3"; Write-Output "TAG_INJECTION',
"release-command-injection": 'v"; Write-Output "RELEASE_INJECTION',
}
workflow_lines = {
"version-check": '$tag = "${TAG}".TrimStart("v")',
"release-view": 'gh release view "${TAG}" 2>$null',
"existing-release-error": 'Write-Error "release ${TAG} already exists"',
"no-release-output": 'Write-Host "no release for ${TAG} yet"',
}
for label, tag in candidates.items():
ref = f"refs/tags/{tag}"
result = subprocess.run(
["git", "check-ref-format", ref],
text=True,
capture_output=True,
)
print(f"{label}: git_ref_name_allowed={result.returncode == 0}")
if result.returncode == 0:
for name, template in workflow_lines.items():
print(f" {name}: {template.replace('${TAG}', tag)}")
print("pwsh_available:", bool(shutil.which("pwsh")))
print("powershell_available:", bool(shutil.which("powershell")))
PYRepository: protonspy/open-wiki
Length of output: 364
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import subprocess
tags = [
'v$(Write-Output TAG_INJECTION)',
'v1.2.3$(Write-Output TAG_INJECTION)',
'v`$(Write-Output TAG_INJECTION)',
'v$(Get-Date)',
'v${HOME}',
]
templates = {
"version-check": '$tag = "${TAG}".TrimStart("v"); Write-Output "VERSION_DONE"',
"release-view": 'Write-Output "before"; Write-Output "${TAG}"; Write-Output "after"',
"existing-release-error": 'Write-Error "release ${TAG} already exists" 2>$null',
"no-release-output": 'Write-Host "no release for ${TAG} yet"',
}
for tag in tags:
allowed = subprocess.run(
["git", "check-ref-format", f"refs/tags/{tag}"],
capture_output=True,
).returncode == 0
print(f"\nTAG {tag!r} allowed={allowed}")
if not allowed:
continue
for name, template in templates.items():
script = template.replace("${TAG}", tag)
result = subprocess.run(
["pwsh", "-NoProfile", "-NonInteractive", "-Command", script],
text=True,
capture_output=True,
)
print(f"{name}: exit={result.returncode}")
print(" stdout:", repr(result.stdout.strip()))
print(" stderr:", repr(result.stderr.strip()))
PYRepository: protonspy/open-wiki
Length of output: 951
🏁 Script executed:
#!/bin/bash
set -eu
pwsh -NoProfile -NonInteractive -Command @'
$tag = 'v$(Get-Date)'
function gh { param([Parameter(ValueFromRemainingArguments=$true)]$Args); Write-Output ("GH_ARGS=" + ($Args -join '|')) }
Write-Output '--- interpolated workflow source ---'
$tag = "v$(Get-Date)".TrimStart("v")
Write-Output ("TAG_VALUE=" + $tag)
gh release view "v$(Get-Date)" 2>$null
Write-Error "release v$(Get-Date) already exists" 2>$null
Write-Host "no release for v$(Get-Date) yet"
Write-Output '--- environment value ---'
$env:RELEASE_TAG = 'v$(Get-Date)'
$tag = ($env:RELEASE_TAG).TrimStart("v")
Write-Output ("TAG_VALUE=" + $tag)
gh release view "$env:RELEASE_TAG" 2>$null
Write-Error "release $env:RELEASE_TAG already exists" 2>$null
Write-Host "no release for $env:RELEASE_TAG yet"
'@Repository: protonspy/open-wiki
Length of output: 389
🏁 Script executed:
#!/bin/bash
set -eu
pwsh -NoProfile -NonInteractive -Command - <<'PS'
$tag = 'v$(Get-Date)'
function gh {
param([Parameter(ValueFromRemainingArguments=$true)]$Args)
Write-Output ("GH_ARGS=" + ($Args -join '|'))
}
Write-Output '--- interpolated workflow source ---'
$tag = "v$(Get-Date)".TrimStart("v")
Write-Output ("TAG_VALUE=" + $tag)
gh release view "v$(Get-Date)" 2>$null
Write-Error "release v$(Get-Date) already exists" 2>$null
Write-Host "no release for v$(Get-Date) yet"
Write-Output '--- environment value ---'
$env:RELEASE_TAG = 'v$(Get-Date)'
$tag = ($env:RELEASE_TAG).TrimStart("v")
Write-Output ("TAG_VALUE=" + $tag)
gh release view "$env:RELEASE_TAG" 2>$null
Write-Error "release $env:RELEASE_TAG already exists" 2>$null
Write-Host "no release for $env:RELEASE_TAG yet"
PSRepository: protonspy/open-wiki
Length of output: 420
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
import json
import pathlib
import subprocess
package = pathlib.Path("apps/desktop/package.json")
version = json.loads(package.read_text())["version"] if package.is_file() else "1.2.3"
tag = f"v$(Write-Output('{version}'))"
allowed = subprocess.run(
["git", "check-ref-format", f"refs/tags/{tag}"],
capture_output=True,
).returncode == 0
print("package_version:", version)
print("tag:", tag)
print("git_ref_name_allowed:", allowed)
PY
pwsh -NoProfile -NonInteractive -Command - <<'PS'
$version = '1.2.3'
$tag = "v$(Write-Output('1.2.3'))".TrimStart("v")
Write-Output ("tag=" + $tag)
Write-Output ("matches=" + ($tag -eq $version))
PSRepository: protonspy/open-wiki
Length of output: 261
Pass github.ref_name through env before using it in PowerShell.
A valid tag can execute a PowerShell subexpression during source interpolation and still produce a version-matching $tag. Set RELEASE_TAG: ${{ github.ref_name }} in both affected steps, then use $env:RELEASE_TAG for the version check, gh command, and messages.
🧰 Tools
🪛 zizmor (1.28.0)
[error] 37-37: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 around lines 37 - 40, Update both affected
release workflow steps to define RELEASE_TAG in their env blocks from
github.ref_name, then replace direct github.ref_name interpolation with
$env:RELEASE_TAG for the PowerShell version comparison, gh command, and related
messages.
Source: Linters/SAST tools
| - name: Refuse to republish an existing release | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| shell: pwsh | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # A published release has been downloaded; deleting it does not undo that. | ||
| gh release view "${{ github.ref_name }}" 2>$null | ||
| if ($LASTEXITCODE -eq 0) { | ||
| Write-Error "release ${{ github.ref_name }} already exists — bump the version and tag again" | ||
| exit 1 | ||
| } | ||
| Write-Host "no release for ${{ github.ref_name }} yet" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- workflow context ---'
sed -n '1,100p' .github/workflows/release.yml
printf '%s\n' '--- ADR and release-related references ---'
rg -n -C 3 'ADR.?0009|exactly.?once|gh release|release create|publish' .github README.md docs . 2>/dev/null | head -250Repository: protonspy/open-wiki
Length of output: 12761
🌐 Web query:
GitHub CLI gh release view exit codes 404 authentication rate limit documentation
💡 Result:
The GitHub CLI (gh) follows standard exit code conventions, where 0 indicates success and 1 indicates a general failure [1]. While some legacy man pages may list specific exit codes such as 4 for authentication [2][3], the current official GitHub CLI documentation defines its exit codes as follows [1]: 0: Successful execution. 1: Command failed for any reason. 2: Command was running but was cancelled. Regarding your specific queries: Exit Code 1: If a command like gh release view fails (for instance, due to a 404 "Not Found" error when a release does not exist), the CLI exits with code 1 [4][5]. It is common practice in automation scripts to check for these failures [5]. Authentication and Rate Limits: There is no distinct exit code for authentication errors or API rate limit exceeded errors in the current documentation; these are treated as command failures and result in an exit code of 1 [1]. Note on documentation: While you may encounter older or community-generated documentation suggesting an exit code of 4 for authentication [2][3], the official CLI help currently directs users to the standard convention where 1 represents all failures [1]. When writing scripts, you should generally rely on exit code 0 for success and non-zero (typically 1) for any failure, rather than relying on granular exit code differentiation [4].
Citations:
- 1: https://cli.github.com/manual/gh_help_exit-codes
- 2: https://man.archlinux.org/man/gh-release-view.1.en.txt
- 3: https://man.archlinux.org/man/gh-release.1.en.txt
- 4: Document exit codes for
gh releasecli/cli#6024 - 5: Harden release workflow with retry/backoff for release ID resolution github/gh-aw#31236
🌐 Web query:
softprops/action-gh-release v2 existing release overwrite behavior update release
💡 Result:
When using softprops/action-gh-release (v2), the action is designed to automatically detect if a GitHub release already exists for the specified tag [1][2]. If an existing release is found, the action will update it rather than creating a new one [1][3]. Regarding the behavior of release data and files: 1. Release Info: If release metadata fields (such as name, body, or prerelease status) are not explicitly defined in the action configuration, the existing release will retain its original information [1][2]. 2. File Overwriting: The action includes an overwrite_files input, which defaults to true [1][4]. When this is enabled, the action will overwrite existing release assets that share the same filename [1][4]. 3. Body Content: By default, the action overwrites the release body. If you wish to preserve existing content, you can set the append_body input to true [1][5]. 4. Draft Status: If the action reuses an existing draft release, you should set draft: true in your configuration to ensure it remains a draft; otherwise, the action will attempt to publish the release after uploading assets [1][2]. If you encounter unexpected behavior, such as release creation errors, ensure that your configuration and permissions are correct, as issues related to release updates are often tracked in the project's issue repository [6][7].
Citations:
- 1: https://github.com/softprops/action-gh-release/tree/refs/tags/v2
- 2: https://github.com/softprops/action-gh-release?tab=readme-ov-file
- 3: https://github.com/marketplace/actions/gh-release
- 4: https://github.com/softprops/action-gh-release/blob/master/action.yml
- 5: https://github.com/softprops/action-gh-release/blob/c062e08bd532815e2082a85e87e3ef29c3e6d191/README.md
- 6: In v2.5.0 we are back to releases getting stuck in retry loop softprops/action-gh-release#704
- 7: Existing releases are no longer updated softprops/action-gh-release#445
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- ADR details ---'
sed -n '55,70p' docs/adr/0009-distribution-through-github-releases.md
printf '%s\n' '--- workflow publish configuration ---'
sed -n '88,120p' .github/workflows/release.yml
printf '%s\n' '--- repository references to action-gh-release inputs ---'
rg -n -C 3 'softprops/action-gh-release|overwrite_files|fail_on_unmatched_files|tag_name|draft|make_latest|overwrite' .github docs plans README.md . 2>/dev/null | head -250Repository: protonspy/open-wiki
Length of output: 6824
Fail closed on release lookup errors. Continue only for an explicit 404. Authentication, rate-limit, and network failures must stop the workflow. softprops/action-gh-release@v2 updates existing releases and overwrites matching assets by default, so use a create-only publication operation to preserve the exactly-once rule.
🧰 Tools
🪛 zizmor (1.28.0)
[error] 52-52: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 54-54: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 57-57: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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 around lines 45 - 57, Update the “Refuse to
republish an existing release” step to continue only when gh release view
returns an explicit 404; fail the workflow for authentication, rate-limit,
network, or any other lookup error. Then configure the subsequent
softprops/action-gh-release@v2 publication step to use create-only behavior and
prevent updating existing releases or overwriting matching assets.
| $installers = @(Get-ChildItem -Path $dir -Filter *.exe -File -ErrorAction SilentlyContinue) | ||
| if ($installers.Count -eq 0) { | ||
| Write-Error "no .exe found in $dir — the package script produced no installer" | ||
| exit 1 | ||
| } | ||
| $installers | ForEach-Object { Write-Host " $($_.Name) $([math]::Round($_.Length / 1MB, 1)) MB" } | ||
| Get-FileHash -Algorithm SHA256 $installers.FullName | | ||
| ForEach-Object { "$($_.Hash.ToLower()) $(Split-Path $_.Path -Leaf)" } | | ||
| Out-File -FilePath "$dir/SHA256SUMS.txt" -Encoding utf8 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/release.yml' 'ADR*' '*ADR*'
printf '%s\n' '--- release workflow ---'
cat -n .github/workflows/release.yml | sed -n '1,125p'
printf '%s\n' '--- ADR 0009 references ---'
rg -n -i -C 4 '0009|single NSIS|NSIS|installer|electron-builder' . --glob '!node_modules' --glob '!dist' --glob '!build'Repository: protonspy/open-wiki
Length of output: 13756
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- package and build configuration references ---'
rg -n -i -C 5 'electron-builder|nsis|win\.target|portable|uninstaller|artifactName|publish|package script' . --glob '!node_modules' --glob '!dist' --glob '!build'
printf '%s\n' '--- workflow expressions and artifact publication ---'
rg -n -C 5 'installers|SHA256SUMS|upload-artifact|Get-ChildItem|package|artifact|\.exe' .github/workflows/release.ymlRepository: protonspy/open-wiki
Length of output: 13245
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path(".github/workflows/release.yml")
lines = p.read_text().splitlines()
for start, end in [(70, 110)]:
for n in range(start, min(end, len(lines)) + 1):
print(f"{n:4}: {lines[n-1]}")
PY
printf '%s\n' '--- candidate ADR files ---'
find . -type f \( -iname '*adr*' -o -iname '*decision*' \) -not -path '*/node_modules/*' -not -path '*/dist/*' -not -path '*/build/*' -printRepository: protonspy/open-wiki
Length of output: 2368
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- desktop package files ---'
git ls-files 'apps/desktop/*' 'package.json' 'pnpm-workspace.yaml' | sed -n '1,160p'
printf '%s\n' '--- package scripts and electron-builder configuration ---'
rg -n -i -C 8 '"package"|"build"|electron-builder|nsis|win[[:space:]]*:' \
--glob 'package.json' --glob '*.json' --glob '*.yaml' --glob '*.yml' \
apps package.json pnpm-workspace.yaml 2>/dev/null || true
printf '%s\n' '--- release references ---'
rg -n -C 4 'apps/desktop/release|SHA256SUMS|single NSIS|expected exactly one' \
. --glob '!node_modules' --glob '!dist' --glob '!build'Repository: protonspy/open-wiki
Length of output: 2773
Require exactly one NSIS installer.
The workflow treats every .exe in apps/desktop/release/ as the installer and publishes every match. Fail unless the expected installer set contains exactly one file. Publish that explicit path and generate its checksum. Validate the electron-builder NSIS target instead of relying on the .exe extension.
🤖 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 around lines 84 - 92, Update the installer
validation and publishing flow around the PowerShell installer discovery to
require exactly one expected electron-builder NSIS installer, rather than
accepting every .exe in the release directory. Fail when the NSIS target is
missing or multiple installers are found, then publish only that explicit
installer path and generate its SHA256 checksum.
| Opus 24 kbps mono is the permanent file format in `raw/`. The WAV is intermediate and is | ||
| discarded as soon as transcription confirms success. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use one WAV retention rule.
The mandatory deletion rule in docs/adr/0006-opus-as-the-provenance-format.md conflicts with the configurable field in docs/adr/0007-plaintext-credentials-in-the-config.md.
docs/adr/0006-opus-as-the-provenance-format.md#L25-L26: define whether deletion is mandatory or configurable.docs/adr/0007-plaintext-credentials-in-the-config.md#L31-L31: remove the setting or document the retention behavior when it isfalse.
📍 Affects 2 files
docs/adr/0006-opus-as-the-provenance-format.md#L25-L26(this comment)docs/adr/0007-plaintext-credentials-in-the-config.md#L31-L31
🤖 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/adr/0006-opus-as-the-provenance-format.md` around lines 25 - 26, Align
the WAV retention policy across both ADRs: in
docs/adr/0006-opus-as-the-provenance-format.md, define whether deletion is
mandatory or configurable, and in
docs/adr/0007-plaintext-credentials-in-the-config.md, remove the setting or
document the behavior when it is false. Ensure both files describe one
consistent retention rule.
| The upload fits under the limit, and twenty meetings take ~220 MB instead of 14 GB. | ||
| Provenance keeps working because the Opus is what remains. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Correct the storage estimate for two tracks.
docs/adr/0005-wasapi-capture-in-a-minimal-sidecar.md captures microphone and system output separately, and Lines 43-45 preserve both tracks. At approximately 11 MB per 24 kbps mono track, one meeting uses approximately 22 MB and twenty meetings use approximately 440 MB, not 220 MB, unless 24 kbps is the combined bitrate. State whether the bitrate applies per track.
Also applies to: 43-45
🤖 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/adr/0006-opus-as-the-provenance-format.md` around lines 30 - 31, Update
the storage estimate in the ADR to account for both separately preserved
microphone and system-output tracks: clarify that the approximately 11 MB
estimate and 24 kbps bitrate apply per track, then state the resulting
approximately 22 MB per meeting and 440 MB for twenty meetings.
| `config.json` in the application data directory, with the secrets in plaintext. Never | ||
| inside the workspace. | ||
|
|
||
| ```json | ||
| { | ||
| "workspace_path": "...", | ||
| "language": "en", | ||
| "stt": { "provider": "groq", "api_key": "" }, | ||
| "mcp": { "port": 7331, "token": "" }, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Define safe bootstrap and permissions for the plaintext config.
The example sets mcp.token to an empty string, while docs/adr/0003-mcp-as-the-only-bridge-to-the-llm.md requires a token on every request. Define an empty token as unconfigured and refuse MCP startup until a high-entropy token exists. Also require user-only ACLs on config.json and its parent directory.
🤖 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/adr/0007-plaintext-credentials-in-the-config.md` around lines 22 - 30,
Update the plaintext-config bootstrap guidance to treat an empty mcp.token as
unconfigured and prevent MCP startup until a high-entropy token is supplied;
also require user-only ACLs for config.json and its parent application-data
directory, consistent with the MCP authentication requirement.
| **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`. | ||
|
|
||
| ## Out of scope | ||
|
|
||
| - Extraction, summarisation or page writing by the application. That is the agent's. | ||
| - Chat inside the application. The conversation happens in the user's harness. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Clarify the application and agent boundary.
The plan excludes extraction and page writing by the application, and says that the application does not write content. Later tasks require application-side file parsing, audio transcription, Markdown validation, persistence, and editor/MCP writes. Define the application as responsible for mechanical processing, validation, and persistence. Reserve semantic summarisation and page generation for the agent.
Proposed wording
-The application does not write content; it validates what comes in
+The application does not call an LLM or generate semantic content; it validates and
+persists user- or agent-authored content-- Extraction, summarisation or page writing by the application. That is the agent's.
+- Semantic extraction, summarisation or page generation by the application. File parsing
+ and audio transcription remain application responsibilities.📝 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.
| **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`. | |
| ## Out of scope | |
| - Extraction, summarisation or page writing by the application. That is the agent's. | |
| - Chat inside the application. The conversation happens in the user's harness. | |
| **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 call an LLM or generate semantic content; it validates and | |
| persists user- or agent-authored content. See `adr:0003-mcp-as-the-only-bridge-to-the-llm`. | |
| ## Out of scope | |
| - Semantic extraction, summarisation or page generation by the application. File parsing | |
| and audio transcription remain application responsibilities. | |
| - Chat inside the application. The conversation happens in the user's harness. |
🤖 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/project-wiki.md` around lines 22 - 30, Clarify the application/agent
boundary in the scope section: state that the application performs mechanical
parsing, transcription, Markdown validation, persistence, and editor/MCP writes,
while the agent performs semantic extraction, summarisation, and page
generation. Update the claims around “calls no LLM” and “does not write content”
so they do not contradict these responsibilities, while preserving the MCP-only
bridge to the model.
|
|
||
| - Extraction, summarisation or page writing by the application. That is the agent's. | ||
| - Chat inside the application. The conversation happens in the user's harness. | ||
| - A hosted service, accounts, authentication of our own, multi-tenancy or telemetry — `adr:0001-no-backend-byok`. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Reconcile the credential and authentication model.
The plan excludes authentication, and says the transcription credential is the only application credential. Section 9.3 still requires a per-workspace token on every MCP request, and Section 9.13 exposes that token to the harness. Define local MCP token authorization as in scope, including its lifecycle, or remove the token requirement.
Also applies to: 53-59
🤖 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/project-wiki.md` at line 31, Reconcile the plan’s credential model
across the exclusion statement and Sections 9.3 and 9.13: either define local
MCP token authorization as an in-scope mechanism with its lifecycle and harness
handling, or remove the per-workspace token requirement and its exposure. Ensure
the plan consistently identifies which application credentials and
authentication controls exist.
Five commits. The branch is still named
docs/project-wiki-plan— renaming it would orphan this PR, so it stays wrong until the next branch.The project is open-wiki, and it is English
The seven original ADRs, the glossary, the stack and the plan are translated, and each ADR slug is renamed with it — the slug is the id everything cites (
adr:0001-no-backend-byok), so leaving it in Portuguese would translate the prose and not the reference. Thenproject-wikibecameopen-wikiacross 18 places, includingplans/open-wiki.mdand the@open-wiki/*package scope.The docs test suite is removed, as asked. What was left of the root config became the shared bases packages extend:
tsconfig.base.jsonandvitest.shared.ts.CI —
.github/workflows/ci.ymlOne
windows-latestjob per pnpm workspace package, discovered from the globs inpnpm-workspace.yaml.fail-fast: false, so one package failing does not hide the others.The 76% floor is enforced twice on purpose.
vitest.shared.tscarries the thresholds so it fails locally first;scripts/ci/check-coverage.mjsre-readscoverage-summary.jsonin CI, so a package that droppedcoverage.thresholdsfrom its own config still fails. A package reporting no coverage at all fails rather than passing silently.It gates all four metrics — lines, statements, functions, branches — at 76. If only
lineswas meant, that is a list incheck-coverage.mjsand four lines invitest.shared.ts.Release —
.github/workflows/release.yml, ADR 0009A
v*tag builds the NSIS installer and publishes it to GitHub Releases with itsSHA256SUMS.txt. It refuses three things before building, each a mistake that is public by the time anyone notices it: a tag disagreeing with the app version, a tag that already has a release, and a package step that produced no.exe.Unsigned in the MVP, so SmartScreen warns with "Don't run" as the default button — worst in exactly the corporate environment
adr:0005was written for.CSC_LINKandCSC_KEY_PASSWORDare read from secrets, so buying a certificate later is a settings change.Five decisions
ow.exe, behind a CLI0008 reaches exactly two places — the transcription hint and the generated
CLAUDE.md. The page schema stays English regardless: translating the frontmatter keys would mean a wiki written in Spanish is not readable by a tool that reads a wiki written in English.0010 is proposed and not accepted, because it contradicts the plan's out-of-scope line and touches the boundary
adr:0005said to defend. Its 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. Two questions are left open in it: 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-2produces ids whose meaning depends on creation order. The id freezes and the title stays editable: citations embed the id andraw/is immutable, so fixing a typo in a title must never break a citation.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 because neither provider gains: Groq returns a ten-minute chunk in about three seconds, 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.
timeline.vttis written besidetimeline.json. This completes the deletion orderingadr:0006demanded but had nothing durable to check against.Consequence worth flagging to a reviewer: 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.design/desktop-draft.htmlEvery screen group 8 asks for, in one self-contained file with no network access. The shell and its four panes work; so does starting a recording and changing the palette.
Amber is provenance rather than a brand colour, and 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 because its problem is disappearing from view, and the chunk strip shows which chunk failed rather than a percentage, because the chunk is the retry unit.
A skill was written and removed again
skills/open-wiki/SKILL.mdtaught an agent to calllist_pagesandread_page— tool names invented for a server nobody has built. ASKILL.mdloads into an agent's context, so this is not merely rework: it is fiction that the next reader honours as a decision somebody made. The convention it carried (one page per entity, every claim cited, supersede rather than overwrite) was already real and belongs to group 5 and task 9.14. The calling sequence was not. Task 9.17 writes it from the tool list group 9 actually shipped, and the plan records why it is last in its group.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 ownconfig.json. That removes the pasted token of task 9.13 — now task 9.16.How this was verified
There is no test suite to run: the docs tests are gone and no package exists yet.
scc validate(sccis not installed here): ADR numbering contiguous 0001–0012, everyadr:citation resolves, every ADR cited from outsidedocs/adr/, every wikilink resolves, no orphan wiki page, 88 tasks all carrying exactly one(Unit)or(TDD)with no repeated number, and noAvoid:synonym used as a whole word in prose.code-reviewandsecurity-reviewsubagents were not run — this session is configured not to dispatch agents unless asked.What is not done
Task 1.3 stays unticked. Until task 1.1 creates the packages the matrix is empty, so the test, lint and Rust jobs skip and the
cigate passes on an empty workspace. There is no Rust build, no linter and no lockfile yet. The release workflow fails loudly untilapps/desktopexists, which is correct for a tag push..gitignoreis left alone: it has an uncommitted local change adding.claude/andCLAUDE.md, which is the opposite of what task 1.4 asks for.🤖 Generated with Claude Code
https://claude.ai/code/session_01GRMARM7Kd1HoRnGLVqSkAe