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/.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/.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/8a6df051-6c56-4d86-b0ed-66f6638ecfdc.jsonl b/.vouch/captures/8a6df051-6c56-4d86-b0ed-66f6638ecfdc.jsonl deleted file mode 100644 index db6ea81b..00000000 --- a/.vouch/captures/8a6df051-6c56-4d86-b0ed-66f6638ecfdc.jsonl +++ /dev/null @@ -1,301 +0,0 @@ -{"cmd": "cd /home/a/Dev/inbounds && npm install @anthropic-ai/sdk && npm install -D typescript vitest tsx @types/node", "summary": "Ran: cd /home/a/Dev/inbounds && npm install @anthropic-ai/sdk && ", "tool": "Bash", "ts": 1783415910.6256332} -{"files": ["/home/a/Dev/inbounds/package.json"], "summary": "Read package.json", "tool": "Read", "ts": 1783415915.6190112} -{"files": ["/home/a/Dev/inbounds/package.json"], "summary": "Edited package.json", "tool": "Edit", "ts": 1783415929.238057} -{"files": ["/home/a/Dev/inbounds/tsconfig.json"], "summary": "Created tsconfig.json", "tool": "Write", "ts": 1783415935.2588933} -{"files": ["/home/a/Dev/inbounds/vitest.config.ts"], "summary": "Created vitest.config.ts", "tool": "Write", "ts": 1783415941.2361197} -{"files": ["/home/a/Dev/inbounds/.gitignore"], "summary": "Created .gitignore", "tool": "Write", "ts": 1783415945.6708295} -{"files": ["/home/a/Dev/inbounds/tests/types.test.ts"], "summary": "Created types.test.ts", "tool": "Write", "ts": 1783415954.255305} -{"files": ["/home/a/Dev/inbounds/src/types.ts"], "summary": "Created types.ts", "tool": "Write", "ts": 1783415980.5372076} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1", "tool": "Bash", "ts": 1783415989.0359955} -{"cmd": "cd /home/a/Dev/inbounds && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git status", "tool": "Bash", "ts": 1783416009.2342477} -{"cmd": "cd /home/a/Dev/inbounds && git add package.json package-lock.json tsconfig.json vitest.config.ts .gitignore src/types.ts tests/types.test.ts", "summary": "Ran: cd /home/a/Dev/inbounds && git add package.json package-lock", "tool": "Bash", "ts": 1783416018.3577073} -{"cmd": "printf 'chore: scaffold detection-engine package and core types\\n' > /tmp/inb-msg.txt && cd /home/a/Dev/inbounds && git commit -F /tmp/inb-msg.txt", "summary": "Ran: printf 'chore: scaffold detection-engine package and core ty", "tool": "Bash", "ts": 1783416034.696153} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -2", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -2", "tool": "Bash", "ts": 1783416040.2233598} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-1-report.md"], "summary": "Created task-1-report.md", "tool": "Write", "ts": 1783416069.5363379} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=a2aba08\n{\n echo \"# Task 1 review package\"\n echo\n echo \"## Revisi", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783416137.4659147} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-1-brief.md"], "summary": "Read task-1-brief.md", "tool": "Read", "ts": 1783416222.2542078} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-1-report.md"], "summary": "Read task-1-report.md", "tool": "Read", "ts": 1783416222.2690136} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-1-review.md"], "summary": "Read task-1-review.md", "tool": "Read", "ts": 1783416222.3714423} -{"cmd": "test -d /home/a/Dev/inbounds && echo \"exists\" || echo \"missing\"", "summary": "Ran: test -d /home/a/Dev/inbounds && echo \"exists\" || echo \"missi", "tool": "Bash", "ts": 1783416240.8824131} -{"cmd": "cd /home/a/Dev/inbounds && git log -1 --format='%H%n%s%n---%n%b' 03ae212 2>&1; echo \"===\"; git show --stat 03ae212 2>&1 | head -20", "summary": "Ran: cd /home/a/Dev/inbounds && git log -1 --format='%H%n%s%n---%", "tool": "Bash", "ts": 1783416245.3671453} -{"cmd": "cd /home/a/Dev/inbounds && ls -la && echo \"---node_modules present?---\" && test -d node_modules && echo yes || echo no && echo \"---git status---\" && git status --short", "summary": "Ran: cd /home/a/Dev/inbounds && ls -la && echo \"---node_modules p", "tool": "Bash", "ts": 1783416249.2171223} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-detection-en", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783416342.7055345} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-2-brief.md"], "summary": "Read task-2-brief.md", "tool": "Read", "ts": 1783416392.2121108} -{"cmd": "ls -la /home/a/Dev/inbounds/ | head -20", "summary": "Ran: ls -la /home/a/Dev/inbounds/ | head -20", "tool": "Bash", "ts": 1783416401.8324676} -{"cmd": "ls -la /home/a/Dev/inbounds/src/", "summary": "Ran: ls -la /home/a/Dev/inbounds/src/", "tool": "Bash", "ts": 1783416406.2203987} -{"files": ["/home/a/Dev/inbounds/src/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783416409.23276} -{"cmd": "ls -la /home/a/Dev/inbounds/tests/", "summary": "Ran: ls -la /home/a/Dev/inbounds/tests/", "tool": "Bash", "ts": 1783416412.8685622} -{"files": ["/home/a/Dev/inbounds/tests/deterministic.test.ts"], "summary": "Created deterministic.test.ts", "tool": "Write", "ts": 1783416425.2343228} -{"files": ["/home/a/Dev/inbounds/src/deterministic.ts"], "summary": "Created deterministic.ts", "tool": "Write", "ts": 1783416446.6751528} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/deterministic.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/deterministi", "tool": "Bash", "ts": 1783416451.704663} -{"cmd": "printf 'feat: add layer-1 deterministic scope checks\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat: add layer-1 deterministic scope checks\\n' > /t", "tool": "Bash", "ts": 1783416458.2608404} -{"cmd": "cd /home/a/Dev/inbounds && git add src/deterministic.ts tests/deterministic.test.ts && git commit -F /tmp/inb-msg.txt 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/deterministic.ts test", "tool": "Bash", "ts": 1783416462.4859912} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-2-report.md"], "summary": "Created task-2-report.md", "tool": "Write", "ts": 1783416481.576392} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=03ae212\n{\n echo \"# Task 2 review package\"; echo\n echo \"## Revisio", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783416503.0434153} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-2-brief.md"], "summary": "Read task-2-brief.md", "tool": "Read", "ts": 1783416529.7738183} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-2-report.md"], "summary": "Read task-2-report.md", "tool": "Read", "ts": 1783416530.230445} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-2-review.md"], "summary": "Read task-2-review.md", "tool": "Read", "ts": 1783416530.694168} -{"cmd": "ls /home/a/Dev/inbounds 2>/dev/null && echo \"---types---\" && cat /home/a/Dev/inbounds/src/types.ts 2>/dev/null", "summary": "Command failed: ls /home/a/Dev/inbounds 2>/dev/null && echo \"---types---\" &&", "tool": "Bash", "ts": 1783416537.5923088} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -10 && echo --- && git show a1557e7 --stat 2>/dev/null | head -20", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -10 && echo ---", "tool": "Bash", "ts": 1783416542.2591913} -{"cmd": "cd /home/a/Dev/inbounds && cat package.json | head -20 && echo --- && cat tsconfig.json", "summary": "Ran: cd /home/a/Dev/inbounds && cat package.json | head -20 && ec", "tool": "Bash", "ts": 1783416547.2602286} -{"cmd": "cd /home/a/Dev/inbounds && ls -la && cat vitest.config.ts 2>/dev/null && echo --- && find . -maxdepth 1 -iname \"*eslint*\" -o -iname \".prettierrc*\" 2>/dev/null", "summary": "Ran: cd /home/a/Dev/inbounds && ls -la && cat vitest.config.ts 2>", "tool": "Bash", "ts": 1783416593.1751} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-detection-en", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783416634.6230276} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-3-brief.md"], "summary": "Read task-3-brief.md", "tool": "Read", "ts": 1783416736.5826159} -{"cmd": "ls -la /home/a/Dev/inbounds/", "summary": "Ran: ls -la /home/a/Dev/inbounds/", "tool": "Bash", "ts": 1783416744.3932507} -{"cmd": "ls -la /home/a/Dev/inbounds/src/ && echo \"---\" && ls -la /home/a/Dev/inbounds/tests/", "summary": "Ran: ls -la /home/a/Dev/inbounds/src/ && echo \"---\" && ls -la /ho", "tool": "Bash", "ts": 1783416751.2726197} -{"files": ["/home/a/Dev/inbounds/tests/prompt.test.ts"], "summary": "Created prompt.test.ts", "tool": "Write", "ts": 1783416766.2703962} -{"files": ["/home/a/Dev/inbounds/src/prompt.ts"], "summary": "Created prompt.ts", "tool": "Write", "ts": 1783416785.5953317} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/prompt.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/prompt.test.", "tool": "Bash", "ts": 1783416792.6955857} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run 2>&1", "tool": "Bash", "ts": 1783416800.7230666} -{"cmd": "printf 'feat: add judge prompt builder\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat: add judge prompt builder\\n' > /tmp/inb-msg.txt", "tool": "Bash", "ts": 1783416807.2756615} -{"cmd": "cd /home/a/Dev/inbounds && git add src/prompt.ts tests/prompt.test.ts && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/prompt.ts tests/promp", "tool": "Bash", "ts": 1783416811.2445877} -{"cmd": "cd /home/a/Dev/inbounds && git commit -F /tmp/inb-msg.txt", "summary": "Ran: cd /home/a/Dev/inbounds && git commit -F /tmp/inb-msg.txt", "tool": "Bash", "ts": 1783416817.2601204} -{"cmd": "cd /home/a/Dev/inbounds && git log -1 --format=\"%H %s\"", "summary": "Ran: cd /home/a/Dev/inbounds && git log -1 --format=\"%H %s\"", "tool": "Bash", "ts": 1783416822.2445652} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-3-report.md"], "summary": "Created task-3-report.md", "tool": "Write", "ts": 1783416844.456532} -{"files": ["/home/a/Dev/inbounds/src/prompt.ts"], "summary": "Read prompt.ts", "tool": "Read", "ts": 1783416848.6265783} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -3", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -3", "tool": "Bash", "ts": 1783416855.4761815} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-3-report.md"], "summary": "Read task-3-report.md", "tool": "Read", "ts": 1783416860.2940414} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=a1557e7\n{\n echo \"# Task 3 review package\"; echo\n echo \"## Revisio", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783416878.096693} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-3-brief.md"], "summary": "Read task-3-brief.md", "tool": "Read", "ts": 1783416905.5724936} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-3-review.md"], "summary": "Read task-3-review.md", "tool": "Read", "ts": 1783416906.7750812} -{"cmd": "find /home/a/Dev/inbounds -maxdepth 2 2>/dev/null; echo \"---\"; test -f /home/a/Dev/inbounds/src/types.ts && cat /home/a/Dev/inbounds/src/types.ts", "summary": "Command failed: find /home/a/Dev/inbounds -maxdepth 2 2>/dev/null; echo \"---", "tool": "Bash", "ts": 1783416911.8460736} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -5 && echo \"---diff head---\" && git show --stat HEAD && echo \"---commit msg---\" && git log -1 --pretty=full", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -5 && echo \"---", "tool": "Bash", "ts": 1783416916.4221568} -{"cmd": "cd /home/a/Dev/inbounds && cat -A tests/prompt.test.ts | head -5 && echo \"---\" && npx tsc --noEmit -p tsconfig.json 2>&1 | head -30", "summary": "Ran: cd /home/a/Dev/inbounds && cat -A tests/prompt.test.ts | hea", "tool": "Bash", "ts": 1783416961.9589152} -{"cmd": "cd /home/a/Dev/inbounds && grep -n -i -A5 -B5 \"prompt\\|judge\\|axis\\|axes\" docs/design.md | head -150", "summary": "Ran: cd /home/a/Dev/inbounds && grep -n -i -A5 -B5 \"prompt\\|judge", "tool": "Bash", "ts": 1783416967.1617446} -{"cmd": "cd /home/a/Dev/inbounds && grep -c \"it(\" tests/types.test.ts tests/deterministic.test.ts tests/prompt.test.ts", "summary": "Ran: cd /home/a/Dev/inbounds && grep -c \"it(\" tests/types.test.ts", "tool": "Bash", "ts": 1783417049.382844} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-detection-en", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783417087.795448} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-4-brief.md"], "summary": "Read task-4-brief.md", "tool": "Read", "ts": 1783417113.249079} -{"cmd": "cd /home/a/Dev/inbounds && find . -type f -name \"*.ts\" -o -name \"*.json\" | grep -E \"(tsconfig|package\\.json|vitest)\" | head -20", "summary": "Ran: cd /home/a/Dev/inbounds && find . -type f -name \"*.ts\" -o -n", "tool": "Bash", "ts": 1783417121.3819} -{"cmd": "cd /home/a/Dev/inbounds && ls -la src/ tests/", "summary": "Ran: cd /home/a/Dev/inbounds && ls -la src/ tests/", "tool": "Bash", "ts": 1783417125.2917376} -{"files": ["/home/a/Dev/inbounds/tests/judge.test.ts"], "summary": "Created judge.test.ts", "tool": "Write", "ts": 1783417139.248484} -{"files": ["/home/a/Dev/inbounds/src/judge.ts"], "summary": "Created judge.ts", "tool": "Write", "ts": 1783417157.6253734} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/judge.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/judge.test.t", "tool": "Bash", "ts": 1783417179.7191365} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run 2>&1", "tool": "Bash", "ts": 1783417188.4834604} -{"cmd": "printf 'feat: add judge interface and claude judge with structured output\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat: add judge interface and claude judge with stru", "tool": "Bash", "ts": 1783417204.3377187} -{"cmd": "cd /home/a/Dev/inbounds && git add src/judge.ts tests/judge.test.ts && git -C /home/a/Dev/inbounds commit -F /tmp/inb-msg.txt", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/judge.ts tests/judge.", "tool": "Bash", "ts": 1783417212.380124} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -1", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -1", "tool": "Bash", "ts": 1783417219.8747313} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-4-report.md"], "summary": "Created task-4-report.md", "tool": "Write", "ts": 1783417254.730016} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=6a2d017\n{\n echo \"# Task 4 review package\"; echo\n echo \"## Revisio", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783417336.5508144} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-4-brief.md"], "summary": "Read task-4-brief.md", "tool": "Read", "ts": 1783417386.5967603} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-4-report.md"], "summary": "Read task-4-report.md", "tool": "Read", "ts": 1783417387.2867234} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-4-review.md"], "summary": "Read task-4-review.md", "tool": "Read", "ts": 1783417388.2569592} -{"cmd": "ls /home/a/Dev/inbounds 2>&1; echo \"---\"; find /home/a/Dev/inbounds -maxdepth 2 -type f -name \"*.ts\" 2>&1", "summary": "Ran: ls /home/a/Dev/inbounds 2>&1; echo \"---\"; find /home/a/Dev/i", "tool": "Bash", "ts": 1783417449.7897046} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== git log ===\" && git log --oneline -5 && echo \"=== judge.ts ===\" && cat -A src/judge.ts | grep -c '\\$' && cat src/judge.ts && echo \"=== types.ts (AxisVerdict, Norma", "summary": "Command failed: cd /home/a/Dev/inbounds && echo \"=== git log ===\" && git log", "tool": "Bash", "ts": 1783417459.2770448} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== AxisVerdict shape ===\" && sed -n '36,60p' src/types.ts && echo \"=== grep for real sdk import ===\" && grep -rn \"@anthropic-ai/sdk\" src tests package.json || echo \"n", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== AxisVerdict shape ===\" ", "tool": "Bash", "ts": 1783417477.6673021} -{"cmd": "cd /home/a/Dev/inbounds && sed -n '1,30p' package.json && echo \"===\" && git log --oneline --all -- package.json", "summary": "Ran: cd /home/a/Dev/inbounds && sed -n '1,30p' package.json && ec", "tool": "Bash", "ts": 1783417488.2780344} -{"cmd": "cd /home/a/Dev/inbounds && cat tsconfig.json && echo \"=== eslint config? ===\" && ls -la | grep -i eslint || echo \"no eslint config\"", "summary": "Ran: cd /home/a/Dev/inbounds && cat tsconfig.json && echo \"=== es", "tool": "Bash", "ts": 1783417518.290554} -{"cmd": "cd /home/a/Dev/inbounds && git show -s --format='%B' bbffb48 | cat -A | head -20 && echo \"=== length of subject ===\" && git show -s --format='%s' bbffb48 | wc -c", "summary": "Ran: cd /home/a/Dev/inbounds && git show -s --format='%B' bbffb48", "tool": "Bash", "ts": 1783417541.6805203} -{"cmd": "cd /home/a/Dev/inbounds && git show --stat bbffb48 | cat", "summary": "Ran: cd /home/a/Dev/inbounds && git show --stat bbffb48 | cat", "tool": "Bash", "ts": 1783417555.2718039} -{"cmd": "cd /home/a/Dev/inbounds && grep -rc \"it(\" tests/*.test.ts", "summary": "Ran: cd /home/a/Dev/inbounds && grep -rc \"it(\" tests/*.test.ts", "tool": "Bash", "ts": 1783417561.2758229} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-detection-en", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783417674.3233645} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-brief.md"], "summary": "Read task-5-brief.md", "tool": "Read", "ts": 1783417715.3687997} -{"files": ["/home/a/Dev/inbounds/tests/evaluate.test.ts"], "summary": "Created evaluate.test.ts", "tool": "Write", "ts": 1783417728.9409602} -{"files": ["/home/a/Dev/inbounds/src/evaluate.ts"], "summary": "Created evaluate.ts", "tool": "Write", "ts": 1783417757.4019742} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/evaluate.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/evaluate.tes", "tool": "Bash", "ts": 1783417770.7579238} -{"cmd": "printf 'feat: add aggregator that combines layers and fails open\\n' > /tmp/inb-msg.txt", "summary": "Ran: printf 'feat: add aggregator that combines layers and fails ", "tool": "Bash", "ts": 1783417776.2463677} -{"cmd": "cd /home/a/Dev/inbounds && git add src/evaluate.ts tests/evaluate.test.ts && git commit -F /tmp/inb-msg.txt", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/evaluate.ts tests/eva", "tool": "Bash", "ts": 1783417793.9150033} -{"cmd": "cd /home/a/Dev/inbounds && git log -1 --pretty=format:'%h %s' && npx vitest run tests/evaluate.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && git log -1 --pretty=format:'%h %s", "tool": "Bash", "ts": 1783417800.7249212} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-report.md"], "summary": "Created task-5-report.md", "tool": "Write", "ts": 1783417816.2526376} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=bbffb48\n{\n echo \"# Task 5 review package\"; echo\n echo \"## Revisio", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783417904.6658409} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-brief.md"], "summary": "Read task-5-brief.md", "tool": "Read", "ts": 1783417972.3630705} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-report.md"], "summary": "Read task-5-report.md", "tool": "Read", "ts": 1783417972.4011502} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-review.md"], "summary": "Read task-5-review.md", "tool": "Read", "ts": 1783417972.773134} -{"cmd": "find / -maxdepth 6 -iname \"evaluate.ts\" -o -iname \"deterministic.ts\" 2>/dev/null | grep -v node_modules", "summary": "Ran: find / -maxdepth 6 -iname \"evaluate.ts\" -o -iname \"determini", "tool": "Bash", "ts": 1783417993.2254045} -{"cmd": "cd /home/a/Dev/inbounds && echo \"--- types.ts ---\" && cat src/types.ts && echo \"--- judge.ts ---\" && cat src/judge.ts && echo \"--- deterministic.ts ---\" && cat src/deterministic.ts && echo \"--- evalua", "summary": "Command failed: cd /home/a/Dev/inbounds && echo \"--- types.ts ---\" && cat sr", "tool": "Bash", "ts": 1783417999.7871332} -{"cmd": "cd /home/a/Dev/inbounds && diff <(sed -n '84,125p' /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-5-brief.md) src/evaluate.ts\necho \"---test di", "summary": "Ran: cd /home/a/Dev/inbounds && diff <(sed -n '84,125p' /tmp/clau", "tool": "Bash", "ts": 1783418068.653655} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-detection-en", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783418121.553359} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-6-brief.md"], "summary": "Read task-6-brief.md", "tool": "Read", "ts": 1783418168.462984} -{"cmd": "ls -la /home/a/Dev/inbounds", "summary": "Ran: ls -la /home/a/Dev/inbounds", "tool": "Bash", "ts": 1783418183.3739285} -{"cmd": "ls -la /home/a/Dev/inbounds/src", "summary": "Ran: ls -la /home/a/Dev/inbounds/src", "tool": "Bash", "ts": 1783418187.124383} -{"cmd": "mkdir -p /home/a/Dev/inbounds/fixtures", "summary": "Ran: mkdir -p /home/a/Dev/inbounds/fixtures", "tool": "Bash", "ts": 1783418192.355608} -{"files": ["/home/a/Dev/inbounds/fixtures/clean.json"], "summary": "Created clean.json", "tool": "Write", "ts": 1783418201.4619362} -{"files": ["/home/a/Dev/inbounds/fixtures/no-issue.json"], "summary": "Created no-issue.json", "tool": "Write", "ts": 1783418206.6065557} -{"files": ["/home/a/Dev/inbounds/fixtures/protected.json"], "summary": "Created protected.json", "tool": "Write", "ts": 1783418212.2991364} -{"files": ["/home/a/Dev/inbounds/fixtures/breadth.json"], "summary": "Created breadth.json", "tool": "Write", "ts": 1783418218.437932} -{"files": ["/home/a/Dev/inbounds/tests/golden.test.ts"], "summary": "Created golden.test.ts", "tool": "Write", "ts": 1783418228.3554232} -{"files": ["/home/a/Dev/inbounds/src/cli.ts"], "summary": "Created cli.ts", "tool": "Write", "ts": 1783418236.609138} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/golden.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/golden.test.", "tool": "Bash", "ts": 1783418247.777825} -{"cmd": "cd /home/a/Dev/inbounds && npx tsx src/cli.ts fixtures --offline 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx tsx src/cli.ts fixtures --off", "tool": "Bash", "ts": 1783418251.7785952} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1", "tool": "Bash", "ts": 1783418259.7590957} -{"cmd": "cd /home/a/Dev/inbounds && npm run build 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npm run build 2>&1", "tool": "Bash", "ts": 1783418265.056399} -{"cmd": "git -C /home/a/Dev/inbounds status", "summary": "Ran: git -C /home/a/Dev/inbounds status", "tool": "Bash", "ts": 1783418270.0091422} -{"cmd": "cd /home/a/Dev/inbounds && git add src/cli.ts fixtures/ tests/golden.test.ts && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/cli.ts fixtures/ test", "tool": "Bash", "ts": 1783418275.569174} -{"cmd": "printf 'feat: add cli and golden-fixture harness\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat: add cli and golden-fixture harness\\n' > /tmp/i", "tool": "Bash", "ts": 1783418283.9672132} -{"cmd": "git -C /home/a/Dev/inbounds commit -F /tmp/inb-msg.txt", "summary": "Ran: git -C /home/a/Dev/inbounds commit -F /tmp/inb-msg.txt", "tool": "Bash", "ts": 1783418290.6186087} -{"cmd": "git -C /home/a/Dev/inbounds log --oneline -3", "summary": "Ran: git -C /home/a/Dev/inbounds log --oneline -3", "tool": "Bash", "ts": 1783418296.3143127} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1 && echo \"---\" && npm run build 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1 && echo \"---\" && np", "tool": "Bash", "ts": 1783418302.842776} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-6-report.md"], "summary": "Created task-6-report.md", "tool": "Write", "ts": 1783418324.1436946} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=f4ca93f\n{\n echo \"# Task 6 review package\"; echo\n echo \"## Revisio", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783418368.5507593} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-6-brief.md"], "summary": "Read task-6-brief.md", "tool": "Read", "ts": 1783418403.5365922} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-6-report.md"], "summary": "Read task-6-report.md", "tool": "Read", "ts": 1783418405.0488389} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/task-6-review.md"], "summary": "Read task-6-review.md", "tool": "Read", "ts": 1783418405.0554109} -{"cmd": "find / -maxdepth 6 -iname \"*inbounds*\" -not -path \"*/node_modules/*\" 2>/dev/null", "summary": "Ran: find / -maxdepth 6 -iname \"*inbounds*\" -not -path \"*/node_mo", "tool": "Bash", "ts": 1783418411.6603458} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -15 && echo --- && git status && echo --- && ls src tests fixtures 2>/dev/null", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -15 && echo ---", "tool": "Bash", "ts": 1783418416.3486888} -{"files": ["/home/a/Dev/inbounds/src/deterministic.ts"], "summary": "Read deterministic.ts", "tool": "Read", "ts": 1783418422.7800493} -{"files": ["/home/a/Dev/inbounds/src/evaluate.ts"], "summary": "Read evaluate.ts", "tool": "Read", "ts": 1783418423.1292017} -{"files": ["/home/a/Dev/inbounds/src/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783418423.4669974} -{"files": ["/home/a/Dev/inbounds/src/judge.ts"], "summary": "Read judge.ts", "tool": "Read", "ts": 1783418424.2882023} -{"files": ["/home/a/Dev/inbounds/tests/golden.test.ts"], "summary": "Read golden.test.ts", "tool": "Read", "ts": 1783418457.356214} -{"cmd": "cd /home/a/Dev/inbounds && grep -rn \"@anthropic-ai/sdk\" src tests package.json 2>/dev/null", "summary": "Ran: cd /home/a/Dev/inbounds && grep -rn \"@anthropic-ai/sdk\" src ", "tool": "Bash", "ts": 1783418457.397151} -{"files": ["/home/a/Dev/inbounds/src/cli.ts"], "summary": "Read cli.ts", "tool": "Read", "ts": 1783418457.6758199} -{"cmd": "cd /home/a/Dev/inbounds && cat package.json && echo --- && cat tsconfig.json 2>/dev/null && echo --- && ls dist 2>/dev/null | head -20", "summary": "Ran: cd /home/a/Dev/inbounds && cat package.json && echo --- && c", "tool": "Bash", "ts": 1783418465.7725816} -{"cmd": "cd /home/a/Dev/inbounds && ls *.config.* 2>/dev/null; cat vitest.config.ts 2>/dev/null; cat vitest.config.mts 2>/dev/null; echo \"---dist/cli.js---\"; cat dist/cli.js", "summary": "Command failed: cd /home/a/Dev/inbounds && ls *.config.* 2>/dev/null; cat vi", "tool": "Bash", "ts": 1783418476.665614} -{"cmd": "cd /home/a/Dev/inbounds && grep -rEo \"\\bit\\(|\\bit\\.each|\\btest\\(\" tests/*.test.ts | sort | uniq -c\necho \"---golden loop cases---\"\ngrep -c \"cases\\[\" tests/golden.test.ts 2>/dev/null\necho \"--- per file ", "summary": "Ran: cd /home/a/Dev/inbounds && grep -rEo \"\\bit\\(|\\bit\\.each|\\bte", "tool": "Bash", "ts": 1783419019.1731465} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -- package.json | head -5 && echo --- && git show 03ae212:package.json | grep -A2 '\"bin\"'", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -- package.json", "tool": "Bash", "ts": 1783419071.1063383} -{"cmd": "cd /home/a/Dev/inbounds && git show -s --format='%s%n%n%b' 7f66d6e", "summary": "Ran: cd /home/a/Dev/inbounds && git show -s --format='%s%n%n%b' 7", "tool": "Bash", "ts": 1783419078.0958009} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nLEDGER=/home/a/Dev/inbounds/.superpowers/sdd/progress.md\nMB=a2aba08\nprin", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783419735.7975485} -{"files": ["/home/a/Dev/inbounds/docs/design.md"], "summary": "Read design.md", "tool": "Read", "ts": 1783419786.421825} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/final-review.md"], "summary": "Read final-review.md", "tool": "Read", "ts": 1783419787.600499} -{"files": ["/home/a/Dev/inbounds/package.json"], "summary": "Read package.json", "tool": "Read", "ts": 1783419796.4176745} -{"cmd": "cd /home/a/Dev/inbounds && ls -la && echo \"---SRC---\" && ls -la src/ && echo \"---TESTS---\" && ls -la tests/ && echo \"---FIXTURES---\" && ls -la fixtures/", "summary": "Ran: cd /home/a/Dev/inbounds && ls -la && echo \"---SRC---\" && ls ", "tool": "Bash", "ts": 1783419796.4456437} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== installed SDK version ===\" && cat node_modules/@anthropic-ai/sdk/package.json 2>/dev/null | grep '\"version\"'\necho \"=== grep for output_config in SDK types ===\" && ", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== installed SDK version =", "tool": "Bash", "ts": 1783419855.4253898} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== output_config in non-beta MessageCreateParams? ===\" && grep -n \"output_config\" node_modules/@anthropic-ai/sdk/resources/messages/messages.d.ts | head\necho \"=== doe", "summary": "Command failed: cd /home/a/Dev/inbounds && echo \"=== output_config in non-be", "tool": "Bash", "ts": 1783419871.343842} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== smallChangeLineThreshold usages in src ===\" && grep -rn \"smallChangeLineThreshold\" src/\necho \"=== main/module/exports/types/files fields in package.json ===\" && gr", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== smallChangeLineThreshol", "tool": "Bash", "ts": 1783419885.494994} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== .gitignore ===\" && cat .gitignore\necho \"=== is there an .npmignore? ===\" && ls -la .npmignore 2>/dev/null || echo \"(no .npmignore)\"\necho \"=== dry-run what npm woul", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== .gitignore ===\" && cat ", "tool": "Bash", "ts": 1783419957.8191385} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== evaluate.ts (numbered) ===\" && grep -n \"out_of_bounds\\|uncertain\\|fails\\|degraded\" src/evaluate.ts\necho \"=== judge.ts parse + enum lines ===\" && grep -n \"enum:\\|JS", "summary": "Command failed: cd /home/a/Dev/inbounds && echo \"=== evaluate.ts (numbered) ", "tool": "Bash", "ts": 1783420035.4184225} -{"cmd": "mkdir -p /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd && cd /home/a/Dev/inbounds && git status && echo --- && git log --oneline -10 && echo --- &", "summary": "Ran: mkdir -p /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6", "tool": "Bash", "ts": 1783420156.7054434} -{"files": ["/home/a/Dev/inbounds/src/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783420162.1454127} -{"files": ["/home/a/Dev/inbounds/src/prompt.ts"], "summary": "Read prompt.ts", "tool": "Read", "ts": 1783420163.0295596} -{"files": ["/home/a/Dev/inbounds/src/judge.ts"], "summary": "Read judge.ts", "tool": "Read", "ts": 1783420163.4510021} -{"files": ["/home/a/Dev/inbounds/src/evaluate.ts"], "summary": "Read evaluate.ts", "tool": "Read", "ts": 1783420163.5578465} -{"files": ["/home/a/Dev/inbounds/src/deterministic.ts"], "summary": "Read deterministic.ts", "tool": "Read", "ts": 1783420164.0287323} -{"files": ["/home/a/Dev/inbounds/src/cli.ts"], "summary": "Read cli.ts", "tool": "Read", "ts": 1783420164.325256} -{"files": ["/home/a/Dev/inbounds/package.json"], "summary": "Read package.json", "tool": "Read", "ts": 1783420164.6965108} -{"files": ["/home/a/Dev/inbounds/tests/prompt.test.ts"], "summary": "Read prompt.test.ts", "tool": "Read", "ts": 1783420168.64271} -{"files": ["/home/a/Dev/inbounds/tests/evaluate.test.ts"], "summary": "Read evaluate.test.ts", "tool": "Read", "ts": 1783420168.9906507} -{"cmd": "cd /home/a/Dev/inbounds && cat tsconfig.json 2>/dev/null && echo --- && npm test 2>&1 | tail -30", "summary": "Ran: cd /home/a/Dev/inbounds && cat tsconfig.json 2>/dev/null && ", "tool": "Bash", "ts": 1783420170.5260797} -{"files": ["/home/a/Dev/inbounds/src/index.ts"], "summary": "Created index.ts", "tool": "Write", "ts": 1783420176.2784586} -{"files": ["/home/a/Dev/inbounds/package.json"], "summary": "Edited package.json", "tool": "Edit", "ts": 1783420185.5531945} -{"files": ["/home/a/Dev/inbounds/src/prompt.ts"], "summary": "Edited prompt.ts", "tool": "Edit", "ts": 1783420193.9762306} -{"files": ["/home/a/Dev/inbounds/tests/prompt.test.ts"], "summary": "Edited prompt.test.ts", "tool": "Edit", "ts": 1783420202.1721857} -{"files": ["/home/a/Dev/inbounds/tests/evaluate.test.ts"], "summary": "Edited evaluate.test.ts", "tool": "Edit", "ts": 1783420211.1030302} -{"files": ["/home/a/Dev/inbounds/tests/provider-isolation.test.ts"], "summary": "Created provider-isolation.test.ts", "tool": "Write", "ts": 1783420219.792938} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1 | tail -50", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1 | tail -50", "tool": "Bash", "ts": 1783420228.1588392} -{"cmd": "cd /home/a/Dev/inbounds && npm run build 2>&1 | tail -50", "summary": "Ran: cd /home/a/Dev/inbounds && npm run build 2>&1 | tail -50", "tool": "Bash", "ts": 1783420250.1681535} -{"cmd": "cd /home/a/Dev/inbounds && ls dist/ && echo --- && cat dist/index.d.ts && echo --- && cat dist/index.js", "summary": "Ran: cd /home/a/Dev/inbounds && ls dist/ && echo --- && cat dist/", "tool": "Bash", "ts": 1783420255.250392} -{"cmd": "cd /home/a/Dev/inbounds && git status && echo --- && git diff --stat && echo --- && ls .git/hooks/ | grep -i pre-commit", "summary": "Ran: cd /home/a/Dev/inbounds && git status && echo --- && git dif", "tool": "Bash", "ts": 1783420259.926969} -{"cmd": "cd /home/a/Dev/inbounds && cat .gitignore 2>/dev/null && echo --- && git status --ignored=matching 2>&1 | grep -i dist", "summary": "Ran: cd /home/a/Dev/inbounds && cat .gitignore 2>/dev/null && ech", "tool": "Bash", "ts": 1783420266.2951334} -{"cmd": "cd /home/a/Dev/inbounds && git add src/index.ts package.json && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/index.ts package.json", "tool": "Bash", "ts": 1783420269.793463} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/msg1.txt"], "summary": "Created msg1.txt", "tool": "Write", "ts": 1783420276.8041077} -{"cmd": "cd /home/a/Dev/inbounds && git commit -F /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/msg1.txt", "summary": "Ran: cd /home/a/Dev/inbounds && git commit -F /tmp/claude-1000/-h", "tool": "Bash", "ts": 1783420290.0875385} -{"cmd": "cd /home/a/Dev/inbounds && git add src/prompt.ts tests/prompt.test.ts tests/evaluate.test.ts tests/provider-isolation.test.ts && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/prompt.ts tests/promp", "tool": "Bash", "ts": 1783420295.8399503} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/msg2.txt"], "summary": "Created msg2.txt", "tool": "Write", "ts": 1783420301.555548} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -5 && echo --- && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -5 && echo --- ", "tool": "Bash", "ts": 1783420329.2464528} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -3 && echo === && git status && echo === && npm test 2>&1 | tail -30 && echo === && npm run build 2>&1 | tail -30 && echo BUILD_OK", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -3 && echo === ", "tool": "Bash", "ts": 1783420349.385285} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/final-fix-report.md"], "summary": "Created final-fix-report.md", "tool": "Write", "ts": 1783420375.4199936} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== npm test ===\" && npm test 2>&1 | tail -20 && echo \"=== npm run build ===\" && npm run build 2>&1 | tail -5 && echo \"=== CLI offline smoke ===\" && npx tsx src/cli.ts", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== npm test ===\" && npm te", "tool": "Bash", "ts": 1783420475.2663918} -{"cmd": "IN=/home/a/Dev/inbounds\nprintf '.superpowers/\\n' >> \"$IN/.gitignore\"\nprintf 'chore: ignore sdd scratch dir\\n' > /tmp/inb-cleanup-msg.txt\ngit -C \"$IN\" add .gitignore\ngit -C \"$IN\" commit -F /tmp/inb-cle", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783420537.9853327} -{"files": ["/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-github-adapter.md"], "summary": "Created 2026-07-07-inbounds-github-adapter.md", "tool": "Write", "ts": 1783420965.1679232} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-github-adapt", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783422642.3636897} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-1-brief.md"], "summary": "Read adapter-task-1-brief.md", "tool": "Read", "ts": 1783422679.4245253} -{"cmd": "ls -la /home/a/Dev/inbounds 2>&1 | head -30", "summary": "Ran: ls -la /home/a/Dev/inbounds 2>&1 | head -30", "tool": "Bash", "ts": 1783422687.3650367} -{"cmd": "find /home/a/Dev/inbounds/src -type f -name \"*.ts\" | head -20", "summary": "Ran: find /home/a/Dev/inbounds/src -type f -name \"*.ts\" | head -2", "tool": "Bash", "ts": 1783422692.5753276} -{"files": ["/home/a/Dev/inbounds/src/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783422699.2413843} -{"cmd": "mkdir -p /home/a/Dev/inbounds/src/github /home/a/Dev/inbounds/tests/github", "summary": "Ran: mkdir -p /home/a/Dev/inbounds/src/github /home/a/Dev/inbound", "tool": "Bash", "ts": 1783422705.2351148} -{"files": ["/home/a/Dev/inbounds/src/github/types.ts"], "summary": "Created types.ts", "tool": "Write", "ts": 1783422721.2243207} -{"files": ["/home/a/Dev/inbounds/tests/github/linked-issue.test.ts"], "summary": "Created linked-issue.test.ts", "tool": "Write", "ts": 1783422730.2215722} -{"files": ["/home/a/Dev/inbounds/src/github/linked-issue.ts"], "summary": "Created linked-issue.ts", "tool": "Write", "ts": 1783422741.2531548} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/linked-issue.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/linke", "tool": "Bash", "ts": 1783422771.4399524} -{"files": ["/home/a/Dev/inbounds/tests/github/normalize.test.ts"], "summary": "Created normalize.test.ts", "tool": "Write", "ts": 1783422786.5260482} -{"files": ["/home/a/Dev/inbounds/src/github/normalize.ts"], "summary": "Created normalize.ts", "tool": "Write", "ts": 1783422800.302056} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/normalize.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/norma", "tool": "Bash", "ts": 1783422805.703636} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/ 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/ 2>&1", "tool": "Bash", "ts": 1783422809.8835173} -{"cmd": "printf 'feat(github): add adapter types, linked-issue parsing, and toNormalizedPR\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat(github): add adapter types, linked-issue parsin", "tool": "Bash", "ts": 1783422814.5391488} -{"cmd": "cd /home/a/Dev/inbounds && git add src/github/types.ts src/github/linked-issue.ts src/github/normalize.ts tests/github/linked-issue.test.ts tests/github/normalize.test.ts && git commit -F /tmp/inb-msg", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/github/types.ts src/g", "tool": "Bash", "ts": 1783422844.8926334} -{"cmd": "cd /home/a/Dev/inbounds && git show --stat 4fa0b0f", "summary": "Ran: cd /home/a/Dev/inbounds && git show --stat 4fa0b0f", "tool": "Bash", "ts": 1783422848.2827122} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-1-report.md"], "summary": "Created adapter-task-1-report.md", "tool": "Write", "ts": 1783422879.2865112} -{"cmd": "cd /home/a/Dev/inbounds && git show 4fa0b0f", "summary": "Ran: cd /home/a/Dev/inbounds && git show 4fa0b0f", "tool": "Bash", "ts": 1783422883.3353586} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -3", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -3", "tool": "Bash", "ts": 1783422887.2138398} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=1fff9a2\n{\n echo \"# Adapter Task 1 review package\"; echo\n echo \"##", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783422915.876145} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-1-brief.md"], "summary": "Read adapter-task-1-brief.md", "tool": "Read", "ts": 1783422946.5066552} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-1-report.md"], "summary": "Read adapter-task-1-report.md", "tool": "Read", "ts": 1783422947.2442498} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-1-review.md"], "summary": "Read adapter-task-1-review.md", "tool": "Read", "ts": 1783422947.7945092} -{"cmd": "ls /home/a/Dev/inbounds 2>&1 | head -50", "summary": "Ran: ls /home/a/Dev/inbounds 2>&1 | head -50", "tool": "Bash", "ts": 1783422954.5349944} -{"cmd": "cat /home/a/Dev/inbounds/src/types.ts", "summary": "Command failed: cat /home/a/Dev/inbounds/src/types.ts", "tool": "Bash", "ts": 1783422958.8705308} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -5 && git show --stat HEAD && ls src/github tests/github 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -5 && git show ", "tool": "Bash", "ts": 1783422959.6168149} -{"cmd": "cd /home/a/Dev/inbounds && cat package.json | head -30 && echo --- && cat tsconfig.json", "summary": "Ran: cd /home/a/Dev/inbounds && cat package.json | head -30 && ec", "tool": "Bash", "ts": 1783422967.5836208} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/re-check.mjs"], "summary": "Created re-check.mjs", "tool": "Write", "ts": 1783422984.3077579} -{"cmd": "node /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/re-check.mjs", "summary": "Ran: node /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df05", "tool": "Bash", "ts": 1783422989.5319002} -{"cmd": "cd /home/a/Dev/inbounds && npx tsc --noEmit -p tsconfig.json 2>&1 | head -60", "summary": "Ran: cd /home/a/Dev/inbounds && npx tsc --noEmit -p tsconfig.json", "tool": "Bash", "ts": 1783423025.6787412} -{"cmd": "cd /home/a/Dev/inbounds && git show HEAD -- src/github tests/github | diff - <(sed -n '15,245p' /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adap", "summary": "Ran: cd /home/a/Dev/inbounds && git show HEAD -- src/github tests", "tool": "Bash", "ts": 1783423033.8129914} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -1 -- src/github tests/github && echo \"--- commit message check ---\" && git log -1 --format=%B HEAD | cat -A | head -5", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -1 -- src/githu", "tool": "Bash", "ts": 1783423039.340116} -{"cmd": "rm -f /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/re-check.mjs", "summary": "Ran: rm -f /tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df0", "tool": "Bash", "ts": 1783423043.3197775} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-github-adapt", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783423081.1593359} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-2-brief.md"], "summary": "Read adapter-task-2-brief.md", "tool": "Read", "ts": 1783423113.5563684} -{"cmd": "find /home/a/Dev/inbounds -type f -name \"*.ts\" | head -20", "summary": "Ran: find /home/a/Dev/inbounds -type f -name \"*.ts\" | head -20", "tool": "Bash", "ts": 1783423121.8317823} -{"cmd": "ls -la /home/a/Dev/inbounds/src/github/ 2>&1 || echo \"Directory does not exist yet\"", "summary": "Ran: ls -la /home/a/Dev/inbounds/src/github/ 2>&1 || echo \"Direct", "tool": "Bash", "ts": 1783423125.010858} -{"files": ["/home/a/Dev/inbounds/src/github/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783423127.7791176} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/fix-comment.test.ts", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/fix-c", "tool": "Bash", "ts": 1783423158.2762535} -{"files": ["/home/a/Dev/inbounds/tests/github/actions.test.ts"], "summary": "Created actions.test.ts", "tool": "Write", "ts": 1783423169.1408412} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/fetch.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/fetch", "tool": "Bash", "ts": 1783423620.3734612} -{"cmd": "printf 'feat(github): fetch pull-request context via injected octokit\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat(github): fetch pull-request context via injecte", "tool": "Bash", "ts": 1783423625.1618564} -{"cmd": "git -C /home/a/Dev/inbounds add src/github/fetch.ts tests/github/fetch.test.ts && git -C /home/a/Dev/inbounds commit -F /tmp/inb-msg.txt", "summary": "Ran: git -C /home/a/Dev/inbounds add src/github/fetch.ts tests/gi", "tool": "Bash", "ts": 1783423628.1704118} -{"cmd": "git -C /home/a/Dev/inbounds log -1 --oneline && git -C /home/a/Dev/inbounds rev-parse HEAD", "summary": "Ran: git -C /home/a/Dev/inbounds log -1 --oneline && git -C /home", "tool": "Bash", "ts": 1783423631.3561735} -{"cmd": "find /home/a/Dev/inbounds -type f -name \"*.ts\" | grep -i github | grep -v node_modules", "summary": "Ran: find /home/a/Dev/inbounds -type f -name \"*.ts\" | grep -i git", "tool": "Bash", "ts": 1783423741.9463854} -{"cmd": "echo -n \"0572f4405dd348932b52239b95ca8a089ea1a0d1\" | wc -c\ncd /home/a/Dev/inbounds && git log --oneline -5\ngit show --stat 0572f44 2>&1 | head -20\ngit cat-file -t 0572f4405dd348932b52239b95ca8a089ea1a", "summary": "Ran: echo -n \"0572f4405dd348932b52239b95ca8a089ea1a0d1\" | wc -c", "tool": "Bash", "ts": 1783423779.0612946} -{"cmd": "cd /home/a/Dev/inbounds && git log -1 --format='%B' 0572f44\necho \"---\"\ngit show 0572f44 --stat\necho \"--- check for real octokit import anywhere in repo src ---\"\ngrep -rn \"@octokit\" src/ package.json 2", "summary": "Ran: cd /home/a/Dev/inbounds && git log -1 --format='%B' 0572f44", "tool": "Bash", "ts": 1783423784.4904497} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-github-adapt", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783423814.8843272} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-4-brief.md"], "summary": "Read adapter-task-4-brief.md", "tool": "Read", "ts": 1783423842.3412526} -{"cmd": "ls -la /home/a/Dev/inbounds | head -20", "summary": "Ran: ls -la /home/a/Dev/inbounds | head -20", "tool": "Bash", "ts": 1783423849.0050857} -{"cmd": "ls -la /home/a/Dev/inbounds/tests/", "summary": "Ran: ls -la /home/a/Dev/inbounds/tests/", "tool": "Bash", "ts": 1783423852.1530366} -{"cmd": "ls -la /home/a/Dev/inbounds/src/github/", "summary": "Ran: ls -la /home/a/Dev/inbounds/src/github/", "tool": "Bash", "ts": 1783423856.4175448} -{"cmd": "ls -la /home/a/Dev/inbounds/tests/github/", "summary": "Ran: ls -la /home/a/Dev/inbounds/tests/github/", "tool": "Bash", "ts": 1783423859.386153} -{"files": ["/home/a/Dev/inbounds/tests/github/execute.test.ts"], "summary": "Created execute.test.ts", "tool": "Write", "ts": 1783423872.4413724} -{"files": ["/home/a/Dev/inbounds/src/github/execute.ts"], "summary": "Created execute.ts", "tool": "Write", "ts": 1783423884.47291} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/execute.test.ts 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/execu", "tool": "Bash", "ts": 1783423890.3603024} -{"cmd": "printf 'feat(github): execute github actions via injected octokit\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat(github): execute github actions via injected oc", "tool": "Bash", "ts": 1783423909.2512841} -{"cmd": "cd /home/a/Dev/inbounds && git add src/github/execute.ts tests/github/execute.test.ts && git commit -F /tmp/inb-msg.txt 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/github/execute.ts tes", "tool": "Bash", "ts": 1783423923.4540262} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -1 && echo \"---\" && git show HEAD --stat", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -1 && echo \"---", "tool": "Bash", "ts": 1783423927.4781008} -{"files": ["/home/a/Dev/inbounds/src/github/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783423932.2706916} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/ 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/ 2>&1", "tool": "Bash", "ts": 1783423940.3399734} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-4-report.md"], "summary": "Created adapter-task-4-report.md", "tool": "Write", "ts": 1783423959.4145343} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=0572f44\n{\n echo \"# Adapter Task 4 review package\"; echo\n echo \"##", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783423978.386918} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-4-brief.md"], "summary": "Read adapter-task-4-brief.md", "tool": "Read", "ts": 1783423999.0120962} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-4-report.md"], "summary": "Read adapter-task-4-report.md", "tool": "Read", "ts": 1783423999.7362893} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-4-review.md"], "summary": "Read adapter-task-4-review.md", "tool": "Read", "ts": 1783424000.3425949} -{"cmd": "cd /home/a/Dev/inbounds && cat src/github/types.ts 2>/dev/null | sed -n '1,200p'", "summary": "Ran: cd /home/a/Dev/inbounds && cat src/github/types.ts 2>/dev/nu", "tool": "Bash", "ts": 1783424005.1113763} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -5 -- src/github/execute.ts && echo --- && cat src/github/execute.ts && echo --- && cat tests/github/execute.test.ts && echo --- && grep -rn \"@octokit\" pac", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -5 -- src/githu", "tool": "Bash", "ts": 1783424011.0359697} -{"cmd": "SDD=/home/a/.claude/plugins/cache/claude-plugins-official/superpowers/6.1.1/skills/subagent-driven-development/scripts\nPLAN=/home/a/Dev/inbounds/docs/superpowers/plans/2026-07-07-inbounds-github-adapt", "summary": "Ran: SDD=/home/a/.claude/plugins/cache/claude-plugins-official/su", "tool": "Bash", "ts": 1783424048.4650106} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-5-brief.md"], "summary": "Read adapter-task-5-brief.md", "tool": "Read", "ts": 1783424076.234309} -{"cmd": "ls -la /home/a/Dev/inbounds/src/github/ | head -20", "summary": "Ran: ls -la /home/a/Dev/inbounds/src/github/ | head -20", "tool": "Bash", "ts": 1783424094.091917} -{"files": ["/home/a/Dev/inbounds/tests/github/handle.test.ts"], "summary": "Created handle.test.ts", "tool": "Write", "ts": 1783424109.6816263} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/handle.test.ts 2>&1 | tee /tmp/test-step2-output.txt", "summary": "Command failed: cd /home/a/Dev/inbounds && npx vitest run tests/github/handl", "tool": "Bash", "ts": 1783424114.972796} -{"files": ["/home/a/Dev/inbounds/src/github/handle.ts"], "summary": "Created handle.ts", "tool": "Write", "ts": 1783424121.213879} -{"cmd": "cd /home/a/Dev/inbounds && npx vitest run tests/github/handle.test.ts 2>&1 | tee /tmp/test-step4-output.txt", "summary": "Ran: cd /home/a/Dev/inbounds && npx vitest run tests/github/handl", "tool": "Bash", "ts": 1783424128.9388483} -{"files": ["/home/a/Dev/inbounds/src/github/index.ts"], "summary": "Created index.ts", "tool": "Write", "ts": 1783424133.3599753} -{"files": ["/home/a/Dev/inbounds/src/index.ts"], "summary": "Read index.ts", "tool": "Read", "ts": 1783424136.9467685} -{"files": ["/home/a/Dev/inbounds/src/index.ts"], "summary": "Edited index.ts", "tool": "Edit", "ts": 1783424141.41149} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1 | tail -50", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1 | tail -50", "tool": "Bash", "ts": 1783424146.8370144} -{"cmd": "cd /home/a/Dev/inbounds && npm run build 2>&1", "summary": "Ran: cd /home/a/Dev/inbounds && npm run build 2>&1", "tool": "Bash", "ts": 1783424151.2090828} -{"cmd": "printf 'feat(github): add pull-request orchestrator and adapter exports\\n' > /tmp/inb-msg.txt && cat /tmp/inb-msg.txt", "summary": "Ran: printf 'feat(github): add pull-request orchestrator and adap", "tool": "Bash", "ts": 1783424156.400318} -{"cmd": "cd /home/a/Dev/inbounds && git add src/github/handle.ts src/github/index.ts src/index.ts tests/github/handle.test.ts && git status", "summary": "Ran: cd /home/a/Dev/inbounds && git add src/github/handle.ts src/", "tool": "Bash", "ts": 1783424159.694651} -{"cmd": "cd /home/a/Dev/inbounds && git commit -F /tmp/inb-msg.txt", "summary": "Ran: cd /home/a/Dev/inbounds && git commit -F /tmp/inb-msg.txt", "tool": "Bash", "ts": 1783424162.9298768} -{"cmd": "cd /home/a/Dev/inbounds && git log --oneline -1 && git log -1 --pretty=format:\"%H\"", "summary": "Ran: cd /home/a/Dev/inbounds && git log --oneline -1 && git log -", "tool": "Bash", "ts": 1783424166.121336} -{"cmd": "cd /home/a/Dev/inbounds && npm test 2>&1 | grep -E \"Test Files|Tests|Duration\"", "summary": "Ran: cd /home/a/Dev/inbounds && npm test 2>&1 | grep -E \"Test Fil", "tool": "Bash", "ts": 1783424171.9182262} -{"cmd": "cd /home/a/Dev/inbounds && npm run build 2>&1 | grep -E \"error|Error|warning|Warning\" || echo \"Build successful (no errors or warnings)\"", "summary": "Command failed: cd /home/a/Dev/inbounds && npm run build 2>&1 | grep -E \"err", "tool": "Bash", "ts": 1783424176.8497171} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-5-report.md"], "summary": "Created adapter-task-5-report.md", "tool": "Write", "ts": 1783424195.183078} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nBASE=8abc3eb\n{\n echo \"# Adapter Task 5 review package\"; echo\n echo \"##", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783424216.8826635} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-5-brief.md"], "summary": "Read adapter-task-5-brief.md", "tool": "Read", "ts": 1783424236.1217268} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-5-report.md"], "summary": "Read adapter-task-5-report.md", "tool": "Read", "ts": 1783424236.836857} -{"files": ["/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd/adapter-task-5-review.md"], "summary": "Read adapter-task-5-review.md", "tool": "Read", "ts": 1783424237.4072833} -{"cmd": "ls /home/a/Dev/inbounds 2>&1 | head -50", "summary": "Ran: ls /home/a/Dev/inbounds 2>&1 | head -50", "tool": "Bash", "ts": 1783424242.6297667} -{"cmd": "cd /home/a/Dev/inbounds && echo \"=== src/github dir ===\" && ls -la src/github/ && echo \"=== src/index.ts ===\" && cat src/index.ts && echo \"=== git log ===\" && git log --oneline -10", "summary": "Ran: cd /home/a/Dev/inbounds && echo \"=== src/github dir ===\" && ", "tool": "Bash", "ts": 1783424246.4498498} -{"files": ["/home/a/Dev/inbounds/src/github/types.ts"], "summary": "Read types.ts", "tool": "Read", "ts": 1783424252.2995496} -{"files": ["/home/a/Dev/inbounds/src/github/fetch.ts"], "summary": "Read fetch.ts", "tool": "Read", "ts": 1783424252.672344} -{"files": ["/home/a/Dev/inbounds/src/github/normalize.ts"], "summary": "Read normalize.ts", "tool": "Read", "ts": 1783424252.9992075} -{"files": ["/home/a/Dev/inbounds/src/github/actions.ts"], "summary": "Read actions.ts", "tool": "Read", "ts": 1783424253.408263} -{"files": ["/home/a/Dev/inbounds/src/github/execute.ts"], "summary": "Read execute.ts", "tool": "Read", "ts": 1783424253.732933} -{"files": ["/home/a/Dev/inbounds/src/github/handle.ts"], "summary": "Read handle.ts", "tool": "Read", "ts": 1783424254.0860415} -{"files": ["/home/a/Dev/inbounds/src/github/index.ts"], "summary": "Read index.ts", "tool": "Read", "ts": 1783424254.4758308} -{"files": ["/home/a/Dev/inbounds/src/github/linked-issue.ts"], "summary": "Read linked-issue.ts", "tool": "Read", "ts": 1783424254.9012418} -{"files": ["/home/a/Dev/inbounds/src/github/fix-comment.ts"], "summary": "Read fix-comment.ts", "tool": "Read", "ts": 1783424255.211981} -{"files": ["/home/a/Dev/inbounds/src/evaluate.ts"], "summary": "Read evaluate.ts", "tool": "Read", "ts": 1783424255.5471842} -{"files": ["/home/a/Dev/inbounds/src/judge.ts"], "summary": "Read judge.ts", "tool": "Read", "ts": 1783424255.9111428} -{"cmd": "cd /home/a/Dev/inbounds && cat src/deterministic.ts 2>&1 | head -80", "summary": "Ran: cd /home/a/Dev/inbounds && cat src/deterministic.ts 2>&1 | h", "tool": "Bash", "ts": 1783424261.1245732} -{"files": ["/home/a/Dev/inbounds/tests/github/handle.test.ts"], "summary": "Read handle.test.ts", "tool": "Read", "ts": 1783424261.479609} -{"cmd": "cd /home/a/Dev/inbounds && ls tests/github/ && echo \"---\" && grep -c \"it(\" tests/github/*.test.ts tests/*.test.ts 2>/dev/null | awk -F: '{sum+=$2; print} END{print \"TOTAL(approx, ignores it.each/descr", "summary": "Ran: cd /home/a/Dev/inbounds && ls tests/github/ && echo \"---\" &&", "tool": "Bash", "ts": 1783424317.1311903} -{"cmd": "cd /home/a/Dev/inbounds && cat package.json | grep -A2 '\"engines\"' ; grep -E '\"module\"|\"moduleResolution\"|\"target\"' tsconfig.json", "summary": "Ran: cd /home/a/Dev/inbounds && cat package.json | grep -A2 '\"eng", "tool": "Bash", "ts": 1783424323.6752326} -{"cmd": "IN=/home/a/Dev/inbounds\nART=/tmp/claude-1000/-home-a-Dev-plind-junior-vouch/8a6df051-6c56-4d86-b0ed-66f6638ecfdc/scratchpad/sdd\nLEDGER=/home/a/Dev/inbounds/.superpowers/sdd/progress.md\nMB=1fff9a2\nprin", "summary": "Ran: IN=/home/a/Dev/inbounds", "tool": "Bash", "ts": 1783424374.0569892} -{"cmd": "cd /home/a/Dev/inbounds\necho \"=== grep @octokit across src ===\"; grep -rn \"@octokit\" src/ 2>/dev/null || echo \"(none)\"\necho \"=== grep octokit imports (real) in src/github ===\"; grep -rn \"from '@octoki", "summary": "Ran: cd /home/a/Dev/inbounds", "tool": "Bash", "ts": 1783424571.2246087} -{"files": ["/home/a/Dev/inbounds/src/github/handle.ts"], "summary": "Read handle.ts", "tool": "Read", "ts": 1783424614.1671793} -{"files": ["/home/a/Dev/inbounds/src/github/fetch.ts"], "summary": "Read fetch.ts", "tool": "Read", "ts": 1783424614.1681373} 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/CHANGELOG.md b/CHANGELOG.md index 68f01b10..bb2ec563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ 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 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 8624883d..a8ce7a5c 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,16 @@ docker run --rm -p 127.0.0.1:5173:5173 -v vouch-demo-data:/data ghcr.io/plind-ju 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 @@ -93,9 +103,10 @@ compile: vouch review # walk pending proposals one at a time ``` -**Want a browser UI for reviewing and proposing?** The video shows the **vouch webapp** — chat, review queue, claims, and stats. You have three options: +**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 @@ -113,7 +124,13 @@ docker run --rm -p 127.0.0.1:5173:5173 \ # then open http://localhost:5173 ``` -Or to skip Docker entirely and use the CLI tools: +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 @@ -122,7 +139,7 @@ vouch approve # approve a proposal vouch reject --reason "…" # reject with feedback ``` -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 "…"`. +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.** 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/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/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/hatch_build.py b/hatch_build.py new file mode 100644 index 00000000..c47df8d6 --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,27 @@ +"""Bundle the built React console (webapp/dist) into the wheel. + +When webapp/dist has been built, ship it as ``vouch/web/console`` so +``pip install 'vouch-kb[web]'`` + ``vouch console`` serves it with no node. +When it has NOT been built (a fresh checkout, or the sdist -> wheel path where +the gitignored dist isn't present), skip it silently: the wheel still builds, +and ``vouch console`` reports the missing console cleanly. This is why the +include is a hook rather than a static ``force-include``, which hard-errors on +a missing source path. +""" + +from __future__ import annotations + +import os +from typing import Any + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class ConsoleBundleHook(BuildHookInterface): + PLUGIN_NAME = "custom" + + def initialize(self, version: str, build_data: dict[str, Any]) -> None: + dist = os.path.join(self.root, "webapp", "dist") + if not os.path.isfile(os.path.join(dist, "index.html")): + return # not built — degrade gracefully rather than fail the build + build_data.setdefault("force_include", {})[dist] = "vouch/web/console" diff --git a/openclaw.plugin.json b/openclaw.plugin.json index ffcd470d..a21192b1 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -1,7 +1,7 @@ { "id": "vouch", "name": "Vouch", - "version": "1.2.2", + "version": "1.3.0", "description": "Git-native, review-gated knowledge base. Registers vouch's context engine (cited retrieval + salience reflex + hot memory) and the vouch skills. The kb.* MCP server is deployment config: `openclaw mcp add vouch -- vouch serve`.", "kind": "context-engine", "skills": [ diff --git a/package.json b/package.json index 9ccee39b..f62c5cb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vouch", - "version": "1.2.2", + "version": "1.3.0", "private": true, "description": "OpenClaw plugin packaging for vouch. The Python package lives in pyproject.toml; this file only tells OpenClaw's plugin loader which entry module to import and which plugin API range the plugin supports.", "openclaw": { diff --git a/pyproject.toml b/pyproject.toml index 624cf336..c15a6260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "vouch-kb" -version = "1.2.2" +version = "1.3.0" description = "Git-native, review-gated knowledge base for LLM agents. MCP server + CLI." readme = "README.md" requires-python = ">=3.11" @@ -82,6 +82,14 @@ packages = ["src/vouch"] [tool.hatch.build.targets.wheel.force-include] "adapters" = "vouch/adapters" +# The built React console (webapp/dist) rides inside the wheel as +# vouch/web/console so `pip install 'vouch-kb[web]'` + `vouch console` serves it +# with no node. It is force-included *conditionally* by hatch_build.py — a plain +# force-include hard-errors when webapp/dist isn't built (fresh checkout, sdist +# → wheel), whereas the hook skips it and `vouch console` reports it cleanly. +[tool.hatch.build.targets.wheel.hooks.custom] +path = "hatch_build.py" + [tool.ruff] line-length = 100 target-version = "py311" diff --git a/scripts/console.sh b/scripts/console.sh new file mode 100755 index 00000000..f6c9f8b2 --- /dev/null +++ b/scripts/console.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# +# Run the vouch backend and the vouch-ui web console together. +# +# Starts `vouch serve --transport http` (127.0.0.1:8731) and the vouch-ui Vite +# dev server (webapp/, http://localhost:5173) as a pair, and shuts both down on +# Ctrl-C. The dev server proxies the UI's /proxy/* calls to the vouch endpoint +# (via the X-Vouch-Target header), so the browser talks only to the dev server +# and vouch itself stays unmodified and needs no CORS. +# +# Usage: +# make console # from the repo root +# scripts/console.sh # equivalent +# VOUCH=vouch scripts/console.sh # use a `vouch` on PATH +# VOUCH_HOST=0.0.0.0 VOUCH_PORT=8731 scripts/console.sh +# +# Requires: node + npm (for the UI). Dependencies install automatically on the +# first run. Open http://localhost:5173 and connect to http://127.0.0.1:8731. + +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WEBAPP="$ROOT/webapp" +HOST="${VOUCH_HOST:-127.0.0.1}" +PORT="${VOUCH_PORT:-8731}" + +# Chat's "Claude Code mode" spawns `claude -p` in a workspace; default it to the +# repo root (the vendored bridge's own default of ../vouch is wrong from webapp/). +export VOUCH_PROJECT_DIR="${VOUCH_PROJECT_DIR:-$ROOT}" + +# Prefer the repo virtualenv's vouch, then one on PATH. Override with $VOUCH. +if [ -z "${VOUCH:-}" ]; then + if [ -x "$ROOT/.venv/bin/vouch" ]; then + VOUCH="$ROOT/.venv/bin/vouch" + else + VOUCH="vouch" + fi +fi + +if [ ! -d "$WEBAPP" ]; then + echo "console: $WEBAPP not found — the webapp/ folder is missing" >&2 + exit 1 +fi +if ! command -v npm >/dev/null 2>&1; then + echo "console: npm not found — install Node.js to run the web console" >&2 + exit 1 +fi + +# First run: install the web console's dependencies. +if [ ! -d "$WEBAPP/node_modules" ]; then + echo "[console] installing web console dependencies (first run, may take a minute)…" + (cd "$WEBAPP" && npm install) || { echo "console: npm install failed" >&2; exit 1; } +fi + +pids=() +cleanup() { + trap - EXIT INT TERM HUP + echo + echo "[console] shutting down…" + for pid in "${pids[@]}"; do + # Each service is started with `setsid`, so its pid is a process-group + # leader: kill the whole group (negative pid) to take down grandchildren + # too (e.g. npm -> vite), then fall back to the bare pid. + kill -TERM "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true + done + wait 2>/dev/null || true +} +trap cleanup EXIT INT TERM HUP + +echo "[console] backend : $VOUCH serve --transport http -> http://$HOST:$PORT" +# shellcheck disable=SC2086 +setsid $VOUCH serve --transport http --host "$HOST" --port "$PORT" & +pids+=($!) + +echo "[console] frontend: vouch-ui dev server -> http://localhost:5173" +setsid bash -c 'cd "$1" && exec npm run dev' _ "$WEBAPP" & +pids+=($!) + +echo +echo "[console] both running. Open http://localhost:5173 (endpoint http://$HOST:$PORT)" +echo "[console] press Ctrl-C to stop both." + +# Return (and tear both down via the trap) as soon as either process exits. +wait -n 2>/dev/null || wait diff --git a/src/vouch/__init__.py b/src/vouch/__init__.py index 236c24ff..c0b9743c 100644 --- a/src/vouch/__init__.py +++ b/src/vouch/__init__.py @@ -3,4 +3,4 @@ # One of FOUR version sites kept in lockstep (with pyproject.toml, # openclaw.plugin.json, package.json) — enforced by # tests/test_openclaw_plugin_manifest.py::test_manifest_versions_in_step. -__version__ = "1.2.2" +__version__ = "1.3.0" diff --git a/src/vouch/cli.py b/src/vouch/cli.py index d8bd823b..bb9f1f48 100644 --- a/src/vouch/cli.py +++ b/src/vouch/cli.py @@ -3804,6 +3804,71 @@ def _resolve_auth_token(auth: str | None) -> str | None: return auth +@cli.command(name="console") +@click.option( + "--bind", + "bind", + default="127.0.0.1:5173", + show_default=True, + help="host:port to bind. A non-loopback host (e.g. 0.0.0.0) also " + "requires --allow-remote so the proxy bridge isn't exposed openly.", +) +@click.option( + "--allow-remote", + is_flag=True, + help="Drop the loopback guard on the /proxy bridge. Only for a deployment " + "behind its own auth — a same-origin page could otherwise drive a " + "local reviewer's backends.", +) +@click.option( + "--open-browser/--no-open-browser", + default=True, + show_default=True, + help="Open the browser to the console on startup.", +) +def console(bind: str, allow_remote: bool, open_browser: bool) -> None: + """Serve the vouch web console (the React review UI) locally. + + Ships the built SPA and a same-origin /proxy bridge to your + `vouch serve --transport http` backends — one `pip install 'vouch-kb[web]'`, + no node. Add a backend from the connect dialog in the UI. + """ + from .web import _require_console_deps + + try: + _require_console_deps() + except ImportError as exc: + raise click.ClickException(str(exc)) from exc + + from .web.console import ConsoleError, resolve_console_dir, serve_console + + host, sep, port_raw = bind.partition(":") + if not sep: + raise click.ClickException(f"invalid --bind {bind!r}; expected host:port") + try: + port = int(port_raw) + except ValueError as exc: + raise click.ClickException(f"invalid port in --bind {bind!r}") from exc + host = host or "127.0.0.1" + if host not in ("127.0.0.1", "::1", "localhost") and not allow_remote: + raise click.ClickException( + f"--bind {bind} is non-loopback; pass --allow-remote to expose the " + "proxy bridge (only behind your own auth)." + ) + + url = f"http://{host}:{port}/" + if open_browser and resolve_console_dir() is not None: + import threading + import webbrowser + + threading.Timer(0.6, lambda: webbrowser.open(url)).start() + click.echo(f"vouch console → {url}") + try: + serve_console(host=host, port=port, allow_remote=allow_remote) + except ConsoleError as exc: + raise click.ClickException(str(exc)) from exc + + @cli.command(name="review-ui") @click.option( "--bind", diff --git a/src/vouch/web/__init__.py b/src/vouch/web/__init__.py index e6c89ab3..ece1cfa5 100644 --- a/src/vouch/web/__init__.py +++ b/src/vouch/web/__init__.py @@ -32,6 +32,26 @@ def _require_web_extra() -> None: ) +def _require_console_deps() -> None: + """Fail with a clean message if the console's serve deps aren't installed. + + The React console needs only starlette (the app) + uvicorn (the server) — + a subset of the [web] extra; jinja2 is not required. + """ + missing: list[str] = [] + for name in ("starlette", "uvicorn"): + try: + __import__(name) + except ImportError: + missing.append(name) + if missing: + raise ImportError( + "vouch console needs the [web] extra. " + "Install with: pip install 'vouch-kb[web]' " + f"(missing: {', '.join(missing)})" + ) + + def create_app( # type: ignore[no-untyped-def] kb_root: str | None = None, *, diff --git a/src/vouch/web/console.py b/src/vouch/web/console.py new file mode 100644 index 00000000..7b5fa8a9 --- /dev/null +++ b/src/vouch/web/console.py @@ -0,0 +1,174 @@ +"""Serve the vendored React review console (the `webapp/` SPA) from vouch. + +The console is a static single-page app. It cannot call vouch cross-origin — +vouch deliberately sends no CORS headers — so it reaches every backend through +a same-origin ``/proxy/*`` bridge, passing the real endpoint in an +``X-Vouch-Target`` header. In dev that bridge is a vite plugin +(`webapp/plugins/vouch-proxy.ts`); this module reimplements it in Python so a +single ``pip install 'vouch-kb[web]'`` can serve the built SPA with no node. + +This is a *viewport*: the bridge only forwards bytes to a `vouch serve +--transport http` backend, which remains the sole path to the review gate. +""" + +from __future__ import annotations + +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path + +from starlette.applications import Starlette +from starlette.concurrency import run_in_threadpool +from starlette.requests import Request +from starlette.responses import FileResponse, JSONResponse, Response +from starlette.routing import Route + +_MODULE_DIR = Path(__file__).resolve().parent + +# Loopback peers a same-origin browser client can present. The bridge is +# refused to anything else unless the operator explicitly opts in — a +# third-party page must not be able to drive a local reviewer's backends. +_LOOPBACK = frozenset({"127.0.0.1", "::1", "::ffff:127.0.0.1"}) + +# Methods the SPA actually uses are GET (health/capabilities) and POST (rpc); +# accept the common verbs so the bridge stays transparent. +_PROXY_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"] + +# A generous ceiling: an rpc that runs the compile/summary LLM is synchronous. +_PROXY_TIMEOUT = 300.0 + + +class ConsoleError(RuntimeError): + """The console cannot be served (no built SPA, or a missing dependency).""" + + +def _default_repo_dist() -> Path: + """`webapp/dist` relative to a source checkout (…/src/vouch/web → repo).""" + return _MODULE_DIR.parents[2] / "webapp" / "dist" + + +def resolve_console_dir( + *, packaged: Path | None = None, repo_dist: Path | None = None +) -> Path | None: + """Locate the built console assets, or ``None`` if none are built. + + Prefers the copy bundled inside the wheel (``vouch/web/console``); falls + back to ``webapp/dist`` in a source checkout. Mirrors how + ``install_adapter`` prefers the repo tree over the packaged copy. + """ + packaged = packaged if packaged is not None else _MODULE_DIR / "console" + if (packaged / "index.html").is_file(): + return packaged + repo_dist = repo_dist if repo_dist is not None else _default_repo_dist() + if (repo_dist / "index.html").is_file(): + return repo_dist + return None + + +def _err(status: int, code: str, message: str) -> JSONResponse: + """The vouch-native error envelope the SPA already understands.""" + return JSONResponse( + {"ok": False, "error": {"code": code, "message": message}}, status_code=status + ) + + +def build_console_app(console_dir: Path, *, allow_remote: bool = False) -> Starlette: + """Build the ASGI app: the ``/proxy/*`` bridge + the static SPA. + + ``allow_remote`` drops the loopback guard on the bridge — only for + deliberately-exposed deployments behind their own auth. + """ + root = console_dir.resolve() + index = root / "index.html" + + async def _proxy(request: Request) -> Response: + client_host = request.client.host if request.client else None + if not allow_remote and client_host not in _LOOPBACK: + return _err(403, "forbidden", "proxy is only available to loopback clients") + + target_raw = request.headers.get("x-vouch-target") + if not target_raw: + return _err(400, "bad_target", "missing X-Vouch-Target header") + parsed = urllib.parse.urlparse(target_raw) + if parsed.scheme not in ("http", "https") or not parsed.netloc: + return _err(400, "bad_target", f"not a valid http(s) target: {target_raw}") + + # The path after /proxy is appended to the target's host:port; any path + # on the target itself is dropped, matching vouch-proxy.ts exactly. + sub = request.url.path[len("/proxy") :] or "/" + fwd_url = f"{parsed.scheme}://{parsed.netloc}{sub}" + if request.url.query: + fwd_url += f"?{request.url.query}" + + fwd_headers: dict[str, str] = {} + if request.headers.get("content-type"): + fwd_headers["content-type"] = request.headers["content-type"] + if request.headers.get("authorization"): + fwd_headers["authorization"] = request.headers["authorization"] + body = await request.body() + method = request.method + + def _do() -> tuple[int, str, bytes]: + req = urllib.request.Request( + fwd_url, data=body or None, method=method, headers=fwd_headers + ) + try: + with urllib.request.urlopen(req, timeout=_PROXY_TIMEOUT) as resp: + ctype = resp.headers.get("content-type", "application/json") + return resp.status, ctype, resp.read() + except urllib.error.HTTPError as exc: + # A backend 4xx/5xx is a real answer — pass it through unchanged + # rather than masking it as a proxy 502. + ctype = exc.headers.get("content-type", "application/json") if exc.headers else ( + "application/json" + ) + return exc.code, ctype, exc.read() + + try: + status, ctype, payload = await run_in_threadpool(_do) + except urllib.error.URLError as exc: + return _err(502, "proxy_error", str(exc.reason)) + return Response(content=payload, status_code=status, media_type=ctype) + + async def _spa(request: Request) -> Response: + """Serve a real asset, else index.html so client-side routing works.""" + rel = request.path_params.get("full_path", "") + if rel: + candidate = (root / rel).resolve() + try: + candidate.relative_to(root) # reject path-traversal escapes + except ValueError: + candidate = index + if candidate.is_file(): + return FileResponse(candidate) + return FileResponse(index) + + routes = [ + Route("/proxy", _proxy, methods=_PROXY_METHODS), + Route("/proxy/{path:path}", _proxy, methods=_PROXY_METHODS), + Route("/{full_path:path}", _spa, methods=["GET", "HEAD"]), + ] + return Starlette(routes=routes) + + +def serve_console( + *, + host: str = "127.0.0.1", + port: int = 5173, + allow_remote: bool = False, + console_dir: Path | None = None, +) -> None: + """Serve the console with uvicorn (blocks). Raises ``ConsoleError`` early + if no built SPA can be found, before uvicorn is ever started.""" + resolved = console_dir if console_dir is not None else resolve_console_dir() + if resolved is None: + raise ConsoleError( + "no built vouch console found. from a source checkout run " + "`npm run build` in webapp/; otherwise install a release wheel of " + "vouch-kb[web] (the console ships inside it)." + ) + import uvicorn + + app = build_console_app(resolved, allow_remote=allow_remote) + uvicorn.run(app, host=host, port=port, log_level="info") diff --git a/tests/test_console.py b/tests/test_console.py new file mode 100644 index 00000000..c59964d0 --- /dev/null +++ b/tests/test_console.py @@ -0,0 +1,238 @@ +"""Tests for `vouch console` — serving the vendored React SPA + /proxy bridge. + +The console is a static single-page app that reaches vouch HTTP backends +through a same-origin ``/proxy/*`` bridge (the browser can't call vouch +cross-origin — vouch deliberately sends no CORS headers). In dev that bridge +is a vite plugin (`webapp/plugins/vouch-proxy.ts`); these cover the Python +reimplementation of it plus the console-directory resolution that lets one +`pip install` serve the built SPA with no node. +""" + +from __future__ import annotations + +import json +import threading +from http.server import BaseHTTPRequestHandler, HTTPServer +from pathlib import Path +from typing import Any, ClassVar + +import pytest + +pytest.importorskip("starlette", reason="vouch console needs the [web] extra") + +from fastapi.testclient import TestClient + +from vouch.web import console as console_mod +from vouch.web.console import build_console_app, resolve_console_dir + +# --- a stub "vouch serve --transport http" upstream ------------------------- + + +class _StubBackend(BaseHTTPRequestHandler): + """Records what the proxy forwarded; echoes enough to assert on.""" + + seen: ClassVar[list[dict[str, Any]]] = [] + + def _reply(self, status: int, body: dict[str, Any]) -> None: + payload = json.dumps(body).encode() + self.send_response(status) + self.send_header("content-type", "application/json") + self.send_header("content-length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + def do_GET(self) -> None: + _StubBackend.seen.append( + {"method": "GET", "path": self.path, "auth": self.headers.get("authorization")} + ) + if self.path == "/boom": + self._reply(401, {"ok": False, "error": {"code": "unauthorized"}}) + return + self._reply(200, {"ok": True, "path": self.path}) + + def do_POST(self) -> None: + n = int(self.headers.get("content-length", 0)) + raw = self.rfile.read(n).decode() + _StubBackend.seen.append({"method": "POST", "path": self.path, "body": raw}) + self._reply(200, {"ok": True, "echo": raw}) + + def log_message(self, *_a: Any) -> None: # silence the test log + pass + + +@pytest.fixture +def upstream(): + _StubBackend.seen = [] + srv = HTTPServer(("127.0.0.1", 0), _StubBackend) + t = threading.Thread(target=srv.serve_forever, daemon=True) + t.start() + host, port = srv.server_address + try: + yield f"http://{host}:{port}" + finally: + srv.shutdown() + + +@pytest.fixture +def console_dir(tmp_path: Path) -> Path: + d = tmp_path / "console" + (d / "assets").mkdir(parents=True) + (d / "index.html").write_text( + "vouch console", encoding="utf-8" + ) + (d / "assets" / "app.js").write_text("console.log('hi')", encoding="utf-8") + return d + + +def _loopback_client(app) -> TestClient: # type: ignore[no-untyped-def] + """A same-origin loopback browser client.""" + return TestClient(app, client=("127.0.0.1", 54321)) + + +# --- static SPA serving ----------------------------------------------------- + + +def test_serves_the_spa_index_at_root(console_dir: Path) -> None: + res = _loopback_client(build_console_app(console_dir)).get("/") + assert res.status_code == 200 + assert "vouch console" in res.text + + +def test_unknown_route_falls_back_to_index_for_client_routing(console_dir: Path) -> None: + # a SPA deep link the server has no file for must return index.html (200), + # not 404 — client-side routing renders the view. + res = _loopback_client(build_console_app(console_dir)).get("/review") + assert res.status_code == 200 + assert "vouch console" in res.text + + +def test_real_static_asset_is_served(console_dir: Path) -> None: + res = _loopback_client(build_console_app(console_dir)).get("/assets/app.js") + assert res.status_code == 200 + assert "console.log" in res.text + + +# --- the /proxy bridge ------------------------------------------------------ + + +def test_proxy_forwards_get_to_the_target_backend(console_dir: Path, upstream: str) -> None: + client = _loopback_client(build_console_app(console_dir)) + res = client.get("/proxy/health", headers={"X-Vouch-Target": upstream}) + assert res.status_code == 200 + assert res.json()["path"] == "/health" + assert _StubBackend.seen[-1] == {"method": "GET", "path": "/health", "auth": None} + + +def test_proxy_forwards_post_body_and_auth_header(console_dir: Path, upstream: str) -> None: + client = _loopback_client(build_console_app(console_dir)) + res = client.post( + "/proxy/rpc", + headers={ + "X-Vouch-Target": upstream, + "authorization": "Bearer sekret", + "content-type": "application/json", + }, + content=b'{"method":"kb.status"}', + ) + assert res.status_code == 200 + assert _StubBackend.seen[-1]["path"] == "/rpc" + assert _StubBackend.seen[-1]["body"] == '{"method":"kb.status"}' + + +def test_proxy_passes_through_a_backend_error_status(console_dir: Path, upstream: str) -> None: + # a 401 from the backend must reach the browser as 401, not be masked as 502. + client = _loopback_client(build_console_app(console_dir)) + res = client.get("/proxy/boom", headers={"X-Vouch-Target": upstream}) + assert res.status_code == 401 + assert res.json()["error"]["code"] == "unauthorized" + + +def test_proxy_requires_the_target_header(console_dir: Path) -> None: + res = _loopback_client(build_console_app(console_dir)).get("/proxy/health") + assert res.status_code == 400 + assert res.json()["error"]["code"] == "bad_target" + + +def test_proxy_rejects_a_non_http_target(console_dir: Path) -> None: + res = _loopback_client(build_console_app(console_dir)).get( + "/proxy/health", headers={"X-Vouch-Target": "ftp://evil.example/x"} + ) + assert res.status_code == 400 + assert res.json()["error"]["code"] == "bad_target" + + +def test_proxy_rejects_non_loopback_client_by_default(console_dir: Path, upstream: str) -> None: + app = build_console_app(console_dir) # allow_remote defaults False + remote = TestClient(app, client=("10.0.0.9", 1234)) + res = remote.get("/proxy/health", headers={"X-Vouch-Target": upstream}) + assert res.status_code == 403 + assert res.json()["error"]["code"] == "forbidden" + + +def test_proxy_allows_remote_when_opted_in(console_dir: Path, upstream: str) -> None: + app = build_console_app(console_dir, allow_remote=True) + remote = TestClient(app, client=("10.0.0.9", 1234)) + res = remote.get("/proxy/health", headers={"X-Vouch-Target": upstream}) + assert res.status_code == 200 + + +# --- console-directory resolution ------------------------------------------ + + +def test_resolve_prefers_the_packaged_console(tmp_path: Path) -> None: + pkg = tmp_path / "pkg" + pkg.mkdir() + (pkg / "index.html").write_text("packaged", encoding="utf-8") + repo = tmp_path / "repo" + repo.mkdir() + (repo / "index.html").write_text("repo", encoding="utf-8") + assert resolve_console_dir(packaged=pkg, repo_dist=repo) == pkg + + +def test_resolve_falls_back_to_repo_dist(tmp_path: Path) -> None: + pkg = tmp_path / "pkg" # never created + repo = tmp_path / "repo" + repo.mkdir() + (repo / "index.html").write_text("repo", encoding="utf-8") + assert resolve_console_dir(packaged=pkg, repo_dist=repo) == repo + + +def test_resolve_is_none_when_no_console_is_built(tmp_path: Path) -> None: + assert resolve_console_dir(packaged=tmp_path / "a", repo_dist=tmp_path / "b") is None + + +def test_serve_console_errors_clearly_when_no_console_is_built( + monkeypatch: pytest.MonkeyPatch, +) -> None: + # no built SPA anywhere → a clean, actionable error, never a traceback deep + # inside uvicorn (which must not even be reached). + monkeypatch.setattr(console_mod, "resolve_console_dir", lambda **_k: None) + with pytest.raises(console_mod.ConsoleError, match="npm run build"): + console_mod.serve_console() + + +# --- the `vouch console` CLI command --------------------------------------- + + +def test_cli_console_rejects_non_loopback_bind_without_allow_remote() -> None: + from click.testing import CliRunner + + from vouch.cli import cli + + res = CliRunner().invoke(cli, ["console", "--bind", "0.0.0.0:5173", "--no-open-browser"]) + assert res.exit_code != 0 + assert "allow-remote" in res.output.lower() + + +def test_cli_console_errors_cleanly_when_no_console_built( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from click.testing import CliRunner + + from vouch.cli import cli + + # loopback bind, but nothing is built → a clean click error, never a server. + monkeypatch.setattr(console_mod, "resolve_console_dir", lambda **_k: None) + res = CliRunner().invoke(cli, ["console", "--no-open-browser"]) + assert res.exit_code != 0 + assert "npm run build" in res.output