@unified-product-graph/mcp-server 0.6.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 (63) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/TOOLS.md +2574 -0
  5. package/dist/index.d.ts +17 -0
  6. package/dist/index.js +50284 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/preflight.d.ts +2 -0
  9. package/dist/preflight.js +35 -0
  10. package/dist/preflight.js.map +1 -0
  11. package/dist/tools-manifest.json +3849 -0
  12. package/package.json +76 -0
  13. package/scripts/claudemd-snippet.md +19 -0
  14. package/scripts/install-skills.sh +259 -0
  15. package/skills/upg/SKILL.md +245 -0
  16. package/skills/upg-analytics/SKILL.md +135 -0
  17. package/skills/upg-capture/SKILL.md +274 -0
  18. package/skills/upg-connect/SKILL.md +167 -0
  19. package/skills/upg-context/SKILL.md +506 -0
  20. package/skills/upg-context-intelligence/SKILL.md +227 -0
  21. package/skills/upg-design-system/SKILL.md +265 -0
  22. package/skills/upg-diff/SKILL.md +150 -0
  23. package/skills/upg-discover/SKILL.md +290 -0
  24. package/skills/upg-explore/SKILL-DETAIL.md +481 -0
  25. package/skills/upg-explore/SKILL.md +297 -0
  26. package/skills/upg-export/SKILL.md +385 -0
  27. package/skills/upg-feedback/SKILL.md +141 -0
  28. package/skills/upg-gaps/SKILL.md +376 -0
  29. package/skills/upg-hypothesis/SKILL.md +190 -0
  30. package/skills/upg-impact/SKILL.md +229 -0
  31. package/skills/upg-import/SKILL.md +189 -0
  32. package/skills/upg-init/SKILL.md +410 -0
  33. package/skills/upg-inspect/SKILL.md +167 -0
  34. package/skills/upg-journey/SKILL.md +207 -0
  35. package/skills/upg-launch/SKILL-DETAIL.md +392 -0
  36. package/skills/upg-launch/SKILL.md +141 -0
  37. package/skills/upg-migrate/SKILL.md +146 -0
  38. package/skills/upg-okr/SKILL-DETAIL.md +351 -0
  39. package/skills/upg-okr/SKILL.md +88 -0
  40. package/skills/upg-persona/SKILL.md +230 -0
  41. package/skills/upg-prioritise/SKILL.md +195 -0
  42. package/skills/upg-pull/SKILL-DETAIL.md +398 -0
  43. package/skills/upg-pull/SKILL.md +57 -0
  44. package/skills/upg-push/SKILL-DETAIL.md +385 -0
  45. package/skills/upg-push/SKILL.md +113 -0
  46. package/skills/upg-reflect/SKILL.md +201 -0
  47. package/skills/upg-research/SKILL.md +336 -0
  48. package/skills/upg-rollback/SKILL.md +163 -0
  49. package/skills/upg-run/SKILL.md +126 -0
  50. package/skills/upg-schema-changelog/SKILL.md +231 -0
  51. package/skills/upg-schema-consolidate/SKILL.md +243 -0
  52. package/skills/upg-schema-edges/SKILL.md +287 -0
  53. package/skills/upg-schema-evolve/SKILL.md +313 -0
  54. package/skills/upg-schema-health/SKILL.md +279 -0
  55. package/skills/upg-schema-update/SKILL.md +206 -0
  56. package/skills/upg-snapshot/SKILL.md +108 -0
  57. package/skills/upg-status/SKILL.md +340 -0
  58. package/skills/upg-strategy/SKILL.md +334 -0
  59. package/skills/upg-template/SKILL.md +145 -0
  60. package/skills/upg-trace/SKILL.md +197 -0
  61. package/skills/upg-tree/SKILL.md +233 -0
  62. package/skills/upg-verify/SKILL.md +223 -0
  63. package/skills/upg-workspace/SKILL.md +103 -0
@@ -0,0 +1,3849 @@
1
+ {
2
+ "schema_version": "2",
3
+ "package": "@unified-product-graph/mcp-server",
4
+ "package_version": "0.6.0",
5
+ "tool_count": 93,
6
+ "domains": [
7
+ "context",
8
+ "nodes",
9
+ "edges",
10
+ "areas",
11
+ "workspace",
12
+ "schema",
13
+ "spec",
14
+ "sync",
15
+ "validation",
16
+ "migrations",
17
+ "skills"
18
+ ],
19
+ "tools": [
20
+ {
21
+ "name": "get_graph_digest",
22
+ "description": "Pre-computed graph analytics in one call: counts, health, chain completeness, business-area coverage, lifecycle balance. ~500 tokens vs ~5-8K for equivalent manual fetches.",
23
+ "domain": "context",
24
+ "inputSchema": {
25
+ "type": "object",
26
+ "properties": {
27
+ "if_changed_since": {
28
+ "type": "string",
29
+ "description": "Hash from a previous response. Returns { changed: false } if graph unchanged (saves ~470 tokens)."
30
+ }
31
+ }
32
+ },
33
+ "throws": [],
34
+ "examples": [
35
+ {
36
+ "description": "Live call against the Notion example graph.",
37
+ "input": "{}",
38
+ "output": "{\n \"product\": {\n \"title\": \"Notion (SATURATED test graph)\",\n \"stage\": \"concept\"\n },\n \"counts\": {\n \"total_nodes\": 2054,\n \"total_edges\": 3120,\n \"by_type\": {\n \"product\": 13,\n \"capability\": 20,\n \"value_stream\": 3,\n \"decision\": 13,\n \"evidence\": 10,\n \"assumption\": 30,\n \"outcome\": 6,\n \"document\": 9,\n \"objective\": 6,\n \"key_result\": 14,\n \"design_component\": 15,\n \"hypothesis\": 6,\n \"experiment\": 5,\n \"experiment_plan\": 13,\n \"job\": 10,\n \"bounded_context\": 13,\n \"initiative\": 11,\n \"strategic_pillar\": 6,\n \"test_plan\": 8,\n \"screen\": 15,\n \"experiment_run\": 18,\n \"design_system\": 4,\n \"team\": 1,\n \"compliance_requirement\": 5,\n \"workspace\": 3,\n \"epic\": 4,\n \"bug\": 6,\n \"task\": 3,\n \"feature_area\": 3,\n \"mission\": 2,\n \"desired_outcome\": 4,\n \"model_comparison\": 3,\n \"agent_session\": 3,\n \"learning\": 17,\n \"research_plan\": 6,\n \"constraint\": 3,\n \"stakeholder\": 10,\n \"feature\": 12,\n \"security_review\": 8,\n \"metric\": 23,\n \"person\": 215,\n \"technical_debt_item\": 12,\n \"risk\": 9,\n \"service\": 11,\n \"opportunity\": 7,\n \"solution\": 11,\n \"feasibility_study\": 3,\n \"design_sprint\": 3,\n \"prototype\": 8,\n \"design_concept\": 7,\n \"vision\": 1,\n… (truncated)"
39
+ }
40
+ ],
41
+ "warnings": [],
42
+ "see": [
43
+ "get_product_context"
44
+ ],
45
+ "source": "src/tools/context.ts:240",
46
+ "symbol": "getGraphDigest",
47
+ "returns": "JSON object: `{ counts, health, chains, coverage, lifecycle,\nlens, lens_digest, _hash }`. ~500 tokens vs ~5-8K for equivalent manual\nfetches.",
48
+ "atomicity": "atomic (read-only)"
49
+ },
50
+ {
51
+ "name": "get_product_context",
52
+ "description": "Product summary, entity counts by type, and a human-readable graph overview. Call first to understand the file. Pass include_summary for edge counts, orphans, and edges-by-type.",
53
+ "domain": "context",
54
+ "inputSchema": {
55
+ "type": "object",
56
+ "properties": {
57
+ "include_summary": {
58
+ "type": "boolean",
59
+ "description": "Include detailed graph statistics (edge counts by type, orphan count)"
60
+ },
61
+ "if_changed_since": {
62
+ "type": "string",
63
+ "description": "Hash from a previous response. Returns { changed: false } if graph unchanged."
64
+ }
65
+ }
66
+ },
67
+ "throws": [],
68
+ "examples": [
69
+ {
70
+ "description": "Live call against the Notion example graph.",
71
+ "input": "{}",
72
+ "output": "## Notion (SATURATED test graph)\n\nStage: concept\n\nLens: product\n\n### 🧭 Product Lens\n- Personas: 4\n- Outcomes: 6\n- Hypotheses: 6 (0 validated)\n\n### Graph Stats\n- Nodes: 2054\n- Edges: 3120\n- Entity types: 312\n\n### Entities by Type\n- Person (`person`): 215\n- Journey Step (`journey_step`): 53\n- Test Case (`test_case`): 48\n- Screen State (`screen_state`): 36\n- Assumption (`assumption`): 30\n- Journey Phase (`journey_phase`): 28\n- Journey Action (`journey_action`): 28\n- Interaction Spec (`interaction_spec`): 28\n- Metric (`metric`): 23\n- Test Result (`test_result`): 23\n- Capability (`capability`): 20\n- Participant (`participant`): 19\n- Regression Test (`regression_test`): 19\n- Experiment Run (`experiment_run`): 18\n- Alert Rule (`alert_rule`): 18\n- Validated Learning (`learning`): 17\n- Observation (`observation`): 17\n- Survey Response (`survey_response`): 17\n- Annotation (`annotation`): 17\n- Vulnerability (`vulnerability`): 16\n- Design Component (`design_component`): 15\n- Screen (`screen`): 15\n- Monitor (`monitor`): 15\n- Research Question (`research_question`): 15\n- Service Level Indicator (`service_level_indicator`): 15\n- Error Budget (`error_budget`): 15\n- Key Result (`key_result`): 14\n- Product (`product`): 13\n- Decision (`decision`): 13\n- Experiment Plan (`experiment_plan`): 13\n- Bounded Context (`bounded_context`): 13\n- Insight (`insight`): 13\n- Theme (`theme`): 13\n- Feature\n… (truncated)"
73
+ }
74
+ ],
75
+ "warnings": [],
76
+ "see": [
77
+ "get_graph_digest",
78
+ "get_entity_schema"
79
+ ],
80
+ "source": "src/tools/context.ts:55",
81
+ "symbol": "getProductContext",
82
+ "returns": "Markdown string with product header, lens preamble, entity counts,\nactive-domain creation sequences, and `_hash` footer for `if_changed_since`\ndiffing.",
83
+ "atomicity": "atomic (read-only)"
84
+ },
85
+ {
86
+ "name": "get_session_context",
87
+ "description": "Read session context: which skills ran, what was recommended, current focus area. Returns `recommendations_to_avoid` — the deduped list of recommendations already given this session. Pick your next recommendation NOT in that array (data-layer dedup, not prose).",
88
+ "domain": "context",
89
+ "inputSchema": {
90
+ "type": "object",
91
+ "properties": {}
92
+ },
93
+ "throws": [],
94
+ "examples": [
95
+ {
96
+ "description": "Live call against the Notion example graph.",
97
+ "input": "{}",
98
+ "output": "{\n \"lens\": \"product\",\n \"skills_invoked\": [],\n \"recommendations_given\": [],\n \"recommendations_to_avoid\": [],\n \"focus_area\": null,\n \"custom\": {},\n \"skills_count\": 0,\n \"last_skill\": null,\n \"last_recommendation\": null\n}"
99
+ }
100
+ ],
101
+ "warnings": [],
102
+ "see": [
103
+ "update_session_context"
104
+ ],
105
+ "source": "src/tools/context.ts:295",
106
+ "symbol": "getSessionContext",
107
+ "returns": "JSON: `{ lens, skills_invoked, recommendations_given,\nrecommendations_to_avoid, focus_area, custom, skills_count, last_skill,\nlast_recommendation }`. `recommendations_to_avoid` is the deduped list of\nevery recommendation given this session — runners should filter their\nnext recommendation against this array rather than re-deriving the\ndedup rule from prose.",
108
+ "atomicity": "atomic (read-only)"
109
+ },
110
+ {
111
+ "name": "update_session_context",
112
+ "description": "Update session context: register a skill invocation, record a recommendation, set focus area, switch lens, or store custom state for cross-skill coordination.",
113
+ "domain": "context",
114
+ "inputSchema": {
115
+ "type": "object",
116
+ "properties": {
117
+ "skill_invoked": {
118
+ "type": "string",
119
+ "description": "Register that this skill was just invoked (e.g. \"upg-status\")"
120
+ },
121
+ "recommendation": {
122
+ "type": "string",
123
+ "description": "Record a recommendation given to the user (e.g. \"Run /upg-strategy to fill strategy gap\")"
124
+ },
125
+ "focus_area": {
126
+ "type": "string",
127
+ "description": "Set the current focus area (e.g. \"strategy\", \"validation\", \"user_research\")"
128
+ },
129
+ "lens": {
130
+ "type": "string",
131
+ "enum": [
132
+ "product",
133
+ "engineering",
134
+ "design",
135
+ "growth"
136
+ ],
137
+ "description": "Switch the active lens. Changes what context, skills, and gaps are surfaced first."
138
+ },
139
+ "persist_lens": {
140
+ "type": "boolean",
141
+ "description": "If true, also save the lens to the .upg file so it persists across sessions"
142
+ },
143
+ "custom": {
144
+ "type": "object",
145
+ "description": "Arbitrary key-value pairs for cross-skill state"
146
+ }
147
+ }
148
+ },
149
+ "throws": [],
150
+ "examples": [
151
+ {
152
+ "description": "Live call against the Notion example graph.",
153
+ "input": "{}",
154
+ "output": "{\n \"updated\": true,\n \"session\": {\n \"lens\": \"product\",\n \"skills_invoked\": [],\n \"recommendations_given\": [],\n \"focus_area\": null,\n \"custom\": {}\n }\n}"
155
+ }
156
+ ],
157
+ "warnings": [],
158
+ "see": [
159
+ "get_session_context"
160
+ ],
161
+ "source": "src/tools/context.ts:336",
162
+ "symbol": "updateSessionContext",
163
+ "returns": "JSON: `{ updated: true, session: SessionContext }` reflecting the\nnew state.",
164
+ "atomicity": "non-atomic. Session mutates in-memory immediately; lens\npersistence flushes the .upg file as a separate side-effect that may\nsucceed or fail independently of the session update."
165
+ },
166
+ {
167
+ "name": "batch_create_nodes",
168
+ "description": "Create up to 50 entities in one atomic call, optionally with explicit edges in the same transaction. Use `parent_ref` (\"$0\", \"$1\") to reference nodes created earlier in the same batch. The optional `edges` array accepts the same `$N` refs (or existing node IDs) for both endpoints. All nodes and edges validate up front; on failure nothing lands.",
169
+ "domain": "nodes",
170
+ "inputSchema": {
171
+ "type": "object",
172
+ "properties": {
173
+ "nodes": {
174
+ "type": "array",
175
+ "items": {
176
+ "type": "object",
177
+ "properties": {
178
+ "type": {
179
+ "type": "string",
180
+ "description": "UPG entity type (e.g. \"persona\", \"opportunity\")"
181
+ },
182
+ "title": {
183
+ "type": "string",
184
+ "description": "Entity title"
185
+ },
186
+ "description": {
187
+ "type": "string",
188
+ "description": "Optional description"
189
+ },
190
+ "status": {
191
+ "type": "string",
192
+ "description": "Lifecycle status"
193
+ },
194
+ "tags": {
195
+ "type": "array",
196
+ "items": {
197
+ "type": "string"
198
+ },
199
+ "description": "Freeform tags"
200
+ },
201
+ "properties": {
202
+ "type": "object",
203
+ "description": "Type-specific fields"
204
+ },
205
+ "parent_id": {
206
+ "type": "string",
207
+ "description": "Parent node ID. Creates an edge automatically."
208
+ },
209
+ "parent_ref": {
210
+ "type": "string",
211
+ "description": "Reference a node created earlier in this batch by index, e.g. \"$0\", \"$1\""
212
+ }
213
+ },
214
+ "required": [
215
+ "type",
216
+ "title"
217
+ ]
218
+ },
219
+ "description": "Array of nodes to create (max 50)"
220
+ },
221
+ "edges": {
222
+ "type": "array",
223
+ "description": "Optional edges to create alongside the nodes (same atomic transaction). Each edge's from/to may be a `$N` ref into the `nodes` array OR an existing node ID.",
224
+ "items": {
225
+ "type": "object",
226
+ "properties": {
227
+ "from_ref": {
228
+ "type": "string",
229
+ "description": "`$N` ref or existing node id for the source endpoint"
230
+ },
231
+ "to_ref": {
232
+ "type": "string",
233
+ "description": "`$N` ref or existing node id for the target endpoint"
234
+ },
235
+ "type": {
236
+ "type": "string",
237
+ "description": "Optional explicit edge type (must be in UPG_EDGE_CATALOG). If omitted, inferred from canonical source/target types."
238
+ }
239
+ },
240
+ "required": [
241
+ "from_ref",
242
+ "to_ref"
243
+ ]
244
+ }
245
+ }
246
+ },
247
+ "required": [
248
+ "nodes"
249
+ ]
250
+ },
251
+ "throws": [
252
+ "Returns a textError when `nodes` is missing/non-array or any\nvalidation fails."
253
+ ],
254
+ "examples": [
255
+ {
256
+ "description": "Live call against the Notion example graph.",
257
+ "input": "{\n \"nodes\": [\n {\n \"type\": \"person\",\n \"title\": \"Example node A\"\n },\n {\n \"type\": \"person\",\n \"title\": \"Example node B\"\n }\n ]\n}",
258
+ "output": "{\n \"created\": [\n {\n \"id\": \"n_j6U_8nTCFs7stzLl\",\n \"type\": \"person\",\n \"title\": \"Example node A\"\n },\n {\n \"id\": \"n_J6z4nc-D40LV54G5\",\n \"type\": \"person\",\n \"title\": \"Example node B\"\n }\n ],\n \"edges\": [],\n \"count\": 2,\n \"warnings\": [\n \"Created 2 nodes with no edges — they are orphans. Use the edges[] array in this call to link them. See get_entity_schema(<type>) for canonical edges per type.\"\n ]\n}"
259
+ }
260
+ ],
261
+ "warnings": [],
262
+ "see": [
263
+ "create_node",
264
+ "batch_create_edges"
265
+ ],
266
+ "source": "src/tools/nodes.ts:958",
267
+ "symbol": "batchCreateNodes",
268
+ "returns": "JSON: `{ created, edges_created, count, edges_count, warnings? }`.",
269
+ "atomicity": "atomic-with-rollback. Full validation pass first, then commit."
270
+ },
271
+ {
272
+ "name": "batch_delete_nodes",
273
+ "description": "Delete up to 50 entities and their connected edges in one atomic call (all succeed or all fail).",
274
+ "domain": "nodes",
275
+ "inputSchema": {
276
+ "type": "object",
277
+ "properties": {
278
+ "node_ids": {
279
+ "type": "array",
280
+ "items": {
281
+ "type": "string"
282
+ },
283
+ "description": "Array of node IDs to delete (max 50)"
284
+ }
285
+ },
286
+ "required": [
287
+ "node_ids"
288
+ ]
289
+ },
290
+ "throws": [
291
+ "Returns a textError when `node_ids` is missing/non-array, empty,\nlonger than 50, or any ID does not resolve."
292
+ ],
293
+ "examples": [
294
+ {
295
+ "description": "Live call against the Notion example graph.",
296
+ "input": "{\n \"node_ids\": [\n \"1c051617-6cb7-4a59-b0a1-7779f404145b\"\n ]\n}",
297
+ "output": "{\n \"deleted\": [\n {\n \"id\": \"1c051617-6cb7-4a59-b0a1-7779f404145b\",\n \"title\": \"Real-time multi-cursor collaboration\"\n }\n ],\n \"edges_removed\": 4,\n \"count\": 1\n}"
298
+ }
299
+ ],
300
+ "warnings": [],
301
+ "see": [
302
+ "delete_node"
303
+ ],
304
+ "source": "src/tools/nodes.ts:1062",
305
+ "symbol": "batchDeleteNodes",
306
+ "returns": "JSON: `{ deleted, edges_removed, count }`.",
307
+ "atomicity": "atomic. Validation pass rejects the entire batch before any\nmutation lands."
308
+ },
309
+ {
310
+ "name": "batch_update_nodes",
311
+ "description": "Update up to 50 entities atomically (all succeed or all fail). Unspecified fields preserved. Properties merge with existing.",
312
+ "domain": "nodes",
313
+ "inputSchema": {
314
+ "type": "object",
315
+ "properties": {
316
+ "updates": {
317
+ "type": "array",
318
+ "items": {
319
+ "type": "object",
320
+ "properties": {
321
+ "node_id": {
322
+ "type": "string",
323
+ "description": "The node ID to update"
324
+ },
325
+ "title": {
326
+ "type": "string"
327
+ },
328
+ "description": {
329
+ "type": "string"
330
+ },
331
+ "status": {
332
+ "type": "string"
333
+ },
334
+ "tags": {
335
+ "type": "array",
336
+ "items": {
337
+ "type": "string"
338
+ }
339
+ },
340
+ "properties": {
341
+ "type": "object",
342
+ "description": "Merged with existing properties"
343
+ }
344
+ },
345
+ "required": [
346
+ "node_id"
347
+ ]
348
+ },
349
+ "description": "Array of updates to apply (max 50)"
350
+ }
351
+ },
352
+ "required": [
353
+ "updates"
354
+ ]
355
+ },
356
+ "throws": [
357
+ "Returns a textError when `updates` is missing/non-array, the array\nis empty, longer than 50, or any item references a missing node."
358
+ ],
359
+ "examples": [],
360
+ "warnings": [],
361
+ "see": [
362
+ "update_node"
363
+ ],
364
+ "source": "src/tools/nodes.ts:986",
365
+ "symbol": "batchUpdateNodes",
366
+ "returns": "JSON: `{ updated, count, warnings? }`. `warnings` carries\nlifecycle-phase hints aggregated across the batch.",
367
+ "atomicity": "atomic. Validation pass rejects the entire batch before any\nmutation lands."
368
+ },
369
+ {
370
+ "name": "create_node",
371
+ "description": "Create one entity, optionally with a parent edge. For 3+ entities, use `batch_create_nodes` instead of looping. Portfolio-scoped types (`portfolio`, `organization`, `product_area`) route to `.upg/portfolio.upg` rather than the active product's `nodes[]`.",
372
+ "domain": "nodes",
373
+ "inputSchema": {
374
+ "type": "object",
375
+ "properties": {
376
+ "type": {
377
+ "type": "string",
378
+ "description": "UPG entity type (e.g. \"persona\", \"opportunity\"). Portfolio-scoped: \"portfolio\", \"organization\", \"product_area\"."
379
+ },
380
+ "title": {
381
+ "type": "string",
382
+ "description": "Entity title"
383
+ },
384
+ "description": {
385
+ "type": "string",
386
+ "description": "Optional description"
387
+ },
388
+ "tags": {
389
+ "type": "array",
390
+ "items": {
391
+ "type": "string"
392
+ },
393
+ "description": "Freeform tags"
394
+ },
395
+ "status": {
396
+ "type": "string",
397
+ "description": "Lifecycle status"
398
+ },
399
+ "properties": {
400
+ "type": "object",
401
+ "description": "Type-specific fields"
402
+ },
403
+ "parent_id": {
404
+ "type": "string",
405
+ "description": "Parent node ID. Creates an edge automatically. Ignored for portfolio-scoped types."
406
+ },
407
+ "overwrite_organization": {
408
+ "type": "boolean",
409
+ "description": "For type=\"organization\" only. When true, replaces the existing portfolio organisation instead of throwing."
410
+ }
411
+ },
412
+ "required": [
413
+ "type",
414
+ "title"
415
+ ]
416
+ },
417
+ "throws": [
418
+ "Returns a textError when `type` or `title` is missing, when the type\nis unknown (`UnknownEntityTypeError`), when `strict: true` and unknown\nproperties are present, or when the underlying store rejects the write."
419
+ ],
420
+ "examples": [
421
+ {
422
+ "description": "Live call against the Notion example graph.",
423
+ "input": "{\n \"type\": \"person\",\n \"title\": \"Example node\"\n}",
424
+ "output": "{\n \"node\": {\n \"id\": \"n_LSjd64yzUUdx5X9r\",\n \"type\": \"person\",\n \"title\": \"Example node\",\n \"slug\": \"example-node\"\n },\n \"edge\": null\n}"
425
+ }
426
+ ],
427
+ "warnings": [],
428
+ "see": [
429
+ "batch_create_nodes",
430
+ "update_node"
431
+ ],
432
+ "source": "src/tools/nodes.ts:702",
433
+ "symbol": "createNode",
434
+ "returns": "JSON: `{ node, edge?, unknown_properties?, warning? }`. The `edge`\nfield is present only when `parent_id` was supplied and a canonical\nhierarchy edge could be inferred. `unknown_properties` and `warning` are\npresent when the caller passed properties not in the entity's schema\n. Pass `strict: true` to reject unknown properties instead of\nwarning. For portfolio-scoped types the response shape is\n`{ node, portfolio_file, written_to, warning? }` where `node` is the\npersisted typed record.",
435
+ "atomicity": "atomic-with-rollback. Schema validation runs before mutation."
436
+ },
437
+ {
438
+ "name": "deduplicate_nodes",
439
+ "description": "Find duplicate entities (same title + type) and return them grouped. `dry_run` previews; otherwise keeps one per group and redirects edges from the others.",
440
+ "domain": "nodes",
441
+ "inputSchema": {
442
+ "type": "object",
443
+ "properties": {
444
+ "type": {
445
+ "type": "string",
446
+ "description": "Only check this entity type. Omit to check all types."
447
+ },
448
+ "dry_run": {
449
+ "type": "boolean",
450
+ "description": "Preview duplicates without merging (default true)"
451
+ },
452
+ "keep": {
453
+ "type": "string",
454
+ "description": "Which duplicate to keep when merging: \"newest\" (default) or \"oldest\"."
455
+ }
456
+ }
457
+ },
458
+ "throws": [
459
+ "Returns a textError when `keep` is provided but is not\n`\"newest\"` or `\"oldest\"`."
460
+ ],
461
+ "examples": [
462
+ {
463
+ "description": "Live call against the Notion example graph.",
464
+ "input": "{}",
465
+ "output": "{\n \"duplicates\": [\n {\n \"title\": \"Cap AI inference cost at $0.18 / MAU / month\",\n \"type\": \"decision\",\n \"count\": 2,\n \"ids\": [\n \"e4078a2d-ff4f-4047-98e7-7d6556109eb0\",\n \"afd2234f-3c50-487d-8507-95762895c7dd\"\n ]\n },\n {\n \"title\": \"AI Q&A acceptance rate ≥ 70%\",\n \"type\": \"key_result\",\n \"count\": 2,\n \"ids\": [\n \"6d6991c4-36f2-46d4-bd1a-cccc87313751\",\n \"eee73bb6-52ac-47eb-b007-d578d17870cf\"\n ]\n },\n {\n \"title\": \"AI-native, not AI-bolted-on\",\n \"type\": \"strategic_pillar\",\n \"count\": 2,\n \"ids\": [\n \"620af368-90b8-4d26-8479-1359826af5e1\",\n \"d8665db0-5210-4354-a4ab-391ae8a843a2\"\n ]\n },\n {\n \"title\": \"Notion Calendar\",\n \"type\": \"product\",\n \"count\": 2,\n \"ids\": [\n \"c355f3ea-1e9f-45a1-aedc-a7adf66050e1\",\n \"4d5b21d6-66b1-4663-add8-2b24a10848ea\"\n ]\n },\n {\n \"title\": \"One workspace, every team\",\n \"type\": \"strategic_pillar\",\n \"count\": 2,\n \"ids\": [\n \"4c23a9dc-a4b3-4c80-83ee-10e35b839c26\",\n \"4d1b0619-b5eb-4d5d-a465-b98bc164a203\"\n ]\n },\n {\n \"title\": \"react\",\n \"type\": \"library_dependency\",\n \"count\": 2,\n \"ids\": [\n \"abe04949-9bc9-48e7-8ff8-8023a6c87b11\",\n \"4a04fd43-2451-4492-9647-c883545af3c2\"\n ]\n },\n {\n \"title\": \"AI-native vs\n… (truncated)"
466
+ }
467
+ ],
468
+ "warnings": [
469
+ "Default is `dry_run: true`. Pass `dry_run: false` to commit.\nIdempotent on retry: a second `dry_run: false` against an\nalready-deduplicated graph reports zero merges."
470
+ ],
471
+ "see": [
472
+ "search_nodes",
473
+ "list_nodes",
474
+ "batch_delete_nodes",
475
+ "validate_graph"
476
+ ],
477
+ "source": "src/tools/nodes.ts:1307",
478
+ "symbol": "deduplicateNodes",
479
+ "returns": "JSON: with `dry_run: true`, `{ duplicates, total_groups,\ntotal_duplicate_nodes, dry_run, message }`. With `dry_run: false`,\n`{ merged: true, groups_merged, nodes_removed, edges_redirected,\nstrategy }`.",
480
+ "atomicity": "non-atomic. Merges are applied group-by-group; a mid-flight\nerror leaves earlier groups merged."
481
+ },
482
+ {
483
+ "name": "delete_node",
484
+ "description": "Remove one entity and all its connected edges. For 3+ entities, use `batch_delete_nodes`.",
485
+ "domain": "nodes",
486
+ "inputSchema": {
487
+ "type": "object",
488
+ "properties": {
489
+ "node_id": {
490
+ "type": "string",
491
+ "description": "The node ID to delete"
492
+ }
493
+ },
494
+ "required": [
495
+ "node_id"
496
+ ]
497
+ },
498
+ "throws": [
499
+ "Returns a textError when `node_id` is missing or the node does not\nexist."
500
+ ],
501
+ "examples": [
502
+ {
503
+ "description": "Live call against the Notion example graph.",
504
+ "input": "{\n \"node_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\"\n}",
505
+ "output": "{\n \"deleted_node_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"deleted_node_title\": \"Notion (SATURATED test graph)\",\n \"deleted_edge_ids\": [\n \"1d645a2e-391b-4bc7-b0de-e15691c8a7d6\",\n \"b4a77a49-7d28-4926-831f-464574d927fe\",\n \"1ee422bc-64c1-4da1-b797-62e4d206b79c\",\n \"013ac760-b99a-423e-bb94-700acda058c0\",\n \"e72fc432-9b4e-443d-b0c3-619b4a1cff62\",\n \"1dd3f0dd-4185-43b0-bbf5-a4685099b22a\",\n \"e1002543-96a9-4d6c-b708-b7c91055f391\",\n \"f080a424-9da2-499e-98b0-b9219493690c\",\n \"66237b58-5c5f-4ce1-9f69-13338b363d51\",\n \"95795513-430f-408c-b4ab-ea08a7744939\",\n \"5db91b8d-0d36-40fa-861d-d528ac22c1f5\",\n \"f986bd5c-19a6-4c97-86b7-c07555c37495\",\n \"2ace745e-05a9-4d2c-9683-343a52ae6e64\",\n \"6e2abc91-7066-4310-9e17-3441d81c4e63\",\n \"f0a45c02-54e4-4cd8-8752-d265fa1957bb\",\n \"10dc7124-99e0-4851-9268-5c5896e4e365\",\n \"02552438-55e4-4bac-957e-c2d451a70eee\",\n \"16308874-0ebc-4303-a2ef-ea1e0a987a8f\",\n \"9ece5292-9874-4b2e-a797-15f542e63467\",\n \"372bf1ec-da83-4ce0-97d6-484374a74c67\",\n \"0fe88c72-7bca-4b65-8f2e-46a7b17a5dde\",\n \"3fc58a2e-79bf-476c-9092-3f658d03536b\",\n \"e0172dc5-4d03-41d7-b615-1c18b6cbfff7\",\n \"45920303-286d-467e-a7c4-5afdce1374ba\",\n \"cfb6d93c-ad4c-4565-971b-df5d5d6da734\",\n \"d3919111-98e7-409a-b212-0bf453aae0c4\",\n \"e149cd50-8fc1-40cc-8a8e-5fab20daa8c9\",\n \"b089a933-a5b7-4a5c-97f2-abfe73d91614\",\n… (truncated)"
506
+ }
507
+ ],
508
+ "warnings": [],
509
+ "see": [
510
+ "batch_delete_nodes"
511
+ ],
512
+ "source": "src/tools/nodes.ts:932",
513
+ "symbol": "deleteNode",
514
+ "returns": "JSON: `{ node, removed_edge_ids }`.",
515
+ "atomicity": "atomic. Node + cascading edges removed in one mutation."
516
+ },
517
+ {
518
+ "name": "get_node",
519
+ "description": "Get a single entity by ID, with full properties and all connected edges.",
520
+ "domain": "nodes",
521
+ "inputSchema": {
522
+ "type": "object",
523
+ "properties": {
524
+ "node_id": {
525
+ "type": "string",
526
+ "description": "The node ID"
527
+ },
528
+ "compact_edges": {
529
+ "type": "boolean",
530
+ "description": "Omit source_title/target_title from edges (saves ~30% on edge-heavy nodes)"
531
+ }
532
+ },
533
+ "required": [
534
+ "node_id"
535
+ ]
536
+ },
537
+ "throws": [
538
+ "Returns a textError when `node_id` is missing or the node does not\nexist."
539
+ ],
540
+ "examples": [
541
+ {
542
+ "description": "Live call against the Notion example graph.",
543
+ "input": "{\n \"node_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\"\n}",
544
+ "output": "{\n \"node\": {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"slug\": \"notion-saturated-test-graph\",\n \"description\": \"Canonical SATURATED test graph for UCS / GPE / .upg-loader / MCP test surfaces. Fictional Notion modelled as a thinly-disguised version of the real Notion. Built via parallel MCP-driven domain waves. Exported as `.upg/notion-saturated.upg` in `the-product-creator` repo at `mr-data/upg-saturated-graph` branch (PR #1532).\",\n \"properties\": {\n \"stage\": \"idea\",\n \"health_status\": \"off_track\",\n \"url\": \"url sample 10\",\n \"logo_url\": \"logo url sample 69\",\n \"launched_at\": \"2026-12-13T09:00:00Z\"\n }\n },\n \"edges_out\": [\n {\n \"id\": \"1d645a2e-391b-4bc7-b0de-e15691c8a7d6\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"dce7a627-2ef4-4cb7-a63a-5044a8ad1802\",\n \"type\": \"product_serves_account\",\n \"target_title\": \"Acme Corp Enterprise Workspace\"\n },\n {\n \"id\": \"b4a77a49-7d28-4926-831f-464574d927fe\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"2a3ab02e-4110-43f2-848b-a1ebeedb6fa9\",\n \"type\": \"product_shares_metric_with_product\",\n \"target_title\": \"Notion Mail (preview)\"\n },\n {\n \"id\": \"1ee422bc-64c1-4da1-b797-62e4d206b79c\",\n \"source\":\n… (truncated)"
545
+ }
546
+ ],
547
+ "warnings": [],
548
+ "see": [
549
+ "get_nodes"
550
+ ],
551
+ "source": "src/tools/nodes.ts:206",
552
+ "symbol": "getNode",
553
+ "returns": "JSON: the node object plus an `edges` array. `compact_edges: true`\nomits `source_title` and `target_title` (saves ~30% on edge-heavy nodes).",
554
+ "atomicity": "atomic (read-only)"
555
+ },
556
+ {
557
+ "name": "get_nodes",
558
+ "description": "Batch-fetch up to 50 entities by ID. Returns each node with its edges. Use instead of looping `get_node`.",
559
+ "domain": "nodes",
560
+ "inputSchema": {
561
+ "type": "object",
562
+ "properties": {
563
+ "ids": {
564
+ "type": "array",
565
+ "items": {
566
+ "type": "string"
567
+ },
568
+ "description": "Array of node IDs to fetch (max 50)"
569
+ },
570
+ "compact_edges": {
571
+ "type": "boolean",
572
+ "description": "Omit titles from edges"
573
+ }
574
+ },
575
+ "required": [
576
+ "ids"
577
+ ]
578
+ },
579
+ "throws": [
580
+ "Returns a textError when `ids` is missing/empty or longer than 50."
581
+ ],
582
+ "examples": [
583
+ {
584
+ "description": "Live call against the Notion example graph.",
585
+ "input": "{\n \"ids\": [\n \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"1c051617-6cb7-4a59-b0a1-7779f404145b\"\n ]\n}",
586
+ "output": "{\n \"nodes\": [\n {\n \"node\": {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"slug\": \"notion-saturated-test-graph\"\n },\n \"edges_out\": [\n {\n \"id\": \"1d645a2e-391b-4bc7-b0de-e15691c8a7d6\",\n \"type\": \"product_serves_account\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"dce7a627-2ef4-4cb7-a63a-5044a8ad1802\"\n },\n {\n \"id\": \"b4a77a49-7d28-4926-831f-464574d927fe\",\n \"type\": \"product_shares_metric_with_product\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"2a3ab02e-4110-43f2-848b-a1ebeedb6fa9\"\n },\n {\n \"id\": \"1ee422bc-64c1-4da1-b797-62e4d206b79c\",\n \"type\": \"product_ships_via_release\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"024aa3ef-d8e0-4791-8979-c8a9f2a69618\"\n },\n {\n \"id\": \"013ac760-b99a-423e-bb94-700acda058c0\",\n \"type\": \"product_sold_via_pipeline_sales\",\n \"source\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"target\": \"8737db50-a948-4225-9fa6-7a54ca4ca374\"\n },\n {\n \"id\": \"e72fc432-9b4e-443d-b0c3-619b4a1cff62\",\n \"type\": \"product_stored_in_code_repository\",\n \"source\":\n… (truncated)"
587
+ }
588
+ ],
589
+ "warnings": [
590
+ "Pre-flight payload guardrail: refuses above\n`UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above\n`UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). 50 edge-heavy nodes can\nstill cross 50 KB. Pass `compact_edges:true` to halve edge size.",
591
+ "Auto-degrade: between soft and hard limits, the server\nmay drop edge titles, optional node fields, or truncate the result list.\nSurfaced as `degraded.applied[]` on the response."
592
+ ],
593
+ "see": [
594
+ "get_node"
595
+ ],
596
+ "source": "src/tools/nodes.ts:250",
597
+ "symbol": "getNodes",
598
+ "returns": "JSON array of node objects with edges. Missing IDs are silently\nskipped. May include a `degraded` block when the response was\nauto-trimmed to fit.",
599
+ "atomicity": "atomic (read-only)"
600
+ },
601
+ {
602
+ "name": "list_nodes",
603
+ "description": "List entities with filtering, edge inclusion, count-only mode, and pagination. For graph-wide edge enumeration, prefer `export_edges` (flat) or `query` (traversal). `list_nodes(include_edges:true)` is for entity-scoped reads.",
604
+ "domain": "nodes",
605
+ "inputSchema": {
606
+ "type": "object",
607
+ "properties": {
608
+ "type": {
609
+ "type": "string",
610
+ "description": "Filter by entity type"
611
+ },
612
+ "status": {
613
+ "type": "string",
614
+ "description": "Filter by status value"
615
+ },
616
+ "tags": {
617
+ "type": "array",
618
+ "items": {
619
+ "type": "string"
620
+ },
621
+ "description": "Filter by tags (matches any)"
622
+ },
623
+ "parent_id": {
624
+ "type": "string",
625
+ "description": "Filter to children of this node (connected by outgoing edge from parent)"
626
+ },
627
+ "include_edges": {
628
+ "type": "boolean",
629
+ "description": "Include compact edge data (id, type, source, target) per node"
630
+ },
631
+ "count_only": {
632
+ "type": "boolean",
633
+ "description": "Return only the total count, no node data"
634
+ },
635
+ "offset": {
636
+ "type": "number",
637
+ "description": "Skip N results (default 0)"
638
+ },
639
+ "limit": {
640
+ "type": "number",
641
+ "description": "Max results (default 50, max 200)"
642
+ },
643
+ "if_changed_since": {
644
+ "type": "string",
645
+ "description": "Hash from a previous response. Returns { changed: false } if graph unchanged."
646
+ }
647
+ }
648
+ },
649
+ "throws": [],
650
+ "examples": [
651
+ {
652
+ "description": "Live call against the Notion example graph.",
653
+ "input": "{}",
654
+ "output": "{\n \"nodes\": [\n {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\"\n },\n {\n \"id\": \"1c051617-6cb7-4a59-b0a1-7779f404145b\",\n \"type\": \"capability\",\n \"title\": \"Real-time multi-cursor collaboration\"\n },\n {\n \"id\": \"478852b7-ca46-4bdd-85fa-cd2334b9565d\",\n \"type\": \"capability\",\n \"title\": \"Block-based composable editing\"\n },\n {\n \"id\": \"0904ef52-cc7e-4bcd-9c9d-0072beb28878\",\n \"type\": \"capability\",\n \"title\": \"Tree-inherited permissions\"\n },\n {\n \"id\": \"f8ce72c1-af1a-48ba-945e-72e08c5aeeb5\",\n \"type\": \"capability\",\n \"title\": \"Permission-aware AI grounding\"\n },\n {\n \"id\": \"4dffea49-4122-4599-858c-f3c2f0c3e5c4\",\n \"type\": \"value_stream\",\n \"title\": \"Personal → Team conversion stream\"\n },\n {\n \"id\": \"9d03304e-8beb-4734-845b-1bc3c07ce20c\",\n \"type\": \"decision\",\n \"title\": \"Build Frankfurt region (not partner)\"\n },\n {\n \"id\": \"6ea52729-4462-4fc0-b2a1-0c7e2c0362b2\",\n \"type\": \"evidence\",\n \"title\": \"Self-hostable waitlist: 1.2k signups, 0% paid intent\"\n },\n {\n \"id\": \"3f1c1804-b33b-4b1f-9094-c91cc8957912\",\n \"type\": \"assumption\",\n \"title\": \"Teams prefer one tool over a stack of four\"\n },\n {\n \"id\": \"6719fce7-61e7-44df-b6cf-92f7c7312554\",\n \"type\":\n… (truncated)"
655
+ }
656
+ ],
657
+ "warnings": [
658
+ "Pre-flight payload guardrail: refuses with a steering\nerror when the estimated response exceeds `UPG_MCP_PAYLOAD_HARD_LIMIT`\n(default 150 KB), and attaches a `_warning` field above\n`UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). For graph-wide reads,\nprefer `query` with a tight projection.",
659
+ "Auto-degrade: between the soft and hard limits, the\nresponse is automatically truncated. Surfaced as\n`degraded.applied: ['truncate_at_count_auto']` on the response."
660
+ ],
661
+ "see": [
662
+ "search_nodes",
663
+ "query"
664
+ ],
665
+ "source": "src/tools/nodes.ts:106",
666
+ "symbol": "listNodes",
667
+ "returns": "JSON: `{ nodes, total, offset, limit, _hash }`. With\n`count_only: true`, returns `{ total, _hash }` only. May include a\n`degraded` block when the response was auto-trimmed to fit.",
668
+ "atomicity": "atomic (read-only)"
669
+ },
670
+ {
671
+ "name": "migrate_properties",
672
+ "description": "Apply `UPG_PROPERTY_MIGRATIONS` graph-wide with no type rename or edge migration. Pure property pass: `drop_props`, `rename_top_level`, `lift_property_to_top_level`, `drop_when_self_referential`. Default `dry_run=true` previews the per-rule change set; pass `dry_run=false` to commit. Use when you want property cleanup standalone; `migrate_type` folds the same pass into its rename.",
673
+ "domain": "nodes",
674
+ "inputSchema": {
675
+ "type": "object",
676
+ "properties": {
677
+ "dry_run": {
678
+ "type": "boolean",
679
+ "description": "Preview changes without applying (default true). Pass false to commit."
680
+ }
681
+ }
682
+ },
683
+ "throws": [],
684
+ "examples": [
685
+ {
686
+ "description": "Live call against the Notion example graph.",
687
+ "input": "{}",
688
+ "output": "{\n \"top_level_renames\": [],\n \"lifted_properties\": [\n {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"from_property\": \"stage\",\n \"to\": \"status\",\n \"value_changed\": true\n },\n {\n \"id\": \"3f1c1804-b33b-4b1f-9094-c91cc8957912\",\n \"from_property\": \"validation_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"6719fce7-61e7-44df-b6cf-92f7c7312554\",\n \"from_property\": \"validation_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"7b22f5f3-b344-4e49-8d5a-35cf9087dc6c\",\n \"from_property\": \"validation_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"c969753e-8e7b-4c14-9c99-acba5b1862be\",\n \"from_property\": \"validation_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"6bc0cfd3-29a2-47b5-8f2e-746ded26351f\",\n \"from_property\": \"validation_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"2326222c-cfab-4793-bf58-818039939753\",\n \"from_property\": \"bug_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"1373c0c9-4aba-49fc-8a26-bbc78661ca5a\",\n \"from_property\": \"bug_status\",\n \"to\": \"status\",\n \"value_changed\": false\n },\n {\n \"id\": \"655c81dc-f827-45e3-b871-d9c78e569076\",\n \"from_property\":\n… (truncated)"
689
+ }
690
+ ],
691
+ "warnings": [
692
+ "Default is `dry_run: true`. Pass `dry_run: false` to commit.\nRe-running with `dry_run: true` after a successful commit reports zero\nchanges (idempotent on the canonical-properties shape)."
693
+ ],
694
+ "see": [
695
+ "migrate_type",
696
+ "validate_graph",
697
+ "list_type_migrations"
698
+ ],
699
+ "source": "src/tools/nodes.ts:1253",
700
+ "symbol": "migrateProperties",
701
+ "returns": "JSON: `{ top_level_renames, lifted_properties, dropped_props,\ndropped_self_referential, dry_run }`.",
702
+ "atomicity": "non-atomic. Mutations are applied node-by-node; a mid-flight\nerror may leave the graph partially migrated."
703
+ },
704
+ {
705
+ "name": "migrate_type",
706
+ "description": "Migrate every entity of one type to another, applying defaults from `UPG_MIGRATIONS`. Three passes commit as one write: (1) node rename, (2) edges through `UPG_EDGE_MIGRATIONS` (catalog-aware renames, direction flips, drops; endpoint guards check post-migration types; uncatalogued edges surface as `unmapped_legacy_edges`), (3) every node through `UPG_PROPERTY_MIGRATIONS` (top-level renames, lifts, drops, self-referential cleanup). Type-specific property rules see the post-rename type.",
707
+ "domain": "nodes",
708
+ "inputSchema": {
709
+ "type": "object",
710
+ "properties": {
711
+ "from_type": {
712
+ "type": "string",
713
+ "description": "The current entity type to migrate FROM"
714
+ },
715
+ "to_type": {
716
+ "type": "string",
717
+ "description": "The new entity type to migrate TO"
718
+ },
719
+ "dry_run": {
720
+ "type": "boolean",
721
+ "description": "Preview changes without applying (default false)"
722
+ }
723
+ },
724
+ "required": [
725
+ "from_type",
726
+ "to_type"
727
+ ]
728
+ },
729
+ "throws": [
730
+ "Returns a textError when `from_type` or `to_type` is missing."
731
+ ],
732
+ "examples": [
733
+ {
734
+ "description": "Live call against the Notion example graph.",
735
+ "input": "{\n \"from_type\": \"jtbd\",\n \"to_type\": \"job\",\n \"dry_run\": true\n}",
736
+ "output": "{\n \"migrated_nodes\": 0,\n \"migrated_edges\": 0,\n \"edge_renames\": [],\n \"dropped_edges\": [],\n \"unmapped_legacy_edges\": [],\n \"defaults_applied\": null,\n \"dry_run\": true\n}"
737
+ }
738
+ ],
739
+ "warnings": [],
740
+ "see": [
741
+ "rename_edge_type",
742
+ "export_edges",
743
+ "update_node"
744
+ ],
745
+ "source": "src/tools/nodes.ts:1116",
746
+ "symbol": "migrateType",
747
+ "returns": "JSON: `{ migrated_nodes, migrated_edges, edge_renames,\ndropped_edges, unmapped_legacy_edges, defaults_applied, dry_run }`.\n`edge_renames` is `[{ id, from, to, flipped }]`; `dropped_edges` is\n`[{ id, from }]`; `unmapped_legacy_edges` is `[{ type, count }]`.\n`migrated_edges` is the total mutated count (renames + drops).",
748
+ "atomicity": "atomic. Single store-level migration call commits or fails as\none mutation. Note: full graph canonicalisation runs as a side-effect of\nany node-type migration, so unrelated legacy edges may also be retargeted."
749
+ },
750
+ {
751
+ "name": "query",
752
+ "description": "Traverse the graph following typed edges. Returns a subgraph (nodes + edges) in a single call. Example: query({ from: \"persona\", traverse: [\"persona_pursues_job\", \"job_surfaces_need\"], depth: 2 })",
753
+ "domain": "nodes",
754
+ "inputSchema": {
755
+ "type": "object",
756
+ "properties": {
757
+ "from": {
758
+ "type": "string",
759
+ "description": "Start from all nodes of this type"
760
+ },
761
+ "from_id": {
762
+ "type": "string",
763
+ "description": "Start from a specific node ID (alternative to from)"
764
+ },
765
+ "traverse": {
766
+ "type": "array",
767
+ "items": {
768
+ "type": "string"
769
+ },
770
+ "description": "Edge types to follow at each level (in order). If omitted, follows all edges. Prefix with ! to exclude (e.g. \"!product_builds_feature\")."
771
+ },
772
+ "depth": {
773
+ "type": "number",
774
+ "description": "Max traversal depth (default 3, max 10)"
775
+ },
776
+ "include": {
777
+ "type": "array",
778
+ "items": {
779
+ "type": "string"
780
+ },
781
+ "description": "Fields to include per node: \"title\", \"status\", \"tags\", \"description\", \"properties\" (default: title, status, type)"
782
+ },
783
+ "limit": {
784
+ "type": "number",
785
+ "description": "Max nodes to return (default 200, max 1000)"
786
+ },
787
+ "edge_include": {
788
+ "type": "array",
789
+ "items": {
790
+ "type": "string"
791
+ },
792
+ "description": "Edge fields to return: \"id\", \"type\", \"source\", \"target\". Empty array = no edges. Default: all fields."
793
+ },
794
+ "property_include": {
795
+ "type": "array",
796
+ "items": {
797
+ "type": "string"
798
+ },
799
+ "description": "When \"properties\" is in include, only return these property keys (e.g. [\"severity\", \"importance\"])"
800
+ },
801
+ "diff_from": {
802
+ "type": "string",
803
+ "description": "Result ID from a previous query. Returns only added/removed nodes since that result."
804
+ }
805
+ }
806
+ },
807
+ "throws": [
808
+ "Returns a textError when neither `from` nor `from_id` is provided,\nor when `from_id` does not exist."
809
+ ],
810
+ "examples": [
811
+ {
812
+ "description": "Live call against the Notion example graph.",
813
+ "input": "{\n \"from\": \"persona\",\n \"traverse\": [\n \"persona_pursues_job\"\n ],\n \"depth\": 1,\n \"limit\": 5,\n \"include\": [\n \"title\"\n ],\n \"edge_include\": []\n}",
814
+ "output": "{\n \"nodes\": [\n {\n \"id\": \"9cd363dc-b1be-4132-b1dc-043467866e57\",\n \"type\": \"persona\",\n \"title\": \"Akira the Admin\"\n },\n {\n \"id\": \"65234b60-eb2b-4c41-be63-7b17b2bc6a38\",\n \"type\": \"persona\",\n \"title\": \"Theo the Consultant\"\n },\n {\n \"id\": \"e956a8ef-288f-4ee2-b758-2a14b9d07206\",\n \"type\": \"persona\",\n \"title\": \"Lin the Thinker\"\n },\n {\n \"id\": \"2e8dd86d-6085-4068-a804-38d3e891ea1b\",\n \"type\": \"persona\",\n \"title\": \"Maya the Maker\"\n },\n {\n \"id\": \"7b2e7c5a-a73e-42c9-9416-e9e9ccfecc20\",\n \"type\": \"job\",\n \"title\": \"Build a team system that survives growth\"\n }\n ],\n \"edges\": [],\n \"total_nodes\": 5,\n \"total_edges\": 0,\n \"truncated\": true,\n \"truncated_at_depth\": 0,\n \"hint\": \"Limit of 5 nodes reached at depth 0. Increase limit to see deeper results.\",\n \"_result_id\": \"qr_1\"\n}"
815
+ }
816
+ ],
817
+ "warnings": [
818
+ "Pre-flight payload guardrail: refuses above\n`UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above\n`UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). Tighten with `include`\n(e.g. `[\"title\"]`) or `edge_include: []` to drop edges from the wire."
819
+ ],
820
+ "see": [
821
+ "list_nodes",
822
+ "get_area_graph"
823
+ ],
824
+ "source": "src/tools/nodes.ts:405",
825
+ "symbol": "query",
826
+ "returns": "JSON: `{ nodes, edges, total_nodes, total_edges, _result_id,\ntruncated?, truncated_at_depth?, diff? }`. The `_result_id` is a cache\nhandle for `diff_from`; cache holds the last 20 results.",
827
+ "atomicity": "atomic (read-only)"
828
+ },
829
+ {
830
+ "name": "search_nodes",
831
+ "description": "Search entities by text. Default fields: title (score 3) and description (score 1). Add `fields` to include tags (score 2) and properties (score 1). Results include `matched_field`.",
832
+ "domain": "nodes",
833
+ "inputSchema": {
834
+ "type": "object",
835
+ "properties": {
836
+ "query": {
837
+ "type": "string",
838
+ "description": "Search text (case-insensitive substring match)"
839
+ },
840
+ "type": {
841
+ "type": "string",
842
+ "description": "Optional type filter"
843
+ },
844
+ "fields": {
845
+ "type": "array",
846
+ "items": {
847
+ "type": "string"
848
+ },
849
+ "description": "Fields to search: \"title\", \"description\", \"tags\", \"properties\" (default: title + description)"
850
+ },
851
+ "limit": {
852
+ "type": "number",
853
+ "description": "Max results (default 20, max 100)"
854
+ }
855
+ },
856
+ "required": [
857
+ "query"
858
+ ]
859
+ },
860
+ "throws": [
861
+ "Returns a textError when `query` is missing."
862
+ ],
863
+ "examples": [
864
+ {
865
+ "description": "Live call against the Notion example graph.",
866
+ "input": "{\n \"query\": \"Notion\"\n}",
867
+ "output": "{\n \"results\": [\n {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"7f5ae55d-68c5-4ca7-9e4c-b5e9d4dd32a5\",\n \"type\": \"design_system\",\n \"title\": \"Notion Design System · Block-primitive foundation\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"24b43d57-48a4-4499-bac7-e38f3eb7bae4\",\n \"type\": \"design_system\",\n \"title\": \"Notion Design System · Database views\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"6700fd68-27be-4b21-9186-82e490bebf33\",\n \"type\": \"workspace\",\n \"title\": \"Notion Internal · Architecture Decisions workspace\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"c355f3ea-1e9f-45a1-aedc-a7adf66050e1\",\n \"type\": \"product\",\n \"title\": \"Notion Calendar\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"2a3ab02e-4110-43f2-848b-a1ebeedb6fa9\",\n \"type\": \"product\",\n \"title\": \"Notion Mail (preview)\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\": \"e83e8cec-8f00-4dd5-b36a-c5e143f7e911\",\n \"type\": \"design_system\",\n \"title\": \"Notion Design System · Mobile parity tokens\",\n \"match_field\": \"title\",\n \"score\": 3\n },\n {\n \"id\":\n… (truncated)"
868
+ }
869
+ ],
870
+ "warnings": [],
871
+ "see": [
872
+ "list_nodes",
873
+ "query"
874
+ ],
875
+ "source": "src/tools/nodes.ts:354",
876
+ "symbol": "searchNodes",
877
+ "returns": "JSON: `{ results: Array<{ id, type, title, status, tags,\nmatch_field, score }>, total, searched_fields }`.",
878
+ "atomicity": "atomic (read-only)"
879
+ },
880
+ {
881
+ "name": "update_node",
882
+ "description": "Update one entity. Unspecified fields are preserved. Passing `type` performs an atomic single-node migration: every incident edge is re-inferred against the catalog and rollback applies on failure. For 3+ entities, use `batch_update_nodes`.",
883
+ "domain": "nodes",
884
+ "inputSchema": {
885
+ "type": "object",
886
+ "properties": {
887
+ "node_id": {
888
+ "type": "string",
889
+ "description": "The node ID to update"
890
+ },
891
+ "type": {
892
+ "type": "string",
893
+ "description": "Change the entity type. Atomic single-node migration: validates against UPG_TYPES, rewrites incident edges to canonical types."
894
+ },
895
+ "title": {
896
+ "type": "string"
897
+ },
898
+ "description": {
899
+ "type": "string"
900
+ },
901
+ "tags": {
902
+ "type": "array",
903
+ "items": {
904
+ "type": "string"
905
+ }
906
+ },
907
+ "status": {
908
+ "type": "string"
909
+ },
910
+ "properties": {
911
+ "type": "object",
912
+ "description": "Merged with existing properties"
913
+ }
914
+ },
915
+ "required": [
916
+ "node_id"
917
+ ]
918
+ },
919
+ "throws": [
920
+ "Returns a textError when `node_id` is missing, the type migration\nfails, when `strict: true` and unknown properties are present, or when\nthe underlying store rejects the patch."
921
+ ],
922
+ "examples": [
923
+ {
924
+ "description": "Live call against the Notion example graph.",
925
+ "input": "{\n \"node_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\"\n}",
926
+ "output": "{\n \"node\": {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"slug\": \"notion-saturated-test-graph\"\n }\n}"
927
+ }
928
+ ],
929
+ "warnings": [],
930
+ "see": [
931
+ "migrate_type",
932
+ "batch_update_nodes"
933
+ ],
934
+ "source": "src/tools/nodes.ts:841",
935
+ "symbol": "updateNode",
936
+ "returns": "JSON: `{ node, warning?, unknown_properties? }`. `warning`\naggregates lifecycle-status hints, migration warnings, and any\nunknown-property notice. `unknown_properties` lists property\nkeys not in the entity's schema. Pass `strict: true` to reject unknown\nproperties instead of warning.",
937
+ "atomicity": "atomic-with-rollback (when `type` is changed); atomic for\nshallow-merge patches."
938
+ },
939
+ {
940
+ "name": "batch_create_edges",
941
+ "description": "Create up to 50 edges in one atomic call. Use this for 3+ edges instead of looping `create_edge`. Edge type auto-infers when omitted.",
942
+ "domain": "edges",
943
+ "inputSchema": {
944
+ "type": "object",
945
+ "properties": {
946
+ "edges": {
947
+ "type": "array",
948
+ "items": {
949
+ "type": "object",
950
+ "properties": {
951
+ "source_id": {
952
+ "type": "string",
953
+ "description": "Source node ID"
954
+ },
955
+ "target_id": {
956
+ "type": "string",
957
+ "description": "Target node ID"
958
+ },
959
+ "type": {
960
+ "type": "string",
961
+ "description": "Edge type. Auto-inferred if omitted."
962
+ }
963
+ },
964
+ "required": [
965
+ "source_id",
966
+ "target_id"
967
+ ]
968
+ },
969
+ "description": "Array of edges to create (max 50)"
970
+ }
971
+ },
972
+ "required": [
973
+ "edges"
974
+ ]
975
+ },
976
+ "throws": [
977
+ "Returns a textError when `edges` is missing/non-array, empty,\nlonger than 50, or any item references a missing endpoint or unresolvable\nedge type."
978
+ ],
979
+ "examples": [],
980
+ "warnings": [],
981
+ "see": [
982
+ "create_edge"
983
+ ],
984
+ "source": "src/tools/edges.ts:209",
985
+ "symbol": "batchCreateEdges",
986
+ "returns": "JSON: `{ created, count }`.",
987
+ "atomicity": "atomic. Full validation pass before any mutation lands."
988
+ },
989
+ {
990
+ "name": "batch_delete_edges",
991
+ "description": "Delete up to 50 edges in one atomic call (all succeed or all fail).",
992
+ "domain": "edges",
993
+ "inputSchema": {
994
+ "type": "object",
995
+ "properties": {
996
+ "edge_ids": {
997
+ "type": "array",
998
+ "items": {
999
+ "type": "string"
1000
+ },
1001
+ "description": "Array of edge IDs to delete (max 50)"
1002
+ }
1003
+ },
1004
+ "required": [
1005
+ "edge_ids"
1006
+ ]
1007
+ },
1008
+ "throws": [
1009
+ "Returns a textError when `edge_ids` is missing/non-array, empty,\nlonger than 50, or any ID does not resolve."
1010
+ ],
1011
+ "examples": [
1012
+ {
1013
+ "description": "Live call against the Notion example graph.",
1014
+ "input": "{\n \"edge_ids\": [\n \"8a3b23fd-172e-4151-8283-aee40eeb4c0d\"\n ]\n}",
1015
+ "output": "{\n \"deleted\": [\n {\n \"id\": \"8a3b23fd-172e-4151-8283-aee40eeb4c0d\",\n \"type\": \"opportunity_drives_solution\"\n }\n ],\n \"count\": 1\n}"
1016
+ }
1017
+ ],
1018
+ "warnings": [],
1019
+ "see": [
1020
+ "delete_edge"
1021
+ ],
1022
+ "source": "src/tools/edges.ts:293",
1023
+ "symbol": "batchDeleteEdges",
1024
+ "returns": "JSON: `{ deleted, count }`.",
1025
+ "atomicity": "atomic. Validation pass rejects the batch before any mutation\nlands."
1026
+ },
1027
+ {
1028
+ "name": "batch_move_nodes",
1029
+ "description": "Apply up to 50 atomic re-parents. All moves validate against the schema first; any failure rolls back the whole batch.",
1030
+ "domain": "edges",
1031
+ "inputSchema": {
1032
+ "type": "object",
1033
+ "properties": {
1034
+ "moves": {
1035
+ "type": "array",
1036
+ "maxItems": 50,
1037
+ "items": {
1038
+ "type": "object",
1039
+ "properties": {
1040
+ "node_id": {
1041
+ "type": "string"
1042
+ },
1043
+ "new_parent_id": {
1044
+ "type": "string"
1045
+ },
1046
+ "new_edge_type": {
1047
+ "type": "string"
1048
+ },
1049
+ "old_edge_id": {
1050
+ "type": "string"
1051
+ }
1052
+ },
1053
+ "required": [
1054
+ "node_id",
1055
+ "new_parent_id"
1056
+ ]
1057
+ }
1058
+ }
1059
+ },
1060
+ "required": [
1061
+ "moves"
1062
+ ]
1063
+ },
1064
+ "throws": [
1065
+ "Returns a textError when `moves` is missing/non-array or any move\nfails validation."
1066
+ ],
1067
+ "examples": [],
1068
+ "warnings": [],
1069
+ "see": [
1070
+ "move_node"
1071
+ ],
1072
+ "source": "src/tools/edges.ts:177",
1073
+ "symbol": "batchMoveNodes",
1074
+ "returns": "JSON: `{ moves, warnings? }` mirroring the per-move result of\n`move_node`.",
1075
+ "atomicity": "atomic-with-rollback."
1076
+ },
1077
+ {
1078
+ "name": "create_edge",
1079
+ "description": "Create one edge between two nodes. Edge type auto-infers when omitted. Target accepts an ID, or a title+type pair the server resolves. For 3+ edges, use `batch_create_edges`.",
1080
+ "domain": "edges",
1081
+ "inputSchema": {
1082
+ "type": "object",
1083
+ "properties": {
1084
+ "source_id": {
1085
+ "type": "string",
1086
+ "description": "Source node ID"
1087
+ },
1088
+ "target_id": {
1089
+ "type": "string",
1090
+ "description": "Target node ID"
1091
+ },
1092
+ "target_title": {
1093
+ "type": "string",
1094
+ "description": "Target node title (alternative to target_id; requires target_type)."
1095
+ },
1096
+ "target_type": {
1097
+ "type": "string",
1098
+ "description": "Target node type (used with target_title for resolution)"
1099
+ },
1100
+ "type": {
1101
+ "type": "string",
1102
+ "description": "Edge type. Auto-inferred if omitted."
1103
+ }
1104
+ },
1105
+ "required": [
1106
+ "source_id"
1107
+ ]
1108
+ },
1109
+ "throws": [
1110
+ "Returns a textError when `source_id` is missing, the target cannot\nbe resolved, or the edge violates the catalog."
1111
+ ],
1112
+ "examples": [
1113
+ "// Wire a persona to a job using the canonical edge type persona_pursues_job\n// Input:\n{ \"source_id\": \"persona_01\", \"target_id\": \"job_03\", \"type\": \"persona_pursues_job\" }\n// Output (truncated):\n{\n \"edge\": { \"id\": \"edge_15\", \"type\": \"persona_pursues_job\", \"source\": \"persona_01\", \"target\": \"job_03\" },\n \"inferred\": false\n}"
1114
+ ],
1115
+ "warnings": [],
1116
+ "see": [
1117
+ "batch_create_edges",
1118
+ "resolve_edge_for_pair",
1119
+ "list_edge_types",
1120
+ "get_edge_type"
1121
+ ],
1122
+ "source": "src/tools/edges.ts:85",
1123
+ "symbol": "createEdge",
1124
+ "returns": "JSON: the created edge object plus optional resolution metadata.",
1125
+ "atomicity": "atomic. Single store mutation."
1126
+ },
1127
+ {
1128
+ "name": "delete_edge",
1129
+ "description": "Remove one edge by ID.",
1130
+ "domain": "edges",
1131
+ "inputSchema": {
1132
+ "type": "object",
1133
+ "properties": {
1134
+ "edge_id": {
1135
+ "type": "string",
1136
+ "description": "The edge ID to delete"
1137
+ }
1138
+ },
1139
+ "required": [
1140
+ "edge_id"
1141
+ ]
1142
+ },
1143
+ "throws": [
1144
+ "Returns a textError when `edge_id` is missing or does not resolve."
1145
+ ],
1146
+ "examples": [],
1147
+ "warnings": [],
1148
+ "see": [
1149
+ "batch_delete_edges",
1150
+ "export_edges",
1151
+ "repair_dangling_edges"
1152
+ ],
1153
+ "source": "src/tools/edges.ts:122",
1154
+ "symbol": "deleteEdge",
1155
+ "returns": "JSON: the removed edge object.",
1156
+ "atomicity": "atomic."
1157
+ },
1158
+ {
1159
+ "name": "export_edges",
1160
+ "description": "Flat edge enumeration. Returns every edge of the listed `types` (or all edges when `types` is omitted) as `{id, source, target, type}` with no parent-node payload. Right for migration and canonicalisation passes. Paginates via offset/limit.",
1161
+ "domain": "edges",
1162
+ "inputSchema": {
1163
+ "type": "object",
1164
+ "properties": {
1165
+ "types": {
1166
+ "type": "array",
1167
+ "items": {
1168
+ "type": "string"
1169
+ },
1170
+ "description": "Filter by exact edge-type match. Omit to enumerate every edge in the document."
1171
+ },
1172
+ "offset": {
1173
+ "type": "number",
1174
+ "description": "Skip N results (default 0)"
1175
+ },
1176
+ "limit": {
1177
+ "type": "number",
1178
+ "description": "Max results (default 500, max 2000)"
1179
+ },
1180
+ "if_changed_since": {
1181
+ "type": "string",
1182
+ "description": "Hash from a previous response. Returns { changed: false } if graph unchanged."
1183
+ }
1184
+ }
1185
+ },
1186
+ "throws": [
1187
+ "Returns a textError when `types` is supplied but is not an array of\nstrings, or when the page would exceed the hard payload limit."
1188
+ ],
1189
+ "examples": [
1190
+ {
1191
+ "description": "Live call against the Notion example graph.",
1192
+ "input": "{}",
1193
+ "output": "{\n \"edges\": [\n {\n \"id\": \"44012ce7-99d5-4a96-b930-c8f7b1d6b45f\",\n \"source\": \"3197ef3e-4ffc-48cf-9903-59db8eed94f8\",\n \"target\": \"a719b36e-97a9-4927-b27c-f95f0da05d8c\",\n \"type\": \"opportunity_drives_solution\"\n },\n {\n \"id\": \"d7aaba50-cf2e-4a46-ac1b-15e61aa49212\",\n \"source\": \"d8c4bbfb-669d-4e29-b75e-fba353455255\",\n \"target\": \"d272290a-7006-4372-b4f4-0deb59161d39\",\n \"type\": \"opportunity_drives_solution\"\n },\n {\n \"id\": \"4c3e592e-61f3-4d7d-a7c9-486fa3a5cb6d\",\n \"source\": \"88766ab4-a3e7-40be-bffa-469882937e47\",\n \"target\": \"d8b467ef-9b3f-4d52-994e-ada00327f73e\",\n \"type\": \"opportunity_drives_solution\"\n },\n {\n \"id\": \"676dcc0e-7fa6-46c0-b0d3-0c4cc0e10982\",\n \"source\": \"b5df3308-dc29-4958-aae7-123bc616217d\",\n \"target\": \"425b4721-6c79-4da7-9b94-a66f97d308ed\",\n \"type\": \"opportunity_drives_solution\"\n },\n {\n \"id\": \"6e4b2226-2d47-4e3a-ba14-4291a2e8896b\",\n \"source\": \"3197ef3e-4ffc-48cf-9903-59db8eed94f8\",\n \"target\": \"b17e4f0e-5ed2-4170-a754-b17cafb42b21\",\n \"type\": \"opportunity_assessed_by_feasibility_study\"\n },\n {\n \"id\": \"5101bf03-3d91-4da1-b3e8-33363a117227\",\n \"source\": \"87d8086c-ac51-4021-882a-7728c394f6ab\",\n \"target\": \"dade8cd7-ffae-4800-9f72-c8767520a21c\",\n \"type\": \"opportunity_assessed_by_feasibility_study\"\n },\n {\n \"id\":\n… (truncated)"
1194
+ }
1195
+ ],
1196
+ "warnings": [],
1197
+ "see": [
1198
+ "query",
1199
+ "list_nodes"
1200
+ ],
1201
+ "source": "src/tools/edges.ts:403",
1202
+ "symbol": "exportEdges",
1203
+ "returns": "JSON: `{ edges, total, offset, limit, types?, _hash }`. Each edge\ncarries `{ id, source, target, type, mapping_confidence? }`.",
1204
+ "atomicity": "atomic (read-only)"
1205
+ },
1206
+ {
1207
+ "name": "move_node",
1208
+ "description": "Atomic re-parent. Removes any existing hierarchy edge and creates a new one to `new_parent_id`. Validates against `UPG_EDGE_CATALOG` first; rolls back fully on failure.",
1209
+ "domain": "edges",
1210
+ "inputSchema": {
1211
+ "type": "object",
1212
+ "properties": {
1213
+ "node_id": {
1214
+ "type": "string",
1215
+ "description": "The node to re-parent"
1216
+ },
1217
+ "new_parent_id": {
1218
+ "type": "string",
1219
+ "description": "The new parent node id"
1220
+ },
1221
+ "new_edge_type": {
1222
+ "type": "string",
1223
+ "description": "Optional override. Must be a key in UPG_EDGE_CATALOG. If omitted, the edge type is inferred from new_parent.type → node.type."
1224
+ },
1225
+ "old_edge_id": {
1226
+ "type": "string",
1227
+ "description": "Required when the node has more than one hierarchy edge. Picks which one to delete."
1228
+ }
1229
+ },
1230
+ "required": [
1231
+ "node_id",
1232
+ "new_parent_id"
1233
+ ]
1234
+ },
1235
+ "throws": [
1236
+ "Returns a textError when `node_id` or `new_parent_id` is missing,\nwhen the inferred edge type is invalid, or when the node has multiple\nhierarchy edges and `old_edge_id` was not supplied."
1237
+ ],
1238
+ "examples": [],
1239
+ "warnings": [],
1240
+ "see": [
1241
+ "batch_move_nodes"
1242
+ ],
1243
+ "source": "src/tools/edges.ts:149",
1244
+ "symbol": "moveNode",
1245
+ "returns": "JSON: `{ moved: true, node_id, new_parent_id, new_edge,\nold_edge_id?, warning? }`. The internal `removed_edge` field is stripped\nfrom the wire payload.",
1246
+ "atomicity": "atomic-with-rollback. Pre-validates the new edge before\ntouching the old one."
1247
+ },
1248
+ {
1249
+ "name": "rename_edge_type",
1250
+ "description": "Exact-match rename of every edge of type `from` to type `to`, optionally flipping source/target. Single transactional pass. Defaults to `dry_run: true`; pass `dry_run: false` to commit. Low-level primitive: skips catalog validation. Use catalog-aware migration tools for validated renames.",
1251
+ "domain": "edges",
1252
+ "inputSchema": {
1253
+ "type": "object",
1254
+ "properties": {
1255
+ "from": {
1256
+ "type": "string",
1257
+ "description": "Current edge type (exact match)"
1258
+ },
1259
+ "to": {
1260
+ "type": "string",
1261
+ "description": "New edge type to assign"
1262
+ },
1263
+ "flip": {
1264
+ "type": "boolean",
1265
+ "description": "When true, swap source/target on each renamed edge (default false)"
1266
+ },
1267
+ "dry_run": {
1268
+ "type": "boolean",
1269
+ "description": "Preview without mutating (default true)"
1270
+ }
1271
+ },
1272
+ "required": [
1273
+ "from",
1274
+ "to"
1275
+ ]
1276
+ },
1277
+ "throws": [
1278
+ "Returns a textError when `from` or `to` is missing, when they are\nequal and `flip` is false (no-op), or when `from === to` with `flip: true`\non zero matches (still safe but the call is degenerate)."
1279
+ ],
1280
+ "examples": [],
1281
+ "warnings": [],
1282
+ "see": [
1283
+ "migrate_type",
1284
+ "export_edges"
1285
+ ],
1286
+ "source": "src/tools/edges.ts:490",
1287
+ "symbol": "renameEdgeType",
1288
+ "returns": "JSON: with `dry_run: true`, `{ dry_run, from, to, flip, would_rename, sample }`.\nWith `dry_run: false`, `{ dry_run, from, to, flip, renamed, ids }`.",
1289
+ "atomicity": "atomic. Single-pass mutation; an empty match-set is a clean\nno-op rather than an error."
1290
+ },
1291
+ {
1292
+ "name": "repair_dangling_edges",
1293
+ "description": "Inspect or drop edges whose source or target node fails to resolve. Each is classified `expected` (cross-product, sibling not loaded; keep), `suspect` (cross-product, missing product-id annotation), or `corrupt` (broken endpoint on a non-cross edge). Defaults to `dry_run: true`. Pass `dry_run: false` plus `drop: [\"suspect\", \"corrupt\"]` to remove.",
1294
+ "domain": "edges",
1295
+ "inputSchema": {
1296
+ "type": "object",
1297
+ "properties": {
1298
+ "dry_run": {
1299
+ "type": "boolean",
1300
+ "description": "When true (default), returns the classification report without mutating. When false, drops edges matching `drop`."
1301
+ },
1302
+ "drop": {
1303
+ "type": "array",
1304
+ "items": {
1305
+ "type": "string",
1306
+ "enum": [
1307
+ "expected",
1308
+ "suspect",
1309
+ "corrupt"
1310
+ ]
1311
+ },
1312
+ "description": "Classes of dangling edge to drop. Only honoured when dry_run is false. Omit to no-op."
1313
+ }
1314
+ }
1315
+ },
1316
+ "throws": [
1317
+ "Returns a textError when `drop` is provided alongside\n`dry_run: true` (ambiguous), or when `drop` includes an unknown class."
1318
+ ],
1319
+ "examples": [
1320
+ {
1321
+ "description": "Live call against the Notion example graph.",
1322
+ "input": "{}",
1323
+ "output": "{\n \"dry_run\": true,\n \"report\": {\n \"total\": 0,\n \"by_class\": {\n \"expected\": 0,\n \"suspect\": 0,\n \"corrupt\": 0\n },\n \"edges\": []\n }\n}"
1324
+ }
1325
+ ],
1326
+ "warnings": [
1327
+ "Dropping `corrupt` edges is irreversible. The integrity stamp is\nre-computed on next save; a subsequent reload won't bring them back."
1328
+ ],
1329
+ "see": [],
1330
+ "source": "src/tools/edges.ts:337",
1331
+ "symbol": "repairDanglingEdges",
1332
+ "returns": "JSON: `{ dry_run, report, dropped?, remaining? }`. `report` is\nthe pre-action classification. With `dry_run: false`, `dropped` is the\ncount of edges removed and `remaining` is the post-action report.",
1333
+ "atomicity": "atomic-with-rollback. Classification runs against the live\ndocument before any mutation; with `dry_run: false`, the drop set is\ncomputed up-front and applied in a single index rebuild."
1334
+ },
1335
+ {
1336
+ "name": "create_area",
1337
+ "description": "Create a product area entity in the portfolio document (`.upg/portfolio.upg`). Product areas represent the organisational axis (who owns what). Supports nesting via `parent_area_id`. The portfolio document is created on demand.",
1338
+ "domain": "areas",
1339
+ "inputSchema": {
1340
+ "type": "object",
1341
+ "properties": {
1342
+ "title": {
1343
+ "type": "string",
1344
+ "description": "Area name (e.g. \"Search\", \"Payments\")"
1345
+ },
1346
+ "description": {
1347
+ "type": "string",
1348
+ "description": "What this area covers"
1349
+ },
1350
+ "parent_area_id": {
1351
+ "type": "string",
1352
+ "description": "Parent area ID for creating a sub-area"
1353
+ },
1354
+ "strategic_priority": {
1355
+ "type": "string",
1356
+ "enum": [
1357
+ "critical",
1358
+ "high",
1359
+ "medium",
1360
+ "low"
1361
+ ],
1362
+ "description": "Strategic priority of this area"
1363
+ },
1364
+ "owner": {
1365
+ "type": "string",
1366
+ "description": "Person or team that owns this area"
1367
+ }
1368
+ },
1369
+ "required": [
1370
+ "title"
1371
+ ]
1372
+ },
1373
+ "throws": [
1374
+ "Returns a textError when `title` is missing or the portfolio write\nfails."
1375
+ ],
1376
+ "examples": [
1377
+ {
1378
+ "description": "Live call against the Notion example graph.",
1379
+ "input": "{\n \"title\": \"Example node\"\n}",
1380
+ "output": "{\n \"node\": {\n \"id\": \"n_cyDWeH77JTYEF4t_\",\n \"title\": \"Example node\"\n },\n \"portfolio_file\": \"/Users/FH/Documents/_Code/tpc-worktrees/wt-cli-section/packages/upg-mcp-server/.upg/portfolio.upg\",\n \"written_to\": \"product_areas\"\n}"
1381
+ }
1382
+ ],
1383
+ "warnings": [],
1384
+ "see": [
1385
+ "list_product_areas"
1386
+ ],
1387
+ "source": "src/tools/areas.ts:261",
1388
+ "symbol": "createArea",
1389
+ "returns": "JSON: `{ node, portfolio_file, written_to }`. `node` is the typed\n`UPGProductArea` record persisted to `portfolio_areas[]`.",
1390
+ "atomicity": "atomic per write — the portfolio file is read, mutated, and\nflushed in one pass."
1391
+ },
1392
+ {
1393
+ "name": "get_area_context",
1394
+ "description": "Check whether the current working directory has a `.upg-area.json` that scopes work to a specific product area.",
1395
+ "domain": "areas",
1396
+ "inputSchema": {
1397
+ "type": "object",
1398
+ "properties": {}
1399
+ },
1400
+ "throws": [],
1401
+ "examples": [
1402
+ {
1403
+ "description": "Live call against the Notion example graph.",
1404
+ "input": "{}",
1405
+ "output": "{\n \"has_area_context\": false\n}"
1406
+ }
1407
+ ],
1408
+ "warnings": [],
1409
+ "see": [],
1410
+ "source": "src/tools/areas.ts:211",
1411
+ "symbol": "getAreaContext",
1412
+ "returns": "JSON: `{ has_area_context: false }` or\n`{ has_area_context: true, area_id, area_name, found_at }`.",
1413
+ "atomicity": "atomic (read-only)"
1414
+ },
1415
+ {
1416
+ "name": "get_area_graph",
1417
+ "description": "Return the sub-graph (entities and edges) scoped to a product area.",
1418
+ "domain": "areas",
1419
+ "inputSchema": {
1420
+ "type": "object",
1421
+ "properties": {
1422
+ "area_id": {
1423
+ "type": "string",
1424
+ "description": "The product area node ID"
1425
+ },
1426
+ "depth": {
1427
+ "type": "number",
1428
+ "description": "How many levels deep to traverse (default 3, max 10)"
1429
+ }
1430
+ },
1431
+ "required": [
1432
+ "area_id"
1433
+ ]
1434
+ },
1435
+ "throws": [
1436
+ "Returns a textError when `area_id` is missing, the node does not\nexist, or the node is not a `product_area`."
1437
+ ],
1438
+ "examples": [],
1439
+ "warnings": [
1440
+ "Pre-flight payload guardrail: refuses above\n`UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above\n`UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). Reduce `depth` or use\n`query` with a tight projection if the area has many neighbours.",
1441
+ "Auto-degrade: between soft and hard, the server may\ncompact edges, drop optional node fields, or truncate. Surfaced as\n`degraded.applied[]` on the response."
1442
+ ],
1443
+ "see": [
1444
+ "list_product_areas"
1445
+ ],
1446
+ "source": "src/tools/areas.ts:69",
1447
+ "symbol": "getAreaGraph",
1448
+ "returns": "JSON: `{ area, nodes, edges, node_count, edge_count }`. May\ninclude a `degraded` block when the response was auto-trimmed.",
1449
+ "atomicity": "atomic (read-only)"
1450
+ },
1451
+ {
1452
+ "name": "get_changes",
1453
+ "description": "Mutation log for this session. Verify what was created, updated, or deleted without re-fetching.",
1454
+ "domain": "areas",
1455
+ "inputSchema": {
1456
+ "type": "object",
1457
+ "properties": {
1458
+ "since": {
1459
+ "type": "string",
1460
+ "description": "ISO 8601 timestamp. Only returns changes after this time (default: all session changes)."
1461
+ }
1462
+ }
1463
+ },
1464
+ "throws": [],
1465
+ "examples": [
1466
+ {
1467
+ "description": "Live call against the Notion example graph.",
1468
+ "input": "{}",
1469
+ "output": "{\n \"changes\": [\n {\n \"action\": \"create\",\n \"entity\": \"node\",\n \"id\": \"n_LSjd64yzUUdx5X9r\",\n \"type\": \"person\",\n \"title\": \"Example node\",\n \"timestamp\": \"2026-05-26T13:23:52.872Z\"\n },\n {\n \"action\": \"update\",\n \"entity\": \"node\",\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"timestamp\": \"2026-05-26T13:23:52.873Z\"\n },\n {\n \"action\": \"delete\",\n \"entity\": \"node\",\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"timestamp\": \"2026-05-26T13:23:52.874Z\"\n },\n {\n \"action\": \"delete\",\n \"entity\": \"edge\",\n \"id\": \"1d645a2e-391b-4bc7-b0de-e15691c8a7d6\",\n \"type\": \"cascade\",\n \"timestamp\": \"2026-05-26T13:23:52.874Z\"\n },\n {\n \"action\": \"delete\",\n \"entity\": \"edge\",\n \"id\": \"b4a77a49-7d28-4926-831f-464574d927fe\",\n \"type\": \"cascade\",\n \"timestamp\": \"2026-05-26T13:23:52.874Z\"\n },\n {\n \"action\": \"delete\",\n \"entity\": \"edge\",\n \"id\": \"1ee422bc-64c1-4da1-b797-62e4d206b79c\",\n \"type\": \"cascade\",\n \"timestamp\": \"2026-05-26T13:23:52.874Z\"\n },\n {\n \"action\": \"delete\",\n \"entity\": \"edge\",\n \"id\": \"013ac760-b99a-423e-bb94-700acda058c0\",\n \"type\": \"cascade\",\n \"timestamp\":\n… (truncated)"
1470
+ }
1471
+ ],
1472
+ "warnings": [],
1473
+ "see": [],
1474
+ "source": "src/tools/areas.ts:300",
1475
+ "symbol": "getChanges",
1476
+ "returns": "JSON: `{ changes, summary: { create, update, delete }, total }`.\n`since` filters to ISO 8601 timestamps after the cutoff.",
1477
+ "atomicity": "atomic (read-only)"
1478
+ },
1479
+ {
1480
+ "name": "list_product_areas",
1481
+ "description": "List product areas from the portfolio document (`.upg/portfolio.upg`). Returns an empty list when no portfolio document exists yet.",
1482
+ "domain": "areas",
1483
+ "inputSchema": {
1484
+ "type": "object",
1485
+ "properties": {}
1486
+ },
1487
+ "throws": [],
1488
+ "examples": [
1489
+ {
1490
+ "description": "Live call against the Notion example graph.",
1491
+ "input": "{}",
1492
+ "output": "{\n \"areas\": [],\n \"total\": 0\n}"
1493
+ }
1494
+ ],
1495
+ "warnings": [],
1496
+ "see": [
1497
+ "create_area",
1498
+ "get_area_graph"
1499
+ ],
1500
+ "source": "src/tools/areas.ts:33",
1501
+ "symbol": "listProductAreas",
1502
+ "returns": "JSON: `{ areas: Array<{ id, title, strategic_priority?,\nparent_area_id?, products? }>, total }`.",
1503
+ "atomicity": "atomic (read-only)"
1504
+ },
1505
+ {
1506
+ "name": "create_cross_product_edge",
1507
+ "description": "Create a cross-product relationship between two entities in different products within a portfolio graph. Types: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`.",
1508
+ "domain": "workspace",
1509
+ "inputSchema": {
1510
+ "type": "object",
1511
+ "properties": {
1512
+ "source_id": {
1513
+ "type": "string",
1514
+ "description": "Source node ID"
1515
+ },
1516
+ "target_id": {
1517
+ "type": "string",
1518
+ "description": "Target node ID"
1519
+ },
1520
+ "type": {
1521
+ "type": "string",
1522
+ "enum": [
1523
+ "shares_persona",
1524
+ "shares_competitor",
1525
+ "shares_metric",
1526
+ "depends_on_product",
1527
+ "cannibalises",
1528
+ "succeeds"
1529
+ ],
1530
+ "description": "Cross-product relationship type"
1531
+ },
1532
+ "source_product_id": {
1533
+ "type": "string",
1534
+ "description": "Product ID of the source node"
1535
+ },
1536
+ "target_product_id": {
1537
+ "type": "string",
1538
+ "description": "Product ID of the target node"
1539
+ }
1540
+ },
1541
+ "required": [
1542
+ "source_id",
1543
+ "target_id",
1544
+ "type"
1545
+ ]
1546
+ },
1547
+ "throws": [
1548
+ "Returns a textError when parameters are missing or invalid, or\nwhen the workspace is not initialised."
1549
+ ],
1550
+ "examples": [],
1551
+ "warnings": [],
1552
+ "see": [
1553
+ "list_portfolios",
1554
+ "list_portfolio_cross_edges",
1555
+ "migrate_cross_edges"
1556
+ ],
1557
+ "source": "src/tools/workspace.ts:395",
1558
+ "symbol": "createCrossProductEdge",
1559
+ "returns": "JSON: `{ edge, portfolio_file }`.",
1560
+ "atomicity": "non-atomic. Portfolio file create (if new) + edge append are\nseparate filesystem operations."
1561
+ },
1562
+ {
1563
+ "name": "create_product",
1564
+ "description": "Create a sibling .upg product in the current workspace. Mints a canonical product id, writes the file, stamps integrity, registers in `workspace.json`. Pairs with `init_workspace` and `switch_product`.",
1565
+ "domain": "workspace",
1566
+ "inputSchema": {
1567
+ "type": "object",
1568
+ "properties": {
1569
+ "name": {
1570
+ "type": "string",
1571
+ "description": "Product display title (required, non-empty)."
1572
+ },
1573
+ "slug": {
1574
+ "type": "string",
1575
+ "description": "Optional slug for the .upg filename. Defaults to a slug derived from `name`. Collisions append `-2`, `-3`, …"
1576
+ },
1577
+ "description": {
1578
+ "type": "string",
1579
+ "description": "Optional product description"
1580
+ },
1581
+ "stage": {
1582
+ "type": "string",
1583
+ "description": "Product lifecycle stage. See UPGProductStage in @unified-product-graph/core."
1584
+ },
1585
+ "portfolio_id": {
1586
+ "type": "string",
1587
+ "description": "Optional portfolio node id in the current store. When provided, a `portfolio_contains_product` edge is created in the current graph."
1588
+ }
1589
+ },
1590
+ "required": [
1591
+ "name"
1592
+ ]
1593
+ },
1594
+ "throws": [
1595
+ "Returns a textError when the workspace is uninitialised\n(`WorkspaceNotInitialisedError`) or the name is invalid\n(`InvalidProductNameError`)."
1596
+ ],
1597
+ "examples": [
1598
+ {
1599
+ "description": "Live call against the Notion example graph.",
1600
+ "input": "{\n \"name\": \"example\"\n}",
1601
+ "output": "{\n \"message\": \"Created product: example\",\n \"id\": \"p_1IGfQKJJbvHp3zuo\",\n \"file\": \"example.upg\",\n \"slug\": \"example\",\n \"title\": \"example\",\n \"workspace_path\": \".upg/\",\n \"portfolio_attached\": false\n}"
1602
+ }
1603
+ ],
1604
+ "warnings": [],
1605
+ "see": [
1606
+ "init_workspace"
1607
+ ],
1608
+ "source": "src/tools/workspace.ts:275",
1609
+ "symbol": "createProductTool",
1610
+ "returns": "JSON: `{ message, ...result }`. `result` carries `id`, `title`,\n`slug`, `file_path`, and the optional portfolio edge.",
1611
+ "atomicity": "non-atomic. File write + workspace.json patch + optional\nportfolio edge are separate mutations."
1612
+ },
1613
+ {
1614
+ "name": "get_organization",
1615
+ "description": "Get the organisation that owns the current workspace's portfolio. Reads the singleton `portfolio.upg.organization`. Returns `{ organization: null }` when no portfolio document exists yet.",
1616
+ "domain": "workspace",
1617
+ "inputSchema": {
1618
+ "type": "object",
1619
+ "properties": {}
1620
+ },
1621
+ "throws": [],
1622
+ "examples": [
1623
+ {
1624
+ "description": "Live call against the Notion example graph.",
1625
+ "input": "{}",
1626
+ "output": "{\n \"organization\": {\n \"id\": \"org_1853eb2e\",\n \"title\": \"Portfolio\"\n },\n \"portfolio_file\": \".upg/portfolio.upg\"\n}"
1627
+ }
1628
+ ],
1629
+ "warnings": [],
1630
+ "see": [
1631
+ "list_portfolios"
1632
+ ],
1633
+ "source": "src/tools/workspace.ts:344",
1634
+ "symbol": "getOrganization",
1635
+ "returns": "JSON: `{ organization: UPGOrganization | null, portfolio_file? }`.\nReturns `{ organization: null }` when no portfolio document exists yet.",
1636
+ "atomicity": "atomic (read-only)"
1637
+ },
1638
+ {
1639
+ "name": "get_workspace_info",
1640
+ "description": "Workspace info: which product is loaded, what other products are available, current workspace mode.",
1641
+ "domain": "workspace",
1642
+ "inputSchema": {
1643
+ "type": "object",
1644
+ "properties": {}
1645
+ },
1646
+ "throws": [],
1647
+ "examples": [
1648
+ {
1649
+ "description": "Live call against the Notion example graph.",
1650
+ "input": "{}",
1651
+ "output": "{\n \"mode\": \"single-file\",\n \"current_file\": \"../../../../../../../../var/folders/68/wlsstx1s23l9n5shd79wgw8r0000gp/T/upg-capture-cdOo0q/fixture.upg\",\n \"products\": [\n {\n \"file\": \"../../../../../../../../var/folders/68/wlsstx1s23l9n5shd79wgw8r0000gp/T/upg-capture-cdOo0q/fixture.upg\",\n \"title\": \"Notion (SATURATED test graph)\",\n \"active\": true\n }\n ]\n}"
1652
+ }
1653
+ ],
1654
+ "warnings": [],
1655
+ "see": [
1656
+ "init_workspace"
1657
+ ],
1658
+ "source": "src/tools/workspace.ts:163",
1659
+ "symbol": "getWorkspaceInfo",
1660
+ "returns": "JSON: `{ mode, workspace_path?, current_product?, current_file?,\nproducts }`. The shape depends on whether `.upg/workspace.json` exists.",
1661
+ "atomicity": "atomic (read-only)"
1662
+ },
1663
+ {
1664
+ "name": "init_workspace",
1665
+ "description": "Initialise a UPG workspace. Creates `.upg/` and moves the current .upg file into it. Unlocks multi-product management.",
1666
+ "domain": "workspace",
1667
+ "inputSchema": {
1668
+ "type": "object",
1669
+ "properties": {
1670
+ "move_existing": {
1671
+ "type": "boolean",
1672
+ "description": "Move existing .upg files into the workspace (default true)"
1673
+ }
1674
+ }
1675
+ },
1676
+ "throws": [
1677
+ "Returns a textError when the workspace already exists\n(`WorkspaceAlreadyExistsError`) or another filesystem error occurs."
1678
+ ],
1679
+ "examples": [
1680
+ {
1681
+ "description": "Live call against the Notion example graph.",
1682
+ "input": "{}",
1683
+ "output": "{\n \"message\": \"Workspace initialized\",\n \"workspace_path\": \".upg/\",\n \"default_product\": \"fixture.upg\",\n \"products\": [\n {\n \"file\": \"fixture.upg\",\n \"title\": \"Notion (SATURATED test graph)\"\n }\n ],\n \"current_product\": {\n \"title\": \"Notion (SATURATED test graph)\",\n \"entities\": 2054\n }\n}"
1684
+ }
1685
+ ],
1686
+ "warnings": [
1687
+ "One-time setup operation. Idempotent failure on retry: if the\nworkspace already exists, raises `WorkspaceAlreadyExistsError`. Pair\nwith `get_workspace_info` to check state before re-running."
1688
+ ],
1689
+ "see": [
1690
+ "create_product",
1691
+ "switch_product",
1692
+ "get_workspace_info"
1693
+ ],
1694
+ "source": "src/tools/workspace.ts:239",
1695
+ "symbol": "initWorkspaceTool",
1696
+ "returns": "JSON: `{ message, ...result }`. `result` carries the workspace\npath and the moved file's new location.",
1697
+ "atomicity": "non-atomic. The operation creates a directory and (optionally)\nmoves a file as separate filesystem mutations."
1698
+ },
1699
+ {
1700
+ "name": "list_local_products",
1701
+ "description": "Find every .upg file in the current directory and its immediate subdirectories.",
1702
+ "domain": "workspace",
1703
+ "inputSchema": {
1704
+ "type": "object",
1705
+ "properties": {}
1706
+ },
1707
+ "throws": [],
1708
+ "examples": [
1709
+ {
1710
+ "description": "Live call against the Notion example graph.",
1711
+ "input": "{}",
1712
+ "output": "{\n \"products\": [\n {\n \"file\": \"test-fixtures/sample.upg\",\n \"title\": \"Example Product Graph\",\n \"stage\": \"mvp\",\n \"nodes\": 8,\n \"edges\": 6\n }\n ]\n}"
1713
+ }
1714
+ ],
1715
+ "warnings": [],
1716
+ "see": [
1717
+ "switch_product",
1718
+ "get_workspace_info"
1719
+ ],
1720
+ "source": "src/tools/workspace.ts:40",
1721
+ "symbol": "listLocalProducts",
1722
+ "returns": "JSON: `{ products: Array<{ file, title, stage, nodes, edges }> }`.",
1723
+ "atomicity": "atomic (read-only)"
1724
+ },
1725
+ {
1726
+ "name": "list_portfolio_cross_edges",
1727
+ "description": "List all cross-product edges stored in the portfolio document (`.upg/portfolio.upg`). Empty list when the portfolio document is absent.",
1728
+ "domain": "workspace",
1729
+ "inputSchema": {
1730
+ "type": "object",
1731
+ "properties": {}
1732
+ },
1733
+ "throws": [],
1734
+ "examples": [],
1735
+ "warnings": [],
1736
+ "see": [
1737
+ "create_cross_product_edge"
1738
+ ],
1739
+ "source": "src/tools/workspace.ts:534",
1740
+ "symbol": "listPortfolioCrossEdges",
1741
+ "returns": "JSON: `{ cross_edges: UPGCrossEdge[], total, portfolio_file? }`.",
1742
+ "atomicity": "atomic (read-only)"
1743
+ },
1744
+ {
1745
+ "name": "list_portfolios",
1746
+ "description": "List portfolios from the portfolio document (`.upg/portfolio.upg`). Portfolios represent the strategic axis (where we invest). Returns an empty list when no portfolio document exists yet.",
1747
+ "domain": "workspace",
1748
+ "inputSchema": {
1749
+ "type": "object",
1750
+ "properties": {}
1751
+ },
1752
+ "throws": [],
1753
+ "examples": [
1754
+ {
1755
+ "description": "Live call against the Notion example graph.",
1756
+ "input": "{}",
1757
+ "output": "{\n \"portfolios\": [],\n \"total\": 0\n}"
1758
+ }
1759
+ ],
1760
+ "warnings": [],
1761
+ "see": [
1762
+ "create_cross_product_edge",
1763
+ "get_organization"
1764
+ ],
1765
+ "source": "src/tools/workspace.ts:317",
1766
+ "symbol": "listPortfolios",
1767
+ "returns": "JSON: `{ portfolios: Array<{ id, title, description?,\nparent_portfolio_id?, hierarchy_model?, products? }>, total }`.",
1768
+ "atomicity": "atomic (read-only)"
1769
+ },
1770
+ {
1771
+ "name": "migrate_cross_edges",
1772
+ "description": "Migrate inline cross-product edges from the current product's `edges[]` into the portfolio document (`.upg/portfolio.upg`) with qualified IDs. `dry_run: true` (default) previews; `dry_run: false` applies. Requires `source_product_id` to qualify source node IDs.",
1773
+ "domain": "workspace",
1774
+ "inputSchema": {
1775
+ "type": "object",
1776
+ "properties": {
1777
+ "source_product_id": {
1778
+ "type": "string",
1779
+ "description": "Product ID that owns the current document's nodes. Used to build qualified source IDs ({product_id}/{node_id})."
1780
+ },
1781
+ "target_product_id": {
1782
+ "type": "string",
1783
+ "description": "Product ID that owns the target nodes, when the target node is not in the current product. Edges without a resolvable target product are skipped."
1784
+ },
1785
+ "dry_run": {
1786
+ "type": "boolean",
1787
+ "description": "When true (default), report what would be migrated without writing anything."
1788
+ }
1789
+ },
1790
+ "required": [
1791
+ "source_product_id"
1792
+ ]
1793
+ },
1794
+ "throws": [
1795
+ "Returns a textError when `source_product_id` is missing or when the\nworkspace is not initialised (in non-dry-run mode)."
1796
+ ],
1797
+ "examples": [],
1798
+ "warnings": [
1799
+ "Default is `dry_run: true`. Pass `dry_run: false` to commit. Idempotent\non retry: a second `dry_run: false` after a successful migration finds zero\ninline cross-edges and reports `migrated: []`."
1800
+ ],
1801
+ "see": [
1802
+ "create_cross_product_edge",
1803
+ "list_portfolio_cross_edges",
1804
+ "list_cross_edge_types",
1805
+ "init_workspace"
1806
+ ],
1807
+ "source": "src/tools/workspace.ts:603",
1808
+ "symbol": "migrateCrossEdges",
1809
+ "returns": "JSON: `{ migrated, skipped, dry_run, portfolio_file? }`.",
1810
+ "atomicity": "non-atomic. Portfolio write + product file save are separate."
1811
+ },
1812
+ {
1813
+ "name": "switch_product",
1814
+ "description": "Switch to a different .upg file without restarting the server. In workspace mode, accepts just a filename (e.g. \"client-project\" or \"client-project.upg\").",
1815
+ "domain": "workspace",
1816
+ "inputSchema": {
1817
+ "type": "object",
1818
+ "properties": {
1819
+ "file": {
1820
+ "type": "string",
1821
+ "description": "Path to the .upg file (relative, absolute, or a bare product name in workspace mode)."
1822
+ }
1823
+ },
1824
+ "required": [
1825
+ "file"
1826
+ ]
1827
+ },
1828
+ "throws": [
1829
+ "Returns a textError when the file cannot be resolved or the load\nfails (file watcher / parse error)."
1830
+ ],
1831
+ "examples": [],
1832
+ "warnings": [
1833
+ "Mutates server-side workspace state. After an MCP reconnect the\nserver reverts to the workspace default. Call `get_workspace_info`\nbefore any read/mutation to confirm the active product."
1834
+ ],
1835
+ "see": [
1836
+ "get_workspace_info",
1837
+ "list_local_products",
1838
+ "init_workspace"
1839
+ ],
1840
+ "source": "src/tools/workspace.ts:108",
1841
+ "symbol": "switchProduct",
1842
+ "returns": "JSON: `{ message, file, product: { title, stage }, entities }`.",
1843
+ "atomicity": "non-atomic. Flushes the current store, stops watching, and\nloads the new file as separate filesystem operations."
1844
+ },
1845
+ {
1846
+ "name": "get_entity_schema",
1847
+ "description": "Return expected properties, valid statuses, valid edge types, and domain for an entity type. Lets agents construct valid entities without skill prompts.",
1848
+ "domain": "schema",
1849
+ "inputSchema": {
1850
+ "type": "object",
1851
+ "properties": {
1852
+ "type": {
1853
+ "type": "string",
1854
+ "description": "Entity type (e.g. \"hypothesis\", \"persona\", \"opportunity\")"
1855
+ }
1856
+ },
1857
+ "required": [
1858
+ "type"
1859
+ ]
1860
+ },
1861
+ "throws": [
1862
+ "Returns a textError when `type` is missing or unknown."
1863
+ ],
1864
+ "examples": [
1865
+ {
1866
+ "description": "Live call against the Notion example graph.",
1867
+ "input": "{\n \"type\": \"person\"\n}",
1868
+ "output": "{\n \"type\": \"person\",\n \"domain\": {\n \"id\": \"team_org\",\n \"label\": \"Team & Organisation\"\n },\n \"expected_properties\": {\n \"email\": {\n \"type\": \"string\",\n \"description\": \"Primary contact email. Stable identifier for de-duplication.\"\n },\n \"role_title\": {\n \"type\": \"string\",\n \"description\": \"Free-text job title. Distinct from the structured `role` entity.\"\n },\n \"time_zone\": {\n \"type\": \"string\",\n \"description\": \"IANA time zone (e.g. \\\"Europe/Berlin\\\"). Useful for capacity / on-call planning.\"\n }\n },\n \"edges_out\": [],\n \"edges_in\": [\n {\n \"edge_type\": \"node_owned_by_person\",\n \"source_type\": \"node\",\n \"reverse_verb\": \"owns\"\n }\n ],\n \"domain_guide\": {\n \"anchor_entity\": \"team\",\n \"creation_sequence\": [\n \"team\",\n \"role\",\n \"stakeholder\",\n \"person\",\n \"team_okr\",\n \"retrospective\",\n \"dependency\",\n \"department\",\n \"skill\",\n \"ceremony\",\n \"capacity_plan\"\n ],\n \"position_in_sequence\": 3,\n \"anti_patterns\": [\n {\n \"description\": \"Teams without OKRs — every team needs clear goals\"\n },\n {\n \"description\": \"Dependencies without both teams linked — a dependency must connect blocker and blocked\"\n },\n {\n \"description\": \"Retrospectives without action items — reflection without action is just venting\"\n }\n ]\n }\n}"
1869
+ }
1870
+ ],
1871
+ "warnings": [],
1872
+ "see": [
1873
+ "get_entity_meta",
1874
+ "list_entity_types",
1875
+ "get_valid_children",
1876
+ "get_lifecycle",
1877
+ "get_domain_guide",
1878
+ "list_edge_types",
1879
+ "create_node"
1880
+ ],
1881
+ "source": "src/tools/schema.ts:32",
1882
+ "symbol": "getEntitySchema",
1883
+ "returns": "JSON: `{ type, alias_of?, domain, expected_properties, edges_out,\nedges_in, phases?, initial_phase?, terminal_phases?, domain_guide? }`.",
1884
+ "atomicity": "atomic (read-only)"
1885
+ },
1886
+ {
1887
+ "name": "get_anti_pattern",
1888
+ "description": "Return one curated anti-pattern by id (kebab-case slug, e.g. \"features-without-hypotheses\", \"personas-without-jobs\"). Includes structured condition, why-it-matters, remediation, applicable stages, severity, optional source citation. IDs are stable URL fragments.",
1889
+ "domain": "spec",
1890
+ "inputSchema": {
1891
+ "type": "object",
1892
+ "properties": {
1893
+ "id": {
1894
+ "type": "string",
1895
+ "description": "Anti-pattern id (kebab-case slug)."
1896
+ }
1897
+ },
1898
+ "required": [
1899
+ "id"
1900
+ ]
1901
+ },
1902
+ "throws": [
1903
+ "textError when `id` is missing or unknown."
1904
+ ],
1905
+ "examples": [
1906
+ {
1907
+ "description": "Live call against the Notion example graph.",
1908
+ "input": "{\n \"id\": \"personas-without-jobs\"\n}",
1909
+ "output": "{\n \"id\": \"personas-without-jobs\",\n \"name\": \"Personas without jobs\",\n \"description\": \"The graph has persona entities, but none link into the user chain via any of the v0.2 chain edges (job, need, desired_outcome, or switching_cost). A persona without chain links is a demographic profile: who someone is, not what they are trying to get done.\",\n \"structured_condition\": {\n \"operator\": \"and\",\n \"checks\": [\n {\n \"check\": {\n \"type\": \"entity_count\",\n \"entity_type\": \"persona\",\n \"comparison\": \"nonzero\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_pursues_job\",\n \"target_type\": \"job\",\n \"comparison\": \"not_exists\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_experiences_need\",\n \"target_type\": \"need\",\n \"comparison\": \"not_exists\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_aspires_to_desired_outcome\",\n \"target_type\": \"desired_outcome\",\n \"comparison\": \"not_exists\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n… (truncated)"
1910
+ }
1911
+ ],
1912
+ "warnings": [],
1913
+ "see": [
1914
+ "list_anti_patterns",
1915
+ "get_anti_pattern_violations_for",
1916
+ "inspect",
1917
+ "validate_graph"
1918
+ ],
1919
+ "source": "src/tools/spec.ts:1425",
1920
+ "symbol": "getAntiPattern",
1921
+ "returns": "JSON: `UPGCuratedAntiPattern`",
1922
+ "atomicity": "atomic (read-only)"
1923
+ },
1924
+ {
1925
+ "name": "get_approach",
1926
+ "description": "Return one `UPGApproach` by id. Valid ids: `plan`, `inspect`, `prioritise`, `trace`, `reflect` (same names as the verb-led MCP tools).",
1927
+ "domain": "spec",
1928
+ "inputSchema": {
1929
+ "type": "object",
1930
+ "properties": {
1931
+ "id": {
1932
+ "type": "string",
1933
+ "description": "Approach id. One of: plan, inspect, prioritise, trace, reflect.",
1934
+ "enum": [
1935
+ "plan",
1936
+ "inspect",
1937
+ "prioritise",
1938
+ "trace",
1939
+ "reflect"
1940
+ ]
1941
+ }
1942
+ },
1943
+ "required": [
1944
+ "id"
1945
+ ]
1946
+ },
1947
+ "throws": [
1948
+ "textError when `id` is missing or unknown."
1949
+ ],
1950
+ "examples": [
1951
+ {
1952
+ "description": "Live call against the Notion example graph.",
1953
+ "input": "{\n \"id\": \"plan\"\n}",
1954
+ "output": "{\n \"id\": \"plan\",\n \"label\": \"Plan\",\n \"description\": \"The path of arrival to \\\"what should I build next?\\\". Plan engages a region by surveying its entity coverage against canonical expectations and surfacing the missing scaffolding: the entities a healthy region carries that this graph does not. Cartographic sense: you are walking the coastline of a region and noting where the contour is incomplete, not deciding a strategy. Frameworks like Now/Next/Later, MoSCoW, and Wardley Mapping live within Plan as the named techniques for organising the gap-filling sequence.\",\n \"question_answered\": \"what should I build next?\",\n \"signature_hint\": \"({ region?: UPGRegionId }) → { missing_entities, coverage_score }\",\n \"framework_id_examples\": [\n \"now-next-later\",\n \"moscow\",\n \"wardley-map\",\n \"okr-framework\",\n \"three-horizons\"\n ]\n}"
1955
+ }
1956
+ ],
1957
+ "warnings": [],
1958
+ "see": [
1959
+ "list_approaches",
1960
+ "plan",
1961
+ "inspect",
1962
+ "prioritise",
1963
+ "trace",
1964
+ "reflect"
1965
+ ],
1966
+ "source": "src/tools/spec.ts:320",
1967
+ "symbol": "getApproach",
1968
+ "returns": "JSON: the full `UPGApproach` record.",
1969
+ "atomicity": "atomic (read-only)"
1970
+ },
1971
+ {
1972
+ "name": "get_domain_guide",
1973
+ "description": "Return the full `UPGDomainUsageGuide` for a domain: anchor entity, creation sequence, named patterns (entity + edge chains), required cross-domain bridges, anti-patterns.",
1974
+ "domain": "spec",
1975
+ "inputSchema": {
1976
+ "type": "object",
1977
+ "properties": {
1978
+ "domain_id": {
1979
+ "type": "string",
1980
+ "description": "Canonical domain id (e.g. \"user\", \"market_intelligence\", \"growth\")."
1981
+ }
1982
+ },
1983
+ "required": [
1984
+ "domain_id"
1985
+ ]
1986
+ },
1987
+ "throws": [
1988
+ "textError when `domain_id` is missing or unknown."
1989
+ ],
1990
+ "examples": [
1991
+ {
1992
+ "description": "Live call against the Notion example graph.",
1993
+ "input": "{\n \"domain_id\": \"strategy\"\n}",
1994
+ "output": "{\n \"domain_id\": \"strategy\",\n \"anchor_entity\": \"outcome\",\n \"creation_sequence\": [\n \"product\",\n \"vision\",\n \"mission\",\n \"outcome\",\n \"objective\",\n \"key_result\",\n \"metric\",\n \"metric_quality_assessment\",\n \"strategic_theme\",\n \"strategic_pillar\",\n \"initiative\",\n \"capability\",\n \"value_stream\",\n \"assumption\",\n \"decision\",\n \"constraint\"\n ],\n \"patterns\": [\n {\n \"name\": \"Strategic Cascade\",\n \"description\": \"Vision grounds mission, product pursues outcomes and targets objectives, objectives achieved through key results measured by metrics\",\n \"entity_types\": [\n \"vision\",\n \"mission\",\n \"outcome\",\n \"objective\",\n \"key_result\",\n \"metric\"\n ],\n \"edge_chain\": [\n \"product_guided_by_vision\",\n \"product_fulfils_mission\",\n \"vision_realised_through_mission\",\n \"product_pursues_outcome\",\n \"product_targets_objective\",\n \"objective_achieved_through_key_result\",\n \"outcome_measured_by_metric\"\n ]\n }\n ],\n \"required_bridges\": [\n {\n \"edge_type\": \"outcome_reveals_opportunity\",\n \"target_domain\": \"discovery\",\n \"when\": \"Outcomes should connect to the opportunities that deliver them\"\n },\n {\n \"edge_type\": \"outcome_delivered_by_feature\",\n \"target_domain\": \"product_spec\",\n \"when\": \"Strategic outcomes should\n… (truncated)"
1995
+ }
1996
+ ],
1997
+ "warnings": [],
1998
+ "see": [
1999
+ "list_domains",
2000
+ "get_domain_ring",
2001
+ "list_anti_patterns",
2002
+ "get_playbook"
2003
+ ],
2004
+ "source": "src/tools/spec.ts:745",
2005
+ "symbol": "getDomainGuide",
2006
+ "returns": "JSON: the full `UPGDomainUsageGuide` record.",
2007
+ "atomicity": "atomic (read-only)"
2008
+ },
2009
+ {
2010
+ "name": "get_domain_ring",
2011
+ "description": "Return one `UPGDomainRing` by id (one of: `nucleus`, `understand`, `define`, `build`, `grow`, `operate`, `extend`). Returns a descriptive message (not an error) when the id is unknown.",
2012
+ "domain": "spec",
2013
+ "inputSchema": {
2014
+ "type": "object",
2015
+ "properties": {
2016
+ "id": {
2017
+ "type": "string",
2018
+ "description": "Ring id. One of: nucleus, understand, define, build, grow, operate, extend."
2019
+ }
2020
+ },
2021
+ "required": [
2022
+ "id"
2023
+ ]
2024
+ },
2025
+ "throws": [],
2026
+ "examples": [
2027
+ {
2028
+ "description": "Live call against the Notion example graph.",
2029
+ "input": "{\n \"id\": \"nucleus\"\n}",
2030
+ "output": "{\n \"id\": \"nucleus\",\n \"label\": \"Product\",\n \"description\": \"The seed\",\n \"domain_ids\": [\n \"portfolio\",\n \"workspace\"\n ]\n}"
2031
+ }
2032
+ ],
2033
+ "warnings": [],
2034
+ "see": [
2035
+ "list_domain_rings",
2036
+ "list_domains",
2037
+ "get_domain_guide"
2038
+ ],
2039
+ "source": "src/tools/spec.ts:1881",
2040
+ "symbol": "getDomainRing",
2041
+ "returns": "JSON: the full `UPGDomainRing` record.",
2042
+ "atomicity": "atomic (read-only)"
2043
+ },
2044
+ {
2045
+ "name": "get_edge_type",
2046
+ "description": "Return one edge catalogue entry by edge type key (e.g. \"persona_pursues_job\", \"feature_addresses_need\").",
2047
+ "domain": "spec",
2048
+ "inputSchema": {
2049
+ "type": "object",
2050
+ "properties": {
2051
+ "type": {
2052
+ "type": "string",
2053
+ "description": "Edge type key from UPG_EDGE_CATALOG."
2054
+ }
2055
+ },
2056
+ "required": [
2057
+ "type"
2058
+ ]
2059
+ },
2060
+ "throws": [
2061
+ "textError when `type` is missing or unknown."
2062
+ ],
2063
+ "examples": [
2064
+ {
2065
+ "description": "Live call against the Notion example graph.",
2066
+ "input": "{\n \"type\": \"opportunity_drives_solution\"\n}",
2067
+ "output": "{\n \"type\": \"opportunity_drives_solution\",\n \"forward_verb\": \"drives\",\n \"reverse_verb\": \"addresses\",\n \"classification\": \"causal\",\n \"source_type\": \"opportunity\",\n \"target_type\": \"solution\"\n}"
2068
+ }
2069
+ ],
2070
+ "warnings": [],
2071
+ "see": [
2072
+ "list_edge_types",
2073
+ "resolve_edge_for_pair",
2074
+ "list_edge_migrations",
2075
+ "rename_edge_type"
2076
+ ],
2077
+ "source": "src/tools/spec.ts:877",
2078
+ "symbol": "getEdgeType",
2079
+ "returns": "JSON: `{ type, forward_verb, reverse_verb, classification, source_type, target_type }`",
2080
+ "atomicity": "atomic (read-only)"
2081
+ },
2082
+ {
2083
+ "name": "get_entity_meta",
2084
+ "description": "Return one `EntityTypeMeta` record by entity type name, plus resolved `domain_id` (null when unmapped). One type's lifecycle metadata: maturity tier, since-version, replacement target if deprecated. Pass the canonical name (e.g. \"persona\", \"pain_point\"), not the immutable `type_id`.",
2085
+ "domain": "spec",
2086
+ "inputSchema": {
2087
+ "type": "object",
2088
+ "properties": {
2089
+ "name": {
2090
+ "type": "string",
2091
+ "description": "Canonical entity type name."
2092
+ }
2093
+ },
2094
+ "required": [
2095
+ "name"
2096
+ ]
2097
+ },
2098
+ "throws": [
2099
+ "textError when `name` is missing or unknown."
2100
+ ],
2101
+ "examples": [
2102
+ {
2103
+ "description": "Live call against the Notion example graph.",
2104
+ "input": "{\n \"name\": \"person\"\n}",
2105
+ "output": "{\n \"name\": \"person\",\n \"type_id\": \"ent_349\",\n \"maturity\": \"stable\",\n \"since\": \"0.5.0\",\n \"domain_id\": \"team_org\"\n}"
2106
+ }
2107
+ ],
2108
+ "warnings": [],
2109
+ "see": [
2110
+ "list_entity_types",
2111
+ "get_type_label",
2112
+ "get_entity_schema",
2113
+ "list_type_migrations"
2114
+ ],
2115
+ "source": "src/tools/spec.ts:1340",
2116
+ "symbol": "getEntityMeta",
2117
+ "returns": "JSON: `EntityTypeMeta & { domain_id: string | null }`",
2118
+ "atomicity": "atomic (read-only)"
2119
+ },
2120
+ {
2121
+ "name": "get_framework",
2122
+ "description": "Return one `UPGFramework` by id (e.g. \"rice-scoring\", \"lean-canvas\"). Includes all four layers: data, structure, presentation, education.",
2123
+ "domain": "spec",
2124
+ "inputSchema": {
2125
+ "type": "object",
2126
+ "properties": {
2127
+ "id": {
2128
+ "type": "string",
2129
+ "description": "Framework id (kebab-case)."
2130
+ }
2131
+ },
2132
+ "required": [
2133
+ "id"
2134
+ ]
2135
+ },
2136
+ "throws": [
2137
+ "textError when `id` is missing or unknown."
2138
+ ],
2139
+ "examples": [
2140
+ {
2141
+ "description": "Live call against the Notion example graph.",
2142
+ "input": "{\n \"id\": \"opportunity-solution-tree\"\n}",
2143
+ "output": "{\n \"id\": \"opportunity-solution-tree\",\n \"approach_ids\": [\n \"trace\"\n ],\n \"name\": \"Opportunity Solution Tree\",\n \"version\": \"1.0.0\",\n \"description\": \"Map desired outcomes to opportunities, then branch into solutions and experiments. Ensures every solution traces back to a real user need.\",\n \"category\": \"discovery\",\n \"origin\": {\n \"type\": \"practitioner\",\n \"attribution\": \"Teresa Torres\",\n \"description\": \"Introduced in Continuous Discovery Habits. Maps outcomes to opportunities, solutions, and experiments.\",\n \"url\": \"https://www.producttalk.org/opportunity-solution-tree/\",\n \"year\": 2021,\n \"license\": \"published_methodology\"\n },\n \"tags\": [\n \"discovery\",\n \"tree\"\n ],\n \"slots\": [\n {\n \"label\": \"Root Outcome\",\n \"entityTypeId\": \"outcome\",\n \"description\": \"The desired business or user outcome\"\n },\n {\n \"label\": \"Opportunities\",\n \"entityTypeId\": \"opportunity\",\n \"description\": \"User needs, pain points, or desires\"\n },\n {\n \"label\": \"Solutions\",\n \"entityTypeId\": \"solution\",\n \"description\": \"Ideas to address each opportunity\"\n },\n {\n \"label\": \"Experiments\",\n \"entityTypeId\": \"experiment_run\",\n \"description\": \"Tests to validate each solution\"\n }\n ],\n \"data\": {\n \"entity_types\": [\n {\n \"type\": \"outcome\",\n \"role\": \"root\"\n },\n {\n \"type\":\n… (truncated)"
2144
+ }
2145
+ ],
2146
+ "warnings": [],
2147
+ "see": [
2148
+ "list_frameworks",
2149
+ "prioritise",
2150
+ "get_playbook",
2151
+ "get_approach"
2152
+ ],
2153
+ "source": "src/tools/spec.ts:814",
2154
+ "symbol": "getFramework",
2155
+ "returns": "JSON: the full `UPGFramework` record.",
2156
+ "atomicity": "atomic (read-only)"
2157
+ },
2158
+ {
2159
+ "name": "get_lens",
2160
+ "description": "Return the full `UPGLens` record by id (e.g. \"product\", \"ux_design\", \"engineering\", \"full\") plus the resolved entity types visible through that lens. Combines the lens record with `visible_types` in one response.",
2161
+ "domain": "spec",
2162
+ "inputSchema": {
2163
+ "type": "object",
2164
+ "properties": {
2165
+ "id": {
2166
+ "type": "string",
2167
+ "description": "Lens id (e.g. \"product\", \"ux_design\", \"full\")."
2168
+ }
2169
+ },
2170
+ "required": [
2171
+ "id"
2172
+ ]
2173
+ },
2174
+ "throws": [
2175
+ "textError when `id` is missing or unknown."
2176
+ ],
2177
+ "examples": [
2178
+ {
2179
+ "description": "Live call against the Notion example graph.",
2180
+ "input": "{\n \"id\": \"product\"\n}",
2181
+ "output": "{\n \"id\": \"product\",\n \"name\": \"Product\",\n \"description\": \"Full graph, PM vocabulary, outcome-driven workflow\",\n \"icon\": \"target\",\n \"framework_id\": \"ost\",\n \"label_overrides\": {\n \"experiment\": \"Experiment\",\n \"learning\": \"Validated Learning\"\n },\n \"visible_domains\": [],\n \"benchmark_domains\": [\n \"strategy\",\n \"user\",\n \"discovery\",\n \"validation\",\n \"market_intelligence\",\n \"product_spec\",\n \"growth\"\n ],\n \"intelligence_prompts\": [\n {\n \"condition\": \"outcomes.length === 0\",\n \"structured_condition\": {\n \"check\": {\n \"type\": \"entity_count\",\n \"entity_type\": \"outcome\",\n \"comparison\": \"zero\"\n }\n },\n \"message\": \"No outcomes defined yet. Start with what success looks like — what measurable result should this product drive?\"\n },\n {\n \"condition\": \"hypotheses.untested.length > 3\",\n \"structured_condition\": {\n \"check\": {\n \"type\": \"entity_count\",\n \"entity_type\": \"hypothesis\",\n \"filter\": {\n \"status\": \"untested\"\n },\n \"comparison\": \"gt\",\n \"threshold\": 3\n }\n },\n \"message\": \"You have untested hypotheses stacking up. Pick the riskiest one and design an experiment before adding more.\"\n },\n {\n \"condition\": \"features.length > 0 && hypotheses.length === 0\",\n \"structured_condition\": {\n… (truncated)"
2182
+ }
2183
+ ],
2184
+ "warnings": [],
2185
+ "see": [
2186
+ "list_lenses",
2187
+ "get_playbook",
2188
+ "get_framework",
2189
+ "list_entity_types"
2190
+ ],
2191
+ "source": "src/tools/spec.ts:1134",
2192
+ "symbol": "getLensTool",
2193
+ "returns": "JSON: `{ ...UPGLens, visible_types: string[] }`",
2194
+ "atomicity": "atomic (read-only)"
2195
+ },
2196
+ {
2197
+ "name": "get_lifecycle",
2198
+ "description": "Return the full `UPGLifecycle` definition for one entity type: initial phase, terminal phases, ordered phases with transitions and core states. Returns a descriptive message (not an error) when the type has no lifecycle.",
2199
+ "domain": "spec",
2200
+ "inputSchema": {
2201
+ "type": "object",
2202
+ "properties": {
2203
+ "entity_type": {
2204
+ "type": "string",
2205
+ "description": "Canonical entity type name (e.g. \"feature\", \"hypothesis\", \"opportunity\")."
2206
+ }
2207
+ },
2208
+ "required": [
2209
+ "entity_type"
2210
+ ]
2211
+ },
2212
+ "throws": [],
2213
+ "examples": [
2214
+ {
2215
+ "description": "Live call against the Notion example graph.",
2216
+ "input": "{\n \"entity_type\": \"hypothesis\"\n}",
2217
+ "output": "{\n \"entity_type\": \"hypothesis\",\n \"initial_phase\": \"drafted\",\n \"terminal_phases\": [\n \"validated\",\n \"invalidated\",\n \"archived\"\n ],\n \"phases\": [\n {\n \"id\": \"drafted\",\n \"label\": \"Drafted\",\n \"description\": \"The claim is being written. We_believe / will_result_in / we_know_when may not yet be complete. Not yet committed for testing.\",\n \"transitions_to\": [\n \"active\",\n \"archived\"\n ]\n },\n {\n \"id\": \"active\",\n \"label\": \"Active\",\n \"description\": \"The claim is committed and being tested. Experiments may be running; evidence may be accruing via supports/refutes edges. current_confidence updates as evidence weight shifts.\",\n \"transitions_to\": [\n \"validated\",\n \"invalidated\",\n \"archived\"\n ]\n },\n {\n \"id\": \"validated\",\n \"label\": \"Validated\",\n \"description\": \"Evidence sufficiently supports the claim. The belief held. Reopens to `active` if new evidence materially changes the picture.\",\n \"transitions_to\": [\n \"active\"\n ]\n },\n {\n \"id\": \"invalidated\",\n \"label\": \"Invalidated\",\n \"description\": \"Evidence sufficiently refutes the claim. The belief did not hold. Reopens to `active` if new evidence materially changes the picture.\",\n \"transitions_to\": [\n \"active\"\n ]\n },\n {\n \"id\": \"archived\",\n \"label\":\n… (truncated)"
2218
+ }
2219
+ ],
2220
+ "warnings": [],
2221
+ "see": [
2222
+ "list_lifecycles",
2223
+ "get_entity_meta",
2224
+ "get_entity_schema"
2225
+ ],
2226
+ "source": "src/tools/spec.ts:1751",
2227
+ "symbol": "getLifecycle",
2228
+ "returns": "JSON: the full `UPGLifecycle` record, or a descriptive message.",
2229
+ "atomicity": "atomic (read-only)"
2230
+ },
2231
+ {
2232
+ "name": "get_playbook",
2233
+ "description": "Return one `UPGPlaybook` by id (e.g. \"playbook:strategy-outcomes\", \"playbook:business-model-bmc\"). Includes the ordered `creation_sequence` with step kinds and prompts. IDs are namespace-prefixed `playbook:*`. For approaches, use `get_approach`.",
2234
+ "domain": "spec",
2235
+ "inputSchema": {
2236
+ "type": "object",
2237
+ "properties": {
2238
+ "id": {
2239
+ "type": "string",
2240
+ "description": "Playbook id (namespace-prefixed: playbook:*)."
2241
+ }
2242
+ },
2243
+ "required": [
2244
+ "id"
2245
+ ]
2246
+ },
2247
+ "throws": [
2248
+ "textError when `id` is missing or unknown."
2249
+ ],
2250
+ "examples": [
2251
+ {
2252
+ "description": "Live call against the Notion example graph.",
2253
+ "input": "{\n \"id\": \"playbook:strategy-outcomes\"\n}",
2254
+ "output": "{\n \"id\": \"playbook:strategy-outcomes\",\n \"name\": \"Strategy & Outcomes\",\n \"version\": \"0.2.0\",\n \"description\": \"Cascade vision through themes, outcomes, objectives, key results — and the bets you are making to get there.\",\n \"region\": \"strategy_outcomes\",\n \"is_canonical\": true,\n \"target_anchor_entity\": \"objective\",\n \"creation_sequence\": [\n {\n \"kind\": \"entity_sequence\",\n \"order\": 1,\n \"phase\": \"Vision & Mission\",\n \"name\": \"Vision & Mission\",\n \"prompt_hint\": \"Name what you are building toward and how you will get there. Vision is the destination; mission is the orientation. One of each — more is dilution.\",\n \"entity_types\": [\n \"vision\",\n \"mission\"\n ]\n },\n {\n \"kind\": \"entity_sequence\",\n \"order\": 2,\n \"phase\": \"Themes\",\n \"name\": \"Themes\",\n \"prompt_hint\": \"Choose 2–4 strategic themes that focus the work. Past four, you have lost focus, not gained coverage.\",\n \"entity_types\": [\n \"strategic_theme\",\n \"strategic_pillar\"\n ]\n },\n {\n \"kind\": \"entity_sequence\",\n \"order\": 3,\n \"phase\": \"Outcomes\",\n \"name\": \"Outcomes\",\n \"prompt_hint\": \"Frame the changes in the world the product is trying to cause — shifts in behavior, perception, or position. Not features shipped.\",\n \"entity_types\": [\n \"outcome\"\n ]\n },\n {\n \"kind\":\n… (truncated)"
2255
+ }
2256
+ ],
2257
+ "warnings": [],
2258
+ "see": [
2259
+ "list_playbooks",
2260
+ "get_approach",
2261
+ "get_framework",
2262
+ "get_region"
2263
+ ],
2264
+ "source": "src/tools/spec.ts:252",
2265
+ "symbol": "getPlaybook",
2266
+ "returns": "JSON: the full `UPGPlaybook` record.",
2267
+ "atomicity": "atomic (read-only)"
2268
+ },
2269
+ {
2270
+ "name": "get_region",
2271
+ "description": "Return the full `UPGRegion` record by id: anchor entity (with rationale and inbound/outbound cross-edge counts), entity memberships with structural roles, intra-domain edge keys, boundary edges to other regions, shape archetype, atomic-domain composition.",
2272
+ "domain": "spec",
2273
+ "inputSchema": {
2274
+ "type": "object",
2275
+ "properties": {
2276
+ "id": {
2277
+ "type": "string",
2278
+ "description": "Region id (e.g. \"strategy_outcomes\", \"users_needs\", \"product_delivery\"). See UPG_REGIONS for the full list of 10."
2279
+ }
2280
+ },
2281
+ "required": [
2282
+ "id"
2283
+ ]
2284
+ },
2285
+ "throws": [
2286
+ "textError when `id` is missing or unknown."
2287
+ ],
2288
+ "examples": [
2289
+ {
2290
+ "description": "Live call against the Notion example graph.",
2291
+ "input": "{\n \"id\": \"strategy_outcomes\"\n}",
2292
+ "output": "{\n \"id\": \"strategy_outcomes\",\n \"label\": \"Strategy & Outcomes\",\n \"order\": 1,\n \"shape\": \"cascade\",\n \"mental_model\": \"Aspiration → direction → bet → measurable → proof.\",\n \"operators\": [\n \"CEO\",\n \"PM\",\n \"leadership\",\n \"strategy team\"\n ],\n \"composes_atomic_domains\": [\n \"strategy\"\n ],\n \"entities\": [\n {\n \"type\": \"vision\",\n \"role\": \"root\"\n },\n {\n \"type\": \"mission\",\n \"role\": \"container\"\n },\n {\n \"type\": \"strategic_pillar\",\n \"role\": \"hub\"\n },\n {\n \"type\": \"strategic_theme\",\n \"role\": \"container\"\n },\n {\n \"type\": \"initiative\",\n \"role\": \"container\"\n },\n {\n \"type\": \"capability\",\n \"role\": \"leaf\"\n },\n {\n \"type\": \"value_stream\",\n \"role\": \"container\"\n },\n {\n \"type\": \"objective\",\n \"role\": \"anchor\"\n },\n {\n \"type\": \"key_result\",\n \"role\": \"hub\"\n },\n {\n \"type\": \"metric\",\n \"role\": \"leaf\",\n \"notes\": \"shared with Analytics\"\n },\n {\n \"type\": \"decision\",\n \"role\": \"hub\",\n \"notes\": \"polymorphic across domains\"\n },\n {\n \"type\": \"assumption\",\n \"role\": \"leaf\"\n },\n {\n \"type\": \"outcome\",\n \"role\": \"hub\"\n }\n ],\n \"anchor\": {\n \"type\": \"objective\",\n \"rationale\": \"The single entity where P5 language meets P3 measurement. The accountability question of strategy flows through\n… (truncated)"
2293
+ }
2294
+ ],
2295
+ "warnings": [],
2296
+ "see": [
2297
+ "list_regions",
2298
+ "get_region_for_entity_type",
2299
+ "get_playbook",
2300
+ "list_lenses"
2301
+ ],
2302
+ "source": "src/tools/spec.ts:936",
2303
+ "symbol": "getRegion",
2304
+ "returns": "JSON: the full `UPGRegion` record.",
2305
+ "atomicity": "atomic (read-only)"
2306
+ },
2307
+ {
2308
+ "name": "get_region_for_entity_type",
2309
+ "description": "Resolve which super-domain region contains a given entity type. Wraps `getRegionForEntityType`; returns the full `UPGRegion` record. Use for adapters and copilots that route or render an entity by its super-domain.",
2310
+ "domain": "spec",
2311
+ "inputSchema": {
2312
+ "type": "object",
2313
+ "properties": {
2314
+ "entity_type": {
2315
+ "type": "string",
2316
+ "description": "Canonical entity type (e.g. \"persona\", \"feature\", \"metric\")."
2317
+ }
2318
+ },
2319
+ "required": [
2320
+ "entity_type"
2321
+ ]
2322
+ },
2323
+ "throws": [
2324
+ "textError when `entity_type` is missing or no region contains it."
2325
+ ],
2326
+ "examples": [
2327
+ {
2328
+ "description": "Live call against the Notion example graph.",
2329
+ "input": "{\n \"entity_type\": \"outcome\"\n}",
2330
+ "output": "{\n \"id\": \"strategy_outcomes\",\n \"label\": \"Strategy & Outcomes\",\n \"order\": 1,\n \"shape\": \"cascade\",\n \"mental_model\": \"Aspiration → direction → bet → measurable → proof.\",\n \"operators\": [\n \"CEO\",\n \"PM\",\n \"leadership\",\n \"strategy team\"\n ],\n \"composes_atomic_domains\": [\n \"strategy\"\n ],\n \"entities\": [\n {\n \"type\": \"vision\",\n \"role\": \"root\"\n },\n {\n \"type\": \"mission\",\n \"role\": \"container\"\n },\n {\n \"type\": \"strategic_pillar\",\n \"role\": \"hub\"\n },\n {\n \"type\": \"strategic_theme\",\n \"role\": \"container\"\n },\n {\n \"type\": \"initiative\",\n \"role\": \"container\"\n },\n {\n \"type\": \"capability\",\n \"role\": \"leaf\"\n },\n {\n \"type\": \"value_stream\",\n \"role\": \"container\"\n },\n {\n \"type\": \"objective\",\n \"role\": \"anchor\"\n },\n {\n \"type\": \"key_result\",\n \"role\": \"hub\"\n },\n {\n \"type\": \"metric\",\n \"role\": \"leaf\",\n \"notes\": \"shared with Analytics\"\n },\n {\n \"type\": \"decision\",\n \"role\": \"hub\",\n \"notes\": \"polymorphic across domains\"\n },\n {\n \"type\": \"assumption\",\n \"role\": \"leaf\"\n },\n {\n \"type\": \"outcome\",\n \"role\": \"hub\"\n }\n ],\n \"anchor\": {\n \"type\": \"objective\",\n \"rationale\": \"The single entity where P5 language meets P3 measurement. The accountability question of strategy flows through\n… (truncated)"
2331
+ }
2332
+ ],
2333
+ "warnings": [],
2334
+ "see": [
2335
+ "get_region",
2336
+ "list_regions",
2337
+ "get_entity_meta",
2338
+ "list_entity_types"
2339
+ ],
2340
+ "source": "src/tools/spec.ts:958",
2341
+ "symbol": "getRegionForEntity",
2342
+ "returns": "JSON: the full `UPGRegion` record.",
2343
+ "atomicity": "atomic (read-only)"
2344
+ },
2345
+ {
2346
+ "name": "get_scale",
2347
+ "description": "Return one spec-defined assessment scale by id (e.g. \"reach_5\", \"severity_5\", \"confidence_binary\"). Includes the full point array. Returns a descriptive message (not an error) when the id is unknown.",
2348
+ "domain": "spec",
2349
+ "inputSchema": {
2350
+ "type": "object",
2351
+ "properties": {
2352
+ "id": {
2353
+ "type": "string",
2354
+ "description": "Scale id (e.g. \"reach_5\", \"frequency_5\", \"severity_5\", \"importance_5\", \"confidence_binary\")."
2355
+ }
2356
+ },
2357
+ "required": [
2358
+ "id"
2359
+ ]
2360
+ },
2361
+ "throws": [],
2362
+ "examples": [],
2363
+ "warnings": [],
2364
+ "see": [
2365
+ "list_scales",
2366
+ "get_entity_schema"
2367
+ ],
2368
+ "source": "src/tools/spec.ts:1797",
2369
+ "symbol": "getScale",
2370
+ "returns": "JSON: the full `UPGScaleDefinition` record including all points.",
2371
+ "atomicity": "atomic (read-only)"
2372
+ },
2373
+ {
2374
+ "name": "get_spec_version",
2375
+ "description": "Spec-level metadata for compatibility checks: `upg_version`, `markdown_format_version`, and canonical counts (entity types, edge types, atomic domains, super-domain regions). Pin against the version pair; counts are informational.",
2376
+ "domain": "spec",
2377
+ "inputSchema": {
2378
+ "type": "object",
2379
+ "properties": {}
2380
+ },
2381
+ "throws": [],
2382
+ "examples": [
2383
+ {
2384
+ "description": "Live call against the Notion example graph.",
2385
+ "input": "{}",
2386
+ "output": "{\n \"upg_version\": \"0.5.8\",\n \"markdown_format_version\": \"0.1\",\n \"entity_count\": 312,\n \"edge_count\": 938,\n \"domain_count\": 36,\n \"region_count\": 10\n}"
2387
+ }
2388
+ ],
2389
+ "warnings": [],
2390
+ "see": [
2391
+ "get_workspace_info",
2392
+ "list_entity_types",
2393
+ "list_edge_types",
2394
+ "list_regions"
2395
+ ],
2396
+ "source": "src/tools/spec.ts:986",
2397
+ "symbol": "getSpecVersion",
2398
+ "returns": "JSON: `{ upg_version, markdown_format_version, entity_count, edge_count, domain_count, region_count }`",
2399
+ "atomicity": "atomic (read-only)"
2400
+ },
2401
+ {
2402
+ "name": "get_type_label",
2403
+ "description": "Return one `UPGTypeLabel` by entity type, plus a resolved display label for an optional `framework_id` and/or `designation` (wraps `resolveLabel`). Lookup is exact-match against `UPG_TYPE_LABELS_MAP`.",
2404
+ "domain": "spec",
2405
+ "inputSchema": {
2406
+ "type": "object",
2407
+ "properties": {
2408
+ "entity_type": {
2409
+ "type": "string",
2410
+ "description": "Canonical entity type id."
2411
+ },
2412
+ "framework_id": {
2413
+ "type": "string",
2414
+ "description": "Optional framework id (e.g. \"lean_canvas\", \"ost\", \"design_thinking\"). When set, resolved_label uses the framework-specific label."
2415
+ },
2416
+ "designation": {
2417
+ "type": "string",
2418
+ "description": "Optional designation key (e.g. \"pain\", \"gap\", \"desire\") for types that use the designation pattern."
2419
+ }
2420
+ },
2421
+ "required": [
2422
+ "entity_type"
2423
+ ]
2424
+ },
2425
+ "throws": [
2426
+ "textError when `entity_type` is missing or unknown."
2427
+ ],
2428
+ "examples": [
2429
+ {
2430
+ "description": "Live call against the Notion example graph.",
2431
+ "input": "{\n \"entity_type\": \"person\"\n}",
2432
+ "output": "{\n \"id\": \"person\",\n \"canonical_label\": \"Person\",\n \"alt_labels\": [],\n \"framework_labels\": {},\n \"resolved_label\": \"Person\"\n}"
2433
+ }
2434
+ ],
2435
+ "warnings": [],
2436
+ "see": [
2437
+ "list_type_labels",
2438
+ "get_entity_meta",
2439
+ "list_frameworks"
2440
+ ],
2441
+ "source": "src/tools/spec.ts:1202",
2442
+ "symbol": "getTypeLabel",
2443
+ "returns": "JSON: `{ ...UPGTypeLabel, resolved_label: string }`",
2444
+ "atomicity": "atomic (read-only)"
2445
+ },
2446
+ {
2447
+ "name": "get_valid_children",
2448
+ "description": "Return valid direct-child entity types for a parent type. Wraps `getValidChildren` / `UPG_VALID_CHILDREN`. Empty array when none registered. Answers \"what can I create under this?\". Pairs with `get_entity_schema`.",
2449
+ "domain": "spec",
2450
+ "inputSchema": {
2451
+ "type": "object",
2452
+ "properties": {
2453
+ "parent_type": {
2454
+ "type": "string",
2455
+ "description": "Canonical parent entity type."
2456
+ }
2457
+ },
2458
+ "required": [
2459
+ "parent_type"
2460
+ ]
2461
+ },
2462
+ "throws": [
2463
+ "textError when `parent_type` is missing."
2464
+ ],
2465
+ "examples": [
2466
+ {
2467
+ "description": "Live call against the Notion example graph.",
2468
+ "input": "{\n \"parent_type\": \"example\"\n}",
2469
+ "output": "{\n \"parent_type\": \"example\",\n \"valid_children\": []\n}"
2470
+ }
2471
+ ],
2472
+ "warnings": [],
2473
+ "see": [
2474
+ "get_entity_schema",
2475
+ "list_entity_types",
2476
+ "get_entity_meta",
2477
+ "create_node"
2478
+ ],
2479
+ "source": "src/tools/spec.ts:1232",
2480
+ "symbol": "getValidChildrenTool",
2481
+ "returns": "JSON: `{ parent_type, valid_children: string[] }`",
2482
+ "atomicity": "atomic (read-only)"
2483
+ },
2484
+ {
2485
+ "name": "inspect",
2486
+ "description": "[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing inspection, invoke the /upg-inspect skill instead of calling this tool directly. — Inspect approach: path of arrival to \"what's broken?\". Returns the Inspect record + invocation params in the family-resemblance envelope. The LLM consumes `signature_hint` and emits `{ violations: [{ severity, kind, entity_id, description, fix_hint }] }` against `UPG_ANTI_PATTERNS` + the live graph. Optional `region` or `entities[]` scope the audit.",
2487
+ "domain": "spec",
2488
+ "inputSchema": {
2489
+ "type": "object",
2490
+ "properties": {
2491
+ "region": {
2492
+ "type": "string",
2493
+ "description": "Optional UPGRegionId. Narrows inspection scope to a single region."
2494
+ },
2495
+ "entities": {
2496
+ "type": "array",
2497
+ "items": {
2498
+ "type": "string"
2499
+ },
2500
+ "description": "Optional entity_id[]. Narrows inspection scope to a specific candidate set. Composable with region."
2501
+ }
2502
+ }
2503
+ },
2504
+ "throws": [],
2505
+ "examples": [],
2506
+ "warnings": [],
2507
+ "see": [
2508
+ "get_approach",
2509
+ "list_anti_patterns",
2510
+ "get_anti_pattern",
2511
+ "get_anti_pattern_violations_for",
2512
+ "validate_graph",
2513
+ "plan",
2514
+ "reflect"
2515
+ ],
2516
+ "source": "src/tools/spec.ts:458",
2517
+ "symbol": "inspect",
2518
+ "returns": "JSON envelope: `{ approach_id, scope, generated_at, approach,\nparams, violations, summary, execution_mode: \"execution_v0_4_0\" }`",
2519
+ "atomicity": "atomic (read-only)"
2520
+ },
2521
+ {
2522
+ "name": "list_anti_patterns",
2523
+ "description": "List curated cross-domain anti-patterns from `UPG_ANTI_PATTERNS`. Each row pairs a memorable name with a machine-evaluable `IntelligenceCondition`, applicable stages, severity, and remediation. Graph-health patterns evaluated whole-graph (distinct from per-domain anti-patterns via `get_domain_guide`). Paginated (default 50, max 200). Filters AND together: `severity` (`high` / `medium` / `low`), `stage` (keeps patterns whose `stages[]` includes the given `UPGProductStage`).",
2524
+ "domain": "spec",
2525
+ "inputSchema": {
2526
+ "type": "object",
2527
+ "properties": {
2528
+ "severity": {
2529
+ "type": "string",
2530
+ "enum": [
2531
+ "high",
2532
+ "medium",
2533
+ "low"
2534
+ ],
2535
+ "description": "Exact-match UPGAntiPatternSeverity."
2536
+ },
2537
+ "stage": {
2538
+ "type": "string",
2539
+ "enum": [
2540
+ "concept",
2541
+ "validation",
2542
+ "build",
2543
+ "beta",
2544
+ "launch",
2545
+ "growth",
2546
+ "mature",
2547
+ "maintenance",
2548
+ "sunset"
2549
+ ],
2550
+ "description": "Keeps anti-patterns whose stages[] includes the given UPGProductStage."
2551
+ },
2552
+ "limit": {
2553
+ "type": "number",
2554
+ "description": "Page size (default 50, max 200)."
2555
+ },
2556
+ "cursor": {
2557
+ "type": "string",
2558
+ "description": "Opaque pagination cursor. Pass next_cursor from a previous response."
2559
+ }
2560
+ }
2561
+ },
2562
+ "throws": [],
2563
+ "examples": [
2564
+ {
2565
+ "description": "Live call against the Notion example graph.",
2566
+ "input": "{}",
2567
+ "output": "{\n \"total\": 12,\n \"count\": 12,\n \"anti_patterns\": [\n {\n \"id\": \"personas-without-jobs\",\n \"name\": \"Personas without jobs\",\n \"description\": \"The graph has persona entities, but none link into the user chain via any of the v0.2 chain edges (job, need, desired_outcome, or switching_cost). A persona without chain links is a demographic profile: who someone is, not what they are trying to get done.\",\n \"structured_condition\": {\n \"operator\": \"and\",\n \"checks\": [\n {\n \"check\": {\n \"type\": \"entity_count\",\n \"entity_type\": \"persona\",\n \"comparison\": \"nonzero\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_pursues_job\",\n \"target_type\": \"job\",\n \"comparison\": \"not_exists\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_experiences_need\",\n \"target_type\": \"need\",\n \"comparison\": \"not_exists\"\n }\n },\n {\n \"check\": {\n \"type\": \"relationship\",\n \"source_type\": \"persona\",\n \"edge_type\": \"persona_aspires_to_desired_outcome\",\n… (truncated)"
2568
+ }
2569
+ ],
2570
+ "warnings": [],
2571
+ "see": [
2572
+ "get_anti_pattern",
2573
+ "get_anti_pattern_violations_for",
2574
+ "validate_graph",
2575
+ "inspect",
2576
+ "get_domain_guide"
2577
+ ],
2578
+ "source": "src/tools/spec.ts:1383",
2579
+ "symbol": "listAntiPatterns",
2580
+ "returns": "JSON: `{ total, count, next_cursor?, anti_patterns: UPGCuratedAntiPattern[] }`",
2581
+ "atomicity": "atomic (read-only)"
2582
+ },
2583
+ {
2584
+ "name": "list_approaches",
2585
+ "description": "List the 5 canonical `UPGApproach` records: Plan, Inspect, Prioritise, Trace, Reflect. An approach is the path of arrival to a region of the graph (final approach to an airport, coastline approach). Each record carries id, label, description, `question_answered`, `signature_hint`, `framework_id_examples`. Optional `framework_id` narrows to approaches whose `framework_id_examples` include it.",
2586
+ "domain": "spec",
2587
+ "inputSchema": {
2588
+ "type": "object",
2589
+ "properties": {
2590
+ "framework_id": {
2591
+ "type": "string",
2592
+ "description": "Exact-match framework id. Narrows to approaches whose framework_id_examples include it (discoverability surface; full reverse lookup is on UPGFramework.approach_ids)."
2593
+ }
2594
+ }
2595
+ },
2596
+ "throws": [],
2597
+ "examples": [
2598
+ {
2599
+ "description": "Live call against the Notion example graph.",
2600
+ "input": "{}",
2601
+ "output": "{\n \"count\": 5,\n \"approaches\": [\n {\n \"id\": \"plan\",\n \"label\": \"Plan\",\n \"description\": \"The path of arrival to \\\"what should I build next?\\\". Plan engages a region by surveying its entity coverage against canonical expectations and surfacing the missing scaffolding: the entities a healthy region carries that this graph does not. Cartographic sense: you are walking the coastline of a region and noting where the contour is incomplete, not deciding a strategy. Frameworks like Now/Next/Later, MoSCoW, and Wardley Mapping live within Plan as the named techniques for organising the gap-filling sequence.\",\n \"question_answered\": \"what should I build next?\",\n \"signature_hint\": \"({ region?: UPGRegionId }) → { missing_entities, coverage_score }\",\n \"framework_id_examples\": [\n \"now-next-later\",\n \"moscow\",\n \"wardley-map\",\n \"okr-framework\",\n \"three-horizons\"\n ]\n },\n {\n \"id\": \"inspect\",\n \"label\": \"Inspect\",\n \"description\": \"The path of arrival to \\\"what's broken?\\\". Inspect engages a region or a set of entities by running canonical health checks (anti-pattern audits, drift reports, lint passes) and emitting a structured violation list with severity, kind, target entity, description, and fix hint. Cartographic sense: you are surveying the coastline for hazards before approach; the violations are the\n… (truncated)"
2602
+ }
2603
+ ],
2604
+ "warnings": [],
2605
+ "see": [
2606
+ "get_approach",
2607
+ "plan",
2608
+ "inspect",
2609
+ "prioritise",
2610
+ "trace",
2611
+ "reflect",
2612
+ "list_playbooks"
2613
+ ],
2614
+ "source": "src/tools/spec.ts:292",
2615
+ "symbol": "listApproaches",
2616
+ "returns": "JSON: `{ count, approaches: UPGApproach[] }`",
2617
+ "atomicity": "atomic (read-only)"
2618
+ },
2619
+ {
2620
+ "name": "list_benchmarks",
2621
+ "description": "Return one of four canonical benchmark catalogs (the data behind `get_graph_digest` health logic). Required `kind` selects the source: `count` → `UPG_COUNT_BENCHMARKS` (per-entity-type ranges across the 9-stage journey); `relationship` → `UPG_RELATIONSHIP_BENCHMARKS` (parent → child minimum counts per stage); `ratio` → `UPG_RATIO_BENCHMARKS` (expected ratios between entity-type counts); `domain_activation` → `UPG_DOMAIN_ACTIVATION` (when each atomic domain is expected to activate). Optional filters AND together: `stage` (`UPGProductStage`), `domain` (atomic-domain id). Non-paginated.",
2622
+ "domain": "spec",
2623
+ "inputSchema": {
2624
+ "type": "object",
2625
+ "properties": {
2626
+ "kind": {
2627
+ "type": "string",
2628
+ "enum": [
2629
+ "count",
2630
+ "relationship",
2631
+ "ratio",
2632
+ "domain_activation"
2633
+ ],
2634
+ "description": "Required. Which benchmark catalog to return."
2635
+ },
2636
+ "stage": {
2637
+ "type": "string",
2638
+ "enum": [
2639
+ "concept",
2640
+ "validation",
2641
+ "build",
2642
+ "beta",
2643
+ "launch",
2644
+ "growth",
2645
+ "mature",
2646
+ "maintenance",
2647
+ "sunset"
2648
+ ],
2649
+ "description": "Optional UPGProductStage filter. Semantics depend on kind (see tool description)."
2650
+ },
2651
+ "domain": {
2652
+ "type": "string",
2653
+ "description": "Optional atomic-domain id filter. Semantics depend on kind (see tool description)."
2654
+ }
2655
+ },
2656
+ "required": [
2657
+ "kind"
2658
+ ]
2659
+ },
2660
+ "throws": [
2661
+ "textError when `kind` is missing or not one of the four supported values."
2662
+ ],
2663
+ "examples": [
2664
+ {
2665
+ "description": "Live call against the Notion example graph.",
2666
+ "input": "{\n \"kind\": \"count\"\n}",
2667
+ "output": "{\n \"kind\": \"count\",\n \"total\": 47,\n \"count\": 47,\n \"benchmarks\": [\n {\n \"type\": \"product\",\n \"domain\": \"strategy\",\n \"concept\": {\n \"min\": 1,\n \"max\": 1\n },\n \"validation\": {\n \"min\": 1,\n \"max\": 1\n },\n \"build\": {\n \"min\": 1,\n \"max\": 1\n },\n \"beta\": {\n \"min\": 1,\n \"max\": 1\n },\n \"launch\": {\n \"min\": 1,\n \"max\": 1\n },\n \"growth\": {\n \"min\": 1,\n \"max\": 1\n },\n \"mature\": {\n \"min\": 1,\n \"max\": 3\n },\n \"maintenance\": {\n \"min\": 1,\n \"max\": 3\n },\n \"sunset\": {\n \"min\": 1,\n \"max\": 1\n },\n \"source\": {\n \"kind\": \"fundamental\"\n },\n \"rationale\": \"Every graph needs exactly one product (or one per product area at scale).\"\n },\n {\n \"type\": \"outcome\",\n \"domain\": \"strategy\",\n \"concept\": {\n \"min\": 1,\n \"max\": 3\n },\n \"validation\": {\n \"min\": 2,\n \"max\": 4\n },\n \"build\": {\n \"min\": 2,\n \"max\": 5\n },\n \"beta\": {\n \"min\": 3,\n \"max\": 7\n },\n \"launch\": {\n \"min\": 2,\n \"max\": 5\n },\n \"growth\": {\n \"min\": 3,\n \"max\": 8\n },\n \"mature\": {\n \"min\": 5,\n \"max\": 15\n },\n \"maintenance\": {\n \"min\": 5,\n… (truncated)"
2668
+ }
2669
+ ],
2670
+ "warnings": [],
2671
+ "see": [
2672
+ "get_graph_digest",
2673
+ "list_product_stages",
2674
+ "list_domains",
2675
+ "list_anti_patterns"
2676
+ ],
2677
+ "source": "src/tools/spec.ts:1472",
2678
+ "symbol": "listBenchmarks",
2679
+ "returns": "JSON: `{ kind, total, count, benchmarks: ... }`",
2680
+ "atomicity": "atomic (read-only)"
2681
+ },
2682
+ {
2683
+ "name": "list_cross_edge_types",
2684
+ "description": "List the canonical cross-product edge types from `UPG_CROSS_EDGE_TYPES`: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`. Portfolio-level relationships across products. Distinct from the within-product `UPG_EDGE_CATALOG`.",
2685
+ "domain": "spec",
2686
+ "inputSchema": {
2687
+ "type": "object",
2688
+ "properties": {}
2689
+ },
2690
+ "throws": [],
2691
+ "examples": [
2692
+ {
2693
+ "description": "Live call against the Notion example graph.",
2694
+ "input": "{}",
2695
+ "output": "{\n \"count\": 6,\n \"types\": [\n \"shares_persona\",\n \"shares_competitor\",\n \"shares_metric\",\n \"depends_on_product\",\n \"cannibalises\",\n \"succeeds\"\n ]\n}"
2696
+ }
2697
+ ],
2698
+ "warnings": [],
2699
+ "see": [
2700
+ "list_edge_types",
2701
+ "list_portfolio_cross_edges",
2702
+ "migrate_cross_edges"
2703
+ ],
2704
+ "source": "src/tools/spec.ts:1072",
2705
+ "symbol": "listCrossEdgeTypes",
2706
+ "returns": "JSON: `{ count, types: readonly UPGCrossEdgeType[] }`",
2707
+ "atomicity": "atomic (read-only)"
2708
+ },
2709
+ {
2710
+ "name": "list_domain_rings",
2711
+ "description": "List every `UPGDomainRing` from `UPG_DOMAIN_RINGS` in canonical order: Nucleus → Understand → Define → Build → Grow → Operate → Extend. Rings are the 7 concentric groupings of the 36 UPG atomic domains. Each ring: `{ id, label, description, domain_ids }`. Non-paginated.",
2712
+ "domain": "spec",
2713
+ "inputSchema": {
2714
+ "type": "object",
2715
+ "properties": {}
2716
+ },
2717
+ "throws": [],
2718
+ "examples": [
2719
+ {
2720
+ "description": "Live call against the Notion example graph.",
2721
+ "input": "{}",
2722
+ "output": "{\n \"rings\": [\n {\n \"id\": \"nucleus\",\n \"label\": \"Product\",\n \"description\": \"The seed\",\n \"domain_ids\": [\n \"portfolio\",\n \"workspace\"\n ]\n },\n {\n \"id\": \"understand\",\n \"label\": \"Understand\",\n \"description\": \"Who are we building for?\",\n \"domain_ids\": [\n \"user\",\n \"user_research\",\n \"market_intelligence\",\n \"discovery\",\n \"validation\",\n \"feedback\"\n ]\n },\n {\n \"id\": \"define\",\n \"label\": \"Define\",\n \"description\": \"What are we building?\",\n \"domain_ids\": [\n \"strategy\",\n \"product_spec\",\n \"legal\",\n \"ux_design\",\n \"design_system\",\n \"brand\"\n ]\n },\n {\n \"id\": \"build\",\n \"label\": \"Build\",\n \"description\": \"How do we construct it?\",\n \"domain_ids\": [\n \"engineering\",\n \"devops\",\n \"testing\",\n \"security\",\n \"accessibility\",\n \"data_analytics\",\n \"ai\",\n \"automation\"\n ]\n },\n {\n \"id\": \"grow\",\n \"label\": \"Grow\",\n \"description\": \"How do we make money?\",\n \"domain_ids\": [\n \"business_model\",\n \"growth\",\n \"go_to_market\",\n \"pricing\",\n \"sales\",\n \"marketing\"\n ]\n },\n {\n \"id\": \"operate\",\n \"label\": \"Operate\",\n \"description\": \"How do we serve?\",\n \"domain_ids\": [\n… (truncated)"
2723
+ }
2724
+ ],
2725
+ "warnings": [],
2726
+ "see": [
2727
+ "get_domain_ring",
2728
+ "list_domains",
2729
+ "get_domain_guide"
2730
+ ],
2731
+ "source": "src/tools/spec.ts:1860",
2732
+ "symbol": "listDomainRings",
2733
+ "returns": "JSON: `{ rings: UPGDomainRing[], total: number }`",
2734
+ "atomicity": "atomic (read-only)"
2735
+ },
2736
+ {
2737
+ "name": "list_domains",
2738
+ "description": "List domains. Default (`with_guide_only: true`) returns every domain with a canonical usage guide: id + `anchor_entity` + `creation_sequence`. Pass `with_guide_only: false` to enumerate every atomic domain from `UPG_DOMAINS`: id + label + description + types + `has_guide`. The two shapes are disjoint by the boolean.",
2739
+ "domain": "spec",
2740
+ "inputSchema": {
2741
+ "type": "object",
2742
+ "properties": {
2743
+ "with_guide_only": {
2744
+ "type": "boolean",
2745
+ "description": "Default true. Returns only domains with a canonical usage guide (compact id + anchor_entity + creation_sequence). Pass false to return every atomic domain (id + label + description + types + has_guide)."
2746
+ }
2747
+ }
2748
+ },
2749
+ "throws": [],
2750
+ "examples": [
2751
+ {
2752
+ "description": "Live call against the Notion example graph.",
2753
+ "input": "{}",
2754
+ "output": "{\n \"count\": 36,\n \"domains\": [\n {\n \"domain_id\": \"portfolio\",\n \"anchor_entity\": \"organization\",\n \"creation_sequence\": [\n \"organization\",\n \"portfolio\",\n \"product_area\"\n ]\n },\n {\n \"domain_id\": \"workspace\",\n \"anchor_entity\": \"workspace\",\n \"creation_sequence\": [\n \"workspace\"\n ]\n },\n {\n \"domain_id\": \"user\",\n \"anchor_entity\": \"persona\",\n \"creation_sequence\": [\n \"persona\",\n \"job\",\n \"need\",\n \"desired_outcome\",\n \"job_step\",\n \"switching_cost\"\n ]\n },\n {\n \"domain_id\": \"user_research\",\n \"anchor_entity\": \"research_study\",\n \"creation_sequence\": [\n \"research_study\",\n \"research_question\",\n \"interview_guide\",\n \"participant\",\n \"observation\",\n \"quote\",\n \"survey_response\",\n \"affinity_cluster\",\n \"insight\"\n ]\n },\n {\n \"domain_id\": \"market_intelligence\",\n \"anchor_entity\": \"competitive_analysis\",\n \"creation_sequence\": [\n \"competitive_analysis\",\n \"competitor\",\n \"competitor_feature\",\n \"market_trend\",\n \"market_segment\",\n \"classification_axis\",\n \"classification_value\"\n ]\n },\n {\n \"domain_id\": \"discovery\",\n \"anchor_entity\": \"opportunity\",\n \"creation_sequence\": [\n \"opportunity\",\n… (truncated)"
2755
+ }
2756
+ ],
2757
+ "warnings": [],
2758
+ "see": [
2759
+ "get_domain_guide",
2760
+ "list_domain_rings",
2761
+ "get_domain_ring",
2762
+ "list_regions",
2763
+ "list_entity_types"
2764
+ ],
2765
+ "source": "src/tools/spec.ts:710",
2766
+ "symbol": "listDomains",
2767
+ "returns": "JSON: `{ count, domains: Array<{ domain_id, anchor_entity, creation_sequence } | { domain_id, label, description, types, has_guide }> }`",
2768
+ "atomicity": "atomic (read-only)"
2769
+ },
2770
+ {
2771
+ "name": "list_edge_migrations",
2772
+ "description": "List every edge-key migration from `UPG_EDGE_MIGRATIONS` (renamed or dropped canonical edge keys, e.g. `persona_has_jtbd` → `persona_pursues_job`). Each row: `{ kind, from, to?, since }` where `kind` is `rename` or `drop`. Optional `from_edge` exact-matches `from`.",
2773
+ "domain": "spec",
2774
+ "inputSchema": {
2775
+ "type": "object",
2776
+ "properties": {
2777
+ "from_edge": {
2778
+ "type": "string",
2779
+ "description": "Exact-match filter on the deprecated edge key (e.g. \"persona_has_jtbd\")."
2780
+ }
2781
+ }
2782
+ },
2783
+ "throws": [],
2784
+ "examples": [
2785
+ {
2786
+ "description": "Live call against the Notion example graph.",
2787
+ "input": "{}",
2788
+ "output": "{\n \"migrations\": [\n {\n \"kind\": \"rename\",\n \"from\": \"solution_proposes_hypothesis_claim\",\n \"to\": \"solution_proposes_hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"hypothesis_claim_requires_experiment_plan\",\n \"to\": \"hypothesis_requires_experiment_plan\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"hypothesis_claim_planned_via_test_plan\",\n \"to\": \"hypothesis_planned_via_test_plan\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"hypothesis_claim_investigated_via_research_plan\",\n \"to\": \"hypothesis_investigated_via_research_plan\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"learning_updates_hypothesis_claim\",\n \"to\": \"learning_updates_hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"learning_refines_hypothesis_claim\",\n \"to\": \"learning_refines_hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"assumption_becomes_hypothesis_claim\",\n \"to\": \"assumption_becomes_hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\": \"experiment_run_validates_hypothesis_claim\",\n \"to\": \"experiment_run_validates_hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"kind\": \"rename\",\n \"from\":\n… (truncated)"
2789
+ }
2790
+ ],
2791
+ "warnings": [],
2792
+ "see": [
2793
+ "list_type_migrations",
2794
+ "list_split_migrations",
2795
+ "rename_edge_type",
2796
+ "list_edge_types",
2797
+ "validate_graph"
2798
+ ],
2799
+ "source": "src/tools/spec.ts:1644",
2800
+ "symbol": "listEdgeMigrations",
2801
+ "returns": "JSON: `{ migrations: [{ kind, from, to?, since }], total: number }`",
2802
+ "atomicity": "atomic (read-only)"
2803
+ },
2804
+ {
2805
+ "name": "list_edge_types",
2806
+ "description": "List every canonical edge type from `UPG_EDGE_CATALOG`, optionally narrowed by `source_type` and/or `target_type`. Each entry carries the edge key (`type`), forward/reverse verbs, classification, and endpoint types. The polymorphic wildcard `\"node\"` is preserved on polymorphic edges.",
2807
+ "domain": "spec",
2808
+ "inputSchema": {
2809
+ "type": "object",
2810
+ "properties": {
2811
+ "source_type": {
2812
+ "type": "string",
2813
+ "description": "Exact-match filter on UPGEdgeDefinition.source_type. Pass \"node\" to find polymorphic edges with a wildcard source."
2814
+ },
2815
+ "target_type": {
2816
+ "type": "string",
2817
+ "description": "Exact-match filter on UPGEdgeDefinition.target_type."
2818
+ }
2819
+ }
2820
+ },
2821
+ "throws": [],
2822
+ "examples": [
2823
+ {
2824
+ "description": "Live call against the Notion example graph.",
2825
+ "input": "{}",
2826
+ "output": "{\n \"count\": 938,\n \"edges\": [\n {\n \"type\": \"product_targets_persona\",\n \"forward_verb\": \"targets\",\n \"reverse_verb\": \"targeted_by\",\n \"classification\": \"semantic\",\n \"source_type\": \"product\",\n \"target_type\": \"persona\"\n },\n {\n \"type\": \"persona_pursues_job\",\n \"forward_verb\": \"pursues\",\n \"reverse_verb\": \"pursued_by\",\n \"classification\": \"semantic\",\n \"source_type\": \"persona\",\n \"target_type\": \"job\"\n },\n {\n \"type\": \"persona_experiences_need\",\n \"forward_verb\": \"experiences\",\n \"reverse_verb\": \"experienced_by\",\n \"classification\": \"semantic\",\n \"source_type\": \"persona\",\n \"target_type\": \"need\"\n },\n {\n \"type\": \"persona_aspires_to_desired_outcome\",\n \"forward_verb\": \"aspires_to\",\n \"reverse_verb\": \"aspirational_for\",\n \"classification\": \"hierarchy\",\n \"source_type\": \"persona\",\n \"target_type\": \"desired_outcome\"\n },\n {\n \"type\": \"persona_incurs_switching_cost\",\n \"forward_verb\": \"incurs\",\n \"reverse_verb\": \"incurred_by\",\n \"classification\": \"hierarchy\",\n \"source_type\": \"persona\",\n \"target_type\": \"switching_cost\"\n },\n {\n \"type\": \"job_surfaces_need\",\n \"forward_verb\": \"surfaces\",\n \"reverse_verb\": \"surfaces_from\",\n \"classification\": \"causal\",\n \"source_type\": \"job\",\n \"target_type\": \"need\"\n },\n {\n… (truncated)"
2827
+ }
2828
+ ],
2829
+ "warnings": [],
2830
+ "see": [
2831
+ "get_edge_type",
2832
+ "resolve_edge_for_pair",
2833
+ "list_cross_edge_types",
2834
+ "list_edge_migrations",
2835
+ "create_edge"
2836
+ ],
2837
+ "source": "src/tools/spec.ts:858",
2838
+ "symbol": "listEdgeTypes",
2839
+ "returns": "JSON: `{ count, edges: Array<{ type, forward_verb, reverse_verb, classification, source_type, target_type }> }`",
2840
+ "atomicity": "atomic (read-only)"
2841
+ },
2842
+ {
2843
+ "name": "list_entity_types",
2844
+ "description": "List canonical entity types from `UPG_ENTITY_META` (source of truth for ontology evolution). Every active, deprecated, or removed type with its immutable `type_id`, maturity tier, and version metadata. Paginated (default 50, max 200). Filters AND together and apply before pagination: `domain` (atomic-domain id), `maturity` (`draft` / `proposed` / `stable` / `deprecated` / `removed`), `deprecated` (boolean shortcut). Each row carries the full `EntityTypeMeta` plus resolved `domain_id` (null if no atomic-domain mapping).",
2845
+ "domain": "spec",
2846
+ "inputSchema": {
2847
+ "type": "object",
2848
+ "properties": {
2849
+ "domain": {
2850
+ "type": "string",
2851
+ "description": "Exact-match atomic-domain id (e.g. \"user\", \"market_intelligence\")."
2852
+ },
2853
+ "maturity": {
2854
+ "type": "string",
2855
+ "enum": [
2856
+ "draft",
2857
+ "proposed",
2858
+ "stable",
2859
+ "deprecated",
2860
+ "removed"
2861
+ ],
2862
+ "description": "Exact-match UPGEntityTypeMaturity."
2863
+ },
2864
+ "deprecated": {
2865
+ "type": "boolean",
2866
+ "description": "true → only deprecated types; false → exclude deprecated and removed types (the active set). Composes with maturity via AND."
2867
+ },
2868
+ "limit": {
2869
+ "type": "number",
2870
+ "description": "Page size (default 50, max 200)."
2871
+ },
2872
+ "cursor": {
2873
+ "type": "string",
2874
+ "description": "Opaque pagination cursor. Pass next_cursor from a previous response."
2875
+ }
2876
+ }
2877
+ },
2878
+ "throws": [],
2879
+ "examples": [
2880
+ {
2881
+ "description": "Live call against the Notion example graph.",
2882
+ "input": "{}",
2883
+ "output": "{\n \"total\": 349,\n \"count\": 50,\n \"types\": [\n {\n \"name\": \"product\",\n \"type_id\": \"ent_001\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"outcome\",\n \"type_id\": \"ent_002\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"kpi\",\n \"type_id\": \"ent_003\",\n \"maturity\": \"deprecated\",\n \"since\": \"0.1.0\",\n \"deprecated_in\": \"0.1.0\",\n \"replacement\": \"metric\",\n \"domain_id\": null\n },\n {\n \"name\": \"objective\",\n \"type_id\": \"ent_004\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"key_result\",\n \"type_id\": \"ent_005\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"metric\",\n \"type_id\": \"ent_006\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"metric_quality_assessment\",\n \"type_id\": \"ent_339\",\n \"maturity\": \"proposed\",\n \"since\": \"0.2.2\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"vision\",\n \"type_id\": \"ent_007\",\n \"maturity\": \"stable\",\n \"since\": \"0.1.0\",\n \"domain_id\": \"strategy\"\n },\n {\n \"name\": \"mission\",\n \"type_id\": \"ent_008\",\n \"maturity\": \"stable\",\n… (truncated)"
2884
+ }
2885
+ ],
2886
+ "warnings": [],
2887
+ "see": [
2888
+ "get_entity_meta",
2889
+ "get_entity_schema",
2890
+ "list_type_labels",
2891
+ "list_type_migrations",
2892
+ "list_domains"
2893
+ ],
2894
+ "source": "src/tools/spec.ts:1284",
2895
+ "symbol": "listEntityTypes",
2896
+ "returns": "JSON: `{ total, count, next_cursor?, types: Array<EntityTypeMeta & { domain_id: string | null }> }`",
2897
+ "atomicity": "atomic (read-only)"
2898
+ },
2899
+ {
2900
+ "name": "list_framework_categories",
2901
+ "description": "List valid framework category values from `UPG_FRAMEWORK_CATEGORIES` (e.g. \"strategy\", \"prioritization\", \"discovery\", \"growth\", \"engineering\"). Use as valid values for the `category` filter on `list_frameworks` / `get_framework`.",
2902
+ "domain": "spec",
2903
+ "inputSchema": {
2904
+ "type": "object",
2905
+ "properties": {}
2906
+ },
2907
+ "throws": [],
2908
+ "examples": [
2909
+ {
2910
+ "description": "Live call against the Notion example graph.",
2911
+ "input": "{}",
2912
+ "output": "{\n \"categories\": [\n \"prioritization\",\n \"strategy\",\n \"discovery\",\n \"business_model\",\n \"metrics\",\n \"validation\",\n \"planning\",\n \"competitive\",\n \"design\",\n \"ux_research\",\n \"user_understanding\",\n \"research\",\n \"accessibility\",\n \"feedback_voc\",\n \"engineering\",\n \"devops\",\n \"security\",\n \"qa_testing\",\n \"ai_ml\",\n \"agentic\",\n \"growth\",\n \"marketing\",\n \"go_to_market\",\n \"sales\",\n \"pricing\",\n \"data_analytics\",\n \"legal_compliance\",\n \"customer_success\",\n \"team_process\",\n \"program_mgmt\",\n \"content\",\n \"education\",\n \"partnerships\",\n \"localisation\",\n \"portfolio\"\n ],\n \"total\": 35\n}"
2913
+ }
2914
+ ],
2915
+ "warnings": [],
2916
+ "see": [
2917
+ "list_frameworks",
2918
+ "list_framework_structure_patterns"
2919
+ ],
2920
+ "source": "src/tools/spec.ts:1816",
2921
+ "symbol": "listFrameworkCategories",
2922
+ "returns": "JSON: `{ categories: string[], total: number }`",
2923
+ "atomicity": "atomic (read-only)"
2924
+ },
2925
+ {
2926
+ "name": "list_framework_structure_patterns",
2927
+ "description": "List valid framework structure-pattern values from `UPG_STRUCTURE_PATTERNS`. Visual topological shapes: tree, table, matrix, funnel, collection, quadrant, flow. Mirrors `UPGFramework.structure.pattern`.",
2928
+ "domain": "spec",
2929
+ "inputSchema": {
2930
+ "type": "object",
2931
+ "properties": {}
2932
+ },
2933
+ "throws": [],
2934
+ "examples": [
2935
+ {
2936
+ "description": "Live call against the Notion example graph.",
2937
+ "input": "{}",
2938
+ "output": "{\n \"patterns\": [\n \"tree\",\n \"table\",\n \"matrix\",\n \"funnel\",\n \"collection\",\n \"quadrant\",\n \"flow\"\n ],\n \"total\": 7\n}"
2939
+ }
2940
+ ],
2941
+ "warnings": [],
2942
+ "see": [
2943
+ "list_frameworks",
2944
+ "list_framework_categories",
2945
+ "get_framework"
2946
+ ],
2947
+ "source": "src/tools/spec.ts:1837",
2948
+ "symbol": "listFrameworkStructurePatterns",
2949
+ "returns": "JSON: `{ patterns: string[], total: number }`",
2950
+ "atomicity": "atomic (read-only)"
2951
+ },
2952
+ {
2953
+ "name": "list_frameworks",
2954
+ "description": "List canonical `UPGFramework` definitions (several hundred records spanning strategy, discovery, prioritisation, design, growth, engineering, and reflection classics). Paginated (default 50, max 200). Cursor is opaque: pass `next_cursor` from a previous response. Optional `category` is exact-match against `UPGFramework.category` and applies before pagination.",
2955
+ "domain": "spec",
2956
+ "inputSchema": {
2957
+ "type": "object",
2958
+ "properties": {
2959
+ "category": {
2960
+ "type": "string",
2961
+ "description": "Exact-match filter on UPGFramework.category (e.g. \"strategy\", \"prioritization\")."
2962
+ },
2963
+ "limit": {
2964
+ "type": "number",
2965
+ "description": "Page size (default 50, max 200)."
2966
+ },
2967
+ "cursor": {
2968
+ "type": "string",
2969
+ "description": "Opaque pagination cursor. Pass next_cursor from a previous response."
2970
+ }
2971
+ }
2972
+ },
2973
+ "throws": [],
2974
+ "examples": [
2975
+ {
2976
+ "description": "Live call against the Notion example graph.",
2977
+ "input": "{}",
2978
+ "output": "{\n \"total\": 216,\n \"count\": 50,\n \"frameworks\": [\n {\n \"id\": \"rice-scoring\",\n \"approach_ids\": [\n \"prioritise\"\n ],\n \"name\": \"RICE Scoring\",\n \"version\": \"1.0.0\",\n \"description\": \"Score features and opportunities by Reach, Impact, Confidence, and Effort to produce a ranked priority list.\",\n \"category\": \"prioritization\",\n \"origin\": {\n \"type\": \"practitioner\",\n \"attribution\": \"Intercom (Sean McBride)\",\n \"description\": \"Developed at Intercom as a way to quantify feature prioritisation. Published as a blog post that became an industry standard.\",\n \"url\": \"https://www.intercom.com/blog/rice-simple-prioritization-for-product-managers/\",\n \"year\": 2014,\n \"license\": \"open_attribution\"\n },\n \"tags\": [\n \"prioritization\",\n \"table\"\n ],\n \"slots\": [\n {\n \"label\": \"Items to score\",\n \"entityTypeId\": \"feature\",\n \"description\": \"Features, opportunities, or solutions being evaluated\"\n },\n {\n \"label\": \"Reach\",\n \"entityTypeId\": \"feature\",\n \"description\": \"How many users does this affect per quarter?\"\n },\n {\n \"label\": \"Impact\",\n \"entityTypeId\": \"feature\",\n \"description\": \"How much does this move the target outcome?\"\n },\n {\n \"label\":\n… (truncated)"
2979
+ }
2980
+ ],
2981
+ "warnings": [],
2982
+ "see": [
2983
+ "get_framework",
2984
+ "list_framework_categories",
2985
+ "list_framework_structure_patterns",
2986
+ "prioritise",
2987
+ "list_approaches"
2988
+ ],
2989
+ "source": "src/tools/spec.ts:778",
2990
+ "symbol": "listFrameworks",
2991
+ "returns": "JSON: `{ total, count, next_cursor?, frameworks: UPGFramework[] }`",
2992
+ "atomicity": "atomic (read-only)"
2993
+ },
2994
+ {
2995
+ "name": "list_lenses",
2996
+ "description": "List every canonical `UPGLens` from `@unified-product-graph/core`: Product, Design, Engineering, Growth, Business, Research, Marketing, Full. Returns a compact summary per lens: id, name, description, icon, audience, perspective, `framework_id`, `playbook_id`, `visible_domain_count`, `intelligence_prompt_count`. Use `get_lens` for the full record.",
2997
+ "domain": "spec",
2998
+ "inputSchema": {
2999
+ "type": "object",
3000
+ "properties": {}
3001
+ },
3002
+ "throws": [],
3003
+ "examples": [
3004
+ {
3005
+ "description": "Live call against the Notion example graph.",
3006
+ "input": "{}",
3007
+ "output": "{\n \"count\": 8,\n \"lenses\": [\n {\n \"id\": \"product\",\n \"name\": \"Product\",\n \"description\": \"Full graph, PM vocabulary, outcome-driven workflow\",\n \"icon\": \"target\",\n \"audience\": \"Product managers, founders making strategic decisions, and anyone thinking about what to build and why\",\n \"perspective\": \"Sees the product as a system of outcomes, opportunities, and validated bets. Outcome-driven, evidence-aware, strategically oriented.\",\n \"framework_id\": \"ost\",\n \"visible_domain_count\": 0,\n \"intelligence_prompt_count\": 5\n },\n {\n \"id\": \"ux_design\",\n \"name\": \"Design\",\n \"description\": \"User-centric view with design vocabulary, journey-first workflow\",\n \"icon\": \"pen-tool\",\n \"audience\": \"Designers, UX researchers, and anyone focused on the user experience\",\n \"perspective\": \"Sees the product through the eyes of the people using it. Journey-first, evidence-based, obsessed with friction and delight.\",\n \"framework_id\": \"design_thinking\",\n \"playbook_id\": \"playbook:experience-design-brand\",\n \"visible_domain_count\": 7,\n \"intelligence_prompt_count\": 5\n },\n {\n \"id\": \"engineering\",\n \"name\": \"Engineering\",\n \"description\": \"Architecture-first view of the product as a technical system\",\n \"icon\": \"cpu\",\n \"audience\": \"Developers, CTOs, and anyone building the technical\n… (truncated)"
3008
+ }
3009
+ ],
3010
+ "warnings": [],
3011
+ "see": [
3012
+ "get_lens",
3013
+ "list_regions",
3014
+ "list_playbooks",
3015
+ "list_frameworks"
3016
+ ],
3017
+ "source": "src/tools/spec.ts:1101",
3018
+ "symbol": "listLenses",
3019
+ "returns": "JSON: `{ count, lenses: Array<{ id, name, description, icon, audience, perspective, framework_id?, playbook_id?, visible_domain_count, intelligence_prompt_count }> }`",
3020
+ "atomicity": "atomic (read-only)"
3021
+ },
3022
+ {
3023
+ "name": "list_lifecycles",
3024
+ "description": "List lifecycle definitions from `UPG_LIFECYCLES`. Response includes `free_types` (`UPG_LIFECYCLE_FREE_TYPES`: static types with no phase progression) and `planned_types` (`UPG_LIFECYCLE_PLANNED_TYPES`: lifecycle planned but not yet authored). Filters: `entity_type` (exact-match), `lifecycle_only` (when true, omits the free/planned lists).",
3025
+ "domain": "spec",
3026
+ "inputSchema": {
3027
+ "type": "object",
3028
+ "properties": {
3029
+ "entity_type": {
3030
+ "type": "string",
3031
+ "description": "Exact-match entity type name (e.g. \"feature\", \"hypothesis\"). Returns at most one lifecycle."
3032
+ },
3033
+ "lifecycle_only": {
3034
+ "type": "boolean",
3035
+ "description": "When true, omit free_types and planned_types from response."
3036
+ }
3037
+ }
3038
+ },
3039
+ "throws": [],
3040
+ "examples": [
3041
+ {
3042
+ "description": "Live call against the Notion example graph.",
3043
+ "input": "{}",
3044
+ "output": "{\n \"total\": 189,\n \"lifecycles\": [\n {\n \"entity_type\": \"product\",\n \"initial_phase\": \"concept\",\n \"terminal_phases\": [\n \"sunset\"\n ],\n \"phases\": [\n {\n \"id\": \"concept\",\n \"label\": \"Concept\",\n \"description\": \"Napkin idea. Problem shape and solution sketch, pre-validation.\",\n \"transitions_to\": [\n \"validation\",\n \"build\",\n \"beta\",\n \"launch\",\n \"growth\",\n \"mature\",\n \"sunset\"\n ]\n },\n {\n \"id\": \"validation\",\n \"label\": \"Validation\",\n \"description\": \"Testing demand. User conversations and experiments pressure-test assumptions before build commits.\",\n \"transitions_to\": [\n \"build\",\n \"beta\",\n \"launch\",\n \"growth\",\n \"mature\",\n \"sunset\"\n ]\n },\n {\n \"id\": \"build\",\n \"label\": \"Build\",\n \"description\": \"Actively developing v1. Core functionality is being authored, pre-user.\",\n \"transitions_to\": [\n \"beta\",\n \"launch\",\n \"growth\",\n \"mature\",\n \"sunset\"\n ]\n },\n {\n \"id\": \"beta\",\n \"label\": \"Beta\",\n \"description\": \"Early users, iterating. Feature-complete enough to learn from,\n… (truncated)"
3045
+ }
3046
+ ],
3047
+ "warnings": [],
3048
+ "see": [
3049
+ "get_lifecycle",
3050
+ "list_entity_types",
3051
+ "get_entity_meta"
3052
+ ],
3053
+ "source": "src/tools/spec.ts:1709",
3054
+ "symbol": "listLifecycles",
3055
+ "returns": "JSON: `{ lifecycles, total, free_types: string[], planned_types: string[] }`",
3056
+ "atomicity": "atomic (read-only)"
3057
+ },
3058
+ {
3059
+ "name": "list_playbooks",
3060
+ "description": "List canonical UPG playbooks from `@unified-product-graph/core`. Each playbook bootstraps a region; its `creation_sequence` answers \"what to create when populating this region\". Filters: `region`, `canonical_only`, `framework_id`. The catalog spans 10 regions: one canonical playbook per region, plus specialised playbooks (three carry a `framework_id`: BMC, AARRR, build-measure-learn).",
3061
+ "domain": "spec",
3062
+ "inputSchema": {
3063
+ "type": "object",
3064
+ "properties": {
3065
+ "region": {
3066
+ "type": "string",
3067
+ "description": "Exact-match UPGRegionId (e.g. \"users_needs\", \"business_gtm_growth\")."
3068
+ },
3069
+ "canonical_only": {
3070
+ "type": "boolean",
3071
+ "description": "When true, return only the canonical playbook per region (W1 invariant restated)."
3072
+ },
3073
+ "framework_id": {
3074
+ "type": "string",
3075
+ "description": "Exact-match UPGFramework.id (e.g. \"business-model-canvas\", \"pirate-metrics-aarrr\")."
3076
+ }
3077
+ }
3078
+ },
3079
+ "throws": [],
3080
+ "examples": [
3081
+ {
3082
+ "description": "Live call against the Notion example graph.",
3083
+ "input": "{}",
3084
+ "output": "{\n \"count\": 23,\n \"playbooks\": [\n {\n \"id\": \"playbook:strategy-outcomes\",\n \"name\": \"Strategy & Outcomes\",\n \"version\": \"0.2.0\",\n \"description\": \"Cascade vision through themes, outcomes, objectives, key results — and the bets you are making to get there.\",\n \"region\": \"strategy_outcomes\",\n \"is_canonical\": true,\n \"target_anchor_entity\": \"objective\",\n \"creation_sequence\": [\n {\n \"kind\": \"entity_sequence\",\n \"order\": 1,\n \"phase\": \"Vision & Mission\",\n \"name\": \"Vision & Mission\",\n \"prompt_hint\": \"Name what you are building toward and how you will get there. Vision is the destination; mission is the orientation. One of each — more is dilution.\",\n \"entity_types\": [\n \"vision\",\n \"mission\"\n ]\n },\n {\n \"kind\": \"entity_sequence\",\n \"order\": 2,\n \"phase\": \"Themes\",\n \"name\": \"Themes\",\n \"prompt_hint\": \"Choose 2–4 strategic themes that focus the work. Past four, you have lost focus, not gained coverage.\",\n \"entity_types\": [\n \"strategic_theme\",\n \"strategic_pillar\"\n ]\n },\n {\n \"kind\": \"entity_sequence\",\n \"order\": 3,\n \"phase\": \"Outcomes\",\n \"name\": \"Outcomes\",\n \"prompt_hint\": \"Frame the changes in the world the\n… (truncated)"
3085
+ }
3086
+ ],
3087
+ "warnings": [],
3088
+ "see": [
3089
+ "get_playbook",
3090
+ "list_regions",
3091
+ "list_approaches",
3092
+ "list_frameworks"
3093
+ ],
3094
+ "source": "src/tools/spec.ts:204",
3095
+ "symbol": "listPlaybooks",
3096
+ "returns": "JSON: `{ count, playbooks: UPGPlaybook[] }`",
3097
+ "atomicity": "atomic (read-only)"
3098
+ },
3099
+ {
3100
+ "name": "list_product_stages",
3101
+ "description": "Return the canonical 9-stage product journey from `UPG_PRODUCT_STAGES` in order: concept → validation → build → beta → launch → growth → mature → maintenance → sunset. The closed enum used by `create_product`, `get_graph_digest` health logic, benchmark stage scoping, and anti-pattern stage filters.",
3102
+ "domain": "spec",
3103
+ "inputSchema": {
3104
+ "type": "object",
3105
+ "properties": {}
3106
+ },
3107
+ "throws": [],
3108
+ "examples": [
3109
+ {
3110
+ "description": "Live call against the Notion example graph.",
3111
+ "input": "{}",
3112
+ "output": "{\n \"count\": 9,\n \"stages\": [\n \"concept\",\n \"validation\",\n \"build\",\n \"beta\",\n \"launch\",\n \"growth\",\n \"mature\",\n \"maintenance\",\n \"sunset\"\n ]\n}"
3113
+ }
3114
+ ],
3115
+ "warnings": [],
3116
+ "see": [
3117
+ "list_benchmarks",
3118
+ "list_anti_patterns",
3119
+ "list_domain_rings",
3120
+ "create_product"
3121
+ ],
3122
+ "source": "src/tools/spec.ts:1580",
3123
+ "symbol": "listProductStages",
3124
+ "returns": "JSON: `{ count, stages: readonly UPGProductStage[] }`",
3125
+ "atomicity": "atomic (read-only)"
3126
+ },
3127
+ {
3128
+ "name": "list_regions",
3129
+ "description": "List the 10 canonical UPG super-domain regions from `UPG_REGIONS`. Returns a compact summary per region: id, label, order, shape, `mental_model`, `anchor_type`, `composes_atomic_domains`, `entity_count`, `intra_edge_count`, `boundary_edge_count`. Fixed list, non-paginated.",
3130
+ "domain": "spec",
3131
+ "inputSchema": {
3132
+ "type": "object",
3133
+ "properties": {}
3134
+ },
3135
+ "throws": [],
3136
+ "examples": [
3137
+ {
3138
+ "description": "Live call against the Notion example graph.",
3139
+ "input": "{}",
3140
+ "output": "{\n \"count\": 10,\n \"regions\": [\n {\n \"id\": \"strategy_outcomes\",\n \"label\": \"Strategy & Outcomes\",\n \"order\": 1,\n \"shape\": \"cascade\",\n \"mental_model\": \"Aspiration → direction → bet → measurable → proof.\",\n \"anchor_type\": \"objective\",\n \"composes_atomic_domains\": [\n \"strategy\"\n ],\n \"entity_count\": 13,\n \"intra_edge_count\": 19,\n \"boundary_edge_count\": 7\n },\n {\n \"id\": \"users_needs\",\n \"label\": \"Users & Needs\",\n \"order\": 2,\n \"shape\": \"convergent\",\n \"mental_model\": \"Who → what they want to do → what is in their way → what they would accept as done.\",\n \"anchor_type\": \"persona\",\n \"composes_atomic_domains\": [\n \"user\"\n ],\n \"entity_count\": 7,\n \"intra_edge_count\": 5,\n \"boundary_edge_count\": 22\n },\n {\n \"id\": \"discovery_research_validation\",\n \"label\": \"Discovery, Research & Validation\",\n \"order\": 3,\n \"shape\": \"directed-cyclic\",\n \"mental_model\": \"Question → hypothesis → test → evidence → decision → loop back.\",\n \"anchor_type\": \"opportunity\",\n \"composes_atomic_domains\": [\n \"discovery\",\n \"validation\",\n \"user_research\"\n ],\n \"entity_count\": 19,\n \"intra_edge_count\": 13,\n \"boundary_edge_count\": 11\n },\n {\n \"id\": \"market_competitive\",\n \"label\": \"Market & Competitive\",\n… (truncated)"
3141
+ }
3142
+ ],
3143
+ "warnings": [],
3144
+ "see": [
3145
+ "get_region",
3146
+ "get_region_for_entity_type",
3147
+ "list_domains",
3148
+ "list_playbooks"
3149
+ ],
3150
+ "source": "src/tools/spec.ts:904",
3151
+ "symbol": "listRegions",
3152
+ "returns": "JSON: `{ count, regions: Array<{ id, label, order, shape, mental_model, anchor_type, composes_atomic_domains, entity_count, intra_edge_count, boundary_edge_count }> }`",
3153
+ "atomicity": "atomic (read-only)"
3154
+ },
3155
+ {
3156
+ "name": "list_scales",
3157
+ "description": "List every spec-defined assessment scale from `UPG_SCALES` (canonical vocabulary for `UPGAssessment` values). Each scale carries id, label, description, min, max, steps, and per-point labels + descriptions. Non-paginated. External `scale_extensions` are graph-instance–scoped and excluded here.",
3158
+ "domain": "spec",
3159
+ "inputSchema": {
3160
+ "type": "object",
3161
+ "properties": {}
3162
+ },
3163
+ "throws": [],
3164
+ "examples": [
3165
+ {
3166
+ "description": "Live call against the Notion example graph.",
3167
+ "input": "{}",
3168
+ "output": "{\n \"scales\": [\n {\n \"id\": \"reach_5\",\n \"label\": \"Reach (5-point)\",\n \"description\": \"How many users experience this problem or benefit from this feature\",\n \"min\": 1,\n \"max\": 5,\n \"steps\": 5,\n \"points\": [\n {\n \"value\": 1,\n \"label\": \"Almost no one\",\n \"description\": \"Affects <5% of users\"\n },\n {\n \"value\": 2,\n \"label\": \"A few\",\n \"description\": \"Affects 5-20% of users\"\n },\n {\n \"value\": 3,\n \"label\": \"Some\",\n \"description\": \"Affects 20-50% of users\"\n },\n {\n \"value\": 4,\n \"label\": \"Most\",\n \"description\": \"Affects 50-80% of users\"\n },\n {\n \"value\": 5,\n \"label\": \"Nearly everyone\",\n \"description\": \"Affects >80% of users\"\n }\n ]\n },\n {\n \"id\": \"frequency_5\",\n \"label\": \"Frequency (5-point)\",\n \"description\": \"How often the problem or situation occurs\",\n \"min\": 1,\n \"max\": 5,\n \"steps\": 5,\n \"points\": [\n {\n \"value\": 1,\n \"label\": \"Rarely\",\n \"description\": \"Less than once a month\"\n },\n {\n \"value\": 2,\n \"label\": \"Occasionally\",\n \"description\": \"A few times a month\"\n },\n {\n \"value\": 3,\n \"label\": \"Sometimes\",\n… (truncated)"
3169
+ }
3170
+ ],
3171
+ "warnings": [],
3172
+ "see": [
3173
+ "get_scale",
3174
+ "get_entity_schema"
3175
+ ],
3176
+ "source": "src/tools/spec.ts:1782",
3177
+ "symbol": "listScales",
3178
+ "returns": "JSON: `{ scales: UPGScaleDefinition[], total: number }`",
3179
+ "atomicity": "atomic (read-only)"
3180
+ },
3181
+ {
3182
+ "name": "list_split_migrations",
3183
+ "description": "List every 1→N split migration from `UPG_SPLIT_MIGRATIONS` (\"one type became multiple types\" rules, e.g. `experiment` → `experiment_plan` + `experiment_run`; `hypothesis` → `hypothesis_claim` + `hypothesis_evidence`). Each row: the full `UPGSplitMigration` record plus `since`. Non-paginated.",
3184
+ "domain": "spec",
3185
+ "inputSchema": {
3186
+ "type": "object",
3187
+ "properties": {}
3188
+ },
3189
+ "throws": [],
3190
+ "examples": [
3191
+ {
3192
+ "description": "Live call against the Notion example graph.",
3193
+ "input": "{}",
3194
+ "output": "{\n \"splits\": [\n {\n \"kind\": \"status_routed\",\n \"from\": \"experiment\",\n \"status_property\": \"status\",\n \"produces\": [\n {\n \"ref\": \"plan\",\n \"type\": \"experiment_plan\",\n \"keep_props\": [\n \"__id\",\n \"method\",\n \"sample_size\",\n \"expected_lift\",\n \"expected_lift_unit\"\n ],\n \"defaults\": {}\n },\n {\n \"ref\": \"run\",\n \"type\": \"experiment_run\",\n \"keep_props\": [\n \"actual_lift\",\n \"start_date\",\n \"end_date\"\n ],\n \"defaults\": {}\n }\n ],\n \"routing\": {\n \"draft\": {\n \"spawn\": [\n \"plan\"\n ],\n \"plan\": {\n \"defaults\": {\n \"status\": \"drafted\"\n }\n }\n },\n \"planned\": {\n \"spawn\": [\n \"plan\"\n ],\n \"plan\": {\n \"defaults\": {\n \"status\": \"scheduled\"\n }\n }\n },\n \"cancelled\": {\n \"spawn\": [\n \"plan\"\n ],\n \"plan\": {\n \"defaults\": {\n \"status\": \"cancelled\"\n }\n }\n },\n \"running\": {\n \"spawn\": [\n \"plan\",\n \"run\"\n ],\n \"plan\": {\n \"defaults\": {\n… (truncated)"
3195
+ }
3196
+ ],
3197
+ "warnings": [],
3198
+ "see": [
3199
+ "list_type_migrations",
3200
+ "list_edge_migrations",
3201
+ "migrate_type",
3202
+ "validate_graph"
3203
+ ],
3204
+ "source": "src/tools/spec.ts:1681",
3205
+ "symbol": "listSplitMigrations",
3206
+ "returns": "JSON: `{ splits: [...], total: number }`",
3207
+ "atomicity": "atomic (read-only)"
3208
+ },
3209
+ {
3210
+ "name": "list_type_labels",
3211
+ "description": "List canonical `UPGTypeLabel` entries: each entity type's display label, alt-labels (synonyms), per-framework labels, and designation labels where applicable. Paginated (default 100, max 500). Cursor is opaque base64 (`offset:N`), same convention as `list_frameworks`. External MCP apps need labels for rendering.",
3212
+ "domain": "spec",
3213
+ "inputSchema": {
3214
+ "type": "object",
3215
+ "properties": {
3216
+ "limit": {
3217
+ "type": "number",
3218
+ "description": "Page size (default 100, max 500)."
3219
+ },
3220
+ "cursor": {
3221
+ "type": "string",
3222
+ "description": "Opaque pagination cursor. Pass next_cursor from a previous response."
3223
+ }
3224
+ }
3225
+ },
3226
+ "throws": [],
3227
+ "examples": [
3228
+ {
3229
+ "description": "Live call against the Notion example graph.",
3230
+ "input": "{}",
3231
+ "output": "{\n \"total\": 312,\n \"count\": 100,\n \"labels\": [\n {\n \"id\": \"product\",\n \"canonical_label\": \"Product\",\n \"alt_labels\": [\n \"offering\",\n \"app\",\n \"service\",\n \"platform\",\n \"product\"\n ],\n \"framework_labels\": {\n \"three-horizons\": \"Product\",\n \"marketing-mix-4ps\": \"Product\"\n }\n },\n {\n \"id\": \"outcome\",\n \"canonical_label\": \"Outcome\",\n \"alt_labels\": [\n \"desired outcome\",\n \"product outcome\",\n \"business outcome\",\n \"target outcome\",\n \"root outcome\",\n \"consequences\",\n \"what went well\",\n \"outcome\"\n ],\n \"framework_labels\": {\n \"opportunity-solution-tree\": \"Root Outcome\",\n \"adr-log\": \"Consequences\",\n \"retrospective\": \"What Went Well\",\n \"metrics-tree\": \"Outcome\",\n \"ost\": \"Desired Outcome\",\n \"okr_tree\": \"Outcome\"\n }\n },\n {\n \"id\": \"objective\",\n \"canonical_label\": \"Objective\",\n \"alt_labels\": [\n \"goal\",\n \"strategic goal\",\n \"team goal\",\n \"objective\"\n ],\n \"framework_labels\": {\n \"okr-framework\": \"Objective\",\n \"okr_tree\": \"Objective\"\n }\n },\n {\n \"id\": \"key_result\",\n \"canonical_label\": \"Key Result\",\n \"alt_labels\": [\n \"kr\",\n \"measurable result\",\n \"target\",\n \"key result 1\",\n… (truncated)"
3232
+ }
3233
+ ],
3234
+ "warnings": [],
3235
+ "see": [
3236
+ "get_type_label",
3237
+ "list_entity_types",
3238
+ "get_entity_meta"
3239
+ ],
3240
+ "source": "src/tools/spec.ts:1167",
3241
+ "symbol": "listTypeLabels",
3242
+ "returns": "JSON: `{ total, count, next_cursor?, labels: UPGTypeLabel[] }`",
3243
+ "atomicity": "atomic (read-only)"
3244
+ },
3245
+ {
3246
+ "name": "list_type_migrations",
3247
+ "description": "List every type-rename migration from `UPG_MIGRATIONS` (version-scoped registry of deprecated `from` → canonical `to` renames, e.g. `pain_point` → `need`, `hypothesis` → `hypothesis_claim`). Each row: `{ from, to, since }` where `since` is the spec version that introduced it. Optional `from_type` exact-matches `from`.",
3248
+ "domain": "spec",
3249
+ "inputSchema": {
3250
+ "type": "object",
3251
+ "properties": {
3252
+ "from_type": {
3253
+ "type": "string",
3254
+ "description": "Exact-match filter on the deprecated type name (e.g. \"pain_point\", \"hypothesis\")."
3255
+ }
3256
+ }
3257
+ },
3258
+ "throws": [],
3259
+ "examples": [
3260
+ {
3261
+ "description": "Live call against the Notion example graph.",
3262
+ "input": "{}",
3263
+ "output": "{\n \"migrations\": [\n {\n \"from\": \"hypothesis_claim\",\n \"to\": \"hypothesis\",\n \"since\": \"0.4.0\"\n },\n {\n \"from\": \"hypothesis_evidence\",\n \"to\": \"evidence\",\n \"since\": \"0.4.0\"\n },\n {\n \"from\": \"story_task\",\n \"to\": \"task\",\n \"since\": \"0.4.0\"\n },\n {\n \"from\": \"hypothesis\",\n \"to\": \"hypothesis_claim\",\n \"since\": \"0.2.8\"\n },\n {\n \"from\": \"pain_point\",\n \"to\": \"need\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"user_need\",\n \"to\": \"need\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"research_insight\",\n \"to\": \"insight\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"finding\",\n \"to\": \"insight\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"ux_insight\",\n \"to\": \"insight\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"highlight\",\n \"to\": \"observation\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"kpi\",\n \"to\": \"metric\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"north_star_metric\",\n \"to\": \"metric\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"input_metric\",\n \"to\": \"metric\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"metric_definition\",\n \"to\": \"metric\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"ab_test\",\n \"to\": \"experiment_run\",\n \"since\": \"0.1.0\"\n },\n {\n \"from\": \"growth_experiment\",\n… (truncated)"
3264
+ }
3265
+ ],
3266
+ "warnings": [],
3267
+ "see": [
3268
+ "list_edge_migrations",
3269
+ "list_split_migrations",
3270
+ "migrate_type",
3271
+ "migrate_properties",
3272
+ "validate_graph",
3273
+ "list_entity_types"
3274
+ ],
3275
+ "source": "src/tools/spec.ts:1613",
3276
+ "symbol": "listTypeMigrations",
3277
+ "returns": "JSON: `{ migrations: [{ from, to, since }], total: number }`",
3278
+ "atomicity": "atomic (read-only)"
3279
+ },
3280
+ {
3281
+ "name": "plan",
3282
+ "description": "Plan approach: path of arrival to \"what should I build next?\". Returns the Plan record + invocation params wrapped in `{ approach_id, scope, generated_at, approach, params }`. The LLM consumes `signature_hint` and synthesises `{ missing_entities, coverage_score }` against the live graph. Optional `region` narrows scope.",
3283
+ "domain": "spec",
3284
+ "inputSchema": {
3285
+ "type": "object",
3286
+ "properties": {
3287
+ "region": {
3288
+ "type": "string",
3289
+ "description": "Optional UPGRegionId. Narrows planning scope to a single region (e.g. \"users_needs\", \"business_gtm_growth\"). Omit for whole-graph planning."
3290
+ }
3291
+ }
3292
+ },
3293
+ "throws": [],
3294
+ "examples": [
3295
+ {
3296
+ "description": "Live call against the Notion example graph.",
3297
+ "input": "{}",
3298
+ "output": "{\n \"approach_id\": \"plan\",\n \"scope\": null,\n \"generated_at\": \"2026-05-26T13:23:53.077Z\",\n \"approach\": {\n \"id\": \"plan\",\n \"label\": \"Plan\",\n \"description\": \"The path of arrival to \\\"what should I build next?\\\". Plan engages a region by surveying its entity coverage against canonical expectations and surfacing the missing scaffolding: the entities a healthy region carries that this graph does not. Cartographic sense: you are walking the coastline of a region and noting where the contour is incomplete, not deciding a strategy. Frameworks like Now/Next/Later, MoSCoW, and Wardley Mapping live within Plan as the named techniques for organising the gap-filling sequence.\",\n \"question_answered\": \"what should I build next?\",\n \"signature_hint\": \"({ region?: UPGRegionId }) → { missing_entities, coverage_score }\",\n \"framework_id_examples\": [\n \"now-next-later\",\n \"moscow\",\n \"wardley-map\",\n \"okr-framework\",\n \"three-horizons\"\n ]\n },\n \"params\": {\n \"region\": null\n },\n \"region\": null,\n \"missing_entities\": [],\n \"coverage_score\": 1,\n \"expected_count\": 312,\n \"covered_count\": 312,\n \"execution_mode\": \"execution_v0_4_0\"\n}"
3299
+ }
3300
+ ],
3301
+ "warnings": [],
3302
+ "see": [
3303
+ "get_approach",
3304
+ "list_playbooks",
3305
+ "get_region",
3306
+ "inspect",
3307
+ "prioritise"
3308
+ ],
3309
+ "source": "src/tools/spec.ts:418",
3310
+ "symbol": "plan",
3311
+ "returns": "JSON envelope: `{ approach_id, scope, generated_at, approach,\nparams, missing_entities, coverage_score, expected_count, covered_count,\nexecution_mode: \"execution_v0_4_0\" }`.",
3312
+ "atomicity": "atomic (read-only)"
3313
+ },
3314
+ {
3315
+ "name": "prioritise",
3316
+ "description": "[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing prioritisation, invoke the /upg-prioritise skill instead of calling this tool directly. — Prioritise approach: path of arrival to \"what's most important?\". Returns the Prioritise record + invocation params + framework metadata in the family-resemblance envelope. Both `candidates` and `framework_id` are required. The LLM looks up the framework via `get_framework`, reads its scoring spec, and emits `{ ranked: [{ entity_id, score, rationale }], framework_used }`.",
3317
+ "domain": "spec",
3318
+ "inputSchema": {
3319
+ "type": "object",
3320
+ "properties": {
3321
+ "candidates": {
3322
+ "type": "array",
3323
+ "items": {
3324
+ "type": "string"
3325
+ },
3326
+ "description": "Required. entity_id[] to rank."
3327
+ },
3328
+ "framework_id": {
3329
+ "type": "string",
3330
+ "description": "Required. UPGFramework.id of the scoring lens (e.g. \"rice-scoring\", \"ice-scoring\", \"kano-model\", \"cost-of-delay\", \"wsjf\")."
3331
+ }
3332
+ },
3333
+ "required": [
3334
+ "candidates",
3335
+ "framework_id"
3336
+ ]
3337
+ },
3338
+ "throws": [
3339
+ "textError when `candidates` or `framework_id` are missing/empty,\nor when `framework_id` is not in `UPG_FRAMEWORKS`."
3340
+ ],
3341
+ "examples": [],
3342
+ "warnings": [],
3343
+ "see": [
3344
+ "get_approach",
3345
+ "list_frameworks",
3346
+ "get_framework",
3347
+ "plan",
3348
+ "trace"
3349
+ ],
3350
+ "source": "src/tools/spec.ts:517",
3351
+ "symbol": "prioritise",
3352
+ "returns": "JSON envelope: `{ approach_id, scope, generated_at, approach,\nparams, framework_resolved, ranked?, required_properties?,\nhint?, execution_mode }`. Execution mode is `\"execution_v0_4_0\"` when\nthe framework has an expression, `\"definition_lookup_v0_4_0\"` otherwise.",
3353
+ "atomicity": "atomic (read-only)"
3354
+ },
3355
+ {
3356
+ "name": "reflect",
3357
+ "description": "[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing reflection, invoke the /upg-reflect skill instead of calling this tool directly. — Reflect approach: path of arrival to \"what should I be questioning?\". Returns the Reflect record + invocation params in the family-resemblance envelope. The LLM consumes `mode` + `scope` + `signature_hint` and emits `{ prompts: [{ kind, question, target_entities? }] }`. `mode` is one of: `assumptions`, `alternatives`, `blind-spots`, `load-bearing`; omit for open reflection. `scope` accepts a region id, entity id, or `null` for whole-graph.",
3358
+ "domain": "spec",
3359
+ "inputSchema": {
3360
+ "type": "object",
3361
+ "properties": {
3362
+ "scope": {
3363
+ "type": [
3364
+ "string",
3365
+ "null"
3366
+ ],
3367
+ "description": "Optional. Region id, entity id, or null for whole-graph."
3368
+ },
3369
+ "mode": {
3370
+ "type": "string",
3371
+ "description": "Optional. One of: assumptions, alternatives, blind-spots, load-bearing. Omit for open reflection.",
3372
+ "enum": [
3373
+ "assumptions",
3374
+ "alternatives",
3375
+ "blind-spots",
3376
+ "load-bearing"
3377
+ ]
3378
+ }
3379
+ }
3380
+ },
3381
+ "throws": [
3382
+ "textError when `mode` is provided but not one of the 4 canonical\nnouns."
3383
+ ],
3384
+ "examples": [
3385
+ {
3386
+ "description": "Live call against the Notion example graph.",
3387
+ "input": "{}",
3388
+ "output": "{\n \"approach_id\": \"reflect\",\n \"scope\": null,\n \"generated_at\": \"2026-05-26T13:23:53.082Z\",\n \"approach\": {\n \"id\": \"reflect\",\n \"label\": \"Reflect\",\n \"description\": \"The path of arrival to \\\"what should I be questioning?\\\". Reflect engages an optional scope (region, entity, or `null` for the whole graph) and emits structured prompts a thinker should consider: assumptions to test, alternatives to weigh, blind-spots to surface, load-bearing claims to verify. Mode is optional; absence is open reflection. Cartographic sense: before approaching the coastline, you are asking which features of your chart you have not actually verified; the prompts mark the parts of the map that may be conjecture. Five Whys, Pre-mortem, Red Team, and Devil's Advocate are the named techniques inside this approach.\",\n \"question_answered\": \"what should I be questioning?\",\n \"signature_hint\": \"({ scope?: UPGRegionId | entity_id | null, mode?: 'assumptions' | 'alternatives' | 'blind-spots' | 'load-bearing' }) → { prompts: [{ kind, question, target_entities? }] }\",\n \"framework_id_examples\": [\n \"five-whys\",\n \"pre-mortem\",\n \"red-team\",\n \"devils-advocate\",\n \"second-order-thinking\",\n \"retrospective\",\n \"four-forces-of-progress\",\n \"assumption-canvas\",\n \"win-loss-analysis\"\n ]\n },\n \"params\": {\n \"scope\": null,\n \"mode\": null\n },\n \"prompts\": [\n… (truncated)"
3389
+ }
3390
+ ],
3391
+ "warnings": [],
3392
+ "see": [
3393
+ "get_approach",
3394
+ "inspect",
3395
+ "plan",
3396
+ "get_anti_pattern"
3397
+ ],
3398
+ "source": "src/tools/spec.ts:660",
3399
+ "symbol": "reflect",
3400
+ "returns": "JSON envelope: `{ approach_id, scope, generated_at, approach,\nparams, prompts, execution_mode: \"execution_v0_4_0\" }`",
3401
+ "atomicity": "atomic (read-only)"
3402
+ },
3403
+ {
3404
+ "name": "resolve_edge_for_pair",
3405
+ "description": "Resolve the canonical `UPGEdgeType` for a `source_type` → `target_type` containment pair. Wraps `resolveContainmentEdge` / `UPG_EDGE_PAIR_MAP`. Adapter-critical: every import adapter (Markdown, Notion, Linear, GitHub) uses it to look up the right `_contains_` edge before falling back to a polymorphic edge. Returns `{ edge_type: null }` when the pair is uncatalogued.",
3406
+ "domain": "spec",
3407
+ "inputSchema": {
3408
+ "type": "object",
3409
+ "properties": {
3410
+ "source_type": {
3411
+ "type": "string",
3412
+ "description": "Parent / source entity type."
3413
+ },
3414
+ "target_type": {
3415
+ "type": "string",
3416
+ "description": "Child / target entity type."
3417
+ }
3418
+ },
3419
+ "required": [
3420
+ "source_type",
3421
+ "target_type"
3422
+ ]
3423
+ },
3424
+ "throws": [
3425
+ "textError when `source_type` or `target_type` is missing."
3426
+ ],
3427
+ "examples": [
3428
+ {
3429
+ "description": "Live call against the Notion example graph.",
3430
+ "input": "{\n \"source_type\": \"example\",\n \"target_type\": \"example\"\n}",
3431
+ "output": "{\n \"source_type\": \"example\",\n \"target_type\": \"example\",\n \"edge_type\": null\n}"
3432
+ }
3433
+ ],
3434
+ "warnings": [
3435
+ "Returns `edge_type: null` when no canonical pair is registered.\nAdapters MUST fall back to a polymorphic edge or skip the relationship,\nnot synthesise a non-canonical key."
3436
+ ],
3437
+ "see": [
3438
+ "list_edge_types",
3439
+ "get_edge_type",
3440
+ "create_edge",
3441
+ "trace"
3442
+ ],
3443
+ "source": "src/tools/spec.ts:1040",
3444
+ "symbol": "resolveEdgeForPair",
3445
+ "returns": "JSON: `{ source_type, target_type, edge_type: string | null,\nanchor_hint?, alternate_anchors?, adjacent_edges? }`",
3446
+ "atomicity": "atomic (read-only)"
3447
+ },
3448
+ {
3449
+ "name": "trace",
3450
+ "description": "[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing tracing, invoke the /upg-trace skill instead of calling this tool directly. — Trace approach: path of arrival to \"walk a meaningful path through existing graph\". Returns the Trace record + invocation params in the family-resemblance envelope. The LLM uses `anchor` + `path` to compose `query()` calls and emits `{ trail: [{ depth, entity_id, edge_type_in }], reached: entity_id[] }`. `path` is type-shorthand: `[\"persona\",\"job\",\"feature\"]` walks persona→job→feature using the canonical edge per pair (via `resolve_edge_for_pair`). Optional `edges_override` selects non-canonical edges per hop; `null` per element means \"use canonical\".",
3451
+ "domain": "spec",
3452
+ "inputSchema": {
3453
+ "type": "object",
3454
+ "properties": {
3455
+ "anchor": {
3456
+ "type": "string",
3457
+ "description": "Required. entity_id where the traversal starts."
3458
+ },
3459
+ "path": {
3460
+ "type": "array",
3461
+ "items": {
3462
+ "type": "string"
3463
+ },
3464
+ "description": "Required. UPGEntityType[] type-shorthand path. Each step walks via the canonical edge for the source→target pair."
3465
+ },
3466
+ "edges_override": {
3467
+ "type": "array",
3468
+ "items": {
3469
+ "type": [
3470
+ "string",
3471
+ "null"
3472
+ ]
3473
+ },
3474
+ "description": "Optional. Per-hop edge override array. Length must match path length; element null means \"use canonical edge for this pair\"."
3475
+ }
3476
+ },
3477
+ "required": [
3478
+ "anchor",
3479
+ "path"
3480
+ ]
3481
+ },
3482
+ "throws": [
3483
+ "textError when `anchor` or `path` are missing/invalid."
3484
+ ],
3485
+ "examples": [
3486
+ {
3487
+ "description": "Live call against the Notion example graph.",
3488
+ "input": "{\n \"anchor\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"path\": [\n \"opportunity_drives_solution\"\n ]\n}",
3489
+ "output": "{\n \"approach_id\": \"trace\",\n \"scope\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"generated_at\": \"2026-05-26T13:23:53.077Z\",\n \"approach\": {\n \"id\": \"trace\",\n \"label\": \"Trace\",\n \"description\": \"The path of arrival to \\\"walk a meaningful path through existing graph\\\". Trace engages an anchor entity and follows a path expressed as a UPGEntityType[] shorthand. Example: `[\\\"persona\\\", \\\"job\\\", \\\"feature\\\"]` walks persona→job→feature using the canonical edge for each pair (resolved via `resolve_edge_for_pair`). An optional `edges_override` array selects non-canonical edges per hop when a pair has multiple resolutions. Cartographic sense: you are tracing a route across charted terrain; anchor is the departure, path is the heading sequence, the canonical edges are the roads. No DSL invented; the shorthand IS the path expression.\",\n \"question_answered\": \"walk a meaningful path through existing graph\",\n \"signature_hint\": \"({ anchor: entity_id, path: UPGEntityType[], edges_override?: (string | null)[] }) → { trail: [{ depth, entity_id, edge_type_in }], reached: entity_id[] }\",\n \"framework_id_examples\": [\n \"opportunity-solution-tree\",\n \"strategic-cascade\",\n \"metrics-tree\",\n \"user-journey-map\",\n \"impact-map\",\n \"dependency-map\"\n ]\n },\n \"params\": {\n \"anchor\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"path\": [\n… (truncated)"
3490
+ }
3491
+ ],
3492
+ "warnings": [],
3493
+ "see": [
3494
+ "get_approach",
3495
+ "resolve_edge_for_pair",
3496
+ "query",
3497
+ "get_node",
3498
+ "plan",
3499
+ "prioritise"
3500
+ ],
3501
+ "source": "src/tools/spec.ts:597",
3502
+ "symbol": "trace",
3503
+ "returns": "JSON envelope: `{ approach_id, scope, generated_at, approach,\nparams, trail, reached, error?, halted_at_depth?,\nexecution_mode: \"execution_v0_4_0\" }`",
3504
+ "atomicity": "atomic (read-only)"
3505
+ },
3506
+ {
3507
+ "name": "apply_pull_changeset",
3508
+ "description": "Apply cloud changes to the local `.upg` file. Takes cloud nodes and edges (from `export_upg_document` on the cloud server), computes the diff, merges into the local graph, and updates `.upg-sync` with new mappings. `strategy`: `cloud_wins` (default), `local_wins`, or `merge` (reports conflicts without resolving).",
3509
+ "domain": "sync",
3510
+ "inputSchema": {
3511
+ "type": "object",
3512
+ "properties": {
3513
+ "cloud_nodes": {
3514
+ "type": "array",
3515
+ "description": "All cloud nodes (from export_upg_document)",
3516
+ "items": {
3517
+ "type": "object"
3518
+ }
3519
+ },
3520
+ "cloud_edges": {
3521
+ "type": "array",
3522
+ "description": "All cloud edges (from export_upg_document)",
3523
+ "items": {
3524
+ "type": "object"
3525
+ }
3526
+ },
3527
+ "cloud_product_id": {
3528
+ "type": "string",
3529
+ "description": "Cloud product ID"
3530
+ },
3531
+ "cloud_endpoint": {
3532
+ "type": "string",
3533
+ "description": "Cloud endpoint URL (e.g. https://cloud.unifiedproductgraph.org)"
3534
+ },
3535
+ "strategy": {
3536
+ "type": "string",
3537
+ "enum": [
3538
+ "cloud_wins",
3539
+ "local_wins",
3540
+ "merge"
3541
+ ],
3542
+ "description": "Conflict resolution: cloud_wins (default), local_wins, or merge (report conflicts without resolving)"
3543
+ }
3544
+ },
3545
+ "required": [
3546
+ "cloud_nodes",
3547
+ "cloud_edges",
3548
+ "cloud_product_id"
3549
+ ]
3550
+ },
3551
+ "throws": [
3552
+ "Returns a textError when `cloud_nodes`, `cloud_edges`, or\n`cloud_product_id` is missing, or when sync-state I/O fails."
3553
+ ],
3554
+ "examples": [],
3555
+ "warnings": [
3556
+ "Mutates the active product. Always call `get_workspace_info`\nfirst to confirm the right product is loaded; otherwise cloud changes\nland in the wrong file. `merge` strategy returns conflicts without\napplying them; the caller must re-run with `cloud_wins`/`local_wins`\nto commit."
3557
+ ],
3558
+ "see": [
3559
+ "push_to_cloud",
3560
+ "get_sync_state",
3561
+ "get_workspace_info",
3562
+ "get_changes"
3563
+ ],
3564
+ "source": "src/tools/sync.ts:80",
3565
+ "symbol": "applyPullChangeset",
3566
+ "returns": "JSON: `{ nodes_created, nodes_updated, nodes_deleted,\nedges_created, edges_deleted, strategy, conflicts?, message? }`.",
3567
+ "atomicity": "non-atomic. Node/edge mutations apply incrementally; a partial\nfailure mid-application leaves the graph in a half-merged state. The\n`.upg-sync` file is updated after the merge sweep so its hashes reflect\nwhatever landed."
3568
+ },
3569
+ {
3570
+ "name": "get_sync_state",
3571
+ "description": "Read the `.upg-sync` file for the active product. Returns cloud product ID, ID mappings, last sync timestamp. Returns null when the product has never been pushed.",
3572
+ "domain": "sync",
3573
+ "inputSchema": {
3574
+ "type": "object",
3575
+ "properties": {}
3576
+ },
3577
+ "throws": [],
3578
+ "examples": [
3579
+ {
3580
+ "description": "Live call against the Notion example graph.",
3581
+ "input": "{}",
3582
+ "output": "{\n \"synced\": false,\n \"message\": \"No .upg-sync file found. This product has never been pushed to the cloud.\"\n}"
3583
+ }
3584
+ ],
3585
+ "warnings": [],
3586
+ "see": [
3587
+ "push_to_cloud",
3588
+ "apply_pull_changeset",
3589
+ "get_workspace_info",
3590
+ "get_changes"
3591
+ ],
3592
+ "source": "src/tools/sync.ts:31",
3593
+ "symbol": "getSyncState",
3594
+ "returns": "JSON: `{ synced: false, message }` or\n`{ synced: true, cloud_endpoint, product_id, last_synced_at,\nmapped_nodes, mapped_edges, last_snapshot_hash }`.",
3595
+ "atomicity": "atomic (read-only)"
3596
+ },
3597
+ {
3598
+ "name": "push_to_cloud",
3599
+ "description": "Push the current local graph to the cloud in one call. Reads the in-memory graph, POSTs to the cloud import endpoint, and creates or updates the `.upg-sync` file with ID mappings. Auto-discovers `cloud_endpoint` and `api_key` from a `upg-cloud` entry in `.mcp.json`. Recommended push path from Claude Code (zero context cost).",
3600
+ "domain": "sync",
3601
+ "inputSchema": {
3602
+ "type": "object",
3603
+ "properties": {
3604
+ "cloud_endpoint": {
3605
+ "type": "string",
3606
+ "description": "Cloud base URL. Auto-discovered from .mcp.json upg-cloud entry if omitted."
3607
+ },
3608
+ "api_key": {
3609
+ "type": "string",
3610
+ "description": "UPG Cloud API key. Auto-discovered from .mcp.json upg-cloud entry if omitted."
3611
+ },
3612
+ "strategy": {
3613
+ "type": "string",
3614
+ "enum": [
3615
+ "create_new",
3616
+ "merge",
3617
+ "replace"
3618
+ ],
3619
+ "description": "Import strategy. Default: create_new"
3620
+ },
3621
+ "product_id": {
3622
+ "type": "string",
3623
+ "description": "Optional. Push to an existing cloud product instead of creating new."
3624
+ }
3625
+ },
3626
+ "required": []
3627
+ },
3628
+ "throws": [
3629
+ "Returns a textError when credentials cannot be resolved, the cloud\nreturns a non-2xx response, or the sync file write fails."
3630
+ ],
3631
+ "examples": [],
3632
+ "warnings": [
3633
+ "Pushes the **currently-loaded** product. Call\n`get_workspace_info` first to confirm. Auto-discovers credentials\nfrom `.mcp.json`'s `upg-cloud` server entry; falls back to explicit\n`cloud_endpoint` + `api_key` arguments. Default `strategy: 'create_new'`\ncreates a fresh cloud product on every call; pass `product_id` to\ntarget an existing one."
3634
+ ],
3635
+ "see": [
3636
+ "apply_pull_changeset",
3637
+ "get_sync_state",
3638
+ "get_workspace_info"
3639
+ ],
3640
+ "source": "src/tools/sync.ts:264",
3641
+ "symbol": "pushToCloud",
3642
+ "returns": "JSON: `{ success, product_id, nodes_created, edges_created,\nerrors, sync_file_updated }`.",
3643
+ "atomicity": "non-atomic. Performs an HTTP round-trip and then writes the\nsync file as a separate filesystem mutation. A partial failure (e.g.\ncloud accepted some entities, then network broke) is reflected in the\n`errors` array; the sync file is only updated when the import call\nsucceeds."
3644
+ },
3645
+ {
3646
+ "name": "get_anti_pattern_violations_for",
3647
+ "description": "Reverse lookup: given an entity id, return anti-pattern violations whose `target_entities` include the entity's type. Use after `validate_graph` to drill into one entity's implicated patterns. Matches by entity type today; tightens to specific ids in a future revision. Underpins the Inspect approach.",
3648
+ "domain": "validation",
3649
+ "inputSchema": {
3650
+ "type": "object",
3651
+ "properties": {
3652
+ "entity_id": {
3653
+ "type": "string",
3654
+ "description": "Node id to look up."
3655
+ }
3656
+ },
3657
+ "required": [
3658
+ "entity_id"
3659
+ ]
3660
+ },
3661
+ "throws": [
3662
+ "textError when `entity_id` is missing or unknown."
3663
+ ],
3664
+ "examples": [
3665
+ {
3666
+ "description": "Live call against the Notion example graph.",
3667
+ "input": "{\n \"entity_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\"\n}",
3668
+ "output": "{\n \"entity_id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"violations\": []\n}"
3669
+ }
3670
+ ],
3671
+ "warnings": [
3672
+ "Phase 1 matches by entity TYPE, not specific id. Every entity of\nthe same type shares the same violation set. Phase 1.x will tighten to\nper-id matching once `target_entities` carries ids."
3673
+ ],
3674
+ "see": [
3675
+ "validate_graph",
3676
+ "list_anti_patterns",
3677
+ "get_anti_pattern",
3678
+ "inspect"
3679
+ ],
3680
+ "source": "src/tools/validation.ts:880",
3681
+ "symbol": "getAntiPatternViolationsFor",
3682
+ "returns": "JSON: `{ entity_id, type, violations: [...] }`.",
3683
+ "atomicity": "atomic (read-only)"
3684
+ },
3685
+ {
3686
+ "name": "validate_graph",
3687
+ "description": "Walk the loaded graph and return a per-class, per-node report of schema drift plus anti-pattern violations from `UPG_ANTI_PATTERNS`. Schema-drift classes: non-canonical entity types, non-canonical edge types, top-level fields outside `UPGBaseNode`, invalid status values, self-referential `source_id`/`source_type`, properties matching `UPG_PROPERTY_MIGRATIONS` rules. Anti-patterns: catalog entries that fired against the live graph, sorted high → medium → low. Each entry carries `suggested_migration` (drift) or `remediation` (anti-pattern). Top-level `valid` is true iff drift is empty AND no violations fired. Read-only; pairs with `migrate_type`, `rename_edge_type`, `get_anti_pattern_violations_for`.",
3688
+ "domain": "validation",
3689
+ "inputSchema": {
3690
+ "type": "object",
3691
+ "properties": {
3692
+ "scope": {
3693
+ "type": "string",
3694
+ "enum": [
3695
+ "all",
3696
+ "entity_drift",
3697
+ "edge_drift",
3698
+ "property_drift",
3699
+ "top_level_drift",
3700
+ "lifecycle_drift",
3701
+ "self_referential"
3702
+ ],
3703
+ "description": "Which drift class(es) to include in the response (default \"all\"). Counts in `summary` are always returned for every class."
3704
+ },
3705
+ "limit": {
3706
+ "type": "number",
3707
+ "description": "Max entries per class (default 100, max 1000)"
3708
+ },
3709
+ "severity": {
3710
+ "type": "string",
3711
+ "enum": [
3712
+ "high",
3713
+ "medium",
3714
+ "low"
3715
+ ],
3716
+ "description": "Filter anti-pattern violations to one severity tier."
3717
+ },
3718
+ "anti_pattern_ids": {
3719
+ "type": "array",
3720
+ "items": {
3721
+ "type": "string"
3722
+ },
3723
+ "description": "Restrict anti-pattern evaluation to a subset of catalog ids (e.g. [\"features-without-hypotheses\"])."
3724
+ },
3725
+ "skip_drift": {
3726
+ "type": "boolean",
3727
+ "description": "Skip the schema-drift block. Only returns anti-pattern violations."
3728
+ },
3729
+ "skip_anti_patterns": {
3730
+ "type": "boolean",
3731
+ "description": "Skip anti-pattern evaluation. Only returns schema drift."
3732
+ },
3733
+ "if_changed_since": {
3734
+ "type": "string",
3735
+ "description": "Hash from a previous response. Returns { changed: false } if graph unchanged."
3736
+ },
3737
+ "include_polymorphic_upgrades": {
3738
+ "type": "boolean",
3739
+ "description": "When true, include a `polymorphic_with_typed_alternative` array listing polymorphic edges (e.g. node_owned_by_person, node_constrains_node) that have a more-specific typed alternative for their actual source/target pair. Opt-in only — omitted by default to avoid cluttering routine validation output. Does not affect `valid`; these are advisory suggestions."
3740
+ }
3741
+ }
3742
+ },
3743
+ "throws": [
3744
+ "Returns a textError when `scope` or `severity` is not one of the\nrecognised values."
3745
+ ],
3746
+ "examples": [
3747
+ {
3748
+ "description": "Live call against the Notion example graph.",
3749
+ "input": "{}",
3750
+ "output": "{\n \"valid\": false,\n \"summary\": {\n \"entity_drift\": 0,\n \"edge_drift\": 0,\n \"top_level_drift\": 0,\n \"lifecycle_drift\": 0,\n \"self_referential\": 0,\n \"property_drift\": 164,\n \"total_nodes\": 2054,\n \"total_edges\": 3120,\n \"spec_version\": \"0.5.8\",\n \"scope\": \"all\",\n \"limit\": 100,\n \"anti_pattern_violations_high\": 0,\n \"anti_pattern_violations_medium\": 1,\n \"anti_pattern_violations_low\": 0,\n \"edge_type_pair_drift\": 100,\n \"graph_topology_self_loops\": 0,\n \"property_type_drift\": 100\n },\n \"_hash\": \"592a8d0ae65f17ac\",\n \"entity_drift\": [],\n \"edge_drift\": [],\n \"top_level_drift\": [],\n \"lifecycle_drift\": [],\n \"self_referential\": [],\n \"property_drift\": [\n {\n \"id\": \"ec3d5479-4947-4bd9-9e77-b5ee01beb851\",\n \"type\": \"product\",\n \"property\": \"stage\",\n \"via\": \"UPG_PROPERTY_MIGRATIONS['0.2.13']\"\n },\n {\n \"id\": \"3f1c1804-b33b-4b1f-9094-c91cc8957912\",\n \"type\": \"assumption\",\n \"property\": \"validation_status\",\n \"via\": \"UPG_PROPERTY_MIGRATIONS['0.5.0']\"\n },\n {\n \"id\": \"6719fce7-61e7-44df-b6cf-92f7c7312554\",\n \"type\": \"assumption\",\n \"property\": \"validation_status\",\n \"via\": \"UPG_PROPERTY_MIGRATIONS['0.5.0']\"\n },\n {\n \"id\": \"7b22f5f3-b344-4e49-8d5a-35cf9087dc6c\",\n \"type\": \"assumption\",\n \"property\": \"validation_status\",\n \"via\": \"UPG_PROPERTY_MIGRATIONS['0.5.0']\"\n… (truncated)"
3751
+ }
3752
+ ],
3753
+ "warnings": [
3754
+ "Top-level `valid` is true ONLY when both drift is empty AND no\nanti-pattern violations fired. Set `skip_anti_patterns: true` for a\npure spec-shape check; `skip_drift: true` for catalog-only."
3755
+ ],
3756
+ "see": [
3757
+ "migrate_type",
3758
+ "migrate_properties",
3759
+ "rename_edge_type",
3760
+ "get_anti_pattern_violations_for",
3761
+ "list_anti_patterns",
3762
+ "list_type_migrations",
3763
+ "list_edge_migrations",
3764
+ "inspect"
3765
+ ],
3766
+ "source": "src/tools/validation.ts:231",
3767
+ "symbol": "validateGraph",
3768
+ "returns": "JSON: `{ valid, summary, entity_drift?, edge_drift?,\nproperty_drift?, top_level_drift?, lifecycle_drift?, self_referential?,\nanti_pattern_violations?, notes?, _hash }`. Per-class drift arrays appear\nonly when the requested `scope` includes that class. Each array is capped\nat `limit` (default 100).",
3769
+ "atomicity": "atomic (read-only)"
3770
+ },
3771
+ {
3772
+ "name": "migrate_status",
3773
+ "description": "Apply `UPG_STATUS_MIGRATIONS` graph-wide: rewrite legacy lifecycle status values to canonical phase ids. Auto-mode (no filters) selects nodes whose current status is invalid against the entity type's lifecycle and has a registered replacement (the same invariant that drives `validate_graph` lifecycle_drift). Surgical mode (`from_status` + `to_status`) overrides the registry and rewrites every (entity_type?, from_status) match. Nodes with invalid statuses but no registered replacement surface under `skipped_no_migration`. Default `dry_run=true`; pass `dry_run=false` to commit.",
3774
+ "domain": "migrations",
3775
+ "inputSchema": {
3776
+ "type": "object",
3777
+ "properties": {
3778
+ "entity_type": {
3779
+ "type": "string",
3780
+ "description": "Optional. Restrict the rewrite to nodes of this canonical entity type (e.g. \"service\", \"feature\")."
3781
+ },
3782
+ "from_status": {
3783
+ "type": "string",
3784
+ "description": "Optional. Restrict the rewrite to nodes whose current status equals this exact value. When provided, `to_status` is required and the registry is bypassed."
3785
+ },
3786
+ "to_status": {
3787
+ "type": "string",
3788
+ "description": "Required when `from_status` is provided. The canonical phase id to write."
3789
+ },
3790
+ "dry_run": {
3791
+ "type": "boolean",
3792
+ "description": "Preview changes without applying (default true). Pass false to commit."
3793
+ }
3794
+ }
3795
+ },
3796
+ "throws": [
3797
+ "Returns a textError when `from_status` is provided without\n`to_status`, or when `entity_type` is provided but isn't a string."
3798
+ ],
3799
+ "examples": [
3800
+ {
3801
+ "description": "Live call against the Notion example graph.",
3802
+ "input": "{}",
3803
+ "output": "{\n \"migrated_nodes\": 0,\n \"skipped_no_migration\": 0,\n \"changes\": [],\n \"dry_run\": true\n}"
3804
+ }
3805
+ ],
3806
+ "warnings": [
3807
+ "Default is `dry_run: true`. Pass `dry_run: false` to commit.\nIdempotent on retry — re-running after a successful commit reports\nzero changes (canonical statuses pass the validity check)."
3808
+ ],
3809
+ "see": [
3810
+ "migrate_type",
3811
+ "migrate_properties",
3812
+ "validate_graph",
3813
+ "list_lifecycles"
3814
+ ],
3815
+ "source": "src/tools/migrations.ts:64",
3816
+ "symbol": "migrateStatus",
3817
+ "returns": "JSON: `MigrateStatusResult`.",
3818
+ "atomicity": "per-node. Status writes go through `store.updateNode`\none at a time. Dry-run is read-only."
3819
+ },
3820
+ {
3821
+ "name": "skill_audit",
3822
+ "description": "Audit one or every UPG skill for source-vs-deployed integrity. Use before recommending a skill to confirm `.claude/skills/<name>/SKILL.md` is a symlink to canonical source and the bodies match. When `in_sync: false`, what you read from `packages/upg-mcp-server/skills/` is NOT what the user will experience.",
3823
+ "domain": "skills",
3824
+ "inputSchema": {
3825
+ "type": "object",
3826
+ "properties": {
3827
+ "name": {
3828
+ "type": "string",
3829
+ "description": "Optional skill name (e.g. \"upg-trace\"). If omitted, audits every canonical skill."
3830
+ }
3831
+ }
3832
+ },
3833
+ "throws": [],
3834
+ "examples": [
3835
+ {
3836
+ "description": "Live call against the Notion example graph.",
3837
+ "input": "{}",
3838
+ "output": "{\n \"skills\": []\n}"
3839
+ }
3840
+ ],
3841
+ "warnings": [],
3842
+ "see": [],
3843
+ "source": "src/tools/skills.ts:191",
3844
+ "symbol": "skillAudit",
3845
+ "returns": "`{ skills: SkillAuditRecord[] }`",
3846
+ "atomicity": "atomic (read-only filesystem stat + read)"
3847
+ }
3848
+ ]
3849
+ }