diff --git a/README.md b/README.md index 22068ca..3614d11 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,8 @@ No single competitor combines build-time stable IDs, deep runtime capture, bidir ## MCP Tools +The agent-facing surface — tools, prompts, wire schemas, error envelope, and a stability policy — is specified by the **Runtime Context Protocol (RCP)**. RCP is versioned independently of this implementation. The current version is **[RCP v1.0.0](./docs/rcp/v1.md)**, published from `@domscribe/protocol@1.0.0`. IDE and agent vendors integrating against Domscribe should treat the spec as the contract; the table below is a human-readable index. + | Tool | Description | | ----------------------------------- | --------------------------------------------------------------------------------------- | | `domscribe.query.bySource` | Query a source file + line and get live runtime context (props, state, DOM snapshot) | @@ -412,7 +414,7 @@ No single competitor combines build-time stable IDs, deep runtime capture, bidir | `domscribe.annotation.search` | Full-text search across annotation content | | `domscribe.status` | Relay daemon health, manifest stats, queue counts | -See the [`@domscribe/mcp` README](./packages/domscribe-mcp/README.md) for detailed tool schemas, response formats, and prompt definitions. +See the [`@domscribe/mcp` README](./packages/domscribe-mcp/README.md) for detailed tool schemas, response formats, and prompt definitions, and **[RCP v1.0.0](./docs/rcp/v1.md)** for the protocol spec and stability policy. --- diff --git a/TECHNICAL_SPEC.md b/TECHNICAL_SPEC.md index 864f079..b2d525d 100644 --- a/TECHNICAL_SPEC.md +++ b/TECHNICAL_SPEC.md @@ -372,6 +372,8 @@ Pluggable transport layer for runtime ↔ overlay communication: #### MCP Tools (for coding agents) +> The agent-facing surface — tool/prompt names, wire schemas, error envelope, and stability policy — is specified by [RCP v1.0.0](./docs/rcp/v1.md), versioned independently as `@domscribe/protocol@1.0.0`. The table below summarises what this implementation exposes; the spec is the contract. + | Tool | Description | | -------------------------- | ------------------------------------------------------------------------------ | | `annotation-get` | Retrieve annotation by ID | diff --git a/docs/rcp/v1.md b/docs/rcp/v1.md new file mode 100644 index 0000000..c89b1e3 --- /dev/null +++ b/docs/rcp/v1.md @@ -0,0 +1,284 @@ +# Runtime Context Protocol — RCP v1.0.0 + +> **RCP v1.0.0 — published from `@domscribe/protocol@1.0.0`** +> +> Status: **stable**. Specifies the agent-facing contract that an IDE, coding agent, or other tool consumes to read Domscribe's manifest and annotation state at runtime. + +RCP is the protocol that Domscribe ships against. The implementation in this repository is one conformant implementation; the protocol is the unit IDE vendors, agents, and downstream tools integrate against. The shapes, names, and stability policy in this document are versioned by `@domscribe/protocol`. Any RCP consumer can depend on `@domscribe/protocol` directly to get the typed wire shapes — no transitive dependency on Domscribe's relay or runtime is required. + +## Status & scope of v1 + +RCP v1 covers, and only covers: + +- A single transport binding: **Model Context Protocol (MCP) over stdio**, as defined by the [Anthropic MCP specification](https://modelcontextprotocol.io). +- A fixed set of MCP **tools** the consumer can call (§3). +- A fixed set of MCP **prompts** the consumer can fetch (§4). +- The wire **schemas** for the request/response payloads of those tools — `ManifestEntry`, `RuntimeContext`, `Annotation`, and the error envelope (§5). +- The **error code** vocabulary returned in failure responses (§6). +- The **stability policy** that tells you what is allowed to change without a major version bump (§7). + +Out of scope for v1, but not precluded for later versions: + +- Transport bindings other than MCP/stdio (gRPC, JSON-RPC over HTTP, WebSocket). +- Server-to-client push of annotation events (today the consumer polls via tools). +- Multi-tenant relay semantics. A v1 relay serves one workspace. +- A public adapter SDK for framework authors. See [RFC 0001](../rfcs/0001-rcp-as-versioning-unit.md), §"out of scope". + +## 1. Conformance + +A conformant RCP v1 **server** MUST: + +- Advertise the `tools` capability on initialize. +- If it claims to support prompts, advertise the `prompts` capability. +- Implement every tool in §3 with input and output that validate against the schemas in `@domscribe/protocol`. +- Implement every prompt in §4 if `prompts` is advertised. +- Return error responses that validate against `ProblemDetails` (§5.4) with a `code` drawn from §6. + +A conformant RCP v1 **client** MUST: + +- Treat unknown optional fields on response payloads as valid (additive forward compatibility, §7.2). +- Recognise the error codes in §6 and not depend on the human-readable `title` or `detail` fields for control flow. + +## 2. Transport + +RCP v1 runs over MCP/stdio. A consumer launches a server process and exchanges JSON-RPC 2.0 messages over its stdin/stdout, per the MCP specification. The reference server in this repository is `domscribe-mcp`. + +Server identity, returned on `initialize`: + +``` +{ "name": "domscribe", "version": "" } +``` + +The server `version` is the implementation's package version, not the RCP version. Consumers wanting to know the protocol version a server speaks SHOULD use the presence of the tools and prompts listed below, combined with the server's announced capability set. + +## 3. Tools + +All tool names match the regex `^[a-zA-Z0-9_-]{1,64}$` and use the canonical underscore-separated form. Aliases for legacy dotted names (e.g. `domscribe.resolve`) MAY be exposed by an implementation through the v1.x minor cycle for migration; they are not part of v1.0.0 and will not be accepted by stricter MCP clients (Windsurf and friends). See §7.4. + +| Tool | Purpose | +| ------------------------------------ | ------------------------------------------------------------------------------------------ | +| `domscribe_resolve` | Resolve a `data-ds` element ID to its source location (file, line, column, component). | +| `domscribe_resolve_batch` | Resolve multiple element IDs in one call. | +| `domscribe_manifest_query` | Find manifest entries by file path, component name, or element ID. | +| `domscribe_manifest_stats` | Manifest coverage statistics (entry count, file count, component count, cache hit rate). | +| `domscribe_query_by_source` | Given a source file + line, return the matching `ManifestEntry` and live `RuntimeContext`. | +| `domscribe_annotation_list` | List annotations, filtered by status. | +| `domscribe_annotation_get` | Retrieve a single annotation by ID. | +| `domscribe_annotation_search` | Full-text search across annotation content. | +| `domscribe_annotation_process` | Atomically claim the next queued annotation (prevents concurrent agent conflicts). | +| `domscribe_annotation_update_status` | Manually transition annotation status. | +| `domscribe_annotation_respond` | Attach an agent response and transition the annotation to `processed`. | +| `domscribe_status` | Relay daemon health, manifest stats, queue counts. | + +When no workspace is detected, an implementation MAY respond to `domscribe_status` with a reduced diagnostic shape (`{ active: false, cwd, guidance, nextSteps }`) instead of the active-mode shape. Consumers SHOULD branch on the `active` field. The diagnostic-mode shape is informational and not part of the v1 normative response surface. + +Input and output shapes for each tool are exported as Zod schemas from `@domscribe/protocol`. Consumers should treat the schemas exported from that package as the source of truth — this document is a human-readable companion, not an alternative source. The schema names follow the pattern `InputSchema` and `OutputSchema`. + +Every tool output extends a common base: + +```ts +{ error?: string } // populated only when the call failed and a structured envelope is also returned +``` + +Failure responses are described in §5.4. + +## 4. Prompts + +| Prompt | Purpose | +| ------------------- | ------------------------------------ | +| `process_next` | Process the next queued annotation. | +| `check_status` | Get a system status overview. | +| `explore_component` | Deep-dive into a specific component. | +| `find_annotations` | Discover pending annotations. | + +Prompts return one or more messages with `role: "user" | "assistant"` and `content: { type: "text", text: string }`. Prompt argument schemas are exported as Zod schemas from `@domscribe/protocol`. + +## 5. Wire schemas + +The full Zod definitions live in `@domscribe/protocol`. The summaries below describe the v1.0.0 fields a consumer can rely on. Every field marked **stable** is covered by the §7 stability policy; every field marked **experimental** may change in a minor release per §7.3. + +### 5.1 ManifestEntry + +A `ManifestEntry` is one row in Domscribe's DOM→source index. It is returned by `domscribe_resolve*`, `domscribe_manifest_query`, and as part of `domscribe_query_by_source` and `Annotation.context.manifestSnapshot`. + +| Field | Type | Stability | Notes | +| ----------------------- | -------------------------- | ------------ | ----------------------------------------------------------------------------------- | +| `id` | `string` (8-char nanoid) | stable | Matches `/^[0-9A-HJ-NP-Za-hj-np-z]{8}$/` (Crockford base32: no `I`, `L`, `O`, `0`). | +| `file` | `string` | stable | Project-root-relative path. | +| `start` | `SourcePosition` | stable | `{ line, column, offset? }`. Line is 1-indexed, column 0-indexed. | +| `end` | `SourcePosition?` | stable | Optional end position. | +| `elementId` | `string?` | stable | The literal `id` attribute on the element, if any. | +| `tagName` | `string?` | stable | HTML/component tag name. | +| `componentName` | `string?` | stable | Owning component, if known. | +| `parent` | `string?` (entry id) | stable | Parent `ManifestEntry.id`. | +| `children` | `string[]?` (entry ids) | stable | Child entry IDs. | +| `wrappers` | `string[]?` | stable | Wrapping component names, outer-first. | +| `styles` | `StyleInfo?` | stable | `{ file?, classNames?, modules?, inline? }`. | +| `dataBindings` | `Record?` | experimental | Shape determined by `FrameworkAdapter`. | +| `componentMetadata` | `Record?` | experimental | Framework-specific extension bag. | +| `isApproximateLocation` | `boolean?` | stable | True if the source mapping is best-effort (e.g., SSR fallback). | +| `fileHash` | `string?` (16 hex) | experimental | xxhash64 of the source file at transform time. | + +### 5.2 RuntimeContext + +A `RuntimeContext` is the live runtime snapshot for a component, returned by `domscribe_query_by_source` and embedded in `Annotation.context.runtimeContext`. + +| Field | Type | Stability | Notes | +| ---------------- | ---------- | ------------ | ----------------------------------------------------------------------------- | +| `componentProps` | `unknown?` | experimental | Serialized props snapshot. Shape determined by the active `FrameworkAdapter`. | +| `componentState` | `unknown?` | experimental | Serialized state snapshot. | +| `eventFlow` | `unknown?` | experimental | Breadcrumb trail of recent events leading to the snapshot. | +| `performance` | `unknown?` | experimental | Performance metrics (frame timings, render counts). | + +The four fields themselves are stable in v1 — consumers can rely on them existing and being optional — but the _contents_ of each are explicitly experimental in v1, because the runtime capture format is still hardening across frameworks. Per §7.3, a v1.x minor release may add structure to these without bumping major. + +### 5.3 Annotation + +An `Annotation` is one captured user interaction with its full context. + +``` +Annotation { + metadata: { id, timestamp, mode, status, schemaVersion, errorDetails? } + interaction: { type, selectedText?, selectedElement?, boundingRect? } + context: { pageUrl, pageTitle, viewport, userAgent, domSnapshot?, manifestSnapshot?, userMessage?, environment?, runtimeContext? } + agentResponse?: { message? } +} +``` + +Enums returned by v1 servers: + +- `metadata.mode`: `"element-click" | "text-selection"` +- `metadata.status`: `"queued" | "processing" | "processed" | "failed" | "archived"` +- `interaction.type`: `"element-annotation" | "text-selection"` + +`metadata.schemaVersion` is an integer that identifies the `Annotation` shape (currently `1`). A consumer that does not recognise the value MUST refuse to parse and surface a `DS_VALIDATION_FAILED`-shaped error to its caller. + +A reduced `AnnotationSummary` is returned by listing endpoints: + +``` +AnnotationSummary { + id, status, timestamp, entryId?, componentName?, userMessageExcerpt? +} +``` + +### 5.4 Error envelope — `ProblemDetails` + +Failed tool calls return an `isError: true` MCP result whose textual content is a JSON-serialized `ProblemDetails` object, modelled after [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807): + +| Field | Type | Stability | Notes | +| ------------ | ----------------------------- | ------------ | ----------------------------------------------------------------------------- | +| `code` | `DomscribeErrorCode` (see §6) | stable | Use this for control flow. | +| `title` | `string` | stable | Short, human-readable summary. Not for control flow. | +| `detail` | `string?` | stable | Longer explanation specific to this occurrence. | +| `instance` | `string?` | stable | URI identifying the specific occurrence. | +| `status` | `number?` | stable | HTTP-style status code, when meaningful. | +| `extensions` | `Record?` | experimental | Bag for code-specific data. Consumers MUST tolerate absence and unknown keys. | + +## 6. Error codes + +The complete v1 vocabulary. New codes MAY be added in a minor release per §7.3; existing codes will not be removed or repurposed within v1. + +| Code | Meaning | +| -------------------------- | ---------------------------------------------------------------- | +| `DS_VALIDATION_FAILED` | Input failed schema validation. | +| `DS_CONFLICT` | Request conflicts with current resource state. | +| `DS_INVALID_INPUT` | Input was syntactically well-formed but semantically rejected. | +| `DS_INTERNAL_ERROR` | An unexpected server-side failure occurred. | +| `DS_NOT_IMPLEMENTED` | The server does not implement the requested capability. | +| `DS_MANIFEST_INVALID` | The manifest could not be parsed. | +| `DS_MANIFEST_NOTFOUND` | No manifest is loaded. | +| `DS_MANIFEST_CORRUPTED` | The manifest loaded but failed an integrity check. | +| `DS_ELEMENT_NOT_FOUND` | Requested element ID does not resolve to a manifest entry. | +| `DS_ANNOTATION_INVALID` | Annotation payload failed schema validation. | +| `DS_ANNOTATION_NOTFOUND` | No annotation with that ID exists. | +| `DS_ANNOTATION_PROCESSING` | Annotation is already being processed by another consumer. | +| `DS_RESOLVE_STALE_TARGET` | Target was resolved but the underlying source has since changed. | +| `DS_DIFF_INVALID` | A supplied diff/patch could not be applied. | +| `DS_WRITE_GUARD_BLOCKED` | A write guard refused the requested mutation. | +| `DS_AGENT_UNAVAILABLE` | The agent end of the protocol could not be reached. | +| `DS_TRANSFORM_FAILED` | A build-time transform failed. | +| `DS_TRANSFORM_UNSUPPORTED` | Requested transform is not supported in this configuration. | +| `DS_RELAY_UNAVAILABLE` | The relay daemon is not reachable. | +| `DS_RELAY_TIMEOUT` | The relay daemon did not respond in time. | +| `DS_MCP_INVALID_REQUEST` | The MCP request was malformed. | +| `DS_MCP_METHOD_NOT_FOUND` | The MCP method is unknown to this server. | + +## 7. Stability policy + +This section is normative. A reader can use the rules here to predict, without consulting the implementation team, what changes count as breaking and which do not. + +### 7.1 What "v1" promises + +Within RCP v1, the following are **frozen**: + +- Every **tool name** in §3. +- Every **prompt name** in §4. +- Every field marked **stable** in §5, including its type, optionality, and semantics. +- Every **error code** in §6 — both that it exists and what it means. +- The error envelope shape itself (`code`, `title`, `detail`, `instance`, `status`, `extensions`). +- The transport binding being MCP over stdio. + +Calling a frozen tool name with v1-shaped input on a v1.x server MUST not return a v2-shaped output or a code outside §6. + +### 7.2 What additive (minor) releases may do — `1.x.0` + +A v1.x minor release MAY: + +- Add a **new tool** or **new prompt**, provided existing tools keep their v1 semantics. +- Add an **optional field** to a response shape. Clients that don't know the field MUST ignore it. +- Add a **new value** to an enum that is documented as extensible. The v1 enums in §5.3 (`mode`, `status`, `type`) are **not extensible** in v1 — adding a value to them is breaking; this is the one place v1 deliberately accepts brittleness in exchange for clarity. +- Add a **new error code** to §6. Clients that don't recognise it MUST fall back to a generic-failure path. +- Tighten the **shape of an experimental field** in §5 — for instance, replace `RuntimeContext.componentProps: unknown` with a structured schema. This is not breaking because consumers were never permitted to depend on the experimental shape. +- Expose an **alias** for an existing tool name. Aliases never replace the canonical name and may be removed in a major release. +- Change implementation-internal details that do not appear in this document. + +A v1.x minor release MUST NOT: + +- Rename or remove a tool, prompt, error code, or stable field. +- Make an optional field required, or vice versa, on a stable field. +- Change the meaning of an error code. +- Change the type of a stable field. + +### 7.3 What patches (`1.x.y`) may do + +Patch releases may fix bugs, tighten validation that was previously too loose, improve performance, and update human-readable strings (`title`, `detail`, prompt copy). A patch MUST NOT change any rule from §7.1 or §7.2. + +### 7.4 Aliases and the canonical name migration + +The v0.5.x implementation exposed tool names in dotted form (`domscribe.resolve`, `domscribe.annotation.get`, …). v1.0.0 canonicalises on the underscore form documented in §3. Implementations MAY ship dotted aliases through one minor cycle (v1.0.x → v1.1.0) to keep existing `.mcp.json` configs working; after v1.1.0, the dotted aliases are no longer guaranteed and SHOULD be considered deprecated. Aliases never appear in this spec — the canonical name is the contract. + +### 7.5 What v2 will look like + +A breaking change — renaming a stable field, removing an error code, changing the transport binding, or making an experimental field's shape break the previous shape — requires a v2 release. v2 will: + +- Be a new package version of `@domscribe/protocol` (`2.0.0`). +- Be published alongside a `docs/rcp/v2.md` spec. +- Maintain a **dual-version deprecation window** of at least one minor release on v1, during which a single server implementation can speak both versions. The window may be longer if adoption signals warrant; it will not be shorter. +- Publish a written migration guide before the deprecation window opens. + +### 7.6 Falsifiability examples + +A reader should be able to look at a candidate change and decide if it's breaking. Worked examples: + +| Change | Verdict | +| ----------------------------------------------------------------------- | ---------------------------------------------------- | +| Add `domscribe_manifest_entries` tool returning a stream of entries. | **Minor** — additive new tool. | +| Add `wrappers: string[]` to `ManifestEntry` (already exists, so n/a). | n/a — already stable. | +| Add `fileSize: number` to `ManifestEntry`. | **Minor** — additive optional field. | +| Promote `dataBindings` from experimental to a structured stable schema. | **Minor** — tightening an experimental field, §7.2. | +| Rename `domscribe_status` to `domscribe_health`. | **Major** — rename of a frozen tool name. | +| Add a new annotation status `"snoozed"` to the `status` enum. | **Major** — v1 enums are non-extensible (§7.2). | +| Change `DS_RESOLVE_STALE_TARGET` to mean "manifest stale". | **Major** — semantic change to a code. | +| Drop the `instance` field from `ProblemDetails`. | **Major** — removal of a stable field. | +| Add a `DS_RATE_LIMITED` code. | **Minor** — additive code. | +| Switch transport to MCP-over-WebSocket only. | **Major** — removal of the stable transport binding. | + +If a contributor is unsure, they should treat the change as breaking and route it through the v2 path until a maintainer says otherwise. + +## 8. Reference + +- Source of truth for shapes: package `@domscribe/protocol@1.0.0` on npm. +- Reference server implementation: `@domscribe/relay` (this repository). +- Architecture and decision record: [`docs/rfcs/0001-rcp-as-versioning-unit.md`](../rfcs/0001-rcp-as-versioning-unit.md). +- MCP specification: . +- Error envelope conventions: [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807). diff --git a/docs/rfcs/0001-rcp-as-versioning-unit.md b/docs/rfcs/0001-rcp-as-versioning-unit.md new file mode 100644 index 0000000..89c7dc5 --- /dev/null +++ b/docs/rfcs/0001-rcp-as-versioning-unit.md @@ -0,0 +1,51 @@ +# RFC 0001: Extract the agent-facing contract into `@domscribe/protocol` as the versioning unit for RCP v1 + +**Status:** Proposed +**Author:** Principal Eng (sprint 2371) +**Date:** 2026-05-28 + +## Context + +The DOP memo (sprint 2371) commits Domscribe to the "neutral bridge" category by publishing a **Runtime Context Protocol (RCP)** that IDE vendors integrate against, rather than competing as another AI IDE. That commits _engineering_ to a question the memo does not answer: **what, concretely, is RCP — and how do we ship it so that IDE-vendor relations have something to point at?** + +Today the agent-facing surface is scattered. The MCP server in `@domscribe/relay` exposes 12 tools and 4 prompts. The wire schemas for `ManifestEntry`, `RuntimeContext`, and `Annotation` live in `@domscribe/core` alongside unrelated utilities (error system, PII redaction, ID generation). Tool naming is already inconsistent — `README.md` advertises dotted names like `domscribe.query.bySource` while `TECHNICAL_SPEC.md` §3.4 still lists dashed names like `annotation-get`. There is no stability policy and no SemVer commitment on either. We need this decision _now_ because every week we delay, more downstream code (skill files, plugin marketplaces, the `npx domscribe init` wizard) hardens around names we will have to break. + +## Decision + +We extract the agent-facing contract — MCP tool & prompt surface, plus the wire schemas (`ManifestEntry`, `RuntimeContext`, `Annotation`, error envelopes) — into a new package `@domscribe/protocol`, version it independently from the implementation packages, and publish RCP v1.0.0 against it with an explicit stability policy. `@domscribe/core` keeps utilities and shrinks; relay/runtime/mcp depend on `@domscribe/protocol` for shapes and names. + +## Alternatives considered + +**Alt A — Treat the existing `@domscribe/core` schemas + the MCP server as "the protocol" and add a `docs/rcp/v1.md` page on top.** This is the cheapest path. The tradeoff: protocol version becomes coupled to `@domscribe/core`'s version, so any internal refactor of error utilities or ID helpers triggers a protocol bump — meaning the SemVer stamp is meaningless, which means IDE vendors have no real signal of stability and will reasonably treat us as "a tool with docs," not a protocol. The stability stamp is exactly the asset we are trying to create. + +**Alt B — Define RCP as a transport-neutral abstract spec (`docs/rcp/v1.md`) with bindings: MCP binding, gRPC binding, JSON-RPC binding.** This is what real standards bodies do. The tradeoff: we are one team shipping the only implementation; "transport-neutral with bindings" is a 6-month yak-shave that produces a beautiful spec and zero adoption. The MCP binding is the only one that matters in 2026 because every target IDE (Cursor, Claude Code, Cline, Continue, Codex) already speaks it. Premature abstraction. + +## The strongest counter-argument + +"Don't publish a v1 protocol off a v0.5 product. Until a non-author implements RCP, you're documenting your own API and calling it a protocol — and you'll be stuck with v1's mistakes." This is the strongest "no," and it's right that the standards literature is full of v1s frozen too early. The reason it still loses: the entire bet of the sprint is that IDE-vendor relations won't engage with "our API surface" but will engage with "the protocol." Publishing v1 is the precipitating action that _generates_ the integration energy DOP's falsifier measures. Waiting for an external implementation before publishing means waiting forever — nobody will implement an unstamped contract. The cost of v1-mistake lock-in is bounded (we ship v1.1 with additive fields; we ship v2 with a deprecation window), and is structurally smaller than the cost of six more months of competing with Stagewise on positioning instead of on category. + +## Blast radius — high + +Shares fate with this change: every shipped IDE plugin (Claude Code, Copilot, Cursor, Gemini, Kiro), the `npx domscribe init` wizard's MCP config, the `skills/` content, the `gemini-extension.json`, the README's tool table, every fixture in `domscribe-test-fixtures`, and every downstream user's `.mcp.json`. Tool-name normalization in particular will break existing plugin configs unless we ship aliases through the migration window. The package extraction also re-shapes the dependency graph documented in `TECHNICAL_SPEC.md` §2.3 — `scope:core` no longer owns the wire shapes. + +## Reversibility + +- **Within 2 weeks of publish:** Roll `@domscribe/protocol` back into `@domscribe/core`, un-stamp v1, retract the spec page. Tool renames already shipped require aliases on the old names indefinitely. Cost: a sprint. +- **At 3 months:** The protocol stamp is itself the wedge — pulling it back is a public admission that RCP was marketing, not engineering. Cost: category-positioning credibility, which is most of what the sprint was buying. Not truly reversible. + +## Falsifier + +By **2026-08-20** (sprint N+6), `@domscribe/protocol@1.0.0` is published with a stability-policy doc, **and** at least one of: (a) an IDE-vendor doc links to the RCP spec, (b) ≥1 npm package not authored by Patch Orbit declares `@domscribe/protocol` as a dependency, (c) ≥10 weekly-active relay sessions reported via opt-in telemetry. **Zero of three → we collapse `@domscribe/protocol` back into `@domscribe/core` in the following sprint and recategorize Domscribe as a tool with stable APIs.** + +## Implications for PM + +Four sprint workstreams fall out, in this order: + +1. **Extract `@domscribe/protocol` package** (Zod schemas relocate from `@domscribe/core`; `relay`/`runtime`/`mcp` depend on it). Blocks everything else. +2. **Normalize MCP tool names against a single grammar** before stamping v1. Per the spec note in this team's memory, MCP SEP-986 permits dots up to 128 chars, but Windsurf enforces stricter rules — staff engineers decide between dotted and underscore form, but must canonicalize one and ship aliases for the legacy names through one minor cycle. +3. **Opt-in telemetry hook in the relay daemon** — single periodic POST of an anonymous session count, gated on a flag in `.domscribe/config.json`, defaulting off. This is the only way to measure DOP-falsifier (c); without it, the bet is unmeasurable, so it must land in this sprint. +4. **Publish `docs/rcp/v1.md`** with explicit stability policy: which fields are stable, which are experimental, how deprecations work. + +**Explicitly out of scope for this RFC:** extracting `FrameworkAdapter` into a public adapter SDK. The DOP memo bundles that with RCP, but they are different bets — RCP is the agent-facing protocol; `FrameworkAdapter` is an internal extension point. Lifting it to a public SDK is real work and worth doing, but it does not constrain the protocol decision and should not gate v1. A follow-on RFC will own that question once the protocol package exists. + +**Out of scope: `docs/architecture.md`.** The principal-eng verifier checklist references a "seven decisions" architecture doc that does not exist in this repo. This RFC stands up `docs/rfcs/` as the canonical decision record going forward; the architecture doc itself is a separate follow-on.