Test#420
Conversation
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(bundle): reject path traversal in import (CVE-2007-4559, #9)
fix(verify): catch ArtifactNotFoundError on missing stored content
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.
* 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.
There was a problem hiding this comment.
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 winAdd the missing
kb.session_transcriptCLI mirror
kb.activityalready has the four-place registration and JSONL coverage.kb.session_transcriptis present insrc/vouch/server.py,src/vouch/jsonl_server.py, andsrc/vouch/capabilities.py, but notsrc/vouch/cli.py; add the CLI command to keep the tool surface consistent.tests/test_session_transcript.pyalready 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
📒 Files selected for processing (35)
CHANGELOG.mddocs/superpowers/plans/2026-07-10-session-transcript-viewer.mddocs/superpowers/specs/2026-07-10-session-transcript-viewer-design.mdsrc/vouch/capabilities.pysrc/vouch/cli.pysrc/vouch/jsonl_server.pysrc/vouch/server.pysrc/vouch/stats.pysrc/vouch/transcript.pysrc/vouch/trust.pytests/test_session_transcript.pytests/test_stats.pytests/test_trust.pywebapp/e2e/review-transcript.spec.tswebapp/e2e/smoke.spec.tswebapp/src/App.test.tsxwebapp/src/App.tsxwebapp/src/components/Shell.tsxwebapp/src/components/transcript/CodeBlock.tsxwebapp/src/components/transcript/DiffView.tsxwebapp/src/components/transcript/MessageBlock.tsxwebapp/src/components/transcript/ThinkingBlock.tsxwebapp/src/components/transcript/ToolBlock.test.tsxwebapp/src/components/transcript/ToolBlock.tsxwebapp/src/components/transcript/blocks.test.tsxwebapp/src/lib/transcript.test.tswebapp/src/lib/transcript.tswebapp/src/lib/types.tswebapp/src/styles.csswebapp/src/views/DashboardView.test.tsxwebapp/src/views/DashboardView.tsxwebapp/src/views/ReviewView.test.tsxwebapp/src/views/ReviewView.tsxwebapp/src/views/TranscriptView.test.tsxwebapp/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
| - `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. |
There was a problem hiding this comment.
📐 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.
| - `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.
| 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 |
There was a problem hiding this comment.
📐 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.
| 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
| <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> |
There was a problem hiding this comment.
📐 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.
| <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.
| 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), | ||
| }) |
There was a problem hiding this comment.
🎯 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.
* 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.
* 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).
What changed
Why
What might break
VEP
Tests
make checkpasses locally (lint + mypy + pytest)CHANGELOG.mdupdated under## [Unreleased]Summary by CodeRabbit