Reset core/viewer/editor/mcp packages and apps/editor from private-editor

Wholesale swap of packages/{core,viewer,editor,mcp} and apps/editor with the
versions from the private editor repo, which is the production source of truth.

Setup changes:
- packages/{core,viewer,editor} versions held at 0.7.0 baseline (matching
  the most recent published release) so a bump=minor publishes 0.8.0
- packages/mcp held at 0.1.1 (never published; first publish will go through
  the new release.yml flow)
- peerDependencies and devDependencies for inter-package @pascal-app/*
  references pinned to ^0.7.0 instead of '*' / 'workspace:*' so they are
  valid for npm consumers
- Root package.json: TypeScript bumped to 6.0.2, added overrides for
  @types/react, @types/react-dom, @types/three to prevent JSX namespace
  fragmentation across the workspace
- release.yml extended to also publish editor and mcp; 'both' option renamed
  to 'all'; added a sync step that updates inter-package peerDeps/devDeps to
  match the new versions on every bump (so viewer/editor/mcp tarballs always
  reference the version of core they were built against)
- Root scripts gained release:editor and release:mcp shortcuts

Verification:
- bun install --frozen-lockfile is consistent
- packages/{core,viewer,mcp} build cleanly, dist/index.d.ts emitted
- packages/editor check-types reports 21 pre-existing errors, identical to
  what private-editor currently reports

Open PRs against editor-v2 will need rebasing/conflict resolution.
This commit is contained in:
Wawa
2026-05-09 20:52:26 +00:00
parent 146f0a846f
commit e618175bba
196 changed files with 5899 additions and 3966 deletions
+66 -34
View File
@@ -2,58 +2,90 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import type { SceneOperations } from '../operations'
export const AGENT_GUIDE = [
'# Pascal MCP agent guide',
'# Pascal MCP Agent Guide',
'',
'Use this guide before inspecting application source code. The MCP surface is intended to expose the construction contract an agent needs for normal scene editing.',
'You are editing Pascal architectural projects. Use MCP tools only; do not inspect the Pascal repository unless the user explicitly asks.',
'',
'## Fast visible-progress workflow',
'## Standard Workflow',
'',
'1. Query `pascal://scene/current/summary` or `list_levels` to orient yourself.',
'2. Create visible massing first: `create_level` as needed, then `create_story_shell` once per story.',
'3. Add room semantics next: zones/rooms, interior walls, slabs, and ceilings. Prefer `create_room` for simple rooms and `apply_patch` only for exact multi-room partitions.',
'4. Add circulation and envelope details: `create_stair_between_levels`, then `add_door` and `add_window`.',
'5. Add `create_roof`, furniture with `furnish_room`/`place_item`, and exterior features such as fences, patios, driveways, lawns, and garden zones.',
'6. Run `validate_scene` and `verify_scene`; fix issues before handing off.',
'1. Read this guide or call `get_capabilities` if available.',
'2. If the user asks for a new project, call `create_project` first.',
'3. For quick starts, call `create_house_from_brief`. For precise edits, build with semantic tools: `create_story_shell`, `create_room`, `add_door`, `add_window`, `furnish_room`, `create_roof`, `place_item`.',
'4. Let semantic tools update the browser-visible draft. Call `save_scene` with `saveMode: "draft"` for autosave-style progress, or `saveMode: "checkpoint"` only for meaningful milestones.',
'5. Call `validate_scene`, `verify_scene`, then `get_project_status`.',
'6. Return the final `editorUrl` from tool output. Do not infer routes.',
'',
'This sequence lets users see a recognizable building quickly instead of waiting for one large hidden planning pass.',
'## Important Concepts',
'',
'## Construction rules',
'- A project is the browser-visible container.',
'- A scene graph is the architectural model.',
'- A draft is the browser-visible working model and may be overwritten many times.',
'- A version/checkpoint is a meaningful saved model revision.',
'- The browser editor opens the current draft when one exists, otherwise the published version.',
'- Always return the `editorUrl`, not an internal API URL.',
'',
'- Levels live under a Building.',
'- Walls, fences, zones, slabs, ceilings, roofs, and stairs live under a Level.',
'- Doors and windows live under their Wall. Use `add_door`/`add_window`; their `t` or `position` is 0..1 along the wall.',
'- Floor items live under a Level; wall/ceiling-attached items live under their target Wall or Ceiling.',
'- For multi-story buildings, create separate level-owned exterior walls for each story. Do not make first-story walls taller to represent upper-story bearing walls.',
'- Use `create_story_shell` once per floor/story to avoid cross-level wall ownership mistakes.',
'- Use `create_stair_between_levels` for stairs. It creates a straight stair and one rectangular manual slab/ceiling opening while disabling automatic stair-opening mode, avoiding duplicate or irregular holes.',
'- Roofs are containers with roof segments and should be isolated on a dedicated roof level for solo/exploded level views. Use `create_roof`; by default it creates a roof level above the reference occupied level. Do not attach roofs directly to the top occupied floor unless explicitly requested.',
'- Story count means occupied stories, not raw level count. A two-story house may correctly have three levels when the third level has metadata role `roof`; do not delete roof/support levels to satisfy a requested story count.',
'- Use `pascal://constraints/{levelId}` when you need existing slab holes or wall footprints for precise placement.',
'## URLs',
'',
'## Scene model facts exposed here so agents do not need repo inspection',
'- Use `editorUrl` returned by tools.',
'- If a tool returns only an id, call `get_project_status` to get the browser URL.',
'- Hosted editor URLs use `/editor/<projectId>`.',
'',
'## Scene Creation Rules',
'',
'- Prefer semantic tools over raw graph patches.',
'- Do not hand-write node graphs unless no semantic tool exists.',
'- For rooms, use `create_room` -> `add_door` -> `add_window` -> `furnish_room`.',
'- For complete homes, create exterior shell, interior rooms, openings, roof, furniture, then landscaping.',
'- For doors/windows, use `t` or `position` from 0 to 1 along the wall unless a tool explicitly says otherwise.',
'- X/Z are floor-plan axes and Y is vertical; dimensions are meters.',
'- A story wall height is normally 2.4-3.0m; wall thickness is normally 0.1-0.3m.',
'- Slab and ceiling holes are polygon arrays. Manual stair openings should have `holeMetadata` with source `manual` and a single rectangular polygon.',
'- Dedicated roof levels use metadata role `roof` and normally contain the roof only; the top occupied level keeps its own walls, rooms, slabs, and ceiling.',
'- `verify_scene` reports `occupiedStoryCount`, `supportLevelCount`, and `roofLevelIds`; use those fields instead of `levelCount` when checking story-count requirements.',
'- Saved site children can contain embedded building objects for compatibility, but tools handle parent/child bookkeeping. Prefer tools over raw graph surgery for common construction.',
'- `validate_scene` checks schema correctness. `verify_scene` checks practical layout issues such as empty levels, missing rooms/floors/doors, bad openings, stair obstructions, and suspicious multi-story wall heights.',
'- Use `create_roof` for roofs. A dedicated roof support level is valid and should not count as an occupied story.',
'',
'## Tool preference',
'## Required Final Checks',
'',
'- Prefer semantic tools first: `create_story_shell`, `create_room`, `add_door`, `add_window`, `create_stair_between_levels`, `create_roof`, `furnish_room`, `place_item`.',
'- Use `apply_patch` for bulk exact edits after semantic tools have established the main structure.',
'- `save_scene` must succeed. Use `saveMode: "checkpoint"` before final handoff only if the user asked for a durable version.',
'- `verify_scene.hasIssues` should be false; otherwise explain remaining issues.',
'- `get_project_status.nodeCount` must be greater than 0 for a non-empty design.',
'- Return `editorUrl` in the final user response.',
'',
'## If Something Looks Empty',
'',
'1. Call `get_project_status`.',
'2. Compare `publishedVersion`, `latestVersion`, `browserVisibleVersion`, `nodeCount`, and `graphHash`.',
'3. If the graph is non-empty but the browser appears empty, call `get_project_status` to re-bind the session, then `save_scene` with `saveMode: "draft"`.',
'4. Re-run `verify_scene`.',
'',
'## Output Contract',
'',
'Final user response should include: project name, `editorUrl`, version, node/room summary, and any known limitations.',
].join('\n')
export function registerAgentGuide(server: McpServer, _bridge: SceneOperations): void {
server.registerResource(
'agent-guide',
'pascal://agent-guide',
{
title: 'Pascal MCP agent guide',
description:
'Short MCP-first project creation, save/publish, validation, and output workflow for external agents.',
mimeType: 'text/markdown',
},
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: 'text/markdown',
text: AGENT_GUIDE,
},
],
}),
)
server.registerResource(
'agent-guide-legacy',
'pascal://agent/guide',
{
title: 'Agent construction guide',
description:
'MCP-first construction workflow, scene invariants, and tool preferences so agents do not need to inspect the Pascal codebase.',
title: 'Pascal MCP agent guide',
description: 'Legacy URI for the Pascal MCP agent guide. Prefer pascal://agent-guide.',
mimeType: 'text/markdown',
},
async (uri) => ({
+2 -1
View File
@@ -14,7 +14,8 @@ import { registerSceneSummary } from './scene-summary'
* - `pascal://scene/current/summary` — text/markdown, human summary
* - `pascal://catalog/items` — application/json, host-supplied catalog
* - `pascal://constraints/{levelId}` — application/json, per-level constraints
* - `pascal://agent/guide` — text/markdown, MCP-first construction guide
* - `pascal://agent-guide` — text/markdown, MCP-first agent guide
* - `pascal://agent/guide` — text/markdown, legacy alias
*/
export function registerResources(server: McpServer, operations: SceneOperations): void {
registerAgentGuide(server, operations)
+19 -8
View File
@@ -201,21 +201,32 @@ describe('pascal://catalog/items', () => {
})
})
describe('pascal://agent/guide', () => {
describe('pascal://agent-guide', () => {
beforeEach(() => resetScene())
test('returns MCP-first construction guidance', async () => {
test('returns MCP-first project guidance', async () => {
const pair = await spinUp(registerAgentGuide)
try {
const res = await pair.client.readResource({ uri: 'pascal://agent/guide' })
const res = await pair.client.readResource({ uri: 'pascal://agent-guide' })
const content = res.contents[0] as { uri: string; mimeType?: string; text?: string }
expect(content.mimeType).toBe('text/markdown')
const text = content.text ?? ''
expect(text).toContain('create_story_shell')
expect(text).toContain('create_stair_between_levels')
expect(text).toContain('dedicated roof level')
expect(text).toContain('Do not make first-story walls taller')
expect(text).toContain('Run `validate_scene` and `verify_scene`')
expect(text).toContain('create_project')
expect(text).toContain('save_scene')
expect(text).toContain('get_project_status')
expect(text).toContain('editorUrl')
expect(text).toContain('0 to 1 along the wall')
} finally {
await pair.close()
}
})
test('keeps the legacy agent guide URI as an alias', async () => {
const pair = await spinUp(registerAgentGuide)
try {
const res = await pair.client.readResource({ uri: 'pascal://agent/guide' })
const text = (res.contents[0] as { text?: string }).text ?? ''
expect(text).toContain('Pascal MCP Agent Guide')
} finally {
await pair.close()
}
+2 -2
View File
@@ -11,7 +11,7 @@ function polygonArea(poly: Poly2D): number {
for (let i = 0; i < poly.length; i++) {
const a = poly[i]
const b = poly[(i + 1) % poly.length]
if (!a || !b) continue
if (!(a && b)) continue
sum += a[0] * b[1] - b[0] * a[1]
}
return Math.abs(sum) / 2
@@ -32,7 +32,7 @@ function emptyBBox(): BBox {
}
function expandBBox(bbox: BBox, x: number, y: number, z: number): void {
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return
if (!(Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z))) return
bbox.empty = false
if (x < bbox.min[0]) bbox.min[0] = x
if (y < bbox.min[1]) bbox.min[1] = y