diff --git a/.dockerignore b/.dockerignore index 1d81d13e..a3b11f57 100644 --- a/.dockerignore +++ b/.dockerignore @@ -15,5 +15,13 @@ tests benchmarks skills proposals +# webapp/ is copied by demo/Dockerfile — ship only its source, never the +# host's node_modules (breaks cross-arch: npm ci reinstalls in-image) or the +# generated dist/. The root vouch image ignores webapp entirely. +webapp/node_modules +webapp/dist +webapp/test-results +webapp/playwright-report +webapp/.superpowers **/__pycache__ **/*.py[cod] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 482b5eb2..bd3c7d5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,11 +23,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + # Build the React console so the wheel can bundle it as vouch/web/console + # (hatch_build.py force-includes webapp/dist when present). + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npm --prefix webapp ci && npm --prefix webapp run build - uses: actions/setup-python@v5 with: python-version: "3.12" - run: python -m pip install --upgrade build - - run: python -m build + # sdist is source-only; the wheel is built from the working tree (not the + # sdist) so the freshly-built, gitignored webapp/dist rides inside it. + - run: python -m build --sdist + - run: python -m build --wheel - uses: actions/upload-artifact@v7 with: name: dist diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml new file mode 100644 index 00000000..840e8598 --- /dev/null +++ b/.github/workflows/security-audit.yml @@ -0,0 +1,40 @@ +name: security-audit + +# Non-blocking dependency vulnerability scan (pip-audit). Runs weekly so +# newly-disclosed advisories surface without waiting for a push, plus on +# any change to the dependency set, plus on demand. continue-on-error +# keeps a fresh CVE from turning the tree red — triage it, don't gate on +# it. (the blocking gates are ci.yml / schema-check.yml / eval.yml.) +on: + schedule: + - cron: "0 6 * * 1" # mondays 06:00 utc + pull_request: + paths: + - "pyproject.toml" + - ".github/workflows/security-audit.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + audit: + name: pip-audit + runs-on: ubuntu-latest + timeout-minutes: 15 + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: install + run: | + python -m pip install --upgrade pip pip-audit + pip install -e . + + - name: audit + run: pip-audit --desc diff --git a/.gitignore b/.gitignore index ee98bd96..b59e1ca0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ build/ # and adapters/claude-code/.claude stay tracked) /web/ /.claude/ +.vouch +docs \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 5b102d99..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,13 +0,0 @@ -repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.0 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 - hooks: - - id: mypy - additional_dependencies: [types-pyyaml] diff --git a/.vouch/audit.log.jsonl b/.vouch/audit.log.jsonl index 3306eae3..73a27d42 100644 --- a/.vouch/audit.log.jsonl +++ b/.vouch/audit.log.jsonl @@ -7,3 +7,21 @@ {"actor":"a","created_at":"2026-05-21T06:05:54.067884Z","data":{},"dry_run":false,"event":"source.add","id":"0b451622d85246089a1d04c45d56c007","object_ids":["67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba"],"reversible":true} {"actor":"a","created_at":"2026-05-21T06:06:58.649209Z","data":{},"dry_run":false,"event":"source.add","id":"b72feb84d842486490bb7616c616a08c","object_ids":["67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba"],"reversible":true} {"actor":"a","created_at":"2026-05-21T06:07:08.973490Z","data":{"slug_hint":"vouch-enforces-a-review-gate-on-agent-writes"},"dry_run":false,"event":"proposal.claim.create","id":"9ef38f6a9d8c4e02ad3fd196c49bcc6f","object_ids":["20260521-060708-2b81cdab"],"reversible":true} +{"actor":"vouch-capture","created_at":"2026-07-10T06:51:08.777382Z","data":{"slug_hint":"session-71-file-s"},"dry_run":false,"event":"proposal.page.create","hash":"76845f95a24cc02ab36c491b8b121340d21291954e35d52a960bbebc85e30cdc","id":"d7b136387184484e8811038ab33cf68b","object_ids":["20260710-065108-54f0c393"],"prev_hash":"0000000000000000000000000000000000000000000000000000000000000000","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.234458Z","data":{"slug_hint":"scaffolded-inbounds-detection-engine-package-with-typescript"},"dry_run":false,"event":"proposal.page.create","hash":"6d65bcafe2c0f48f78ea5a8b52d76a924e49d80d9195b928cda6ae3a2dd6bb3b","id":"b1d08c345681419297ce580018ae4a6c","object_ids":["20260710-065335-f6053bee"],"prev_hash":"76845f95a24cc02ab36c491b8b121340d21291954e35d52a960bbebc85e30cdc","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.239312Z","data":{"slug_hint":"defined-core-domain-types-and-initial-type-level-test-covera"},"dry_run":false,"event":"proposal.page.create","hash":"c99d094fed530bed419a65cec72c63a515910cf0761aa69ef3bb2a76be4b5e14","id":"709f36729bd84a94a9d260d71eeeab24","object_ids":["20260710-065335-bc8ede6c"],"prev_hash":"6d65bcafe2c0f48f78ea5a8b52d76a924e49d80d9195b928cda6ae3a2dd6bb3b","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.242918Z","data":{"slug_hint":"built-the-github-adapter-layer-fetch-normalize-handle-action"},"dry_run":false,"event":"proposal.page.create","hash":"ac62f3954014810ff07fa2fc80a8852ae8c10571015b80c5318092dd2bb1ff08","id":"f4110c94f0404fc5b7be15e8cb8f6c56","object_ids":["20260710-065335-a882803f"],"prev_hash":"c99d094fed530bed419a65cec72c63a515910cf0761aa69ef3bb2a76be4b5e14","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.247145Z","data":{"slug_hint":"implemented-deterministic-scoring-module-with-unit-tests"},"dry_run":false,"event":"proposal.page.create","hash":"ceeb8483f50f9a6b383c0be2347fedce07941c7a70bc79e2eb924a7accc2d047","id":"0a718c0581484802a4c76038a1fb700f","object_ids":["20260710-065335-eb4a7247"],"prev_hash":"ac62f3954014810ff07fa2fc80a8852ae8c10571015b80c5318092dd2bb1ff08","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.250920Z","data":{"slug_hint":"wired-evaluate-and-cli-entry-points"},"dry_run":false,"event":"proposal.page.create","hash":"3fc88df87d56cbdb5ace39b6df14afba28748b0c1512e39eebfcf06509c07bd1","id":"f9a5694f054c4ce09c69f1feb50e497b","object_ids":["20260710-065335-142a3abd"],"prev_hash":"ceeb8483f50f9a6b383c0be2347fedce07941c7a70bc79e2eb924a7accc2d047","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.254094Z","data":{"slug_hint":"created-json-fixture-files-for-scenario-driven-testing"},"dry_run":false,"event":"proposal.page.create","hash":"0660c585390b276a3255617710e8105fe572ee23b4aed8aa54b519d3c93dc1a8","id":"c3f3d0583f8e4272b949705f977e41cc","object_ids":["20260710-065335-a8161813"],"prev_hash":"3fc88df87d56cbdb5ace39b6df14afba28748b0c1512e39eebfcf06509c07bd1","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.263211Z","data":{"reason":"superseded by llm narrative summary"},"dry_run":false,"event":"proposal.page.reject","hash":"270b31129cdf9096dbdc4717f7bfd1ebb6a0a4d6ade2e9df467b8989465f88dd","id":"900e8f66fcdf4babbdea394716afa54a","object_ids":["20260710-065108-54f0c393"],"prev_hash":"0660c585390b276a3255617710e8105fe572ee23b4aed8aa54b519d3c93dc1a8","reversible":true} +{"actor":"session-split","created_at":"2026-07-10T06:53:35.265122Z","data":{"dropped":0,"observations":0,"proposed":6,"truncated":false},"dry_run":false,"event":"session.split","hash":"234acadc3c644da01487935d5f117a25892e7b46fa1ca8dbc188ea3b77cc1c3d","id":"a98c8540c53b4021b1adc938def2335e","object_ids":["20260710-065335-f6053bee","20260710-065335-bc8ede6c","20260710-065335-a882803f","20260710-065335-eb4a7247","20260710-065335-142a3abd","20260710-065335-a8161813"],"prev_hash":"270b31129cdf9096dbdc4717f7bfd1ebb6a0a4d6ade2e9df467b8989465f88dd","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T07:24:02.966238Z","data":{"reason":null},"dry_run":false,"event":"proposal.page.approve","hash":"0a979813ec3ce3f3909e5f8519527157052ccdb85a5dea3fd12184aae1697130","id":"71fece2560d040a493b8f89534730775","object_ids":["20260710-065335-bc8ede6c","defined-core-domain-types-and-initial-type-level-test-covera"],"prev_hash":"234acadc3c644da01487935d5f117a25892e7b46fa1ca8dbc188ea3b77cc1c3d","reversible":true} +{"actor":"wiki-compiler","created_at":"2026-07-10T07:24:33.615350Z","data":{"slug_hint":"review-gated-knowledge-proposal-workflow"},"dry_run":false,"event":"proposal.page.create","hash":"dd18e31fc67152a2135b2ebb5e241b9e6a04d1fb19c4d77b2b91fbbcb5de775b","id":"fa3ba361a4fa4094ba52c07624c924b7","object_ids":["20260710-072433-ffff6671"],"prev_hash":"0a979813ec3ce3f3909e5f8519527157052ccdb85a5dea3fd12184aae1697130","reversible":true} +{"actor":"wiki-compiler","created_at":"2026-07-10T07:24:33.617853Z","data":{"slug_hint":"repository-as-knowledge-store"},"dry_run":false,"event":"proposal.page.create","hash":"8b35e84a4e0ef89e89c244568de6158f33117a1a64bc131542b3a94072291f60","id":"daa78f7818a1442496c0b69071378fbe","object_ids":["20260710-072433-123db33a"],"prev_hash":"dd18e31fc67152a2135b2ebb5e241b9e6a04d1fb19c4d77b2b91fbbcb5de775b","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T07:24:33.618337Z","data":{"dropped":0,"proposed":2,"proposer":"wiki-compiler"},"dry_run":false,"event":"compile.run","hash":"6aa930c42142624a0ca4753564efaaf5bfbf6e19ced80e7f0b0fd4c09ee66aed","id":"83373b1dda3c45f19748753b2bc9de88","object_ids":["20260710-072433-ffff6671","20260710-072433-123db33a"],"prev_hash":"8b35e84a4e0ef89e89c244568de6158f33117a1a64bc131542b3a94072291f60","reversible":true} +{"actor":"vouch-extractor","created_at":"2026-07-10T07:25:41.876231Z","data":{"slug_hint":"review-gated-knowledge-proposal-workflow-mentions-reviewed-k"},"dry_run":false,"event":"proposal.relation.create","hash":"f35167409cbe540e24293b348e0127e60553e171a35d32fb8b5b71c33317bfce","id":"e73a022074444ddb9e30407963f20907","object_ids":["20260710-072541-96ce439f"],"prev_hash":"6aa930c42142624a0ca4753564efaaf5bfbf6e19ced80e7f0b0fd4c09ee66aed","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T07:25:41.877790Z","data":{"reason":null},"dry_run":false,"event":"proposal.page.approve","hash":"1a2d24c3a512bc4c0171efb61c9631f4882477170e152aad4199e732f829fc5e","id":"34b056e066f7444d819f0486bc5a0584","object_ids":["20260710-072433-ffff6671","review-gated-knowledge-proposal-workflow"],"prev_hash":"f35167409cbe540e24293b348e0127e60553e171a35d32fb8b5b71c33317bfce","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T07:25:59.154361Z","data":{"reason":null},"dry_run":false,"event":"proposal.page.approve","hash":"b4cc6e9d79da47b096917cdeefd5d5b0910bda7b7bc06a7e146d8dd9d0038310","id":"b8c6719b8d44472883e40b016f8faad0","object_ids":["20260710-072433-123db33a","repository-as-knowledge-store"],"prev_hash":"1a2d24c3a512bc4c0171efb61c9631f4882477170e152aad4199e732f829fc5e","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T09:48:36.518125Z","data":{"reason":null},"dry_run":false,"event":"proposal.page.approve","hash":"9f2e75b6d37082ee754475878d52da597cc2cffaf42627b832adcf16a642df4c","id":"d509714619ff4e36b46e7c6360614f26","object_ids":["20260710-065335-eb4a7247","implemented-deterministic-scoring-module-with-unit-tests"],"prev_hash":"b4cc6e9d79da47b096917cdeefd5d5b0910bda7b7bc06a7e146d8dd9d0038310","reversible":true} +{"actor":"unknown-agent","created_at":"2026-07-10T09:48:48.002564Z","data":{"reason":null},"dry_run":false,"event":"proposal.page.approve","hash":"dbb2d71c150fec0055c3e1c825c9503d6e2bcf638fcb57c33f71f46e8cb8b630","id":"ff3b16ca8ebc484fb57bcd75700dcb7e","object_ids":["20260710-065335-142a3abd","wired-evaluate-and-cli-entry-points"],"prev_hash":"9f2e75b6d37082ee754475878d52da597cc2cffaf42627b832adcf16a642df4c","reversible":true} diff --git a/.vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl b/.vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl new file mode 100644 index 00000000..cd7a0715 --- /dev/null +++ b/.vouch/captures/1218169c-de25-48f4-bd93-70ff77d2fda2.jsonl @@ -0,0 +1,93 @@ +{"cmd": "grep -n 'name=\"serve\"\\|def serve\\|@cli.command()' src/vouch/cli.py | grep -A1 -i serve; echo \"---\"; grep -n \"serve\" src/vouch/cli.py | head; echo \"--- how serve launches MCP ---\"; grep -n \"serve\\|run_", "summary": "Command failed: grep -n 'name=\"serve\"\\|def serve\\|@cli.command()' src/vouch/", "tool": "Bash", "ts": 1783415885.3343549} +{"files": ["/home/a/Dev/plind-junior/vouch/src/vouch/cli.py"], "summary": "Read cli.py", "tool": "Read", "ts": 1783415898.5705624} +{"files": ["/home/a/Dev/plind-junior/vouch/src/vouch/__init__.py"], "summary": "Read __init__.py", "tool": "Read", "ts": 1783416041.4045353} +{"cmd": "echo \"=== CHANGELOG top ===\" && head -40 CHANGELOG.md && echo \"=== console-script/version assertions in tests ===\" && grep -rn \"vouch-kb\\|project.scripts\\|__version__\\|1.2.1\\|server.json\\|mcp-name\\|co", "summary": "Ran: echo \"=== CHANGELOG top ===\" && head -40 CHANGELOG.md && ech", "tool": "Bash", "ts": 1783416042.681192} +{"cmd": "sed -n '55,110p' tests/test_openclaw_plugin_manifest.py", "summary": "Ran: sed -n '55,110p' tests/test_openclaw_plugin_manifest.py", "tool": "Bash", "ts": 1783416053.253931} +{"cmd": "git fetch origin main --quiet && git switch -c feat/mcp-registry origin/main 2>&1 && echo \"--- branch ---\" && git branch --show-current && echo \"--- status (short) ---\" && git status --short | head -5", "summary": "Ran: git fetch origin main --quiet && git switch -c feat/mcp-regi", "tool": "Bash", "ts": 1783416095.2466135} +{"files": ["/home/a/Dev/plind-junior/vouch/README.md"], "summary": "Read README.md", "tool": "Read", "ts": 1783416115.2192614} +{"files": ["/home/a/Dev/plind-junior/vouch/README.md"], "summary": "Edited README.md", "tool": "Edit", "ts": 1783416129.2332954} +{"files": ["/home/a/Dev/plind-junior/vouch/pyproject.toml"], "summary": "Edited pyproject.toml", "tool": "Edit", "ts": 1783416143.2396264} +{"files": ["/home/a/Dev/plind-junior/vouch/src/vouch/__init__.py"], "summary": "Edited __init__.py", "tool": "Edit", "ts": 1783416161.2477953} +{"files": ["/home/a/Dev/plind-junior/vouch/openclaw.plugin.json"], "summary": "Edited openclaw.plugin.json", "tool": "Edit", "ts": 1783416162.2770805} +{"files": ["/home/a/Dev/plind-junior/vouch/package.json"], "summary": "Edited package.json", "tool": "Edit", "ts": 1783416163.3459423} +{"files": ["/home/a/Dev/plind-junior/vouch/CHANGELOG.md"], "summary": "Read CHANGELOG.md", "tool": "Read", "ts": 1783416173.222827} +{"files": ["/home/a/Dev/plind-junior/vouch/CHANGELOG.md"], "summary": "Edited CHANGELOG.md", "tool": "Edit", "ts": 1783416194.206192} +{"files": ["/home/a/Dev/plind-junior/vouch/server.json"], "summary": "Created server.json", "tool": "Write", "ts": 1783416209.2143273} +{"cmd": "grep -rn \"VOUCH_KB_PATH\\|VOUCH_KB\\|getenv\\|os.environ\\|environ.get\" src/vouch/cli.py src/vouch/storage.py src/vouch/config.py 2>/dev/null | grep -i \"kb\\|path\\|vouch_\" | head -20; echo \"--- broad env s", "summary": "Ran: grep -rn \"VOUCH_KB_PATH\\|VOUCH_KB\\|getenv\\|os.environ\\|envir", "tool": "Bash", "ts": 1783416220.26138} +{"files": ["/home/a/Dev/plind-junior/vouch/src/vouch/storage.py"], "summary": "Read storage.py", "tool": "Read", "ts": 1783416230.2114375} +{"files": ["/home/a/Dev/plind-junior/vouch/server.json"], "summary": "Edited server.json", "tool": "Edit", "ts": 1783416249.550375} +{"cmd": "test -x .venv/bin/python && echo \"venv present\" || echo \"NO VENV\"; .venv/bin/python -m pytest tests/test_openclaw_plugin_manifest.py -q 2>&1 | tail -20", "summary": "Ran: test -x .venv/bin/python && echo \"venv present\" || echo \"NO ", "tool": "Bash", "ts": 1783416278.856125} +{"cmd": ".venv/bin/python -m pytest tests/ -q --ignore=tests/embeddings 2>&1 | tail -25", "summary": "Command failed: .venv/bin/python -m pytest tests/ -q --ignore=tests/embeddin", "tool": "Bash", "ts": 1783416326.067484} +{"cmd": "git stash push -u -- README.md pyproject.toml package.json openclaw.plugin.json src/vouch/__init__.py CHANGELOG.md server.json >/dev/null 2>&1 && echo \"stashed my changes\" && .venv/bin/python -m pytes", "summary": "Command failed: git stash push -u -- README.md pyproject.toml package.json o", "tool": "Bash", "ts": 1783416344.4675686} +{"cmd": "echo \"=== mypy ===\" && .venv/bin/python -m mypy src 2>&1 | tail -5; echo \"=== ruff ===\" && .venv/bin/python -m ruff check src tests 2>&1 | tail -5", "summary": "Ran: echo \"=== mypy ===\" && .venv/bin/python -m mypy src 2>&1 | t", "tool": "Bash", "ts": 1783416356.5439394} +{"cmd": "python3 -c \"import json,sys; json.load(open('server.json')); print('server.json: valid JSON')\" && rm -rf /tmp/vouch-wheel-check && .venv/bin/python -m pip wheel . --no-deps -w /tmp/vouch-wheel-check -", "summary": "Ran: python3 -c \"import json,sys; json.load(open('server.json'));", "tool": "Bash", "ts": 1783416379.7256448} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77d2fda2/scratchpad/commit-msg.txt"], "summary": "Created commit-msg.txt", "tool": "Write", "ts": 1783416417.261711} +{"cmd": "git add README.md pyproject.toml package.json openclaw.plugin.json src/vouch/__init__.py CHANGELOG.md server.json && echo \"=== staged (should be exactly 7, no .vouch) ===\" && git diff --cached --name-", "summary": "Ran: git add README.md pyproject.toml package.json openclaw.plugi", "tool": "Bash", "ts": 1783416427.3318202} +{"cmd": "git push -u origin feat/mcp-registry 2>&1 | tail -12", "summary": "Ran: git push -u origin feat/mcp-registry 2>&1 | tail -12", "tool": "Bash", "ts": 1783416446.2180362} +{"cmd": "echo \"=== remotes ===\" && git remote -v && echo \"=== gh auth identity ===\" && gh auth status 2>&1 | grep -i \"account\\|logged\" | head -4; echo \"=== does plind-junior/vouch exist? ===\" && gh api repos/p", "summary": "Ran: echo \"=== remotes ===\" && git remote -v && echo \"=== gh auth", "tool": "Bash", "ts": 1783416503.2145357} +{"cmd": "echo \"=== vouchdev org exists? type? ===\" && gh api orgs/vouchdev --jq '.login + \" | \" + (.type // \"n/a\")' 2>&1 | head -2; echo \"=== is plind-junior a member of vouchdev? ===\" && gh api orgs/vouchdev/", "summary": "Ran: echo \"=== vouchdev org exists? type? ===\" && gh api orgs/vou", "tool": "Bash", "ts": 1783416548.189724} +{"files": ["/home/a/Dev/plind-junior/vouch/server.json"], "summary": "Edited server.json", "tool": "Edit", "ts": 1783416622.9995663} +{"files": ["/home/a/Dev/plind-junior/vouch/README.md"], "summary": "Edited README.md", "tool": "Edit", "ts": 1783416625.2691755} +{"files": ["/home/a/Dev/plind-junior/vouch/CHANGELOG.md"], "summary": "Edited CHANGELOG.md", "tool": "Edit", "ts": 1783416638.236736} +{"cmd": "echo \"=== any lingering plind-junior in the 3 files? ===\" && grep -n \"plind-junior\" server.json README.md CHANGELOG.md || echo \"none \u2014 clean\"; echo \"=== server.json still valid JSON? ===\" && python3 -", "summary": "Ran: echo \"=== any lingering plind-junior in the 3 files? ===\" &&", "tool": "Bash", "ts": 1783416647.394468} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77d2fda2/scratchpad/commit-msg.txt"], "summary": "Edited commit-msg.txt", "tool": "Edit", "ts": 1783416675.5851853} +{"cmd": "git add README.md CHANGELOG.md server.json && echo \"=== staged ===\" && git diff --cached --name-only && git commit --amend -F \"/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-7", "summary": "Ran: git add README.md CHANGELOG.md server.json && echo \"=== stag", "tool": "Bash", "ts": 1783416734.468036} +{"cmd": "echo \"=== git state ===\" && git branch --show-current && git log --oneline -3 && echo \"=== is 1.2.2 on PyPI? ===\" && curl -s https://pypi.org/pypi/vouch-kb/json | python3 -c \"import json,sys; d=json.l", "summary": "Ran: echo \"=== git state ===\" && git branch --show-current && git", "tool": "Bash", "ts": 1783417455.24167} +{"cmd": "echo \"=== current branch + status ===\" && git branch --show-current && git status --short | grep -vE \"^\\?\\? \\.vouch/|^ M \\.vouch/\" | head -20; echo \"=== is server.json tracked on this branch? ===\" && ", "summary": "Command failed: echo \"=== current branch + status ===\" && git branch --show-", "tool": "Bash", "ts": 1783417514.1123304} +{"cmd": "git worktree add /tmp/vouch-mcp-registry-wt feat/mcp-registry 2>&1 | tail -3 && echo \"=== worktree HEAD ===\" && git -C /tmp/vouch-mcp-registry-wt log --oneline -1 && echo \"=== current committed descri", "summary": "Ran: git worktree add /tmp/vouch-mcp-registry-wt feat/mcp-registr", "tool": "Bash", "ts": 1783417643.664355} +{"files": ["/tmp/vouch-mcp-registry-wt/server.json"], "summary": "Read server.json", "tool": "Read", "ts": 1783417674.2757118} +{"files": ["/tmp/vouch-mcp-registry-wt/server.json"], "summary": "Edited server.json", "tool": "Edit", "ts": 1783417688.0398996} +{"cmd": "cd /tmp/vouch-mcp-registry-wt && python3 -c \"\nimport json\nd = json.load(open('server.json'))\ndef walk(o, path=''):\n if isinstance(o, dict):\n for k,v in o.items(): walk(v, f'{path}.{k}')\n ", "summary": "Ran: cd /tmp/vouch-mcp-registry-wt && python3 -c \"", "tool": "Bash", "ts": 1783417709.6342616} +{"cmd": "cd /tmp/vouch-mcp-registry-wt && git add server.json && git commit --amend --no-edit 2>&1 | tail -3 && echo \"=== push ===\" && git push --force-with-lease origin feat/mcp-registry 2>&1 | tail -4", "summary": "Ran: cd /tmp/vouch-mcp-registry-wt && git add server.json && git ", "tool": "Bash", "ts": 1783417798.2339296} +{"cmd": "git worktree remove /tmp/vouch-mcp-registry-wt 2>&1 | tail -2 && echo \"worktree removed\" && git worktree list | cat", "summary": "Ran: git worktree remove /tmp/vouch-mcp-registry-wt 2>&1 | tail -", "tool": "Bash", "ts": 1783417813.294893} +{"files": ["/home/a/Dev/plind-junior/vouch/server.json"], "summary": "Read server.json", "tool": "Read", "ts": 1783417833.5213194} +{"files": ["/home/a/Dev/plind-junior/vouch/server.json"], "summary": "Created server.json", "tool": "Write", "ts": 1783417852.7031524} +{"cmd": "gh auth switch -u plind-junior 2>&1 | tail -2; echo \"=== active gh user ===\" && gh api user --jq .login; echo \"=== existing PR for feat/mcp-registry? ===\" && gh pr list --repo vouchdev/vouch --head fe", "summary": "Ran: gh auth switch -u plind-junior 2>&1 | tail -2; echo \"=== act", "tool": "Bash", "ts": 1783418063.0932186} +{"cmd": "echo \"=== plind-junior perms on vouchdev/vouch ===\" && gh api repos/vouchdev/vouch --jq '.permissions' 2>&1 | head -3; echo \"=== diff stat: feat/mcp-registry vs main ===\" && git fetch origin main feat", "summary": "Ran: echo \"=== plind-junior perms on vouchdev/vouch ===\" && gh ap", "tool": "Bash", "ts": 1783418091.6960363} +{"cmd": "echo \"=== origin/main recent log ===\" && git log origin/main --oneline -6 | cat; echo \"=== version on origin/main ===\" && git show origin/main:pyproject.toml 2>/dev/null | grep -m1 \"^version\"; echo \"=", "summary": "Ran: echo \"=== origin/main recent log ===\" && git log origin/main", "tool": "Bash", "ts": 1783418118.044178} +{"cmd": "echo \"=== v1.2.2 tag exist? ===\" && git ls-remote --tags origin \"v1.2.2\" 2>&1 | head -2; echo \"(blank = no tag)\"; echo \"=== all v1.2.x tags ===\" && git ls-remote --tags origin \"v1.2*\" 2>&1 | head; ech", "summary": "Ran: echo \"=== v1.2.2 tag exist? ===\" && git ls-remote --tags ori", "tool": "Bash", "ts": 1783418225.6979148} +{"cmd": "git worktree add -b fix/mcp-registry-desc-limit /tmp/vouch-desc-fix-wt origin/main 2>&1 | tail -2 && echo \"=== current main server.json desc lengths ===\" && python3 -c \"import json; d=json.load(open('", "summary": "Ran: git worktree add -b fix/mcp-registry-desc-limit /tmp/vouch-d", "tool": "Bash", "ts": 1783418261.4507353} +{"files": ["/tmp/vouch-desc-fix-wt/server.json"], "summary": "Read server.json", "tool": "Read", "ts": 1783418268.017659} +{"files": ["/tmp/vouch-desc-fix-wt/server.json"], "summary": "Edited server.json", "tool": "Edit", "ts": 1783418275.288974} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77d2fda2/scratchpad/fix-msg.txt"], "summary": "Created fix-msg.txt", "tool": "Write", "ts": 1783418290.4113755} +{"cmd": "cd /tmp/vouch-desc-fix-wt && python3 -c \"import json; d=json.load(open('server.json')); print('desc:', len(d['description']), '| env desc:', len(d['packages'][0]['environmentVariables'][0]['descriptio", "summary": "Ran: cd /tmp/vouch-desc-fix-wt && python3 -c \"import json; d=json", "tool": "Bash", "ts": 1783418347.1354432} +{"cmd": "gh pr create --repo vouchdev/vouch --base main --head fix/mcp-registry-desc-limit \\\n --title \"fix(mcp): trim server.json descriptions under the registry 100-char cap\" \\\n --body \"the mcp registry rej", "summary": "Ran: gh pr create --repo vouchdev/vouch --base main --head fix/mc", "tool": "Bash", "ts": 1783418371.116139} +{"cmd": "gh pr merge 412 --repo vouchdev/vouch --admin --squash --delete-branch 2>&1 | tail -4; echo \"=== fetch main ===\" && git fetch origin main --quiet && echo \"=== new main tip ===\" && git log origin/main ", "summary": "Ran: gh pr merge 412 --repo vouchdev/vouch --admin --squash --del", "tool": "Bash", "ts": 1783418403.1710706} +{"cmd": "git worktree remove /tmp/vouch-desc-fix-wt 2>&1 | tail -1; echo \"=== tag v1.2.2 at main tip 95c666f ===\" && git tag v1.2.2 95c666f && git push origin v1.2.2 2>&1 | tail -4; echo \"=== triggered workflo", "summary": "Ran: git worktree remove /tmp/vouch-desc-fix-wt 2>&1 | tail -1; e", "tool": "Bash", "ts": 1783418438.2597678} +{"cmd": "echo \"=== local v1.2.2 points to ===\" && git rev-parse v1.2.2 && git log -1 --oneline v1.2.2 | cat; echo \"=== main tip ===\" && git rev-parse origin/main; echo \"=== is local tag on remote? ===\" && git ", "summary": "Ran: echo \"=== local v1.2.2 points to ===\" && git rev-parse v1.2.", "tool": "Bash", "ts": 1783418459.2861285} +{"cmd": "git tag -d v1.2.2 2>&1 | tail -1 && git tag v1.2.2 95c666f && echo \"=== v1.2.2 now points to ===\" && git log -1 --oneline v1.2.2 | cat && echo \"=== verify this commit has 1.2.2 + marker ===\" && git sh", "summary": "Ran: git tag -d v1.2.2 2>&1 | tail -1 && git tag v1.2.2 95c666f &", "tool": "Bash", "ts": 1783418488.3877065} +{"cmd": "sleep 6; gh run list --repo vouchdev/vouch --workflow=release.yml --limit 3 --json databaseId,status,conclusion,headBranch,event,createdAt --jq '.[] | \"\\(.databaseId) status=\\(.status) concl=\\(.conclu", "summary": "Ran: sleep 6; gh run list --repo vouchdev/vouch --workflow=releas", "tool": "Bash", "ts": 1783418514.3947628} +{"cmd": "gh run watch 28857902799 --repo vouchdev/vouch --exit-status --interval 20 2>&1 | tail -25", "summary": "Ran: gh run watch 28857902799 --repo vouchdev/vouch --exit-status", "tool": "Bash", "ts": 1783418662.3447204} +{"cmd": "for i in 1 2 3 4 5; do\n out=$(curl -s https://pypi.org/pypi/vouch-kb/json | python3 -c \"import json,sys; d=json.load(sys.stdin); v=d['info']['version']; desc=d['info'].get('description') or ''; print", "summary": "Ran: for i in 1 2 3 4 5; do", "tool": "Bash", "ts": 1783419028.4056067} +{"cmd": "echo \"=== local server.json (what publish will read) ===\" && python3 -c \"import json; d=json.load(open('server.json')); print('name:', d['name']); print('pkg:', d['packages'][0]['registryType'], d['pa", "summary": "Ran: echo \"=== local server.json (what publish will read) ===\" &&", "tool": "Bash", "ts": 1783419049.9222343} +{"cmd": "mcp-publisher publish 2>&1 | tail -20", "summary": "Command failed: mcp-publisher publish 2>&1 | tail -20", "tool": "Bash", "ts": 1783419074.03995} +{"cmd": "mcp-publisher login github 2>&1", "summary": "Ran: mcp-publisher login github 2>&1", "tool": "Bash", "ts": 1783419105.0348454} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77d2fda2/tasks/bu2np1cra.output"], "summary": "Read bu2np1cra.output", "tool": "Read", "ts": 1783419127.0421228} +{"cmd": "echo \"=== exact publish error ===\" && mcp-publisher publish 2>&1 | tail -15", "summary": "Command failed: echo \"=== exact publish error ===\" && mcp-publisher publish ", "tool": "Bash", "ts": 1783420160.4457347} +{"summary": "Fetched: https://modelcontextprotocol.io/registry/authentication", "tool": "WebFetch", "ts": 1783420163.7955225} +{"cmd": "echo \"=== active gh account ===\" && gh api user --jq .login; echo \"=== is plind-junior now a PUBLIC member of vouchdev? ===\" && gh api orgs/vouchdev/public_members --jq '.[].login' 2>&1 | head -20; ec", "summary": "Ran: echo \"=== active gh account ===\" && gh api user --jq .login;", "tool": "Bash", "ts": 1783420221.0744834} +{"cmd": "echo \"=== publish ===\" && mcp-publisher publish 2>&1 | tail -15", "summary": "Command failed: echo \"=== publish ===\" && mcp-publisher publish 2>&1 | tail ", "tool": "Bash", "ts": 1783420292.7722068} +{"cmd": "echo \"=== look for mcp-publisher token files ===\" && find / -maxdepth 6 -name \"*mcp-publisher*\" -o -name \".mcpregistry*\" 2>/dev/null | head -20; echo \"=== any recently-modified dotfiles in likely spot", "summary": "Ran: echo \"=== look for mcp-publisher token files ===\" && find / ", "tool": "Bash", "ts": 1783420318.553565} +{"cmd": "python3 -c \"\nimport json, base64, time\nd = json.load(open('/home/a/.config/mcp-publisher/token.json'))\nprint('keys in token.json:', list(d.keys()))\ntok = d.get('token') or d.get('access_token') or ''\n", "summary": "Ran: python3 -c \"", "tool": "Bash", "ts": 1783420336.7329283} +{"cmd": "echo \"=== registry API: search for the server ===\" \ncurl -s \"https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.vouchdev/vouch\" | python3 -c \"\nimport json,sys\ntry:\n d=json.load(", "summary": "Ran: echo \"=== registry API: search for the server ===\" ", "tool": "Bash", "ts": 1783420625.7861362} +{"cmd": "echo \"=== full published record ===\" \ncurl -s \"https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.vouchdev/vouch\" | python3 -c \"\nimport json,sys\nd=json.load(sys.stdin); s=d['server", "summary": "Ran: echo \"=== full published record ===\" ", "tool": "Bash", "ts": 1783420644.1610866} +{"summary": "Fetched: https://github.com/mcp/vouchdev/vouch", "tool": "WebFetch", "ts": 1783420647.4104495} +{"cmd": "if command -v uvx >/dev/null 2>&1; then\n echo \"=== uvx resolves + runs the published vouch-kb 1.2.2 (the advertised command) ===\"\n uvx --refresh vouch-kb@1.2.2 --version 2>&1 | tail -5\n echo \"=== a", "summary": "Command failed: if command -v uvx >/dev/null 2>&1; then", "tool": "Bash", "ts": 1783420700.178302} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/mcp-registry-publish.md"], "summary": "Created mcp-registry-publish.md", "tool": "Write", "ts": 1783420769.015541} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md"], "summary": "Read MEMORY.md", "tool": "Read", "ts": 1783420777.708722} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md"], "summary": "Edited MEMORY.md", "tool": "Edit", "ts": 1783420786.3390305} +{"cmd": "echo \"=== github.com/mcp mirror status ===\" && curl -s -o /dev/null -w \"%{http_code}\\n\" \"https://github.com/mcp/vouchdev/vouch\"; echo \"=== official registry (live) returns it? ===\" && curl -s \"https:/", "summary": "Ran: echo \"=== github.com/mcp mirror status ===\" && curl -s -o /d", "tool": "Bash", "ts": 1783422699.2106729} +{"cmd": "DYN='https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fregistry.modelcontextprotocol.io%2Fv0.1%2Fservers%3Fsearch%3Dio.github.vouchdev%2Fvouch&query=%24.servers%5B0%5D.server.version&label=M", "summary": "Ran: DYN='https://img.shields.io/badge/dynamic/json?url=https%3A%", "tool": "Bash", "ts": 1783422977.599783} +{"cmd": "git fetch origin main --quiet && git worktree add /tmp/vouch-mcp-badge-wt origin/main 2>&1 | tail -2 && echo \"=== badge row ===\" && sed -n '9,16p' /tmp/vouch-mcp-badge-wt/README.md", "summary": "Ran: git fetch origin main --quiet && git worktree add /tmp/vouch", "tool": "Bash", "ts": 1783423035.6745927} +{"cmd": "sed -n '14,22p' /tmp/vouch-mcp-badge-wt/README.md", "summary": "Ran: sed -n '14,22p' /tmp/vouch-mcp-badge-wt/README.md", "tool": "Bash", "ts": 1783423044.6886802} +{"files": ["/tmp/vouch-mcp-badge-wt/README.md"], "summary": "Read README.md", "tool": "Read", "ts": 1783423053.5483944} +{"files": ["/tmp/vouch-mcp-badge-wt/README.md"], "summary": "Edited README.md", "tool": "Edit", "ts": 1783423067.131864} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77d2fda2/scratchpad/badge-msg.txt"], "summary": "Created badge-msg.txt", "tool": "Write", "ts": 1783423089.847237} +{"cmd": "cd /tmp/vouch-mcp-badge-wt && git switch -c docs/mcp-registry-badge 2>&1 | tail -1 && git add README.md && git commit -F \"/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/1218169c-de25-48f4-bd93-70ff77", "summary": "Ran: cd /tmp/vouch-mcp-badge-wt && git switch -c docs/mcp-registr", "tool": "Bash", "ts": 1783423103.1097858} +{"cmd": "gh pr create --repo vouchdev/vouch --base main --head docs/mcp-registry-badge \\\n --title \"docs(readme): add mcp registry badge\" \\\n --body \"adds a dynamic shields badge that reads the live version fr", "summary": "Command failed: gh pr create --repo vouchdev/vouch --base main --head docs/m", "tool": "Bash", "ts": 1783423115.6131938} +{"cmd": "gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== PR #419 mergeability ===\" && gh pr view 419 --repo vouchdev/vouch --json mergeable,mergeStateStatus,baseRefName,headRefOid --jq '{mergeable, m", "summary": "Ran: gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== PR", "tool": "Bash", "ts": 1783423770.2269228} +{"cmd": "gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== retarget PR #419 base -> main ===\" && gh pr edit 419 --repo vouchdev/vouch --base main 2>&1 | tail -2; echo \"=== recheck mergeability (give Gi", "summary": "Ran: gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== re", "tool": "Bash", "ts": 1783423812.8079658} +{"cmd": "gh auth switch -u plind-junior >/dev/null 2>&1; gh pr merge 419 --repo vouchdev/vouch --admin --squash --delete-branch 2>&1 | tail -3; echo \"=== confirm badge on main ===\" && git fetch origin main --q", "summary": "Ran: gh auth switch -u plind-junior >/dev/null 2>&1; gh pr merge ", "tool": "Bash", "ts": 1783423836.9901996} +{"cmd": "gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== PR #419 state ===\" && gh pr view 419 --repo vouchdev/vouch --json state,mergedAt --jq '{state, mergedAt}'; echo \"=== badge renders live from m", "summary": "Ran: gh auth switch -u plind-junior >/dev/null 2>&1; echo \"=== PR", "tool": "Bash", "ts": 1783423851.033854} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/vouch-github-write-paths.md"], "summary": "Read vouch-github-write-paths.md", "tool": "Read", "ts": 1783423872.0328524} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/vouch-github-write-paths.md"], "summary": "Edited vouch-github-write-paths.md", "tool": "Edit", "ts": 1783423886.4580438} diff --git a/.vouch/captures/5720220c-b77c-4947-aeec-9d6ef91d70b8.jsonl b/.vouch/captures/5720220c-b77c-4947-aeec-9d6ef91d70b8.jsonl new file mode 100644 index 00000000..8d2467e2 --- /dev/null +++ b/.vouch/captures/5720220c-b77c-4947-aeec-9d6ef91d70b8.jsonl @@ -0,0 +1,19 @@ +{"cmd": "gh pr view 418 --json number,title,headRefName,baseRefName,state,statusCheckRollup", "summary": "Ran: gh pr view 418 --json number,title,headRefName,baseRefName,s", "tool": "Bash", "ts": 1783423093.927102} +{"cmd": "gh pr view 418 --json mergeStateStatus", "summary": "Ran: gh pr view 418 --json mergeStateStatus", "tool": "Bash", "ts": 1783423099.6623337} +{"cmd": "git fetch origin test && git show origin/test --stat | head -20", "summary": "Command failed: git fetch origin test && git show origin/test --stat | head ", "tool": "Bash", "ts": 1783423108.4746783} +{"cmd": "git merge-base origin/main origin/test && git diff origin/main...origin/test --name-only", "summary": "Ran: git merge-base origin/main origin/test && git diff origin/ma", "tool": "Bash", "ts": 1783423112.3211405} +{"cmd": "git switch test 2>&1 || true", "summary": "Command failed: git switch test 2>&1 || true", "tool": "Bash", "ts": 1783423115.6308467} +{"cmd": "\n# Back up local artifacts\nmkdir -p /tmp/vouch-local-backup\nmv .claude/commands .claude/settings.json web /tmp/vouch-local-backup/ 2>/dev/null || true\n\n# Now switch to test branch\ngit switch test\n", "summary": "Ran: ", "tool": "Bash", "ts": 1783423121.803357} +{"cmd": "git merge --abort", "summary": "Ran: git merge --abort", "tool": "Bash", "ts": 1783423136.2827957} +{"cmd": "echo \"=== Last 5 commits on test ===\" && git log --oneline -5 && echo -e \"\\n=== Last 5 commits on main ===\" && git log --oneline -5 origin/main", "summary": "Ran: echo \"=== Last 5 commits on test ===\" && git log --oneline -", "tool": "Bash", "ts": 1783423158.2759066} +{"cmd": "git log --oneline origin/main..test | head -20", "summary": "Ran: git log --oneline origin/main..test | head -20", "tool": "Bash", "ts": 1783423164.307516} +{"cmd": "git status", "summary": "Ran: git status", "tool": "Bash", "ts": 1783423223.8349576} +{"cmd": "git merge --abort", "summary": "Ran: git merge --abort", "tool": "Bash", "ts": 1783423233.6671906} +{"cmd": "\ngit diff CHANGELOG.md | head -100\n", "summary": "Ran: ", "tool": "Bash", "ts": 1783423621.2922633} +{"cmd": "grep -n \"^<<<<<<< \\|^======= \\|^>>>>>>>\" CHANGELOG.md", "summary": "Ran: grep -n \"^<<<<<<< \\|^======= \\|^>>>>>>>\" CHANGELOG.md", "tool": "Bash", "ts": 1783423624.135201} +{"cmd": "git diff --name-only --diff-filter=U", "summary": "Ran: git diff --name-only --diff-filter=U", "tool": "Bash", "ts": 1783423626.975001} +{"cmd": "git status", "summary": "Ran: git status", "tool": "Bash", "ts": 1783423629.8912878} +{"cmd": "\ngit fetch origin && \\\ngit checkout -b final-test origin/main && \\\ngit merge --no-ff --no-commit origin/resolve-conflicts 2>&1 | head -5 && \\\necho \"---\" && \\\ngit status | grep -E \"nothing to commit|wo", "summary": "Ran: ", "tool": "Bash", "ts": 1783423765.7631629} +{"cmd": "\ngit merge --abort\ngit log --oneline origin/main..origin/resolve-conflicts\n", "summary": "Command failed: ", "tool": "Bash", "ts": 1783423776.9763196} +{"cmd": "\ngit fetch origin main && \\\ngit checkout -b final-check origin/main && \\\ngit merge --no-commit --no-ff origin/test && \\\necho \"\u2705 MERGE SUCCEEDS CLEANLY\" && \\\ngit merge --abort\n", "summary": "Ran: ", "tool": "Bash", "ts": 1783424451.3126318} +{"cmd": "git merge --abort", "summary": "Ran: git merge --abort", "tool": "Bash", "ts": 1783424561.0509841} diff --git a/.vouch/captures/ec4b8842-f167-467e-a33c-dc87911c9e0e.jsonl b/.vouch/captures/ec4b8842-f167-467e-a33c-dc87911c9e0e.jsonl new file mode 100644 index 00000000..25531a4f --- /dev/null +++ b/.vouch/captures/ec4b8842-f167-467e-a33c-dc87911c9e0e.jsonl @@ -0,0 +1,36 @@ +{"cmd": "gh pr view 413 --repo vouchdev/vouch --json title,headRefName,baseRefName,state,statusCheckRollup,url,body 2>&1 | head -100", "summary": "Ran: gh pr view 413 --repo vouchdev/vouch --json title,headRefNam", "tool": "Bash", "ts": 1783420285.3371742} +{"cmd": "git status --short && echo \"---BRANCH---\" && git branch --show-current && echo \"---REMOTES---\" && git remote -v", "summary": "Ran: git status --short && echo \"---BRANCH---\" && git branch --sh", "tool": "Bash", "ts": 1783420285.6137311} +{"cmd": "gh run view 28858525725 --repo vouchdev/vouch --log-failed 2>&1 | head -120", "summary": "Command failed: gh run view 28858525725 --repo vouchdev/vouch --log-failed 2", "tool": "Bash", "ts": 1783420301.0642297} +{"cmd": "git fetch origin main test chore/untrack-owner-local 2>&1 | tail -5", "summary": "Ran: git fetch origin main test chore/untrack-owner-local 2>&1 | ", "tool": "Bash", "ts": 1783420322.5310986} +{"cmd": "echo \"=== test_capabilities.py on PR branch (lines 1-70) ===\"; git show origin/chore/untrack-owner-local:tests/test_capabilities.py 2>&1 | sed -n '1,70p'", "summary": "Ran: echo \"=== test_capabilities.py on PR branch (lines 1-70) ===", "tool": "Bash", "ts": 1783420328.6267447} +{"cmd": "echo \"=== openclaw.plugin.json on PR branch ===\"; git show origin/chore/untrack-owner-local:openclaw.plugin.json 2>&1\necho \"\"\necho \"=== openclaw.plugin.json on origin/test ===\"; git show origin/test:o", "summary": "Ran: echo \"=== openclaw.plugin.json on PR branch ===\"; git show o", "tool": "Bash", "ts": 1783420334.4674866} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\necho \"=== Does test_capabilities.py differ: PR vs test ===\"\ngit diff origin/test origin/chore/untrack-owner-local -- tests/test_capabilities.py | head -40\necho \"\"\nech", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420351.2940838} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\necho \"=== manifest sync test: what fields are rejected ===\"\ngit show origin/test:tests/test_openclaw_plugin_manifest.py 2>&1 | grep -n \"openclaw\\|dead\\|reject\\|forbid", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420412.5426834} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\necho \"=== commit d416e65 (host-compat baseline): what path did it read? ===\"\ngit show d416e65 -- src/vouch/capabilities.py tests/test_capabilities.py 2>&1 | grep -n \"", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420472.8251843} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\necho \"=== openclaw.plugin.json at d416e65 (did it have openclaw.compat?) ===\"\ngit show d416e65:openclaw.plugin.json 2>&1 | grep -n \"openclaw\\|compat\\|pluginApi\" | hea", "summary": "Command failed: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420494.4138958} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\necho \"=== full test_capabilities.py on origin/test (70-end) ===\"\ngit show origin/test:tests/test_capabilities.py 2>&1 | sed -n '68,140p'\necho \"\"\necho \"=== other refer", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420547.499702} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\ngit worktree add -b fix/host-compat-reads-package-json /tmp/vouch-hostcompat-wt origin/test 2>&1 | tail -5\necho \"=== EXIT: $? ===\"\ncd /tmp/vouch-hostcompat-wt && git ", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783420612.0641134} +{"files": ["/tmp/vouch-hostcompat-wt/src/vouch/capabilities.py"], "summary": "Read capabilities.py", "tool": "Read", "ts": 1783420619.611568} +{"files": ["/tmp/vouch-hostcompat-wt/src/vouch/capabilities.py"], "summary": "Edited capabilities.py", "tool": "Edit", "ts": 1783420633.7029052} +{"files": ["/tmp/vouch-hostcompat-wt/tests/test_capabilities.py"], "summary": "Read test_capabilities.py", "tool": "Read", "ts": 1783420650.6349075} +{"files": ["/tmp/vouch-hostcompat-wt/tests/test_capabilities.py"], "summary": "Edited test_capabilities.py", "tool": "Edit", "ts": 1783420659.2362714} +{"cmd": "cd /tmp/vouch-hostcompat-wt\necho \"=== any stale refs? ===\"\ngrep -rn \"_PLUGIN_MANIFEST_PATH\\|_manifest_plugin_api\\|_MANIFEST_PATH\" src/vouch/capabilities.py tests/test_capabilities.py || echo \"none \u2014 c", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783420727.5925763} +{"cmd": "cd /tmp/vouch-hostcompat-wt\npython3 -m venv .venv 2>&1 | tail -2\n. .venv/bin/activate\npython -m pip install -q --upgrade pip 2>&1 | tail -2\npython -m pip install -q -e '.[dev]' 2>&1 | tail -15\necho \"=", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783420790.9049978} +{"cmd": "cd /tmp/vouch-hostcompat-wt\n. .venv/bin/activate\necho \"=== pytest summary line ===\"\npython -m pytest tests/ -q --ignore=tests/embeddings 2>&1 | grep -E \"passed|failed|error\"\necho \"\"\necho \"=== mypy src", "summary": "Command failed: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783420907.2344725} +{"cmd": "cd /tmp/vouch-hostcompat-wt\n. .venv/bin/activate\npython -m pip install -q jinja2 2>&1 | tail -2\necho \"=== mypy src (with jinja2) ===\"\npython -m mypy src 2>&1 | tail -4\necho \"\"\necho \"=== pytest count =", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783420970.277363} +{"cmd": "cd /tmp/vouch-hostcompat-wt\n. .venv/bin/activate\npython -m pytest tests/ --ignore=tests/embeddings -q -p no:cacheprovider > /tmp/vouch-hostcompat-wt/pytest.out 2>&1\necho \"exit=$?\"\ntail -4 /tmp/vouch-h", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783421070.6548836} +{"cmd": "cd /tmp/vouch-hostcompat-wt\ngit diff --stat\necho \"======================================================\"\ngit diff", "summary": "Command failed: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783421087.2713645} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/ec4b8842-f167-467e-a33c-dc87911c9e0e/scratchpad/commit-msg.txt"], "summary": "Created commit-msg.txt", "tool": "Write", "ts": 1783421138.0006502} +{"cmd": "cd /tmp/vouch-hostcompat-wt\ngit add src/vouch/capabilities.py tests/test_capabilities.py\ngit commit -F /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/ec4b8842-f167-467e-a33c-dc87911c9e0e/scratchpad/c", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783422667.1229622} +{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/ec4b8842-f167-467e-a33c-dc87911c9e0e/scratchpad/pr-body.md"], "summary": "Created pr-body.md", "tool": "Write", "ts": 1783422712.2533314} +{"cmd": "cd /tmp/vouch-hostcompat-wt\ngh pr create --repo vouchdev/vouch --base test --head fix/host-compat-reads-package-json \\\n --title \"fix(capabilities): read openclaw.compat from package.json\" \\\n --body-", "summary": "Command failed: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783422747.202466} +{"cmd": "gh auth status 2>&1 | head -20\necho \"=== active account ===\"\ngh api user --jq .login 2>&1", "summary": "Ran: gh auth status 2>&1 | head -20", "tool": "Bash", "ts": 1783422770.190565} +{"cmd": "gh auth switch --user plind-junior 2>&1 | tail -3\necho \"=== active account now ===\"\ngh api user --jq .login 2>&1", "summary": "Ran: gh auth switch --user plind-junior 2>&1 | tail -3", "tool": "Bash", "ts": 1783422780.530343} +{"cmd": "cd /tmp/vouch-hostcompat-wt\ngh pr create --repo vouchdev/vouch --base test --head fix/host-compat-reads-package-json \\\n --title \"fix(capabilities): read openclaw.compat from package.json\" \\\n --body-", "summary": "Ran: cd /tmp/vouch-hostcompat-wt", "tool": "Bash", "ts": 1783422792.7796779} +{"cmd": "gh auth switch --user dripsmvcp 2>&1 | tail -1\necho \"=== PR #417 checks (may still be queuing) ===\"\nsleep 8\ngh pr checks 417 --repo vouchdev/vouch 2>&1 | head -15 || echo \"(checks not yet registered)\"", "summary": "Ran: gh auth switch --user dripsmvcp 2>&1 | tail -1", "tool": "Bash", "ts": 1783422815.4791303} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\ntimeout 240 gh pr checks 417 --repo vouchdev/vouch --watch --interval 15 2>&1 | tail -15\necho \"=== final exit: $? ===\"", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783422917.5198302} +{"cmd": "gh auth switch --user plind-junior 2>&1 | tail -1\necho \"=== allowed merge methods on repo ===\"\ngh api repos/vouchdev/vouch --jq '{merge: .allow_merge_commit, squash: .allow_squash_merge, rebase: .allo", "summary": "Ran: gh auth switch --user plind-junior 2>&1 | tail -1", "tool": "Bash", "ts": 1783422998.4361453} +{"cmd": "cd /home/a/Dev/plind-junior/vouch\ngh pr merge 417 --repo vouchdev/vouch --squash 2>&1 | tail -5\necho \"=== merge exit: $? ===\"\ngh pr view 417 --repo vouchdev/vouch --json state,mergedAt --jq '{state, m", "summary": "Ran: cd /home/a/Dev/plind-junior/vouch", "tool": "Bash", "ts": 1783423025.531665} +{"cmd": "gh auth switch --user dripsmvcp 2>&1 | tail -1\ncd /home/a/Dev/plind-junior/vouch\nsleep 12\necho \"=== #413 latest commit + checks ===\"\ngh pr view 413 --repo vouchdev/vouch --json headRefOid --jq .headRe", "summary": "Ran: gh auth switch --user dripsmvcp 2>&1 | tail -1", "tool": "Bash", "ts": 1783423070.8068275} +{"cmd": "gh auth switch --user dripsmvcp 2>&1 | tail -1\ngh api user --jq .login 2>&1", "summary": "Ran: gh auth switch --user dripsmvcp 2>&1 | tail -1", "tool": "Bash", "ts": 1783423220.3171566} +{"files": ["/home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/vouch-github-write-paths.md"], "summary": "Read vouch-github-write-paths.md", "tool": "Read", "ts": 1783423238.9971538} diff --git a/.vouch/claims/vouch-starter-reviewed-knowledge.yaml b/.vouch/claims/vouch-starter-reviewed-knowledge.yaml new file mode 100644 index 00000000..f8d8caa9 --- /dev/null +++ b/.vouch/claims/vouch-starter-reviewed-knowledge.yaml @@ -0,0 +1,23 @@ +id: vouch-starter-reviewed-knowledge +text: Vouch stores reviewed, cited knowledge in the repository so future agent sessions + can retrieve agreed project context. +type: workflow +status: actionable +confidence: 0.95 +evidence: +- be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2 +entities: [] +supersedes: [] +superseded_by: null +contradicts: [] +scope: + visibility: project + project: null + agent: null +tags: +- vouch +- onboarding +created_at: '2026-07-07T09:17:56.750429Z' +updated_at: '2026-07-07T09:17:56.750430Z' +last_confirmed_at: null +approved_by: a diff --git a/.vouch/config.yaml b/.vouch/config.yaml index 108d471b..43c0ec1e 100644 --- a/.vouch/config.yaml +++ b/.vouch/config.yaml @@ -5,3 +5,5 @@ retrieval: backends: - fts5 - substring +compile: + llm_cmd: "claude -p --model sonnet" \ No newline at end of file diff --git a/.vouch/decided/20260707-092531-154cb163.yaml b/.vouch/decided/20260707-092531-154cb163.yaml new file mode 100644 index 00000000..6159d4c2 --- /dev/null +++ b/.vouch/decided/20260707-092531-154cb163.yaml @@ -0,0 +1,45 @@ +id: 20260707-092531-154cb163 +kind: page +proposed_by: vouch-capture +session_id: 4537bcac-57f1-4ec8-8a8e-cb50c39944aa +proposed_at: '2026-07-07T09:25:31.884505Z' +payload: + id: session-review-the-codebase-very-very-very-deeply-and-carefu + title: 'session: review the codebase very very very deeply and carefully analyze… + [vouch]' + body: "# session: review the codebase very very very deeply and carefully analyze…\ + \ [vouch]\n\n- generated: 2026-07-07T09:25:31.875312+00:00\n- session: `4537bcac-57f1-4ec8-8a8e-cb50c39944aa`\n\ + - observations: 0\n\n## prompt\n\n> review the codebase very very very deeply\ + \ and carefully analyze what is better than us. report me the plan to upgrade\n\ + \n## files modified this session\n\n- .vouch/.gitignore\n- .vouch/audit.log.jsonl\n\ + - .vouch/claims/vouch-uses-a-review-gated-proposal-workflow-agents-propose-c.yaml\n\ + - .vouch/config.yaml\n- .vouch/decided/20260521-055206-7d6d92d6.yaml\n- .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/content\n\ + - .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/meta.yaml\n\ + - .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/content\n\ + - .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/meta.yaml\n\ + - CHANGELOG.md\n- README.md\n- openclaw.plugin.json\n- package.json\n- pyproject.toml\n\ + - src/vouch/__init__.py\n\n## git changes\n\n```\n.vouch/.gitignore \ + \ | 1 +\n .vouch/audit.log.jsonl \ + \ | 10 +-\n ...w-gated-proposal-workflow-agents-propose-c.yaml | 22\ + \ --\n .vouch/config.yaml | 20 +-\n .vouch/decided/20260521-055206-7d6d92d6.yaml\ + \ | 25 ---\n .../content | 6 -\n\ + \ .../meta.yaml | 12 --\n .../content \ + \ | 236 ---------------------\n .../meta.yaml\ + \ | 12 --\n CHANGELOG.md \ + \ | 13 ++\n README.md \ + \ | 5 +\n openclaw.plugin.json | 2\ + \ +-\n package.json | 2 +-\n pyproject.toml\ + \ | 6 +-\n src/vouch/__init__.py \ + \ | 2 +-\n 15 files changed, 45 insertions(+), 329 deletions(-)\n\ + ```\n" + type: session + claims: [] + entities: [] + sources: [] + tags: [] + metadata: {} +rationale: auto-captured session summary +status: rejected +decided_at: '2026-07-07T09:30:38.823259Z' +decided_by: unknown-agent +decision_reason: 'yes' diff --git a/.vouch/decided/20260707-093005-05345f47.yaml b/.vouch/decided/20260707-093005-05345f47.yaml new file mode 100644 index 00000000..24b9d52e --- /dev/null +++ b/.vouch/decided/20260707-093005-05345f47.yaml @@ -0,0 +1,59 @@ +id: 20260707-093005-05345f47 +kind: page +proposed_by: vouch-capture +session_id: e5ead202-24c2-4916-b59c-ddb4c123d63f +proposed_at: '2026-07-07T09:30:05.602098Z' +payload: + id: session-what-are-the-non-development-related-files-and-folde + title: 'session: what are the non-development related files and folders in this… + [vouch]' + body: "# session: what are the non-development related files and folders in this…\ + \ [vouch]\n\n- generated: 2026-07-07T09:30:05.593327+00:00\n- session: `e5ead202-24c2-4916-b59c-ddb4c123d63f`\n\ + - observations: 11\n\n## prompt\n\n> what are the non-development related files\ + \ and folders in this project? list down them\n\n## files modified this session\n\ + \n- .gitignore\n- .vouch/.gitignore\n- .vouch/audit.log.jsonl\n- .vouch/claims/vouch-uses-a-review-gated-proposal-workflow-agents-propose-c.yaml\n\ + - .vouch/config.yaml\n- .vouch/decided/20260521-055206-7d6d92d6.yaml\n- .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/content\n\ + - .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/meta.yaml\n\ + - .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/content\n\ + - .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/meta.yaml\n\ + - /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md\n-\ + \ /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/commit-scope-src-adapters.md\n\ + - /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py\n\ + \n## git changes\n\n```\n.gitignore |\ + \ 2 +\n .vouch/.gitignore | 1 +\n .vouch/audit.log.jsonl\ + \ | 11 +-\n ...w-gated-proposal-workflow-agents-propose-c.yaml\ + \ | 22 --\n .vouch/config.yaml | 20 +-\n .vouch/decided/20260521-055206-7d6d92d6.yaml\ + \ | 25 ---\n .../content | 6 -\n\ + \ .../meta.yaml | 12 --\n .../content \ + \ | 236 ---------------------\n .../meta.yaml\ + \ | 12 --\n 10 files changed, 22 insertions(+),\ + \ 325 deletions(-)\n```\n\n## activity\n\n- Bash: 7\n- Edit: 1\n- Read: 1\n- Write:\ + \ 2\n\n## notable commands\n\n- `ls -la`\n- `git ls-files | sed 's|/.*||' | sort\ + \ -u`\n- `ls -la .claude/ 2>/dev/null; echo \"---- settings.json ----\"; cat .claude/settings.json\ + \ 2>/dev/null || echo \"(none)\"; echo \"---- settings.local.json ----\"; cat\ + \ .claude/settings.local.json 2>/dev/null |`\n- `git ls-files .claude/ | head;\ + \ echo \"--- check-ignore ---\"; git check-ignore .claude/settings.local.json\ + \ .claude/settings.json 2>/dev/null; echo \"exit:$?\"; echo \"--- status of .claude\ + \ ---\"; git status `\n- `echo \"=== test 1: mixed list (should ask, listing only\ + \ non-src/adapters) ===\"\noutside=$(printf 'src/vouch/foo.py\\nadapters/claude-code/x.md\\\ + nREADME.md\\n.vouch/config.yaml\\ndocs/a.md\\n' | grep -vE '^(s`\n- `python3 /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py`\n\ + - `cd /home/a/Dev/plind-junior/vouch\n\necho \"=== schema validation (jq -e finds\ + \ the command) ===\"\njq -e '.hooks.PreToolUse[] | select(.matcher==\"Bash\")\ + \ | .hooks[] | select(.[\"if\"]==\"Bash(git commit*)\") | `\n\n## observations\n\ + \n- Ran: ls -la\n- Ran: git ls-files | sed 's|/.*||' | sort -u\n- Ran: ls -la\ + \ .claude/ 2>/dev/null; echo \"---- settings.json ----\";\n- Ran: git ls-files\ + \ .claude/ | head; echo \"--- check-ignore ---\"; g\n- Ran: echo \"=== test 1:\ + \ mixed list (should ask, listing only non-s\n- Created merge_hook.py\n- Ran:\ + \ python3 /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ea\n- Ran: cd /home/a/Dev/plind-junior/vouch\n\ + - Created commit-scope-src-adapters.md\n- Read MEMORY.md\n- Edited MEMORY.md\n" + type: session + claims: [] + entities: [] + sources: [] + tags: [] + metadata: {} +rationale: auto-captured session summary +status: approved +decided_at: '2026-07-07T09:30:42.658758Z' +decided_by: unknown-agent +decision_reason: null diff --git a/.vouch/decided/20260707-093125-4d6972f9.yaml b/.vouch/decided/20260707-093125-4d6972f9.yaml new file mode 100644 index 00000000..e19cda86 --- /dev/null +++ b/.vouch/decided/20260707-093125-4d6972f9.yaml @@ -0,0 +1,33 @@ +id: 20260707-093125-4d6972f9 +kind: page +proposed_by: wiki-compiler +session_id: null +proposed_at: '2026-07-07T09:31:25.619173Z' +payload: + id: reviewed-knowledge-store + title: reviewed-knowledge-store + body: 'Vouch maintains a curated knowledge store inside the repository — a set of + durable, cited topic pages that capture agreed project context. [claim: vouch-starter-reviewed-knowledge] + Rather than relying on session memory or ad-hoc notes, every load-bearing claim + is reviewed before it enters the store and carries an inline citation back to + its source. [claim: vouch-starter-reviewed-knowledge] This means a future agent + or human starting a new session can read the wiki first and immediately recover + the reasoning behind key decisions, without replaying conversation history. [claim: + vouch-starter-reviewed-knowledge] The store is intentionally small and durable: + ephemeral session records are raw material, not final pages. Only synthesized, + topic-oriented content is promoted here.' + type: concept + claims: + - vouch-starter-reviewed-knowledge + entities: [] + sources: [] + tags: + - wiki + - compiled + metadata: {} +rationale: compiled from approved claims; every inline citation was verified against + the store +status: approved +decided_at: '2026-07-07T09:31:32.162974Z' +decided_by: unknown-agent +decision_reason: null diff --git a/.vouch/pages/edit-in-obsidian.md b/.vouch/pages/edit-in-obsidian.md new file mode 100644 index 00000000..85520d99 --- /dev/null +++ b/.vouch/pages/edit-in-obsidian.md @@ -0,0 +1,36 @@ +--- +id: edit-in-obsidian +title: Edit in Obsidian +type: workflow +status: active +claims: +- vouch-starter-reviewed-knowledge +entities: [] +sources: +- be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2 +tags: +- vouch +- onboarding +- obsidian +metadata: {} +created_at: '2026-07-07T09:17:56.750983Z' +updated_at: '2026-07-07T09:17:56.750984Z' +--- +# Edit in Obsidian + +Vouch's pages are plain markdown with YAML frontmatter -- your knowledge +base is already an Obsidian-compatible vault. To edit pages in your own +Obsidian vault: + +1. Run `vouch sync --vault ~/Obsidian/YourVault` once to mirror approved + pages and claims under `/vouch/`. +2. Open `/vouch/pages/.md` in Obsidian and edit it. +3. Re-run `vouch sync --vault ~/Obsidian/YourVault` to file your edits as + page-edit proposals in `.vouch/proposed/`. +4. Review and approve with `vouch approve `. The next sync mirrors the + approved version back into the vault. + +Claims appear as stub markdown files under `/vouch/claims/`; pages +that cite them are linked via Obsidian `[[wikilink]]` syntax so the graph +view connects them. Use `--watch` to keep a polling loop alive while you +edit. diff --git a/.vouch/pages/reviewed-knowledge-store.md b/.vouch/pages/reviewed-knowledge-store.md new file mode 100644 index 00000000..dc4c7728 --- /dev/null +++ b/.vouch/pages/reviewed-knowledge-store.md @@ -0,0 +1,17 @@ +--- +id: reviewed-knowledge-store +title: reviewed-knowledge-store +type: concept +status: draft +claims: +- vouch-starter-reviewed-knowledge +entities: [] +sources: [] +tags: +- wiki +- compiled +metadata: {} +created_at: '2026-07-07T09:31:32.159920Z' +updated_at: '2026-07-07T09:31:32.159923Z' +--- +Vouch maintains a curated knowledge store inside the repository — a set of durable, cited topic pages that capture agreed project context. [claim: vouch-starter-reviewed-knowledge] Rather than relying on session memory or ad-hoc notes, every load-bearing claim is reviewed before it enters the store and carries an inline citation back to its source. [claim: vouch-starter-reviewed-knowledge] This means a future agent or human starting a new session can read the wiki first and immediately recover the reasoning behind key decisions, without replaying conversation history. [claim: vouch-starter-reviewed-knowledge] The store is intentionally small and durable: ephemeral session records are raw material, not final pages. Only synthesized, topic-oriented content is promoted here. \ No newline at end of file diff --git a/.vouch/pages/session-what-are-the-non-development-related-files-and-folde.md b/.vouch/pages/session-what-are-the-non-development-related-files-and-folde.md new file mode 100644 index 00000000..51639338 --- /dev/null +++ b/.vouch/pages/session-what-are-the-non-development-related-files-and-folde.md @@ -0,0 +1,89 @@ +--- +id: session-what-are-the-non-development-related-files-and-folde +title: 'session: what are the non-development related files and folders in this… [vouch]' +type: session +status: draft +claims: [] +entities: [] +sources: [] +tags: [] +metadata: {} +created_at: '2026-07-07T09:30:42.654973Z' +updated_at: '2026-07-07T09:30:42.654976Z' +--- +# session: what are the non-development related files and folders in this… [vouch] + +- generated: 2026-07-07T09:30:05.593327+00:00 +- session: `e5ead202-24c2-4916-b59c-ddb4c123d63f` +- observations: 11 + +## prompt + +> what are the non-development related files and folders in this project? list down them + +## files modified this session + +- .gitignore +- .vouch/.gitignore +- .vouch/audit.log.jsonl +- .vouch/claims/vouch-uses-a-review-gated-proposal-workflow-agents-propose-c.yaml +- .vouch/config.yaml +- .vouch/decided/20260521-055206-7d6d92d6.yaml +- .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/content +- .vouch/sources/06d8519f8dcf4149d23c8a48984541b2e9365ec364e7e58192e28ed149a2c47c/meta.yaml +- .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/content +- .vouch/sources/67478e72acfb8fac3a059143e95c95f5cc6f7e8d4dccc05fbcea8dbccb8a4eba/meta.yaml +- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/MEMORY.md +- /home/a/.claude/projects/-home-a-Dev-plind-junior-vouch/memory/commit-scope-src-adapters.md +- /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py + +## git changes + +``` +.gitignore | 2 + + .vouch/.gitignore | 1 + + .vouch/audit.log.jsonl | 11 +- + ...w-gated-proposal-workflow-agents-propose-c.yaml | 22 -- + .vouch/config.yaml | 20 +- + .vouch/decided/20260521-055206-7d6d92d6.yaml | 25 --- + .../content | 6 - + .../meta.yaml | 12 -- + .../content | 236 --------------------- + .../meta.yaml | 12 -- + 10 files changed, 22 insertions(+), 325 deletions(-) +``` + +## activity + +- Bash: 7 +- Edit: 1 +- Read: 1 +- Write: 2 + +## notable commands + +- `ls -la` +- `git ls-files | sed 's|/.*||' | sort -u` +- `ls -la .claude/ 2>/dev/null; echo "---- settings.json ----"; cat .claude/settings.json 2>/dev/null || echo "(none)"; echo "---- settings.local.json ----"; cat .claude/settings.local.json 2>/dev/null |` +- `git ls-files .claude/ | head; echo "--- check-ignore ---"; git check-ignore .claude/settings.local.json .claude/settings.json 2>/dev/null; echo "exit:$?"; echo "--- status of .claude ---"; git status ` +- `echo "=== test 1: mixed list (should ask, listing only non-src/adapters) ===" +outside=$(printf 'src/vouch/foo.py\nadapters/claude-code/x.md\nREADME.md\n.vouch/config.yaml\ndocs/a.md\n' | grep -vE '^(s` +- `python3 /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ead202-24c2-4916-b59c-ddb4c123d63f/scratchpad/merge_hook.py` +- `cd /home/a/Dev/plind-junior/vouch + +echo "=== schema validation (jq -e finds the command) ===" +jq -e '.hooks.PreToolUse[] | select(.matcher=="Bash") | .hooks[] | select(.["if"]=="Bash(git commit*)") | ` + +## observations + +- Ran: ls -la +- Ran: git ls-files | sed 's|/.*||' | sort -u +- Ran: ls -la .claude/ 2>/dev/null; echo "---- settings.json ----"; +- Ran: git ls-files .claude/ | head; echo "--- check-ignore ---"; g +- Ran: echo "=== test 1: mixed list (should ask, listing only non-s +- Created merge_hook.py +- Ran: python3 /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/e5ea +- Ran: cd /home/a/Dev/plind-junior/vouch +- Created commit-scope-src-adapters.md +- Read MEMORY.md +- Edited MEMORY.md diff --git a/.vouch/schema_version b/.vouch/schema_version new file mode 100644 index 00000000..6e8bf73a --- /dev/null +++ b/.vouch/schema_version @@ -0,0 +1 @@ +0.1.0 diff --git a/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/content b/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/content new file mode 100644 index 00000000..19c3accb --- /dev/null +++ b/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/content @@ -0,0 +1,7 @@ +# Vouch starter source + +This starter source is created by `vouch init` so new users can see how a +reviewed claim cites durable evidence. + +Keep facts small, cite their sources, and approve only the knowledge you want +future agents to retrieve. diff --git a/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/meta.yaml b/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/meta.yaml new file mode 100644 index 00000000..ab00b64f --- /dev/null +++ b/.vouch/sources/be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2/meta.yaml @@ -0,0 +1,17 @@ +id: be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2 +type: message +locator: vouch:init +title: Vouch starter source +hash: be7aec64b0fc803a33cb3d610f67ae95e636877db20231ef72440a7cbe6b69d2 +immutable: true +scope: + visibility: project + project: null + agent: null +byte_size: 243 +media_type: text/markdown +created_at: '2026-07-07T09:17:56.749963Z' +metadata: {} +tags: +- vouch +- onboarding diff --git a/CHANGELOG.md b/CHANGELOG.md index 986dcb0e..bb2ec563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ All notable changes to vouch are documented here. Format follows ## [Unreleased] +### Added +- `vouch console`: serve the vendored React web console straight from the + installed package — a same-origin `/proxy` bridge (loopback-guarded) to + `vouch serve --transport http` backends, reimplementing the vite dev-proxy + in python. the built SPA is bundled into the wheel as `vouch/web/console` + (conditionally, via a hatch build hook), so `pip install 'vouch-kb[web]'` + then `vouch console` needs no node and no repo clone. +- `kb.activity` read method (+ `vouch activity` CLI mirror): audit-log + activity buckets for dashboards — per-day counts with proposal/decision + breakdowns, an hour-of-week matrix, and actor/event histograms. windowed + in viewer-local calendar days (IANA `tz` or a fixed utc offset), scope- + filtered like `kb.audit`. +- console Dashboard view: 12-month activity calendar, last-30-days bars, + hour-of-week heatmap, top actors and event mix, driven by `kb.activity`. + ## [1.2.2] — 2026-07-07 ### Packaging diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b06f924..2cfdca22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -125,6 +125,9 @@ strict: - A `CHANGELOG.md` entry under `## [Unreleased]` for user-visible changes. - Lowercase prose in the PR body, matching the repo voice. No `Co-Authored-By` or AI-attribution trailers in commits. +- For changes that affect the webapp, CLI output, or user-facing behavior: + screenshots or screenrecording from the vouch-ui webapp showing the + behavior working as intended. ## Commit and PR titles diff --git a/Makefile b/Makefile index 021738a6..523e443f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install dev test test-cov bench lint format type check build clean examples-screenshots smoke-capture smoke-recall +.PHONY: help install dev test test-cov bench lint format type check build clean examples-screenshots smoke-capture smoke-recall console webapp-build PY ?= python PIP ?= $(PY) -m pip @@ -19,6 +19,7 @@ help: @echo " make examples-screenshots re-render docs/img/examples/*.svg" @echo " make smoke-capture end-to-end check of session auto-capture" @echo " make smoke-recall end-to-end check of session-start recall" + @echo " make console run the vouch backend + vouch-ui web console together" install: $(PIP) install -e '.[dev]' @@ -58,9 +59,23 @@ smoke-capture: smoke-recall: VOUCH="$(PY) -m vouch" bash scripts/smoke-recall.sh -build: +# Run the vouch HTTP backend and the vouch-ui web console together (Ctrl-C stops +# both). Installs the web console's node deps on first run. +console: + bash scripts/console.sh + +# Build the React console into webapp/dist so the wheel can bundle it as +# vouch/web/console (hatch_build.py force-includes it when present). Installs +# the web console's node deps on first run. +webapp-build: + cd webapp && { [ -d node_modules ] || npm ci; } && npm run build + +# sdist is source-only; the wheel is built from the working tree (not the +# sdist) so the freshly-built, gitignored webapp/dist rides inside it. +build: webapp-build $(PY) -m pip install --upgrade build - $(PY) -m build + $(PY) -m build --sdist + $(PY) -m build --wheel clean: rm -rf build dist *.egg-info src/*.egg-info \ diff --git a/README.md b/README.md index b2fde3f9..a8ce7a5c 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,6 @@ - -

- vouch — sessions auto-capture into a review-gated knowledge base: propose or capture → review → commit → retrieve -

-

CI PyPI @@ -37,6 +32,27 @@ Everything below exists to reproduce that loop on your own project. ## Install +**For the full UI experience** (recommended first time): + +```bash +docker run --rm -p 127.0.0.1:5173:5173 -v vouch-demo-data:/data ghcr.io/plind-junior/vouch-demo +# then open http://localhost:5173 +``` + +Pre-seeded KB + full webapp console, zero setup. Pass `-e ANTHROPIC_API_KEY=sk-ant-...` to enable LLM features. + +**For the full UI without Docker** — Python only, no clone, no node: + +```bash +pipx install 'vouch-kb[web]' # the browser console ships inside the wheel +vouch serve --transport http --port 8731 & # a backend for the current .vouch/ +vouch console # console at http://localhost:5173 — connect it to :8731 +``` + +`vouch console` serves the same React console as the Docker demo, straight from the installed package. + +**For CLI + Claude Code integration** (most common ongoing workflow): + ```bash # one-liner (Linux + macOS) — picks a Python, ensures pipx, installs vouch-kb curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh @@ -45,7 +61,9 @@ curl -fsSL https://raw.githubusercontent.com/vouchdev/vouch/main/install.sh | sh pipx install vouch-kb ``` -The one-liner is POSIX `sh` and never needs `sudo` — inspect [`install.sh`](install.sh) first if you'd like. Prefer containers? The released image runs the same CLI and MCP server ([`ghcr.io/vouchdev/vouch`](https://github.com/vouchdev/vouch/pkgs/container/vouch)): +The one-liner is POSIX `sh` and never needs `sudo` — inspect [`install.sh`](install.sh) first if you'd like. + +**For MCP server or CLI-only use**: ```bash docker run -i --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch:latest # stdio MCP server @@ -54,15 +72,17 @@ docker run --rm -v "$PWD:/data" ghcr.io/vouchdev/vouch:latest status # any ## Reproduce the loop on your project +After exploring the demo above, set up vouch in your own project: + **1. Set up the KB and wire Claude Code** (one-time, per repo): ```bash cd /path/to/your/project -vouch init -vouch install-mcp claude-code +vouch init # creates .vouch/ with starter config +vouch install-mcp claude-code # wires capture hooks into Claude Code ``` -`init` creates `.vouch/` with a starter config; `install-mcp` writes `.mcp.json` (the `kb.*` MCP tools), the `/vouch-*` slash commands, and three hooks — `PostToolUse` capture, `SessionEnd` rollup, `SessionStart` recall. Restart Claude Code so they load. +`install-mcp` writes `.mcp.json` (the `kb.*` MCP tools), the `/vouch-*` slash commands, and three hooks — `PostToolUse` capture, `SessionEnd` rollup, `SessionStart` recall. Restart Claude Code so they load. **2. Point `compile` at an LLM** — the only step that needs a model. In `.vouch/config.yaml`: @@ -83,16 +103,43 @@ compile: vouch review # walk pending proposals one at a time ``` -The browser console in the video is the **[vouch webapp](https://github.com/vouchdev/webApp)** — chat, review, pending queue, claims, and stats over a running KB. Connect it in two commands: +**Want a browser UI for reviewing and proposing?** The video shows the **vouch webapp** — chat, review queue, claims, and stats. You have four options: + +- **No setup**: Use the Docker demo (recommended) +- **pip, no clone**: `pipx install 'vouch-kb[web]'` then `vouch console` — serves the same React console from the installed package (Python only, no Docker, no node), open http://localhost:5173 +- **Local development**: Clone the repo, run `make console`, open http://localhost:5173 +- **CLI-only**: Use `vouch review`, `vouch show `, `vouch approve ` commands instead + +**Point the webapp at your existing KB:** ```bash -vouch serve --transport http # serves the kb.* surface on 127.0.0.1:8731 -# then, in a clone of the vouch webapp: -npm install && npm run dev # opens http://localhost:5173 — point the - # connect dialog at http://127.0.0.1:8731 +# Terminal 1: start the vouch server pointing at your .vouch/ +cd /path/to/your/project +vouch serve --transport http --port 8731 + +# Terminal 2: run the Docker UI pointing at that server +docker run --rm -p 127.0.0.1:5173:5173 \ + -e VOUCH_TARGET=http://host.docker.internal:8731 \ + ghcr.io/plind-junior/vouch-demo +# then open http://localhost:5173 ``` -Lighter alternatives ship with vouch itself: `vouch review-ui` (a built-in browser queue; `pipx install 'vouch-kb[web]'` for the extra), or piecemeal `vouch pending`, `vouch show `, `vouch approve `, `vouch reject --reason "…"`. +Or serve that same console with no Docker — `vouch console` in place of Terminal 2 (needs the `[web]` extra), then add the `:8731` backend in the connect dialog: + +```bash +vouch console # http://localhost:5173, proxying to the server above +``` + +Or to skip the browser entirely and use the CLI tools: + +```bash +vouch review # walk pending proposals +vouch show # inspect a claim or page +vouch approve # approve a proposal +vouch reject --reason "…" # reject with feedback +``` + +Both browser UIs ship with vouch under the `[web]` extra (`pipx install 'vouch-kb[web]'`): `vouch console` is the full React console shown in the video; `vouch review-ui` is a lighter built-in review queue. Or go piecemeal: `vouch pending`, `vouch show `, `vouch approve `, `vouch reject --reason "…"`. **5. Compile the wiki.** @@ -152,6 +199,10 @@ Pending drafts (`proposed/`) and the derived search index (`state.db`) are gitig * [vouch webapp](https://github.com/vouchdev/webApp) — the chat-first browser console from the video; [vouch-desktop](https://github.com/vouchdev/vouch-desktop) wraps the same loop as a desktop app * [CONTRIBUTING.md](CONTRIBUTING.md) — development setup and the test gate +## Incubated by Gittensor + +Vouch was incubated and supported by [Gittensor](https://gittensor.io), a protocol that rewards open-source contributions. The knowledge-base-as-code pattern and review-gated persistence model emerged directly from conversations about trusted AI agents and long-term memory in collaborative development workflows. + ## License MIT. diff --git a/demo/.env.example b/demo/.env.example new file mode 100644 index 00000000..f4d675ba --- /dev/null +++ b/demo/.env.example @@ -0,0 +1,18 @@ +# vouch demo — environment (optional). Copy to .env to override defaults. +# +# cp .env.example .env + +# Bearer token vouch requires on its HTTP transport. The console injects it +# server-side, so the browser never sees it. Any non-empty value works for the +# local demo; change it if you like. +VOUCH_HTTP_TOKEN=vouch-demo + +# Optional — your own Anthropic API key. Set it to turn on the two LLM-backed +# actions in the console: "Compile" (approved claims -> topic pages) and +# "Summarize session". Leave it blank and the demo still runs; those two just +# report "not configured". The key stays in this container, calls go straight +# to Anthropic, and nothing is committed. +ANTHROPIC_API_KEY= + +# Optional — override the model the shim uses (defaults to a current Sonnet). +# ANTHROPIC_MODEL=claude-sonnet-4-5 diff --git a/demo/Dockerfile b/demo/Dockerfile new file mode 100644 index 00000000..df498de4 --- /dev/null +++ b/demo/Dockerfile @@ -0,0 +1,59 @@ +# syntax=docker/dockerfile:1 +# +# vouch demo — one image, one command. Bundles the vouch server (built from +# THIS checkout, so it carries the newest kb.* surface incl. delete / archive / +# supersede), the vouch-ui web console, and seeds a starter knowledge base on +# first run. `docker compose up` in this folder, open the browser, and explore +# a populated, review-gated KB. +# +# Two runtimes in one image on purpose: python runs `vouch serve`, node serves +# the console via `vite preview` (which reuses the console's own /proxy/* +# middleware, pinned at the in-container vouch endpoint). + +# ---- stage 1: build the console to static (keep the tree for vite preview) --- +FROM node:22-slim AS web +WORKDIR /web +COPY webapp/package.json webapp/package-lock.json ./ +RUN npm ci +COPY webapp/ ./ +RUN npm run build + +# ---- stage 2: runtime = node (console) + python (vouch from source) --------- +FROM node:22-slim +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + LANG=C.UTF-8 \ + NODE_ENV=production \ + VOUCH_UI_ALLOW_REMOTE=1 \ + VOUCH_TARGET=http://127.0.0.1:8731 \ + VOUCH_HTTP_TOKEN=vouch-demo \ + VOUCH_DATA_DIR=/data \ + ANTHROPIC_MODEL=claude-sonnet-4-5 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + python3 python3-venv curl \ + && rm -rf /var/lib/apt/lists/* + +# vouch from this checkout — the [web] extra brings the HTTP transport in. +COPY pyproject.toml README.md /src/ +COPY src /src/src +COPY adapters /src/adapters +RUN python3 -m venv /opt/venv && /opt/venv/bin/pip install --no-cache-dir "/src[web]" +ENV PATH="/opt/venv/bin:$PATH" + +# the built console tree (vite preview needs vite + plugins + dist/ at runtime) +COPY --from=web /web /app/webapp + +# bring-your-own-key LLM shim: reads a prompt on stdin, calls the Anthropic +# Messages API from ANTHROPIC_API_KEY. Wired in as compile.llm_cmd by the +# entrypoint only when a key is present. Stdlib only — runs on the venv python. +COPY demo/vouch-llm.py /usr/local/bin/vouch-llm +RUN chmod +x /usr/local/bin/vouch-llm + + +COPY demo/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +VOLUME ["/data"] +EXPOSE 5173 +ENTRYPOINT ["/entrypoint.sh"] diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 00000000..e47eaaa7 --- /dev/null +++ b/demo/README.md @@ -0,0 +1,121 @@ +# vouch demo — try it in one command + +A self-contained Docker demo of [vouch](https://github.com/vouchdev/vouch), the +git-native, **review-gated** knowledge base for LLM agents. One image bundles +the vouch server and the vouch-ui web console, and seeds a starter knowledge +base on first run — so you open the browser and immediately have something to +explore. + +## Run it (no clone needed) + +One command pulls the published image and starts everything: + +```bash +docker run --rm -p 127.0.0.1:5173:5173 -v vouch-demo-data:/data \ + ghcr.io/plind-junior/vouch-demo +``` + +Then open **http://localhost:5173** and connect with the pre-filled endpoint. + +That's it. The first run seeds a starter KB (a claim, a page, a source), so the +console opens onto a populated, review-gated knowledge base — not an empty one. +Your data persists in the `vouch-demo-data` volume between runs; `Ctrl-C` stops +the demo (the `--rm` removes only the container, never the volume). + +## Update to the latest + +The image is updated in place, so pulling gets you the newest build (and any +fixes). Stop the demo, then: + +```bash +docker pull ghcr.io/plind-junior/vouch-demo # fetch the latest image +``` + +Re-run the command from "Run it" — Docker now starts the updated image, and +your `vouch-demo-data` volume carries over. To start completely fresh instead, +reset the data with `docker volume rm vouch-demo-data` before running. + +## Build from source (to hack on it) + +Working in a clone of this repo? Build the image from the checkout instead of +pulling — it picks up your local changes to `webapp/` and `src/`: + +```bash +cd demo +cp .env.example .env # optional: edit VOUCH_HTTP_TOKEN, add ANTHROPIC_API_KEY +docker compose up --build +``` + +## Turn on the LLM features (optional) + +Two console actions call a language model: **Compile** (turn approved claims +into topic pages) and **Summarize session**. They're off by default because +the demo ships no API key. Everything else — browsing, propose → approve, +delete / archive / supersede, clear queue — works without one. + +To switch them on, give the demo *your own* Anthropic key. With the pulled +image, pass it in with `-e`: + +```bash +docker run --rm -p 127.0.0.1:5173:5173 -v vouch-demo-data:/data \ + -e ANTHROPIC_API_KEY=sk-ant-... \ + ghcr.io/plind-junior/vouch-demo +``` + +Building from source? Put it in `.env` instead: + +```bash +cp .env.example .env # then set ANTHROPIC_API_KEY=sk-ant-... +docker compose up --build +``` + +The key never reaches the browser — vouch runs a tiny stdlib shim +(`vouch-llm`) inside the container that calls the Anthropic Messages API +directly. Override the model with `ANTHROPIC_MODEL` if you want a newer Sonnet. +Without a key, Compile / Summarize simply report "not configured" — that's the +review gate telling you the step is unavailable, not a crash. + +## What you can do in the console + +- **Browse / Claims** — the seeded knowledge, with citations and provenance + ("why does this claim exist?"). +- **Pending** — the propose → approve review gate, made visible. +- **Delete / Archive / Supersede** — retire a claim through the gate: delete + files a review-gated proposal (refused if other pages still cite it, which is + the point), archive hides it from retrieval, supersede replaces it. +- **Clear queue** — reject the whole pending queue at once. + +## How it works + +One container runs two processes (managed by `entrypoint.sh`): + +1. `vouch serve --transport http` on `127.0.0.1:8731` inside the container, with + a bearer token. +2. the console via `vite preview`, whose `/proxy/*` middleware forwards to the + in-container vouch endpoint. The token is **pinned and injected server-side** + (`VOUCH_TARGET` / `VOUCH_HTTP_TOKEN`), so the browser never sees it. + +Only the console port is published, and only on `127.0.0.1` — nothing leaves +your machine. Your KB lives in the `vouch-demo-data` volume: + +```bash +# pulled image (docker run): +Ctrl-C # stop (keeps your data) +docker volume rm vouch-demo-data # reset the demo KB + +# built from source (docker compose): +docker compose down # stop (keeps your data) +docker compose down -v # stop and reset the demo KB +``` + +## Notes + +- The published image (`ghcr.io/plind-junior/vouch-demo`) carries the newest + `kb.*` surface, including delete / archive / supersede — a released `vouch` + from PyPI or `ghcr.io/vouchdev/vouch` would not yet advertise those. Building + from source picks up whatever is in your checkout. +- The LLM-backed actions (Compile, Summarize session) need an + `ANTHROPIC_API_KEY` — see "Turn on the LLM features" above. The rest of the + console is fully functional without one. +- The console's "Chat / Claude Code" mode is a dev-server feature and is not + wired up in this preview build; everything else works. diff --git a/demo/docker-compose.yml b/demo/docker-compose.yml new file mode 100644 index 00000000..8b116b13 --- /dev/null +++ b/demo/docker-compose.yml @@ -0,0 +1,39 @@ +# Try vouch in one command: +# +# cd demo +# docker compose up --build # then open http://localhost:5173 +# +# A starter, review-gated knowledge base is seeded on first run. Your data +# persists in the `vouch-demo-data` volume; `docker compose down -v` resets it. +# Only the console port is published, and only on 127.0.0.1 — nothing leaves +# your machine. +name: vouch-demo + +services: + vouch-demo: + build: + context: .. + dockerfile: demo/Dockerfile + image: vouch-demo:latest + ports: + - "127.0.0.1:5173:5173" + environment: + # in-container bearer token; injected server-side, never sent to the browser + VOUCH_HTTP_TOKEN: ${VOUCH_HTTP_TOKEN:-vouch-demo} + # optional: your own Anthropic key turns on page compile & session + # summaries (Claude). Left empty, the demo still runs — those two actions + # just report "not configured". Never commit a real key. + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-5} + volumes: + - vouch-demo-data:/data + healthcheck: + test: ["CMD", "curl", "-fsS", "-m", "3", "http://127.0.0.1:5173/proxy/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 20s + restart: unless-stopped + +volumes: + vouch-demo-data: diff --git a/demo/entrypoint.sh b/demo/entrypoint.sh new file mode 100644 index 00000000..a3d6869a --- /dev/null +++ b/demo/entrypoint.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# vouch demo entrypoint: seed a starter KB on first run, start the vouch server +# on loopback inside this container, then serve the console. Both processes live +# in one container; killing the container stops both. +set -euo pipefail + +DATA="${VOUCH_DATA_DIR:-/data}" +TOKEN="${VOUCH_HTTP_TOKEN:-vouch-demo}" +# actor recorded in the seeded KB's audit log (avoids getpass in a bare image) +export VOUCH_USER="${VOUCH_USER:-demo}" + +# First run: an empty /data volume gets a seeded, review-gated starter KB so the +# console has something to show. Idempotent — skipped once .vouch/ exists. +if [ ! -d "$DATA/.vouch" ]; then + echo "[demo] seeding a starter knowledge base in $DATA ..." + vouch init --path "$DATA" +fi + +# vouch's LLM features (page compile, session summaries) shell out to the +# command in `compile.llm_cmd`. Support two workflows: +# 1. If Claude CLI is available (~/.claude exists), use `claude -p` to capture +# in Claude sessions (same as the real vouch project). +# 2. If only ANTHROPIC_API_KEY is set, use the direct API shim (vouch-llm). +# 3. If neither, leave LLM features unset so actions return "not configured". +CONFIG="$DATA/.vouch/config.yaml" +LLM_CMD="" +if [ -d "$HOME/.claude" ]; then + LLM_CMD="claude -p --model sonnet-4-5" + echo "[demo] Claude CLI found — LLM features wire to 'claude -p', compile & summarize will capture in Claude sessions." +elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then + LLM_CMD="vouch-llm" + echo "[demo] ANTHROPIC_API_KEY set — LLM features enabled via direct API (compile & summarize will NOT capture in Claude sessions; use 'claude login' + mount ~/.claude to enable session capture)." +else + echo "[demo] LLM features DISABLED — provide ANTHROPIC_API_KEY or mount ~/.claude (with 'claude login' done) to enable compile & summarize." +fi + +if [ -n "$LLM_CMD" ]; then + python3 - "$CONFIG" "$LLM_CMD" <<'PY' +import sys, yaml +path, cmd = sys.argv[1:3] +with open(path) as f: + cfg = yaml.safe_load(f) or {} +cfg.setdefault("compile", {})["llm_cmd"] = cmd +with open(path, "w") as f: + yaml.safe_dump(cfg, f, sort_keys=False) +PY +else + python3 - "$CONFIG" <<'PY' +import sys, yaml +path = sys.argv[1] +with open(path) as f: + cfg = yaml.safe_load(f) or {} +if isinstance(cfg.get("compile"), dict): + cfg["compile"].pop("llm_cmd", None) + if not cfg["compile"]: + cfg.pop("compile") +with open(path, "w") as f: + yaml.safe_dump(cfg, f, sort_keys=False) +PY +fi + +# vouch on loopback (same container). A token is set so the console's proxy can +# inject it server-side; the browser never sees it. +echo "[demo] starting vouch server on 127.0.0.1:8731 ..." +( cd "$DATA" && exec vouch serve --transport http --host 127.0.0.1 --port 8731 --token "$TOKEN" ) & +VOUCH_PID=$! +trap 'kill "$VOUCH_PID" 2>/dev/null || true' EXIT INT TERM + +# Wait for vouch to answer its public liveness probe before starting the UI. +for _ in $(seq 1 30); do + if curl -fsS -m 2 "http://127.0.0.1:8731/health" >/dev/null 2>&1; then break; fi + sleep 1 +done + +echo "[demo] starting the vouch console on :5173 — open http://localhost:5173" +cd /app/webapp +exec npm run preview -- --host 0.0.0.0 --port 5173 diff --git a/demo/vouch-llm.py b/demo/vouch-llm.py new file mode 100644 index 00000000..ead3a180 --- /dev/null +++ b/demo/vouch-llm.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Minimal Anthropic Messages shim for the vouch demo image. + +vouch's LLM-backed features (page compile, session summaries) don't call an +API directly — they shell out to a deployment-configured command +(`compile.llm_cmd` in .vouch/config.yaml) with the prompt on stdin, and read +the model's reply from stdout. In a normal install that command is the local +`claude` CLI. The demo image has no CLI and no baked-in key, so this shim is +the `llm_cmd`: it reads the prompt on stdin and calls the Anthropic Messages +API using a key the *user* supplies via ANTHROPIC_API_KEY. + +Stdlib only (urllib) — no extra pip dependency, mirroring vouch's own client +in src/vouch/pr_cache.py. Emits only the model's text on stdout so vouch's +`parse_drafts` sees a clean JSON array; all diagnostics go to stderr, and a +non-zero exit lets vouch surface a clean "compile.llm_cmd failed" message. + +Env: + ANTHROPIC_API_KEY required — user's key; absent => exit 3, features off. + ANTHROPIC_MODEL default claude-sonnet-4-5 (override for a newer Sonnet). + ANTHROPIC_BASE_URL default https://api.anthropic.com + ANTHROPIC_MAX_TOKENS default 8192 (compile/split return multi-page JSON). + ANTHROPIC_TIMEOUT default 150 (seconds; below vouch's own subprocess cap). +""" +from __future__ import annotations + +import json +import os +import sys +import urllib.error +import urllib.request + + +def main() -> int: + key = os.environ.get("ANTHROPIC_API_KEY", "").strip() + if not key: + sys.stderr.write( + "ANTHROPIC_API_KEY is not set — this demo's LLM features " + "(page compile, session summaries) are disabled. Set the key and " + "restart to enable Claude.\n" + ) + return 3 + + model = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-5").strip() + base = os.environ.get("ANTHROPIC_BASE_URL", "https://api.anthropic.com").rstrip("/") + try: + max_tokens = int(os.environ.get("ANTHROPIC_MAX_TOKENS", "8192")) + timeout = float(os.environ.get("ANTHROPIC_TIMEOUT", "150")) + except ValueError as e: + sys.stderr.write(f"invalid ANTHROPIC_MAX_TOKENS/ANTHROPIC_TIMEOUT: {e}\n") + return 2 + + prompt = sys.stdin.read() + payload = json.dumps({ + "model": model, + "max_tokens": max_tokens, + "messages": [{"role": "user", "content": prompt}], + }).encode() + req = urllib.request.Request( + f"{base}/v1/messages", + data=payload, + method="POST", + headers={ + "content-type": "application/json", + "x-api-key": key, + "anthropic-version": "2023-06-01", + }, + ) + + try: + with urllib.request.urlopen(req, timeout=timeout) as resp: + body = resp.read() + except urllib.error.HTTPError as e: + detail = (e.read().decode("utf-8", "replace") or "").strip()[:400] + sys.stderr.write(f"anthropic API {e.code}: {detail}\n") + return 1 + except (urllib.error.URLError, TimeoutError) as e: + sys.stderr.write(f"anthropic API call failed: {e}\n") + return 1 + + try: + data = json.loads(body) + except json.JSONDecodeError: + sys.stderr.write(f"anthropic API returned non-JSON: {body[:200]!r}\n") + return 1 + + text = "".join( + block.get("text", "") + for block in (data.get("content") or []) + if isinstance(block, dict) and block.get("type") == "text" + ) + if not text.strip(): + sys.stderr.write(f"anthropic API returned no text content: {body[:200]!r}\n") + return 1 + + sys.stdout.write(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/desktop/.gitignore b/desktop/.gitignore deleted file mode 100644 index 014eb209..00000000 --- a/desktop/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# deps -node_modules/ -src/renderer/node_modules/ - -# build output -dist/ -out/ -release/ -*.log -*.tsbuildinfo - -# os -.DS_Store -Thumbs.db - -# editor / local -.vscode/ -.idea/ -.claude/ -.superpowers/ -docs/superpowers/ -docs/releases/ -*.local -.env diff --git a/desktop/CHANGELOG.md b/desktop/CHANGELOG.md deleted file mode 100644 index bc5972f1..00000000 --- a/desktop/CHANGELOG.md +++ /dev/null @@ -1,64 +0,0 @@ -# Changelog - -All notable changes to vouch-desktop are documented here. The format follows -[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - -## [1.0.0] — 2026-06-26 - -### Changed -- **Reshaped to React + TypeScript + electron-vite.** The renderer is rebuilt as - React 18 components (`src/renderer/src/`); main and preload are converted from - plain JS to TypeScript. electron-vite replaces the buildless setup, producing - three typed targets (main → CJS, preload → CJS, renderer → React/ESM). -- **Typed IPC contract.** `src/shared/ipc.ts` declares the `VouchApi` interface - and all channel payload/return types; main, preload, and renderer share it as a - single source of truth. No behavior change — same channels, same semantics. -- **Generated typed catalog.** `scripts/gen-methods.ts` now emits - `src/shared/methods.gen.ts` (a typed `Method[]` array + a `MethodName` union) - instead of a plain JSON file. The generator's `enrich()` function is unit-tested. -- All 11 views, the dark theme, the form generator, result renderers, review gate, - and dual-solve runner are faithful ports — **behavior unchanged**. -- Tests migrated to Vitest; 85 tests covering catalog, gen-methods, vouch-locator, - http-client, jsonl-client, MethodForm collect parity, controls, and MethodCard. - -## [0.1.0] — 2026-06-26 - -### Added -- Initial Electron desktop app: a GUI over the full vouch `kb.*` command surface - (all 54 methods), plus a bespoke dual-solve runner. -- **Process bridge.** Main-process `JsonlClient` spawns `vouch serve --transport - jsonl` and exchanges newline-delimited JSON, correlating responses by `id`, - with per-method timeouts (long ops get 10 min). A `Supervisor` health-polls - `kb.status`, restarts the child with backoff, and surfaces process state. -- **Lazy dual-solve.** An `HttpClient` spawns - `vouch review-ui --allow-dual-solve --dual-solve-sandbox` only when the - Dual-Solve view is opened, drives `/dual-solve/run|job|choose`, and streams - phase progress over the review-ui `/ws`. The review queue itself runs over - JSONL, so the rest of the app needs no `[web]` extra. -- **Data-driven forms.** A single form generator builds typed controls from a - verified parameter catalog (`scripts/gen-methods.ts` → `src/shared/methods.gen.ts`): - text/textarea, number/slider, toggle, tag input, JSON editor, enum select and - combobox (option lists taken from vouch's own model enums), native file/save - pickers, and a search-backed id typeahead for reference params. -- **Views.** Dashboard, Search & Ask, Browse, Propose, Review & Lifecycle (with a - pending-queue + approve/reject), Sessions, Graph, Maintenance, Export/Import, - Audit, and Dual-Solve. A detail drawer opens any claim/page/entity/relation. -- **Capability-gating.** Methods not advertised by the connected vouch are shown - disabled; views light up automatically when vouch is upgraded. -- **Companion.** Tray icon with pending-count badge + KB switcher, native OS - notifications (dual-solve ready, new pending proposals, process down), and a - no-terminal launch flow (open/init a KB, the app supervises vouch). -- **Review gate preserved.** The UI never auto-approves; durable writes only ever - happen through vouch's own `propose → approve` flow. -- Sandboxed renderer (`contextIsolation`, `sandbox`, no node), a single frozen - `window.vouch` preload bridge, and a tight Content-Security-Policy. -- electron-builder config for dmg / nsis / AppImage; a layered vouch locator - (configured path → bundled-frozen → PATH → `python -m vouch`). -- Tests: a catalog-coverage suite (`npm test`) and a JSONL smoke harness against - a real vouch binary (`npm run smoke `). - -### Not yet covered -- The CLI-only orchestration commands `auto-pr`, `migrate`, `schema`, and - `sync-check`/`sync-apply` are not yet surfaced in the UI. -- Packaging a bundled, frozen vouch (PyInstaller) for zero-Python installs is - scaffolded (`scripts/freeze-vouch.sh` slot, `extraResources`) but not built. diff --git a/desktop/LICENSE b/desktop/LICENSE deleted file mode 100644 index b4d30ffd..00000000 --- a/desktop/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 vouch-desktop contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/desktop/README.md b/desktop/README.md deleted file mode 100644 index 08c49cf2..00000000 --- a/desktop/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# vouch-desktop - -A cross-platform **Electron** desktop app that puts a GUI on the entire -[vouch](https://github.com/vouchdev/vouch) command surface — the review-gated, -local-first decision-memory knowledge base. - -![The vouch-desktop dashboard — health, artifact counts, and review throughput for the open knowledge base](docs/screenshots/dashboard.png) - -vouch is an **unmodified dependency**: vouch-desktop spawns the `vouch` CLI's -machine transports and never patches its Python source. It talks to vouch two -ways, and never writes durable KB state except through vouch's own -**propose → approve** review gate. - -- **JSONL stdio** (`vouch serve --transport jsonl`) — the workhorse. All 54 - `kb.*` methods (read, search, list, propose, review, lifecycle, sessions, - graph, maintenance, export/import, audit) flow over one newline-delimited - JSON pipe. -- **HTTP + WebSocket** (`vouch review-ui --allow-dual-solve`) — spawned lazily, - only when you open the Dual-Solve view, for the one thing JSONL can't do: - the long-running, sandboxed two-engine dual-solve runner with streamed - progress. - -## What you get - -- **One window over every command.** A left-rail of ten task-shaped views plus a - bespoke Dual-Solve runner. Data-entry methods get forms **generated** from a - verified parameter catalog (typed controls, enum dropdowns, sliders, tag - inputs, native file pickers, and search-backed id typeaheads) — so the UI - stays in step with vouch instead of hand-coding 54 forms. -- **The gate, made visible.** Propose anything; it lands in the review queue. - Approve / reject from the Review view. Nothing is ever auto-approved. -- **Capability-aware.** Methods the connected vouch doesn't advertise are shown - disabled, and light up automatically when you upgrade vouch. -- **A companion, not just a window.** Tray icon with a pending-count badge and KB - switcher, native notifications (a dual-solve run is ready to judge; new - proposals arrived; the process went down), and no terminal required — the app - finds, launches, and supervises vouch for you. - -## A look around - -| The gate, made visible | Forms from the catalog | -|---|---| -| [![Review & Lifecycle — the pending queue with approve/reject controls](docs/screenshots/review.png)](docs/screenshots/review.png) | [![Browse — read methods rendered as generated forms](docs/screenshots/browse.png)](docs/screenshots/browse.png) | -| Propose anything and it lands in the **review queue**. Approve or reject it here — nothing is ever auto-approved. | Every method gets a typed form **generated** from the verified parameter catalog, with id typeaheads and result cards. | - -The bespoke **Dual-Solve** runner is the one thing JSONL can't do — it drives -vouch's sandboxed two-engine runner over HTTP, streams progress, then proposes -the winning diff into the same review queue: - -[![Dual-Solve — run claude and codex on one issue, compare diffs, pick a winner](docs/screenshots/dual-solve.png)](docs/screenshots/dual-solve.png) - -## Requirements - -- **Node 18+** and **npm** to build/run from source (Node 20+ recommended). -- **vouch** on your `PATH` (`pipx install vouch`), or set its path in the app. - Dual-solve additionally needs vouch's `[web]` extra (`pipx install - 'vouch[web]'`), `git` / `gh` / `docker` on `PATH`, the `vouch/coder:latest` - sandbox image, and the KB inside a git repository. - -## Quick start - -From the monorepo root, enter the desktop package first: - -```bash -cd desktop -npm install -npm start -``` - -On first launch, **Open existing…** a folder containing a `.vouch/` directory, -or **Initialize new…** to create one. The app spawns and supervises the vouch -process; you never touch a shell. - -## Tech stack - -React 18 + TypeScript 5 renderer, built with **electron-vite** (three-target -build: main → CJS, preload → CJS, renderer → React/ESM). Vitest 2 + -`@testing-library/react` for unit tests. Typed IPC contract shared between main, -preload, and renderer via `src/shared/ipc.ts`; typed parameter catalog in -`src/shared/methods.gen.ts` (generated from `src/catalog/methods.json`). - -## Develop - -```bash -npm run dev # electron-vite dev — launches Electron with HMR -npm run dev:vouch # dev mode pinned to ../.venv/bin/vouch; restarts on Python changes -npm run build # electron-vite build → out/main, out/preload, out/renderer -npm test # vitest run — catalog, gen-methods, form, controls (85 tests) -npm run typecheck # tsc -b --noEmit across main + preload + renderer -npm run gen:methods # regenerate src/shared/methods.gen.ts from src/catalog/methods.json -npm run dist # electron-vite build + electron-builder → dmg / nsis / AppImage -``` - -From the monorepo root, the same validation gate is available as -`make desktop-check`. - -For Python + desktop development from the monorepo root, use the universal -dev-loop command: - -```bash -make desktop-dev-vouch KB=/path/to/repo-with-.vouch -``` - -It runs `electron-vite dev`, refreshes the editable vouch install when needed, -sets `VOUCH_DESKTOP_VOUCH_PATH` to the root `.venv/bin/vouch`, opens the -optional `KB=` path, and restarts Electron whenever vouch's Python sources or -schema/template files change. Once a KB is open, the desktop app starts vouch as -`.venv/bin/vouch serve --transport jsonl`, so the next restart picks up Python -code changes cleanly. - -`src/catalog/methods.json` is the verified surface catalog (method names, -parameters, types, enum sets) extracted from vouch's source. `scripts/gen-methods.ts` -enriches it and writes `src/shared/methods.gen.ts`, which drives the form -generator. Regenerate it when vouch's surface changes. - -To ship a zero-Python install, freeze vouch with PyInstaller into -`resources/vouch/` (see `scripts/freeze-vouch.sh`); the locator prefers it. - -## Architecture - -``` - renderer (sandboxed, no node) - │ window.vouch.call(method, params) ← one frozen preload bridge - ▼ IPC - main process - ├─ JsonlClient → vouch serve --transport jsonl (all 54 kb.* methods) - ├─ HttpClient → vouch review-ui --allow-dual-solve --dual-solve-sandbox - ├─ Supervisor → health poll, restart, shutdown - ├─ Tray/Notifier → companion + OS notifications - └─ KbStore → recent KBs, prefs -``` - -See [`docs/architecture.md`](./docs/architecture.md) for the full design, -including the method-by-method coverage table. - -## Status - -v0.1.0 covers the entire `kb.*` surface plus dual-solve. The CLI-only -orchestration commands (`auto-pr`, `migrate`, `schema`, `sync-*`) are not yet -surfaced — see the [CHANGELOG](./CHANGELOG.md). - -## License - -MIT. diff --git a/desktop/docs/architecture.md b/desktop/docs/architecture.md deleted file mode 100644 index aacf3c38..00000000 --- a/desktop/docs/architecture.md +++ /dev/null @@ -1,604 +0,0 @@ -# vouch-desktop — Architecture & Build Design - -> A cross-platform Electron GUI over the entire vouch command surface. vouch is an **unmodified** Python dependency, spawned as an installed binary. The desktop app talks to it only through its existing transports: **JSONL stdio** (all 54 `kb.*` methods) and **HTTP+WS** (`vouch review-ui` for the live review queue and dual-solve runner). The app never writes durable KB state except through vouch's own `propose → approve` gate. - ---- - -## 0. Design tenets (the load-bearing invariants) - -These are non-negotiable and every later section is downstream of them. - -1. **vouch is read-only as a dependency.** vouch-desktop lives under `desktop/` in the vouch monorepo, but it still treats the Python package as an external runtime dependency. It never patches, vendors-and-edits, or reaches into vouch's Python source. It shells out to the `vouch` console-script (or ` -m vouch`) and speaks the documented wire protocols. -2. **The review gate is sacred.** The only durable writes the app can cause are: (a) `kb.propose_*` / `kb.register_source*` (proposals + ungated evidence intake), and (b) `kb.approve` / `kb.reject` and the lifecycle ops, which are themselves the gate's decision step. The UI must make the gate *visible*, never route around it. There is no "auto-approve on propose" path in the UI. -3. **JSONL is the full surface; HTTP is the live surface.** Every one of the 54 methods is reachable over JSONL. HTTP/WS exists for the two things JSONL can't do well: a *push* feed (review-queue refresh, dual-solve progress) and the *long-running* dual-solve orchestration. Where a capability exists on both, prefer JSONL for request/response and reserve HTTP for push + dual-solve. -4. **No terminal, ever.** A non-technical operator double-clicks an icon. The app locates or bundles vouch, spawns and supervises the child processes, surfaces health, and recovers from crashes — all without the user seeing a shell. -5. **Renderer is sandboxed.** `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`. The renderer never spawns processes or touches the filesystem; it talks to a tiny typed `window.vouch` bridge exposed by the preload. All process I/O lives in main. - ---- - -## 1. Architecture - -### 1.1 Process topology - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ Electron MAIN process (Node, CommonJS or ESM) │ -│ │ -│ ┌────────────────┐ ┌──────────────────┐ ┌────────────────────┐ │ -│ │ VouchLocator │ │ JsonlClient │ │ HttpClient │ │ -│ │ (find/bundle │ │ - spawns │ │ - spawns │ │ -│ │ the binary) │ │ `vouch serve │ │ `vouch review-ui │ │ -│ └────────────────┘ │ --transport │ │ --bind 127...` │ │ -│ │ jsonl` │ │ - REST + /ws │ │ -│ ┌────────────────┐ │ - NDJSON over │ │ client │ │ -│ │ ProcessSuper- │ │ stdin/stdout │ └────────────────────┘ │ -│ │ visor (health, │ │ - req/resp │ │ -│ │ restart, │ │ correlation │ ┌────────────────────┐ │ -│ │ shutdown) │ └──────────────────┘ │ Tray + Notifier │ │ -│ └────────────────┘ │ (OS integration) │ │ -│ └────────────────────┘ │ -│ ▲ IPC (ipcMain.handle / webContents.send) │ -└─────────┼─────────────────────────────────────────────────────────────┘ - │ contextBridge (preload.ts, sandboxed) - ▼ -┌─────────────────────────────────────────────────────────────────────┐ -│ Electron RENDERER (sandboxed, no node) — the UI │ -│ window.vouch.call(method, params) → Promise │ -│ window.vouch.on(channel, cb) → live frames (ws, progress) │ -│ Views: read/search · browse · propose · review · sessions · │ -│ maintenance · export-import · graph · audit · dual-solve │ -└─────────────────────────────────────────────────────────────────────┘ - │ spawns / pipes / TCP - ▼ -┌──────────────────────────┐ ┌──────────────────────────────────────┐ -│ vouch serve │ │ vouch review-ui --bind 127.0.0.1:PORT │ -│ --transport jsonl │ │ (only when web features are opened) │ -│ (always running) │ │ REST + WebSocket /ws │ -│ stdin/stdout NDJSON │ │ --no-open-browser --allow-dual-solve │ -└──────────────────────────┘ └──────────────────────────────────────┘ - │ │ - └───────────► .vouch/ KB ◄───────────┘ - (one KB root, chosen by the user) -``` - -Two distinct vouch children, both children of the Electron **main** process: - -- **The JSONL child** is the workhorse. Spawned once at KB-open, kept alive for the app's lifetime, restarted on crash. Carries all 54 methods. -- **The HTTP child** (`vouch review-ui`) is **lazy** — spawned only when the user opens the Review or Dual-Solve view (or enables "live queue" in settings). It needs the `[web]` extra; if that's missing the app degrades gracefully (review queue falls back to JSONL `kb.list_pending` polling; dual-solve is hidden). - -Both children point at the **same KB root** via `VOUCH_KB_PATH=/.vouch` in the child env (cwd-independent, the recommended discovery pin). - -### 1.2 Main-process modules - -| Module | Responsibility | -|---|---| -| `main/index.ts` | App entry: single-instance lock, create `BrowserWindow`, wire IPC, build tray, lifecycle hooks. | -| `main/vouch-locator.ts` | Resolve a runnable vouch: configured path → env override → bundled-frozen → repo dev venv → sibling dev checkout → PATH (`vouch`) → `python -m vouch`. Probe `vouch --version` / `kb.capabilities`. | -| `main/jsonl-client.ts` | Spawn `vouch serve --transport jsonl`; line-buffer stdout; correlate requests by envelope `id`; queue + FIFO fallback; surface parse + protocol errors. | -| `main/http-client.ts` | Spawn `vouch review-ui`; wait for `/healthz`; REST helpers (`/api/pending`, `/approve`, `/dual-solve/*`); manage the `/ws` socket and re-fan frames into IPC. | -| `main/supervisor.ts` | Health checks, exponential-backoff restart, crash counters, graceful shutdown (close stdin → SIGINT → SIGKILL). | -| `main/kb-store.ts` | Persist recent KB roots, the chosen vouch binary, ports, tokens (via `safeStorage`), window state, notification prefs. | -| `main/ipc.ts` | Register `ipcMain.handle`/`on` for the channel contract (§1.4); the *only* place renderer calls cross into Node. | -| `main/tray.ts` | Tray/menu-bar icon, badge counts, context menu, notification dispatch. | - -### 1.3 Preload (the trust boundary) - -`preload/index.ts` runs with `contextIsolation` and exposes exactly one frozen object via `contextBridge.exposeInMainWorld`: - -```js -// preload/index.js -const { contextBridge, ipcRenderer } = require("electron"); - -const ALLOWED_EVENTS = new Set(["vouch:ws", "vouch:progress", "vouch:health", "vouch:proc"]); - -contextBridge.exposeInMainWorld("vouch", { - // request/response over JSONL (the 54 methods) - call: (method, params) => ipcRenderer.invoke("vouch:call", { method, params }), - // HTTP-only review-gate actions (form-encoded under the hood, in main) - http: { - listPending: (page) => ipcRenderer.invoke("vouch:http:pending", { page }), - approve: (id, reason) => ipcRenderer.invoke("vouch:http:approve", { id, reason }), - reject: (id, reason) => ipcRenderer.invoke("vouch:http:reject", { id, reason }), - contradict: (id, against) => ipcRenderer.invoke("vouch:http:contradict", { id, against }), - dualSolveRun: (body) => ipcRenderer.invoke("vouch:ds:run", body), - dualSolveJob: (jobId) => ipcRenderer.invoke("vouch:ds:job", { jobId }), - dualSolveChoose: (body) => ipcRenderer.invoke("vouch:ds:choose", body), - }, - // KB lifecycle / process control - openKb: (root) => ipcRenderer.invoke("vouch:openKb", { root }), - pickKb: () => ipcRenderer.invoke("vouch:pickKb"), // native dir dialog - initKb: (root) => ipcRenderer.invoke("vouch:initKb", { root }), - status: () => ipcRenderer.invoke("vouch:status"), // proc + health snapshot - ensureWeb: () => ipcRenderer.invoke("vouch:ensureWeb"), // lazily spawn review-ui - capabilities: () => ipcRenderer.invoke("vouch:capabilities"), - // push channels (main → renderer) - on: (event, cb) => { - if (!ALLOWED_EVENTS.has(event)) throw new Error("unknown event: " + event); - const handler = (_e, payload) => cb(payload); - ipcRenderer.on(event, handler); - return () => ipcRenderer.removeListener(event, handler); - }, -}); -``` - -No raw `ipcRenderer`, no `require`, no `process` leak into the renderer. The event allow-list prevents the renderer subscribing to arbitrary channels. - -### 1.4 The IPC contract - -**Request/response channels** (`ipcMain.handle`, renderer awaits a Promise): - -| Channel | Payload | Returns | Notes | -|---|---|---|---| -| `vouch:call` | `{method, params}` | `{ok, result}` or `{ok:false, error:{code,message,traceback?}}` | The universal JSONL bridge. Method must be in the known 54; main validates against the catalog before sending. | -| `vouch:openKb` | `{root}` | `{ok, capabilities, status}` | Pin `VOUCH_KB_PATH`, (re)spawn JSONL child, probe `kb.capabilities` + `kb.status`. | -| `vouch:pickKb` | — | `{root\|null}` | `dialog.showOpenDialog({properties:['openDirectory']})`. | -| `vouch:initKb` | `{root}` | `{ok}` | Runs `vouch init` as a one-shot subprocess (the only CLI-subprocess use; see §2.3). | -| `vouch:capabilities` | — | capabilities object | Cached after open. | -| `vouch:status` | — | `{jsonl:{up,pid,restarts}, http:{up,port,auth}, health}` | For the status bar + tray. | -| `vouch:ensureWeb` | — | `{up, port, allowDualSolve}` | Idempotent lazy spawn of `review-ui`. | -| `vouch:http:*` | per §HTTP | JSON / `{ok}` | Form-encoded mutations done in main; renderer never sees 303s. | -| `vouch:ds:*` | dual-solve bodies | JSON | JSON-bodied dual-solve endpoints. | - -**Streaming / push channels** (`webContents.send`, renderer subscribes via `vouch.on`): - -| Event | Frame | Source | -|---|---|---| -| `vouch:ws` | `{view, action?, proposal_id?, claim_id?, artifact_id?}` | `/ws` `type:"refresh"` frames, re-emitted. Renderer re-pulls the affected view. | -| `vouch:progress` | `{job_id, event, message}` | `/ws` `type:"dual_solve"` frames. Drives the progress log + "ready to judge" notification. | -| `vouch:health` | `{jsonl, http, pendingCount}` | Periodic supervisor poll; drives status bar + tray badge. | -| `vouch:proc` | `{which, state, restarts, error?}` | Process up/down/restarting events for the diagnostics panel. | - -**Correlation.** JSONL processing is strictly one-request-one-response, in order. The client still assigns a **monotonic integer `id`** per request and resolves the matching pending Promise when a response with that `id` arrives. A `Map` holds in-flight calls; FIFO is the fallback if an `id` is ever absent (it never should be, since we always send one). A per-call timeout (default 30 s; configurable for long ops like `index_rebuild`, `reindex_embeddings`, `export`) rejects with a synthetic `timeout` error and tears down the pending entry. The JSONL transport is **synchronous server-side**, so the client also maintains a write queue: requests are written one at a time and the next is sent only after the prior response (or a small pipeline depth) — this prevents head-of-line surprises and keeps the `id`↔response mapping trivially correct even though correlation by `id` would tolerate pipelining. - -### 1.5 Process lifecycle - -**Start.** On app launch: single-instance lock; restore last KB root from `kb-store`. If a valid root exists and the user opted into auto-open, spawn the JSONL child immediately; otherwise show the KB picker. The HTTP child is **not** started at launch. - -**KB-open sequence:** -1. Validate `/.vouch` exists (else offer `initKb`). -2. Resolve the vouch binary (§5.3); if none, show the "install vouch" wizard. -3. Spawn `vouch serve --transport jsonl` with `cwd=`, env `{VOUCH_KB_PATH:/.vouch, VOUCH_AGENT:"vouch-desktop:", VOUCH_LOG_FORMAT:"json", VOUCH_LOG_LEVEL:"WARNING"}`, `stdio:["pipe","pipe","pipe"]`. -4. Probe `kb.capabilities` (with a short timeout). Success ⇒ KB is live; cache caps; emit `vouch:health`. Failure / exit code 2 ⇒ "no KB / init needed" UX. - -**Health.** Supervisor pings `kb.status` over JSONL every N seconds (cheap, read-only). For the HTTP child, it polls `GET /healthz` (unguarded, returns `pending`, `auth`, `clients`). Results fan out on `vouch:health` and drive the tray badge. - -**Restart.** If the JSONL child exits unexpectedly: mark down, reject all in-flight Promises with a `process_down` error, then exponential backoff (250 ms → 4 s, cap 5 attempts/60 s window). On success, re-probe capabilities. If max attempts exceeded, surface a persistent error banner with a "Retry" button and a "view stderr log" link. stderr is **never** parsed as protocol — it is captured to a ring buffer and a rotating log file (`VOUCH_LOG_FILE`) for diagnostics. - -**Shutdown.** On `app.before-quit`: stop the supervisor timers; for JSONL, **close child.stdin** (clean EOF-driven exit) then SIGINT after a 2 s grace, SIGKILL after 5 s. For HTTP, send SIGINT (uvicorn catches `KeyboardInterrupt`), then escalate. Persist window + KB state. The single-instance second-launch handler focuses the existing window instead of spawning a duplicate. - -### 1.6 Choosing the KB root / working repo - -- **First run:** empty-state screen with two actions — **Open existing KB** (native directory picker; we validate `.vouch/` exists by walking the chosen dir, matching `discover_root`) and **Initialize a new KB here** (picker → `vouch init `). -- **Recent KBs:** `kb-store` keeps an MRU list; the tray and a top-bar dropdown switch between them. Switching tears down both children and re-runs the open sequence against the new root. -- **Dual-solve constraint:** the HTTP child with `--allow-dual-solve --dual-solve-sandbox` calls `ds.repo_root` at construction and **fails fast outside a git repo**. So the Dual-Solve view is only enabled when the KB root is itself inside a git working tree (main checks `git rev-parse` via the locator's git probe). Otherwise the view shows a "dual-solve requires the KB to live in a git repo (plus git/gh/docker and the vouch/coder image)" notice. -- We pin discovery with `VOUCH_KB_PATH` rather than relying on cwd, so the choice is launcher-independent and robust. - ---- - -## 2. Backend strategy - -### 2.1 Transport decision matrix - -| Need | Transport | Why | -|---|---|---| -| Any of the 54 `kb.*` methods, request/response | **JSONL** | Full surface; cheap; synchronous; no port/token. The default for everything. | -| Live review-queue push (a proposal appears / is decided elsewhere) | **HTTP `/ws`** (`refresh` frames) | JSONL has no server-push. WS wakes the UI; we then re-pull via JSONL `kb.list_pending` or HTTP `/api/pending`. | -| Dual-solve orchestration (multi-minute, two engines, progress) | **HTTP** (`/dual-solve/*` + `/ws` `dual_solve` frames) | Only exists on the HTTP surface; long-running + streamed progress. | -| `vouch init` (create a brand-new KB) | **CLI subprocess** | Not a `kb.*` method; one-shot; see §2.3. | - -**Rule of thumb:** read/write a method ⇒ JSONL. Need a *push* or *dual-solve* ⇒ HTTP. Bootstrap a KB ⇒ CLI. Never use the HTTP `/approve`,`/reject`,`/contradict` form endpoints *in place of* JSONL for those actions **except** inside the Review view where we want the same `/ws` broadcast to refresh other clients — there we deliberately route approve/reject through HTTP so the audit `approved_by` label and the broadcast match the web console exactly. Outside the live Review view (e.g. an approve triggered from a graph card), JSONL `kb.approve` is fine and equivalent. - -### 2.2 The HTTP-vs-JSONL gap (important) - -The HTTP surface is **read + decide only**. These are **JSONL/CLI-only** and must be wired to the JSONL client even though they're "review/lifecycle"-flavored: - -- `kb.supersede`, `kb.archive`, `kb.confirm`, `kb.expire`, `kb.reject_extracted` -- the entire propose family (`kb.propose_*`, `kb.register_source*`) -- `kb.cite`, `kb.source_verify` -- all read/search/graph/maintenance/session/export-import methods - -The HTTP surface exposes only: list pending, view claim/session/source, `/approve`, `/reject`, `/contradict`, `/audit`, and the dual-solve endpoints. So the Review view uses HTTP for *approve/reject/contradict + live refresh*, but every other lifecycle and propose action (and the supersede/archive/confirm/expire/reject_extracted set) goes over **JSONL**. - -### 2.3 CLI subprocess — the one sanctioned use - -The only thing neither transport offers is **creating a KB**. `vouch init` is a Click command, not a `kb.*` method. `main/vouch-locator.ts` runs it as a detached one-shot (`vouch init `), captures exit code + stderr, and reports success. We do **not** use the CLI for any `kb.*` operation — that would create a parallel data path and is forbidden by tenet 1. (`vouch --version` and a `kb.capabilities` probe are the only other subprocess/JSONL calls used for locating/validating the binary.) - -### 2.4 Driving dual-solve - -Sequence, exactly mirroring the documented flow, all from main with the HTTP child built `--allow-dual-solve`: - -1. **Run:** renderer → `vouch.http.dualSolveRun({issue_url, claude_effort?, codex_effort?})` → main POSTs JSON to `/dual-solve/run`. `201 {job_id}` stored in renderer state. `409` ⇒ a job is already running/finalizing (surface "wait for the current run"). `400` ⇒ bad `issue_url`. -2. **Observe:** main is already subscribed to `/ws`; `type:"dual_solve"` frames for that `job_id` are re-emitted on `vouch:progress`. The renderer appends `progress` lines to a live log. On `ready` (or `done`), it **re-fetches** `GET /dual-solve/job/{job_id}` (frames carry no diffs) to render the two candidate diff panes (`candidates[].diff`, `.ok`, `.error`). -3. **Choose:** renderer → `vouch.http.dualSolveChoose({job_id, winner:'claude'|'codex'|null, reason?})`. `409` if status ≠ `ready`. On success the winner's diff is recorded as a Source and up to 3 **pending** claims are proposed; `{kept_branch, proposed_ids}` returned. -4. **Approve (separate):** the `proposed_ids` land in the normal queue. The user reviews them in the Review view via the gate endpoints. **Dual-solve never auto-approves** — the UI makes that explicit ("3 claims proposed — review them in the queue"). - -Caveats baked into the UI: single job slot (polling an old `job_id` 404s — we keep only the current one), engines run sequentially (multi-minute; show a spinner + elapsed timer), and the run button is disabled while a job is `running`/`finalizing`. A pre-flight `/healthz` + a sandbox probe (`git`/`gh`/`docker` on PATH and `vouch/coder:latest` available) gates the whole view. - -### 2.5 Surfacing the websockets to the renderer - -Main owns a single `/ws` connection per HTTP child. It authenticates with `?token=` on the WS URL when auth is enabled (a native client can't read the HttpOnly cookie). On `4401` it surfaces an auth error and retries with the stored token. Frames are normalized and forwarded: - -- `type:"hello"` → ignored (logged once). -- `type:"refresh"` → `vouch:ws` (the renderer treats it as a *signal* and re-pulls `/api/pending` or `/audit`). Polling is the source of truth; WS is a wake-up. Main also runs a slow safety poll (e.g. every 20 s) so a missed frame can't strand the queue. -- `type:"dual_solve"` → `vouch:progress`, and main raises the "ready to judge" OS notification on the `ready` event (§4). - -The WS client auto-reconnects with backoff; on reconnect the renderer force-re-pulls all live views (a frame may have been missed during the gap). - ---- - -## 3. Renderer UX - -### 3.1 Navigation: all 54 methods grouped into ten views - -The left rail mirrors the `group` field in the catalog, collapsed to ten task-shaped views: - -| View | Methods it surfaces | -|---|---| -| **Dashboard** | `kb.status`, `kb.stats`, `kb.capabilities` (health, counts, pending-by-agent, approval rates, citation coverage). | -| **Search & Ask** | `kb.search`, `kb.context`, `kb.synthesize`. | -| **Browse** | `kb.list_claims`, `kb.list_pages`, `kb.list_entities`, `kb.list_relations`, `kb.list_sources`, and the readers `kb.read_claim`/`read_page`/`read_entity`/`read_relation`, plus `kb.cite`. | -| **Propose** | `kb.propose_claim`, `kb.propose_page`, `kb.propose_entity`, `kb.propose_relation`, `kb.register_source`, `kb.register_source_from_path`. | -| **Review & Lifecycle** | `kb.list_pending`, `kb.approve`, `kb.reject`, `kb.reject_extracted`, `kb.expire`, `kb.supersede`, `kb.contradict`, `kb.archive`, `kb.confirm`. | -| **Sessions** | `kb.session_start`, `kb.session_end`, `kb.volunteer_context`, `kb.crystallize`. | -| **Graph** | `kb.neighbors`, `kb.why`, `kb.trace`, `kb.impact`, `kb.graph_export`, `kb.provenance_rebuild`. | -| **Maintenance** | `kb.index_rebuild`, `kb.lint`, `kb.doctor`, `kb.reindex_embeddings`, `kb.dedup_scan`, `kb.eval_embeddings`, `kb.embeddings_stats`, `kb.source_verify`. | -| **Export / Import** | `kb.export`, `kb.export_check`, `kb.import_check`, `kb.import_apply`. | -| **Audit** | `kb.audit`. | -| **Dual-Solve** | HTTP runner (`/dual-solve/*`). | - -A persistent **"Method Console"** (developer drawer, ⌘K) lets a power user invoke *any* method by name through the generic form generator (§3.2) — this is the catch-all that guarantees 100% coverage even before bespoke views exist, and is the first UI milestone after the bridge. - -### 3.2 The data-driven form generator (so we never hand-write 54 forms) - -We ship the **parameter catalog as JSON** in the renderer (`renderer/data/methods.json`, generated from the verified surface catalog — see §6). Every propose/lifecycle/maintenance form is *generated* from `method.params`, not authored by hand. - -**Param-type → control mapping:** - -| Catalog `type` | Generated control | Validation / coercion | -|---|---|---| -| `string` | `` (or ` + + +

+ + + + + + + + diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json new file mode 100644 index 00000000..12058f97 --- /dev/null +++ b/webapp/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "noEmit": true, + "types": ["vite/client", "node"] + }, + "include": ["src", "plugins", "e2e", "vite.config.ts", "vitest.config.ts", "playwright.config.ts"] +} diff --git a/webapp/vite.config.ts b/webapp/vite.config.ts new file mode 100644 index 00000000..de6acf2b --- /dev/null +++ b/webapp/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import { vouchProxy } from './plugins/vouch-proxy' +import { claudeBridge } from './plugins/claude-bridge' + +export default defineConfig({ + plugins: [react(), tailwindcss(), vouchProxy(), claudeBridge()], +}) diff --git a/webapp/vitest.config.ts b/webapp/vitest.config.ts new file mode 100644 index 00000000..75196c01 --- /dev/null +++ b/webapp/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + include: ['src/**/*.test.{ts,tsx}', 'plugins/**/*.test.ts'], + }, +})