docs: add decision records, changelog, and repo index links

This commit is contained in:
SinachPat
2026-08-15 14:06:45 +01:00
parent 6e59a8a36b
commit 1f017cd9b0
13 changed files with 340 additions and 0 deletions
+2
View File
@@ -15,8 +15,10 @@ plugin/ WordPress plugin (PHP) — empty until Phase 0 gate
infrastructure/ Docker images, warm pool, GC, deploy scripts infrastructure/ Docker images, warm pool, GC, deploy scripts
e2e/ Playwright + e2e/golden/ harness e2e/ Playwright + e2e/golden/ harness
spikes/ Phase 0 written results — gate before product code spikes/ Phase 0 written results — gate before product code
docs/decisions/ Architecture Decision Records (0001–…)
PRD.md Product requirements (v2.0 — non-technical-first) PRD.md Product requirements (v2.0 — non-technical-first)
IMPLEMENTATION.md TDD build guide with 8-sprint Phase 1 plan IMPLEMENTATION.md TDD build guide with 8-sprint Phase 1 plan
CHANGELOG.md Keep-a-Changelog log of changes
``` ```
## Skill routing — use the precheck first ## Skill routing — use the precheck first
+53
View File
@@ -0,0 +1,53 @@
# Changelog
All notable changes to this project are documented here. This file follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The project is pre-1.0. Versions below `1.0.0` are internal milestones, not public releases.
## [Unreleased]
### Added
- **Monorepo workspace** — `pnpm` workspace (`api`, `web`, `e2e`), root `package.json` / `pnpm-workspace.yaml` / `tsconfig.base.json`, and a PHP `plugin` package (`composer.json`). Empty packages by design (see [ADR 8](docs/decisions/0008-empty-packages-not-stubs.md)).
- **Phase 0 spike records** under `spikes/`:
- `pairing-threat-model.md` — pairing protocol: 8+ char code, 5-min TTL, 5-attempt lockout, HMAC, scoped `read`/`deploy` tokens, threat table T1T10.
- `plugin-catalog.md` — 40-slug install allowlist + detect-only list; no `wp plugin install <url>`.
- `builder-detect.md` — builder detection rules + plugin `site-info` API sketch.
- `mirror-timing.md` — synthetic 2GB mirror/proxy timing result.
- `golden-task.md` — golden harness status (live Grok pending key).
- `phase-0-harness.md` — batch writeup with open questions.
- **Golden-task harness** under `e2e/golden/` — 20 prompts × 2 site fixtures (`gutenberg-business`, `elementor-restaurant`), apply-then-assert scorer, offline runner, live Grok client, and 21 passing Vitest tests.
- **Builder detection** — `detectBuilder()` (elementor / beaver / divi / gutenberg / classic) from theme + plugin slugs + post meta + content.
- **Subset + media proxy** — `exportDbSubset()` (content tables only, secret redaction) and `mediaProxyTarget()` / `stageReplacement()` (origin proxy, copy-on-replace).
- **Mirror timing runner** — `run-mirror-timing.ts` with a real 2GB `dd` baseline; result: slice p50/p95 ≈ 0.003/0.010 ms, 0 upload bytes copied.
- **Documentation** — ADR set under `docs/decisions/` (00010009), this changelog, and `.env.example`.
### Changed
- **Repository identity** — renamed from `originmain` to `wursor`; description set to the product tagline; stale homepage cleared; topics `wordpress`, `ai`, `agent` added (see [ADR 9](docs/decisions/0009-repo-rename.md)).
- **PRD / IMPLEMENTATION** — expanded risk register (R1R14 with mitigations and sprint ownership), media-proxy and capability-tier decisions locked, pairing and warm-pool details corrected.
- **README / AGENTS.md** — repo layout and Phase 0 gate documented; status updated to reflect the workspace existing while product code remains gated.
- **`.gitignore`** — added coverage/report/build artifacts and the large-export fixture path.
### Removed
- Nothing removed from the current product; the prior `originmain` codebase was removed earlier in `702ce4e`.
### Security
- Pairing threat model and plugin install allowlist formalize the two highest-risk boundaries (live-site backdoor, arbitrary plugin install). See [ADR 3](docs/decisions/0003-pairing-code-direction.md) and [ADR 4](docs/decisions/0004-plugin-install-allowlist.md).
## [0.1.0] - 2026-08-15
First tracked Wursor artifact set.
### Added
- pstack-style engineering rigor skill family (`.agents/skills/`) and `AGENTS.md` routing (`0dae13c`).
- Wursor v2.0 PRD and IMPLEMENTATION guide — non-technical-first pivot (`dfb3c93`).
## [0.0.0] - 2026-08-13
### Added
- Initial Wursor PRD after removing the prior Originmain codebase (`702ce4e`).
+2
View File
@@ -11,6 +11,8 @@ Just describe what you want. Wursor does the rest.
- Full product spec: [PRD.md](./PRD.md) - Full product spec: [PRD.md](./PRD.md)
- Build guide (TDD, sprints, CI): [IMPLEMENTATION.md](./IMPLEMENTATION.md) - Build guide (TDD, sprints, CI): [IMPLEMENTATION.md](./IMPLEMENTATION.md)
- Phase 0 gate: [spikes/README.md](./spikes/README.md) - Phase 0 gate: [spikes/README.md](./spikes/README.md)
- Decisions: [docs/decisions/](./docs/decisions/)
- Changelog: [CHANGELOG.md](./CHANGELOG.md)
## What it does ## What it does
@@ -0,0 +1,31 @@
# 1. MVP is the content-change loop, not the full P0 feature list
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
PRD §7.1 lists plugin-install and site-build playbooks as P0 ("launch-blocking"), but IMPLEMENTATION.md schedules them into Phase 2 (Sprints 910). The two documents disagreed on what "P0" meant.
## Decision
The MVP is the Phase 1 exit criterion only: a new user signs up, pairs a WordPress site, types "change the homepage heading", sees the change in a sandbox preview, and approves — in under five minutes.
Plugin-install, site-build, design picker, and multi-step workflows are explicitly deferred to Phase 2.
### Options considered
- Ship the full PRD P0 (plugin install + site build in the first release).
- Thin vertical slice even smaller than Phase 1 (fixture-only, deploy stubbed).
- **Chosen:** Phase 1 exit only.
### Rejected
- Full P0 — a plugin-install playbook requires the catalog, reputation gate, and egress watch (ADR 4) that are not built; shipping them prematurely reintroduces R2.
- Thinner slice — deploy/rollback is the product's trust guarantee (R3); stubbing it hides the one thing that can break a live site.
## Consequences
- Clear, single exit test: `e2e/phase1-exit-criteria.test.ts`.
- PRD §7.1 will be annotated to move plugin/site-build out of P0 (doc-sync follow-up).
- Fewer parallel workstreams; content + deploy are the critical path.
@@ -0,0 +1,29 @@
# 2. Phase 0 spikes gate the scaffold
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
IMPLEMENTATION.md says "Do not start the web-app scaffold until the four boxes have a written result." The four spikes were golden-task harness, builder detection, pairing threat model, and large-site mirror timing — plus a plugin catalog.
## Decision
Treat the spikes as a hard gate. Product code (`web/` chat, playbooks, deploy) does not start until each spike has a written, verifiable result in the repo. Fixtures and throwaway scripts are allowed during the gate.
### Options considered
- Honor the gate.
- Spike in parallel with Sprint 1.
- Skip spikes and start the scaffold.
### Rejected
- Parallel — spike results change the architecture (e.g. mirror slice, pairing direction); building Sprint 1 against unverified assumptions means rework.
- Skip — the spikes exist precisely because an unvalidated assumption can kill the 5-minute exit or the live-site safety guarantee.
## Consequences
- Phase 0 exit is reviewable: five written spike notes, three with executable proofs.
- The scaffold exists only as empty packages (ADR 8), not product code.
- The remaining gate item is a live Grok score (see `spikes/golden-task.md`).
@@ -0,0 +1,27 @@
# 3. Wursor generates the pairing code, not the plugin
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
The IMPLEMENTATION.md Sprint 2 sketch had `Wursor_Auth::generate_pairing_code()` run on the plugin (TV-style pairing). PRD §7.1.4 says the user "copies a code from the Wursor web app" — the code must be bound to the signed-in account before the site is known. The plugin is a privileged backdoor (files, DB, WP-CLI) to the live site (R9).
## Decision
Wursor generates the pairing code bound to the account; the user pastes it into the plugin; the plugin redeems it with its `site_url`; Wursor issues scoped `read`/`deploy` tokens plus an HMAC secret. Full protocol in `spikes/pairing-threat-model.md`.
### Options considered
- Plugin-local generate/redeem (the sketch).
- Wursor-generated, pasted into the plugin.
### Rejected
- Plugin-local — cannot bind the code to an account before the site is known; a 6-char code with no TTL/lockout/HMAC is site ownership via a leaked URL.
## Consequences
- 8+ char code, 5-min TTL, 5-attempt lockout, one redeem, HMAC, hashed scoped tokens are the Sprint 2 test spec.
- The `generate_pairing_code` sketch in IMPLEMENTATION.md is wrong and will be corrected during Sprint 2.
- The threat model (T1T10) is the audit trail for this boundary.
@@ -0,0 +1,29 @@
# 4. Plugin install is a 40-slug allowlist, fail-closed
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
wordpress.org is not a reviewed-safe catalog. A malicious plugin looks fine in preview and only phones home after deploy. The agent will eventually install plugins, but must not be able to in the MVP either (R2).
## Decision
The agent may install only from a written ~40-slug allowlist (`spikes/plugin-catalog.md`), and only via `wp plugin install <slug>`. No URLs, no zips, no premium marketplaces. Unknown slug → refuse. A detect-only list (Wordfence, Jetpack, cache suites) is never installed.
### Options considered
- Open wordpress.org search.
- Written allowlist now; reputation/egress gate later (Sprint 9).
- No wall until the install playbook exists.
### Rejected
- Open search — reintroduces R2.
- No wall until later — the MVP agent could still `wp plugin install <url>` unless the tool schema forbids it from day one.
## Consequences
- Sprint 3 `tool-schemas.ts` must forbid `wp plugin install http` and unknown slugs even though no install playbook ships in MVP.
- Reputation gate, zip SHA, and egress watch on first activate are Sprint 9 scope, already specced.
- Catalog changes are PRs, not prompt edits.
@@ -0,0 +1,29 @@
# 5. Golden harness scores slot-fill tool calls against JSON fixtures
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
R7: can we score a model on WordPress tasks without vibes? A live WordPress sandbox was not available on the development machine (no Docker), and a live Grok key was not set. The harness still had to exist and produce a scoreable path.
## Decision
Twenty prompts across two JSON site fixtures (`gutenberg-business`, `elementor-restaurant`). Each prompt has a hard assertion (`preview_text`, `option`, or `screenshot`). A tool call is applied to the fixture and the assertion is checked. `run-golden.ts` scores the 20 expected traces offline and, when `XAI_API_KEY` is set, sends `gb-01` to `api.x.ai`.
### Options considered
- Docker WordPress fixtures + Playwright.
- JSON fixtures + slot-fill apply-then-assert.
- Defer the harness until Docker exists.
### Rejected
- Docker now — unavailable in this environment; blocks progress for a reason that does not change the assertion design.
- Defer — ships nothing; the assertion types and prompt file survive the swap to a real runner.
## Consequences
- WordPress work is modeled as slot-fill (`page`, `old`, `new`) + a deterministic write, which is how playbooks will actually execute.
- `applyTool` is the seam that gets replaced by a REST / `wp post update` runner when sandboxes exist.
- Unit tests stay offline (TDD rule: no network in unit tests). A live Grok score is the outstanding gate item.
+29
View File
@@ -0,0 +1,29 @@
# 6. Builder detection uses slugs + post meta with a priority order
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
WordPress pages live in different stores: Gutenberg in `post_content` block markup, Elementor in `_elementor_data` post meta, Classic as raw HTML. Editing the wrong store means the preview does not change and we do not know why (R6, R13).
## Decision
`detectBuilder()` returns `elementor | beaver | divi | gutenberg | classic` from theme + active plugin slugs + post meta + content. Priority: elementor (slug + `_elementor_edit_mode`/`_elementor_data`) → beaver (`_fl_builder_data`) → divi (theme + `_et_pb_use_builder`) → gutenberg (`<!-- wp:`) → classic.
### Options considered
- Theme-name-only.
- "Elementor is installed" (active plugin only).
- Slugs + the meta keys each builder actually writes.
### Rejected
- Theme-name-only — lies for child themes / Hello Elementor vs Divi.
- Active-plugin-only — inactive junk and leftover block markup produce false positives.
## Consequences
- Elementor wins over leftover Gutenberg markup, matching what a visitor actually renders.
- The TypeScript function is the spec; the PHP plugin reimplements the same rules, and `site-info` reports `builder`.
- Content and design playbooks branch on this field; editing `post_content` on an Elementor site is a failed test.
@@ -0,0 +1,26 @@
# 7. Sandboxes proxy uploads; never copy the media library
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
The MVP must preview a change in under five minutes. A WooCommerce store with years of posts and a 2GB media library cannot be copied in time — warm pool hides boot, not copy (R4). A full mirror also copies customer PII and secrets (R10).
## Decision
Task-scoped DB subset (content tables only, drop orders/comments/transients, redact `*_key`/`*_secret`/`smtp_pass`) plus an origin proxy for `/wp-content/uploads/*`. A file is copied into the sandbox only when the agent replaces it. Full-library copy is not a v1 path, even for 20GB+ sites.
### Options considered
- Full mirror + lazy media sync.
- Proxy uploads, subset content tables.
### Rejected
- Full mirror — the synthetic 2GB timing shows subset+proxy is ~0.01ms vs 2.4s+ to copy the blob locally (worse over a network); and it drags PII into a container with outbound internet.
## Consequences
- `subset.ts` and `media-proxy.ts` implement the decision and are unit-tested; `run-mirror-timing.ts` records the p50/p95 evidence.
- A real-host pull of the content slice is the unproven part; if it exceeds 60s, shrink the slice further — do not start copying uploads.
@@ -0,0 +1,26 @@
# 8. Workspace ships empty packages, not placeholder source
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
The repo had only documentation. A buildable monorepo was needed before code could land, but Phase 0 was not done. The `wursor-principles` skill says "subtract before you add" and "delete the placeholder rather than working around it."
## Decision
Create the pnpm workspace (`api`, `web`, `e2e`, plus `plugin` composer + `infrastructure` dirs) with real manifests and tsconfigs but **no application source** — only `.gitkeep` markers and the Phase 0 spike/runner code that has tests.
### Options considered
- Empty packages.
- Stub every file from the IMPLEMENTATION.md tree.
### Rejected
- Stubs — `// TODO` files are placeholders we would have to delete; they also look like progress without being testable.
## Consequences
- `pnpm install` / `pnpm test:e2e` are green and prove the workspace is real.
- Product code remains gated on Phase 0 (ADR 2).
+26
View File
@@ -0,0 +1,26 @@
# 9. Repository renamed originmain → wursor
- **Status:** Accepted
- **Date:** 2026-08-15
## Context
The GitHub repo was still named `originmain` — a prior product whose code was deleted in commit `702ce4e` — with a stale description and a homepage pointing at `originmain.vercel.app`. The repo now builds Wursor.
## Decision
Rename the repository to `wursor`, set the description to the product tagline, clear the stale homepage, and add topics `wordpress`, `ai`, `agent`.
### Options considered
- Rename in place.
- Create a new repo and migrate.
### Rejected
- New repo — loses history and the continuity of the pivot commits (`702ce4e``dfb3c93``0dae13c`).
## Consequences
- Remote is `https://github.com/SinachPat/wursor`; identity matches the product.
- Redirects from the old name are handled by GitHub automatically.
+31
View File
@@ -0,0 +1,31 @@
# Architecture Decision Records
Wursor records significant technical and product decisions as ADRs. This directory is the source of truth; PRD §14 holds the product-level "locked decisions", while these records capture the engineering choices and tradeoffs behind them.
Each ADR is a single file following the [Nygard format](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions): **Status**, **Context**, **Decision**, **Consequences**. "Options considered" and "Rejected" are kept explicit to honor the `wursor-decision-log` skill.
## Status legend
- **Accepted** — agreed; in force.
- **Proposed** — under discussion, not yet binding.
- **Superseded** — replaced by a later ADR; see the link.
## Index
| ADR | Title | Status |
|---|---|---|
| [0001](0001-mvp-scope-content-loop-first.md) | MVP is the content-change loop, not the full P0 feature list | Accepted |
| [0002](0002-phase-zero-spikes-gate.md) | Phase 0 spikes gate the scaffold | Accepted |
| [0003](0003-pairing-code-direction.md) | Wursor generates the pairing code, not the plugin | Accepted |
| [0004](0004-plugin-install-allowlist.md) | Plugin install is a 40-slug allowlist, fail-closed | Accepted |
| [0005](0005-golden-harness-fixtures.md) | Golden harness scores slot-fill tool calls against JSON fixtures | Accepted |
| [0006](0006-builder-detection.md) | Builder detection uses slugs + post meta with a priority order | Accepted |
| [0007](0007-media-proxy-not-copy.md) | Sandboxes proxy uploads; never copy the media library | Accepted |
| [0008](0008-empty-packages-not-stubs.md) | Workspace ships empty packages, not placeholder source | Accepted |
| [0009](0009-repo-rename.md) | Repository renamed originmain → wursor | Accepted |
## How to add one
1. Copy the previous number + 1.
2. Write Status / Context / Decision (with options + rejected) / Consequences.
3. Add a row to this index.