Skip to content

Test#420

Open
plind-junior wants to merge 131 commits into
mainfrom
test
Open

Test#420
plind-junior wants to merge 131 commits into
mainfrom
test

Conversation

@plind-junior

@plind-junior plind-junior commented Jul 7, 2026

Copy link
Copy Markdown
Member

What changed

Why

What might break

VEP

Tests

  • make check passes locally (lint + mypy + pytest)
  • New / changed behaviour has a test
  • CHANGELOG.md updated under ## [Unreleased]

Summary by CodeRabbit

  • New Features

    • Added weekly and manual security auditing for Python dependencies.
    • Expanded search and context features with better filtering, richer results, and broader retrieval options.
    • Added new review, triage, audit, embedding, provenance, and theme tools for deeper project analysis.
  • Bug Fixes

    • Improved import validation and integrity checks for bundled content.
    • Made health checks and indexing more resilient to invalid or unreadable data.
  • Documentation

    • Updated contributor guidance and refreshed project docs for the new workflows and knowledge base structure.

plind-junior and others added 30 commits May 19, 2026 15:44
fix: track backend label separately in JSONL search handler (#14)
fix: add existence guards to all put_* methods (#12)
…act-guard

fix(proposals): refuse to overwrite existing artifact on approve
kb.register_source_from_path read the contents of any file the process
could access, letting an agent register /etc/passwd, ~/.ssh/id_rsa,
~/.aws/credentials etc. as a "source" and then retrieve the bytes via
kb.cite or kb.list_sources.

Resolve the path (following symlinks) and require it to be inside the
KB root before reading. Adds KBStore.resolve_under_root() so both the
JSONL handler and the MCP tool share one containment check.

Fixes #10
The CLI handlers for approve and reject caught only
(ArtifactNotFoundError, ValueError) but proposals.approve() /
proposals.reject() raise ProposalError -- a RuntimeError subclass. The
four propose-* shortcuts caught nothing at all. Result: a double-
approve, an empty rejection reason, an empty claim text, or an unknown
source id surfaced a raw Python traceback instead of the one-line
`Error: ...` the rest of the CLI emits.

Add a small `_cli_errors` context manager that translates
ArtifactNotFoundError / ValueError / ProposalError / LifecycleError
into click.ClickException, and apply it to every command that calls
into proposals, lifecycle, sessions, or storage. The MCP and JSONL
servers already do the equivalent in their own envelopes; this brings
the human-facing surface in line.

Adds tests/test_cli.py with regressions for approve, reject, propose-
claim, propose-entity, and show.
ruff B904 requires `raise ... from err` inside an except block so the
original exception is preserved on the chained __cause__. Without it,
the CI lint step rejects the file. The seven sites are pre-existing
(put_claim, put_page, put_entity, put_relation, put_evidence,
put_session, put_proposal); this PR inherited the failure from main
rather than introducing it, but the path traversal fix cannot land
until lint is green, so the cleanup happens here.

No behavior change: the chained exception carries .__cause__ but the
public ValueError message and type are unchanged.
verify_source() caught FileNotFoundError, but store.read_source_content()
raises ArtifactNotFoundError (a KeyError subclass) when the content blob
is missing -- so the "stored content missing" graceful path never ran
and a single broken source crashed the entire verify_all() sweep,
breaking `vouch source verify` and `vouch doctor`.

The same call can also raise OSError (permission denied, TOCTOU race
between exists() and read_bytes(), underlying I/O error) which was
likewise unhandled. Catch both: the existing "missing" path keeps its
note, and OSError surfaces as "stored content unreadable: <reason>".

Adds three regression tests:
- missing-content blob -> graceful per-source failure
- unreadable stored content (monkeypatched PermissionError) -> graceful
  per-source failure with the underlying reason in the note
- mixed sweep with one good + one broken source -> verify_all returns
  both results instead of aborting at the first failure

Fixes #30
The CI workflow runs `ruff check`, `mypy`, then `pytest`, and the
fix/cli-clean-domain-errors branch was failing all three on
pre-existing main-branch issues unrelated to the CLI change itself.

* ruff: add `from e` to the seven `raise ValueError(...) from
  FileExistsError` re-raises added by the recent exclusive-create
  guards in storage.put_* (B904), and let ruff re-sort the cli.py
  import block (I001).
* mypy: narrow the `kind` value flowing into `ContextItem(type=...)`
  with a `Literal` cast so the strict-typed field accepts what the
  search backends actually return.
* pytest: `session_end()` mutates a session that `session_start()` has
  already written, but `put_session()` now uses exclusive create and
  rejects the second write. Add `KBStore.update_session()` mirroring
  `update_claim` and have `session_end` call it; existing test_sessions
  coverage now passes again.

No behavior change for the CLI surface — these are infrastructure
fixes so the existing test_sessions / lint / type assertions pass on
this branch.
The B904 lint failures were already addressed in 2a439a5, but the CI
matrix still fails on two pre-existing main-branch issues:

* mypy: context.py:64 passed `kind: str` into ContextItem.type, which
  is Literal["claim","page","entity","relation","source"]. Narrow it
  with a typing.cast so strict mode accepts the search-backend output.
* pytest: session_end() mutates a session that session_start() already
  wrote, but put_session() switched to exclusive create and rejects
  the second write. Add KBStore.update_session() mirroring update_claim
  and have session_end call it; test_sessions coverage passes again.

No behavior change for the path-traversal fix itself.
CodeRabbit on PR #28 noted that resolve_under_root() only validated a
pathname snapshot. Callers then re-opened the same name with .is_file()
and .read_bytes(), so an attacker who can swap the resolved path for a
symlink between the containment check and the read can still
exfiltrate an out-of-root file via kb.register_source_from_path.

Collapse the validate-then-read into a single trusted helper
`KBStore.read_under_root(path)` that returns `(resolved, bytes)`:

1. Path.resolve() chases pre-existing symlinks and the resulting target
   is checked for containment (existing behaviour -- legitimate in-root
   symlinks still work).
2. The read goes through `os.open(resolved, O_RDONLY | O_NOFOLLOW)` so
   a fresh symlink placed at the resolved name *after* the check fails
   with ELOOP rather than following the swap.
3. `fstat` + `S_ISREG` rejects directories / device nodes / pipes
   atomically on the same fd, replacing the racy `is_file()` test the
   callers used to do.

Both register_source_from_path handlers (MCP + JSONL) switch to the
new helper and drop their now-redundant follow-up checks.

Adds tests:
- symlink swapped into the resolved name -> rejected via ELOOP
- directory at a valid path -> rejected via S_ISREG

Existing "outside the root" and "inside the root" tests still pass.
…versal

fix(server): block path traversal in register_source_from_path
ruff B904 requires `raise ... from err` inside an except block so the
original exception is preserved on the chained __cause__. Without it,
the CI lint step rejects the file. The seven sites are pre-existing
(put_claim, put_page, put_entity, put_relation, put_evidence,
put_session, put_proposal); this PR inherited the failure from main
rather than introducing it, but the path traversal fix cannot land
until lint is green, so the cleanup happens here.

No behavior change: the chained exception carries .__cause__ but the
public ValueError message and type are unchanged.
The B904 lint failures were already addressed in 2a439a5, but the CI
matrix still fails on two pre-existing main-branch issues:

* mypy: context.py:64 passed `kind: str` into ContextItem.type, which
  is Literal["claim","page","entity","relation","source"]. Narrow it
  with a typing.cast so strict mode accepts the search-backend output.
* pytest: session_end() mutates a session that session_start() already
  wrote, but put_session() switched to exclusive create and rejects
  the second write. Add KBStore.update_session() mirroring update_claim
  and have session_end call it; test_sessions coverage passes again.

No behavior change for the path-traversal fix itself.
Resolve conflicts in context.py, sessions.py, storage.py (keep main).

fix: block bad writes in import_apply per review feedback (#13)

import_apply now captures schema validation issues and skips the file
instead of passing a throwaway list.
fix(cli): translate domain errors into clean ClickException output
fix(verify): catch ArtifactNotFoundError on missing stored content
fix: catch ArtifactNotFoundError in verify (#30) and validate bundle content on import (#13)
Approved design for feat/semantic-search. Embedding (sentence-
transformers all-mpnet-base-v2) becomes the primary search backend
with FTS5 as fallback. Synchronous-at-write indexing across all six
artifact types (claim, page, source, entity, relation, evidence).

Maximally functional scope (~3000 LOC): pluggable model adapter
registry, sqlite-vec ANN with NumPy fallback, cross-encoder rerank,
HyDE query expansion, ingest-time duplicate detection, model-identity
migration, recall/MRR/nDCG eval harness, and full CLI/MCP/JSONL parity.

The writing-plans step will turn the rollout order in section 16
into concrete phased tasks.
32-task TDD plan for the semantic-search feature: foundation, storage,
write hooks across all six artifact types, semantic-primary search
integration in MCP/JSONL/CLI, RRF fusion + hybrid, cross-encoder rerank,
HyDE expansion, ingest-time duplicate detection, model-identity
migration, recall/MRR/nDCG scorer, end-to-end integration test, and
user docs.

Each task: failing test, minimal implementation, passing test, commit.
MockEmbedder test double keeps the unit suite fast; the real model is
exercised only under @pytest.mark.integration.

The evaluation module is named scorer.py rather than eval.py to avoid
shadowing the Python builtin and to keep static analysers quiet; the
user-facing CLI subcommand remains `vouch eval embedding` (the Click
group is registered under the name "eval", with the Python identifier
eval_group).
CI ran ruff which flagged SIM105 on the three try/except ImportError
guard blocks in src/vouch/embeddings/__init__.py. Replace each with
`contextlib.suppress(ImportError)` -- same semantics, satisfies ruff.

Also add mypy overrides for numpy / sqlite_vec / sentence_transformers /
fastembed so the type check passes in the base [dev] CI install where
those optional extras aren't present. The embedding code paths that
import these libraries are only reached when the extras are installed
at runtime; for the static type check the missing stubs are noise.
CI runs `pip install -e '.[dev]'` which deliberately excludes the
optional `[embeddings]` extras. tests/embeddings/test_*.py modules
import numpy at top level, so pytest collection fails with
ModuleNotFoundError before any test can be deselected.

Add tests/embeddings/conftest.py with `pytest.importorskip("numpy")`
so the entire embeddings test directory skips gracefully when numpy
is absent. Once `pip install vouch[embeddings]` (or numpy itself) is
present, the skip is a no-op and the tests run normally.
`_validate_content` in bundle.py uses the path's first directory
component to look up a Pydantic validator. For sources, both
`sources/<sha>/meta.yaml` (the Source model) and
`sources/<sha>/content` (raw opaque bytes) hit the same "sources"
key, so the validator was being run on the raw content bytes and
failing with "1 validation error for Source".

Add an early return for any non-meta.yaml path under `sources/` so
opaque content bytes are not Pydantic-validated. Only the Source
metadata file is checked, which matches the original intent of the
PR #13 validation.

Unblocks three pre-existing test_bundle.py failures inherited from
upstream main.
@github-actions github-actions Bot added embeddings embedding-backed retrieval sync sync, vault mirror, and diff flows schemas json schemas and generated schema assets packaging packaging, build metadata, and make targets tests tests and fixtures size: XL 1000 or more changed non-doc lines labels Jul 7, 2026
@github-actions github-actions Bot removed cli command line interface dual-solve dual-solve orchestration review-ui browser review ui website static website adapters agent host adapters and install manifests mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation embeddings embedding-backed retrieval sync sync, vault mirror, and diff flows schemas json schemas and generated schema assets packaging packaging, build metadata, and make targets tests tests and fixtures labels Jul 7, 2026
Add back KB_FORMAT_VERSION, SCHEMA_VERSION, SCHEMA_VERSION_FILENAME,
_starter_config to storage.py and _RETRACTED_CLAIM_STATUSES to context.py
to fix import errors in test suite.
@github-actions github-actions Bot added the retrieval context, search, synthesis, and evaluation label Jul 7, 2026
the test-branch merges (2ddd1f9 onward) took main's older, smaller
storage.py / context.py / index_db.py / cli.py / server.py / health.py /
jsonl_server.py / bundle.py / sessions.py while keeping the newer caller
modules (lifecycle, proposals, graph, notify, sync, provenance,
codex_rollout, triage). that desync left 31 mypy errors — callers
referencing methods the truncated backing no longer defined
(put_relation_idempotent, _validate_claim_refs, update_page,
update_proposal, index_prov_edge, get_meta) — so the test job failed at
mypy before pytest ever ran.

restore src/ and tests/ to bb06565, the last coherent snapshot, where the
fuller backing matches the callers. this also brings back the reindex cli
command the recall-eval job invokes (the truncated cli only had index),
fixing the second red job.

keep __version__ at 1.2.2 so the four version sites stay in lockstep.
contributors should attach screenshots from the vouch-ui webapp as proof
when their PR changes the CLI output or user-facing behavior.
contributors should attach screenshots from the vouch-ui webapp as proof
when their PR changes the CLI output or user-facing behavior.
@github-actions github-actions Bot added cli command line interface mcp mcp, jsonl, and http surfaces embeddings embedding-backed retrieval tests tests and fixtures labels Jul 7, 2026

@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: 7

🤖 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/security-audit.yml:
- Line 27: The security-audit workflow is using actions/checkout with default
persisted credentials, which leaves the token stored in local git config
unnecessarily. Update the checkout step in the workflow to disable credential
persistence by setting persist-credentials to false on the actions/checkout@v4
step so the PR-controlled code cannot reuse the token.

In @.vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl:
- Around line 68-71: Remove the auth/account probing from this capture by
redacting the `gh` account checks and any direct references to
`/home/a/.config/mcp-publisher/token.json`, since they expose machine-specific
identity and token location details. Keep the surrounding publish/debug context
in the JSONL entry, but replace those sensitive commands and paths with generic
redactions so the `mcp-publisher` capture can be safely shared. Use the existing
capture entries in this JSONL file as the place to scrub the sensitive lines
before commit.

In @.vouch/decided/20260707-092531-154cb163.yaml:
- Around line 42-45: The rejection record uses a placeholder decision_reason
value, which leaves the audit trail unclear. Update the rejected entry in the
YAML so the decided_at/decided_by metadata remains intact but decision_reason is
replaced with a short human-readable explanation, or remove decision_reason
entirely if no reason is available yet.

In
@.vouch/pages/session-what-are-the-non-development-related-files-and-folde.md:
- Around line 36-38: The session snapshot includes absolute local paths that
leak the developer’s home and temp directory layout. Redact these entries in the
session markdown and the matching `.vouch/decided/...yaml` artifact by replacing
them with repo-relative paths or neutral placeholders, using the same path list
entries shown in the snapshot.

In `@src/vouch/health.py`:
- Around line 568-626: The progress callback wiring in rebuild_index is missing:
_tick is defined but never used, so on_progress never receives phase updates.
Update rebuild_index to invoke _tick at the start of each rebuild stage—before
processing claims, pages, entities, and embeddings—so the documented phase
labels are actually emitted while keeping the rest of the rebuild logic
unchanged.
- Around line 629-652: _rebuild_embeddings is writing vectors through
index_db.index_embedding, but semantic search reads from embedding_index via
search_semantic, so the rebuild is targeting the wrong storage. Update
_rebuild_embeddings to use the same put_embedding path that writes to
embedding_index, keeping the existing batch loop and embedder.encode_batch flow
intact while replacing the final persistence call.

In `@src/vouch/index_db.py`:
- Around line 128-167: The rebuild path in index_db is currently backfilling
only the legacy embeddings table, while search_semantic() reads from
embedding_index, so a reset/rebuild leaves semantic search with no usable data.
Update _rebuild_embeddings() and the related index_embedding/search_embeddings
flow so the rebuild populates embedding_index as well, or explicitly remove
embeddings from the rebuild path if it is no longer used, keeping the live
search store and rebuild target aligned.
🪄 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: 16ae3082-59ae-4868-8bc1-b87023073c74

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae2806 and 661b59c.

⛔ Files ignored due to path filters (6)
  • desktop/docs/screenshots/browse.png is excluded by !**/*.png
  • desktop/docs/screenshots/dashboard.png is excluded by !**/*.png
  • desktop/docs/screenshots/dual-solve.png is excluded by !**/*.png
  • desktop/docs/screenshots/review.png is excluded by !**/*.png
  • desktop/package-lock.json is excluded by !**/package-lock.json
  • docs/banner.svg is excluded by !**/*.svg
📒 Files selected for processing (120)
  • .github/workflows/security-audit.yml
  • .pre-commit-config.yaml
  • .vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl
  • .vouch/captures/5720220c-b77c-4947-aeec-9d6ef91d70b8.jsonl
  • .vouch/captures/8a6df051-6c56-4d86-b0ed-66f6638ecfdc.jsonl
  • .vouch/captures/ec4b8842-f167-467e-a33c-dc87911c9e0e.jsonl
  • .vouch/claims/vouch-starter-reviewed-knowledge.yaml
  • .vouch/decided/20260707-092531-154cb163.yaml
  • .vouch/decided/20260707-093005-05345f47.yaml
  • .vouch/decided/20260707-093125-4d6972f9.yaml
  • .vouch/pages/edit-in-obsidian.md
  • .vouch/pages/reviewed-knowledge-store.md
  • .vouch/pages/session-what-are-the-non-development-related-files-and-folde.md
  • .vouch/schema_version
  • .vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/content
  • .vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/meta.yaml
  • CONTRIBUTING.md
  • README.md
  • desktop/.gitignore
  • desktop/CHANGELOG.md
  • desktop/LICENSE
  • desktop/README.md
  • desktop/docs/architecture.md
  • desktop/docs/reports/2026-06-26.md
  • desktop/electron-builder.yml
  • desktop/electron.vite.config.ts
  • desktop/package.json
  • desktop/resources/vouch/.gitkeep
  • desktop/scripts/dev-with-vouch.ts
  • desktop/scripts/gen-methods.ts
  • desktop/src/catalog/backend.json
  • desktop/src/catalog/methods.json
  • desktop/src/main/http-client.ts
  • desktop/src/main/index.ts
  • desktop/src/main/ipc.ts
  • desktop/src/main/jsonl-client.ts
  • desktop/src/main/kb-store.ts
  • desktop/src/main/supervisor.ts
  • desktop/src/main/tray.ts
  • desktop/src/main/types.ts
  • desktop/src/main/vouch-locator.ts
  • desktop/src/preload/index.ts
  • desktop/src/renderer/index.html
  • desktop/src/renderer/src/App.tsx
  • desktop/src/renderer/src/app.css
  • desktop/src/renderer/src/components/Diff.tsx
  • desktop/src/renderer/src/components/Drawer.tsx
  • desktop/src/renderer/src/components/EmptyState.tsx
  • desktop/src/renderer/src/components/MethodCard.tsx
  • desktop/src/renderer/src/components/MethodForm.tsx
  • desktop/src/renderer/src/components/Placeholder.tsx
  • desktop/src/renderer/src/components/Rail.tsx
  • desktop/src/renderer/src/components/StatusBar.tsx
  • desktop/src/renderer/src/components/Topbar.tsx
  • desktop/src/renderer/src/components/controls/index.tsx
  • desktop/src/renderer/src/components/results/Cards.tsx
  • desktop/src/renderer/src/components/results/JsonTree.tsx
  • desktop/src/renderer/src/components/results/Renderers.tsx
  • desktop/src/renderer/src/components/results/ResultView.tsx
  • desktop/src/renderer/src/components/results/atoms.tsx
  • desktop/src/renderer/src/global.d.ts
  • desktop/src/renderer/src/lib/VouchContext.tsx
  • desktop/src/renderer/src/lib/client.ts
  • desktop/src/renderer/src/lib/format.ts
  • desktop/src/renderer/src/lib/useOnOpen.tsx
  • desktop/src/renderer/src/lib/useVouchEvents.ts
  • desktop/src/renderer/src/main.tsx
  • desktop/src/renderer/src/views/Dashboard.tsx
  • desktop/src/renderer/src/views/DualSolve.tsx
  • desktop/src/renderer/src/views/GenericView.tsx
  • desktop/src/renderer/src/views/Review.tsx
  • desktop/src/renderer/src/views/blurbs.ts
  • desktop/src/renderer/src/views/registry.tsx
  • desktop/src/shared/ipc.ts
  • desktop/src/shared/methods.gen.ts
  • desktop/src/shared/methods.types.ts
  • desktop/test/catalog.test.ts
  • desktop/test/controls.test.tsx
  • desktop/test/gen-methods.test.ts
  • desktop/test/http-client.test.ts
  • desktop/test/jsonl-client.test.ts
  • desktop/test/method-card.test.tsx
  • desktop/test/method-form.test.tsx
  • desktop/test/method-gate.test.ts
  • desktop/test/smoke-jsonl.ts
  • desktop/test/vouch-locator.test.ts
  • desktop/tsconfig.json
  • desktop/tsconfig.node.json
  • desktop/tsconfig.scripts.json
  • desktop/tsconfig.web.json
  • desktop/vitest.config.ts
  • docs/superpowers/specs/2026-07-06-review-ui-multi-kb-design.md
  • migrations/README.md
  • spec/2026-05-21/README.md
  • spec/2026-05-21/SPEC.md
  • spec/2026-05-21/audit-vocabulary.md
  • spec/2026-05-21/methods.md
  • spec/2026-05-21/retrieval.md
  • spec/2026-05-21/review-gate.md
  • spec/2026-05-21/transports.md
  • spec/README.md
  • spec/audit-vocabulary.md
  • spec/methods.md
  • spec/retrieval.md
  • spec/review-gate.md
  • spec/transports.md
  • src/vouch/bundle.py
  • src/vouch/cli.py
  • src/vouch/context.py
  • src/vouch/embeddings/base.py
  • src/vouch/health.py
  • src/vouch/index_db.py
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • src/vouch/sessions.py
  • src/vouch/storage.py
  • tests/embeddings/conftest.py
  • tests/embeddings/test_integration.py
  • tests/embeddings/test_search.py
  • tests/test_cli.py
💤 Files with no reviewable changes (87)
  • desktop/.gitignore
  • spec/README.md
  • spec/2026-05-21/review-gate.md
  • desktop/src/renderer/src/views/registry.tsx
  • desktop/docs/reports/2026-06-26.md
  • spec/retrieval.md
  • spec/2026-05-21/audit-vocabulary.md
  • spec/2026-05-21/SPEC.md
  • spec/transports.md
  • desktop/test/catalog.test.ts
  • desktop/src/renderer/src/views/Review.tsx
  • desktop/CHANGELOG.md
  • desktop/test/smoke-jsonl.ts
  • desktop/test/gen-methods.test.ts
  • desktop/src/renderer/src/views/GenericView.tsx
  • desktop/tsconfig.json
  • spec/methods.md
  • desktop/src/renderer/src/components/Diff.tsx
  • desktop/src/renderer/src/views/Dashboard.tsx
  • desktop/test/jsonl-client.test.ts
  • desktop/src/renderer/src/views/DualSolve.tsx
  • desktop/src/main/ipc.ts
  • desktop/electron.vite.config.ts
  • desktop/src/main/supervisor.ts
  • desktop/src/main/jsonl-client.ts
  • desktop/LICENSE
  • desktop/scripts/dev-with-vouch.ts
  • desktop/src/renderer/index.html
  • desktop/src/shared/methods.types.ts
  • migrations/README.md
  • desktop/scripts/gen-methods.ts
  • desktop/package.json
  • desktop/test/controls.test.tsx
  • desktop/test/vouch-locator.test.ts
  • desktop/src/renderer/src/components/EmptyState.tsx
  • desktop/src/renderer/src/lib/format.ts
  • desktop/tsconfig.web.json
  • desktop/src/preload/index.ts
  • spec/review-gate.md
  • desktop/test/http-client.test.ts
  • spec/2026-05-21/transports.md
  • desktop/src/main/types.ts
  • desktop/src/renderer/src/lib/useVouchEvents.ts
  • desktop/vitest.config.ts
  • desktop/README.md
  • desktop/src/renderer/src/lib/useOnOpen.tsx
  • spec/2026-05-21/README.md
  • desktop/test/method-card.test.tsx
  • desktop/src/shared/ipc.ts
  • desktop/electron-builder.yml
  • desktop/test/method-gate.test.ts
  • desktop/src/renderer/src/components/Drawer.tsx
  • desktop/src/renderer/src/App.tsx
  • desktop/src/renderer/src/views/blurbs.ts
  • desktop/src/renderer/src/lib/VouchContext.tsx
  • desktop/tsconfig.node.json
  • desktop/src/renderer/src/components/results/ResultView.tsx
  • desktop/tsconfig.scripts.json
  • desktop/src/renderer/src/main.tsx
  • spec/2026-05-21/methods.md
  • desktop/src/main/kb-store.ts
  • desktop/src/renderer/src/app.css
  • desktop/src/renderer/src/components/StatusBar.tsx
  • desktop/src/renderer/src/components/Placeholder.tsx
  • desktop/src/catalog/backend.json
  • spec/audit-vocabulary.md
  • desktop/src/renderer/src/components/results/atoms.tsx
  • desktop/test/method-form.test.tsx
  • desktop/src/main/tray.ts
  • spec/2026-05-21/retrieval.md
  • desktop/src/main/index.ts
  • desktop/src/main/vouch-locator.ts
  • desktop/src/catalog/methods.json
  • desktop/src/renderer/src/components/MethodForm.tsx
  • desktop/docs/architecture.md
  • desktop/src/renderer/src/components/MethodCard.tsx
  • desktop/src/renderer/src/components/controls/index.tsx
  • desktop/src/renderer/src/global.d.ts
  • desktop/src/shared/methods.gen.ts
  • desktop/src/renderer/src/components/Topbar.tsx
  • desktop/src/main/http-client.ts
  • desktop/src/renderer/src/components/results/Renderers.tsx
  • desktop/src/renderer/src/components/Rail.tsx
  • desktop/src/renderer/src/components/results/Cards.tsx
  • desktop/src/renderer/src/components/results/JsonTree.tsx
  • .pre-commit-config.yaml
  • desktop/src/renderer/src/lib/client.ts

timeout-minutes: 15
continue-on-error: true
steps:
- uses: actions/checkout@v4

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow and nearby workflow settings.
sed -n '1,220p' .github/workflows/security-audit.yml

echo
echo "----"
# Look for token/permissions settings in this workflow and related workflows.
rg -n "persist-credentials|permissions:|checkout@v4|pip-audit|pull_request|workflow_dispatch|schedule" .github/workflows -S

Repository: vouchdev/vouch

Length of output: 4157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect packaging metadata to see what the editable install can execute.
sed -n '1,220p' pyproject.toml

echo
echo "----"
# Find any setup.py or build backend files that might run during install.
git ls-files | rg '(^|/)(setup\.py|setup\.cfg|pyproject\.toml|setup_hooks|build\.py)$' -n

Repository: vouchdev/vouch

Length of output: 4149


Disable persisted checkout credentials.

actions/checkout stores the token in local git config by default. This workflow installs PR-controlled code, so leaving it there is unnecessary exposure; set persist-credentials: false.

🔧 Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 27-27: 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/security-audit.yml at line 27, The security-audit workflow
is using actions/checkout with default persisted credentials, which leaves the
token stored in local git config unnecessarily. Update the checkout step in the
workflow to disable credential persistence by setting persist-credentials to
false on the actions/checkout@v4 step so the PR-controlled code cannot reuse the
token.

Source: Linters/SAST tools

Comment on lines +68 to +71
{"cmd": "echo \"=== active gh account ===\" && gh api user --jq .login; echo \"=== is plind-junior now a PUBLIC member of vouchdev? ===\" && gh api orgs/vouchdev/public_members --jq '.[].login' 2>&1 | head -20; ec", "summary": "Ran: echo \"=== active gh account ===\" && gh api user --jq .login;", "tool": "Bash", "ts": 1783420221.0744834}
{"cmd": "echo \"=== publish ===\" && mcp-publisher publish 2>&1 | tail -15", "summary": "Command failed: echo \"=== publish ===\" && mcp-publisher publish 2>&1 | tail ", "tool": "Bash", "ts": 1783420292.7722068}
{"cmd": "echo \"=== look for mcp-publisher token files ===\" && find / -maxdepth 6 -name \"*mcp-publisher*\" -o -name \".mcpregistry*\" 2>/dev/null | head -20; echo \"=== any recently-modified dotfiles in likely spot", "summary": "Ran: echo \"=== look for mcp-publisher token files ===\" && find / ", "tool": "Bash", "ts": 1783420318.553565}
{"cmd": "python3 -c \"\nimport json, base64, time\nd = json.load(open('/home/a/.config/mcp-publisher/token.json'))\nprint('keys in token.json:', list(d.keys()))\ntok = d.get('token') or d.get('access_token') or ''\n", "summary": "Ran: python3 -c \"", "tool": "Bash", "ts": 1783420336.7329283}

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== file exists ==="
git ls-files -- .vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl

echo "=== nearby lines 60-80 ==="
sed -n '60,80p' .vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl | cat -n

echo "=== repo references to .vouch/captures ==="
rg -n --hidden --no-messages '\.vouch/captures|mcp-publisher|token\.json|public_members|gh api user' . || true

Repository: vouchdev/vouch

Length of output: 11653


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '68,71p' .vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl | cat -n

Repository: vouchdev/vouch

Length of output: 1367


Scrub the auth/account probes from this capture. The gh auth switch commands and /home/a/.config/mcp-publisher/token.json path expose machine-specific account details; redact those lines before this log is committed or shared.

🤖 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 @.vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl around lines 68 -
71, Remove the auth/account probing from this capture by redacting the `gh`
account checks and any direct references to
`/home/a/.config/mcp-publisher/token.json`, since they expose machine-specific
identity and token location details. Keep the surrounding publish/debug context
in the JSONL entry, but replace those sensitive commands and paths with generic
redactions so the `mcp-publisher` capture can be safely shared. Use the existing
capture entries in this JSONL file as the place to scrub the sensitive lines
before commit.

Comment on lines +42 to +45
status: rejected
decided_at: '2026-07-07T09:30:38.823259Z'
decided_by: unknown-agent
decision_reason: 'yes'

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

Replace the placeholder rejection reason.

decision_reason: 'yes' doesn't explain why this record was rejected, so the audit trail won't be useful later. Please replace it with a short human-readable reason, or omit the field if there isn't one yet.

🤖 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 @.vouch/decided/20260707-092531-154cb163.yaml around lines 42 - 45, The
rejection record uses a placeholder decision_reason value, which leaves the
audit trail unclear. Update the rejected entry in the YAML so the
decided_at/decided_by metadata remains intact but decision_reason is replaced
with a short human-readable explanation, or remove decision_reason entirely if
no reason is available yet.

Comment on lines +36 to +38
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/commit-scope-src-adapters.md
- /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py

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 | 🟡 Minor | ⚡ Quick win

Redact the local-path leak from the session snapshot.

These entries expose a home directory and temp-path layout in a committed artifact, and the same leak is also present in .vouch/decided/20260707-093005-05345f47.yaml. Replace them with repo-relative paths or placeholders before committing.

Suggested redaction
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/commit-scope-src-adapters.md
- /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py
+ [redacted]/memory/MEMORY.md
+ [redacted]/memory/commit-scope-src-adapters.md
+ [redacted]/scratchpad/merge_hook.py
📝 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
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md
- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/commit-scope-src-adapters.md
- /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py
[redacted]/memory/MEMORY.md
[redacted]/memory/commit-scope-src-adapters.md
[redacted]/scratchpad/merge_hook.py
🤖 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 @.vouch/pages/session-what-are-the-non-development-related-files-and-folde.md
around lines 36 - 38, The session snapshot includes absolute local paths that
leak the developer’s home and temp directory layout. Redact these entries in the
session markdown and the matching `.vouch/decided/...yaml` artifact by replacing
them with repo-relative paths or neutral placeholders, using the same path list
entries shown in the snapshot.

Comment thread src/vouch/health.py
Comment on lines +568 to 626
def rebuild_index(store: KBStore, *, on_progress: Callable[[str], None] | None = None) -> dict:
"""Drop and rebuild state.db from the durable files. Idempotent.

`on_progress`, if given, is called with a short phase label ("claims",
"pages", "entities", "embeddings") as each stage starts — for CLI
progress display. It never affects the result.
"""

def _tick(phase: str) -> None:
if on_progress is not None:
on_progress(phase)

# Detect a stale embedding-model identity before reset() wipes the meta.
try:
from . import audit
from .embeddings.migration import detect_mismatch

m = detect_mismatch(store.kb_dir)
if m is not None:
audit.log_event(
store.kb_dir,
event="embedding.model_mismatch",
actor="vouch-health",
object_ids=[],
data=m,
)
except ImportError:
pass
index_db.reset(store.kb_dir)
with index_db.open_db(store.kb_dir) as conn:
for c in store.list_claims():
index_db.index_claim(
conn, id=c.id, text=c.text,
type=c.type.value, status=c.status.value, tags=c.tags,
conn,
id=c.id,
text=c.text,
type=c.type.value,
status=c.status.value,
tags=c.tags,
)
for p in store.list_pages():
index_db.index_page(
conn, id=p.id, title=p.title, body=p.body,
type=p.type.value, tags=p.tags,
conn,
id=p.id,
title=p.title,
body=p.body,
type=p.type,
tags=p.tags,
)
for e in store.list_entities():
index_db.index_entity(
conn, id=e.id, name=e.name, description=e.description,
type=e.type.value, aliases=e.aliases,
conn,
id=e.id,
name=e.name,
description=e.description,
type=e.type.value,
aliases=e.aliases,
)
_rebuild_embeddings(store)
return index_db.stats(store.kb_dir)

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

_tick is defined but never invoked — on_progress never fires.

The helper is declared and documented to emit "claims"/"pages"/"entities"/"embeddings" phase labels, but no stage in the rebuild body actually calls it, so the CLI progress callback is dead code.

🐛 Proposed fix to wire up the phase ticks
     index_db.reset(store.kb_dir)
     with index_db.open_db(store.kb_dir) as conn:
+        _tick("claims")
         for c in store.list_claims():
             index_db.index_claim(
                 conn,
                 id=c.id,
                 text=c.text,
                 type=c.type.value,
                 status=c.status.value,
                 tags=c.tags,
             )
+        _tick("pages")
         for p in store.list_pages():
             index_db.index_page(
                 conn,
                 id=p.id,
                 title=p.title,
                 body=p.body,
                 type=p.type,
                 tags=p.tags,
             )
+        _tick("entities")
         for e in store.list_entities():
             index_db.index_entity(
                 conn,
                 id=e.id,
                 name=e.name,
                 description=e.description,
                 type=e.type.value,
                 aliases=e.aliases,
             )
+    _tick("embeddings")
     _rebuild_embeddings(store)
     return index_db.stats(store.kb_dir)
📝 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
def rebuild_index(store: KBStore, *, on_progress: Callable[[str], None] | None = None) -> dict:
"""Drop and rebuild state.db from the durable files. Idempotent.
`on_progress`, if given, is called with a short phase label ("claims",
"pages", "entities", "embeddings") as each stage startsfor CLI
progress display. It never affects the result.
"""
def _tick(phase: str) -> None:
if on_progress is not None:
on_progress(phase)
# Detect a stale embedding-model identity before reset() wipes the meta.
try:
from . import audit
from .embeddings.migration import detect_mismatch
m = detect_mismatch(store.kb_dir)
if m is not None:
audit.log_event(
store.kb_dir,
event="embedding.model_mismatch",
actor="vouch-health",
object_ids=[],
data=m,
)
except ImportError:
pass
index_db.reset(store.kb_dir)
with index_db.open_db(store.kb_dir) as conn:
for c in store.list_claims():
index_db.index_claim(
conn, id=c.id, text=c.text,
type=c.type.value, status=c.status.value, tags=c.tags,
conn,
id=c.id,
text=c.text,
type=c.type.value,
status=c.status.value,
tags=c.tags,
)
for p in store.list_pages():
index_db.index_page(
conn, id=p.id, title=p.title, body=p.body,
type=p.type.value, tags=p.tags,
conn,
id=p.id,
title=p.title,
body=p.body,
type=p.type,
tags=p.tags,
)
for e in store.list_entities():
index_db.index_entity(
conn, id=e.id, name=e.name, description=e.description,
type=e.type.value, aliases=e.aliases,
conn,
id=e.id,
name=e.name,
description=e.description,
type=e.type.value,
aliases=e.aliases,
)
_rebuild_embeddings(store)
return index_db.stats(store.kb_dir)
def rebuild_index(store: KBStore, *, on_progress: Callable[[str], None] | None = None) -> dict:
"""Drop and rebuild state.db from the durable files. Idempotent.
`on_progress`, if given, is called with a short phase label ("claims",
"pages", "entities", "embeddings") as each stage startsfor CLI
progress display. It never affects the result.
"""
def _tick(phase: str) -> None:
if on_progress is not None:
on_progress(phase)
# Detect a stale embedding-model identity before reset() wipes the meta.
try:
from . import audit
from .embeddings.migration import detect_mismatch
m = detect_mismatch(store.kb_dir)
if m is not None:
audit.log_event(
store.kb_dir,
event="embedding.model_mismatch",
actor="vouch-health",
object_ids=[],
data=m,
)
except ImportError:
pass
index_db.reset(store.kb_dir)
with index_db.open_db(store.kb_dir) as conn:
_tick("claims")
for c in store.list_claims():
index_db.index_claim(
conn,
id=c.id,
text=c.text,
type=c.type.value,
status=c.status.value,
tags=c.tags,
)
_tick("pages")
for p in store.list_pages():
index_db.index_page(
conn,
id=p.id,
title=p.title,
body=p.body,
type=p.type,
tags=p.tags,
)
_tick("entities")
for e in store.list_entities():
index_db.index_entity(
conn,
id=e.id,
name=e.name,
description=e.description,
type=e.type.value,
aliases=e.aliases,
)
_tick("embeddings")
_rebuild_embeddings(store)
return index_db.stats(store.kb_dir)
🤖 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 `@src/vouch/health.py` around lines 568 - 626, The progress callback wiring in
rebuild_index is missing: _tick is defined but never used, so on_progress never
receives phase updates. Update rebuild_index to invoke _tick at the start of
each rebuild stage—before processing claims, pages, entities, and embeddings—so
the documented phase labels are actually emitted while keeping the rest of the
rebuild logic unchanged.

Comment thread src/vouch/health.py
Comment on lines +629 to +652
def _rebuild_embeddings(store: KBStore) -> None:
try:
from .embeddings import get_embedder

embedder = get_embedder()
except Exception:
return
with index_db.open_db(store.kb_dir) as conn:
texts: list[tuple[str, str, str]] = []
for c in store.list_claims():
texts.append(("claim", c.id, c.text))
for p in store.list_pages():
texts.append(("page", p.id, f"{p.title} {p.body}"))
for e in store.list_entities():
texts.append(("entity", e.id, f"{e.name} {e.description or ''}"))
if not texts:
return
batch_size = 64
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size]
vecs = embedder.encode_batch([t[2] for t in batch])
for (kind, eid, _), row in zip(batch, vecs, strict=True):
index_db.index_embedding(conn, kind=kind, id=eid, vec=row.tolist())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Which table does semantic search / put_embedding read/write, vs index_embedding?
ast-grep outline src/vouch/index_db.py --items all
rg -nP 'FROM\s+(embeddings|embedding_index)\b|INTO\s+(embeddings|embedding_index)\b' src/vouch/index_db.py
rg -nP 'def\s+(search_semantic|put_embedding|index_embedding|reset)\b' -A15 src/vouch/index_db.py

Repository: vouchdev/vouch

Length of output: 5070


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect schema and the full read/write paths for both embedding tables.
sed -n '24,90p;120,180p;315,470p' src/vouch/index_db.py

# Find all call sites/usages to understand intended behavior.
rg -n '\b(index_embedding|put_embedding|get_embedding|search_embeddings|search_embedding|search_semantic)\b' src/vouch -A3 -B3

# Inspect the health rebuild path in context.
sed -n '600,690p' src/vouch/health.py

Repository: vouchdev/vouch

Length of output: 22119


Write rebuilt vectors to embedding_index
_rebuild_embeddings calls index_db.index_embedding, which writes to embeddings, while search_semantic reads from embedding_index. A full reindex will repopulate the wrong table and leave semantic search empty until later artifact writes backfill it. Use the put_embedding path here instead.

🤖 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 `@src/vouch/health.py` around lines 629 - 652, _rebuild_embeddings is writing
vectors through index_db.index_embedding, but semantic search reads from
embedding_index via search_semantic, so the rebuild is targeting the wrong
storage. Update _rebuild_embeddings to use the same put_embedding path that
writes to embedding_index, keeping the existing batch loop and
embedder.encode_batch flow intact while replacing the final persistence call.

Source: Linters/SAST tools

Comment thread src/vouch/index_db.py
Comment on lines +128 to +167
"DELETE FROM prov_edges;"
"DELETE FROM index_meta WHERE key LIKE 'embedding_%';"
"DELETE FROM index_meta WHERE key LIKE 'prov_%';"
)


def index_embedding(conn: sqlite3.Connection, *, kind: str, id: str,
vec: list[float]) -> None:
conn.execute(
"INSERT OR REPLACE INTO embeddings (kind, id, vec) VALUES (?, ?, ?)",
(kind, id, json.dumps(vec)),
)


def search_embeddings(kb_dir: Path, query_vec: list[float], *,
limit: int = 10
) -> list[tuple[str, str, str, float]]:
"""Return (kind, id, snippet, cosine_score) via brute-force NumPy scan."""
import numpy as np # type: ignore[import-not-found]
out: list[tuple[str, str, str, float]] = []
if not query_vec:
return out
q = np.array(query_vec, dtype=np.float32)
q_norm = np.linalg.norm(q)
if q_norm == 0.0:
return out
q = q / q_norm
with open_db(kb_dir) as conn:
rows = conn.execute(
"SELECT kind, id, vec FROM embeddings"
).fetchall()
for kind, eid, vec_json in rows:
v = np.array(json.loads(vec_json), dtype=np.float32)
if v.ndim != 1 or v.shape[0] != q.shape[0]:
continue
score = float(np.dot(q, v))
snippet = _snippet_for(kb_dir, kind, eid)
out.append((kind, eid, snippet, score))
out.sort(key=lambda x: x[3], reverse=True)
return out[:limit]

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
echo "=== search_embedding (singular) impl ==="
rg -nP -A25 'def search_embedding\b' src/vouch/index_db.py
echo "=== callers of search_embeddings (plural) ==="
rg -n 'search_embeddings' src/vouch
echo "=== reset() body ==="
rg -nP -A20 'def reset\b' src/vouch/index_db.py
echo "=== table read/write sites ==="
rg -n "INTO embedding_index|INTO embeddings |FROM embedding_index|FROM embeddings\b" src/vouch

Repository: vouchdev/vouch

Length of output: 3528


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== _rebuild_embeddings and related callers ==="
rg -nP -A40 'def _rebuild_embeddings\b|_rebuild_embeddings\(' src/vouch/health.py src/vouch/index_db.py

echo "=== search_semantic implementation ==="
rg -nP -A40 'def search_semantic\b' src/vouch/index_db.py

echo "=== embedding table schema references ==="
rg -n "CREATE TABLE .*embeddings|CREATE TABLE .*embedding_index|embeddings\b" src/vouch

echo "=== any callers of search_embeddings or index_embedding outside index_db.py ==="
rg -n "search_embeddings\(|index_embedding\(" src/vouch

Repository: vouchdev/vouch

Length of output: 11942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== index_db.py table definitions and search helpers ==="
sed -n '1,220p' src/vouch/index_db.py

echo "=== search_embedding implementation tail ==="
sed -n '400,470p' src/vouch/index_db.py

echo "=== health.py orphan embedding logic ==="
sed -n '530,580p' src/vouch/health.py

Repository: vouchdev/vouch

Length of output: 11994


Rebuild should target embedding_index
_rebuild_embeddings() is backfilling the legacy embeddings table, but search_semantic() still reads embedding_index. After reset(), a rebuild leaves the live semantic-search index empty; either populate embedding_index here or clear embeddings out of the rebuild path as well.

🧰 Tools
🪛 ast-grep (0.44.1)

[info] 137-137: use jsonify instead of json.dumps for JSON output
Context: json.dumps(vec)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 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 `@src/vouch/index_db.py` around lines 128 - 167, The rebuild path in index_db
is currently backfilling only the legacy embeddings table, while
search_semantic() reads from embedding_index, so a reset/rebuild leaves semantic
search with no usable data. Update _rebuild_embeddings() and the related
index_embedding/search_embeddings flow so the rebuild populates embedding_index
as well, or explicitly remove embeddings from the rebuild path if it is no
longer used, keeping the live search store and rebuild target aligned.

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

Labels

ci github actions and automation cli command line interface docs documentation, specs, examples, and repo guidance embeddings embedding-backed retrieval mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation size: XL 1000 or more changed non-doc lines storage kb storage, migrations, schemas, and proposals tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants