Skip to content

Test#420

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

Test#420
plind-junior wants to merge 178 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 a weekly security audit workflow (plus manual and PR-triggered runs).
    • Expanded knowledge-base tooling with viewer-scoped search/context, synthesis/diffing, triage/expire/reject, sessions/summaries, and new embeddings/provenance/theme/audit capabilities.
    • Added KB activity support and a Session Transcript viewer to the web console.
  • Bug Fixes
    • Strengthened bundle import validation, graph/source integrity checks, and health/lint resilience to invalid data.
  • Documentation
    • Updated README and contributor guidance; added new vouch documentation/spec pages.
  • Refactor
    • Removed/downsized the desktop client implementation and related tests to refocus on the web experience.

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 the schemas json schemas and generated schema assets label Jul 10, 2026
* feat(transcript): locate raw Claude session files by id

* feat(transcript): parse Claude JSONL into normalized blocks

* feat(transcript): orchestrate load with observation fallback

* feat(transcript): expose kb.session_transcript across all surfaces

* feat(webapp): transcript client types and fetch

* feat(webapp): thinking, diff, and code block renderers

* feat(webapp): tool block with per-tool rendering and diffs

* feat(webapp): sessions tab with full transcript viewer

* feat(transcript): parse Codex rollouts into normalized blocks

* test(webapp): cover degraded render and subagent drill-down

* test(webapp): e2e smoke for the sessions transcript tab

* docs(transcript): session transcript viewer design and plan

* refactor(webapp): merge session transcript into Review, drop Sessions tab

* docs(transcript): note the merge of the viewer into Review

* feat(activity): expose kb.activity audit buckets across all surfaces

one pass over the audit log returning per-day counts (with
proposal/decision breakdowns), an hour-of-week matrix, and actor/event
histograms — the data a dashboard needs that kb.audit's tail-limited
raw events and kb.stats' review aggregates don't cover.

windowed in viewer-local calendar days so the oldest heatmap cell is
never partially counted (days=0 for all-time, negatives rejected).
local bucketing prefers an iana tz name (dst-correct across the year)
and falls back to a clamped fixed utc offset. scope-filtered through
the same viewer context as kb.audit so multi-project kbs don't leak
actor names or activity timing across project boundaries.

registered at all four sites (mcp tool, jsonl handler, capabilities
methods, vouch activity cli) plus trust read methods.

* feat(webapp): dashboard view of kb activity, landing on /

new /dashboard view driven by kb.activity: a 12-month activity
calendar (ember heat ramp themed for dark and light via --heat-* css
vars), last-30-days bars, an hour-of-week heatmap, and top-actor /
event-mix bar lists, with stat tiles fed by kb.status and kb.stats.

the calendar fetches 371 days so its oldest drawn column always sits
inside the server window, sends the browser's iana timezone for
dst-correct bucketing, and degrades cleanly: endpoints that don't
advertise kb.activity get an upgrade hint, empty audit logs get an
empty state, and a malformed stats payload no longer crashes the view.

/ now redirects to the dashboard instead of chat and the tab leads the
sidebar; the e2e smoke flow clicks through to chat accordingly.
…#456)

* feat(transcript): locate raw Claude session files by id

* feat(transcript): parse Claude JSONL into normalized blocks

* feat(transcript): orchestrate load with observation fallback

* feat(transcript): expose kb.session_transcript across all surfaces

* feat(webapp): transcript client types and fetch

* feat(webapp): thinking, diff, and code block renderers

* feat(webapp): tool block with per-tool rendering and diffs

* feat(webapp): sessions tab with full transcript viewer

* feat(transcript): parse Codex rollouts into normalized blocks

* test(webapp): cover degraded render and subagent drill-down

* test(webapp): e2e smoke for the sessions transcript tab

* docs(transcript): session transcript viewer design and plan

* refactor(webapp): merge session transcript into Review, drop Sessions tab

* docs(transcript): note the merge of the viewer into Review

* feat(console): serve the react web console from the installed package

add `vouch console`: a starlette app that serves the vendored webapp SPA
plus a same-origin /proxy bridge to `vouch serve --transport http`
backends — a python port of the vite dev-proxy (loopback-guarded, reads
X-Vouch-Target, passes backend statuses through unmasked). the built SPA
is bundled into the wheel as vouch/web/console via a conditional hatch
build hook, so `pip install 'vouch-kb[web]'` then `vouch console` needs no
node and no repo clone.

wire the release workflow to build the webapp before packaging and build
the wheel from the working tree so the console rides inside it. bump to
1.3.0 across the four version sites, document the pip path in the readme,
and add a changelog entry.
@github-actions github-actions Bot added openclaw openclaw integration packaging packaging, build metadata, and make targets labels Jul 10, 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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/vouch/capabilities.py (1)

30-97: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add the missing kb.session_transcript CLI mirror
kb.activity already has the four-place registration and JSONL coverage. kb.session_transcript is present in src/vouch/server.py, src/vouch/jsonl_server.py, and src/vouch/capabilities.py, but not src/vouch/cli.py; add the CLI command to keep the tool surface consistent. tests/test_session_transcript.py already covers the JSONL envelope.

🤖 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/capabilities.py` around lines 30 - 97, The CLI is missing the
kb.session_transcript command despite server and capability support. Add a
corresponding CLI registration and handler in src/vouch/cli.py, mirroring the
existing kb.activity command’s four-place registration, argument handling, and
output behavior; use the existing session_transcript implementation and preserve
the JSONL envelope covered by tests.

Source: Coding guidelines

🤖 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 `@docs/superpowers/specs/2026-07-10-session-transcript-viewer-design.md`:
- Around line 304-308: Update the e2e smoke-test description to reference the
Review tab instead of the removed standalone Sessions tab: instruct opening
Review, selecting a session, and verifying the rendered transcript, while
preserving the existing mocked proxy endpoints and frontend-only test setup.

In `@tests/test_stats.py`:
- Around line 269-275: Extend test_jsonl_kb_activity to assert the successful
response id and exact success envelope fields, then add a failure-case request
for kb.activity with invalid parameters and assert the response has the expected
id, ok=False, and an error field while excluding result.

In `@webapp/src/components/transcript/ThinkingBlock.tsx`:
- Around line 8-14: Add aria-expanded={open} to the collapsible toggle button in
the ThinkingBlock component, alongside its existing onClick handler and
className, so assistive technologies receive the current expanded state.

In `@webapp/src/views/TranscriptView.tsx`:
- Around line 47-52: Reset TranscriptView state when the selected session
changes by adding a key derived from sessionId to the TranscriptView usage in
ReviewView, forcing remount and reinitializing stack. Locate the TranscriptView
render in ReviewView and ensure the key changes whenever sessionId changes.

---

Outside diff comments:
In `@src/vouch/capabilities.py`:
- Around line 30-97: The CLI is missing the kb.session_transcript command
despite server and capability support. Add a corresponding CLI registration and
handler in src/vouch/cli.py, mirroring the existing kb.activity command’s
four-place registration, argument handling, and output behavior; use the
existing session_transcript implementation and preserve the JSONL envelope
covered by tests.
🪄 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: 93ab1e1d-8591-4b55-a6b5-ba67f4144394

📥 Commits

Reviewing files that changed from the base of the PR and between bb8335f and 02bcd3a.

📒 Files selected for processing (35)
  • CHANGELOG.md
  • docs/superpowers/plans/2026-07-10-session-transcript-viewer.md
  • docs/superpowers/specs/2026-07-10-session-transcript-viewer-design.md
  • src/vouch/capabilities.py
  • src/vouch/cli.py
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • src/vouch/stats.py
  • src/vouch/transcript.py
  • src/vouch/trust.py
  • tests/test_session_transcript.py
  • tests/test_stats.py
  • tests/test_trust.py
  • webapp/e2e/review-transcript.spec.ts
  • webapp/e2e/smoke.spec.ts
  • webapp/src/App.test.tsx
  • webapp/src/App.tsx
  • webapp/src/components/Shell.tsx
  • webapp/src/components/transcript/CodeBlock.tsx
  • webapp/src/components/transcript/DiffView.tsx
  • webapp/src/components/transcript/MessageBlock.tsx
  • webapp/src/components/transcript/ThinkingBlock.tsx
  • webapp/src/components/transcript/ToolBlock.test.tsx
  • webapp/src/components/transcript/ToolBlock.tsx
  • webapp/src/components/transcript/blocks.test.tsx
  • webapp/src/lib/transcript.test.ts
  • webapp/src/lib/transcript.ts
  • webapp/src/lib/types.ts
  • webapp/src/styles.css
  • webapp/src/views/DashboardView.test.tsx
  • webapp/src/views/DashboardView.tsx
  • webapp/src/views/ReviewView.test.tsx
  • webapp/src/views/ReviewView.tsx
  • webapp/src/views/TranscriptView.test.tsx
  • webapp/src/views/TranscriptView.tsx
✅ Files skipped from review due to trivial changes (4)
  • webapp/src/lib/transcript.test.ts
  • webapp/src/components/transcript/blocks.test.tsx
  • CHANGELOG.md
  • docs/superpowers/plans/2026-07-10-session-transcript-viewer.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/vouch/jsonl_server.py

Comment on lines +304 to +308
- `webapp/e2e/` smoke: open Sessions, pick a row, see the rendered transcript.
It stubs `/proxy/*` via Playwright `page.route` (health, capabilities,
`kb.list_pending`, `kb.list_sessions`, `kb.session_transcript`) so it drives
the real frontend independent of the backend build — the local `vouch` on
PATH is an editable install of a different checkout without the new RPC.

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

Update the e2e test plan to use Review, not Sessions.

The standalone Sessions tab was removed; this test should say to open Review, select a session, and verify the transcript there, matching Lines [221-230] and [332-338].

Suggested wording
-- webapp/e2e/ smoke: open Sessions, pick a row, see the rendered transcript.
+- webapp/e2e/ smoke: open Review, pick a session row, and see the rendered transcript.
📝 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
- `webapp/e2e/` smoke: open Sessions, pick a row, see the rendered transcript.
It stubs `/proxy/*` via Playwright `page.route` (health, capabilities,
`kb.list_pending`, `kb.list_sessions`, `kb.session_transcript`) so it drives
the real frontend independent of the backend build — the local `vouch` on
PATH is an editable install of a different checkout without the new RPC.
- `webapp/e2e/` smoke: open Sessions, pick a row, see the rendered transcript.
It stubs `/proxy/*` via Playwright `page.route` (health, capabilities,
`kb.list_pending`, `kb.list_sessions`, `kb.session_transcript`) so it drives
the real frontend independent of the backend build — the local `vouch` on
PATH is an editable install of a different checkout without the new RPC.
🤖 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/superpowers/specs/2026-07-10-session-transcript-viewer-design.md` around
lines 304 - 308, Update the e2e smoke-test description to reference the Review
tab instead of the removed standalone Sessions tab: instruct opening Review,
selecting a session, and verifying the rendered transcript, while preserving the
existing mocked proxy endpoints and frontend-only test setup.

Comment thread tests/test_stats.py
Comment on lines +269 to +275
def test_jsonl_kb_activity(store: KBStore) -> None:
src = store.put_source(b"x")
propose_claim(store, text="a", evidence=[src.id], proposed_by="agent-a")
resp = handle_request({"id": "1", "method": "kb.activity", "params": {"days": 0}})
assert resp["ok"] is True
assert resp["result"]["total_events"] >= 1
assert len(resp["result"]["by_hour"]) == 7

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

Add a failure-case test for the kb.activity JSONL envelope.

test_jsonl_kb_activity covers success but omits the failure envelope ({id, ok: false, error}) and doesn't assert resp["id"]. The coding guidelines require both success and failure envelope shape assertions for each new kb.* method.

As per coding guidelines: "For each new kb.* method, add a test that asserts the JSONL envelope shape: {id, ok, result} on success and {id, ok: false, error} on failure."

🧪 Suggested failure test
 def test_jsonl_kb_activity(store: KBStore) -> None:
     src = store.put_source(b"x")
     propose_claim(store, text="a", evidence=[src.id], proposed_by="agent-a")
     resp = handle_request({"id": "1", "method": "kb.activity", "params": {"days": 0}})
     assert resp["ok"] is True
+    assert resp["id"] == "1"
     assert resp["result"]["total_events"] >= 1
     assert len(resp["result"]["by_hour"]) == 7
+
+
+def test_jsonl_kb_activity_error_envelope(store: KBStore) -> None:
+    resp = handle_request({"id": "2", "method": "kb.activity", "params": {"days": -1}})
+    assert resp["id"] == "2"
+    assert resp["ok"] is False
+    assert "error" in resp
📝 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 test_jsonl_kb_activity(store: KBStore) -> None:
src = store.put_source(b"x")
propose_claim(store, text="a", evidence=[src.id], proposed_by="agent-a")
resp = handle_request({"id": "1", "method": "kb.activity", "params": {"days": 0}})
assert resp["ok"] is True
assert resp["result"]["total_events"] >= 1
assert len(resp["result"]["by_hour"]) == 7
def test_jsonl_kb_activity(store: KBStore) -> None:
src = store.put_source(b"x")
propose_claim(store, text="a", evidence=[src.id], proposed_by="agent-a")
resp = handle_request({"id": "1", "method": "kb.activity", "params": {"days": 0}})
assert resp["ok"] is True
assert resp["id"] == "1"
assert resp["result"]["total_events"] >= 1
assert len(resp["result"]["by_hour"]) == 7
def test_jsonl_kb_activity_error_envelope(store: KBStore) -> None:
resp = handle_request({"id": "2", "method": "kb.activity", "params": {"days": -1}})
assert resp["id"] == "2"
assert resp["ok"] is False
assert "error" in resp
🤖 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 `@tests/test_stats.py` around lines 269 - 275, Extend test_jsonl_kb_activity to
assert the successful response id and exact success envelope fields, then add a
failure-case request for kb.activity with invalid parameters and assert the
response has the expected id, ok=False, and an error field while excluding
result.

Source: Coding guidelines

Comment on lines +8 to +14
<button
onClick={() => setOpen((o) => !o)}
className="flex w-full items-center gap-2 px-3 py-1.5 text-left font-mono text-[10px] uppercase tracking-widest text-sepia"
>
<ChevronRight size={12} className={`transition ${open ? 'rotate-90' : ''}`} />
<Brain size={12} /> Thinking
</button>

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

Add aria-expanded to the toggle button.

The button controls a collapsible section but doesn't communicate its expanded/collapsed state to assistive technology. Add aria-expanded={open} so screen readers announce the toggle state.

♿ Proposed fix
       <button
         onClick={() => setOpen((o) => !o)}
+        aria-expanded={open}
         className="flex w-full items-center gap-2 px-3 py-1.5 text-left font-mono text-[10px] uppercase tracking-widest text-sepia"
       >
📝 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
<button
onClick={() => setOpen((o) => !o)}
className="flex w-full items-center gap-2 px-3 py-1.5 text-left font-mono text-[10px] uppercase tracking-widest text-sepia"
>
<ChevronRight size={12} className={`transition ${open ? 'rotate-90' : ''}`} />
<Brain size={12} /> Thinking
</button>
<button
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
className="flex w-full items-center gap-2 px-3 py-1.5 text-left font-mono text-[10px] uppercase tracking-widest text-sepia"
>
<ChevronRight size={12} className={`transition ${open ? 'rotate-90' : ''}`} />
<Brain size={12} /> Thinking
</button>
🤖 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 `@webapp/src/components/transcript/ThinkingBlock.tsx` around lines 8 - 14, Add
aria-expanded={open} to the collapsible toggle button in the ThinkingBlock
component, alongside its existing onClick handler and className, so assistive
technologies receive the current expanded state.

Comment on lines +47 to +52
const [stack, setStack] = useState<{ id: string; agent?: string }[]>([{ id: sessionId, agent }])
const top = stack[stack.length - 1]
const q = useQuery({
queryKey: ['transcript', conn.endpoint, top.id],
queryFn: () => fetchTranscript(conn, top.id, top.agent),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stack state doesn't reset when sessionId prop changes — user sees stale transcript.

useState initializes the stack from sessionId on first mount only. When the parent (ReviewView) passes a new sessionId (user selects a different session), the stack retains the old session ID, so top.id and the useQuery key stay unchanged. The user sees the previous session's transcript instead of the newly selected one.

Fix by adding a key prop in ReviewView.tsx to force remount:

- <TranscriptView
+ <TranscriptView
+   key={selected.s.session_id ?? undefined}
    conn={selected.project.conn}
    sessionId={selected.s.session_id as string}
  />

Alternatively, reset the stack when sessionId changes via useEffect here:

+ useEffect(() => {
+   setStack([{ id: sessionId, agent }])
+ }, [sessionId, agent])

The key prop approach is preferred — it's simpler and avoids an extra render cycle.

🤖 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 `@webapp/src/views/TranscriptView.tsx` around lines 47 - 52, Reset
TranscriptView state when the selected session changes by adding a key derived
from sessionId to the TranscriptView usage in ReviewView, forcing remount and
reinitializing stack. Locate the TranscriptView render in ReviewView and ensure
the key changes whenever sessionId changes.

plind-junior and others added 4 commits July 10, 2026 03:30
* feat(transcript): locate raw Claude session files by id

* feat(transcript): parse Claude JSONL into normalized blocks

* feat(transcript): orchestrate load with observation fallback

* feat(transcript): expose kb.session_transcript across all surfaces

* feat(webapp): transcript client types and fetch

* feat(webapp): thinking, diff, and code block renderers

* feat(webapp): tool block with per-tool rendering and diffs

* feat(webapp): sessions tab with full transcript viewer

* feat(transcript): parse Codex rollouts into normalized blocks

* test(webapp): cover degraded render and subagent drill-down

* test(webapp): e2e smoke for the sessions transcript tab

* docs(transcript): session transcript viewer design and plan

* refactor(webapp): merge session transcript into Review, drop Sessions tab

* docs(transcript): note the merge of the viewer into Review

* feat(activity): expose kb.activity audit buckets across all surfaces

one pass over the audit log returning per-day counts (with
proposal/decision breakdowns), an hour-of-week matrix, and actor/event
histograms — the data a dashboard needs that kb.audit's tail-limited
raw events and kb.stats' review aggregates don't cover.

windowed in viewer-local calendar days so the oldest heatmap cell is
never partially counted (days=0 for all-time, negatives rejected).
local bucketing prefers an iana tz name (dst-correct across the year)
and falls back to a clamped fixed utc offset. scope-filtered through
the same viewer context as kb.audit so multi-project kbs don't leak
actor names or activity timing across project boundaries.

registered at all four sites (mcp tool, jsonl handler, capabilities
methods, vouch activity cli) plus trust read methods.

* feat(webapp): dashboard view of kb activity, landing on /

new /dashboard view driven by kb.activity: a 12-month activity
calendar (ember heat ramp themed for dark and light via --heat-* css
vars), last-30-days bars, an hour-of-week heatmap, and top-actor /
event-mix bar lists, with stat tiles fed by kb.status and kb.stats.

the calendar fetches 371 days so its oldest drawn column always sits
inside the server window, sends the browser's iana timezone for
dst-correct bucketing, and degrades cleanly: endpoints that don't
advertise kb.activity get an upgrade hint, empty audit logs get an
empty state, and a malformed stats payload no longer crashes the view.

/ now redirects to the dashboard instead of chat and the tab leads the
sidebar; the e2e smoke flow clicks through to chat accordingly.

* feat(console): add vouch-ui console, demo setup, and web integration

add web-based console with dashboard view of kb activity and session management.
integrate vouch-ui webapp, docker-compose demo environment, and llm-backed
actions (compile + summarize). includes hatch build script and full test coverage.

* fix(review): remove remaining conflict markers

cleaned up merge conflict markers that were preventing the component from loading.
…#453)

taken_names was only seeded from on-disk pages and pending proposals,
never updated as drafts were accepted within the phase-1 loop. Two
same-titled (or same-slug) drafts from one LLM batch both passed the
collision guard, filing duplicate PAGE proposals. Approving the second
would silently route through update_page() and overwrite the first.

Fold each survivor's title/slug into taken_names immediately upon
acceptance, mirroring what phase 2 already does for wikilinks.
* feat(context): gate reranking behind retrieval config

* fix(context): address rerank review feedback

* test(context): isolate reranker cache reset
…#402)

five fixes to code merged on the test branch, all variants of "a failure
or not-yet-ready state reported as success":

- install-mcp (codex toml_merge): a config.toml the minimal serializer
  can't round-trip (non-bmp string value, nan/inf float) was bucketed as
  skipped and printed "(already present)" with a clean "Done", so the user
  believed vouch was wired into codex when it wasn't. add a distinct
  `failed` bucket, report it, and exit non-zero.
- install-mcp: reject a manifest `dst` that escapes the target tree
  (`..`/absolute) instead of writing outside it — defense in depth for the
  manifest file writer. shipped adapters use only contained relative paths.
- dual-solve review-ui: `_serialize` computed the recommendation over the
  empty candidate list on every poll, so a running job showed "neither
  engine produced a usable diff" for its whole duration. omit the hint
  until candidates exist.
- session.crystallize: retrying on a not-yet-ended session rewrote the
  summary page with a fresh wall-clock "Ended:" stamp each time (and
  re-embedded), so #256's "idempotent retry" wasn't. render a stable
  marker for an open session.
- capture ingest-codex: cap rollout parsing at 64 MiB so an oversized or
  newline-free-blob rollout can't be read whole into memory, matching the
  byte caps on other untrusted reads.

each fix has a regression test; make check green.
@github-actions github-actions Bot added dual-solve dual-solve orchestration adapters agent host adapters and install manifests labels Jul 13, 2026
dripsmvcp and others added 18 commits July 12, 2026 22:14
* feat(desktop): file-changes tree view in dual-solve panes

each dual-solve candidate pane showed a flat filename list plus a single
column that stacked every changed file's hunks, which is hard to scan once
a diff touches more than one or two files.

replace that block with a reusable FileChanges view modeled on the
gittensor-ui repositories components: a compact nested file tree
(folders-first) as a narrow rail drives a content pane showing only the
selected file's diff. selection is local per candidate, so the claude and
codex panes navigate independently.

- fileTree.ts: pure buildFileTree(paths) -> nested FileNode[], folders-first
  alphabetical, intermediate dirs synthesized from path segments; unit-tested.
- diffParse.ts: lift parseDiff out of Diff.tsx so the renderer and the new
  view share one parser (behavior unchanged).
- FileChanges.tsx: tree rail + per-file diff pane, derived entirely from the
  candidate's existing diff string. no new ipc or server change.
- DualSolve.tsx: swap the ul + Diff block for <FileChanges>.

renderer-only. typecheck clean; 100 tests pass including 7 new tree cases.

* docs(desktop): add dual-solve file-changes view screenshot

shows the per-candidate file tree rail driving the diff pane.

* fix(desktop): address review on dual-solve file-changes view

two coderabbit findings on #294:

- diffParse dropped any content line starting with "+++"/"---", so a real
  added "++counter" or removed "--flag" line vanished from the diff. restrict
  the header skip to the actual markers "+++ " / "--- " (trailing space + path).
  adds diff-parse.test.ts covering the regression and basic parsing.
- file rows in the tree rail were clickable <div>s — mouse-only, not
  keyboard-focusable. switch to <button type="button"> and add a focus-visible
  style; the .fc-file class now resets button chrome so the look is unchanged.

verified live: rows are real buttons, keyboard-focusable, selection still swaps
the diff pane. typecheck clean; 104 tests pass.

* feat(web): port dual-solve file-changes tree view from the desktop app

the desktop app this feature originally targeted was removed from the
test branch (e52ecd0); the dual-solve ui now lives in the fastapi web
console. this ports the candidate-pane file-changes view there: a
compact folders-first file tree (intermediate directories synthesized
from path segments) drives a per-file diff pane, replacing the flat
changed-files list and the stacked all-files diff. selection is
per-candidate — picking a file in the claude pane never moves the
codex pane — and file rows are real buttons, keyboard-focusable.

the shared parser also fixes a live bug in the web diff renderer:
added/removed content lines beginning with ++/-- (e.g. "++counter")
were dropped as +++/--- file headers; the header skip now requires
the trailing space-and-path form.

parse/tree helpers live in static/diff_view.js, a pure es module with
no imports, so tests/test_web_diff_view.py can execute it directly
under node (skips where node is absent).
* feat(config): mcp.publish_skills flag gating the skill catalogue

ships kb.list_skills / kb.get_skill — agents enumerate the claude code
slash-command and SKILL.md catalogue visible at <kb_root>/.claude/ and
~/.claude/ over mcp, then fetch one body by name (project-local entries
override user-global on collision). registered across mcp, jsonl, and the
cli (vouch list-skills / vouch get-skill), and in capabilities.METHODS so
test_capabilities keeps the surfaces in lockstep.

adds the mcp.publish_skills config flag (default true) so existing kbs
keep the catalogue. flipping it to false — "company-brain" mode where the
catalogue itself is sensitive — makes list_skills return an empty list
and get_skill raise permission_denied. the flag is read fresh from
config.yaml on every call, so toggling it hides the catalogue without
restarting the server, and is surfaced on kb.capabilities.mcp so clients
can detect the gate. a kb with no mcp: block stays default-on.

closes #235

* fix(schemas): regenerate capabilities schema for the mcp flags block
…e codex (#425) (#459)

claude-code's UserPromptSubmit hook (context-hook / build_claude_prompt_hook)
computed retrieval via build_context_pack but never recorded the prompt into
the entity-salience reflex (#223) -- salience.record_query was simply never
called from the hook path, leaving the reflex permanently dormant for every
claude-code session. Fixed additively: record_query now runs when a
session_id is present, wrapped so a salience failure can never break the
hook's real contract (never raises, never blocks the turn).

codex exposes UserPromptSubmit with the same {prompt, session_id, ...}
payload and additionalContext response shape as claude-code, so the
existing context-hook command now serves both hosts unmodified -- wired via
a new hooks.json entry alongside the existing Stop (session capture) hook.

OpenClaw needed no changes: its context-engine slot
(src/vouch/openclaw/context_engine.py) already calls
record_query/attach_salience correctly on every assemble() -- a separate,
already-correct code path from the claude-code/codex hooks.py one this
fixes.

cursor is not wired: its beforeSubmitPrompt hook is validation/block-only
and cannot inject additionalContext (confirmed against Cursor's docs; an
open Cursor feature request asks for exactly this, unresolved). Documented
in adapters/cursor/install.yaml rather than silently skipped.

Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
the pending, decisions, and stale sections each slice their rows to
`limit`, and the `--limit` help lists followups among the capped
sections, but followups_due was built without the slice — so the digest
returned every due followup regardless of --limit. add the same
`[:limit]` slice the sibling sections use, plus a regression test.

Co-authored-by: Yaroslav98214 <diakovichyaroslav30@gmail.com>
Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
…s cannot fork the hash chain (#263)

without the lock, two log_event calls racing on the same kb both observe
the same prev_hash from _last_hash, both append to audit.log.jsonl, and
verify_chain reports "previous hash mismatch" at the second concurrent
entry — permanently breaking the tamper-evidence guarantee #244/#253
added. reachable from vouch serve handling concurrent agents, vouch serve
alongside cli commands on the same kb, scripted backgrounded approvals,
and scheduled expire / sync runs.

holds an exclusive lock on a sibling audit.log.jsonl.lock file for the
duration of read-prev-hash → derive → append. uses fcntl.flock on posix
and msvcrt.locking on windows. the audit log itself is never opened in a
mode that could truncate it. dynamic __import__ keeps mypy clean on both
platforms.

new regression spawns four multiprocessing.spawn workers writing 20
events each and asserts verify_chain.ok plus the full 80 events landed —
threads hide the race behind the gil. a second test asserts the lock is
released on an exception mid-write so the next call succeeds.

Fixes #262
…pic (closes #315) (#347)

* feat(experts): kb.experts — rank entities by evidence density on a topic

Closes #315.

Add a read-only kb.experts query: given a free-text topic, rank the entities
carrying the most matched evidence (count / recency / citation weightings)
identically across mcp / jsonl / cli. Aggregates approved, live claims only —
excludes superseded/archived/redacted so a non-live claim never inflates a
score; no proposals, writes, network, or llm. Ranking lives in a new
src/vouch/experts.py, wired at the four registration sites.

* test(experts): cover the kb.experts JSONL request/response envelope

The suite exercised rank_experts() directly but not the kb.experts JSONL
entrypoint. Add two envelope tests mirroring tests/test_jsonl_server.py:
a well-formed request returns {id, ok, result} with the ranking under
result["experts"], and a request missing the required `topic` param returns
the {id, ok: false, error} failure envelope (code "missing_param").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
Co-authored-by: e11734937-beep <e11734937@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…sals (#323) (#353)

reviewers scan the pending queue linearly and every item looks the same
weight; the ones that most deserve a hard look blend in. the near-
duplicate direction already surfaces (find_similar_on_propose); this
surfaces the outlier direction.

scores every pending claim proposal worst-first with reason codes:
- thin_evidence: evidence count at or below a floor (barely cited)
- contradicts_many: declares contradictions against >= n approved live
  claims (retired claims don't count)
- far_from_corpus: nearest-neighbour cosine to the approved claim corpus
  below a floor (an outlier). embedding-derived, so it degrades
  gracefully to no code when the embeddings extra is absent — the same
  swallow find_similar_on_propose does — leaving the two non-embedding
  codes still computed.

thresholds resolve from review.anomaly.* (mirroring similarity_threshold).
read-only by construction: a hint for the reviewer that emits no proposal,
writes no artifact, and never rejects or quarantines — the human gate is
untouched. scoring lives in a new anomaly.py, not storage.py.

shipped cli-only (`vouch flag-anomalies`); the kb.flag_anomalies method
and a list-pending --flag-anomalies flag are a separate concern.

Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
…268)

the citation gate and uncited_items listed claims dropped by the max_chars
budget, so a pack could fail require_citations for items the caller never
received. evaluate uncited only over the returned item list.

fixes #174.

Co-authored-by: Yaroslav98214 <diakovichyaroslav30@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
…shes (#360)

* fix: resolve list_pages crash on corrupt page files

list_pages() lacked the _load_or_skip resilience applied to other
list_* paths, so one bad pages/*.md file crashed status, search,
lint, and MCP tools. Skip unreadable pages with a warning instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(scripts): add repro script for list_pages crash (PR #360)

Gives maintainers a one-command proof of the pre-fix crash path and
the fixed skip-and-continue behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Add a numbered, imperative install guide for agents (closes #329): detect host via install-mcp --list, wire MCP, verify capabilities, init KB, and run a propose-to-human-approve smoke test. Link from llms.txt and distinguish from getting-started.md.

Co-authored-by: luciferlive112116 <luciferlive112116@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
contradict() had no guard against claim_a == claim_b, unlike its
sibling supersede() which explicitly rejects self-reference. calling
it with the same id on both sides wrote a self-loop contradicts
reference and flipped the claim to contested with no real
counterparty -- durable, nonsensical state with no way to recover
short of manual yaml editing.

Co-authored-by: plind <59729252+plind-junior@users.noreply.github.com>
* docs: add local dev install to readme, fix dev setup steps

following the documented dev setup verbatim fails twice on a clean
machine: `python -m venv` breaks on stock ubuntu (only `python3`
exists), and `make check` after `pip install -e '.[dev]'` fails at the
mypy step because src/vouch/web imports jinja2, which lives in the web
extra. ci installs '.[dev,web]' (.github/workflows/ci.yml), so the docs
now match: python3 + '.[dev,web]' in readme, contributing, and
claude.md.

also adds the previously missing local-development install block to the
readme's install section. verified end-to-end on a fresh clone: install,
vouch --version, vouch status, and a green make check (1092 passed).

* docs: note webapp build step for source checkouts

`vouch console` serves prebuilt assets that only release wheels ship
(hatch_build.py bundles webapp/dist); a fresh clone has no build, so
the command errors with "no built vouch console found" even after
`pip install -e '.[dev,web]'`. document `make webapp-build` (one-time,
needs node) and `make console` (backend + console dev server) in the
readme dev block and contributing dev setup.

verified in a fresh clone on the test branch: after `make webapp-build`,
`vouch console` serves the react console at http 200.

* docs: make readme dev block a full runnable sequence

the dev-install block listed clone + venv + pip but left the webapp to
prose, so following the commands alone never got the console running.
the block now carries the whole verified path: editable install,
`vouch --version` smoke check, `make console` for the backend + vite
dev-server pair, and `make check` for the ci gate.

verified in a fresh clone on the test branch: `make console` brings up
`vouch serve --transport http` on :8731 (streamablehttp session manager
started) and the vite console dev server, node deps auto-installed on
first run.
* feat(transcript): locate raw Claude session files by id

* feat(transcript): parse Claude JSONL into normalized blocks

* feat(transcript): orchestrate load with observation fallback

* feat(transcript): expose kb.session_transcript across all surfaces

* feat(webapp): transcript client types and fetch

* feat(webapp): thinking, diff, and code block renderers

* feat(webapp): tool block with per-tool rendering and diffs

* feat(webapp): sessions tab with full transcript viewer

* feat(transcript): parse Codex rollouts into normalized blocks

* test(webapp): cover degraded render and subagent drill-down

* test(webapp): e2e smoke for the sessions transcript tab

* docs(transcript): session transcript viewer design and plan

* refactor(webapp): merge session transcript into Review, drop Sessions tab

* docs(transcript): note the merge of the viewer into Review

* feat(activity): expose kb.activity audit buckets across all surfaces

one pass over the audit log returning per-day counts (with
proposal/decision breakdowns), an hour-of-week matrix, and actor/event
histograms — the data a dashboard needs that kb.audit's tail-limited
raw events and kb.stats' review aggregates don't cover.

windowed in viewer-local calendar days so the oldest heatmap cell is
never partially counted (days=0 for all-time, negatives rejected).
local bucketing prefers an iana tz name (dst-correct across the year)
and falls back to a clamped fixed utc offset. scope-filtered through
the same viewer context as kb.audit so multi-project kbs don't leak
actor names or activity timing across project boundaries.

registered at all four sites (mcp tool, jsonl handler, capabilities
methods, vouch activity cli) plus trust read methods.

* feat(webapp): dashboard view of kb activity, landing on /

new /dashboard view driven by kb.activity: a 12-month activity
calendar (ember heat ramp themed for dark and light via --heat-* css
vars), last-30-days bars, an hour-of-week heatmap, and top-actor /
event-mix bar lists, with stat tiles fed by kb.status and kb.stats.

the calendar fetches 371 days so its oldest drawn column always sits
inside the server window, sends the browser's iana timezone for
dst-correct bucketing, and degrades cleanly: endpoints that don't
advertise kb.activity get an upgrade hint, empty audit logs get an
empty state, and a malformed stats payload no longer crashes the view.

/ now redirects to the dashboard instead of chat and the tab leads the
sidebar; the e2e smoke flow clicks through to chat accordingly.

* feat(console): add vouch-ui console, demo setup, and web integration

add web-based console with dashboard view of kb activity and session management.
integrate vouch-ui webapp, docker-compose demo environment, and llm-backed
actions (compile + summarize). includes hatch build script and full test coverage.

* fix(review): remove remaining conflict markers

cleaned up merge conflict markers that were preventing the component from loading.

* feat(synthesize): llm answer backend grounded in pages, live in chat

implement the reserved llm=true path of kb.synthesize: retrieval picks
kb pages and approved claims, the deployment-configured compile.llm_cmd
drafts the prose, and code verifies every [id] citation against the
offered sources — invented ids are stripped, and a draft left with no
verifiable citation returns an empty answer instead of a guess. the
wire shape is unchanged plus additive pages and _meta.synthesis_backend
fields.

the console chat now asks with llm: true and falls back to
deterministic claim synthesis when no llm_cmd is configured; page
citations open the page drawer and llm answers carry a badge. cli
mirror: vouch synthesize --llm. the mcp tool gains the llm param; the
jsonl/http surface already forwarded it.
…467)

implement the reserved llm=true path of kb.synthesize: retrieval picks
kb pages and approved claims, the deployment-configured compile.llm_cmd
drafts the prose, and code verifies every [id] citation against the
offered sources — invented ids are stripped, and a draft left with no
verifiable citation returns an empty answer instead of a guess. the
wire shape is unchanged plus additive pages and _meta.synthesis_backend
fields.

the console chat now asks with llm: true and falls back to
deterministic claim synthesis when no llm_cmd is configured; page
citations open the page drawer and llm answers carry a badge. cli
mirror: vouch synthesize --llm. the mcp tool gains the llm param; the
jsonl/http surface already forwarded it.
drop the @vouch_dev follow badge from the header badge row; the
remaining badges (ci, pypi, mcp registry, python versions, license,
gittensor) are all project-status links rather than social ones.
…ls (#261)

* feat(server): extend _meta.vouch_hot_memory to all read-side kb.* tools

* fix: update

* test(page-filters): read kb.list_pages through the items envelope

rebasing feat/hot-memory-universal-coverage onto main's kb.list_pages
(frontmatter filters, #234-adjacent) collided with this branch's own
kb.list_pages (hot-memory envelope, #225) — both touched the same
function. the conflict resolution keeps both: filter_pages() runs first,
then the result wraps in {"items": [...], "_meta": {...}} like every
other kb.list_* method.

test_page_filters.py predates that envelope and asserted on the bare
list; update the two JSONL/MCP assertions to read result["items"].

* fix(capabilities): read host_compat from package.json, not openclaw.plugin.json

_load_host_compat (#237) has been reading openclaw.compat from
openclaw.plugin.json since it shipped in 1.2.0, so kb.capabilities.host_compat
has always reported {} — that manifest never carries a top-level "openclaw"
key (test_manifest_carries_no_dead_dialect_fields enforces this; it's a dead
field from the pre-2026.6 dialect). openclaw.compat.pluginApi has only ever
lived in package.json, alongside openclaw.extensions.

repoints _load_host_compat (renamed _PLUGIN_MANIFEST_PATH ->
_HOST_COMPAT_MANIFEST_PATH for clarity) and the mirroring test at
package.json. fixes test_capabilities_host_compat_matches_openclaw_manifest
and test_capabilities_host_compat_present_and_nonempty, both failing before
this change.

* fix(hot-memory): classify nine methods added since the last rebase

test_hot_memory_universal_coverage requires every kb.* method to be in
HOT_MEMORY_COVERED or HOT_MEMORY_EXCLUDED. main grew nine methods this
branch didn't know about: kb.activity, kb.clear_claims, kb.experts,
kb.get_skill, kb.list_sessions, kb.list_skills, kb.propose_delete,
kb.session_transcript, kb.summarize_session.

classified by shape: kb.activity joins kb.digest/kb.stats as an
aggregate; kb.experts joins kb.detect_themes as self-contained ranked
analysis; kb.list_skills/kb.get_skill/kb.list_sessions/
kb.session_transcript aren't claim/artifact reads at all; kb.propose_delete
and kb.summarize_session are write paths behind the review gate;
kb.clear_claims mutates durable state directly.

* fix(hot-memory): address review — changelog placement, deprecation version, in-repo jsonl clients

three blocking review comments on #225's hot-memory work:

- the feature's CHANGELOG "Added" bullet had landed under the immutable
  [1.0.0] — 2026-06-26 section instead of [Unreleased] (a rebase artifact:
  the hunk's context still matched after [1.0.0] got cut, so it merged
  without conflict in the wrong place). moved it to [Unreleased] -> Added.
- LIST_ENVELOPE_DEPRECATION.remove_in said "0.3.0" while the repo is on
  1.3.0, which reads as already-removed. #225 asked for one release
  cycle, so "1.4.0".
- the repo's own jsonl clients still read the pre-envelope flat-list shape
  and none of them run in CI: adapters/jsonl-shell/example-pipeline.sh,
  adapters/jsonl-shell/README.md (two spots), examples/browse-and-read/
  run.sh's first_field() helper, and — found while grepping for other
  .result[] consumers per the review — two assertions in
  examples/review-gate-dry-run-preview/run.sh and the pending-count
  tracking in examples/sessions-and-crystallize/run.sh. all now read
  result.items. verified by running each script end to end (jq-based
  ones use jq, which isn't installed here, but the three python-parsed
  scripts ran and passed).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adapters agent host adapters and install manifests ci github actions and automation cli command line interface docs documentation, specs, examples, and repo guidance dual-solve dual-solve orchestration embeddings embedding-backed retrieval mcp mcp, jsonl, and http surfaces openclaw openclaw integration packaging packaging, build metadata, and make targets retrieval context, search, synthesis, and evaluation review-ui browser review ui schemas json schemas and generated schema assets 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.