pi-revit 0.3.1 → 0.5.0

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.
Files changed (120) hide show
  1. package/AGENTS.md +167 -0
  2. package/CHANGELOG.md +114 -42
  3. package/README.md +598 -138
  4. package/bin/pi-revit.js +9 -9
  5. package/docs/architecture.md +271 -0
  6. package/docs/evaluation.md +434 -0
  7. package/docs/invariants.json +147 -0
  8. package/extensions/pi-revit/completion-monitor.ts +55 -0
  9. package/extensions/pi-revit/contracts.ts +146 -0
  10. package/extensions/pi-revit/discovery.ts +93 -0
  11. package/extensions/pi-revit/index.ts +231 -85
  12. package/extensions/pi-revit/instance-router.ts +86 -0
  13. package/extensions/pi-revit/platform-prompt.ts +40 -0
  14. package/extensions/pi-revit/scope-monitor.ts +114 -0
  15. package/extensions/pi-revit/script-library.ts +146 -0
  16. package/extensions/pi-revit/tool-catalog.ts +166 -0
  17. package/extensions/pi-revit/tool-documentation.ts +72 -0
  18. package/extensions/pi-revit/tool-schema.ts +8 -0
  19. package/package.json +65 -59
  20. package/scripts/build.ps1 +9 -9
  21. package/scripts/check-sdk.ps1 +66 -66
  22. package/scripts/check-tool-documentation.mjs +287 -0
  23. package/scripts/deploy.ps1 +16 -16
  24. package/scripts/generate-contracts.mjs +80 -0
  25. package/scripts/lib/platform.mjs +226 -0
  26. package/scripts/test-extension.mjs +15 -0
  27. package/skills/pi-revit/SKILL.md +39 -63
  28. package/skills/pi-revit/contracts.generated.json +3524 -0
  29. package/skills/pi-revit/references/execution-rules.md +41 -0
  30. package/skills/pi-revit/references/model-audit-export.md +40 -0
  31. package/skills/pi-revit/references/operation-recovery.md +33 -0
  32. package/skills/pi-revit/references/room-documentation.md +39 -0
  33. package/skills/pi-revit/references/tool-index.md +89 -0
  34. package/skills/pi-revit/references/tools/capture_view.md +62 -0
  35. package/skills/pi-revit/references/tools/change_element_types.md +65 -0
  36. package/skills/pi-revit/references/tools/create_tags.md +85 -0
  37. package/skills/pi-revit/references/tools/delete_elements.md +66 -0
  38. package/skills/pi-revit/references/tools/execute_csharp.md +81 -0
  39. package/skills/pi-revit/references/tools/export_documents.md +75 -0
  40. package/skills/pi-revit/references/tools/find_revit_tools.md +96 -0
  41. package/skills/pi-revit/references/tools/get_element_details.md +66 -0
  42. package/skills/pi-revit/references/tools/get_element_relationships.md +61 -0
  43. package/skills/pi-revit/references/tools/get_element_types.md +67 -0
  44. package/skills/pi-revit/references/tools/get_elements.md +87 -0
  45. package/skills/pi-revit/references/tools/get_linked_elements.md +79 -0
  46. package/skills/pi-revit/references/tools/get_linked_models.md +57 -0
  47. package/skills/pi-revit/references/tools/get_model_coordinates.md +64 -0
  48. package/skills/pi-revit/references/tools/get_model_health.md +53 -0
  49. package/skills/pi-revit/references/tools/get_model_overview.md +57 -0
  50. package/skills/pi-revit/references/tools/get_revit_operation.md +54 -0
  51. package/skills/pi-revit/references/tools/get_schedule_fields.md +62 -0
  52. package/skills/pi-revit/references/tools/get_schedules.md +71 -0
  53. package/skills/pi-revit/references/tools/manage_element_sets.md +92 -0
  54. package/skills/pi-revit/references/tools/manage_revit_instances.md +63 -0
  55. package/skills/pi-revit/references/tools/manage_revit_scripts.md +109 -0
  56. package/skills/pi-revit/references/tools/manage_schedules.md +90 -0
  57. package/skills/pi-revit/references/tools/manage_selection.md +66 -0
  58. package/skills/pi-revit/references/tools/manage_sheet_placements.md +82 -0
  59. package/skills/pi-revit/references/tools/manage_sheets.md +71 -0
  60. package/skills/pi-revit/references/tools/manage_views.md +95 -0
  61. package/skills/pi-revit/references/tools/measure_geometry.md +71 -0
  62. package/skills/pi-revit/references/tools/open_view.md +59 -0
  63. package/skills/pi-revit/references/tools/ping.md +41 -0
  64. package/skills/pi-revit/references/tools/query_spatial_elements.md +74 -0
  65. package/skills/pi-revit/references/tools/read_revit_result.md +53 -0
  66. package/skills/pi-revit/references/tools/search_api_docs.md +65 -0
  67. package/skills/pi-revit/references/tools/set_parameters.md +75 -0
  68. package/skills/pi-revit/references/tools/summarize_elements.md +64 -0
  69. package/skills/pi-revit/references/tools/transform_elements.md +79 -0
  70. package/skills/pi-revit/references/visual-verification.md +36 -0
  71. package/skills/pi-revit/tool-manifest.json +338 -0
  72. package/src/Revit/BridgeServer.cs +75 -19
  73. package/src/Revit/OperationStore.cs +178 -0
  74. package/src/Revit/ToolRegistry.cs +61 -8
  75. package/src/Revit/Tools/CaptureView.cs +9 -0
  76. package/src/Revit/Tools/ChangeElementTypes.cs +74 -0
  77. package/src/Revit/Tools/ChangeSet.cs +39 -0
  78. package/src/Revit/Tools/CreateTags.cs +107 -0
  79. package/src/Revit/Tools/DeleteElements.cs +53 -0
  80. package/src/Revit/Tools/DocumentGuard.cs +12 -2
  81. package/src/Revit/Tools/ElementNames.cs +103 -0
  82. package/src/Revit/Tools/ElementQueryScope.cs +27 -0
  83. package/src/Revit/Tools/ElementTraits.cs +53 -0
  84. package/src/Revit/Tools/ExecuteCsharp.cs +26 -7
  85. package/src/Revit/Tools/ExportDocuments.cs +9 -0
  86. package/src/Revit/Tools/FailureGuard.cs +26 -26
  87. package/src/Revit/Tools/GetElementDetails.cs +28 -2
  88. package/src/Revit/Tools/GetElementRelationships.cs +82 -0
  89. package/src/Revit/Tools/GetElementTypes.cs +8 -0
  90. package/src/Revit/Tools/GetElements.cs +58 -55
  91. package/src/Revit/Tools/GetLinkedElements.cs +89 -0
  92. package/src/Revit/Tools/GetLinkedModels.cs +73 -0
  93. package/src/Revit/Tools/GetModelCoordinates.cs +56 -0
  94. package/src/Revit/Tools/GetModelHealth.cs +7 -0
  95. package/src/Revit/Tools/GetModelOverview.cs +187 -160
  96. package/src/Revit/Tools/GetScheduleFields.cs +44 -0
  97. package/src/Revit/Tools/GetSchedules.cs +96 -0
  98. package/src/Revit/Tools/InheritedState.Summary.cs +57 -0
  99. package/src/Revit/Tools/InheritedState.cs +144 -0
  100. package/src/Revit/Tools/ManageElementSets.cs +114 -0
  101. package/src/Revit/Tools/ManageSchedules.cs +174 -0
  102. package/src/Revit/Tools/ManageSelection.cs +9 -0
  103. package/src/Revit/Tools/ManageSheetPlacements.cs +113 -0
  104. package/src/Revit/Tools/ManageSheets.cs +72 -0
  105. package/src/Revit/Tools/ManageViews.cs +115 -0
  106. package/src/Revit/Tools/MeasureGeometry.cs +60 -0
  107. package/src/Revit/Tools/ModelChanges.cs +154 -0
  108. package/src/Revit/Tools/ModelEditBatch.cs +105 -0
  109. package/src/Revit/Tools/ModelEditInputs.cs +49 -0
  110. package/src/Revit/Tools/OpenView.cs +8 -0
  111. package/src/Revit/Tools/ParameterResolver.cs +94 -0
  112. package/src/Revit/Tools/QuerySpatialElements.cs +70 -0
  113. package/src/Revit/Tools/SearchApiDocs.cs +72 -4
  114. package/src/Revit/Tools/SetParameters.cs +50 -119
  115. package/src/Revit/Tools/SpatialBounds.cs +30 -0
  116. package/src/Revit/Tools/SummarizeElements.cs +94 -0
  117. package/src/Revit/Tools/ToolContract.cs +48 -0
  118. package/src/Revit/Tools/ToolSupport.cs +4 -0
  119. package/src/Revit/Tools/TransformElements.cs +73 -0
  120. package/workspace/AGENTS.md +54 -48
@@ -0,0 +1,226 @@
1
+ // Shared offline platform helpers for the contract generator and the documentation
2
+ // checker. Nothing here contacts Revit, the network or a live model: bridge metadata
3
+ // comes from the real C# registry compiled with stubbed tool bodies, and the Pi
4
+ // extension is registered against an isolated fake bridge.
5
+ import { createRequire } from 'node:module';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { existsSync } from 'node:fs';
8
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
14
+ export const skillRoot = path.join(root, 'skills', 'pi-revit');
15
+ export const manualRoot = path.join(skillRoot, 'references', 'tools');
16
+ export const snapshotPath = path.join(skillRoot, 'contracts.generated.json');
17
+ export const manifestPath = path.join(skillRoot, 'tool-manifest.json');
18
+ export const toolIndexPath = path.join(skillRoot, 'references', 'tool-index.md');
19
+
20
+ export function resolvePiDependencies() {
21
+ const candidates = [process.env.PI_CODING_AGENT_PATH,
22
+ path.join(path.dirname(process.execPath), 'node_modules', '@earendil-works', 'pi-coding-agent')].filter(Boolean);
23
+ for (const candidate of candidates) {
24
+ const require = createRequire(path.join(path.resolve(candidate), 'package.json'));
25
+ try { require.resolve('jiti'); require.resolve('typebox/value'); return require; } catch {}
26
+ }
27
+ throw new Error('Set PI_CODING_AGENT_PATH to an existing @earendil-works/pi-coding-agent directory containing jiti and typebox. These checks never install dependencies.');
28
+ }
29
+
30
+ export function createLoader(piRequire) {
31
+ const { createJiti } = piRequire('jiti');
32
+ return createJiti(import.meta.url, { alias: { typebox: piRequire.resolve('typebox') }, moduleCache: false });
33
+ }
34
+
35
+ /** Real ToolRegistry.DescribeAll() output, via the Roslyn schema extractor. */
36
+ export async function extractBridgeDescriptors() {
37
+ const scratch = await mkdtemp(path.join(os.tmpdir(), 'pi-revit-contracts-'));
38
+ try {
39
+ const output = path.join(scratch, 'bridge.json');
40
+ const run = spawnSync('dotnet', ['run', '--project', path.join(root, 'tests', 'tool-documentation', 'schema-extractor.csproj'), '--', root, output],
41
+ { cwd: root, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024, env: { ...process.env, DOTNET_CLI_TELEMETRY_OPTOUT: '1', DOTNET_SKIP_FIRST_TIME_EXPERIENCE: '1' } });
42
+ if (run.error || run.status !== 0)
43
+ throw new Error(`Bridge schema extraction failed (installed .NET SDK 8+ required).\n${run.error?.message ?? ''}\n${run.stdout}\n${run.stderr}`);
44
+ return JSON.parse(await readFile(output, 'utf8'));
45
+ } finally { await rm(scratch, { recursive: true, force: true }); }
46
+ }
47
+
48
+ /**
49
+ * Register the real extension against an in-memory bridge that serves `descriptors`.
50
+ * Returns the registrations and event handlers. Every non-metadata route and all
51
+ * real network access are rejected, and no registered execute() is invoked.
52
+ */
53
+ export async function registerExtension(loader, descriptors, addinVersion) {
54
+ const scratch = await mkdtemp(path.join(os.tmpdir(), 'pi-revit-register-'));
55
+ const original = { fetch: globalThis.fetch, appdata: process.env.APPDATA, setInterval: globalThis.setInterval };
56
+ process.env.APPDATA = scratch;
57
+ await mkdir(path.join(scratch, 'RevitBridge'));
58
+ await writeFile(path.join(scratch, 'RevitBridge', 'bridge.json'), JSON.stringify({ baseUrl: 'http://platform-fixture.invalid', token: 'offline-fixture' }));
59
+ const routes = [];
60
+ globalThis.fetch = async (input, init) => {
61
+ const url = new URL(typeof input === 'string' || input instanceof URL ? input : input.url);
62
+ if (url.origin !== 'http://platform-fixture.invalid') throw new Error('real network access is forbidden');
63
+ if ((init?.method ?? 'GET') !== 'GET' || !['/ping', '/tools'].includes(url.pathname)) throw new Error(`tool execution is forbidden: ${url.pathname}`);
64
+ routes.push(url.pathname);
65
+ return new Response(JSON.stringify(url.pathname === '/tools' ? { tools: descriptors } : { ok: true, addinVersion }));
66
+ };
67
+ globalThis.setInterval = () => { throw new Error('Background polling is forbidden in platform fixtures'); };
68
+ const registered = new Map(), events = new Map();
69
+ let active = [];
70
+ const pi = {
71
+ registerTool(tool) { if (registered.has(tool.name)) throw new Error(`duplicate registered tool ${tool.name}`); registered.set(tool.name, tool); active = [...active, tool.name]; },
72
+ on(name, handler) { events.set(name, handler); },
73
+ getActiveTools() { return [...active]; },
74
+ setActiveTools(names) { active = [...names]; },
75
+ sendMessage() { throw new Error('Messages are forbidden in platform fixtures'); },
76
+ };
77
+ const restore = async () => {
78
+ await events.get('session_shutdown')?.({}, {});
79
+ globalThis.fetch = original.fetch; globalThis.setInterval = original.setInterval;
80
+ if (original.appdata === undefined) delete process.env.APPDATA; else process.env.APPDATA = original.appdata;
81
+ await rm(scratch, { recursive: true, force: true });
82
+ };
83
+ try {
84
+ const { default: connector } = await loader.import(path.join(root, 'extensions', 'pi-revit', 'index.ts'));
85
+ await connector(pi);
86
+ } catch (error) { await restore(); throw error; }
87
+ return { pi, registered, events, routes, activeTools: () => [...active], restore };
88
+ }
89
+
90
+ /** Human-readable type, requiredness, default and allowed values of one JSON schema property. */
91
+ function describeProperty(schema) {
92
+ const values = [];
93
+ let type = schema?.type;
94
+ if (Array.isArray(schema?.enum)) values.push(...schema.enum);
95
+ for (const variant of schema?.anyOf ?? schema?.oneOf ?? []) {
96
+ if (variant.const !== undefined) { values.push(variant.const); type ??= typeof variant.const; }
97
+ else if (variant.type) type = type ? `${type} | ${variant.type}` : variant.type;
98
+ }
99
+ if (schema?.const !== undefined) values.push(schema.const);
100
+ if (type === 'array') {
101
+ const items = schema.items ?? {};
102
+ const itemValues = Array.isArray(items.enum) ? items.enum : (items.anyOf ?? []).filter(v => v.const !== undefined).map(v => v.const);
103
+ values.push(...itemValues);
104
+ type = `array of ${items.type ?? (itemValues.length ? typeof itemValues[0] : 'value')}`;
105
+ }
106
+ if (Array.isArray(type)) type = type.join(' | ');
107
+ return { type: type ?? 'value', values, default: schema?.default };
108
+ }
109
+
110
+ const VERIFICATION_TEXT = {
111
+ reread: 'query the changed state again with a read tool',
112
+ capture: 'capture the visible result and inspect the image',
113
+ inspect_output: 'open and inspect the produced file',
114
+ none: 'no durable outcome to check; report what was done',
115
+ };
116
+ const cell = text => String(text).replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
117
+
118
+ export function alternativeText(limit) {
119
+ const ref = limit.alternative.ref ? cell(limit.alternative.ref) : '';
120
+ switch (limit.alternative.kind) {
121
+ case 'tool': return `Tool: ${ref}`;
122
+ case 'api': return `Revit API: ${ref}. ${limit.lookup ? `Check all its members in one call: \`search_api_docs\` with query \`${cell(limit.lookup.query)}\`` : 'Check with `search_api_docs`'}, then use \`execute_csharp\` within the requested scope.`;
123
+ case 'user': return `User action: ${ref}`;
124
+ case 'revit_unsupported': return `Not offered by the Revit API (${ref}). Report this with the evidence checked.`;
125
+ default: return ref;
126
+ }
127
+ }
128
+
129
+ export const CONTRACT_START = '<!-- generated:contract:start (npm run generate:contracts; do not edit this block) -->';
130
+ export const CONTRACT_END = '<!-- generated:contract:end -->';
131
+
132
+ export function renderManualBlock(tool) {
133
+ const lines = [CONTRACT_START, '## Contract (generated)', ''];
134
+ const tier = tool.source === 'native' ? 'Pi extension utility, always active'
135
+ : tool.tier === 'advanced' ? 'bridge tool, advanced tier: activate it with `find_revit_tools`' : 'bridge tool, core tier: active by default';
136
+ lines.push(`- **Source:** ${tier}.`);
137
+ lines.push(`- **Writes model:** ${tool.write ? 'yes' : 'no'}. **Effects:** ${tool.effects.length ? tool.effects.join(', ') : 'none'}. **Requires an open document:** ${tool.requires_document ? 'yes' : 'no'}.`);
138
+ if (tool.document_kinds?.length && tool.requires_document)
139
+ lines.push(`- **Works in:** ${tool.document_kinds.length > 1 ? 'project and family documents' : `${tool.document_kinds[0]} documents only; the bridge refuses other documents before running, with the route to use instead`}.`);
140
+ if (tool.verification) lines.push(`- **Verify the outcome:** \`${tool.verification}\`: ${VERIFICATION_TEXT[tool.verification]}.`);
141
+ lines.push(`- **Contract hash:** \`${tool.contract_hash}\`. \`find_revit_tools\` compares it with the selected bridge's live contract.`);
142
+ lines.push('', '| Input | Type | Required | Default | Allowed values |', '| --- | --- | --- | --- | --- |');
143
+ const schema = tool.parameters ?? {};
144
+ const required = new Set(schema.required ?? []);
145
+ const properties = Object.entries(schema.properties ?? {});
146
+ if (!properties.length) lines.push('| _(none)_ | | | | |');
147
+ for (const [name, property] of properties) {
148
+ const d = describeProperty(property);
149
+ lines.push(`| \`${name}\` | ${cell(d.type)} | ${required.has(name) ? 'yes' : 'no'} | ${d.default === undefined ? '' : `\`${cell(JSON.stringify(d.default))}\``} | ${d.values.map(v => `\`${cell(v)}\``).join(', ')} |`);
150
+ }
151
+ if (tool.source === 'bridge') lines.push('', 'Bridge calls also accept `_operation_id`, only to retry an identical earlier request (see [operation recovery](../operation-recovery.md)).');
152
+ if (tool.limits.length) {
153
+ lines.push('', '| Not covered by this tool | Use instead |', '| --- | --- |');
154
+ for (const limit of tool.limits) lines.push(`| ${cell(limit.what)} | ${alternativeText(limit)} |`);
155
+ }
156
+ lines.push(CONTRACT_END);
157
+ return lines.join('\n');
158
+ }
159
+
160
+ /** Replace the generated block, or insert it before the first "## Inputs" heading (else the second H2). */
161
+ export function applyManualBlock(markdown, block) {
162
+ const eol = markdown.includes('\r\n') ? '\r\n' : '\n';
163
+ const body = block.replace(/\n/g, eol);
164
+ const start = markdown.indexOf(CONTRACT_START), end = markdown.indexOf(CONTRACT_END);
165
+ if (start >= 0 && end > start) return markdown.slice(0, start) + body + markdown.slice(end + CONTRACT_END.length);
166
+ const headings = [...markdown.matchAll(/^## .*$/gm)];
167
+ const anchor = headings.find(h => /^## Inputs/.test(h[0])) ?? headings[1];
168
+ if (!anchor) return markdown.trimEnd() + eol + eol + body + eol;
169
+ return markdown.slice(0, anchor.index) + body + eol + eol + markdown.slice(anchor.index);
170
+ }
171
+
172
+ export const INDEX_START = '<!-- generated:tool-index:start (npm run generate:contracts; do not edit this block) -->';
173
+ export const INDEX_END = '<!-- generated:tool-index:end -->';
174
+
175
+ export function renderToolIndex(manifest, contracts) {
176
+ const byName = new Map(contracts.map(tool => [tool.name, tool]));
177
+ const lines = [INDEX_START];
178
+ for (const group of manifest.groups) {
179
+ lines.push('', `## ${group.title}`, '', '| Manual | Use | Tier | Verify |', '| --- | --- | --- | --- |');
180
+ for (const tool of manifest.tools.filter(entry => entry.group === group.id)) {
181
+ const contract = byName.get(tool.name);
182
+ const tier = tool.source === 'native' ? 'native' : contract?.tier ?? '';
183
+ lines.push(`| [${tool.name}](tools/${tool.name}.md) | ${cell(tool.summary)} | ${tier} | ${contract?.verification ?? ''} |`);
184
+ }
185
+ }
186
+ lines.push('', '## Workflows and shared guidance', '', '| Guide | Kind | Use |', '| --- | --- | --- |');
187
+ for (const guide of manifest.guidance) lines.push(`| [${guide.name}](${guide.path.replace(/^references\//, '')}) | ${guide.kind} | ${cell(guide.summary)} |`);
188
+ lines.push(INDEX_END);
189
+ return lines.join('\n');
190
+ }
191
+
192
+ export function applyIndexBlock(markdown, block) {
193
+ const eol = markdown.includes('\r\n') ? '\r\n' : '\n';
194
+ const body = block.replace(/\n/g, eol);
195
+ const start = markdown.indexOf(INDEX_START), end = markdown.indexOf(INDEX_END);
196
+ if (start < 0 || end < start) throw new Error(`tool-index.md needs ${INDEX_START} ... ${INDEX_END} markers`);
197
+ return markdown.slice(0, start) + body + markdown.slice(end + INDEX_END.length);
198
+ }
199
+
200
+ /**
201
+ * Score the discovery corpus against packaged documentation (no bridge needed).
202
+ * An entry passes when its expected tool or guidance appears in the top_n results,
203
+ * or, for expect_fallback, when the response carries the capability route.
204
+ */
205
+ export async function evaluateCorpus(loader, corpus, addinVersion) {
206
+ const fixture = await registerExtension(loader, [], addinVersion);
207
+ try {
208
+ const find = fixture.registered.get('find_revit_tools');
209
+ const results = [];
210
+ for (const entry of corpus.entries) {
211
+ const response = (await find.execute('corpus', { scope: 'documentation', query: entry.q, limit: corpus.top_n })).details;
212
+ const tools = response.tools.map(tool => tool.name), guides = response.guidance.map(guide => guide.name);
213
+ const pass = entry.expect_fallback ? Boolean(response.fallback) && response.match === 'none'
214
+ : entry.expect_guidance ? guides.slice(0, corpus.top_n).includes(entry.expect_guidance)
215
+ : tools.slice(0, corpus.top_n).includes(entry.expect);
216
+ results.push({ ...entry, pass, match: response.match, got: entry.expect_guidance ? guides : tools });
217
+ }
218
+ return results;
219
+ } finally { await fixture.restore(); }
220
+ }
221
+
222
+ /** Candidate RevitAPI.xml files for optional API-reference verification. */
223
+ export function revitApiXmlPaths() {
224
+ const roots = [process.env.REVIT_API_PATH, 'C:\\Program Files\\Autodesk\\Revit 2025', 'C:\\Program Files\\Autodesk\\Revit 2026', 'C:\\Program Files\\Autodesk\\Revit 2027'].filter(Boolean);
225
+ return roots.map(directory => path.join(directory, 'RevitAPI.xml')).filter(file => existsSync(file));
226
+ }
@@ -0,0 +1,15 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ // Run sequentially: fixtures replace fetch, timers and discovery paths in-process.
6
+ // Each module has a separate process and uses Node's test API directly.
7
+ const directory = new URL('../tests/result-contract/', import.meta.url);
8
+ for (const file of readdirSync(directory).filter(name => name.endsWith('.test.mjs')).sort()) {
9
+ process.stdout.write(`\n${file}\n`);
10
+ const result = spawnSync(process.execPath, [fileURLToPath(new URL(file, directory))], {
11
+ stdio: 'inherit', env: process.env,
12
+ });
13
+ if (result.error) throw result.error;
14
+ if (result.status !== 0) process.exit(result.status ?? 1);
15
+ }
@@ -1,63 +1,39 @@
1
- ---
2
- name: pi-revit
3
- description: Work with the open Autodesk Revit model through the Revit bridge tools (ping, get_model_overview, get_elements, get_element_details, get_element_types, manage_selection, open_view, set_parameters, search_api_docs, execute_csharp, capture_view, export_documents, get_model_health) and retrieve saved results with read_revit_result. Use when the user asks about the Revit project, its elements, parameters, selection, or wants to change, script, capture, or export the model.
4
- ---
5
-
6
- # Revit
7
-
8
- Work with the live Revit model. The bridge targets Revit 2025, 2026, and 2027; the 0.3.0 changes were tested live on Revit 2025. Bridge document tools require Revit running with a project open; `ping` and `search_api_docs` work without a document. `read_revit_result` reads an already saved result locally without contacting Revit.
9
-
10
- ## Tool selection
11
-
12
- | Task | Tool |
13
- |------|------|
14
- | Bridge alive? Which Revit version? | `ping` |
15
- | Orientation: project info, units, levels, grids, category counts | `get_model_overview` |
16
- | List or count elements of ANY category (walls, doors, rooms, sheets, views, ...) | `get_elements` |
17
- | Read parameter VALUES, location, bounding box, materials of specific elements | `get_element_details` |
18
- | List element types / family symbols; "used vs merely loaded" | `get_element_types` |
19
- | Read or change the user's selection; zoom; temporary isolate | `manage_selection` |
20
- | Put a view or sheet on the user's screen (activate it) | `open_view` |
21
- | Write parameter values; rename anything (levels, views, sheets, types) | `set_parameters` |
22
- | Look up Revit API classes/members/signatures | `search_api_docs` |
23
- | Everything else (create, delete, move, views, sheets, tagging, ...) | `execute_csharp` |
24
- | PNG snapshot of a view (visual QA) | `capture_view` (advanced) |
25
- | PDF/DWG/PNG/IFC file export | `export_documents` (advanced) |
26
- | Warnings / model quality audit | `get_model_health` (advanced) |
27
- | Continue a saved large tool result | `read_revit_result` (local, no Revit call) |
28
-
29
- Workflow guidance:
30
-
31
- - Call `get_model_overview` for the intended open model before changing it. Copy `project.documentId` unchanged into `expected_document_id` for `set_parameters`, `execute_csharp`, `export_documents`, `open_view`, and selection/zoom changes. `manage_selection` also requires it whenever `isolate_in_view: true`, even with action `get`. Pure reads may omit it; a supplied ID is always checked. A matching legacy `expected_document` title alone is insufficient.
32
- - The ID belongs to one open document in one bridge session. Refresh it after closing/reopening the model or restarting Revit. If the guard rejects a call, activate the intended model and obtain its overview again; do not blindly substitute the currently active model's ID.
33
- - `get_elements` is the listing/counting primitive (`count_only: true` for bare counts). It returns identity fields only (id, name, category, typeName, levelId); read parameter values with `get_element_details`. Prefer a `category` or `of_class` scope when filtering by a parameter's display name.
34
- - The selection pipeline is `get_elements` -> ids -> `manage_selection` (action `set`); there is no inline filter on selection.
35
- - `set_parameters` handles bulk parameter writes and renames (the `Name` parameter covers levels, views, sheets, types). It can commit a partially successful batch: inspect every failed update, `commitWarnings`, and the reported transaction outcome before describing what changed.
36
- - Parameter display names are LOCALIZED: in a non-English Revit UI, `Mark`, `Comments`, and every other display name appear under their translated names. When a display-name lookup or `parameter_names` filter finds nothing, or the document may be non-English, use the language-independent `BuiltInParameter` enum name instead (e.g. `ALL_MODEL_MARK` for Mark, `ALL_MODEL_INSTANCE_COMMENTS` for Comments) — `set_parameters`, `get_element_details.parameter_names`, and `get_elements` filter rules all accept them, and `get_element_details` reports each parameter's `builtInParameter` name for discovery.
37
- - Before writing `execute_csharp` code, verify unfamiliar classes/members with `search_api_docs` (works with no document open; first query builds the index and takes a few seconds). The top match carries its remarks, parameter docs, and returns inline, and every public API enum value is searchable — trust the result over guessing or web search; narrow the query to promote a different match into the top slot.
38
- - `export_documents` defaults to `Documents\pi-revit\Models\<model title>--<identity hash>\exports`. Use its returned `outputDir` and file paths as authoritative; do not construct a destination from the title or opaque `project.documentId`. Saved paths and cloud/server identities determine the folder; unsaved/unavailable identities use a session fallback. Save As can select a new folder. Pass `output_dir` when the user requests a different destination. Existing title-only folders remain untouched.
39
- - Large tool payloads return a `result_id`, `file_path`, and continuation instructions. Call `read_revit_result` with that ID and `offset: 0`, then follow `next_offset` until `has_more` is false. Concatenate `text` fragments in order; each fragment is not a standalone JSON result. Offsets count UTF-16 code units. IDs last for the current extension instance; after reload, use the returned absolute file path with `read` while the file exists. Retrieval does not replace the original query's pagination or remove its limits.
40
-
41
- ## execute_csharp playbook
42
-
43
- - Globals: `doc` (Document), `uidoc` (UIDocument), `uiapp` (UIApplication), and `Dump(value)` to record intermediates into the result's `dumps[]`.
44
- - The script runs inside one backend-owned transaction. Do not start another transaction on `doc` (sub-transactions are allowed). The tool checks commit status and attempts rollback on script failure; read its actual outcome instead of assuming rollback succeeded. Result projection can fail after a successful commit and report `returnValueError`. Filesystem and UI effects are separate from model rollback.
45
- - Scripts must be fully synchronous: `await`/`async` is rejected at compile time; never block on `Task.Result`/`.Wait()`.
46
- - Return primitives, strings, or anonymous objects/lists; raw Revit API objects are projected to compact shapes (Element -> `{id,name,category,typeName,levelId}`, ElementId -> number, XYZ -> `{x,y,z}`).
47
- - Lengths are internal units (decimal feet) — convert with `UnitUtils.ConvertToInternalUnits`/`ConvertFromInternalUnits`.
48
- - Common pitfalls: call `FamilySymbol.Activate()` before `NewFamilyInstance`; use collector-level filtering (`OfCategory`/`OfClass`/`WhereElementIsNotElementType`) and bounded loops. The budget is 120s and Revit cannot be interrupted mid-script. The dialog guard attempts dismissive responses and reports `suppressedDialogs`; it cannot guarantee handling every modal dialog.
49
- - `capture_view` returns a `filePath` to a temp PNG, never image data — open it with the read tool to actually see it.
50
-
51
- ## Failure modes
52
-
53
- - **Identity rejected**: no tool action was performed. Verify the intended active model, refresh `project.documentId`, and pass `expected_document_id` unchanged.
54
- - **Partial UI/export effects**: selection or zoom can already have changed when isolation fails. Export errors can leave incomplete files, and IFC commit warnings are returned. Inspect the reported effects and output paths; model rollback does not remove files or reverse earlier UI actions.
55
- - **Large-result save failed**: Revit may already have completed the operation. Verify its effects before retrying a write.
56
- - **Bridge not reachable** ("Revit bridge is not available" / "Could not reach the Revit bridge"): Revit is not running or the add-in did not load. Ask the user to start Revit, then retry `ping`.
57
- - **HTTP 409 / "No active Revit document is open."** (`hasActiveDocument: false`): Revit is running but no project is open. Ask the user to open a project, then retry. This fails immediately; do not wait or retry blindly.
58
- - **Timeout** ("Revit did not answer within Ns", 30s default / 120s for execute_csharp, capture_view, export_documents): Revit is busy or showing a modal dialog. An already-started tool still runs to completion in Revit — verify model state (e.g. `get_elements`) before re-issuing a write.
59
- - **Cancelled**: same caveat — the bridge cannot abort queued or running work, so verify model state before retrying a write tool.
60
-
61
- ## Upgrading from 0.2.x
62
-
63
- Version 0.3.0 requires exact document IDs for the operations above. Update the Pi package and deploy the matching add-in with Revit closed, then restart Revit and start a fresh Pi session so the new schemas and instructions are loaded. Obtain a fresh overview before writes. `ping` reports an installed/loaded version mismatch. Setup preserves existing workspace `AGENTS.md`; merge these targeting, result-reading, and folder rules into an older workspace's instructions when needed.
1
+ ---
2
+ name: pi-revit
3
+ description: Inspect, edit, document, script, and export Autodesk Revit models using PI-Revit. Use for questions about the connected project, selecting Revit capabilities, or carrying out model and drawing workflows. Routes to focused tool manuals and execution guidance.
4
+ ---
5
+
6
+ # PI-Revit
7
+
8
+ Choose the path that matches the user's request. This skill routes tasks; the always-present PI-Revit protocol states the capability, completion, evidence, identity and language rules for every tool. Executable schemas and bridge checks remain authoritative. Guidance does not grant permission for additional edits, exports, model saving, or deployment.
9
+
10
+ ## Choose the task path
11
+
12
+ - **Explain or plan:** answer from relevant Revit knowledge and tool manuals. A general Revit explanation does not require an open model. `find_revit_tools` with `scope: "documentation"` can find packaged manuals without connecting to Revit. Connect only when the answer depends on the actual project. Do not turn a question into a modification.
13
+ - **Inspect:** select the intended session and read the requested model scope. Use `get_elements` for listings or `count_only: true` for a simple count. An audit does not authorize repairs, selection changes, or exports.
14
+ - **Modify or produce a deliverable:** establish model identity and scope, inspect the existing state, read the relevant manuals, then execute and verify requested work. Use preview and atomic behavior where they help validate a proposed change. A successful transaction does not save the Revit file.
15
+
16
+ ## Essential rules
17
+
18
+ 1. Before working against a model, read [execution rules](references/execution-rules.md). For several Revit sessions or a stale selection, use [manage_revit_instances](references/tools/manage_revit_instances.md). Select the intended model; do not substitute whichever document happens to be active.
19
+ 2. Obtain `project.documentId` from `get_model_overview` before edits and pass it unchanged as `expected_document_id`, including previews. The same guard applies to export, view activation, selection changes, and every `manage_sheet_placements` action. Refresh after reopening the document, restarting Revit, or changing instances. A title is insufficient.
20
+ 3. Discover dedicated capabilities with [find_revit_tools](references/tools/find_revit_tools.md) before custom code; search with English task words. Specialist tools begin inactive. Registration, activation, and reading a manual are separate actions: read the relevant returned manual, and inspect the activated public schema before calling the tool. Use the [tool index](references/tool-index.md) for all public tools and direct manual links; do not load every manual.
21
+ 4. Each tool declares what it does not cover and what to use instead. When a tool or manual does not cover the request, follow that alternative. When nothing dedicated fits, verify the API members with [search_api_docs](references/tools/search_api_docs.md) and use [execute_csharp](references/tools/execute_csharp.md) within the requested scope. Report an operation as not possible only after that check, naming what you checked and whether the gap is a missing tool, the Revit API, or a user decision. <!-- inv:capability-claims-checked -->
22
+ 5. Discover IDs and parameter identities from the model. Display names are localized; prefer discovered `BuiltInParameter` names or shared GUIDs where supported. Respect each tool's units and coordinate frame; raw measurements commonly use internal feet.
23
+ 6. Check `committed`, `succeeded`, `proposed`, `failed`, warnings, and commit-validation status. Rolled-back proposals are not retained changes. Discard preview-created or replacement IDs after rollback; reuse only committed IDs.
24
+ 7. For large responses, use [read_revit_result](references/tools/read_revit_result.md) and follow every continuation needed for the requested result. Saved-result fragments and the original tool's query pages are separate layers.
25
+ 8. For a timeout, cancellation, uncertain outcome, or incomplete result, read [operation recovery](references/operation-recovery.md) before repeating an action. Check the original receipt. Only an identical request with its original `_operation_id` is a deduplicated retry.
26
+ 9. For visible changes or graphical deliverables, read [visual verification](references/visual-verification.md), capture the actual result, and inspect the image. Correct only what breaks the request; once its requirements are verified, stop and report, offering further improvements as suggestions. Report blocked verification honestly. Evidence capture does not authorize saving the model.
27
+
28
+ ## Read the detail needed for this task
29
+
30
+ | Need | Read |
31
+ | --- | --- |
32
+ | Choose among tools; inspect input/output and effects | [Tool index and individual manuals](references/tool-index.md) |
33
+ | Room plans/sections, tags, schedules, sheets | [Room documentation workflow](references/room-documentation.md) |
34
+ | Multi-step model audit, data review, and requested exports | [Model audit and export workflow](references/model-audit-export.md) |
35
+ | No dedicated tool covers the operation, or an unfamiliar Revit API member | The tool's declared alternative; else [search_api_docs](references/tools/search_api_docs.md), then [execute_csharp](references/tools/execute_csharp.md) |
36
+ | Save, inspect, or run a reusable script | [manage_revit_scripts](references/tools/manage_revit_scripts.md) |
37
+ | Bridge unavailable or version mismatch | [ping](references/tools/ping.md) and [operation recovery](references/operation-recovery.md) |
38
+
39
+ The bridge targets Revit 2025–2027. Manuals describe this package's contract; check the selected bridge's schema and version. `ping` and API search need no open document. Saved results and local script-library inspection work without Revit. General modeling standards and the future Revit subject library are separate from these tool contracts.