opentakeoff-mcp 0.9.81 → 0.9.83

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,12 +51,14 @@ register both verbs (<!--tool-count-all-->55<!--/tool-count-all--> tools); the p
51
51
  describe the lifted build. Design note: [`docs/design/ONE_CLICK_GATE.md`](../docs/design/ONE_CLICK_GATE.md).
52
52
 
53
53
 
54
- The takeoff engine—One-Click Area, the scale model, conditions, totals—on
55
- **stdio for your MCP client**. An agent can open a plan, read the title block,
56
- set the scale, click rooms, and hand back the same takeoff payload the browser
57
- app autosaves. Same engine, same math: the server imports
58
- `web/src/lib/{oneclick,sheets,geometry,totals}` directly, so a shape committed
59
- here is field-identical to one committed on the canvas.
54
+ The takeoff engine—scale model, conditions and totals—on **stdio for your MCP
55
+ client**. An agent can open a plan, read the title block, set the scale, inspect
56
+ source geometry and commit defensible measurements. The server imports shared
57
+ web modules, so quantity math and takeoff records are compatible with the
58
+ canvas. Browser and MCP room-detection paths currently differ; a shared module
59
+ does not establish identical boundaries on every plan. See the [capability
60
+ status](../docs/wiki/status.md) and [compatibility matrix](../protocol/COMPATIBILITY.md)
61
+ before claiming detector parity or a lossless handoff.
60
62
 
61
63
  ## Run with Docker
62
64
 
@@ -251,6 +253,18 @@ there is no filesystem path or remote URL input. `npm run check:wiki` compares
251
253
  the embedded copy to source; `-- --write` regenerates it. Build and CI fail when
252
254
  it is stale. The distribution smoke check reads all nine pages over stdio.
253
255
 
256
+ MCP 0.9.82 also embeds the explicit allowlist of draft Takeoff Protocol schemas
257
+ as read-only resources. Read `takeoff://protocol` for the machine-readable
258
+ index, then a schema such as
259
+ `takeoff://protocol/v1/measurement.schema.json`. This route is available before
260
+ plan load and while stages are closed; it adds no tool and does not change
261
+ `exportPayload()` or any writer. The URI is a transport address separate from
262
+ the unchanged schema `$id`; the `$id` is an offline-resolution identifier, not
263
+ a hosted-file promise. See `takeoff://wiki/protocol` for scope and limitations.
264
+
265
+ The generated registry is checked with `npm run check:protocol-resources`; it
266
+ must remain current before building or publishing the package.
267
+
254
268
  Tools let an agent act; resources let it **see**. When a plan loads, the sheet
255
269
  set becomes browsable natively (`resources/list` re-announces itself through
256
270
  `list_changed`):
@@ -15055,23 +15055,23 @@ No review gate: the pencil-not-ink rule exists to stop an agent inventing geomet
15055
15055
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
15056
15056
 
15057
15057
  // src/wiki.generated.ts
15058
- var WIKI_VERSION = "0.9.81";
15058
+ var WIKI_VERSION = "0.9.83";
15059
15059
  var WIKI_PAGES = [
15060
15060
  {
15061
15061
  "key": "index",
15062
15062
  "uri": "takeoff://wiki",
15063
15063
  "title": "OpenTakeoff knowledge index",
15064
15064
  "source": "docs/wiki/README.md",
15065
- "source_sha256": "26f6c1b098cbec0743a01225cead29925ad281071e8ec217a80b2d037f4220a6",
15066
- "text": "# OpenTakeoff knowledge index\n\nRead the page that answers the current question. Agents can read this index at\n`takeoff://wiki` and individual pages at `takeoff://wiki/{page}` without loading\na plan. These are documentation resources, not additional measurement tools.\n\n| Question | Page | MCP resource |\n|---|---|---|\n| What works, what is draft, and what is unsupported? | [Capability status](takeoff://wiki/status) | `takeoff://wiki/status` |\n| How do the browser, MCP and shared engine fit together? | [Architecture](takeoff://wiki/architecture) | `takeoff://wiki/architecture` |\n| What does a saved takeoff mean? | [Protocol](takeoff://wiki/protocol) | `takeoff://wiki/protocol` |\n| How do I measure, stitch, review and hand off work? | [Workflows](takeoff://wiki/workflows) | `takeoff://wiki/workflows` |\n| Which tool should I call, and in which coordinate frame? | [MCP routing](takeoff://wiki/mcp) | `takeoff://wiki/mcp` |\n| What do floor SF, wall SF, base LF and review mean? | [Domain knowledge](takeoff://wiki/domain) | `takeoff://wiki/domain` |\n| How do I change this repository and verify a PR? | [Repository guide](takeoff://wiki/repo-guide) | `takeoff://wiki/repo-guide` |\n| What tools and required inputs does the runtime expose? | [Generated tool index](takeoff://wiki/tool-index) | `takeoff://wiki/tool-index` |\n\nFor detailed canvas instructions, use the [human guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md).\nFor a full takeoff session, use the [agent guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md).\nThe wiki routes to their detailed procedures rather than copying every control\nand schema into every page.\n\nThe MCP build embeds these pages. CI checks the embedded copy, tool inventory,\nschema references and documentation links against their sources. Reading a\nresource does not mutate a session, set scale, review work or grant approval.\n"
15065
+ "source_sha256": "eab1088854a035cdf8e279b56256d5f256bde6407b589e04d13184e36816ee33",
15066
+ "text": "# OpenTakeoff knowledge index\n\nRead the page that answers the current question. Agents can read this index at\n`takeoff://wiki` and individual pages at `takeoff://wiki/{page}` without loading\na plan. These are documentation resources, not additional measurement tools.\n\n| Question | Page | MCP resource |\n|---|---|---|\n| What works, what is draft, and what is unsupported? | [Capability status](takeoff://wiki/status) | `takeoff://wiki/status` |\n| How do the browser, MCP and shared engine fit together? | [Architecture](takeoff://wiki/architecture) | `takeoff://wiki/architecture` |\n| What does a saved takeoff mean? | [Protocol](takeoff://wiki/protocol) | `takeoff://wiki/protocol` |\n| How do I measure, stitch, review and hand off work? | [Workflows](takeoff://wiki/workflows) | `takeoff://wiki/workflows` |\n| Which tool should I call, and in which coordinate frame? | [MCP routing](takeoff://wiki/mcp) | `takeoff://wiki/mcp` |\n| What do floor SF, wall SF, base LF and review mean? | [Domain knowledge](takeoff://wiki/domain) | `takeoff://wiki/domain` |\n| How do I change this repository and verify a PR? | [Repository guide](takeoff://wiki/repo-guide) | `takeoff://wiki/repo-guide` |\n| What tools and required inputs does the runtime expose? | [Generated tool index](takeoff://wiki/tool-index) | `takeoff://wiki/tool-index` |\n\nFor detailed canvas instructions, use the [human guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md).\nFor a full takeoff session, use the [agent guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md).\nThe wiki routes to their detailed procedures rather than copying every control\nand schema into every page.\n\nProposed priorities are tracked separately in the [roadmap](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/ROADMAP.md). They\nare acceptance targets, not shipped capability; use [capability status](takeoff://wiki/status)\nfor what is available today.\n\nThe MCP build embeds these pages. CI checks the embedded copy, tool inventory,\nschema references and documentation links against their sources. Reading a\nresource does not mutate a session, set scale, review work or grant approval.\n"
15067
15067
  },
15068
15068
  {
15069
15069
  "key": "status",
15070
15070
  "uri": "takeoff://wiki/status",
15071
15071
  "title": "Capability status",
15072
15072
  "source": "docs/wiki/status.md",
15073
- "source_sha256": "85924a7da3e5e8255ccbfcabdc6106426d0b5a329c043012515108b74836987d",
15074
- "text": "# Capability status\n\nThis page describes implementation support, not a promise that every plan can\nbe measured automatically. Follow its source links before changing behavior.\n\n| Capability | Current boundary | Evidence |\n|---|---|---|\n| Human and agent measurements | Shared geometry/quantity modules; source interpretation still requires inspection | [Geometry workflow](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md) |\n| Original machine geometry after human correction | Outer vertices preserved, including manual agent traces; missing historical originals cannot be recovered | [Compatibility](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md) |\n| Human stitching | Browser gallery and alignment; editable browser archive preserves composites | [Workflow](takeoff://wiki/workflows) |\n| MCP stitched documents | No stitch tool; current import/export omits stitch records | [Field inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) |\n| One-Click | Temporarily gated in the default browser/MCP builds | [Gate](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/design/ONE_CLICK_GATE.md) |\n| TakeoffDocument v1 | Draft schemas, preflight and opt-in repository adapters for a bounded profile; legacy writer format remains | [Protocol](takeoff://wiki/protocol) |\n| Annotation cleanup | Text-only MCP edit with undo; RFI-linked notes refuse | [MCP routing](takeoff://wiki/mcp) |\n| Agent knowledge | Packaged wiki resources and source-generated tool/schema references | [Index](takeoff://wiki) |\n| Academy interoperability | Incompatibilities inventoried; no adapter or certification bridge claimed | [Academy report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md) |\n| Independent geometry benchmark | Reference-assisted and synthetic checks are not independently reviewed ground truth | [Geometry evidence](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md#reusable-evidence-from-a-project-test) |\n\nNext protocol work connects the tested contracts to focused MCP workflows and\nmeasures tool-selection/completion results. Broader adapter profiles remain\nbounded by preservation tests. Package extraction, verified identity/signatures, Academy credentials\nand optional anchoring come later. Rendering optimization is separate from\nthis protocol and knowledge work.\n"
15073
+ "source_sha256": "2db64b41c4510a679a5d589efc4546e4b74a9b7d31f34c9a8d13512d6bb5955b",
15074
+ "text": "# Capability status\n\nThis page describes implementation support, not a promise that every plan can\nbe measured automatically. Follow its source links before changing behavior.\n\n| Capability | Current boundary | Evidence |\n|---|---|---|\n| Human and agent measurements | Shared geometry/quantity modules; source interpretation still requires inspection | [Geometry workflow](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md) |\n| Original machine geometry after human correction | Outer vertices preserved, including manual agent traces; missing historical originals cannot be recovered | [Compatibility](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md) |\n| Human stitching | Browser gallery and alignment; editable browser archive preserves composites | [Workflow](takeoff://wiki/workflows) |\n| MCP stitched documents | No stitch tool; current import/export omits stitch records | [Field inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) |\n| One-Click | Temporarily gated in the default browser/MCP builds | [Gate](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/design/ONE_CLICK_GATE.md) |\n| Browser/MCP room boundaries | Shared quantity modules do not prove identical detector boundaries; the open [#385](https://github.com/Kentucky-ai/opentakeoff/issues/385) report remains unresolved | [Architecture](takeoff://wiki/architecture), [geometry evidence](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md) |\n| Toolbar command and voice | Gated in the default build; use normal controls and keyboard shortcuts | [User guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md#17-voice-and-the-command-box) |\n| TakeoffDocument v1 | Draft schemas, preflight and opt-in repository adapters for a bounded profile; legacy writer format remains | [Protocol](takeoff://wiki/protocol) |\n| Annotation cleanup | Text-only MCP edit with undo; RFI-linked notes refuse | [MCP routing](takeoff://wiki/mcp) |\n| Agent knowledge | Packaged wiki resources and source-generated tool/schema references | [Index](takeoff://wiki) |\n| Academy interoperability | Incompatibilities inventoried; no adapter or certification bridge claimed | [Academy report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md) |\n| Shared work review | Personal layouts/shared review proposal is not merged; do not describe [PR #388](https://github.com/Kentucky-ai/opentakeoff/pull/388) as shipped | [Roadmap](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/ROADMAP.md) |\n| Microsoft 365 annotation sync | Unproven across machines; the live validation remains open in [#315](https://github.com/Kentucky-ai/opentakeoff/issues/315) | [Roadmap](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/ROADMAP.md) |\n| Learned floor-model bridge | Not on `main`; no model-generated geometry integration is advertised | [Roadmap](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/ROADMAP.md) |\n| Independent geometry benchmark | Reference-assisted and synthetic checks are not independently reviewed ground truth | [Geometry evidence](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md#reusable-evidence-from-a-project-test) |\n\nNext protocol work connects the tested contracts to focused MCP workflows and\nmeasures tool-selection/completion results. Broader adapter profiles remain\nbounded by preservation tests. Package extraction, verified identity/signatures, Academy credentials\nand optional anchoring come later. Rendering optimization is separate from\nthis protocol and knowledge work.\n"
15075
15075
  },
15076
15076
  {
15077
15077
  "key": "architecture",
@@ -15086,16 +15086,16 @@ var WIKI_PAGES = [
15086
15086
  "uri": "takeoff://wiki/protocol",
15087
15087
  "title": "Takeoff Protocol",
15088
15088
  "source": "docs/wiki/protocol.md",
15089
- "source_sha256": "0a0604dade9d41f025204d1dbe7e5aea89ac4db56a58236497242470bb3c4a05",
15090
- "text": "# Takeoff Protocol\n\nThe writer format remains `opentakeoff.takeoff_canvas.v1`. The proposed\n`opentakeoff.takeoff-document.v1` is a draft contract with offline validation,\nnot a new autosave format. Never rename the schema identifier and call that a\ncompleted migration: transport support, semantic checks and loss handling still\nneed evidence.\n\n| Contract concern | Canonical source |\n|---|---|\n| Measurement, Calibration, Provenance, Evidence and Review | [Generated schema reference](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/README.md#generated-schema-reference) |\n| Existing fields, writers, readers and projections | [Field inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) |\n| Conforms versus unsupported, including stitches | [Compatibility status](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md) |\n| Inspect or explicitly convert a document | [Preflight](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/PREFLIGHT.md) and [opt-in adapter CLI](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ADAPTERS.md) |\n| Differences from Academy's records and trust model | [Academy compatibility report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md) |\n| Minimal event vocabulary without a new event store | [Draft events](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/EVENTS.md) |\n\nActor and drawing method are separate. An agent can draw with a manual tool;\nhuman correction still preserves its original outer ring. Existing missing\nhistory cannot be reconstructed, and outer-ring preservation does not imply a\ncomplete historical snapshot of holes or curves.\n\nHuman review, a human approval seal, an agent verdict, confidence and Academy\ncertification are different claims. Validation checks record structure; it does\nnot prove who authored or approved it. MCP cannot create a human approval.\n\nPrivate repository tooling now converts the bounded core profile between canvas\nand draft JSON after preflight, target validation and exact preservation checks.\nIt changes only the schema identifier and writes a separate file; unsupported\nrecords refuse. Browser/MCP readers still require canvas JSON. Conversion does\nnot establish geometry accuracy or repair downstream transport loss. The CLI is\nnot a packaged MCP tool; use the canonical adapter guide for commands and limits.\n\nFollow the implementation order: schema, compatibility tests, then explicit\nopt-in adapters. Preserve inputs, IDs, quantities, originals, extensions and\nmissing-value meaning; refuse unsupported loss. No default writer migration,\nAcademy schema change, identity/signature system or storage infrastructure is\npart of the draft. Run `npm run check --prefix protocol` when changing it.\n"
15089
+ "source_sha256": "88bfb63d900d6905fc4bfd3e828b6c59a51eda1e5ea736facaf12b2656925e9b",
15090
+ "text": "# Takeoff Protocol\n\nThe writer format remains `opentakeoff.takeoff_canvas.v1`. The proposed\n`opentakeoff.takeoff-document.v1` is a draft contract with offline validation,\nnot a new autosave format. Never rename the schema identifier and call that a\ncompleted migration: transport support, semantic checks and loss handling still\nneed evidence.\n\n## Machine-readable MCP resources\n\nMCP 0.9.82 packages the allowlisted draft schemas as read-only resources. Start\nat `takeoff://protocol` for the compact index, then read an individual schema at\n`takeoff://protocol/{path}` (for example,\n`takeoff://protocol/v1/measurement.schema.json`). These resources are available\nbefore a plan is loaded and in staged mode. They are discovery and contract\nmaterial; they do not add a validator tool, change a writer, or make a session a\ncomplete `TakeoffDocument`.\n\nThe resource URI is a transport address. Each schema keeps its existing `$id`\nand relative `$ref` behavior, so the embedded registry can resolve references\noffline. An `$id` URL is a stable schema identifier for resolution, not a\npromise that the schema is hosted at that URL. The packaged index links back to\nthis wiki page for conceptual protocol guidance.\n\nThe schemas describe record shape and declared relationships. They do not prove\npolygon topology, geometry or quantity accuracy, source/PDF availability,\ncomplete history, active-reference integrity, transport preservation, or review\nor approval authenticity. Evidence, provenance, and review fields remain\nrecords and claims, not authentication. Unknown extension fields remain opaque.\nSecondary families such as markups, RFIs, rules, and layout/workspace data are\nnot fully semantically validated. `exportPayload()` is a projection: it omits\nrules and browser stitches, strips RFI tombstones, and does not represent the\ncomplete browser workspace or project document.\n\n| Contract concern | Canonical source |\n|---|---|\n| Measurement, Calibration, Provenance, Evidence and Review | [Generated schema reference](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/README.md#generated-schema-reference) |\n| Existing fields, writers, readers and projections | [Field inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) |\n| Conforms versus unsupported, including stitches | [Compatibility status](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md) |\n| Inspect or explicitly convert a document | [Preflight](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/PREFLIGHT.md) and [opt-in adapter CLI](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ADAPTERS.md) |\n| Differences from Academy's records and trust model | [Academy compatibility report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md) |\n| Minimal event vocabulary without a new event store | [Draft events](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/EVENTS.md) |\n\nActor and drawing method are separate. An agent can draw with a manual tool;\nhuman correction still preserves its original outer ring. Existing missing\nhistory cannot be reconstructed, and outer-ring preservation does not imply a\ncomplete historical snapshot of holes or curves.\n\nHuman review, a human approval seal, an agent verdict, confidence and Academy\ncertification are different claims. Validation checks record structure; it does\nnot prove who authored or approved it. MCP cannot create a human approval.\n\nPrivate repository tooling now converts the bounded core profile between canvas\nand draft JSON after preflight, target validation and exact preservation checks.\nIt changes only the schema identifier and writes a separate file; unsupported\nrecords refuse. Browser/MCP readers still require canvas JSON. Conversion does\nnot establish geometry accuracy or repair downstream transport loss. The CLI is\nnot a packaged MCP tool; use the canonical adapter guide for commands and limits.\n\nFollow the implementation order: schema, compatibility tests, then explicit\nopt-in adapters. Preserve inputs, IDs, quantities, originals, extensions and\nmissing-value meaning; refuse unsupported loss. No default writer migration,\nAcademy schema change, identity/signature system or storage infrastructure is\npart of the draft. Run `npm run check --prefix protocol` when changing it.\n"
15091
15091
  },
15092
15092
  {
15093
15093
  "key": "workflows",
15094
15094
  "uri": "takeoff://wiki/workflows",
15095
15095
  "title": "Workflows",
15096
15096
  "source": "docs/wiki/workflows.md",
15097
- "source_sha256": "defe8732d8dee9e38f040c97c315195f63dd891f2a66c8281c770b621b9ccad2",
15098
- "text": "# Workflows\n\n## Human: open, stitch and measure\n\nOpen the plans and check their revision. In the sheet gallery, select the\n2\u20134 split sheets in left-to-right order and click **Stitch N into one surface**.\nOn the composite, choose **Align**, click a recognizable point near the joint,\nthen click the same drawn point on the other sheet. Check the joint and scale\nbefore tracing. Once shapes exist on a stitch, alignment is locked.\n\nUse the composite as one measuring surface. Its marked-set page is labeled as\na composite, not an architect-issued sheet. Save the editable project archive\nwhen preserving the composite is required. MCP has no stitch/alignment tool and\nits current import/export path omits stitches: it cannot be used as a lossless\nhandoff for that document. Have an MCP agent measure source sheets individually\nwith explicit scope boundaries instead.\n\nSource: [Human stitching instructions](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md#stitching-a-floor-split-at-a-match-line),\n[stitch records](https://github.com/Kentucky-ai/opentakeoff/blob/main/web/src/lib/stitches.ts),\n[compatibility evidence](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md).\n\n## Agent: source to reviewed handoff\n\n1. `load_plan` \u2192 inspect sheets/revisions \u2192 `set_scale` on each measured sheet.\n Confirm the relevant detail's scale, which may differ from the overall plan.\n2. Read source text and vectors; inspect the matching region with `view_sheet`.\n Use each room's schedule evidence for its finish. Missing/ambiguous evidence\n is a qualification or RFI, not an inferred assignment.\n3. `propose_takeoff` names a batch; it creates no geometry. Measure small batches\n with the appropriate area, run, surface or count tool.\n4. Inspect overlays and actual boundaries, openings, jambs and deductions.\n `edit_shape` corrects pending shapes. Physical base gaps use explicit runs;\n stepped wall faces use separate height bands. See the [geometry workflow](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md).\n5. `takeoff_summary` and `export_report` check quantities and material coverage.\n Shorten notes through `list_annotations` \u2192 `edit_annotation` where permitted.\n6. Export editable takeoff JSON and a marked-set PDF, reopen the JSON against\n the same source, and check the handoff. Leave agent work pending for the\n human's review; exported files and agent verdicts do not create approval.\n\nThe [MCP route map](takeoff://wiki/mcp) gives the next tool at each step. The\n[agent guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md) covers staging and refusal recovery.\n\n## Evidence that another person can review\n\nKeep source/revision identifiers, calibration choices, measured geometry,\nexpected-versus-observed checks and an overlay or marked-set view. Compare\nspatial agreement as well as quantities. Disclose reference-assisted work;\nit is not a blind accuracy benchmark. Public PRs include screenshots, video or\nreproducible stats, using publishable fixtures. Private drawings and pricing stay\noutside the public repository. See the [repository guide](takeoff://wiki/repo-guide).\n"
15097
+ "source_sha256": "23d921b95108731f41f1a68d132bd64693c081e7ee30627a12d147cced6a15f0",
15098
+ "text": "# Workflows\n\n## Human: open, stitch and measure\n\nOpen the plans and check their revision. Press **G** (or choose **Sheets \u2192 Open\ngallery\u2026**) to open the sheet gallery, then select the\n2\u20134 split sheets in left-to-right order and click **Stitch N into one surface**.\nOn the composite, choose **Align**, click a recognizable point near the joint,\nthen click the same drawn point on the other sheet. Check a second recognizable\npoint and the scale before tracing. Align translates a member; it does not\nrotate or resize it. Once shapes exist on a stitch, alignment is locked.\n\nIf the controls seem missing, **Stitch N into one surface** is in the gallery\nfooter and requires 2\u20134 selected sheets; **Align** appears only on an open\nstitched surface. Create a fresh stitch if Align refuses because takeoffs\nalready exist; deleting a stitch also refuses while takeoffs or markups exist.\n\nUse the composite as one measuring surface. Its marked-set page is labeled as\na composite, not an architect-issued sheet. Save the editable project archive\nwhen preserving the composite is required. MCP has no stitch/alignment tool and\nits current import/export path omits stitches: it cannot be used as a lossless\nhandoff for that document. Have an MCP agent measure source sheets individually\nwith explicit scope boundaries instead.\n\nSource: [Human stitching instructions](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md#stitching-a-floor-split-at-a-match-line),\n[stitch records](https://github.com/Kentucky-ai/opentakeoff/blob/main/web/src/lib/stitches.ts),\n[compatibility evidence](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/COMPATIBILITY.md).\n\n## Agent: source to reviewed handoff\n\n1. `load_plan` \u2192 inspect sheets/revisions \u2192 `set_scale` on each measured sheet.\n Confirm the relevant detail's scale, which may differ from the overall plan.\n2. Read source text and vectors; inspect the matching region with `view_sheet`.\n Use each room's schedule evidence for its finish. Missing/ambiguous evidence\n is a qualification or RFI, not an inferred assignment.\n3. `propose_takeoff` names a batch; it creates no geometry. Measure small batches\n with the appropriate area, run, surface or count tool.\n4. Inspect overlays and actual boundaries, openings, jambs and deductions.\n `edit_shape` corrects pending shapes. Physical base gaps use explicit runs;\n stepped wall faces use separate height bands. See the [geometry workflow](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/GEOMETRY_WORKFLOW.md).\n5. `takeoff_summary` and `export_report` check quantities and material coverage.\n Shorten notes through `list_annotations` \u2192 `edit_annotation` where permitted.\n6. Export editable takeoff JSON and a marked-set PDF, reopen the JSON against\n the same source, and check the handoff. Leave agent work pending for the\n human's review; exported files and agent verdicts do not create approval.\n\nThe [MCP route map](takeoff://wiki/mcp) gives the next tool at each step. The\n[agent guide](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md) covers staging and refusal recovery.\n\n## Evidence that another person can review\n\nKeep source/revision identifiers, calibration choices, measured geometry,\nexpected-versus-observed checks and an overlay or marked-set view. Compare\nspatial agreement as well as quantities. Disclose reference-assisted work;\nit is not a blind accuracy benchmark. Public PRs include screenshots, video or\nreproducible stats, using publishable fixtures. Private drawings and pricing stay\noutside the public repository. See the [repository guide](takeoff://wiki/repo-guide).\n"
15099
15099
  },
15100
15100
  {
15101
15101
  "key": "mcp",
@@ -15118,8 +15118,8 @@ var WIKI_PAGES = [
15118
15118
  "uri": "takeoff://wiki/repo-guide",
15119
15119
  "title": "Working on the repository",
15120
15120
  "source": "docs/wiki/repo-guide.md",
15121
- "source_sha256": "f57a56894aed4d50564a49ef3e97a289de46b28bf9e1c365577df8527a4248e4",
15122
- "text": "# Working on the repository\n\nOpenTakeoff is a **client-only React app**: a PDF construction-takeoff canvas for flooring (useful for any trade). No backend, no database, no auth\u2014everything runs and persists in the browser. Apache-2.0. (For the one-page project pitch and vision, see [`AGENT_BRIEF.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/AGENT_BRIEF.md); for capability \u2192 code mapping, see [`FEATURES.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/FEATURES.md).)\n\n## Run / build / check\n\n```bash\ncd web\nnvm use # Node pinned by web/.nvmrc (CI reads the same file)\nnpm install\nnpm run dev # http://localhost:5173 \u2014 hot reload\nnpm test # node:test over the pure geometry + totals math (test/*.test.ts)\nnpm run build # \u2192 web/dist/ (static output; this is what Netlify deploys)\nnpm run check # typecheck + lint + test + build \u2014 exactly what CI runs; green here \u21D2 green CI\n```\n\n## Shipping \u2014 the required steps, every change\n\n`main` is protected on GitHub by a ruleset (PR-only, one approving review,\ngreen `web` check, branch up to date\u2014the repo owner has a standing bypass\nas the solo maintainer). **Merging to `main` deploys to production**\n(<https://opentakeoff.kentucky-ai.com>)\u2014Netlify's own git integration builds\nthe merge commit and publishes it. `netlify.toml` holds the whole recipe\n(`base = \"web\"`, `command = npm run build`, `publish = \"dist\"`), so the deploy\nruns a fresh build from the merged source; nothing is uploaded from CI.\n\n`.github/workflows/deploy.yml` used to do this by publishing `web/dist` with\n`--no-build`, and it was **deleted on 2026-07-13 in `e701f1a`** (\"deploys here\nare manual CLI; it fails on every push without the fork's secrets\"). Current checks and release automation live in\n[the workflow directory](https://github.com/Kentucky-ai/opentakeoff/blob/main/.github/workflows); `publish-mcp.yml` fires on\nan `mcp-v*` tag. The old line here said \"Netlify never builds anything\nitself\", which is now exactly backwards\u2014Netlify is the only thing that\nbuilds production. **Merge = deploy either way: that part has never changed.**\n\n> **This is the canonical `Kentucky-ai/opentakeoff` repo\u2014production is\n> <https://opentakeoff.kentucky-ai.com>, nothing else.** A downstream fork\n> (`knmurphy/opentakeoff`) tracks this repo as its own upstream and deploys\n> separately to `takeoff.345flooring.com`\u2014that URL belongs to *that* fork,\n> not this repo. If you see `takeoff.345flooring.com` referenced elsewhere in\n> this repo's docs, it's either an example value in the optional cloud-mode\n> guides or leftover content from that fork's docs that rode along in a\n> wholesale history merge (2026-07-13)\u2014treat it as describing the\n> *downstream* fork's deployment, not this one's.\n\nSo:\n\n1. **Branch first**\u2014never commit on `main`: `git checkout -b <topic>`.\n2. **`npm run check` before pushing** (in `web/`). It is exactly what CI runs,\n on the same Node (`web/.nvmrc`)\u2014green here means green CI.\n3. **Include review evidence in every PR**: screenshots or a short video for\n visible changes; measured expected-versus-observed stats and reproducible\n commands for engine/tool changes. Link the actual checks or evidence. This\n repository is also the maintainer's public portfolio: keep claims verifiable\n and private project data out of public artifacts.\n4. **Open a PR** and wait for the `web` check to pass. Don't merge red or\n pending.\n5. **Squash-merge with branch delete**\n (`gh pr merge <n> --squash --delete-branch`), then\n `git checkout main && git pull --ff-only` and delete the local branch\n (`git branch -D <topic>`\u2014squash merges need `-D`).\n6. **Remember a merge is a deploy.** Don't merge work you haven't verified in\n the running app.\n\nThe tests cover the pure math (`web/test/geometry.test.ts`, `web/test/totals.test.ts`); the canvas itself is verified by hand\u2014**Vite does not flag undefined identifiers in JSX**, so grep for your new identifiers after editing and load the app once before you call it done. The bundled sample plan (`web/public/demo/`, wired to the \"Load sample plan\" button) is the fastest end-to-end check: load it, press `A`, trace a room, open Report.\n\n## Where things live\n\n| Concern | Path |\n|---|---|\n| **The canvas\u201490% of the app** | `web/src/pages/TakeoffCanvas.jsx` (one large, deliberately monolithic component) |\n| Geometry: vector extraction, One-Click flood fill, vertex snap | `web/src/lib/oneclick.ts` |\n| Sheet/page helpers, scale detection | `web/src/lib/sheets.ts` |\n| Totals and materials math (waste, SY, coverage \u2192 order qty) | `web/src/lib/totals.js` |\n| Persistence (IndexedDB + localStorage) | `web/src/lib/store.js` |\n| PDF/image/zip ingest | `web/src/lib/ingest.js` |\n| Icon set | `web/src/brand/icons.jsx` |\n| Design tokens (colors, spacing\u2014the source of truth) | `web/src/styles/tokens.css` |\n| Sheet gallery / report UI | `web/src/components/` |\n| Pure-math tests (node:test) | `web/test/` |\n| **Optional AI backend** (pluggable adapter: scale/room/finish suggestions) | `server/`\u2014`app.py` + `adapters/base.py` (interface) + `adapters/heuristic.py` (default, no model) |\n\n## How the canvas works (the mental model)\n\n- Each open sheet renders into a `<canvas>` bitmap; **all takeoff geometry is an SVG overlay** on top; pan/zoom is a single CSS transform on the stage div, written imperatively (`tfRef` \u2192 `style.transform`) to avoid React re-renders per frame.\n- Coordinates: pointer events (client px) \u2192 `toImage()` \u2192 **stage px**; committed shapes store **normalized [0..1] vertices per sheet** (`verts_norm`), so quantities survive re-renders and zoom.\n- Cursor-following UI (crosshair hairlines, readout chip, rubber band) updates through **direct DOM writes in `moveCrosshair`**\u2014never React state per mousemove. Keep it that way.\n- Angle snapping: `angleSnap()` locks in-progress segments to the 45\xB0 family; endpoint snap (`nearestSnap` over a spatial hash of PDF vector endpoints) takes priority. The committed click reuses the same locked point (`angleRef`).\n- Past ~1.15\xD7 zoom, a **detail-view canvas** re-renders the visible region from PDF vectors at the current zoom (crispness); the base bitmap stays as first paint.\n- pdf.js rendering schedules work on `requestAnimationFrame`\u2014a fully hidden/occluded window will pause mid-render by design; it resumes when visible.\n\n## Conventions\n\n- **SVG presentation attributes take literal colors** (CSS vars don't resolve there): cobalt `#1f3fc7`, danger `#b03a26`, positive `#1f6b4a`\u2014centralized in `web/src/lib/ui.js` (`SVG`, with HUD-dark counterparts through `svgAccent(isDark)`). DOM/HTML chrome may use `var(--\u2026)` from `tokens.css`.\n- Condition palettes (`PALETTE` in `web/src/components/hatches.jsx`, the seeded condition colors in `FLOORING_DEFAULTS` in `web/src/lib/canvasConstants.js`, and the mirrored copies in `mcp/src/session.ts`) are **user data**\u2014don't re-theme them.\n- Waste applies only in the report (order quantities), never to live measured numbers.\n- Keyboard shortcuts are single letters registered on `window` (see `docs/USER_GUIDE.md` \xA715); toolbar menus pause them through `menuDepthRef`.\n- Brand voice: **precision instrument** (2026-08 overhaul). Light theme = \"ice\": bright white surfaces on a cool field, cool-slate neutrals, cobalt the one saturated thing. Dark theme = \"HUD\": true-black cockpit, electric blue `#3f8cff`, phosphor `--glow` on exactly five elements (active tool face, status verb, hero quantity, primary CTA, calibration dot). Square corners; the single sanctioned radius is `--r-1` on floating chrome. Mono tabular numerals on every readout. Drafting-table language stays. No vendor mimicry.\n- Layout/spacing/type come from the token scales in `tokens.css` (`--sp-*`, `--fs-*`, `--ctl-*`); zIndex comes from the `Z` ladder in `web/src/lib/ui.js`. No new magic numbers.\n\n## Docs to keep in sync when you change behavior\n\nThe draft Takeoff Protocol lives in [`protocol/`](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/README.md). When\nchanging persisted fields, consult its [inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) and\n[compatibility report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md). Run\n`npm run check --prefix protocol` after installing web, MCP, and protocol\ndependencies. Refresh schema references with\n`node protocol/scripts/check-docs.mjs --write`. The draft does not authorize\nchanging writer formats or adopting a migration.\n\n1. `README.md` (Features + \"What's in the box\")\n2. `docs/USER_GUIDE.md` (shortcuts + the relevant section)\n3. `CHANGELOG.md`\n\nTouching the **MCP server** adds four more, and they drift independently\u2014the\ntool count alone lives in five places, so grep the old number before you assume\nyou got them all:\n\n4. `mcp/src/tools.ts`\u2014the tool's own `description` **is** its integration.\n An MCP client reads it at runtime; nothing else you write reaches the model.\n5. `mcp/server.ts`\u2014the `instructions` block sent at `initialize`. This is the\n decision tree every client receives before its first call. A new *verb* does\n not belong here; a new *step in the standard finish* does.\n6. `mcp/README.md` (the tool table) and `docs/MCP.md` (the reach-for-it ordering,\n the example session, and the tool count in its opening line). A new tool also\n needs a row in `mcp/src/staging.ts`'s `TOOL_STAGES`\u2014the four lists must\n partition the tool set exactly, and a test fails CI if one doesn't.\n `npm run check:tool-count --prefix mcp` checks default, gated and setup counts across the current entry points,\n reference-table coverage, and [`docs/MCP_TOOL_INDEX.md`](takeoff://wiki/tool-index)\n against runtime schemas. Add `-- --write` to regenerate counts and the index. If the\n change alters *doctrine* rather than adding a verb\u2014what withholds, what\n refuses, what has no agent verb\u2014it belongs in `docs/AGENT_GUIDE.md` too,\n and the tool count appears there and in `docs/USER_GUIDE.md` \xA714.\n7. **Version, on three surfaces that must agree**: `mcp/package.json`,\n `mcp/server.json`, `web/public/.well-known/mcp.json`. They have drifted\n before (#171, and again at 0.9.28). Check `git show HEAD:mcp/package.json`\n before bumping\u2014a concurrent branch may already have claimed the number.\n\nArchitecture rather than behavior\u2014what MCP is versus what the `/ai` sandbox\nis, and why the server imports the web engine in-process\u2014lives at the end of\n[`docs/MCP.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/MCP.md) (\"Where this sits\") and in\n[`server/README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/server/README.md).\n\n## The doc set, and who each one is for\n\nFour documents carry the product, and they're deliberately split by audience\u2014don't\nanswer an estimator's question in the agent manual or vice versa:\n\n| Document | Audience | What belongs in it |\n|---|---|---|\n| [`README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/README.md) | everyone, ~60 seconds | what this is, the three doors, what's in the box |\n| [`docs/USER_GUIDE.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md) | the estimator at the canvas | every shipped UI behavior, the working order on a real bid, the glossary |\n| [`docs/AGENT_GUIDE.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md) | an agent driving the engine | the operating model, the standard finish, withheld doctrine, staging, refusal\u2192next-move |\n| [`mcp/README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/mcp/README.md) | an agent's integrator | tool-by-tool reference, resources, coordinate contract, limits |\n\nAll of them follow one house style\u2014the Apple Style Guide, with the rules that\nactually come up written out in [`CONTRIBUTING.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/CONTRIBUTING.md#docs-house-style).\nInterface text quoted in a doc is copied from the code verbatim, so changing a\nmessage means changing it in both places.\n\n[`AGENT_BRIEF.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/AGENT_BRIEF.md) is the one-page orientation that routes to\nall four. A behavior change usually touches two of them; a new MCP tool touches\nthree plus this file's sync list above.\n"
15121
+ "source_sha256": "f7ed92fc2e49cbb1e00b7e90335120dccd226bd837d1d7064c38bc9e74500cf7",
15122
+ "text": "# Working on the repository\n\nOpenTakeoff is a **client-only React app**: a PDF construction-takeoff canvas for flooring (useful for any trade). No backend, no database, no auth\u2014everything runs and persists in the browser. Apache-2.0. (For the one-page project pitch and vision, see [`AGENT_BRIEF.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/AGENT_BRIEF.md); for capability \u2192 code mapping, see [`FEATURES.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/FEATURES.md).)\n\n## Run / build / check\n\n```bash\ncd web\nnvm use # web/.nvmrc matches the root .nvmrc used by CI\nnpm install\nnpm run dev # http://localhost:5173 \u2014 hot reload\nnpm test # node:test over the pure geometry + totals math (test/*.test.ts)\nnpm run build # \u2192 web/dist/ (static output; this is what Netlify deploys)\nnpm run check # web typecheck + lint + test + benchmark + build\n```\n\n## Shipping \u2014 the required steps, every change\n\n`main` is protected on GitHub by a ruleset (PR-only, one approving review,\ngreen `web` check\u2014the repo owner has a standing bypass\nas the solo maintainer). **Merging to `main` deploys to production**\n(<https://opentakeoff.kentucky-ai.com>)\u2014Netlify's own git integration builds\nthe merge commit and publishes it. `netlify.toml` holds the whole recipe\n(`base = \"web\"`, `command = npm run build`, `publish = \"dist\"`), so the deploy\nruns a fresh build from the merged source; nothing is uploaded from CI.\n\n`.github/workflows/deploy.yml` used to do this by publishing `web/dist` with\n`--no-build`, and it was **deleted on 2026-07-13 in `e701f1a`** (\"deploys here\nare manual CLI; it fails on every push without the fork's secrets\"). Current checks and release automation live in\n[the workflow directory](https://github.com/Kentucky-ai/opentakeoff/blob/main/.github/workflows); `publish-mcp.yml` fires on\nan `mcp-v*` tag. The old line here said \"Netlify never builds anything\nitself\", which is now exactly backwards\u2014Netlify is the only thing that\nbuilds production. **Merge = deploy either way: that part has never changed.**\n\n> **This is the canonical `Kentucky-ai/opentakeoff` repo\u2014production is\n> <https://opentakeoff.kentucky-ai.com>, nothing else.** A downstream fork\n> (`knmurphy/opentakeoff`) tracks this repo as its own upstream and deploys\n> separately to `takeoff.345flooring.com`\u2014that URL belongs to *that* fork,\n> not this repo. If you see `takeoff.345flooring.com` referenced elsewhere in\n> this repo's docs, it's either an example value in the optional cloud-mode\n> guides or leftover content from that fork's docs that rode along in a\n> wholesale history merge (2026-07-13)\u2014treat it as describing the\n> *downstream* fork's deployment, not this one's.\n\nSo:\n\n1. **Branch first**\u2014never commit on `main`: `git checkout -b <topic>`.\n2. **`npm run check` before pushing** (in `web/`). It covers the independent web\n job on Node 24; a green web check does not replace the MCP, protocol, docs,\n capture or optional-server jobs.\n3. **Include review evidence in every PR**: screenshots or a short video for\n visible changes; measured expected-versus-observed stats and reproducible\n commands for engine/tool changes. Link the actual checks or evidence. This\n repository is also the maintainer's public portfolio: keep claims verifiable\n and private project data out of public artifacts.\n4. **Open a PR** and wait for the `web` check to pass. Don't merge red or\n pending.\n5. **Squash-merge with branch delete**\n (`gh pr merge <n> --squash --delete-branch`), then\n `git checkout main && git pull --ff-only` and delete the local branch\n (`git branch -D <topic>`\u2014squash merges need `-D`).\n6. **Remember a merge is a deploy.** Don't merge work you haven't verified in\n the running app.\n\nThe tests cover the pure math (`web/test/geometry.test.ts`, `web/test/totals.test.ts`); the canvas itself is verified by hand\u2014**Vite does not flag undefined identifiers in JSX**, so grep for your new identifiers after editing and load the app once before you call it done. The bundled sample plan (`web/public/demo/`, wired to the \"Load sample plan\" button) is the fastest end-to-end check: load it, press `A`, trace a room, open Report.\n\n## Where things live\n\n| Concern | Path |\n|---|---|\n| **The canvas\u201490% of the app** | `web/src/pages/TakeoffCanvas.jsx` (one large, deliberately monolithic component) |\n| Geometry: vector extraction, One-Click flood fill, vertex snap | `web/src/lib/oneclick.ts` |\n| Sheet/page helpers, scale detection | `web/src/lib/sheets.ts` |\n| Totals and materials math (waste, SY, coverage \u2192 order qty) | `web/src/lib/totals.js` |\n| Persistence (IndexedDB + localStorage) | `web/src/lib/store.js` |\n| PDF/image/zip ingest | `web/src/lib/ingest.js` |\n| Icon set | `web/src/brand/icons.jsx` |\n| Design tokens (colors, spacing\u2014the source of truth) | `web/src/styles/tokens.css` |\n| Sheet gallery / report UI | `web/src/components/` |\n| Pure-math tests (node:test) | `web/test/` |\n| **Optional AI backend** (pluggable adapter: scale/room/finish suggestions) | `server/`\u2014`app.py` + `adapters/base.py` (interface) + `adapters/heuristic.py` (default, no model) |\n\n## How the canvas works (the mental model)\n\n- Each open sheet renders into a `<canvas>` bitmap; **all takeoff geometry is an SVG overlay** on top; pan/zoom is a single CSS transform on the stage div, written imperatively (`tfRef` \u2192 `style.transform`) to avoid React re-renders per frame.\n- Coordinates: pointer events (client px) \u2192 `toImage()` \u2192 **stage px**; committed shapes store **normalized [0..1] vertices per sheet** (`verts_norm`), so quantities survive re-renders and zoom.\n- Cursor-following UI (crosshair hairlines, readout chip, rubber band) updates through **direct DOM writes in `moveCrosshair`**\u2014never React state per mousemove. Keep it that way.\n- Angle snapping: `angleSnap()` locks in-progress segments to the 45\xB0 family; endpoint snap (`nearestSnap` over a spatial hash of PDF vector endpoints) takes priority. The committed click reuses the same locked point (`angleRef`).\n- Past ~1.15\xD7 zoom, a **detail-view canvas** re-renders the visible region from PDF vectors at the current zoom (crispness); the base bitmap stays as first paint.\n- pdf.js rendering schedules work on `requestAnimationFrame`\u2014a fully hidden/occluded window will pause mid-render by design; it resumes when visible.\n\n## Conventions\n\n- **SVG presentation attributes take literal colors** (CSS vars don't resolve there): cobalt `#1f3fc7`, danger `#b03a26`, positive `#1f6b4a`\u2014centralized in `web/src/lib/ui.js` (`SVG`, with HUD-dark counterparts through `svgAccent(isDark)`). DOM/HTML chrome may use `var(--\u2026)` from `tokens.css`.\n- Condition palettes (`PALETTE` in `web/src/components/hatches.jsx`, the seeded condition colors in `FLOORING_DEFAULTS` in `web/src/lib/canvasConstants.js`, and the mirrored copies in `mcp/src/session.ts`) are **user data**\u2014don't re-theme them.\n- Waste applies only in the report (order quantities), never to live measured numbers.\n- Keyboard shortcuts are single letters registered on `window` (see `docs/USER_GUIDE.md` \xA715); toolbar menus pause them through `menuDepthRef`.\n- Brand voice: **precision instrument** (2026-08 overhaul). Light theme = \"ice\": bright white surfaces on a cool field, cool-slate neutrals, cobalt the one saturated thing. Dark theme = \"HUD\": true-black cockpit, electric blue `#3f8cff`, phosphor `--glow` on exactly five elements (active tool face, status verb, hero quantity, primary CTA, calibration dot). Square corners; the single sanctioned radius is `--r-1` on floating chrome. Mono tabular numerals on every readout. Drafting-table language stays. No vendor mimicry.\n- Layout/spacing/type come from the token scales in `tokens.css` (`--sp-*`, `--fs-*`, `--ctl-*`); zIndex comes from the `Z` ladder in `web/src/lib/ui.js`. No new magic numbers.\n\n## Docs to keep in sync when you change behavior\n\nThe draft Takeoff Protocol lives in [`protocol/`](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/README.md). When\nchanging persisted fields, consult its [inventory](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/INVENTORY.md) and\n[compatibility report](https://github.com/Kentucky-ai/opentakeoff/blob/main/protocol/ACADEMY_COMPATIBILITY.md). Run\n`npm run check --prefix protocol` after installing web, MCP, and protocol\ndependencies. Refresh schema references with\n`node protocol/scripts/check-docs.mjs --write`. The draft does not authorize\nchanging writer formats or adopting a migration.\n\n1. `README.md` (Features + \"What's in the box\")\n2. `docs/USER_GUIDE.md` (shortcuts + the relevant section)\n3. `CHANGELOG.md`\n\nTouching the **MCP server** also requires checking its runtime guidance and\ngenerated references:\n\n4. `mcp/src/tools.ts`\u2014the tool's own `description` is part of its integration.\n An MCP client reads it at runtime. Initialize instructions and packaged wiki\n resources are also agent-facing surfaces, so keep them consistent with the\n runtime behavior.\n5. `mcp/server.ts`\u2014the `instructions` block sent at `initialize`. This is the\n decision tree every client receives before its first call. A new *verb* does\n not belong here; a new *step in the standard finish* does. Packaged wiki\n resources are another deliberate agent-facing surface; update their source\n pages and run the wiki/resource checks when their guidance changes.\n6. `mcp/README.md` (the tool table) and `docs/MCP.md` (the reach-for-it ordering,\n the example session, and the tool count in its opening line). A new tool also\n needs a row in `mcp/src/staging.ts`'s `TOOL_STAGES`\u2014the four lists must\n partition the tool set exactly, and a test fails CI if one doesn't.\n `npm run check:tool-count --prefix mcp` checks default, gated and setup counts across the current entry points,\n reference-table coverage, and [`docs/MCP_TOOL_INDEX.md`](takeoff://wiki/tool-index)\n against runtime schemas. Add `-- --write` to regenerate counts and the index. If the\n change alters *doctrine* rather than adding a verb\u2014what withholds, what\n refuses, what has no agent verb\u2014it belongs in `docs/AGENT_GUIDE.md` too,\n and the tool count appears there and in `docs/USER_GUIDE.md` \xA714.\n7. **Version fields must agree.** The MCP source version is\n in `mcp/package.json`; its lockfile has two matching fields, while\n `mcp/server.json` and `web/public/.well-known/mcp.json` each have two. The\n version checker checks all seven MCP fields. Separately, it checks the web\n package version against the two root-version fields in its lockfile.\n Check current `origin/main` and existing tags before\n reserving a number. The web package has its own version; it is not the MCP\n release number.\n8. `npm run check:tool-count --prefix mcp` runs version agreement before checking\n counts and the schema-backed inventory. With `-- --write`, it refuses version\n mismatches before changing generated documents; update release metadata\n explicitly, then regenerate. Run `npm run check:versions --prefix mcp` for the\n version check alone. Wiki content has its own generator:\n `npm run check:wiki --prefix mcp -- --write`. Review generated diffs and run\n both checks without `--write` afterward.\n\nArchitecture rather than behavior\u2014what MCP is versus what the `/ai` sandbox\nis, and why the server imports the web engine in-process\u2014lives at the end of\n[`docs/MCP.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/MCP.md) (\"Where this sits\") and in\n[`server/README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/server/README.md).\n\n## The doc set, and who each one is for\n\nFour documents carry the product, and they're deliberately split by audience\u2014don't\nanswer an estimator's question in the agent manual or vice versa:\n\n| Document | Audience | What belongs in it |\n|---|---|---|\n| [`README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/README.md) | everyone, ~60 seconds | what this is, the three doors, what's in the box |\n| [`docs/USER_GUIDE.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/USER_GUIDE.md) | the estimator at the canvas | every shipped UI behavior, the working order on a real bid, the glossary |\n| [`docs/AGENT_GUIDE.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/docs/AGENT_GUIDE.md) | an agent driving the engine | the operating model, the standard finish, withheld doctrine, staging, refusal\u2192next-move |\n| [`mcp/README.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/mcp/README.md) | an agent's integrator | tool-by-tool reference, resources, coordinate contract, limits |\n\nAll of them follow one house style\u2014the Apple Style Guide, with the rules that\nactually come up written out in [`CONTRIBUTING.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/CONTRIBUTING.md#docs-house-style).\nInterface text quoted in a doc is copied from the code verbatim, so changing a\nmessage means changing it in both places.\n\n[`AGENT_BRIEF.md`](https://github.com/Kentucky-ai/opentakeoff/blob/main/AGENT_BRIEF.md) is the one-page orientation that routes to\nall four. A behavior change usually touches two of them; a new MCP tool touches\nthree plus this file's sync list above.\n"
15123
15123
  },
15124
15124
  {
15125
15125
  "key": "tool-index",
@@ -15131,6 +15131,99 @@ var WIKI_PAGES = [
15131
15131
  }
15132
15132
  ];
15133
15133
 
15134
+ // src/protocol.generated.ts
15135
+ var PROTOCOL_INDEX_JSON = '{\n "protocol": "TakeoffDocument v1 (draft)",\n "legacy": "takeoff_canvas.v1 structural profile (draft)",\n "scope": "Machine-readable discovery and contract resources; writers and runtime validation are unchanged.",\n "coordinate_contract": "Tool coordinates are full-sheet image pixels at render scale 2.0, top-left origin, y-down; persisted verts_norm are normalized to the sheet/composite frame.",\n "roles": [\n "floor_area",\n "deduct",\n "linear",\n "surface_area",\n "count"\n ],\n "identifiers": {\n "draft": "opentakeoff.takeoff-document.v1",\n "canvas": "opentakeoff.takeoff_canvas.v1"\n },\n "calibration": "Calibration is feet per logical image pixel; metric display does not rewrite stored geometry.",\n "records": "Evidence/provenance and review fields are records and claims, not authentication. Unknown extension fields remain opaque.",\n "limits": [\n "Schemas do not prove polygon topology, geometry accuracy, quantity recomputation, source/PDF availability, complete history, active reference integrity, MCP transport preservation, or approval authenticity.",\n "Secondary families such as markups, RFIs, rules, and layout/workspace data are not fully semantically validated.",\n "MCP exportPayload() is a projection: it omits rules and browser stitches, strips RFI tombstones, and does not represent the complete browser workspace/project document.",\n "The $id URLs are stable identifiers for offline resolution, not a promise that the files are deployed at those URLs.",\n "Both the draft TakeoffDocument v1 and existing canvas identifier are described; neither writer is changed by this increment.",\n "MCP cannot create human approval; schema-valid records do not authenticate their author or grant review authority."\n ],\n "wiki": "takeoff://wiki/protocol",\n "resources": [\n {\n "uri": "takeoff://protocol/legacy/takeoff-canvas.v1.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/legacy/takeoff-canvas.v1.schema.json",\n "title": "takeoff_canvas.v1 structural profile (draft)",\n "source": "protocol/legacy/takeoff-canvas.v1.schema.json",\n "sha256": "111ca2e5294dca11d742d5ae240d2910dd78ad52a631075b791f1483c736f01d"\n },\n {\n "uri": "takeoff://protocol/v1/calibration.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/calibration.schema.json",\n "title": "Calibration (draft)",\n "source": "protocol/v1/calibration.schema.json",\n "sha256": "53ab69f4df0ce57d869db576aef46f95f7592672315f7ba772e728f6c2d6ca12"\n },\n {\n "uri": "takeoff://protocol/v1/common.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/common.schema.json",\n "title": "Shared value types (draft)",\n "source": "protocol/v1/common.schema.json",\n "sha256": "b5bea272a92f051b780b4a1615105416406387c4dba47b3ca65cf2892c3e8c11"\n },\n {\n "uri": "takeoff://protocol/v1/condition.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/condition.schema.json",\n "title": "Condition (draft)",\n "source": "protocol/v1/condition.schema.json",\n "sha256": "fc5694ef95d4627cc07f9a7efa34b479345341ffd905931136e4d80fa9081d51"\n },\n {\n "uri": "takeoff://protocol/v1/document-fields.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/document-fields.schema.json",\n "title": "Existing document collections (draft)",\n "source": "protocol/v1/document-fields.schema.json",\n "sha256": "9ec6b05869bae0eb0e4c9d6caa12a525b6565a0e795134cfb64e6a7a232416b4"\n },\n {\n "uri": "takeoff://protocol/v1/evidence.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/evidence.schema.json",\n "title": "Evidence (draft)",\n "source": "protocol/v1/evidence.schema.json",\n "sha256": "5602648262758e35cf503a46fe65fb58f7e8d2d22717e313dd5e974664d771b9"\n },\n {\n "uri": "takeoff://protocol/v1/measurement.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/measurement.schema.json",\n "title": "Measurement (draft)",\n "source": "protocol/v1/measurement.schema.json",\n "sha256": "1f554393245404b009481efd6a8a9847ca82a3639919a04adf5e1e619d96bbcb"\n },\n {\n "uri": "takeoff://protocol/v1/provenance.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/provenance.schema.json",\n "title": "Provenance (draft)",\n "source": "protocol/v1/provenance.schema.json",\n "sha256": "fd5347aaed173391361b0372ef73c0d45547f9abdb4e7a6ca226de53ae24cd89"\n },\n {\n "uri": "takeoff://protocol/v1/review.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/review.schema.json",\n "title": "Review (draft)",\n "source": "protocol/v1/review.schema.json",\n "sha256": "3d1f326ff85e3498521e81df0cc2f2758b998be803400a25c21c65014657f245"\n },\n {\n "uri": "takeoff://protocol/v1/stitch.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/stitch.schema.json",\n "title": "Composite surface (draft)",\n "source": "protocol/v1/stitch.schema.json",\n "sha256": "db9ea1dffe5fd2360e1d231efc86ba84309316ba9add72c5c0fafef006e76154"\n },\n {\n "uri": "takeoff://protocol/v1/takeoff-document.schema.json",\n "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/takeoff-document.schema.json",\n "title": "TakeoffDocument v1 (draft)",\n "source": "protocol/v1/takeoff-document.schema.json",\n "sha256": "9c7800a9423a12e9334cda507fdd3c216205a4304e220df376dd67753accbd69"\n }\n ]\n}\n';
15136
+ var PROTOCOL_SCHEMAS = [
15137
+ {
15138
+ "uri": "takeoff://protocol/legacy/takeoff-canvas.v1.schema.json",
15139
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/legacy/takeoff-canvas.v1.schema.json",
15140
+ "title": "takeoff_canvas.v1 structural profile (draft)",
15141
+ "source": "protocol/legacy/takeoff-canvas.v1.schema.json",
15142
+ "sha256": "111ca2e5294dca11d742d5ae240d2910dd78ad52a631075b791f1483c736f01d",
15143
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/legacy/takeoff-canvas.v1.schema.json",\n "title": "takeoff_canvas.v1 structural profile (draft)",\n "description": "Descriptive profile for complete current browser/MCP records. The existing import gate is more permissive; acceptance by that gate does not establish conformance. This schema does not replace it.",\n "allOf": [\n {\n "$ref": "../v1/document-fields.schema.json"\n }\n ],\n "type": "object",\n "properties": {\n "schema": {\n "const": "opentakeoff.takeoff_canvas.v1"\n }\n },\n "required": [\n "schema",\n "conditions",\n "shapes"\n ]\n}\n'
15144
+ },
15145
+ {
15146
+ "uri": "takeoff://protocol/v1/calibration.schema.json",
15147
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/calibration.schema.json",
15148
+ "title": "Calibration (draft)",
15149
+ "source": "protocol/v1/calibration.schema.json",
15150
+ "sha256": "53ab69f4df0ce57d869db576aef46f95f7592672315f7ba772e728f6c2d6ca12",
15151
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/calibration.schema.json",\n "title": "Calibration (draft)",\n "type": "object",\n "properties": {\n "sheet_id": {\n "type": "string",\n "minLength": 1\n },\n "units_per_px": {\n "type": "number",\n "exclusiveMinimum": 0\n },\n "scale_source": {\n "type": "string"\n },\n "scale_confirmed": {\n "type": "boolean"\n }\n },\n "required": [\n "sheet_id",\n "units_per_px"\n ],\n "additionalProperties": true,\n "description": "A persisted calibrated-sheet row. units_per_px is feet per logical image pixel at PDF render scale 2, independent of metric display. Unscaled sheets can be absent. Missing confirmation follows legacy behavior; it is not a fresh human confirmation."\n}\n'
15152
+ },
15153
+ {
15154
+ "uri": "takeoff://protocol/v1/common.schema.json",
15155
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/common.schema.json",
15156
+ "title": "Shared value types (draft)",
15157
+ "source": "protocol/v1/common.schema.json",
15158
+ "sha256": "b5bea272a92f051b780b4a1615105416406387c4dba47b3ca65cf2892c3e8c11",
15159
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/common.schema.json",\n "title": "Shared value types (draft)",\n "$defs": {\n "point": {\n "type": "array",\n "prefixItems": [\n {\n "type": "number"\n },\n {\n "type": "number"\n }\n ],\n "items": false,\n "minItems": 2,\n "maxItems": 2,\n "description": "Two finite coordinates. Stored geometry is normalized to its sheet or stitch; values outside 0..1 are retained, not clamped. Tool input image pixels are a different frame."\n },\n "timestamp": {\n "type": "string",\n "format": "date-time"\n },\n "computed": {\n "type": "object",\n "properties": {\n "area_sf": {\n "type": "number"\n },\n "perimeter_lf": {\n "type": "number"\n },\n "count": {\n "type": "number"\n }\n },\n "additionalProperties": true,\n "description": "Stored quantity cache: square feet, linear feet, and each. Metric is display conversion. Validation never recomputes values."\n },\n "parentSnapshot": {\n "type": "object",\n "properties": {\n "verts_norm": {\n "type": "array",\n "items": {\n "$ref": "common.schema.json#/$defs/point"\n }\n },\n "verts_norm_holes": {\n "type": "array",\n "items": {\n "type": "array",\n "items": {\n "$ref": "common.schema.json#/$defs/point"\n }\n }\n },\n "computed": {\n "$ref": "#/$defs/computed"\n }\n },\n "required": [\n "verts_norm"\n ],\n "additionalProperties": true\n }\n }\n}\n'
15160
+ },
15161
+ {
15162
+ "uri": "takeoff://protocol/v1/condition.schema.json",
15163
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/condition.schema.json",
15164
+ "title": "Condition (draft)",
15165
+ "source": "protocol/v1/condition.schema.json",
15166
+ "sha256": "fc5694ef95d4627cc07f9a7efa34b479345341ffd905931136e4d80fa9081d51",
15167
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/condition.schema.json",\n "title": "Condition (draft)",\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "minLength": 1\n },\n "finish_tag": {\n "type": "string"\n },\n "color": {\n "type": "string"\n },\n "fill": {\n "type": "string"\n },\n "hatch": {\n "type": "string"\n },\n "multiplier": {\n "type": "number"\n },\n "waste_pct": {\n "type": "number"\n },\n "height_ft": {\n "type": "number"\n },\n "thickness_in": {\n "type": "number"\n },\n "materials": {\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "string"\n },\n "name": {\n "type": "string"\n },\n "per": {\n "type": "number"\n },\n "basis": {\n "type": "string"\n },\n "unit": {\n "type": "string"\n },\n "round": {\n "type": "boolean"\n },\n "note": {\n "type": "string"\n },\n "lib_id": {\n "type": "string"\n },\n "origin_id": {\n "type": "string"\n },\n "inherited": {\n "type": "boolean"\n }\n },\n "additionalProperties": true\n }\n },\n "roll_setup": {\n "type": [\n "object",\n "null"\n ]\n },\n "tile_setup": {\n "type": [\n "object",\n "null"\n ]\n },\n "created_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "updated_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n }\n },\n "required": [\n "id"\n ],\n "additionalProperties": true,\n "description": "Existing condition configuration, not a computed report or price. Additional setup, variant, style, and material fields remain opaque and preserved in this draft."\n}\n'
15168
+ },
15169
+ {
15170
+ "uri": "takeoff://protocol/v1/document-fields.schema.json",
15171
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/document-fields.schema.json",
15172
+ "title": "Existing document collections (draft)",
15173
+ "source": "protocol/v1/document-fields.schema.json",
15174
+ "sha256": "9ec6b05869bae0eb0e4c9d6caa12a525b6565a0e795134cfb64e6a7a232416b4",
15175
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/document-fields.schema.json",\n "title": "Existing document collections (draft)",\n "type": "object",\n "properties": {\n "project_name": {\n "type": "string"\n },\n "units": {\n "enum": [\n "imperial",\n "metric"\n ]\n },\n "client_info": {\n "type": "object"\n },\n "sheets": {\n "type": "array",\n "items": {\n "$ref": "calibration.schema.json"\n }\n },\n "conditions": {\n "type": "array",\n "items": {\n "$ref": "condition.schema.json"\n }\n },\n "shapes": {\n "type": "array",\n "items": {\n "$ref": "measurement.schema.json"\n }\n },\n "markups": {\n "type": "array",\n "items": {\n "type": "object"\n }\n },\n "approvals": {\n "type": "array",\n "items": {\n "$ref": "review.schema.json#/$defs/verdict"\n }\n },\n "stitches": {\n "type": "array",\n "items": {\n "$ref": "stitch.schema.json"\n }\n },\n "proposals": {\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "minLength": 1\n },\n "label": {\n "type": "string"\n },\n "rationale": {\n "type": "string"\n },\n "created_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "withdrawn_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n }\n },\n "required": [\n "id"\n ],\n "additionalProperties": true\n }\n },\n "condition_edit_proposals": {\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "minLength": 1\n },\n "condition_id": {\n "type": "string",\n "minLength": 1\n },\n "proposed": {\n "type": "object"\n },\n "rationale": {\n "type": "string"\n },\n "proposed_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n }\n },\n "required": [\n "id",\n "condition_id",\n "proposed"\n ],\n "additionalProperties": true\n }\n },\n "rfis": {\n "type": "array",\n "items": {\n "type": "object"\n }\n },\n "rules": {\n "type": "array",\n "items": {\n "type": "object"\n }\n },\n "sheet_group": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "last_group": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "sheet_tabs": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "sheet_levels": {\n "type": "object",\n "additionalProperties": {\n "type": "string"\n }\n },\n "layer_overrides": {\n "type": "object"\n },\n "condition_columns": {\n "type": "array"\n },\n "shape_labels": {\n "type": "array"\n },\n "palette": {\n "type": "array"\n },\n "provenance_counters": {\n "type": "object"\n }\n },\n "additionalProperties": true,\n "description": "Known collections shared by the draft and legacy structural profile. Some secondary families are opaque objects pending the full compatibility corpus. Unknown extensions are permitted and must be preserved by future adapters."\n}\n'
15176
+ },
15177
+ {
15178
+ "uri": "takeoff://protocol/v1/evidence.schema.json",
15179
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/evidence.schema.json",
15180
+ "title": "Evidence (draft)",
15181
+ "source": "protocol/v1/evidence.schema.json",
15182
+ "sha256": "5602648262758e35cf503a46fe65fb58f7e8d2d22717e313dd5e974664d771b9",
15183
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/evidence.schema.json",\n "title": "Evidence (draft)",\n "type": "object",\n "properties": {\n "schedule_row_tag": {\n "type": "string"\n },\n "matched_text": {\n "type": "string"\n },\n "seed_norm": {\n "$ref": "common.schema.json#/$defs/point"\n }\n },\n "additionalProperties": true,\n "description": "The existing origin.evidence citation object. Unknown source fields are retained, not authenticated. Contribution exports apply a separate privacy whitelist.",\n "$defs": {\n "assignment": {\n "type": "object",\n "properties": {\n "source": {\n "enum": [\n "schedule",\n "asserted"\n ]\n },\n "room_tag": {\n "type": "string"\n },\n "surface": {\n "type": "string"\n },\n "schedule_sheet": {\n "type": "string"\n }\n },\n "required": [\n "source"\n ],\n "additionalProperties": true\n },\n "symbol": {\n "type": "object",\n "properties": {\n "score": {\n "type": "number"\n },\n "rotation": {\n "type": "number"\n },\n "mirrored": {\n "type": "boolean"\n },\n "seed": {\n "type": "object",\n "properties": {\n "source": {\n "type": "string"\n },\n "sheet": {\n "type": "string"\n },\n "role": {\n "type": "string"\n },\n "seed_instance": {\n "type": "boolean"\n },\n "row": {\n "type": "object",\n "properties": {\n "sheet": {\n "type": "string"\n },\n "key": {\n "type": "string"\n },\n "table": {\n "type": "string"\n }\n },\n "additionalProperties": true\n }\n },\n "additionalProperties": true\n }\n },\n "additionalProperties": true\n }\n }\n}\n'
15184
+ },
15185
+ {
15186
+ "uri": "takeoff://protocol/v1/measurement.schema.json",
15187
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/measurement.schema.json",
15188
+ "title": "Measurement (draft)",
15189
+ "source": "protocol/v1/measurement.schema.json",
15190
+ "sha256": "1f554393245404b009481efd6a8a9847ca82a3639919a04adf5e1e619d96bbcb",
15191
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/measurement.schema.json",\n "title": "Measurement (draft)",\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "minLength": 1\n },\n "sheet_id": {\n "type": "string",\n "minLength": 1\n },\n "condition_id": {\n "type": "string",\n "minLength": 1\n },\n "measure_role": {\n "enum": [\n "floor_area",\n "deduct",\n "linear",\n "surface_area",\n "count"\n ]\n },\n "verts_norm": {\n "type": "array",\n "items": {\n "$ref": "common.schema.json#/$defs/point"\n },\n "minItems": 1\n },\n "verts_norm_holes": {\n "type": "array",\n "items": {\n "type": "array",\n "items": {\n "$ref": "common.schema.json#/$defs/point"\n }\n }\n },\n "computed": {\n "$ref": "common.schema.json#/$defs/computed"\n },\n "height_ft": {\n "type": "number"\n },\n "label": {\n "type": "string"\n },\n "curved": {\n "type": "boolean"\n },\n "cuts_shape_id": {\n "type": "string",\n "minLength": 1\n },\n "origin": {\n "$ref": "provenance.schema.json"\n },\n "created_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "updated_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "author": {\n "type": "string"\n },\n "updated_by": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "sheet_id",\n "condition_id",\n "measure_role",\n "verts_norm",\n "computed"\n ],\n "additionalProperties": true,\n "description": "One existing shape record. Legacy curved=true means spline control points; new baked arcs use ordinary vertices and origin.curved. Do not conflate or regenerate them."\n}\n'
15192
+ },
15193
+ {
15194
+ "uri": "takeoff://protocol/v1/provenance.schema.json",
15195
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/provenance.schema.json",
15196
+ "title": "Provenance (draft)",
15197
+ "source": "protocol/v1/provenance.schema.json",
15198
+ "sha256": "fd5347aaed173391361b0372ef73c0d45547f9abdb4e7a6ca226de53ae24cd89",
15199
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/provenance.schema.json",\n "title": "Provenance (draft)",\n "type": "object",\n "properties": {\n "method": {\n "type": "string",\n "minLength": 1\n },\n "actor": {\n "type": "string",\n "minLength": 1\n },\n "seed_norm": {\n "$ref": "common.schema.json#/$defs/point"\n },\n "evidence": {\n "$ref": "evidence.schema.json"\n },\n "assignment": {\n "$ref": "evidence.schema.json#/$defs/assignment"\n },\n "symbol": {\n "$ref": "evidence.schema.json#/$defs/symbol"\n },\n "confidence": {\n "type": "number",\n "minimum": 0,\n "maximum": 1\n },\n "confidence_factors": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "proposed_verts_norm": {\n "type": "array",\n "items": {\n "$ref": "common.schema.json#/$defs/point"\n }\n },\n "edits": {\n "type": "object",\n "additionalProperties": {\n "type": "integer",\n "minimum": 0\n }\n },\n "agent_edits": {\n "type": "integer",\n "minimum": 0\n },\n "derived": {\n "type": "object",\n "properties": {\n "from_shape_id": {\n "type": "string",\n "minLength": 1\n },\n "gross_lf": {\n "type": "number"\n },\n "openings_lf": {\n "type": "number"\n },\n "between_shape_ids": {\n "type": "array",\n "items": {\n "type": "string",\n "minLength": 1\n }\n },\n "between": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "case": {\n "type": "string"\n },\n "gap_in": {\n "type": "number"\n }\n },\n "additionalProperties": true\n },\n "parent_prev": {\n "$ref": "common.schema.json#/$defs/parentSnapshot"\n },\n "hatch_filtered": {\n "type": "boolean"\n },\n "layer_bounded": {\n "type": "boolean"\n },\n "raster_traced": {\n "type": "boolean"\n },\n "edited": {\n "type": "boolean"\n },\n "edited_before_create": {\n "type": "boolean"\n },\n "copied": {\n "type": "boolean"\n },\n "curved": {\n "type": "boolean"\n },\n "gap_bridged_px": {\n "type": "number"\n },\n "gap_sealed_px": {\n "type": "number"\n },\n "min_pass_px": {\n "type": "number"\n },\n "min_pass_delta": {\n "type": "number"\n },\n "door_wedges": {\n "type": "number"\n },\n "ring_interiors": {\n "type": "number"\n },\n "fill_sensitivity": {\n "type": "number"\n },\n "net_faces": {\n "type": "number"\n },\n "net_starved": {\n "type": "boolean"\n },\n "rule_id": {\n "type": "string"\n },\n "seed_shape_id": {\n "type": "string"\n },\n "container_shape_id": {\n "type": "string"\n },\n "cuts_shape_id": {\n "type": "string"\n },\n "proposal_id": {\n "type": "string"\n },\n "net_mode": {\n "type": "string"\n }\n },\n "required": [\n "method"\n ],\n "additionalProperties": true,\n "description": "Existing origin object. Method and actor are independent strings: manual + agent is valid. Known canvas methods include derived and net_v1 beyond the MCP type union. Unknown values remain unclassified; omission is not verified identity.",\n "allOf": [\n {\n "$ref": "review.schema.json#/$defs/state"\n }\n ]\n}\n'
15200
+ },
15201
+ {
15202
+ "uri": "takeoff://protocol/v1/review.schema.json",
15203
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/review.schema.json",
15204
+ "title": "Review (draft)",
15205
+ "source": "protocol/v1/review.schema.json",
15206
+ "sha256": "3d1f326ff85e3498521e81df0cc2f2758b998be803400a25c21c65014657f245",
15207
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/review.schema.json",\n "title": "Review (draft)",\n "description": "Existing review-state fragments and approval-family records. Structural validity is not proof of human action; only existing runtime authority may mint human review/approval.",\n "anyOf": [\n {\n "$ref": "#/$defs/shapeReview"\n },\n {\n "$ref": "#/$defs/verdict"\n }\n ],\n "$defs": {\n "state": {\n "type": "object",\n "properties": {\n "reviewed": {\n "type": "boolean"\n },\n "proposed_ts": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "accepted_ts": {\n "$ref": "common.schema.json#/$defs/timestamp"\n }\n },\n "additionalProperties": true,\n "description": "Inline origin fields. Absence stays absent; never synthesize acceptance."\n },\n "shapeReview": {\n "allOf": [\n {\n "$ref": "#/$defs/state"\n },\n {\n "type": "object",\n "required": [\n "reviewed"\n ]\n }\n ]\n },\n "verdict": {\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "minLength": 1\n },\n "actor": {\n "enum": [\n "estimator",\n "agent"\n ]\n },\n "ts": {\n "$ref": "common.schema.json#/$defs/timestamp"\n },\n "sheet_id": {\n "type": "string",\n "minLength": 1\n },\n "at": {\n "$ref": "common.schema.json#/$defs/point"\n },\n "shape_id": {\n "type": "string",\n "minLength": 1\n },\n "text": {\n "type": "string"\n }\n },\n "required": [\n "id",\n "actor",\n "sheet_id",\n "at"\n ],\n "additionalProperties": true,\n "description": "Human APPROVED seal or agent verdict. Imported records may lack ts. Transport of existing seals is not authority to mint one."\n }\n }\n}\n'
15208
+ },
15209
+ {
15210
+ "uri": "takeoff://protocol/v1/stitch.schema.json",
15211
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/stitch.schema.json",
15212
+ "title": "Composite surface (draft)",
15213
+ "source": "protocol/v1/stitch.schema.json",
15214
+ "sha256": "db9ea1dffe5fd2360e1d231efc86ba84309316ba9add72c5c0fafef006e76154",
15215
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/stitch.schema.json",\n "title": "Composite surface (draft)",\n "type": "object",\n "properties": {\n "id": {\n "type": "string",\n "pattern": "^stitch:"\n },\n "name": {\n "type": "string"\n },\n "members": {\n "type": "array",\n "items": {\n "type": "object",\n "properties": {\n "key": {\n "type": "string",\n "minLength": 1\n },\n "dx": {\n "type": "number"\n },\n "dy": {\n "type": "number"\n }\n },\n "required": [\n "key",\n "dx",\n "dy"\n ],\n "additionalProperties": true\n },\n "minItems": 2,\n "maxItems": 4\n },\n "created_at": {\n "$ref": "common.schema.json#/$defs/timestamp"\n }\n },\n "required": [\n "id",\n "name",\n "members"\n ],\n "additionalProperties": true,\n "description": "Persisted browser stitch. dx/dy are logical image-pixel offsets. Member uniqueness, source availability, scale compatibility, and alignment require semantic checks; schema validity does not enable MCP stitching."\n}\n'
15216
+ },
15217
+ {
15218
+ "uri": "takeoff://protocol/v1/takeoff-document.schema.json",
15219
+ "id": "https://opentakeoff.kentucky-ai.com/protocol/v1/takeoff-document.schema.json",
15220
+ "title": "TakeoffDocument v1 (draft)",
15221
+ "source": "protocol/v1/takeoff-document.schema.json",
15222
+ "sha256": "9c7800a9423a12e9334cda507fdd3c216205a4304e220df376dd67753accbd69",
15223
+ "schema_json": '{\n "$schema": "https://json-schema.org/draft/2020-12/schema",\n "$id": "https://opentakeoff.kentucky-ai.com/protocol/v1/takeoff-document.schema.json",\n "title": "TakeoffDocument v1 (draft)",\n "description": "Proposed document identifier over the existing persisted record shape. No application writer emits this format yet; validation is structural, not migration, referential integrity, authority, or a complete evidence audit.",\n "allOf": [\n {\n "$ref": "document-fields.schema.json"\n }\n ],\n "type": "object",\n "properties": {\n "schema": {\n "const": "opentakeoff.takeoff-document.v1"\n }\n },\n "required": [\n "schema",\n "conditions",\n "shapes",\n "sheets"\n ]\n}\n'
15224
+ }
15225
+ ];
15226
+
15134
15227
  // src/resources.ts
15135
15228
  function toBase64(bytes) {
15136
15229
  return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
@@ -15154,6 +15247,19 @@ function registerResources(server, session) {
15154
15247
  ${page.text}`
15155
15248
  }] }));
15156
15249
  }
15250
+ server.registerResource("protocol-index", "takeoff://protocol", {
15251
+ title: "Takeoff Protocol resource index",
15252
+ description: "Draft TakeoffDocument and legacy schema registry, scope, coordinates, and limits.",
15253
+ mimeType: "application/json"
15254
+ }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "application/json", text: PROTOCOL_INDEX_JSON }] }));
15255
+ for (const schema of PROTOCOL_SCHEMAS) {
15256
+ const resourceName = `protocol-${schema.source.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}`;
15257
+ server.registerResource(resourceName, schema.uri, {
15258
+ title: schema.title,
15259
+ description: `Draft JSON Schema for ${schema.title}; see takeoff://protocol for scope and identity.`,
15260
+ mimeType: "application/schema+json"
15261
+ }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "application/schema+json", text: schema.schema_json }] }));
15262
+ }
15157
15263
  const sheetEntries = (suffix, mimeType, what) => () => ({
15158
15264
  resources: session.sheetList().map((s) => ({
15159
15265
  uri: `takeoff://sheet/${s.ord}${suffix}`,
@@ -15387,7 +15493,7 @@ function nameTheStageInRefusals(server) {
15387
15493
  // package.json
15388
15494
  var package_default = {
15389
15495
  name: "opentakeoff-mcp",
15390
- version: "0.9.81",
15496
+ version: "0.9.83",
15391
15497
  mcpName: "io.github.Kentucky-ai/opentakeoff",
15392
15498
  type: "module",
15393
15499
  description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
@@ -15398,14 +15504,16 @@ var package_default = {
15398
15504
  scripts: {
15399
15505
  dev: "node --import tsx server.ts",
15400
15506
  start: "node dist/server.js",
15401
- build: "npm run check:wiki && esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
15507
+ build: "npm run check:protocol-resources && npm run check:wiki && esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
15402
15508
  "smoke:dist": "node scripts/smoke-dist.mjs",
15403
15509
  mcpb: "npm run build && node scripts/build-mcpb.mjs",
15404
15510
  prepublishOnly: "npm run typecheck && npm test && npm run build",
15405
15511
  typecheck: "tsc --noEmit",
15512
+ "check:protocol-resources": "node scripts/check-protocol-resources.mjs",
15406
15513
  "check:wiki": "node scripts/check-wiki.mjs",
15514
+ "check:versions": "node ../scripts/check-version-consistency.mjs",
15407
15515
  "check:tool-count": "node --import tsx scripts/check-tool-count.mjs",
15408
- test: "node --import tsx --test test/conformance.test.ts test/context.test.ts test/dxf.test.ts test/e2e.test.ts test/gate.test.ts test/graphEvalRowsym.test.ts test/labels.test.ts test/mep.test.ts test/overlap.test.ts test/parity.test.ts test/proposals.test.ts test/raster.test.ts test/resources.test.ts test/safewrite.test.ts test/scalewarn.test.ts test/scope.test.ts test/session.test.ts test/staging.test.ts test/sweepguard.test.ts test/tools.test.ts test/transitions.test.ts test/twins.test.ts test/view.test.ts test/wiki.test.ts"
15516
+ test: "node --import tsx --test test/checkers.test.ts test/conformance.test.ts test/context.test.ts test/dxf.test.ts test/e2e.test.ts test/gate.test.ts test/graphEvalRowsym.test.ts test/labels.test.ts test/mep.test.ts test/overlap.test.ts test/parity.test.ts test/proposals.test.ts test/raster.test.ts test/resources.test.ts test/safewrite.test.ts test/scalewarn.test.ts test/scope.test.ts test/session.test.ts test/staging.test.ts test/sweepguard.test.ts test/tools.test.ts test/transitions.test.ts test/twins.test.ts test/view.test.ts test/wiki.test.ts"
15409
15517
  },
15410
15518
  dependencies: {
15411
15519
  "@modelcontextprotocol/sdk": "^1.12.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentakeoff-mcp",
3
- "version": "0.9.81",
3
+ "version": "0.9.83",
4
4
  "mcpName": "io.github.Kentucky-ai/opentakeoff",
5
5
  "type": "module",
6
6
  "description": "OpenTakeoff MCP server — drive the takeoff engine from your MCP client over stdio.",
@@ -11,14 +11,16 @@
11
11
  "scripts": {
12
12
  "dev": "node --import tsx server.ts",
13
13
  "start": "node dist/server.js",
14
- "build": "npm run check:wiki && esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
14
+ "build": "npm run check:protocol-resources && npm run check:wiki && esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
15
15
  "smoke:dist": "node scripts/smoke-dist.mjs",
16
16
  "mcpb": "npm run build && node scripts/build-mcpb.mjs",
17
17
  "prepublishOnly": "npm run typecheck && npm test && npm run build",
18
18
  "typecheck": "tsc --noEmit",
19
+ "check:protocol-resources": "node scripts/check-protocol-resources.mjs",
19
20
  "check:wiki": "node scripts/check-wiki.mjs",
21
+ "check:versions": "node ../scripts/check-version-consistency.mjs",
20
22
  "check:tool-count": "node --import tsx scripts/check-tool-count.mjs",
21
- "test": "node --import tsx --test test/conformance.test.ts test/context.test.ts test/dxf.test.ts test/e2e.test.ts test/gate.test.ts test/graphEvalRowsym.test.ts test/labels.test.ts test/mep.test.ts test/overlap.test.ts test/parity.test.ts test/proposals.test.ts test/raster.test.ts test/resources.test.ts test/safewrite.test.ts test/scalewarn.test.ts test/scope.test.ts test/session.test.ts test/staging.test.ts test/sweepguard.test.ts test/tools.test.ts test/transitions.test.ts test/twins.test.ts test/view.test.ts test/wiki.test.ts"
23
+ "test": "node --import tsx --test test/checkers.test.ts test/conformance.test.ts test/context.test.ts test/dxf.test.ts test/e2e.test.ts test/gate.test.ts test/graphEvalRowsym.test.ts test/labels.test.ts test/mep.test.ts test/overlap.test.ts test/parity.test.ts test/proposals.test.ts test/raster.test.ts test/resources.test.ts test/safewrite.test.ts test/scalewarn.test.ts test/scope.test.ts test/session.test.ts test/staging.test.ts test/sweepguard.test.ts test/tools.test.ts test/transitions.test.ts test/twins.test.ts test/view.test.ts test/wiki.test.ts"
22
24
  },
23
25
  "dependencies": {
24
26
  "@modelcontextprotocol/sdk": "^1.12.0",