Skip to content
This repository was archived by the owner on Aug 13, 2026. It is now read-only.

Latest commit

 

History

History
134 lines (123 loc) · 8.39 KB

File metadata and controls

134 lines (123 loc) · 8.39 KB

Decisions & amendments (supersede mvp.md where they conflict)

Reviewed 2026-08-11 by three parallel plan subagents (architecture, MCP spec currency, M0 tooling feasibility — verified against live sources: npm registry, modelcontextprotocol.io 2026-07-28 spec pages, ext-tasks, ext-apps). These amendments take precedence over the corresponding lines of mvp.md.

Protocol layer (verified against the 2026-07-28 spec)

  1. SDK choice: use the v2 packages @modelcontextprotocol/server and @modelcontextprotocol/client (the 2026-07-28 line). Do not use the legacy @modelcontextprotocol/sdk. We still own Tasks + MCP Apps ourselves (they live in separate ext-tasks / ext-apps packages).
  2. Tasks extension is EXPERIMENTAL, not ratified core (ext-tasks, SEP-2663). Method names are correct (tasks/get, tasks/update with inputResponses, tasks/cancel; statuses working|input_required|completed|failed|cancelled). Build it as a design target with a hard fallback path (blocking call / long-poll with progress) since hosts may not support it.
  3. subscriptions/listen is ratified core; domChanged, navigated, consoleError, networkFailed, dialogOpened are NOT standard MCP notification types and are not in the ratified SubscriptionFilter. Deliver browser events as a custom extension (like Tasks does, behind our own extension declaration) or via ratified notifications/resources/updated on subscribed resources.
  4. MRTR: InputRequiredResult (resultType: "input_required", inputRequests map + integrity-protected opaque requestState) is ratified but only allowed on tools/call, prompts/get, resources/read. HITL gates use elicitation/create (form mode) inside inputRequests.
  5. MCP Apps: stable official extension io.modelcontextprotocol/ui, ui:// resource URIs, MIME text/html;profile=mcp-app, tool linkage via nested _meta.ui.resourceUri (the legacy flat key is deprecated).
  6. Tool Annotations: readOnlyHint / destructiveHint confirmed (plus idempotentHint, openWorldHint). Treat as untrusted hints.
  7. server/discover: MUST implement; response requires ttlMs/cacheScope on complete results (also tools/list, prompts/list, resources/list, resources/read). Deterministic tool order is a SHOULD.
  8. Deprecated set confirmed: Roots, Sampling, Logging, HTTP+SSE — avoid.

Architecture amendments (from review)

  1. The 100% mutation gate vs. black-box testing tension: black-box tests through ContextPage cannot kill every internal mutant. Resolution: unit tests through public interfaces (as the plan says) PLUS focused mutation tests for internal paths where the public API cannot reach them. The gate is one module at a time, so internal-path tests are written per module and committed with it — not after the fact.
  2. Tool framework is a missing milestone: defineTool/definePageTool/ ToolHandler/mutex/Response have no home. It is now M1-part-0 — before observe at the top of M1.
  3. Swap M3 and M4: implement event collection (console/network/DOM/ navigation) BEFORE action primitives, because act-then-wait depends on DOM-stability/navigation events. subscriptions/listen integration itself moves to M5 (needs the protocol transport).
  4. Uid stability across navigation: loaderId changes on every navigation, killing cross-turn uids. The diff engine gets fingerprint-based uid re-resolution/rebinding — added to M2.
  5. Action log / replay seed: the semantic action log begins in M3 (now after event collection) — unchanged, but its schema should be written so a navigation-triggered uid rebind doesn't orphan it.
  6. observe atomicity: a11y tree, screenshot, overlay come from separate CDP calls; page mutation between them makes boxes disagree with the image. M1 keeps the screenshot but documents the frame-sync decision (single-dom-snapshot + separate screenshot is acceptable for M1; revisit in M8).
  7. Diff engine is single-consumer state: concurrent read-only observes must not race the diff; M2 defines the consumption semantics (owner-token, last-observe-wins) explicitly.

M0 tooling decisions (verified against npm registry 2026-08-11)

  1. TypeScript: pin ^6.0.3 — NOT 7.x. TS 7 is the Go port; typescript-eslint 8.67.0, knip 6.32.2, vitest 4.1.10 are NOT TS-7-ready. Re-evaluate when ts-eslint supports >=6.1.0.
  2. Versions: eslint ^10.8.1 (flat-only, .eslintrc removed), @typescript-eslint/* ^8.67.0, prettier ^3.9.6, vitest + @vitest/coverage-v8 ^4.1.10, @stryker-mutator/core + vitest-runner ^9.6.1, knip ^6.32.2.
  3. TypeScript-ESLint ban matrix:
    • @typescript-eslint/consistent-type-assertions with { assertionStyle: "never" } (bans as; as const is exempt by design).
    • @typescript-eslint/no-non-null-assertion: "error" (bans !).
    • @typescript-eslint/no-explicit-any: "error" (bans any).
    • @typescript-eslint/ban-ts-comment: "error" with { 'ts-ignore': true, 'ts-nocheck': true, 'ts-expect-error': true, 'ts-check': true }.
    • No canonical rule bans .forEach; a tiny custom ESLint rule in eslint.config.mjs walks CallExpression.callee.property === 'forEach'.
  4. Coverage gate: @vitest/coverage-v8, thresholds 100 across lines/functions/branches/statements on src/, fail below threshold.
  5. Mutation gate: stryker thresholds: { high: 100, low: 100, break: 100 } (exit 1 below break). Vitest runner, mutate: ['src/**/*.ts', '!src/cli.ts'] (src/cli.ts is the process entry; it only wires live Puppeteer and is covered by tests/cli.test.ts, not by mutant-killing unit tests). Speed follows https://stryker-mutator.io/blog/stryker4s-40-minutes-to-40-seconds/ : in-process Vitest runner, coverageAnalysis: 'perTest', no concurrency cap (all cores), ignoreStatic: true (static mutants force a full reload plus every test), and incremental: true writing reports/stryker-incremental.json. CI restores/saves that file via actions/cache@v4 keyed by OS + SHA with an OS-wide restore-key. Windows: forward-slash globs only, gitignore .stryker-tmp, set timeoutMS: 30000, and CI kills orphan Chrome between runs (taskkill /F /IM chrome.exe) — a stuck mutant spawns headless Chrome.
  6. JUnit reporter is built into vitest 4 (reporters: ['junit'], outputFile: { junit: 'reports/junit.xml' }) — no extra package.
  7. knip on Windows: if the oxc raw-transfer hangs, set KNIP_DISABLE_RAW_TRANSFER=1; quote globs in pwsh.
  8. Survivor registry: mvp.md allows "documented justified survivors" but allows no mechanism to enforce them in CI. M0 introduces scripts/survivors.mjs + mutation-survivors.json — every permitted survivor is a named entry; CI fails on any survivor not in the registry. This is the ONLY escape from the mutation gate (per mvp.md lines 92–96) — the registry replaces the empty "document it" clause with a checked gate.

TypeScript-only enforcement

  1. No .js / .mjs / .cjs anywhere — configs, scripts, and build tooling included. eslint.config.ts, stryker.config.ts, vitest.config.ts, and tsconfig*.json are the only config formats. Scripts under scripts/ are TypeScript emitted to dist-scripts/ via tsconfig.scripts.json (build:scripts) for the rare cases that need plain-Node execution.
  2. Enforced by (a) an AGENTS.md rule, (b) a CI step that fails on any tracked .js/.mjs/.cjs, and (c) knip/lint/format on the repo. The auto-generated dist-scripts/ output is gitignored and excluded from lint/coverage/mutation.

Operating guardrails for the loop

  1. Master agent (Grok) orchestrates; subagents implement well-scoped modules per milestone in parallel where the milestone permits.
  2. Every module is RED first (failing test), then GREEN, then refactor, then coverage + mutation on that module, then commit. Orchestrator re-runs the full gate chain before each milestone commit.
  3. Subagents may NOT merge anything not red→green, mutation-clean, and tooling-clean (typecheck/knip/lint/format). Orchestrator enforces on merge.
  4. No core product code before M0 is proven green (hard gate from mvp.md). "Core product code" = anything under src/ beyond the M0 smoke module.