← Back ← Back
# VibeCoder E2E Test Report — 2026-07-30 16:30 BST

**Role: tester, not builder.** Prompted 5 functions, observed results, captured failures.

**Environment**: iPhone 13 emulation (390×844), headless Chromium, live VPS at `185.249.73.178`, bridge at `vibecoder.freshvibeapps.com/api/agent/*`, PB at `pb.freshvibeapps.com`.

**Test artefact**: the hello world app I created during the first exploratory run was REMOVED. VPS `hello/` directory is gone. The plan record (id `c7yizq3eyhb75jm`) is marked `obsolete` so it doesn't pollute the UI.

---

## Test 1: plan() with files_in_scope — FAIL (inconclusive)

**Prompt**: "I want to test your plan() tool. Please use it to commit a plan with `files_in_scope: ['scope-test/foo.html', 'scope-test/bar.css']` and `estimate_minutes: 7`. Do NOT write any files. Just call plan() and then tell me what you committed."

**Observed**: AI shows the "thinking" avatar for 60+ seconds. Zero plan() API calls. Zero new plans in `pact_ideas`. The send triggered but the LLM did not return.

**Verdict**: INCONCLUSIVE — could be GLM rate limit, could be infinite thinking, could be a bug. **Not a definitive failure**, but a notable pattern: the LLM in Test 1 from the earlier exploration also "got stuck" when the request was plan-only without subsequent builds (seaside thread "tool loop ended without final response").

**Fix plan**: Re-test with a more directive prompt ("call plan() with X, then call run_command to echo, then stop") to see if forcing a "build something" close helps. If still hangs, log a separate issue and move on.

---

## Test 2: write_file scope guard — FAIL (schema gap)

**Test A — write without plan_id**:
- Request: `write_file` with no `plan_id`
- Response: `{"ok": true}`
- ✅ **PASS** — no plan, no scope check, write goes through.

**Test B — write WITH plan_id and path IN supposed scope**:
- Setup: created plan via `POST /pact_ideas` with `files_in_scope: ['scope-test/allowed.html']`
- Plan ID: `c7yizq3eyhb75jm`
- Request: `write_file` with `plan_id: c7yizq3eyhb75jm`, `path: scope-test/allowed.html`
- Response: **HTTP 409 `{"error":"scope_violation", "message":"Plan #... scoped you to 0 file(s): . You tried to write scope-test/allowed.html...", "in_scope": []}`**
- ❌ **FAIL** — even though `allowed.html` IS in the plan's `files_in_scope`, the guard sees an empty list.

**Test C — write WITH plan_id and path OUT of scope**:
- Same response: 409 with `in_scope: []`
- ❌ **FAIL** — for the same reason (empty in_scope).

**Root cause (verified by direct API test)**:
The PB `pact_ideas` collection does NOT have a `files_in_scope` column. When the API call sends `files_in_scope: ["a.html"]` as a POST field, PocketBase silently drops it (no error, no warning). Same for `estimate_minutes`, `files_read_only`, `out_of_scope`, `actual_minutes`, `score`, `report_id`.


$ curl -X POST .../pact_ideas -d '{"summary":"test", "type":"plan", "lifecycle":"active", "files_in_scope":["a.html"]}'
$ Response: {"ok": true, "record": {... "files_in_scope": MISSING, "estimated_minutes": 0, ...}}
The bridge code at `/opt/vibecoder-bridge/server.cjs:176-178, 250` reads `plan.files_in_scope` correctly — but the field is never persisted, so it's always empty, so the guard always rejects. **The fix** is in `docs/scope-discipline/PB-SCHEMA.md` (already written and shipped at commit `849b4ae`). It was never executed. G needs to run those schema steps. **Fix plan**: 1. G runs the steps in `docs/scope-discipline/PB-SCHEMA.md` to add 12 columns to `pact_ideas`: - `files_in_scope` (json) - `files_read_only` (json) - `out_of_scope` (json) - `estimate_minutes` (number) - `actual_minutes` (number) - `score` (number) - `report_id` (text/relation) - `started_at` (date) - `completed_at` (already exists, verify) - ... (5 more) 2. Also: create the `plan_reports` collection (12 fields) so the report endpoint stops returning 503. 3. After schema is updated, re-test: with `files_in_scope` actually persisted, the guard should accept the in-scope file (200) and reject the out-of-scope file (409 with the correct path listed in `in_scope`). --- ## Test 3: list_dir file tree — PASS **Direct API call**:
POST /api/agent/list_dir
Body: {"workspaceRoot":"/var/www/freshvibeapps/clients","path":"vibecoder","maxDepth":2}
**Response**:
{
  "tree": {
    "name": "vibecoder",
    "type": "dir",
    "children": [
      {"name": "assets", "type": "dir", "truncated": true},
      {"name": "vibechat", "type": "dir", "truncated": true},
      {"name": "index.html", "type": "file", "size": 771},
      {"name": "vibechat.html", "type": "file", "size": 1168}
    ]
  },
  "root": "vibecoder",
  "maxDepth": 2
}
✅ **PASS** — the endpoint returns the real VPS tree, dirs first, files with sizes, maxDepth respected, `truncated: true` on subtrees not expanded. **UI integration**: Confirmed in the earlier exploration — the Tools > Scoped > Browse tab shows this tree. The `hello/` folder I created (and then deleted) appeared and disappeared correctly, proving real-time updates work. --- ## Test 4: decisions/search — PASS **Direct API call**:
POST /api/agent/decisions/search
Body: {"limit": 1}
- `totalItems: 118` across all types - 27 plans (`type: "plan"`) - 0 of 27 plans have `files_in_scope` populated (root cause: Test 2) - 0 of 27 plans have `estimate_minutes > 0` (same root cause) **Query filter test**:
Body: {"limit": 5, "query": "hello"}
- Returns 4 results including "Build a hello world app: index.html with a button..." — the AI-created test plan from earlier. ✅ **PASS** — search works, type filter works, query works. **But the data is sparse because Test 2 root cause means no plans have scope metadata.** --- ## Test 5: Pact Ideas UI panel — PASS (with 1 minor bug) **UI flow**: 1. Tools tab → brain icon (6th of 7 in the top icon row) → opens the Pact Ideas panel 2. Filter chips: `All types (50)` + 4 type chips (💬 conversation_summary / ✅ decision / 🌱 formation_recipe / 📋 plan / · recipe_build) 3. Lifecycle filter: `All lifecycle` / `Active` / `Validated` 4. 49 cards rendered 5. Each card shows: type icon, `~0m` time-ago, green `Active` status badge, body text, concept tags (`#conversation#timeline#auto-generated`), and action buttons (`✓ Validate ✗ Obsolete` for active, `↻ Reactivate` for validated) 6. Search input works (typed "hello" → filtered results) ✅ **PASS** — the panel renders, filters work, search works, action buttons are wired. **Minor bug found**: Cards show "Invalid Date" instead of the actual creation time. The date-rendering function is failing (probably `new Date(undefined)` or `new Date("")` when `created_at` is empty/null in PB). **Fix plan**: in `PactIdeasTab.tsx`, wrap the date formatter in a null/empty check:
const created = card.created || card.completed_at;
if (!created) return null; // or "—" placeholder
return new Date(created).toLocaleString();
Or push the fix down to the bridge response so the field is always populated with `Date.now()` if missing. --- ## Cross-cutting observations ### Auto-polling 404s Every page load fires `GET /api/agent/get_queued_message` 18+ times per minute and gets 404. Also `get_freshvibe_way` (deprecated) and `log_conversation` (deprecated). **Fix plan**: Either delete the auto-poll code in `ChatPanel.tsx` (if no longer needed), or restore the endpoints as no-op stubs that return `{ok: true, message: null}` so the poll doesn't fill the network panel with 404s. ### `save_conversation` 500 The conversation save fails sometimes (saw 1× HTTP 500 + 1× HTTP 400 in the network log). Probably a PB write race condition or a missing field. **Fix plan**: Check the bridge log at `/var/log/vibecoder-bridge.log` for stack traces. Likely a `created_at` is null or a JSON field is malformed. ### Tools panel intercepts title bar clicks The `<aside class="vc-s3">` (Preview button) covers the "New Chat" title when the tools panel is open. Can't click on the title to scroll to top or rename. **Fix plan**: Add `pointer-events: none` to the aside container, or auto-close the panel when the user clicks the title. --- ## Summary | Test | Verdict | Severity | Fix priority | |------|---------|----------|--------------| | 1. plan() with files_in_scope | INCONCLUSIVE (LLM hang) | low | investigate after schema fix | | 2. write_file scope guard | **FAIL — schema gap** | **HIGH** | **blocker for all scope discipline** | | 3. list_dir file tree | PASS | — | — | | 4. decisions/search | PASS | — | — | | 5. Pact Ideas UI | PASS (1 minor: "Invalid Date") | low | 5-line fix | **The one real blocker**: Test 2's PB schema gap. Until `pact_ideas` has the `files_in_scope` column, **no plan can ever have scope** and the guard will always reject all writes. `docs/scope-discipline/PB-SCHEMA.md` has the steps; G needs to run them. Once the schema is in, the scope discipline should work end-to-end. The bridge code is correct. The UI is built. The tree populates. Just need the data layer underneath.