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.
- SDK choice: use the v2 packages
@modelcontextprotocol/serverand@modelcontextprotocol/client(the 2026-07-28 line). Do not use the legacy@modelcontextprotocol/sdk. We still own Tasks + MCP Apps ourselves (they live in separateext-tasks/ext-appspackages). - Tasks extension is EXPERIMENTAL, not ratified core (ext-tasks, SEP-2663).
Method names are correct (
tasks/get,tasks/updatewithinputResponses,tasks/cancel; statusesworking|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. subscriptions/listenis ratified core;domChanged,navigated,consoleError,networkFailed,dialogOpenedare NOT standard MCP notification types and are not in the ratifiedSubscriptionFilter. Deliver browser events as a custom extension (like Tasks does, behind our own extension declaration) or via ratifiednotifications/resources/updatedon subscribed resources.- MRTR:
InputRequiredResult(resultType: "input_required",inputRequestsmap + integrity-protected opaquerequestState) is ratified but only allowed ontools/call,prompts/get,resources/read. HITL gates useelicitation/create(form mode) insideinputRequests. - MCP Apps: stable official extension
io.modelcontextprotocol/ui,ui://resource URIs, MIMEtext/html;profile=mcp-app, tool linkage via nested_meta.ui.resourceUri(the legacy flat key is deprecated). - Tool Annotations:
readOnlyHint/destructiveHintconfirmed (plusidempotentHint,openWorldHint). Treat as untrusted hints. server/discover: MUST implement; response requiresttlMs/cacheScopeon complete results (alsotools/list,prompts/list,resources/list,resources/read). Deterministic tool order is a SHOULD.- Deprecated set confirmed: Roots, Sampling, Logging, HTTP+SSE — avoid.
- The 100% mutation gate vs. black-box testing tension: black-box tests
through
ContextPagecannot 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. - Tool framework is a missing milestone:
defineTool/definePageTool/ToolHandler/mutex/Responsehave no home. It is now M1-part-0 — beforeobserveat the top of M1. - Swap M3 and M4: implement event collection (console/network/DOM/
navigation) BEFORE action primitives, because
act-then-waitdepends on DOM-stability/navigation events.subscriptions/listenintegration itself moves to M5 (needs the protocol transport). - Uid stability across navigation:
loaderIdchanges on every navigation, killing cross-turn uids. The diff engine gets fingerprint-based uid re-resolution/rebinding — added to M2. - 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.
- 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).
- 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.
- 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. - Versions: eslint
^10.8.1(flat-only,.eslintrcremoved), @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. - TypeScript-ESLint ban matrix:
@typescript-eslint/consistent-type-assertionswith{ assertionStyle: "never" }(bansas;as constis exempt by design).@typescript-eslint/no-non-null-assertion: "error"(bans!).@typescript-eslint/no-explicit-any: "error"(bansany).@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 ineslint.config.mjswalksCallExpression.callee.property === 'forEach'.
- Coverage gate:
@vitest/coverage-v8, thresholds 100 across lines/functions/branches/statements onsrc/, fail below threshold. - Mutation gate: stryker
thresholds: { high: 100, low: 100, break: 100 }(exit 1 belowbreak). Vitest runner,mutate: ['src/**/*.ts', '!src/cli.ts'](src/cli.tsis the process entry; it only wires live Puppeteer and is covered bytests/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), andincremental: truewritingreports/stryker-incremental.json. CI restores/saves that file viaactions/cache@v4keyed by OS + SHA with an OS-wide restore-key. Windows: forward-slash globs only, gitignore.stryker-tmp, settimeoutMS: 30000, and CI kills orphan Chrome between runs (taskkill /F /IM chrome.exe) — a stuck mutant spawns headless Chrome. - JUnit reporter is built into vitest 4 (
reporters: ['junit'],outputFile: { junit: 'reports/junit.xml' }) — no extra package. - knip on Windows: if the oxc raw-transfer hangs, set
KNIP_DISABLE_RAW_TRANSFER=1; quote globs in pwsh. - Survivor registry:
mvp.mdallows "documented justified survivors" but allows no mechanism to enforce them in CI. M0 introducesscripts/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.
- No
.js/.mjs/.cjsanywhere — configs, scripts, and build tooling included.eslint.config.ts,stryker.config.ts,vitest.config.ts, andtsconfig*.jsonare the only config formats. Scripts underscripts/are TypeScript emitted todist-scripts/viatsconfig.scripts.json(build:scripts) for the rare cases that need plain-Node execution. - 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-generateddist-scripts/output is gitignored and excluded from lint/coverage/mutation.
- Master agent (Grok) orchestrates; subagents implement well-scoped modules per milestone in parallel where the milestone permits.
- 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.
- Subagents may NOT merge anything not red→green, mutation-clean, and tooling-clean (typecheck/knip/lint/format). Orchestrator enforces on merge.
- 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.