Skip to content

Complete live graph integration and slim MCP context - #300

Merged
ooples merged 4 commits into
masterfrom
fix/live-graph-full-system
Aug 9, 2026
Merged

ooples merged 4 commits into
masterfrom
fix/live-graph-full-system

Conversation

@ooples

@ooples ooples commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What changed

  • Repair the Codex plugin manifest, MCP declaration, lifecycle hooks, generated entries, release versioning, and installation verification.
  • Raise the supported runtime floor to Node 22, update the SQLite binding, and test real MCP startup on Node 22, 24, 25, and 26.
  • Advertise an 18-tool core MCP catalog by default while retaining all 98 tools behind TOKEN_OPTIMIZER_TOOL_PROFILE=full.
  • Drive semantic harvesting through PostToolUse and one non-looping Stop continuation so the active Codex model records durable findings with wiki_write; keep the external harvester as an optional fallback.
  • Add a repeatable full-flow verifier covering model write, later-session delivery, cross-project delivery, dashboard visibility, provenance, and controlled positive balance.
  • Run the live-graph verifier in CI and update integration/runtime documentation.

Why

The Codex plugin used the wrong MCP manifest key and referenced an incomplete lifecycle, the server advertised all 98 schemas into every session, Node 25 could fail through the older native SQLite dependency, and semantic graph writes were not reachable from the active Codex model. Those gaps made installation appear complete while the intended cross-session/cross-project system was not proven end to end.

Impact

Codex can now load the plugin and use its own reasoning to harvest findings without sending transcripts to a second model. Default MCP catalog serialization falls from approximately 35,415 to 5,604 tokens while advanced users retain the complete catalog. Portable lessons can flow across sessions and repositories, and the dashboard has a CI-gated proof that the graph it displays is the graph the hooks wrote.

Validation

  • npm run build
  • npm run doctor — 11/11 checks
  • Real MCP initialize and tools/list on Node 22, 24, 25, and 26
  • npm run verify:live-graph — agent provenance, cross-session and cross-project delivery, dashboard routes, and +43,968 controlled net tokens
  • npm run verify:ui — 22/22 browser checks
  • npm run verify:interactions — 57/57 interaction/security checks
  • Active repository Jest suite — 162 suites, 2,058 passed, 5 skipped
  • Hook sync, client verifier — 141/141, plugin validator, lint (zero errors), Prettier, and git diff --check

This branch starts from master after the PR #297 review fixes; those resolved threads and their commit remain separate.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 273 files, which is 173 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ac12fd6-e7d6-40f7-a8c5-e18a5a4bf143

📥 Commits

Reviewing files that changed from the base of the PR and between 20f7bea and 5cb4844.

📒 Files selected for processing (273)
  • .github/BRANCH_PROTECTION.md
  • .github/CI_CD.md
  • .github/RELEASE_FLOW.md
  • docs/CLIENT_SUPPORT.md
  • docs/LIVE_MODEL_EVALUATION.md
  • hooks-core/adapter.mjs
  • hooks-core/decide.mjs
  • hooks-core/policy.mjs
  • integrations/README.md
  • integrations/amp/AGENTS.md
  • integrations/amp/README.md
  • integrations/cline/README.md
  • integrations/cline/hooks/PostToolUse
  • integrations/cline/hooks/PostToolUse.ps1
  • integrations/cline/hooks/PreToolUse
  • integrations/cline/hooks/PreToolUse.ps1
  • integrations/cline/hooks/TaskResume
  • integrations/cline/hooks/TaskResume.ps1
  • integrations/cline/hooks/TaskStart
  • integrations/cline/hooks/TaskStart.ps1
  • integrations/cline/hooks/token-optimizer/lib/adapter.mjs
  • integrations/cline/hooks/token-optimizer/lib/audit.mjs
  • integrations/cline/hooks/token-optimizer/lib/cache.mjs
  • integrations/cline/hooks/token-optimizer/lib/calibration.mjs
  • integrations/cline/hooks/token-optimizer/lib/consolidate.mjs
  • integrations/cline/hooks/token-optimizer/lib/curate.mjs
  • integrations/cline/hooks/token-optimizer/lib/decide.mjs
  • integrations/cline/hooks/token-optimizer/lib/disclose.mjs
  • integrations/cline/hooks/token-optimizer/lib/doctor.mjs
  • integrations/cline/hooks/token-optimizer/lib/expand.mjs
  • integrations/cline/hooks/token-optimizer/lib/fleet.mjs
  • integrations/cline/hooks/token-optimizer/lib/forecast.mjs
  • integrations/cline/hooks/token-optimizer/lib/harvest-write.mjs
  • integrations/cline/hooks/token-optimizer/lib/harvest.mjs
  • integrations/cline/hooks/token-optimizer/lib/inject.mjs
  • integrations/cline/hooks/token-optimizer/lib/keepwarm.mjs
  • integrations/cline/hooks/token-optimizer/lib/lessons.mjs
  • integrations/cline/hooks/token-optimizer/lib/manifest.mjs
  • integrations/cline/hooks/token-optimizer/lib/metrics.mjs
  • integrations/cline/hooks/token-optimizer/lib/paths.mjs
  • integrations/cline/hooks/token-optimizer/lib/policy.mjs
  • integrations/cline/hooks/token-optimizer/lib/pricing.mjs
  • integrations/cline/hooks/token-optimizer/lib/recording.mjs
  • integrations/cline/hooks/token-optimizer/lib/remedy.mjs
  • integrations/cline/hooks/token-optimizer/lib/restore.mjs
  • integrations/cline/hooks/token-optimizer/lib/routing.mjs
  • integrations/cline/hooks/token-optimizer/lib/skeleton.mjs
  • integrations/cline/hooks/token-optimizer/lib/staleness.mjs
  • integrations/cline/hooks/token-optimizer/lib/standing.mjs
  • integrations/cline/hooks/token-optimizer/lib/surface.mjs
  • integrations/cline/hooks/token-optimizer/lib/symbols.mjs
  • integrations/cline/hooks/token-optimizer/lib/transcript.mjs
  • integrations/cline/hooks/token-optimizer/lib/waste.mjs
  • integrations/cline/hooks/token-optimizer/lib/wiki.mjs
  • integrations/cline/hooks/token-optimizer/lib/wire.mjs
  • integrations/cline/hooks/token-optimizer/post-tool.mjs
  • integrations/cline/hooks/token-optimizer/pre-tool.mjs
  • integrations/cline/hooks/token-optimizer/session-start.mjs
  • integrations/cline/token-optimizer.md
  • integrations/codex/hooks/lib/adapter.mjs
  • integrations/codex/hooks/lib/decide.mjs
  • integrations/codex/hooks/lib/policy.mjs
  • integrations/codex/plugin/hooks/lib/adapter.mjs
  • integrations/codex/plugin/hooks/lib/decide.mjs
  • integrations/codex/plugin/hooks/lib/policy.mjs
  • integrations/codex/plugin/skills/token-optimization/SKILL.md
  • integrations/continue/README.md
  • integrations/continue/token-optimizer.md
  • integrations/copilot/.github/hooks/lib/adapter.mjs
  • integrations/copilot/.github/hooks/lib/audit.mjs
  • integrations/copilot/.github/hooks/lib/cache.mjs
  • integrations/copilot/.github/hooks/lib/calibration.mjs
  • integrations/copilot/.github/hooks/lib/consolidate.mjs
  • integrations/copilot/.github/hooks/lib/curate.mjs
  • integrations/copilot/.github/hooks/lib/decide.mjs
  • integrations/copilot/.github/hooks/lib/disclose.mjs
  • integrations/copilot/.github/hooks/lib/doctor.mjs
  • integrations/copilot/.github/hooks/lib/expand.mjs
  • integrations/copilot/.github/hooks/lib/fleet.mjs
  • integrations/copilot/.github/hooks/lib/forecast.mjs
  • integrations/copilot/.github/hooks/lib/harvest-write.mjs
  • integrations/copilot/.github/hooks/lib/harvest.mjs
  • integrations/copilot/.github/hooks/lib/inject.mjs
  • integrations/copilot/.github/hooks/lib/keepwarm.mjs
  • integrations/copilot/.github/hooks/lib/lessons.mjs
  • integrations/copilot/.github/hooks/lib/manifest.mjs
  • integrations/copilot/.github/hooks/lib/metrics.mjs
  • integrations/copilot/.github/hooks/lib/paths.mjs
  • integrations/copilot/.github/hooks/lib/policy.mjs
  • integrations/copilot/.github/hooks/lib/pricing.mjs
  • integrations/copilot/.github/hooks/lib/recording.mjs
  • integrations/copilot/.github/hooks/lib/remedy.mjs
  • integrations/copilot/.github/hooks/lib/restore.mjs
  • integrations/copilot/.github/hooks/lib/routing.mjs
  • integrations/copilot/.github/hooks/lib/skeleton.mjs
  • integrations/copilot/.github/hooks/lib/staleness.mjs
  • integrations/copilot/.github/hooks/lib/standing.mjs
  • integrations/copilot/.github/hooks/lib/surface.mjs
  • integrations/copilot/.github/hooks/lib/symbols.mjs
  • integrations/copilot/.github/hooks/lib/transcript.mjs
  • integrations/copilot/.github/hooks/lib/waste.mjs
  • integrations/copilot/.github/hooks/lib/wiki.mjs
  • integrations/copilot/.github/hooks/lib/wire.mjs
  • integrations/copilot/.github/hooks/post-tool.mjs
  • integrations/copilot/.github/hooks/pre-tool.mjs
  • integrations/copilot/.github/hooks/session-start.mjs
  • integrations/copilot/.github/hooks/stop.mjs
  • integrations/copilot/.github/hooks/token-optimizer.json
  • integrations/crush/AGENTS.md
  • integrations/crush/README.md
  • integrations/cursor/README.md
  • integrations/cursor/hooks.json
  • integrations/cursor/hooks/lib/adapter.mjs
  • integrations/cursor/hooks/lib/audit.mjs
  • integrations/cursor/hooks/lib/cache.mjs
  • integrations/cursor/hooks/lib/calibration.mjs
  • integrations/cursor/hooks/lib/consolidate.mjs
  • integrations/cursor/hooks/lib/curate.mjs
  • integrations/cursor/hooks/lib/decide.mjs
  • integrations/cursor/hooks/lib/disclose.mjs
  • integrations/cursor/hooks/lib/doctor.mjs
  • integrations/cursor/hooks/lib/expand.mjs
  • integrations/cursor/hooks/lib/fleet.mjs
  • integrations/cursor/hooks/lib/forecast.mjs
  • integrations/cursor/hooks/lib/harvest-write.mjs
  • integrations/cursor/hooks/lib/harvest.mjs
  • integrations/cursor/hooks/lib/inject.mjs
  • integrations/cursor/hooks/lib/keepwarm.mjs
  • integrations/cursor/hooks/lib/lessons.mjs
  • integrations/cursor/hooks/lib/manifest.mjs
  • integrations/cursor/hooks/lib/metrics.mjs
  • integrations/cursor/hooks/lib/paths.mjs
  • integrations/cursor/hooks/lib/policy.mjs
  • integrations/cursor/hooks/lib/pricing.mjs
  • integrations/cursor/hooks/lib/recording.mjs
  • integrations/cursor/hooks/lib/remedy.mjs
  • integrations/cursor/hooks/lib/restore.mjs
  • integrations/cursor/hooks/lib/routing.mjs
  • integrations/cursor/hooks/lib/skeleton.mjs
  • integrations/cursor/hooks/lib/staleness.mjs
  • integrations/cursor/hooks/lib/standing.mjs
  • integrations/cursor/hooks/lib/surface.mjs
  • integrations/cursor/hooks/lib/symbols.mjs
  • integrations/cursor/hooks/lib/transcript.mjs
  • integrations/cursor/hooks/lib/waste.mjs
  • integrations/cursor/hooks/lib/wiki.mjs
  • integrations/cursor/hooks/lib/wire.mjs
  • integrations/cursor/hooks/post-tool.mjs
  • integrations/cursor/hooks/pre-tool.mjs
  • integrations/cursor/hooks/session-start.mjs
  • integrations/cursor/hooks/stop.mjs
  • integrations/cursor/token-optimizer.mdc
  • integrations/droid/AGENTS.md
  • integrations/droid/README.md
  • integrations/gemini/hooks/hooks.json
  • integrations/gemini/hooks/lib/adapter.mjs
  • integrations/gemini/hooks/lib/decide.mjs
  • integrations/gemini/hooks/lib/policy.mjs
  • integrations/gemini/hooks/pre-tool.mjs
  • integrations/gemini/hooks/stop.mjs
  • integrations/kilo/.kilo/plugin/token-optimizer.js
  • integrations/kilo/README.md
  • integrations/kilo/hooks/lib/adapter.mjs
  • integrations/kilo/hooks/lib/audit.mjs
  • integrations/kilo/hooks/lib/cache.mjs
  • integrations/kilo/hooks/lib/calibration.mjs
  • integrations/kilo/hooks/lib/consolidate.mjs
  • integrations/kilo/hooks/lib/curate.mjs
  • integrations/kilo/hooks/lib/decide.mjs
  • integrations/kilo/hooks/lib/disclose.mjs
  • integrations/kilo/hooks/lib/doctor.mjs
  • integrations/kilo/hooks/lib/expand.mjs
  • integrations/kilo/hooks/lib/fleet.mjs
  • integrations/kilo/hooks/lib/forecast.mjs
  • integrations/kilo/hooks/lib/harvest-write.mjs
  • integrations/kilo/hooks/lib/harvest.mjs
  • integrations/kilo/hooks/lib/inject.mjs
  • integrations/kilo/hooks/lib/keepwarm.mjs
  • integrations/kilo/hooks/lib/lessons.mjs
  • integrations/kilo/hooks/lib/manifest.mjs
  • integrations/kilo/hooks/lib/metrics.mjs
  • integrations/kilo/hooks/lib/paths.mjs
  • integrations/kilo/hooks/lib/policy.mjs
  • integrations/kilo/hooks/lib/pricing.mjs
  • integrations/kilo/hooks/lib/recording.mjs
  • integrations/kilo/hooks/lib/remedy.mjs
  • integrations/kilo/hooks/lib/restore.mjs
  • integrations/kilo/hooks/lib/routing.mjs
  • integrations/kilo/hooks/lib/skeleton.mjs
  • integrations/kilo/hooks/lib/staleness.mjs
  • integrations/kilo/hooks/lib/standing.mjs
  • integrations/kilo/hooks/lib/surface.mjs
  • integrations/kilo/hooks/lib/symbols.mjs
  • integrations/kilo/hooks/lib/transcript.mjs
  • integrations/kilo/hooks/lib/waste.mjs
  • integrations/kilo/hooks/lib/wiki.mjs
  • integrations/kilo/hooks/lib/wire.mjs
  • integrations/kilo/hooks/post-tool.mjs
  • integrations/kilo/hooks/pre-tool.mjs
  • integrations/kilo/hooks/session-start.mjs
  • integrations/kilo/token-optimizer.md
  • integrations/opencode/.opencode/plugins/token-optimizer.js
  • integrations/opencode/hooks/lib/adapter.mjs
  • integrations/opencode/hooks/lib/decide.mjs
  • integrations/opencode/hooks/lib/policy.mjs
  • integrations/opencode/hooks/post-tool.mjs
  • integrations/qwen/hooks/hooks.json
  • integrations/qwen/hooks/lib/adapter.mjs
  • integrations/qwen/hooks/lib/decide.mjs
  • integrations/qwen/hooks/lib/policy.mjs
  • integrations/qwen/hooks/pre-tool.mjs
  • integrations/qwen/hooks/stop.mjs
  • integrations/roo/README.md
  • integrations/roo/token-optimizer.md
  • integrations/windsurf/README.md
  • integrations/windsurf/hooks.json
  • integrations/windsurf/hooks/lib/adapter.mjs
  • integrations/windsurf/hooks/lib/audit.mjs
  • integrations/windsurf/hooks/lib/cache.mjs
  • integrations/windsurf/hooks/lib/calibration.mjs
  • integrations/windsurf/hooks/lib/consolidate.mjs
  • integrations/windsurf/hooks/lib/curate.mjs
  • integrations/windsurf/hooks/lib/decide.mjs
  • integrations/windsurf/hooks/lib/disclose.mjs
  • integrations/windsurf/hooks/lib/doctor.mjs
  • integrations/windsurf/hooks/lib/expand.mjs
  • integrations/windsurf/hooks/lib/fleet.mjs
  • integrations/windsurf/hooks/lib/forecast.mjs
  • integrations/windsurf/hooks/lib/harvest-write.mjs
  • integrations/windsurf/hooks/lib/harvest.mjs
  • integrations/windsurf/hooks/lib/inject.mjs
  • integrations/windsurf/hooks/lib/keepwarm.mjs
  • integrations/windsurf/hooks/lib/lessons.mjs
  • integrations/windsurf/hooks/lib/manifest.mjs
  • integrations/windsurf/hooks/lib/metrics.mjs
  • integrations/windsurf/hooks/lib/paths.mjs
  • integrations/windsurf/hooks/lib/policy.mjs
  • integrations/windsurf/hooks/lib/pricing.mjs
  • integrations/windsurf/hooks/lib/recording.mjs
  • integrations/windsurf/hooks/lib/remedy.mjs
  • integrations/windsurf/hooks/lib/restore.mjs
  • integrations/windsurf/hooks/lib/routing.mjs
  • integrations/windsurf/hooks/lib/skeleton.mjs
  • integrations/windsurf/hooks/lib/staleness.mjs
  • integrations/windsurf/hooks/lib/standing.mjs
  • integrations/windsurf/hooks/lib/surface.mjs
  • integrations/windsurf/hooks/lib/symbols.mjs
  • integrations/windsurf/hooks/lib/transcript.mjs
  • integrations/windsurf/hooks/lib/waste.mjs
  • integrations/windsurf/hooks/lib/wiki.mjs
  • integrations/windsurf/hooks/lib/wire.mjs
  • integrations/windsurf/hooks/post-tool.mjs
  • integrations/windsurf/hooks/pre-tool.mjs
  • integrations/windsurf/token-optimizer.md
  • integrations/zed/AGENTS.md
  • integrations/zed/README.md
  • plugin/hooks/hooks.json
  • plugin/hooks/lib/adapter.mjs
  • plugin/hooks/lib/decide.mjs
  • plugin/hooks/lib/policy.mjs
  • plugin/hooks/post-tool.mjs
  • plugin/hooks/stop.mjs
  • plugin/skills/token-optimization/SKILL.md
  • scripts/generate-client-configs.mjs
  • scripts/generate-client-entries.mjs
  • scripts/sync-hook-core.mjs
  • scripts/verify-client-configs.mjs
  • tests/hooks/adapter-injection-e2e.test.mjs
  • tests/hooks/clients.test.mjs
  • tests/hooks/codex-semantic-harvest.test.mjs
  • tests/hooks/recording.test.mjs
  • tests/hooks/semantic-harvest-clients.test.mjs
  • tests/unit/manifest-versions-track-package.test.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Summary by CodeRabbit

  • New Features
    • Added a default 18-tool profile, with an optional full 98-tool catalog.
    • Added end-of-session prompts to capture durable conclusions after substantive edits.
    • Added post-edit and session-stop support across integrations.
  • Bug Fixes
    • Improved diagnostics when optional credentials or fallback harvesting are unavailable.
    • Added clearer rejection messages for tools outside the active profile.
  • Documentation
    • Updated setup, support, and workflow guidance for Node.js 22+ and tool profiles.
  • Chores
    • Updated CI and validation workflows for newer Node.js versions.

Walkthrough

The changes add semantic harvesting to client hooks, configurable MCP tool profiles, live-graph verification, Node.js 22 support, updated Codex plugin wiring, and related documentation and tests.

Changes

Semantic harvesting hooks

Layer / File(s) Summary
Adapter behavior and recording
hooks-core/*, plugin/hooks/lib/*, integrations/*/hooks/lib/*
Adapters track substantive edits, emit recording context, and process guarded Stop events.
Lifecycle wiring and validation
integrations/codex/hooks/*, integrations/codex/plugin/hooks/*, scripts/generate-client-entries.mjs, scripts/verify-client-configs.mjs, tests/hooks/*, tests/integration/client-hooks.test.ts
Codex PostToolUse and Stop hooks are wired and tested across standalone and plugin installations.
Harvest diagnostics
hooks-core/doctor.mjs, integrations/*/hooks/lib/doctor.mjs, tests/unit/doctor-reports-harvest-state.test.ts
Diagnostics treat active-model wiki_write as primary and fallback extraction as optional.

MCP tool profiles

Layer / File(s) Summary
Profile selection and server enforcement
src/server/tool-profile.ts, src/server/index.ts, tests/unit/required-fields-enforced.test.ts
The server supports core and full profiles and rejects calls outside the active profile.
Profile integration tests and documentation
tests/integration/mcp-tool-profiles.test.ts, README.md, docs/CLIENT_SUPPORT.md
Tests cover the default 18-tool catalog, the 98-tool full catalog, invalid profiles, and unavailable tools.

Live graph and release verification

Layer / File(s) Summary
End-to-end live-graph verification
scripts/verify-live-graph-flow.mjs, package.json, .github/workflows/quality-gates.yml
A production-path verifier checks writes, retrieval, telemetry, dashboard APIs, provenance, balance, and cleanup.
Release metadata tracking
release-please-config.json, integrations/codex/plugin/.codex-plugin/plugin.json, tests/unit/manifest-versions-track-package.test.ts
Release automation and tests track the Codex plugin manifest version.

Node.js 22 platform update

Layer / File(s) Summary
Runtime and CI
package.json, .github/workflows/*
The minimum Node.js version is 22. CI jobs and the test matrix use Node.js 22 and newer configured versions.
Documentation and status checks
README.md, .github/*.md, docs/*, integrations/README.md
Prerequisites, runtime references, and required status checks now document Node.js 22.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Agent
  participant PostToolHook
  participant HookAdapter
  participant StopHook
  participant MCPServer
  Agent->>PostToolHook: Complete an editing tool call
  PostToolHook->>HookAdapter: Record edited files
  HookAdapter-->>Agent: Return recording guidance
  Agent->>StopHook: End the session
  StopHook->>HookAdapter: Request semantic harvest
  HookAdapter->>MCPServer: Prompt active model to call wiki_write
  MCPServer-->>Agent: Return harvest result or completion guidance
Loading

Poem

I’m a rabbit with notes in a bright little file,
Hooks gather conclusions and hop every mile.
Node twenty-two keeps the pipeline in tune,
Core tools stay lean; full tools bloom like the moon.
Stop once, write wisely, then safely resume—
Thump, test, and ship with a knowledge-filled room!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description is detailed but omits most required template sections, including Type of Change, Related Issues, Checklist, and Commit Message Convention. Complete the repository template and mark applicable type, testing, checklist, commit convention, performance, and issue fields.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary changes: completing live-graph integration and reducing MCP context.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/live-graph-full-system

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark Results

(node:2428) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/benchmarks/performance.bench.ts (5.782 s)
  Performance Benchmarks
    Token Counting Benchmarks
      ✓ should benchmark small text token counting (147 ms)
      ✓ should benchmark medium text token counting (286 ms)
      ✓ should benchmark large text token counting (1045 ms)
      ✓ should benchmark batch token counting (272 ms)
      ✓ should benchmark token estimation (2 ms)
    Compression Benchmarks
      ✓ should benchmark small text compression (306 ms)
      ✓ should benchmark medium text compression (169 ms)
      ✓ should benchmark large text compression (106 ms)
      ✓ should benchmark decompression (15 ms)
      ✓ should benchmark base64 encoding (188 ms)
      ✓ should benchmark compression quality levels (94 ms)
    Cache Operations Benchmarks
      ✓ should benchmark cache write operations (82 ms)
      ✓ should benchmark cache read operations (memory) (71 ms)
      ✓ should benchmark cache read operations (disk) (43 ms)
      ✓ should benchmark cache delete operations (139 ms)
      ✓ should benchmark cache stats retrieval (128 ms)
    Metrics Collection Benchmarks
      ✓ should benchmark metric recording (2 ms)
      ✓ should benchmark cache stats calculation (38 ms)
      ✓ should benchmark operation breakdown (94 ms)
      ✓ should benchmark percentiles calculation (13 ms)
    End-to-End Workflow Benchmarks
      ✓ should benchmark complete optimization cycle (113 ms)
      ✓ should benchmark cache hit path (93 ms)
    Memory Usage Benchmarks
      ✓ should track memory usage for large cache (398 ms)
      ✓ should track memory usage for compression (735 ms)
    Regression Detection
      ✓ should detect performance regression in token counting (167 ms)
      ✓ should detect performance regression in compression (132 ms)
      ✓ should detect performance regression in cache operations (75 ms)
    Token Reduction Metrics
      ✓ should measure token reduction for repetitive content (2 ms)
      ✓ should measure token reduction for code (3 ms)
      ✓ should measure token reduction for JSON (4 ms)

Test Suites: 1 passed, 1 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        6.019 s
Ran all test suites matching tests/benchmarks.

@ooples
ooples marked this pull request as ready for review August 9, 2026 04:51
Copilot AI lite review requested due to automatic review settings August 9, 2026 04:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Commit Message Format Issue

Your commit messages don't follow the Conventional Commits specification.

Required Format:

<type>(<optional scope>): <description>

[optional body]

[optional footer]

Valid Types:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that don't affect code meaning (white-space, formatting)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

Examples:

feat(auth): add OAuth2 authentication
fix(api): resolve race condition in token refresh
docs(readme): update installation instructions
refactor(core): simplify token optimization logic

Breaking Changes:

Add BREAKING CHANGE: in the footer or append ! after the type:

feat!: remove deprecated API endpoints

Please amend your commit messages to follow this format.

Learn more: Conventional Commits

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark Results

(node:2450) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/benchmarks/performance.bench.ts (5.759 s)
  Performance Benchmarks
    Token Counting Benchmarks
      ✓ should benchmark small text token counting (145 ms)
      ✓ should benchmark medium text token counting (303 ms)
      ✓ should benchmark large text token counting (1101 ms)
      ✓ should benchmark batch token counting (288 ms)
      ✓ should benchmark token estimation (2 ms)
    Compression Benchmarks
      ✓ should benchmark small text compression (299 ms)
      ✓ should benchmark medium text compression (165 ms)
      ✓ should benchmark large text compression (105 ms)
      ✓ should benchmark decompression (15 ms)
      ✓ should benchmark base64 encoding (187 ms)
      ✓ should benchmark compression quality levels (91 ms)
    Cache Operations Benchmarks
      ✓ should benchmark cache write operations (82 ms)
      ✓ should benchmark cache read operations (memory) (68 ms)
      ✓ should benchmark cache read operations (disk) (42 ms)
      ✓ should benchmark cache delete operations (134 ms)
      ✓ should benchmark cache stats retrieval (125 ms)
    Metrics Collection Benchmarks
      ✓ should benchmark metric recording (2 ms)
      ✓ should benchmark cache stats calculation (39 ms)
      ✓ should benchmark operation breakdown (84 ms)
      ✓ should benchmark percentiles calculation (12 ms)
    End-to-End Workflow Benchmarks
      ✓ should benchmark complete optimization cycle (115 ms)
      ✓ should benchmark cache hit path (93 ms)
    Memory Usage Benchmarks
      ✓ should track memory usage for large cache (397 ms)
      ✓ should track memory usage for compression (725 ms)
    Regression Detection
      ✓ should detect performance regression in token counting (175 ms)
      ✓ should detect performance regression in compression (128 ms)
      ✓ should detect performance regression in cache operations (72 ms)
    Token Reduction Metrics
      ✓ should measure token reduction for repetitive content (2 ms)
      ✓ should measure token reduction for code (3 ms)
      ✓ should measure token reduction for JSON (4 ms)

Test Suites: 1 passed, 1 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        5.986 s
Ran all test suites matching tests/benchmarks.

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

🧹 Nitpick comments (1)
tests/hooks/recording.test.mjs (1)

108-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the default branches of semanticHarvestPrompt.

The tests cover the model-supplied path and both null paths. Two branches stay uncovered: the fallback actor text when model is empty, and the empty subject when files is empty.

🧪 Proposed additional test
   test('does not invent work or loop after Codex already continued Stop', () => {
     expect(semanticHarvestPrompt({ edits: 0 })).toBeNull();
     expect(semanticHarvestPrompt({ edits: 5, stopHookActive: true })).toBeNull();
   });
+
+  test('names no model and no files when neither is reported', () => {
+    const out = semanticHarvestPrompt({ edits: 1 });
+
+    expect(out).toMatch(/the active model that did the reasoning/i);
+    expect(out).not.toMatch(/Work touched/);
+  });
 });
🤖 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/hooks/recording.test.mjs` around lines 108 - 127, Extend the
`semanticHarvestPrompt` tests with a case using an empty `model` to assert the
fallback actor text, and an empty `files` array to assert the prompt’s empty
`subject` behavior. Keep the existing model-supplied and null-path coverage
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/RELEASE_FLOW.md:
- Line 46: Synchronize the CI documentation with the current workflow matrix and
check names: in .github/RELEASE_FLOW.md lines 46-46, list Node.js 22, 24, 25,
and 26; in .github/BRANCH_PROTECTION.md lines 36-36, replace stale required
checks with exact “Test (Node <version>)” names or document “All Checks Passed”
if that is the intended gate; apply the same required-check update in
.github/CI_CD.md lines 255-255 and .github/RELEASE_FLOW.md lines 236-236,
removing Node.js 20 and the duplicate Node.js 22 entry at both latter sites.

In `@integrations/codex/plugin/hooks/lib/adapter.mjs`:
- Around line 232-257: Gate the persistent edit tracking in the post-tool
mutation handling so state.edits and state.editedFiles are updated only after
the tool result confirms a successful file mutation; preserve the existing
recordingContext behavior for confirmed edits. Apply this change in
integrations/codex/plugin/hooks/lib/adapter.mjs lines 232-257 and
integrations/gemini/hooks/lib/adapter.mjs lines 232-257, keeping their hook
behavior equivalent. Add a failed-mutation regression test that verifies the
subsequent Stop event emits {}.

In `@integrations/qwen/hooks/lib/adapter.mjs`:
- Around line 200-212: Update the shared hook core’s loadState/saveState flow to
persist a harvested marker, then in the Stop handler set and save
state.harvested when a prompt is emitted and pass raw.stop_hook_active === true
|| state.harvested === true as stopHookActive. Apply this in
integrations/qwen/hooks/lib/adapter.mjs lines 200-212 and
integrations/opencode/hooks/lib/adapter.mjs lines 200-212; regenerate
plugin/hooks/lib/adapter.mjs lines 200-212 with scripts/sync-hook-core.mjs
rather than editing it manually.

In `@scripts/verify-client-configs.mjs`:
- Around line 240-255: Update the validation around parsed.hooks and command
extraction to require each required lifecycle event—SessionStart, PreToolUse,
PostToolUse, and Stop—to contain a non-empty group array, with every group
containing a non-empty hooks array. Perform these structural checks before
extracting commands, and ensure invalid or empty groups cannot pass the
hook-command and referenced-script validations.

In `@tests/unit/manifest-versions-track-package.test.ts`:
- Around line 70-74: Add the Claude plugin manifest version entry to the targets
returned by targets(), using plugin/.claude-plugin/plugin.json and $.version
consistently with the existing manifest targets. Update the minimum target-count
assertion to require six targets.

---

Nitpick comments:
In `@tests/hooks/recording.test.mjs`:
- Around line 108-127: Extend the `semanticHarvestPrompt` tests with a case
using an empty `model` to assert the fallback actor text, and an empty `files`
array to assert the prompt’s empty `subject` behavior. Keep the existing
model-supplied and null-path coverage unchanged.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d6e21c8e-d732-41df-ae15-f1f99ce77269

📥 Commits

Reviewing files that changed from the base of the PR and between 5491652 and 20f7bea.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (56)
  • .github/BRANCH_PROTECTION.md
  • .github/CI_CD.md
  • .github/QUICKSTART.md
  • .github/RELEASE_FLOW.md
  • .github/workflows/ci.yml
  • .github/workflows/codex-autofix.yml
  • .github/workflows/commitlint.yml
  • .github/workflows/quality-gates.yml
  • README.md
  • docs/CLIENT_SUPPORT.md
  • docs/NPM_TRUSTED_PUBLISHER_SETUP.md
  • docs/QUICK_START_GUIDE.md
  • hooks-core/adapter.mjs
  • hooks-core/doctor.mjs
  • hooks-core/recording.mjs
  • integrations/README.md
  • integrations/codex/hooks/hooks.json
  • integrations/codex/hooks/lib/adapter.mjs
  • integrations/codex/hooks/lib/doctor.mjs
  • integrations/codex/hooks/lib/recording.mjs
  • integrations/codex/hooks/post-tool.mjs
  • integrations/codex/hooks/stop.mjs
  • integrations/codex/plugin/.codex-plugin/plugin.json
  • integrations/codex/plugin/.mcp.json
  • integrations/codex/plugin/hooks/hooks.json
  • integrations/codex/plugin/hooks/lib/adapter.mjs
  • integrations/codex/plugin/hooks/lib/doctor.mjs
  • integrations/codex/plugin/hooks/lib/recording.mjs
  • integrations/codex/plugin/hooks/post-tool.mjs
  • integrations/codex/plugin/hooks/stop.mjs
  • integrations/gemini/hooks/lib/adapter.mjs
  • integrations/gemini/hooks/lib/doctor.mjs
  • integrations/gemini/hooks/lib/recording.mjs
  • integrations/opencode/hooks/lib/adapter.mjs
  • integrations/opencode/hooks/lib/doctor.mjs
  • integrations/opencode/hooks/lib/recording.mjs
  • integrations/qwen/hooks/lib/adapter.mjs
  • integrations/qwen/hooks/lib/doctor.mjs
  • integrations/qwen/hooks/lib/recording.mjs
  • package.json
  • plugin/hooks/lib/adapter.mjs
  • plugin/hooks/lib/doctor.mjs
  • plugin/hooks/lib/recording.mjs
  • release-please-config.json
  • scripts/generate-client-entries.mjs
  • scripts/verify-client-configs.mjs
  • scripts/verify-live-graph-flow.mjs
  • src/server/index.ts
  • src/server/tool-profile.ts
  • tests/hooks/codex-semantic-harvest.test.mjs
  • tests/hooks/recording.test.mjs
  • tests/integration/client-hooks.test.ts
  • tests/integration/mcp-tool-profiles.test.ts
  • tests/unit/doctor-reports-harvest-state.test.ts
  • tests/unit/manifest-versions-track-package.test.ts
  • tests/unit/required-fields-enforced.test.ts

Comment thread .github/RELEASE_FLOW.md
Comment thread integrations/codex/plugin/hooks/lib/adapter.mjs Outdated
Comment thread integrations/qwen/hooks/lib/adapter.mjs
Comment thread scripts/verify-client-configs.mjs Outdated
Comment thread tests/unit/manifest-versions-track-package.test.ts
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Commit Message Format Issue

Your commit messages don't follow the Conventional Commits specification.

Required Format:

<type>(<optional scope>): <description>

[optional body]

[optional footer]

Valid Types:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that don't affect code meaning (white-space, formatting)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

Examples:

feat(auth): add OAuth2 authentication
fix(api): resolve race condition in token refresh
docs(readme): update installation instructions
refactor(core): simplify token optimization logic

Breaking Changes:

Add BREAKING CHANGE: in the footer or append ! after the type:

feat!: remove deprecated API endpoints

Please amend your commit messages to follow this format.

Learn more: Conventional Commits

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark Results

(node:2217) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/benchmarks/performance.bench.ts
  Performance Benchmarks
    Token Counting Benchmarks
      ✓ should benchmark small text token counting (91 ms)
      ✓ should benchmark medium text token counting (163 ms)
      ✓ should benchmark large text token counting (590 ms)
      ✓ should benchmark batch token counting (152 ms)
      ✓ should benchmark token estimation (1 ms)
    Compression Benchmarks
      ✓ should benchmark small text compression (208 ms)
      ✓ should benchmark medium text compression (113 ms)
      ✓ should benchmark large text compression (75 ms)
      ✓ should benchmark decompression (9 ms)
      ✓ should benchmark base64 encoding (132 ms)
      ✓ should benchmark compression quality levels (62 ms)
    Cache Operations Benchmarks
      ✓ should benchmark cache write operations (189 ms)
      ✓ should benchmark cache read operations (memory) (74 ms)
      ✓ should benchmark cache read operations (disk) (84 ms)
      ✓ should benchmark cache delete operations (216 ms)
      ✓ should benchmark cache stats retrieval (65 ms)
    Metrics Collection Benchmarks
      ✓ should benchmark metric recording (1 ms)
      ✓ should benchmark cache stats calculation (29 ms)
      ✓ should benchmark operation breakdown (61 ms)
      ✓ should benchmark percentiles calculation (7 ms)
    End-to-End Workflow Benchmarks
      ✓ should benchmark complete optimization cycle (83 ms)
      ✓ should benchmark cache hit path (293 ms)
    Memory Usage Benchmarks
      ✓ should track memory usage for large cache (268 ms)
      ✓ should track memory usage for compression (490 ms)
    Regression Detection
      ✓ should detect performance regression in token counting (96 ms)
      ✓ should detect performance regression in compression (86 ms)
      ✓ should detect performance regression in cache operations (70 ms)
    Token Reduction Metrics
      ✓ should measure token reduction for repetitive content (1 ms)
      ✓ should measure token reduction for code (2 ms)
      ✓ should measure token reduction for JSON (3 ms)

Test Suites: 1 passed, 1 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        4.996 s
Ran all test suites matching tests/benchmarks.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Commit Message Format Issue

Your commit messages don't follow the Conventional Commits specification.

Required Format:

<type>(<optional scope>): <description>

[optional body]

[optional footer]

Valid Types:

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that don't affect code meaning (white-space, formatting)
  • refactor: Code change that neither fixes a bug nor adds a feature
  • perf: Code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

Examples:

feat(auth): add OAuth2 authentication
fix(api): resolve race condition in token refresh
docs(readme): update installation instructions
refactor(core): simplify token optimization logic

Breaking Changes:

Add BREAKING CHANGE: in the footer or append ! after the type:

feat!: remove deprecated API endpoints

Please amend your commit messages to follow this format.

Learn more: Conventional Commits

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark Results

(node:2534) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/benchmarks/performance.bench.ts (5.68 s)
  Performance Benchmarks
    Token Counting Benchmarks
      ✓ should benchmark small text token counting (148 ms)
      ✓ should benchmark medium text token counting (286 ms)
      ✓ should benchmark large text token counting (1031 ms)
      ✓ should benchmark batch token counting (270 ms)
      ✓ should benchmark token estimation (2 ms)
    Compression Benchmarks
      ✓ should benchmark small text compression (300 ms)
      ✓ should benchmark medium text compression (164 ms)
      ✓ should benchmark large text compression (105 ms)
      ✓ should benchmark decompression (14 ms)
      ✓ should benchmark base64 encoding (184 ms)
      ✓ should benchmark compression quality levels (92 ms)
    Cache Operations Benchmarks
      ✓ should benchmark cache write operations (80 ms)
      ✓ should benchmark cache read operations (memory) (67 ms)
      ✓ should benchmark cache read operations (disk) (41 ms)
      ✓ should benchmark cache delete operations (129 ms)
      ✓ should benchmark cache stats retrieval (124 ms)
    Metrics Collection Benchmarks
      ✓ should benchmark metric recording (3 ms)
      ✓ should benchmark cache stats calculation (37 ms)
      ✓ should benchmark operation breakdown (89 ms)
      ✓ should benchmark percentiles calculation (12 ms)
    End-to-End Workflow Benchmarks
      ✓ should benchmark complete optimization cycle (110 ms)
      ✓ should benchmark cache hit path (91 ms)
    Memory Usage Benchmarks
      ✓ should track memory usage for large cache (384 ms)
      ✓ should track memory usage for compression (733 ms)
    Regression Detection
      ✓ should detect performance regression in token counting (166 ms)
      ✓ should detect performance regression in compression (127 ms)
      ✓ should detect performance regression in cache operations (76 ms)
    Token Reduction Metrics
      ✓ should measure token reduction for repetitive content (1 ms)
      ✓ should measure token reduction for code (3 ms)
      ✓ should measure token reduction for JSON (4 ms)

Test Suites: 1 passed, 1 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        5.907 s
Ran all test suites matching tests/benchmarks.

@ooples
ooples force-pushed the fix/live-graph-full-system branch from aa61de1 to 5cb4844 Compare August 9, 2026 11:00
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark Results

(node:2374) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
PASS tests/benchmarks/performance.bench.ts (5.73 s)
  Performance Benchmarks
    Token Counting Benchmarks
      ✓ should benchmark small text token counting (149 ms)
      ✓ should benchmark medium text token counting (283 ms)
      ✓ should benchmark large text token counting (1027 ms)
      ✓ should benchmark batch token counting (271 ms)
      ✓ should benchmark token estimation (2 ms)
    Compression Benchmarks
      ✓ should benchmark small text compression (305 ms)
      ✓ should benchmark medium text compression (166 ms)
      ✓ should benchmark large text compression (105 ms)
      ✓ should benchmark decompression (15 ms)
      ✓ should benchmark base64 encoding (184 ms)
      ✓ should benchmark compression quality levels (93 ms)
    Cache Operations Benchmarks
      ✓ should benchmark cache write operations (83 ms)
      ✓ should benchmark cache read operations (memory) (70 ms)
      ✓ should benchmark cache read operations (disk) (43 ms)
      ✓ should benchmark cache delete operations (133 ms)
      ✓ should benchmark cache stats retrieval (125 ms)
    Metrics Collection Benchmarks
      ✓ should benchmark metric recording (2 ms)
      ✓ should benchmark cache stats calculation (38 ms)
      ✓ should benchmark operation breakdown (90 ms)
      ✓ should benchmark percentiles calculation (13 ms)
    End-to-End Workflow Benchmarks
      ✓ should benchmark complete optimization cycle (112 ms)
      ✓ should benchmark cache hit path (94 ms)
    Memory Usage Benchmarks
      ✓ should track memory usage for large cache (397 ms)
      ✓ should track memory usage for compression (728 ms)
    Regression Detection
      ✓ should detect performance regression in token counting (168 ms)
      ✓ should detect performance regression in compression (131 ms)
      ✓ should detect performance regression in cache operations (75 ms)
    Token Reduction Metrics
      ✓ should measure token reduction for repetitive content (2 ms)
      ✓ should measure token reduction for code (3 ms)
      ✓ should measure token reduction for JSON (4 ms)

Test Suites: 1 passed, 1 total
Tests:       30 passed, 30 total
Snapshots:   0 total
Time:        5.963 s
Ran all test suites matching tests/benchmarks.

@ooples
ooples merged commit 00e9f04 into master Aug 9, 2026
23 checks passed
@ooples
ooples deleted the fix/live-graph-full-system branch August 9, 2026 12:00
ooples added a commit that referenced this pull request Aug 26, 2026
Closes #322.

#300 unregistered `plugin/hooks/stop-harvest.mjs` and nothing replaced it, so
four capabilities went silently: the transcript archive, the harvest worker
spawn, refusal detection, and the notice that says why no findings exist. On a
full session with a valid credential, `harvestMode()` reports `remote`, the
user has consented by not opting out, and nothing happens.

THE ISSUE UNDERSTATES IT, and the correction matters for the fix. It says the
other ten adapter clients are unaffected because they reach the harvest through
the shared adapter path. They do not: `grep` finds exactly one spawner of
`harvest-worker.mjs` in the repository, and it is the unregistered file. Every
client routes Stop to its own generated entry, which routes to the adapter, and
the adapter's stop branch only ever built the IN-BAND prompt asking the model to
call `wiki_write` -- the very thing the design says the out-of-band harvest
exists to avoid, since it makes the session pay for summarising itself. So the
out-of-band harvest was dead everywhere, on all eleven clients. Qwen's Stop
entry is even named `token-optimizer-semantic-harvest`.

The archive is dead on master too, not restored: `archive()` has one caller and
it is the same unregistered file.

MOVED TO THE SHARED CORE rather than re-registered on one client.
`stop-harvest.mjs` becomes `hooks-core/stop-harvest.mjs`, exporting
`runStopHarvest(payload)`, and the adapter's stop branch awaits it. Fixing only
Claude Code would have left ten clients with the same dead feature.

AND THE WORKER MOVED WITH IT, which is the part that made a shared fix possible
at all: `harvest-worker.mjs` lived in `plugin/hooks/` and is vendored to no
other client, so even a correctly registered Stop on the other ten would have
found no worker to spawn. It now sits in the core every client receives an
identical copy of, and the adapter resolves it as a sibling.

THE NOTICE IS RETURNED, NOT WRITTEN. The old hook wrote its own JSON to stdout
beside whatever the hook emitted; two writers on one hook's stdout is how a
Stop payload gets corrupted. The adapter folds it into the single object it
emits, so the one line explaining why no findings exist can no longer be
dropped by a full pipe or truncated by an exit.

WHY IT SURVIVED, and the guard that now stops it. Nothing observed that the
harvest had not run: there is no `kind:'harvest'` event to be absent from a
report, no error, and a harvest that ran and found nothing looks exactly like
one that never started. The reachability guard could not see it either -- the
unregistered file's own imports gave `archive`, `detectRefusals` and
`recordRefusal` their call sites, and a name-based scan proves a REFERENCE
exists, never that it RUNS, which is the limitation #323 documented. The
existing marker suite even went on passing, because it spawned the removed hook
directly; it now drives the registered entry.

Nine assertions in tests/hooks/stop-reaches-the-harvest.test.mjs, all through
the file the host is actually configured to run, and both defects are
mutation-tested: unwiring the adapter call fails four of them, and deleting
Stop from hooks.json -- the #300 defect itself -- fails one.

221 suites, 2706 tests, 0 failed. tsc, eslint, prettier, sync:hooks:check and
validate clean; verify:clients, :certification, :harvest, :live-graph, :ui and
:interactions all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSFxBpREeR7mMkJXYb6wuX
ooples pushed a commit that referenced this pull request Aug 26, 2026
…lt honestly

`derive` was already wired into the shared Stop branch by Task 4, and
`plugin/hooks/stop.mjs` is a generated entry into it -- so Claude Code was
reachable. Running the real hook binary showed reachable was not enough: a real
session derived 0 candidates and stored 0 findings, for three measured reasons.

RESTORE THE TRANSCRIPT ARCHIVE. #300 replaced `stop-harvest.mjs` with the
generated `stop.mjs` in `hooks.json`, and everything the old entry did was lost
silently -- the archive among it. `readArchive` returning [] is indistinguishable
from a session with nothing in it, so no Claude Code session has been archived
since. `derive`'s correction detector reads the archive first, and `lessons.mjs`
can verify a verbatim quote against nothing else. Restored before `derive`, in
its own fail-open try. Verified: 723 turns archived from a real transcript, and a
real transcript containing corrections now stores 6 findings through the Claude
Code Stop hook on a cold graph.

STANDING RULE. One line in `standingRules`' heading, charged inside the same
400-token budget: a model reading a list of standing rules has no way to add to
it unless the block says how one is made. Fixed overhead with one rule: 60
estimated tokens.

SURFACE THE LOCAL ENDPOINT. `doctor` now says "local model found -- semantic
harvest is on, free and private" and, on `off:no-key`, names the trade rather
than the absence: `derive` needs no credential, a local endpoint runs the harvest
free and private, a credential runs it from a bounded digest and never file
contents. A mode-derived sentence joins the SessionStart policy, addressed to the
model -- "a conclusion you do not record is not written down in words anywhere"
changes what it does with one. Cache-safe in all four modes.

STATE THE DEFAULT HONESTLY. README and WIKI_GRAPH: a default install produces the
structural graph plus locally derived findings. The model-based harvest is NOT
opt-in -- `TOKEN_OPTIMIZER_HARVEST=0` turns it off -- its real gate is a
credential, and a local endpoint runs it free and private.

Two of the new tests passed for the wrong reason and were rewritten: a budget
test a five-line paragraph survived, and a "no digits" test that `volatileLines`
never checked for. 17 mutations, each killing only its intended tests; the
pre-existing kill sets still discriminate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ooples pushed a commit that referenced this pull request Aug 26, 2026
`derive` was already wired into the shared Stop branch by Task 4, and
`plugin/hooks/stop.mjs` is a generated entry into it -- so Claude Code was
reachable. Running the real hook binary showed reachable was not enough: a real
session derived 0 candidates and stored 0 findings, for three measured reasons.

RESTORE THE TRANSCRIPT ARCHIVE. #300 replaced `stop-harvest.mjs` with the
generated `stop.mjs` in `hooks.json`, and everything the old entry did was lost
silently -- the archive among it. `readArchive` returning [] is indistinguishable
from a session with nothing in it, so no Claude Code session has been archived
since. `derive`'s correction detector reads the archive first, and `lessons.mjs`
can verify a verbatim quote against nothing else. Restored before `derive`, in
its own fail-open try. Verified: 723 turns archived from a real transcript, and a
real transcript containing corrections now stores 6 findings through the Claude
Code Stop hook on a cold graph.

STANDING RULE. One line in `standingRules`' heading, charged inside the same
400-token budget: a model reading a list of standing rules has no way to add to
it unless the block says how one is made. Fixed overhead with one rule: 60
estimated tokens.

SURFACE THE LOCAL ENDPOINT. `doctor` now says "local model found -- semantic
harvest is on, free and private" and, on `off:no-key`, names the trade rather
than the absence: `derive` needs no credential, a local endpoint runs the harvest
free and private, a credential runs it from a bounded digest and never file
contents. A mode-derived sentence joins the SessionStart policy, addressed to the
model -- "a conclusion you do not record is not written down in words anywhere"
changes what it does with one. Cache-safe in all four modes.

STATE THE DEFAULT HONESTLY. README and WIKI_GRAPH: a default install produces the
structural graph plus locally derived findings. The model-based harvest is NOT
opt-in -- `TOKEN_OPTIMIZER_HARVEST=0` turns it off -- its real gate is a
credential, and a local endpoint runs it free and private.

Two of the new tests passed for the wrong reason and were rewritten: a budget
test a five-line paragraph survived, and a "no digits" test that `volatileLines`
never checked for. 17 mutations, each killing only its intended tests; the
pre-existing kill sets still discriminate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ooples added a commit that referenced this pull request Aug 26, 2026
* fix(harvest): run the semantic harvest on the shared stop path

Closes #322.

#300 unregistered `plugin/hooks/stop-harvest.mjs` and nothing replaced it, so
four capabilities went silently: the transcript archive, the harvest worker
spawn, refusal detection, and the notice that says why no findings exist. On a
full session with a valid credential, `harvestMode()` reports `remote`, the
user has consented by not opting out, and nothing happens.

THE ISSUE UNDERSTATES IT, and the correction matters for the fix. It says the
other ten adapter clients are unaffected because they reach the harvest through
the shared adapter path. They do not: `grep` finds exactly one spawner of
`harvest-worker.mjs` in the repository, and it is the unregistered file. Every
client routes Stop to its own generated entry, which routes to the adapter, and
the adapter's stop branch only ever built the IN-BAND prompt asking the model to
call `wiki_write` -- the very thing the design says the out-of-band harvest
exists to avoid, since it makes the session pay for summarising itself. So the
out-of-band harvest was dead everywhere, on all eleven clients. Qwen's Stop
entry is even named `token-optimizer-semantic-harvest`.

The archive is dead on master too, not restored: `archive()` has one caller and
it is the same unregistered file.

MOVED TO THE SHARED CORE rather than re-registered on one client.
`stop-harvest.mjs` becomes `hooks-core/stop-harvest.mjs`, exporting
`runStopHarvest(payload)`, and the adapter's stop branch awaits it. Fixing only
Claude Code would have left ten clients with the same dead feature.

AND THE WORKER MOVED WITH IT, which is the part that made a shared fix possible
at all: `harvest-worker.mjs` lived in `plugin/hooks/` and is vendored to no
other client, so even a correctly registered Stop on the other ten would have
found no worker to spawn. It now sits in the core every client receives an
identical copy of, and the adapter resolves it as a sibling.

THE NOTICE IS RETURNED, NOT WRITTEN. The old hook wrote its own JSON to stdout
beside whatever the hook emitted; two writers on one hook's stdout is how a
Stop payload gets corrupted. The adapter folds it into the single object it
emits, so the one line explaining why no findings exist can no longer be
dropped by a full pipe or truncated by an exit.

WHY IT SURVIVED, and the guard that now stops it. Nothing observed that the
harvest had not run: there is no `kind:'harvest'` event to be absent from a
report, no error, and a harvest that ran and found nothing looks exactly like
one that never started. The reachability guard could not see it either -- the
unregistered file's own imports gave `archive`, `detectRefusals` and
`recordRefusal` their call sites, and a name-based scan proves a REFERENCE
exists, never that it RUNS, which is the limitation #323 documented. The
existing marker suite even went on passing, because it spawned the removed hook
directly; it now drives the registered entry.

Nine assertions in tests/hooks/stop-reaches-the-harvest.test.mjs, all through
the file the host is actually configured to run, and both defects are
mutation-tested: unwiring the adapter call fails four of them, and deleting
Stop from hooks.json -- the #300 defect itself -- fails one.

221 suites, 2706 tests, 0 failed. tsc, eslint, prettier, sync:hooks:check and
validate clean; verify:clients, :certification, :harvest, :live-graph, :ui and
:interactions all pass.

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

* fix(harvest): keep the vendored worker parseable, and normalise the stop payload

Four review findings, all valid. One was critical and would have shipped this
pull request as a no-op.

THE HASHBANG. `sync-hook-core` prepends its GENERATED banner, which pushed
harvest-worker's `#!/usr/bin/env node` to line 3 -- and Node accepts a hashbang
only at byte zero. Every vendored copy was a SYNTAX ERROR. The worker is
spawned detached with stdio ignored, so it would have failed instantly and
silently: the harvest still not running, for exactly the reason it was not
running before, with a green suite on either side of it. Verified with `node
--check` on the generated file before the fix.

Fixed in the generator rather than by deleting the hashbang, because the next
executable added to the core would hit it again and the failure mode is
invisible by construction. `withBanner` keeps a leading hashbang on line one
and puts the banner after it.

AND THE ASSERTION WHOSE ABSENCE ALLOWED IT. This branch's own test checked that
the vendored worker EXISTS -- and it said so while every copy was unparseable.
Existence is not executability, which is this pull request's whole point
applied to its own output. Every vendored core file is now `node --check`ed in
three client directories, and the hashbang property is pinned separately.
Mutation-tested: restoring banner-before-hashbang fails both.

THE STOP PAYLOAD. The adapter resolves `sessionId` and `cwd` through every
alias a host might use -- `sessionId`, `conversation_id`, `working_directory`
-- and then handed `raw` to `runStopHarvest`, which reads `session_id` and
`cwd` only. On a client sending an alias the session id came back undefined and
the archive root fell back to `process.cwd()`: the once-per-session notice and
the debounce marker shared across every session, and transcripts filed under
whatever directory the hook happened to start in. A normalised payload is
passed now, and `cwd` is hoisted out of the try block below so both halves of
the hook reason about one resolved directory.

THE SPAWN. `spawn` reports a failed start through an asynchronous `error`
event, and an unlistened one is raised rather than returned -- which would
abort the Stop response itself. A harvest that cannot start must degrade to no
harvest, never to a broken Stop. `child.on('error', () => {})` after unref.

221 suites, 2708 tests, 0 failed. tsc, eslint, prettier, sync:hooks:check and
validate clean; all six verify gates pass.

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

* test(harvest): check every vendored copy, and pin the banner to line two

Two review findings, both about the guard this branch added rather than the
code it guards.

PARTIAL COVERAGE READ AS COVERAGE. The parse check looked at three of the
eleven vendored directories and the hashbang check at two, so a broken
generated file in any other client stayed invisible -- which is the same
silence the guard was written to break. The directories are now DISCOVERED
rather than listed: any directory named `lib` holding an `adapter.mjs` is a
vendored core by construction, so a twelfth client is covered the day it is
added rather than the day somebody remembers. A floor of eleven guards a
walk that finds nothing, which would pass every assertion below forever.

Widening it needed it to get faster, not slower. Spawning `node --check` per
file cost 7.5s for three directories and would have cost half a minute for
eleven; `vm.SourceTextModule` performs the same parse in process, without
evaluating, and covers all eleven in 300ms.

THE BANNER ASSERTION WAS `toContain`, which passes wherever the banner happens
to land in the file -- including below the hashbang, which is the exact layout
the test exists to reject. It now asserts the second line. Trimmed per line,
because the vendored copies carry CRLF on Windows and the assertion is about
which line the banner is on rather than about endings.

Mutation-tested against a client the previous version never looked at: moving
the hashbang to line three in integrations/kilo fails both checks, and
displacing the banner in integrations/windsurf fails the layout one.

223 suites, 2737 tests, 0 failed. tsc, eslint, prettier and sync:hooks:check
clean.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ooples added a commit that referenced this pull request Aug 26, 2026
… 2 of 3) (#328)

* feat(derive): mandatory redaction for claims derived from captured output

* fix(derive): isolate secret redaction tests and assert the null guard

Fix round 1 on the redact.mjs task per coordinator review:

- Split the bearer-token and sk-/pk-/ghp- prefix tests so each is triggered
  by a fixture that only the pattern under test can match (the original
  shared token satisfied both patterns at once, so disabling either alone
  left the suite green).
- Added explicit catch/preserve tests for the PEM private-key pattern,
  previously verified only by hand.
- Asserted redact(null) and redact(undefined) return '', not just that they
  don't throw -- without the `?? ''` guard they'd silently become the
  literal strings "null"/"undefined", a garbage claim worse than a thrown
  error because nothing would surface it.
- Documented the redact-then-truncate ordering invariant and the null-guard
  rationale directly in hooks-core/redact.mjs so a later regex/formatting
  tweak can't quietly "simplify" either one away.

Re-ran the full mutation matrix: each of the 7 mutations (5 patterns, the
truncation off-by-one, the null guard) now kills exactly one test.

* feat(capture): carry output and exit code on tool-outcome

Only a success boolean was recorded, which cannot distinguish a compile error
from a test failure from a denied permission.

Extends the existing tool-outcome event rather than adding a parallel kind:
that pipeline already runs (234 live events in this repo) and already joins
each outcome to its injection, so a second event describing the same thing
would have duplicated the join and given the overloaded kind field a fifth
meaning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(capture): read mcp error status and capture only failed output

Two corrections to the tool-outcome capture, both raised as concerns against
the brief and both confirmed.

toolSucceeded read no isError while src/server/index.ts sets it in seven
places, so every failed smart_edit was recorded as a success. That boolean
feeds episode outcomes and effectiveness accounting, so the bias ran in the
optimizer's own favour -- a measurement that flatters the thing being measured.
A differential over 16 clients x 5 envelopes x 13 response bodies (1088
verdicts) changes 96 of them, and every one is an isError:true payload going
true -> false. Nothing else moved on any client.

Output is now captured only when the call failed. The brief predates MCP tool
names reaching the post-tool path, so it never considered that a successful
smart_read would deposit 4 KB of file content into the evidence log on every
call. A failure claim quotes the error text; a "this works" claim needs only
success and exit, which are already recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(capture): read mcp error status in mutation accounting

mutationSucceeded read isError zero times while src/server/index.ts sets it in
seven places, so a failed MCP smart_write was still counted as a successful
mutation. Its allowlist returns true for claude-code, codex, qwen, opencode,
kilo and windsurf on any post-tool event with no error and no status, which is
exactly the shape of a failed smart_write. That inflates edit counts and
harvest pressure -- different consumers from the effectiveness figures, same
direction of bias, toward flattering the optimizer.

Extracted rather than copied: reportedMcpError is now the single definition of
what a failed MCP call looks like, shared with toolSucceeded, and
responseEnvelopes sits above both readers. The duplication was not theoretical
-- toolSucceeded listed four envelopes, mutationSucceeded listed three, and
neither mentioned isError.

The gate precedes the status read, the explicit-success read and the client
allowlist, all three of which would otherwise pass a failed write; the
function's own header already required that conservatism.

A differential over 16 clients x 5 envelopes x 18 response bodies plus 6
top-level shapes (1536 verdicts) changes 123, every one an isError:true payload
going true -> false, with zero changes not involving isError. The change is
monotone: it can only remove a mutation count, never add one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plans): make plans 2 and 3 self-sufficient for another machine

The plans were committed; the ledgers holding the corrections were not, and they
are gitignored. So a fresh session elsewhere would have followed a plan text that
is wrong in five specific places while a ledger it could not see knew better --
which is the same failure this whole effort exists to close, one level up.

Both plans now carry an "Execution state and corrections" section that is
authoritative over the task text above it: where execution stopped, what the plan
says that is no longer true, which Plan 1 surfaces each task will collide with,
the rulings that bind future tasks with their cost if wrong, and the verification
bar (mutate every test, confirm only it fails, confirm no prior kill set shrank).

Plan 3 additionally gains the three guard holes discovered after it was written --
an import specifier counting as a use, unread record fields being invisible, and
holdout-consulting tests needing to pin the arm -- plus the allowlist ownership
table showing which plan removes which of the remaining seven entries.

Both also carry the measurement-bias class: four defects found so far that would
each have inflated this project's own numbers in its own favour, none of which a
test suite would notice, because the code works and the number lies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(metrics): share re-read grouping between reread waste and derive

The churn detector needs per-anchor rows; rereadWaste returned only an
aggregate. One shared helper rather than two definitions of what counts as a
wasteful re-read. Existing aggregate fields are unchanged -- they are now a fold
over the same rows -- and `worst` is additive.

Rows are merged per anchor while GROUPING stays per session, so a file re-read
in two sessions is one row with two repeats rather than two rows, and a
cross-session pair is still not a repeat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(derive): three zero-cost extractors, so a default install produces findings

This repository's graph held 2,965 symbol nodes, 904 file nodes, 128 task nodes
and ONE finding, because every path to a verdict needed an API key this machine
does not have. derive.mjs needs none: it reads outcomes, transitions and
corrections already on disk and sends nothing anywhere, which makes it the only
finding producer on CI, a corporate laptop, or a subscription-only login.

Three detectors produce findings, not four. Churn describes our own reading
behaviour, not the code; filed as a content-dependent `map` it would take the
hottest files in the project as anchors, flip stale on edits to files it never
described, and compete for the injection budget with real findings while saying
nothing a future agent could act on. It returns as an observation instead -- the
measurement it already is on the balance sheet.

Precision is refusal, not a confidence number. Identical command text either
side of a failure emits nothing: "X succeeded where X failed" is incoherent and
"X fails" has just been disproved by the same pair, and no ceiling rescues a
claim whose content is wrong. Claims say "succeeded where", never "fixes" --
two ordered outcomes do not establish cause.

Wired at Stop in adapter.mjs, recording the candidate count. Storage is Task 4;
`written` is empty by design.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(derive): budget selection before storage and delete content anchor

selectForConsolidation had no caller, so nothing bounded what entered the
graph -- risk 2 (bloat) unaddressed. derive now routes every candidate through
it (1000 tokens, two per-command injection budgets' worth) and into
writeHarvested as ORIGIN_HARVESTED, with the hook payload's own session id so
the `answers` edge finally fires on a default install.

Three defects found on the way, each of which would have made this ship as a
no-op that reported success:

- selectForConsolidation scored `entry.summary` while every layer that STORES a
  finding calls the same text `claim` -- including consolidationRatio in the same
  file. So the carrying cost was 0 for every candidate, `spent + 0 > budget` was
  never true, and the budget admitted everything while reporting `tokens: 0`.
- Candidates anchored to the project ROOT, a directory. writeHarvested resolves
  anchors through indexFile, which cannot read a directory, so every candidate
  would have been refused and nothing stored. Now anchored to the file that
  declares the project's commands, which staleness.mjs already names as the right
  anchor for a command claim and which these types are never content-checked
  against.
- derive's own evidence boilerplate said "a flaky run explains the same pair",
  and irrecoverability word-matches /flak/ into its top tier -- so every command
  candidate scored as an irrecoverable observation on the strength of its own
  epistemic caveat. Same shape as the race/"stack trace" defect that function was
  already fixed for once.

derive stays synchronous: nothing under consolidate.mjs or harvest-write.mjs
imports it, so there is no cycle for a dynamic import to break, and making it
async would have changed every hook caller for nothing.

contentAnchor is deleted rather than left dormant; the idea remains in issue #319.
The reachability allowlist goes 7 -> 5: one entry left by being
wired, one by being deleted.

Also fixes a pre-existing fixture that proved nothing: `decisions are on the
floor too` passed with ALWAYS_KEEP emptied, because its finding never fit the
budget and the decision was kept by plain ranking.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(derive): archive at stop and surface local harvest honestly

`derive` was already wired into the shared Stop branch by Task 4, and
`plugin/hooks/stop.mjs` is a generated entry into it -- so Claude Code was
reachable. Running the real hook binary showed reachable was not enough: a real
session derived 0 candidates and stored 0 findings, for three measured reasons.

RESTORE THE TRANSCRIPT ARCHIVE. #300 replaced `stop-harvest.mjs` with the
generated `stop.mjs` in `hooks.json`, and everything the old entry did was lost
silently -- the archive among it. `readArchive` returning [] is indistinguishable
from a session with nothing in it, so no Claude Code session has been archived
since. `derive`'s correction detector reads the archive first, and `lessons.mjs`
can verify a verbatim quote against nothing else. Restored before `derive`, in
its own fail-open try. Verified: 723 turns archived from a real transcript, and a
real transcript containing corrections now stores 6 findings through the Claude
Code Stop hook on a cold graph.

STANDING RULE. One line in `standingRules`' heading, charged inside the same
400-token budget: a model reading a list of standing rules has no way to add to
it unless the block says how one is made. Fixed overhead with one rule: 60
estimated tokens.

SURFACE THE LOCAL ENDPOINT. `doctor` now says "local model found -- semantic
harvest is on, free and private" and, on `off:no-key`, names the trade rather
than the absence: `derive` needs no credential, a local endpoint runs the harvest
free and private, a credential runs it from a bounded digest and never file
contents. A mode-derived sentence joins the SessionStart policy, addressed to the
model -- "a conclusion you do not record is not written down in words anywhere"
changes what it does with one. Cache-safe in all four modes.

STATE THE DEFAULT HONESTLY. README and WIKI_GRAPH: a default install produces the
structural graph plus locally derived findings. The model-based harvest is NOT
opt-in -- `TOKEN_OPTIMIZER_HARVEST=0` turns it off -- its real gate is a
credential, and a local endpoint runs it free and private.

Two of the new tests passed for the wrong reason and were rewritten: a budget
test a five-line paragraph survived, and a "no digits" test that `volatileLines`
never checked for. 17 mutations, each killing only its intended tests; the
pre-existing kill sets still discriminate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plan): add task 11 transcript reader for failed tool results

Task 5 measured why the two highest-confidence detectors produced nothing:
Claude Code never fires PostToolUse for a failed tool call. Proved with a
deliberately failing command that emitted no event at all, and 2,238 of 2,238
live outcomes on the measuring machine were success: true.

So command failed-then-succeeded (0.90) and test/build red-to-green (0.85) have
no input on the primary client while working normally on the ten adapter
clients. This task feeds them from the one place those failures do exist -- the
local transcript archive the correction detector already reads.

The task's deliverable is a measurement rather than a claim, and it carries an
explicit stop condition: if failed results are not recoverable from the archive,
report that and halt, because saying so is worth more than an extractor that
finds nothing.

* feat(derive): read failed tool results from the transcript, and refuse the junk they turn out to be

Claude Code never fires PostToolUse for a failed tool call -- 2,238 of 2,238
live tool-outcome events carry success:true -- so derive's two strongest
detectors, command failed-then-succeeded (0.90) and test/build red-to-green
(0.85), had NO INPUT AT ALL on the primary client while working normally on the
ten adapter clients. This gives them the only source those failures exist in.

`failedResultsFromTranscript` (transcript.mjs) reads the raw transcript and
stores nothing: no capture path, no hook, no event kind, and no widening of
what leaves the machine. The plan's `failedResultsFromArchive(turns)` cannot
work -- `readTurns` drops tool results by design, so `readArchive` returns no
failure ever, and making the archive carry them would push command stderr into
`buildFeedbackDigest`, the one path that transmits anything.

The shape, measured rather than assumed: a `tool_result` block in a user
message with `is_error: true` and a bare string `content`, joined to the
command by `tool_use_id` -> the assistant's `tool_use.input.command`. Only
`Exit code N` is admitted, on a positive allowlist: of 214 real `is_error`
results just 83 were a command exiting non-zero, and the largest other group
(109) is THIS TOOL'S OWN PreToolUse denial text. Ingesting those would derive
0.90-confidence findings from its own advice and serve them back as
observations about the project.

Keys match across sources because the reader applies the same 120-character
truncation `recordToolOutcome` applies to `anchor`: on one real session that
made 266 of 266 attempt keys agree, where the untruncated text agreed on 240.
Deduplication is exact on `toolCallId`, which is the same string in both
sources, with identical-text-within-2s as the fallback for a client that
reports no call id.

Two refusals were then forced by measurement, and are why this ships at zero
rather than at four. The first wiring produced four candidates at the module's
highest ceiling, every one quoting a truncated fragment of a throwaway shell
script. So:

- `quotable` refuses a command containing a newline or at/over the 120-char cap.
  0 of 83 captured command failures across three transcripts are quotable; 8 of
  571 across all 163 transcripts on this machine (1.4%). A fragment of a program
  is not a weaker claim, it is not a claim.
- `hasAttemptIdentity` refuses a pair whose attempt key holds a bare shell
  separator. The single key `cd <repo> &&` covers 539 distinct command lines in
  this repository, so any pair drawn from it is false about both halves. This
  class was unreachable while no failures arrived; the reader makes it
  reachable, so it is closed here.

Both apply to BOTH sources: where evidence arrived from has no bearing on
whether the resulting sentence says anything. Ceilings unchanged, and both Task
3 refusals still hold. Bounded: last 8 MB of transcript, 50 failures, 4 KB of
output each, redacted at this boundary because transcript text has never passed
one. Fail-open throughout -- this runs at session end.

Measured end to end: 0 candidates before, 0 after, on this machine's real
evidence and through the real Stop hook. That is the finding. The fix that
would recover most of the 571 is to skip a leading `cd <path> &&` when building
attemptKey, which changes the key for every client and wants its own change.

18 mutations, each killing at least one test and the two pre-existing kill sets
intact. Four new tests passed for the wrong reason and were fixed -- all four
the one-fixture-two-mechanisms shape, including a dedup test whose two copies
shared a timestamp so the fallback masked the call-id check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plan): add task 12 for unrelated command grouping

Task 11 measured 539 distinct commands sharing one key in this repository,
because a leading 'cd <repo> &&' consumes the whole three-token key.

Framed as a correctness bug rather than an unlock: on the ten clients where the
detectors do receive failures, the command detector can pair a failure with a
success from an unrelated command and emit a 0.90-confidence claim that is false
about both halves. 0.90 is the highest confidence any detector may claim.

The task requires measured before/after evidence on real transcripts, and states
explicitly that a larger candidate count is not success on its own -- the
question is whether the pairs are true.

* fix(derive): key an attempt on the command, not on the cd that preceded it

`attemptKey` grouped by up to three non-flag tokens, so a leading
`cd <path> &&` consumed the whole key. Measured on this repository's
2,243 command outcomes plus 573 exit-code failures from 164 transcripts:
the single key `cd c:/users/.../token-optimizer-mcp &&` covered 547
distinct command lines, and one real quotable failure sat in a key holding
222 quotable successes -- one guard away from a 0.90 claim, the highest any
detector may make, that would have been false about both halves.

`commandBody` skips a leading `cd` up to `&&`, `;` or a newline: the three
separators actually observed after a leading cd (1147 / 204 / 38 of 1400).
Applied inside `attemptKey`, which is the one function both the event path
and the transcript reader pass through, so both sources get it identically;
the 120-char anchor cap that makes them agree is untouched and now MORE
load-bearing (4342/4342 keys agree with it, on both arms).

Nothing wider, each refusal measured. Environment assignments carry
information about what ran -- the one true instance here is
`TOKEN_OPTIMIZER_HOLDOUT=1 node ... jest.js`, and the holdout arm is a
different attempt. `time`, `env`, `bash -c`, `sudo` and `pushd` occur zero
times in 2,243 commands. `||` is an error branch, not the next step.

The identical-text refusal is WIDENED rather than left behind: it now
compares `commandBody` on both sides, because `cd repo && npm test` and
`npm test` are one attempt spelled two ways and the better key would
otherwise have emitted "`npm test` succeeded where `cd repo && npm test`
failed" at 0.85. The claim text still quotes the raw command, since that
is what ran.

Most-populated key 547 -> 76 distinct commands; keys 904 -> 1726; keys
holding >100 distinct commands 3 -> 0; false-pair exposure 222 -> 0.
Candidates 0 -> 0, reported rather than dressed up: the binding constraint
is `quotable` (8 of 573 command failures are actionable), not the key. With
the quality filters disabled the key admits 40 pairs against 49 before, and
all 40 are false -- unrelated heredoc scripts sharing an opener, every one
refused downstream.

Two existing tests were made to pass for the WRONG REASON by this change
and are rewired: the hasAttemptIdentity test (its `cd repo &&` fixture now
yields different keys, so it would have passed on a key mismatch) and the
multi-line test (`cd sub\nnpm run ship` vs `...build` now split). Both
found by mutating rather than reading.

21 mutations, baseline and restored 103/103, verdicts read from
`jest --json`. Every pre-existing kill set held, including both Task 11
refusals. The circularity refusal rechecked on the full corpus: 1,984
is_error results, 1,149 of them denials, 0 ingested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plan): record tasks 11 and 12 in the execution state

The state table stopped at Task 10, so both mid-plan tasks were invisible
to the next reader. Task 12's row carries the numbers a future change to
`attemptKey` needs: 547 -> 76 on the most-populated key, 222 -> 0 on
false-pair exposure, and the prefixes deliberately NOT stripped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(metrics): add layer 1 explicit-reference classification

Deliberately independent of read-suppression: that is Layer 2's estimand, and
using it in both would make Task 8's calibration compare two spellings of one
quantity. unknown is excluded from the denominator, never counted as a miss.

Built on the injection-to-outcome join recordToolOutcome already performs --
which prefers an exact tool-call-id match and already reports when it cannot
attribute -- rather than a weaker (sessionId, findingKey) join of its own. No
change to inject.mjs was needed: it has recorded findingIds all along.

TWO DETERMINATIONS, ANSWERED AGAINST REAL DATA rather than assumed.

`expand` DOES NOT NAME A FINDING, so the brief's "a later query or expand
event" is wrong and expand is excluded. recordExpansion writes a truncation
capture `ref` (a 16-hex digest of tool output), a tool, a shape and free-text
`asked`; none is a graph key, and all 64 live expand events on this machine
carry sessionId: null, so they could not be scoped to an injection even if they
did. Including it for symmetry would have added a branch that can never fire --
a metric with no producer, the exact defect this effort exists to close. It
still counts as OPPORTUNITY, the only role it can honestly play.

THE JOIN IS 99.97% UNATTRIBUTED AND THE JOIN IS NOT WHAT FAILED. 2,994 of
2,995 live tool-outcomes report joinMethod 'none' -- every one because no
injection existed for that tool call. There are 2 inject events in 16,387 rows;
the graph holds one finding. On the single tool call that did receive one, the
join worked on its strongest key. So the denominator is 1 and the rate is null,
not 0: one observation is not a rate, and the query channel has produced zero
events on this machine, so a 0% would measure the absence of a producer. The
audit prints no line at all rather than a figure.

A MEASUREMENT-BIAS DEFECT IN THE FIRST DRAFT, recorded because it is the class
this plan hunts and because it was biased pessimistic, which is no better.
Keying "the join failed" on the episode scored 2,559 tool calls of a session
that contained ONE injection as failed joins, where the true number is 0 --
episodeId IS the session id. Re-keyed onto the tool-call id, recordToolOutcome's
own primary key, and pinned by a test that reproduces the 2,559-row shape.

Also: the read window, not the join, is the binding constraint here. Both
inject events sit at lines 502 and 883 of 6,871 and have aged out of the 2 MB
tail, so the windowed denominator is 0 while the full-file one is 1.

Wired to a real reader rather than parked for Task 8: renderAudit calls
referenceNote -> referenceRate -> classify, so the allowlist stays at 5.

24 tests; 21 mutations, 21 killed, 0 survived, no vacuous test. Five mutations
survived the first run and each exposed a real gap -- the two field guards were
untested alone, nothing asserted the metric reached a human, inTimeOrder had no
behavioural effect until row order was made contract, and the positive path was
killed by nothing until M21 was added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plan): record task 6 in the execution state

Layer 1 shipped. The two determinations it was told to make rather than assume
both came back negative on live data -- expand names no finding, and the join's
99.97% unattributed rate is an absence of injections rather than a failure to
attribute them -- so the honest output is a null rate over a denominator of 1,
and the audit prints nothing at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(metrics): attribute a reference by finding key and time order, not by session

An unverifiable model-supplied id is not evidence. wiki_query writes
`options.sessionId ?? null`, so scoping the numerator on the session made it
depend on the model choosing to identify itself -- the concern raised in round
0, and the fix is to stop depending on the field rather than hope for
cooperation. Plan 1 settled the same question for the `answers` edge.

So a query naming key K now counts as a reference to any injection of K that
precedes it in time, in the same graph, whatever session either came from. Time
order is the ONLY remaining guard, so it carries the whole weight of the
attribution -- mutation M3 removes it and the "does not count a query that
preceded the injection" test fails, which is the point.

THE COST IS DISCLOSED WHERE THE NUMBER IS READ. Two concurrent sessions on one
repository can cross-attribute: A's injection of K plus B's later query for K
reads as a reference, and nothing in the log can separate them once the session
id is gone. referenceNote -- the one place a human meets the figure, via
renderAudit -- now says so beside every rate it prints, not only in the task
report where nobody quoting the number will look.

unscopedReferences is REMOVED rather than left at a permanent zero, which a
later maintainer would read as evidence the loss had been fixed. sessionOf
survives for the `unknown` determination alone, where a session is genuinely
what the question is about.

THE READ WINDOW IS REPORTED, NOT FIXED, and the reason is a bias fixing it
would have introduced. referenceRate now returns `windowed`, from
readTruncation read immediately after the only read that sets it, and the note
appends "the denominator can understate" when it is true -- live, the repo graph
reports denominator 0 / windowed true where the whole-file truth is 1. Giving
only the INJECTION arm unwindowed access (it already has it, via BALANCE_KINDS
and EVIDENCE_KINDS; `query` has neither) would make the two arms cover different
time spans: every old injection would enter the denominator with no chance of a
matching query in the window and score not-referenced, manufacturing a
pessimistic rate out of the asymmetry -- the same shape as the episodeId defect
from round 0. Unwindowing the query arm means widening EVIDENCE_KINDS, which two
other consumers read, and this project's standard says that needs a per-consumer
verdict comparison. A caller wanting the unwindowed truth passes its own events.

28 tests; 25 mutations, 25 killed, 0 survived, no vacuous test. Live figures
unchanged where it matters: still zero query events on this machine, so the
ceiling rose and the value did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(plan): record task 6 fix round 1 in the execution state

Session-scoped reference attribution is gone: an unverifiable model-supplied id
is not evidence, so time order is the only guard and the cross-attribution cost
is disclosed beside the figure. The read window is reported via a windowed flag
rather than fixed, because unwindowing one arm alone manufactures a pessimistic
rate from the asymmetry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(metrics): add layer 2 causal utility by leave-one-out

Nobody in this space measures per-item value causally in production -- the
state of the art is offline eval sets. Withhold ONE finding while serving the
rest of that touch, then compare what the session spent reading the anchor
afterwards.

Guarded, because an experiment that degrades a turn owes an accounting: never
a pinned or human-origin finding (a person asserted it), never a key whose
node cannot be read, never more than one per touch, never more than one per
SESSION, never on a command surface (no read event can join to command text),
never fewer than two kept findings and never inside inHoldout -- either would
make it withhold everything and call it a leave-one-out. Four prior injections
before entry. Worst case a user can feel: one extra file read per session.

Withholding runs AFTER fit, so the withheld arm differs by one finding rather
than by one plus whatever took the freed budget, and the delivered set is
re-priced so tokens bills only what was sent. Exploration at eps=0.10 runs
BEFORE fit, so a low score cannot keep a finding from ever being served again.

Statistics that can refuse: exact permutation p-values (84 relabellings at the
6-served/3-withheld floor, so the floor and q=0.10 are compatible by
arithmetic), Benjamini-Hochberg across every candidate row, empirical-Bayes
shrinkage with both the prior mean and the weight taken from the data, and the
serving-policy version on every observation so effects are never pooled across
a config change. Below the floor there is no verdict -- and that is not a zero.

Disjoint from Layer 1 by construction: loo.mjs never reads a query event,
usage.mjs never reads a token count, and tests move each layer while the other
stays deeply equal. Task 8 would otherwise correlate a quantity with itself.

On this machine it reports nothing at all, which is the correct output: 0
file-surface injections in 16,875 rows, one finding in the graph, no anchor
with two. effects() is [], looNote() is null, and the audit gains no line.

43 tests, 43/43 mutations killed, every test killed by at least one mutation.
Two defects found in my own work that way: a publication gate on
costObservations that could not change any verdict (deleted -- an
unfalsifiable guard is worse than none) and exploration wired but unproven.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(metrics): calibration loop, graph balance sheet in the report, allowlist 5->4

Does Layer 1's cheap LABEL predict Layer 2's expensive EFFECT? calibration() in
the new hooks-core/crosslayer.mjs answers it, and on this machine its only
honest answer is a refusal: Layer 1 publishes no rate (0 classifiable
observations, 0 reference events) and Layer 2 has 0 observations, so the verdict
names BOTH insufficient inputs and there is no `gap` key at all. Not gap: 0,
which would read as "measured, and they agree" -- the same reason Task 7 removed
a field rather than zero it. Seven distinct refusals, each with its own verdict,
each pinned by a test and killed by its own mutation.

Independence with Layer 2 is preserved by construction: only Layer 1's
categorical label and only Layer 2's effect magnitude, joined on the finding key.
Proven from both sides -- flipping the labels over identical Layer 2 data flips
the gap's sign, and moving Layer 1's rate without moving a label leaves the gap
byte-identical.

balanceSheet() gains layer1, layer2, calibration and consolidation -- composed
in crosslayer.mjs rather than metrics.mjs, which is the base of the import graph
and cannot import back into loo/usage/consolidate. get_optimization_report serves
the sheet as `graph` and renders it, with currency on the measured-counterfactual
line and on no line saying "estimate".

Three of this task's premises were stale. approxCost and the hardcoded $3/1M do
not exist anywhere in the repo, so no change was manufactured to have one --
pricing.mjs and provider-pricing.ts were already right, and what was open was
the RULE, now checked against the rendered text with a rate configured.
hasOutstandingContradiction already has its consumer at staleness.mjs:497, and
no promotion path exists for utility to feed: every confidence write in
hooks-core happens at creation. So mayPromote was NOT added as an unreachable
gate; the invariant is enforced instead, and a mutation that adds a real
promotion kills both of its tests.

consolidationRatio is wired: cost-to-derive against cost-to-carry, labelled an
estimate and never priced, which is the most legible statement of what the graph
is for. Its allowlist excuse was doubly stale -- expand.promote does persist
derivedCost and src/server/disclosure.ts calls it. Allowlist 5 -> 4.

Two measurement-bias defects found in my own new code by mutation, both
flattering to the project: a publication gate of `gap > 0` would have published
a calibration resting on a ~1e-13 floating-point gap as "suppress 0 more
tokens/touch" (MIN_GAP_TOKENS = 1, tied to the unit the verdict prints), and the
rendered re-read line printed the confirmed figure without the repeats the
classifier could not judge.

Also fixes the headline Task 7 left: "Nothing addressable found." is now scoped
to the remediation queue, so it can no longer be a false negative printed
directly above a published Layer 2 verdict.

30 mutations, 30 killed, 0 survived; all 29 new tests killed by at least one
mutation. Full suite 224 suites / 2,849 passed, green twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(keepwarm): close the loop -- refreshes now learn whether they paid off

keepWarmDecision spent money on refreshes and could never check whether one
bought a read, because both recording halves had no call site.

The turn-arrival signal was already there: gapDistribution is fitted to
inter-turn gaps from metrics.jsonl, so the same log answers the outcome question
directly -- a 5m refresh was used if a turn of the same session arrived within
five minutes of it. scoreRefreshes scores every recorded refresh whose TTL has
elapsed, on the Stop path, and shouldKeepWarm reads the rate back from the
outcome array the tripwire has already read.

Four refusals, each declining in the direction that costs this project its own
good news: a hit is not scored before its window closes (scoring hits early
while misses wait out the TTL is right-censoring, biased upward); an arrival log
that no longer reaches the refresh records NOTHING rather than a miss, because
an absent signal is not a miss; a refresh naming no session is unattributable;
and one with no id cannot be paired, so scoring it would score it every turn.

An observation may only ever LOWER a modelled probability, never raise it. A
recorded hit is P(gap < ttl) -- 99.5% here -- while keepWarmDecision's
probability is the narrow ping window P(ttl < gap < 2*ttl), 0.19% here, so
substituting one for the other would have recommended refreshing forever. It is
ttlTier, whose hit IS the same estimand, where the observation does real work,
and it is bounded rather than substituted even there.

recordRefresh STAYS on the reachability allowlist, and that is the finding:
nothing in this repository issues a refresh. cache_audit is the only consumer of
the decision and it prints a recommendation; the prompt cache belongs to the
client. Recording a refresh at the recommendation site would compute a hit rate
over refreshes never bought and make keep-warm look self-funding -- the ninth
instance of the measurement-bias class on these plans, and the first avoided by
declining to wire something.

Allowlist 4 -> 3. 20 tests, 26 mutations, 26 killed, every new test killed by at
least one. Full suite 224 suites / 2,869 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(wiki): a recall probe that can fail, so the no-embeddings stance is falsifiable

The design says embeddings get added if measurement shows recall loss, and
nothing measured recall. The plan's own probe could not have: findingsFor walks
derived_from edges backwards into an anchor, and the edge F -> A is what MAKES A
an anchor of F, so "retrieve each finding from its own anchor" asks whether an
edge that exists exists. Its rate is 1.0 for every graph forever, and
`recall: 100%` in the savings report reads as "retrieval is perfect, embeddings
unnecessary" -- one unfalsifiable claim swapped for another.

So the by-construction check ships as `integrity`, carrying its own "NOT a recall
rate" disclaimer, and the rate comes from leave-one-edge-out instead: delete each
anchor edge, then ask whether traversal from the anchor's contains-neighbourhood
or BM25 over the anchor's own key still surfaces the finding. Across four
fixtures the measured rate is 0, 0.5, 1 and null while integrity is 1.0 on every
one of them.

Four refusals, each costing this project its own good news: an observation floor
of 10 distinct findings (a resolution argument, not an effect-size convention);
a corpus no larger than the retrieval limit, where nothing is cut for budget and
the lexical arm is more permissive than production; a dangling anchor edge as
UNPROBEABLE rather than a miss; and strict aggregation over every anchor edge.

On this machine it refuses. The project graph holds one finding, so
`rate: null` with the count stated instead. The shared graph holds 14 active
findings and all 14 are unprobeable -- every one anchored to a file id with no
node in the graph -- so integrity is 1.0 while nothing is measurable, which is
exactly why the two numbers are reported apart.

32 mutations, 32 killed. Two of the new tests were vacuous and only the mutation
run found them: a cap assertion read a reported all.length while BM25 ranked
over a separate corpus variable, and a fail-open test passed a nonexistent
directory where load returns an empty graph rather than throwing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(metrics): significance-test the calibration headline

* fix(metrics): preserve honest measurements after rebase

* style: satisfy formatting and whitespace checks

---------

Co-authored-by: franklinic <franklin@ivorycloud.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
hamsterish pushed a commit to hamsterish/token-optimizer-mcp that referenced this pull request Oct 2, 2026
* feat: complete live graph integration

* style: format mcp tool profile

* feat: support live graph across cli clients

* fix: address live graph review findings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants