@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
package/TOOLS.md ADDED
@@ -0,0 +1,2574 @@
1
+ # UPG MCP Server: Tool Reference
2
+
3
+ Reference for the 93 tools exposed by `@unified-product-graph/mcp-server`. Generated from JSDoc on `src/tools/*.ts` (do not edit by hand).
4
+
5
+ ## Contents
6
+
7
+ - [Context & Session](#context-session): 4 tools
8
+ - [Nodes](#nodes): 14 tools
9
+ - [Edges](#edges): 9 tools
10
+ - [Areas & Change Log](#areas-change-log): 5 tools
11
+ - [Workspace & Portfolios](#workspace-portfolios): 10 tools
12
+ - [Schema](#schema): 1 tool
13
+ - [Spec Introspection](#spec-introspection): 43 tools
14
+ - [Cloud Sync](#cloud-sync): 3 tools
15
+ - [Validation](#validation): 2 tools
16
+ - [Migrations](#migrations): 1 tool
17
+ - [Skills Introspection](#skills-introspection): 1 tool
18
+
19
+ ## Context & Session
20
+
21
+ _Product overview, graph digest, lens-aware session state._
22
+
23
+ - [`get_graph_digest`](#get-graph-digest)
24
+ - [`get_product_context`](#get-product-context)
25
+ - [`get_session_context`](#get-session-context)
26
+ - [`update_session_context`](#update-session-context)
27
+
28
+ ### `get_graph_digest`
29
+
30
+ 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.
31
+
32
+ **Atomicity:** `atomic (read-only)`
33
+
34
+ **Arguments:**
35
+
36
+ | Name | Type | Required | Description |
37
+ | ---- | ---- | -------- | ----------- |
38
+ | `if_changed_since` | string | | Hash from a previous response. Returns { changed: false } if graph unchanged (saves ~470 tokens). |
39
+
40
+ **Returns:**
41
+
42
+ JSON object: `{ counts, health, chains, coverage, lifecycle,
43
+ lens, lens_digest, _hash }`. ~500 tokens vs ~5-8K for equivalent manual
44
+ fetches.
45
+
46
+ **Examples:**
47
+
48
+ // Fetch machine-readable graph health metrics (no args required)
49
+ // Input:
50
+ {}
51
+ // Output (truncated):
52
+ {
53
+ "counts": { "total": 42, "by_type": { "persona": 3, "job": 7, "feature": 12, "hypothesis": 8 } },
54
+ "health": { "orphan_rate": 0.05, "edge_density": 0.74 },
55
+ "chains": { "hypothesis_total": 8, "hypothesis_untested": 6, "hypothesis_validated": 2 },
56
+ "coverage": {
57
+ "identity": { "covered": 1, "total": 3, "counted_toward_stage": true, "types_present": ["product"], "types_missing": ["vision", "mission"] },
58
+ "sustaining": { "covered": 0, "total": 5, "counted_toward_stage": false, "types_present": [], "types_missing": ["business_model", "revenue_stream", "cost_structure", "unit_economics", "pricing_strategy"] },
59
+ "stage_summary": { "stage": "concept", "regions_counted": 3, "regions_complete": 0, "regions_partial": 1, "overall_pct": 11 }
60
+ },
61
+ "lens": "product",
62
+ "lens_digest": { "personas": 3, "outcomes": 5, "hypotheses_validated": 2 },
63
+ "_hash": "sha256-abc123"
64
+ }
65
+
66
+ **See also:** `get_product_context`
67
+
68
+
69
+ ### `get_product_context`
70
+
71
+ 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.
72
+
73
+ **Atomicity:** `atomic (read-only)`
74
+
75
+ **Arguments:**
76
+
77
+ | Name | Type | Required | Description |
78
+ | ---- | ---- | -------- | ----------- |
79
+ | `if_changed_since` | string | | Hash from a previous response. Returns { changed: false } if graph unchanged. |
80
+ | `include_summary` | boolean | | Include detailed graph statistics (edge counts by type, orphan count) |
81
+
82
+ **Returns:**
83
+
84
+ Markdown string with product header, lens preamble, entity counts,
85
+ active-domain creation sequences, and `_hash` footer for `if_changed_since`
86
+ diffing.
87
+
88
+ **Examples:**
89
+
90
+ // Get the product overview in the default product lens (no args required)
91
+ // Input:
92
+ {}
93
+ // Output (truncated):
94
+ "## Checkout Redesign\nAn e-commerce checkout optimisation product.\nStage: build\nLens: product\n\n### 🧭 Product Lens\n- Personas: 3\n- Outcomes: 5\n- Hypotheses: 8 (2 validated)\n\n### Graph Stats\n- Nodes: 42\n- Edges: 31\n- Entity types: 9\n...\n_hash: sha256-abc123"
95
+
96
+ **See also:** `get_graph_digest`, `get_entity_schema`
97
+
98
+
99
+ ### `get_session_context`
100
+
101
+ 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).
102
+
103
+ **Atomicity:** `atomic (read-only)`
104
+
105
+ _No arguments._
106
+
107
+ **Returns:**
108
+
109
+ JSON: `{ lens, skills_invoked, recommendations_given,
110
+ recommendations_to_avoid, focus_area, custom, skills_count, last_skill,
111
+ last_recommendation }`. `recommendations_to_avoid` is the deduped list of
112
+ every recommendation given this session — runners should filter their
113
+ next recommendation against this array rather than re-deriving the
114
+ dedup rule from prose.
115
+
116
+ **See also:** `update_session_context`
117
+
118
+
119
+ ### `update_session_context`
120
+
121
+ Update session context: register a skill invocation, record a recommendation, set focus area, switch lens, or store custom state for cross-skill coordination.
122
+
123
+ **Atomicity:** `non-atomic. Session mutates in-memory immediately; lens
124
+ persistence flushes the .upg file as a separate side-effect that may
125
+ succeed or fail independently of the session update.`
126
+
127
+ **Arguments:**
128
+
129
+ | Name | Type | Required | Description |
130
+ | ---- | ---- | -------- | ----------- |
131
+ | `custom` | object | | Arbitrary key-value pairs for cross-skill state |
132
+ | `focus_area` | string | | Set the current focus area (e.g. "strategy", "validation", "user_research") |
133
+ | `lens` | `product` \| `engineering` \| `design` \| `growth` | | Switch the active lens. Changes what context, skills, and gaps are surfaced first. |
134
+ | `persist_lens` | boolean | | If true, also save the lens to the .upg file so it persists across sessions |
135
+ | `recommendation` | string | | Record a recommendation given to the user (e.g. "Run /upg-strategy to fill strategy gap") |
136
+ | `skill_invoked` | string | | Register that this skill was just invoked (e.g. "upg-status") |
137
+
138
+ **Returns:**
139
+
140
+ JSON: `{ updated: true, session: SessionContext }` reflecting the
141
+ new state.
142
+
143
+ **See also:** `get_session_context`
144
+
145
+
146
+ ## Nodes
147
+
148
+ _Read, search, traverse, mutate, batch, migrate, dedupe._
149
+
150
+ - [`batch_create_nodes`](#batch-create-nodes)
151
+ - [`batch_delete_nodes`](#batch-delete-nodes)
152
+ - [`batch_update_nodes`](#batch-update-nodes)
153
+ - [`create_node`](#create-node)
154
+ - [`deduplicate_nodes`](#deduplicate-nodes)
155
+ - [`delete_node`](#delete-node)
156
+ - [`get_node`](#get-node)
157
+ - [`get_nodes`](#get-nodes)
158
+ - [`list_nodes`](#list-nodes)
159
+ - [`migrate_properties`](#migrate-properties)
160
+ - [`migrate_type`](#migrate-type)
161
+ - [`query`](#query)
162
+ - [`search_nodes`](#search-nodes)
163
+ - [`update_node`](#update-node)
164
+
165
+ ### `batch_create_nodes`
166
+
167
+ 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.
168
+
169
+ **Atomicity:** `atomic-with-rollback. Full validation pass first, then commit.`
170
+
171
+ **Arguments:**
172
+
173
+ | Name | Type | Required | Description |
174
+ | ---- | ---- | -------- | ----------- |
175
+ | `edges` | array | | 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. |
176
+ | `nodes` | array | ✓ | Array of nodes to create (max 50) |
177
+
178
+ **Returns:**
179
+
180
+ JSON: `{ created, edges_created, count, edges_count, warnings? }`.
181
+
182
+ **Throws:**
183
+
184
+ - Returns a textError when `nodes` is missing/non-array or any
185
+ validation fails.
186
+
187
+ **See also:** `create_node`, `batch_create_edges`
188
+
189
+
190
+ ### `batch_delete_nodes`
191
+
192
+ Delete up to 50 entities and their connected edges in one atomic call (all succeed or all fail).
193
+
194
+ **Atomicity:** `atomic. Validation pass rejects the entire batch before any
195
+ mutation lands.`
196
+
197
+ **Arguments:**
198
+
199
+ | Name | Type | Required | Description |
200
+ | ---- | ---- | -------- | ----------- |
201
+ | `node_ids` | array | ✓ | Array of node IDs to delete (max 50) |
202
+
203
+ **Returns:**
204
+
205
+ JSON: `{ deleted, edges_removed, count }`.
206
+
207
+ **Throws:**
208
+
209
+ - Returns a textError when `node_ids` is missing/non-array, empty,
210
+ longer than 50, or any ID does not resolve.
211
+
212
+ **See also:** `delete_node`
213
+
214
+
215
+ ### `batch_update_nodes`
216
+
217
+ Update up to 50 entities atomically (all succeed or all fail). Unspecified fields preserved. Properties merge with existing.
218
+
219
+ **Atomicity:** `atomic. Validation pass rejects the entire batch before any
220
+ mutation lands.`
221
+
222
+ **Arguments:**
223
+
224
+ | Name | Type | Required | Description |
225
+ | ---- | ---- | -------- | ----------- |
226
+ | `updates` | array | ✓ | Array of updates to apply (max 50) |
227
+
228
+ **Returns:**
229
+
230
+ JSON: `{ updated, count, warnings? }`. `warnings` carries
231
+ lifecycle-phase hints aggregated across the batch.
232
+
233
+ **Throws:**
234
+
235
+ - Returns a textError when `updates` is missing/non-array, the array
236
+ is empty, longer than 50, or any item references a missing node.
237
+
238
+ **See also:** `update_node`
239
+
240
+
241
+ ### `create_node`
242
+
243
+ 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[]`.
244
+
245
+ **Atomicity:** `atomic-with-rollback. Schema validation runs before mutation.`
246
+
247
+ **Arguments:**
248
+
249
+ | Name | Type | Required | Description |
250
+ | ---- | ---- | -------- | ----------- |
251
+ | `description` | string | | Optional description |
252
+ | `overwrite_organization` | boolean | | For type="organization" only. When true, replaces the existing portfolio organisation instead of throwing. |
253
+ | `parent_id` | string | | Parent node ID. Creates an edge automatically. Ignored for portfolio-scoped types. |
254
+ | `properties` | object | | Type-specific fields |
255
+ | `status` | string | | Lifecycle status |
256
+ | `tags` | array | | Freeform tags |
257
+ | `title` | string | ✓ | Entity title |
258
+ | `type` | string | ✓ | UPG entity type (e.g. "persona", "opportunity"). Portfolio-scoped: "portfolio", "organization", "product_area". |
259
+
260
+ **Returns:**
261
+
262
+ JSON: `{ node, edge?, unknown_properties?, warning? }`. The `edge`
263
+ field is present only when `parent_id` was supplied and a canonical
264
+ hierarchy edge could be inferred. `unknown_properties` and `warning` are
265
+ present when the caller passed properties not in the entity's schema
266
+ . Pass `strict: true` to reject unknown properties instead of
267
+ warning. For portfolio-scoped types the response shape is
268
+ `{ node, portfolio_file, written_to, warning? }` where `node` is the
269
+ persisted typed record.
270
+
271
+ **Throws:**
272
+
273
+ - Returns a textError when `type` or `title` is missing, when the type
274
+ is unknown (`UnknownEntityTypeError`), when `strict: true` and unknown
275
+ properties are present, or when the underlying store rejects the write.
276
+
277
+ **See also:** `batch_create_nodes`, `update_node`
278
+
279
+
280
+ ### `deduplicate_nodes`
281
+
282
+ Find duplicate entities (same title + type) and return them grouped. `dry_run` previews; otherwise keeps one per group and redirects edges from the others.
283
+
284
+ **Atomicity:** `non-atomic. Merges are applied group-by-group; a mid-flight
285
+ error leaves earlier groups merged.`
286
+
287
+ **Arguments:**
288
+
289
+ | Name | Type | Required | Description |
290
+ | ---- | ---- | -------- | ----------- |
291
+ | `dry_run` | boolean | | Preview duplicates without merging (default true) |
292
+ | `keep` | string | | Which duplicate to keep when merging: "newest" (default) or "oldest". |
293
+ | `type` | string | | Only check this entity type. Omit to check all types. |
294
+
295
+ **Returns:**
296
+
297
+ JSON: with `dry_run: true`, `{ duplicates, total_groups,
298
+ total_duplicate_nodes, dry_run, message }`. With `dry_run: false`,
299
+ `{ merged: true, groups_merged, nodes_removed, edges_redirected,
300
+ strategy }`.
301
+
302
+ **Throws:**
303
+
304
+ - Returns a textError when `keep` is provided but is not
305
+ `"newest"` or `"oldest"`.
306
+
307
+ **Warnings (non-error surfaces):**
308
+
309
+ - Default is `dry_run: true`. Pass `dry_run: false` to commit.
310
+ Idempotent on retry: a second `dry_run: false` against an
311
+ already-deduplicated graph reports zero merges.
312
+
313
+ **See also:** `search_nodes`, `list_nodes`, `batch_delete_nodes`, `validate_graph`
314
+
315
+
316
+ ### `delete_node`
317
+
318
+ Remove one entity and all its connected edges. For 3+ entities, use `batch_delete_nodes`.
319
+
320
+ **Atomicity:** `atomic. Node + cascading edges removed in one mutation.`
321
+
322
+ **Arguments:**
323
+
324
+ | Name | Type | Required | Description |
325
+ | ---- | ---- | -------- | ----------- |
326
+ | `node_id` | string | ✓ | The node ID to delete |
327
+
328
+ **Returns:**
329
+
330
+ JSON: `{ node, removed_edge_ids }`.
331
+
332
+ **Throws:**
333
+
334
+ - Returns a textError when `node_id` is missing or the node does not
335
+ exist.
336
+
337
+ **See also:** `batch_delete_nodes`
338
+
339
+
340
+ ### `get_node`
341
+
342
+ Get a single entity by ID, with full properties and all connected edges.
343
+
344
+ **Atomicity:** `atomic (read-only)`
345
+
346
+ **Arguments:**
347
+
348
+ | Name | Type | Required | Description |
349
+ | ---- | ---- | -------- | ----------- |
350
+ | `compact_edges` | boolean | | Omit source_title/target_title from edges (saves ~30% on edge-heavy nodes) |
351
+ | `node_id` | string | ✓ | The node ID |
352
+
353
+ **Returns:**
354
+
355
+ JSON: the node object plus an `edges` array. `compact_edges: true`
356
+ omits `source_title` and `target_title` (saves ~30% on edge-heavy nodes).
357
+
358
+ **Throws:**
359
+
360
+ - Returns a textError when `node_id` is missing or the node does not
361
+ exist.
362
+
363
+ **See also:** `get_nodes`
364
+
365
+
366
+ ### `get_nodes`
367
+
368
+ Batch-fetch up to 50 entities by ID. Returns each node with its edges. Use instead of looping `get_node`.
369
+
370
+ **Atomicity:** `atomic (read-only)`
371
+
372
+ **Arguments:**
373
+
374
+ | Name | Type | Required | Description |
375
+ | ---- | ---- | -------- | ----------- |
376
+ | `compact_edges` | boolean | | Omit titles from edges |
377
+ | `ids` | array | ✓ | Array of node IDs to fetch (max 50) |
378
+
379
+ **Returns:**
380
+
381
+ JSON array of node objects with edges. Missing IDs are silently
382
+ skipped. May include a `degraded` block when the response was
383
+ auto-trimmed to fit.
384
+
385
+ **Throws:**
386
+
387
+ - Returns a textError when `ids` is missing/empty or longer than 50.
388
+
389
+ **Warnings (non-error surfaces):**
390
+
391
+ - Pre-flight payload guardrail: refuses above
392
+ `UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above
393
+ `UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). 50 edge-heavy nodes can
394
+ still cross 50 KB. Pass `compact_edges:true` to halve edge size.
395
+ - Auto-degrade: between soft and hard limits, the server
396
+ may drop edge titles, optional node fields, or truncate the result list.
397
+ Surfaced as `degraded.applied[]` on the response.
398
+
399
+ **See also:** `get_node`
400
+
401
+
402
+ ### `list_nodes`
403
+
404
+ 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.
405
+
406
+ **Atomicity:** `atomic (read-only)`
407
+
408
+ **Arguments:**
409
+
410
+ | Name | Type | Required | Description |
411
+ | ---- | ---- | -------- | ----------- |
412
+ | `count_only` | boolean | | Return only the total count, no node data |
413
+ | `if_changed_since` | string | | Hash from a previous response. Returns { changed: false } if graph unchanged. |
414
+ | `include_edges` | boolean | | Include compact edge data (id, type, source, target) per node |
415
+ | `limit` | number | | Max results (default 50, max 200) |
416
+ | `offset` | number | | Skip N results (default 0) |
417
+ | `parent_id` | string | | Filter to children of this node (connected by outgoing edge from parent) |
418
+ | `status` | string | | Filter by status value |
419
+ | `tags` | array | | Filter by tags (matches any) |
420
+ | `type` | string | | Filter by entity type |
421
+
422
+ **Returns:**
423
+
424
+ JSON: `{ nodes, total, offset, limit, _hash }`. With
425
+ `count_only: true`, returns `{ total, _hash }` only. May include a
426
+ `degraded` block when the response was auto-trimmed to fit.
427
+
428
+ **Warnings (non-error surfaces):**
429
+
430
+ - Pre-flight payload guardrail: refuses with a steering
431
+ error when the estimated response exceeds `UPG_MCP_PAYLOAD_HARD_LIMIT`
432
+ (default 150 KB), and attaches a `_warning` field above
433
+ `UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). For graph-wide reads,
434
+ prefer `query` with a tight projection.
435
+ - Auto-degrade: between the soft and hard limits, the
436
+ response is automatically truncated. Surfaced as
437
+ `degraded.applied: ['truncate_at_count_auto']` on the response.
438
+
439
+ **See also:** `search_nodes`, `query`
440
+
441
+
442
+ ### `migrate_properties`
443
+
444
+ 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.
445
+
446
+ **Atomicity:** `non-atomic. Mutations are applied node-by-node; a mid-flight
447
+ error may leave the graph partially migrated.`
448
+
449
+ **Arguments:**
450
+
451
+ | Name | Type | Required | Description |
452
+ | ---- | ---- | -------- | ----------- |
453
+ | `dry_run` | boolean | | Preview changes without applying (default true). Pass false to commit. |
454
+
455
+ **Returns:**
456
+
457
+ JSON: `{ top_level_renames, lifted_properties, dropped_props,
458
+ dropped_self_referential, dry_run }`.
459
+
460
+ **Warnings (non-error surfaces):**
461
+
462
+ - Default is `dry_run: true`. Pass `dry_run: false` to commit.
463
+ Re-running with `dry_run: true` after a successful commit reports zero
464
+ changes (idempotent on the canonical-properties shape).
465
+
466
+ **See also:** `migrate_type`, `validate_graph`, `list_type_migrations`
467
+
468
+
469
+ ### `migrate_type`
470
+
471
+ 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.
472
+
473
+ **Atomicity:** `atomic. Single store-level migration call commits or fails as
474
+ one mutation. Note: full graph canonicalisation runs as a side-effect of
475
+ any node-type migration, so unrelated legacy edges may also be retargeted.`
476
+
477
+ **Arguments:**
478
+
479
+ | Name | Type | Required | Description |
480
+ | ---- | ---- | -------- | ----------- |
481
+ | `dry_run` | boolean | | Preview changes without applying (default false) |
482
+ | `from_type` | string | ✓ | The current entity type to migrate FROM |
483
+ | `to_type` | string | ✓ | The new entity type to migrate TO |
484
+
485
+ **Returns:**
486
+
487
+ JSON: `{ migrated_nodes, migrated_edges, edge_renames,
488
+ dropped_edges, unmapped_legacy_edges, defaults_applied, dry_run }`.
489
+ `edge_renames` is `[{ id, from, to, flipped }]`; `dropped_edges` is
490
+ `[{ id, from }]`; `unmapped_legacy_edges` is `[{ type, count }]`.
491
+ `migrated_edges` is the total mutated count (renames + drops).
492
+
493
+ **Throws:**
494
+
495
+ - Returns a textError when `from_type` or `to_type` is missing.
496
+
497
+ **See also:** `rename_edge_type`, `export_edges`, `update_node`
498
+
499
+
500
+ ### `query`
501
+
502
+ 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 })
503
+
504
+ **Atomicity:** `atomic (read-only)`
505
+
506
+ **Arguments:**
507
+
508
+ | Name | Type | Required | Description |
509
+ | ---- | ---- | -------- | ----------- |
510
+ | `depth` | number | | Max traversal depth (default 3, max 10) |
511
+ | `diff_from` | string | | Result ID from a previous query. Returns only added/removed nodes since that result. |
512
+ | `edge_include` | array | | Edge fields to return: "id", "type", "source", "target". Empty array = no edges. Default: all fields. |
513
+ | `from` | string | | Start from all nodes of this type |
514
+ | `from_id` | string | | Start from a specific node ID (alternative to from) |
515
+ | `include` | array | | Fields to include per node: "title", "status", "tags", "description", "properties" (default: title, status, type) |
516
+ | `limit` | number | | Max nodes to return (default 200, max 1000) |
517
+ | `property_include` | array | | When "properties" is in include, only return these property keys (e.g. ["severity", "importance"]) |
518
+ | `traverse` | array | | Edge types to follow at each level (in order). If omitted, follows all edges. Prefix with ! to exclude (e.g. "!product_builds_feature"). |
519
+
520
+ **Returns:**
521
+
522
+ JSON: `{ nodes, edges, total_nodes, total_edges, _result_id,
523
+ truncated?, truncated_at_depth?, diff? }`. The `_result_id` is a cache
524
+ handle for `diff_from`; cache holds the last 20 results.
525
+
526
+ **Throws:**
527
+
528
+ - Returns a textError when neither `from` nor `from_id` is provided,
529
+ or when `from_id` does not exist.
530
+
531
+ **Warnings (non-error surfaces):**
532
+
533
+ - Pre-flight payload guardrail: refuses above
534
+ `UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above
535
+ `UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). Tighten with `include`
536
+ (e.g. `["title"]`) or `edge_include: []` to drop edges from the wire.
537
+
538
+ **See also:** `list_nodes`, `get_area_graph`
539
+
540
+
541
+ ### `search_nodes`
542
+
543
+ 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`.
544
+
545
+ **Atomicity:** `atomic (read-only)`
546
+
547
+ **Arguments:**
548
+
549
+ | Name | Type | Required | Description |
550
+ | ---- | ---- | -------- | ----------- |
551
+ | `fields` | array | | Fields to search: "title", "description", "tags", "properties" (default: title + description) |
552
+ | `limit` | number | | Max results (default 20, max 100) |
553
+ | `query` | string | ✓ | Search text (case-insensitive substring match) |
554
+ | `type` | string | | Optional type filter |
555
+
556
+ **Returns:**
557
+
558
+ JSON: `{ results: Array<{ id, type, title, status, tags,
559
+ match_field, score }>, total, searched_fields }`.
560
+
561
+ **Throws:**
562
+
563
+ - Returns a textError when `query` is missing.
564
+
565
+ **See also:** `list_nodes`, `query`
566
+
567
+
568
+ ### `update_node`
569
+
570
+ 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`.
571
+
572
+ **Atomicity:** `atomic-with-rollback (when `type` is changed); atomic for
573
+ shallow-merge patches.`
574
+
575
+ **Arguments:**
576
+
577
+ | Name | Type | Required | Description |
578
+ | ---- | ---- | -------- | ----------- |
579
+ | `description` | string | | |
580
+ | `node_id` | string | ✓ | The node ID to update |
581
+ | `properties` | object | | Merged with existing properties |
582
+ | `status` | string | | |
583
+ | `tags` | array | | |
584
+ | `title` | string | | |
585
+ | `type` | string | | Change the entity type. Atomic single-node migration: validates against UPG_TYPES, rewrites incident edges to canonical types. |
586
+
587
+ **Returns:**
588
+
589
+ JSON: `{ node, warning?, unknown_properties? }`. `warning`
590
+ aggregates lifecycle-status hints, migration warnings, and any
591
+ unknown-property notice. `unknown_properties` lists property
592
+ keys not in the entity's schema. Pass `strict: true` to reject unknown
593
+ properties instead of warning.
594
+
595
+ **Throws:**
596
+
597
+ - Returns a textError when `node_id` is missing, the type migration
598
+ fails, when `strict: true` and unknown properties are present, or when
599
+ the underlying store rejects the patch.
600
+
601
+ **See also:** `migrate_type`, `batch_update_nodes`
602
+
603
+
604
+ ## Edges
605
+
606
+ _Single create/delete/move plus matching atomic batches._
607
+
608
+ - [`batch_create_edges`](#batch-create-edges)
609
+ - [`batch_delete_edges`](#batch-delete-edges)
610
+ - [`batch_move_nodes`](#batch-move-nodes)
611
+ - [`create_edge`](#create-edge)
612
+ - [`delete_edge`](#delete-edge)
613
+ - [`export_edges`](#export-edges)
614
+ - [`move_node`](#move-node)
615
+ - [`rename_edge_type`](#rename-edge-type)
616
+ - [`repair_dangling_edges`](#repair-dangling-edges)
617
+
618
+ ### `batch_create_edges`
619
+
620
+ 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.
621
+
622
+ **Atomicity:** `atomic. Full validation pass before any mutation lands.`
623
+
624
+ **Arguments:**
625
+
626
+ | Name | Type | Required | Description |
627
+ | ---- | ---- | -------- | ----------- |
628
+ | `edges` | array | ✓ | Array of edges to create (max 50) |
629
+
630
+ **Returns:**
631
+
632
+ JSON: `{ created, count }`.
633
+
634
+ **Throws:**
635
+
636
+ - Returns a textError when `edges` is missing/non-array, empty,
637
+ longer than 50, or any item references a missing endpoint or unresolvable
638
+ edge type.
639
+
640
+ **See also:** `create_edge`
641
+
642
+
643
+ ### `batch_delete_edges`
644
+
645
+ Delete up to 50 edges in one atomic call (all succeed or all fail).
646
+
647
+ **Atomicity:** `atomic. Validation pass rejects the batch before any mutation
648
+ lands.`
649
+
650
+ **Arguments:**
651
+
652
+ | Name | Type | Required | Description |
653
+ | ---- | ---- | -------- | ----------- |
654
+ | `edge_ids` | array | ✓ | Array of edge IDs to delete (max 50) |
655
+
656
+ **Returns:**
657
+
658
+ JSON: `{ deleted, count }`.
659
+
660
+ **Throws:**
661
+
662
+ - Returns a textError when `edge_ids` is missing/non-array, empty,
663
+ longer than 50, or any ID does not resolve.
664
+
665
+ **See also:** `delete_edge`
666
+
667
+
668
+ ### `batch_move_nodes`
669
+
670
+ Apply up to 50 atomic re-parents. All moves validate against the schema first; any failure rolls back the whole batch.
671
+
672
+ **Atomicity:** `atomic-with-rollback.`
673
+
674
+ **Arguments:**
675
+
676
+ | Name | Type | Required | Description |
677
+ | ---- | ---- | -------- | ----------- |
678
+ | `moves` | array | ✓ | |
679
+
680
+ **Returns:**
681
+
682
+ JSON: `{ moves, warnings? }` mirroring the per-move result of
683
+ `move_node`.
684
+
685
+ **Throws:**
686
+
687
+ - Returns a textError when `moves` is missing/non-array or any move
688
+ fails validation.
689
+
690
+ **See also:** `move_node`
691
+
692
+
693
+ ### `create_edge`
694
+
695
+ 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`.
696
+
697
+ **Atomicity:** `atomic. Single store mutation.`
698
+
699
+ **Arguments:**
700
+
701
+ | Name | Type | Required | Description |
702
+ | ---- | ---- | -------- | ----------- |
703
+ | `source_id` | string | ✓ | Source node ID |
704
+ | `target_id` | string | | Target node ID |
705
+ | `target_title` | string | | Target node title (alternative to target_id; requires target_type). |
706
+ | `target_type` | string | | Target node type (used with target_title for resolution) |
707
+ | `type` | string | | Edge type. Auto-inferred if omitted. |
708
+
709
+ **Returns:**
710
+
711
+ JSON: the created edge object plus optional resolution metadata.
712
+
713
+ **Throws:**
714
+
715
+ - Returns a textError when `source_id` is missing, the target cannot
716
+ be resolved, or the edge violates the catalog.
717
+
718
+ **Examples:**
719
+
720
+ // Wire a persona to a job using the canonical edge type persona_pursues_job
721
+ // Input:
722
+ { "source_id": "persona_01", "target_id": "job_03", "type": "persona_pursues_job" }
723
+ // Output (truncated):
724
+ {
725
+ "edge": { "id": "edge_15", "type": "persona_pursues_job", "source": "persona_01", "target": "job_03" },
726
+ "inferred": false
727
+ }
728
+
729
+ **See also:** `batch_create_edges`, `resolve_edge_for_pair`, `list_edge_types`, `get_edge_type`
730
+
731
+
732
+ ### `delete_edge`
733
+
734
+ Remove one edge by ID.
735
+
736
+ **Atomicity:** `atomic.`
737
+
738
+ **Arguments:**
739
+
740
+ | Name | Type | Required | Description |
741
+ | ---- | ---- | -------- | ----------- |
742
+ | `edge_id` | string | ✓ | The edge ID to delete |
743
+
744
+ **Returns:**
745
+
746
+ JSON: the removed edge object.
747
+
748
+ **Throws:**
749
+
750
+ - Returns a textError when `edge_id` is missing or does not resolve.
751
+
752
+ **See also:** `batch_delete_edges`, `export_edges`, `repair_dangling_edges`
753
+
754
+
755
+ ### `export_edges`
756
+
757
+ 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.
758
+
759
+ **Atomicity:** `atomic (read-only)`
760
+
761
+ **Arguments:**
762
+
763
+ | Name | Type | Required | Description |
764
+ | ---- | ---- | -------- | ----------- |
765
+ | `if_changed_since` | string | | Hash from a previous response. Returns { changed: false } if graph unchanged. |
766
+ | `limit` | number | | Max results (default 500, max 2000) |
767
+ | `offset` | number | | Skip N results (default 0) |
768
+ | `types` | array | | Filter by exact edge-type match. Omit to enumerate every edge in the document. |
769
+
770
+ **Returns:**
771
+
772
+ JSON: `{ edges, total, offset, limit, types?, _hash }`. Each edge
773
+ carries `{ id, source, target, type, mapping_confidence? }`.
774
+
775
+ **Throws:**
776
+
777
+ - Returns a textError when `types` is supplied but is not an array of
778
+ strings, or when the page would exceed the hard payload limit.
779
+
780
+ **See also:** `query`, `list_nodes`
781
+
782
+
783
+ ### `move_node`
784
+
785
+ 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.
786
+
787
+ **Atomicity:** `atomic-with-rollback. Pre-validates the new edge before
788
+ touching the old one.`
789
+
790
+ **Arguments:**
791
+
792
+ | Name | Type | Required | Description |
793
+ | ---- | ---- | -------- | ----------- |
794
+ | `new_edge_type` | string | | Optional override. Must be a key in UPG_EDGE_CATALOG. If omitted, the edge type is inferred from new_parent.type → node.type. |
795
+ | `new_parent_id` | string | ✓ | The new parent node id |
796
+ | `node_id` | string | ✓ | The node to re-parent |
797
+ | `old_edge_id` | string | | Required when the node has more than one hierarchy edge. Picks which one to delete. |
798
+
799
+ **Returns:**
800
+
801
+ JSON: `{ moved: true, node_id, new_parent_id, new_edge,
802
+ old_edge_id?, warning? }`. The internal `removed_edge` field is stripped
803
+ from the wire payload.
804
+
805
+ **Throws:**
806
+
807
+ - Returns a textError when `node_id` or `new_parent_id` is missing,
808
+ when the inferred edge type is invalid, or when the node has multiple
809
+ hierarchy edges and `old_edge_id` was not supplied.
810
+
811
+ **See also:** `batch_move_nodes`
812
+
813
+
814
+ ### `rename_edge_type`
815
+
816
+ 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.
817
+
818
+ **Atomicity:** `atomic. Single-pass mutation; an empty match-set is a clean
819
+ no-op rather than an error.`
820
+
821
+ **Arguments:**
822
+
823
+ | Name | Type | Required | Description |
824
+ | ---- | ---- | -------- | ----------- |
825
+ | `dry_run` | boolean | | Preview without mutating (default true) |
826
+ | `flip` | boolean | | When true, swap source/target on each renamed edge (default false) |
827
+ | `from` | string | ✓ | Current edge type (exact match) |
828
+ | `to` | string | ✓ | New edge type to assign |
829
+
830
+ **Returns:**
831
+
832
+ JSON: with `dry_run: true`, `{ dry_run, from, to, flip, would_rename, sample }`.
833
+ With `dry_run: false`, `{ dry_run, from, to, flip, renamed, ids }`.
834
+
835
+ **Throws:**
836
+
837
+ - Returns a textError when `from` or `to` is missing, when they are
838
+ equal and `flip` is false (no-op), or when `from === to` with `flip: true`
839
+ on zero matches (still safe but the call is degenerate).
840
+
841
+ **See also:** `migrate_type`, `export_edges`
842
+
843
+
844
+ ### `repair_dangling_edges`
845
+
846
+ 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.
847
+
848
+ **Atomicity:** `atomic-with-rollback. Classification runs against the live
849
+ document before any mutation; with `dry_run: false`, the drop set is
850
+ computed up-front and applied in a single index rebuild.`
851
+
852
+ **Arguments:**
853
+
854
+ | Name | Type | Required | Description |
855
+ | ---- | ---- | -------- | ----------- |
856
+ | `drop` | array | | Classes of dangling edge to drop. Only honoured when dry_run is false. Omit to no-op. |
857
+ | `dry_run` | boolean | | When true (default), returns the classification report without mutating. When false, drops edges matching `drop`. |
858
+
859
+ **Returns:**
860
+
861
+ JSON: `{ dry_run, report, dropped?, remaining? }`. `report` is
862
+ the pre-action classification. With `dry_run: false`, `dropped` is the
863
+ count of edges removed and `remaining` is the post-action report.
864
+
865
+ **Throws:**
866
+
867
+ - Returns a textError when `drop` is provided alongside
868
+ `dry_run: true` (ambiguous), or when `drop` includes an unknown class.
869
+
870
+ **Warnings (non-error surfaces):**
871
+
872
+ - Dropping `corrupt` edges is irreversible. The integrity stamp is
873
+ re-computed on next save; a subsequent reload won't bring them back.
874
+
875
+
876
+ ## Areas & Change Log
877
+
878
+ _Product areas, the `.upg-area.json` cwd scoper, and the session change log._
879
+
880
+ - [`create_area`](#create-area)
881
+ - [`get_area_context`](#get-area-context)
882
+ - [`get_area_graph`](#get-area-graph)
883
+ - [`get_changes`](#get-changes)
884
+ - [`list_product_areas`](#list-product-areas)
885
+
886
+ ### `create_area`
887
+
888
+ 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.
889
+
890
+ **Atomicity:** `atomic per write — the portfolio file is read, mutated, and
891
+ flushed in one pass.`
892
+
893
+ **Arguments:**
894
+
895
+ | Name | Type | Required | Description |
896
+ | ---- | ---- | -------- | ----------- |
897
+ | `description` | string | | What this area covers |
898
+ | `owner` | string | | Person or team that owns this area |
899
+ | `parent_area_id` | string | | Parent area ID for creating a sub-area |
900
+ | `strategic_priority` | `critical` \| `high` \| `medium` \| `low` | | Strategic priority of this area |
901
+ | `title` | string | ✓ | Area name (e.g. "Search", "Payments") |
902
+
903
+ **Returns:**
904
+
905
+ JSON: `{ node, portfolio_file, written_to }`. `node` is the typed
906
+ `UPGProductArea` record persisted to `portfolio_areas[]`.
907
+
908
+ **Throws:**
909
+
910
+ - Returns a textError when `title` is missing or the portfolio write
911
+ fails.
912
+
913
+ **See also:** `list_product_areas`
914
+
915
+
916
+ ### `get_area_context`
917
+
918
+ Check whether the current working directory has a `.upg-area.json` that scopes work to a specific product area.
919
+
920
+ **Atomicity:** `atomic (read-only)`
921
+
922
+ _No arguments._
923
+
924
+ **Returns:**
925
+
926
+ JSON: `{ has_area_context: false }` or
927
+ `{ has_area_context: true, area_id, area_name, found_at }`.
928
+
929
+
930
+ ### `get_area_graph`
931
+
932
+ Return the sub-graph (entities and edges) scoped to a product area.
933
+
934
+ **Atomicity:** `atomic (read-only)`
935
+
936
+ **Arguments:**
937
+
938
+ | Name | Type | Required | Description |
939
+ | ---- | ---- | -------- | ----------- |
940
+ | `area_id` | string | ✓ | The product area node ID |
941
+ | `depth` | number | | How many levels deep to traverse (default 3, max 10) |
942
+
943
+ **Returns:**
944
+
945
+ JSON: `{ area, nodes, edges, node_count, edge_count }`. May
946
+ include a `degraded` block when the response was auto-trimmed.
947
+
948
+ **Throws:**
949
+
950
+ - Returns a textError when `area_id` is missing, the node does not
951
+ exist, or the node is not a `product_area`.
952
+
953
+ **Warnings (non-error surfaces):**
954
+
955
+ - Pre-flight payload guardrail: refuses above
956
+ `UPG_MCP_PAYLOAD_HARD_LIMIT` (default 150 KB), warns above
957
+ `UPG_MCP_PAYLOAD_SOFT_LIMIT` (default 50 KB). Reduce `depth` or use
958
+ `query` with a tight projection if the area has many neighbours.
959
+ - Auto-degrade: between soft and hard, the server may
960
+ compact edges, drop optional node fields, or truncate. Surfaced as
961
+ `degraded.applied[]` on the response.
962
+
963
+ **See also:** `list_product_areas`
964
+
965
+
966
+ ### `get_changes`
967
+
968
+ Mutation log for this session. Verify what was created, updated, or deleted without re-fetching.
969
+
970
+ **Atomicity:** `atomic (read-only)`
971
+
972
+ **Arguments:**
973
+
974
+ | Name | Type | Required | Description |
975
+ | ---- | ---- | -------- | ----------- |
976
+ | `since` | string | | ISO 8601 timestamp. Only returns changes after this time (default: all session changes). |
977
+
978
+ **Returns:**
979
+
980
+ JSON: `{ changes, summary: { create, update, delete }, total }`.
981
+ `since` filters to ISO 8601 timestamps after the cutoff.
982
+
983
+
984
+ ### `list_product_areas`
985
+
986
+ List product areas from the portfolio document (`.upg/portfolio.upg`). Returns an empty list when no portfolio document exists yet.
987
+
988
+ **Atomicity:** `atomic (read-only)`
989
+
990
+ _No arguments._
991
+
992
+ **Returns:**
993
+
994
+ JSON: `{ areas: Array<{ id, title, strategic_priority?,
995
+ parent_area_id?, products? }>, total }`.
996
+
997
+ **See also:** `create_area`, `get_area_graph`
998
+
999
+
1000
+ ## Workspace & Portfolios
1001
+
1002
+ _Multi-product discovery, switching, init, cross-product edges._
1003
+
1004
+ - [`create_cross_product_edge`](#create-cross-product-edge)
1005
+ - [`create_product`](#create-product)
1006
+ - [`get_organization`](#get-organization)
1007
+ - [`get_workspace_info`](#get-workspace-info)
1008
+ - [`init_workspace`](#init-workspace)
1009
+ - [`list_local_products`](#list-local-products)
1010
+ - [`list_portfolio_cross_edges`](#list-portfolio-cross-edges)
1011
+ - [`list_portfolios`](#list-portfolios)
1012
+ - [`migrate_cross_edges`](#migrate-cross-edges)
1013
+ - [`switch_product`](#switch-product)
1014
+
1015
+ ### `create_cross_product_edge`
1016
+
1017
+ 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`.
1018
+
1019
+ **Atomicity:** `non-atomic. Portfolio file create (if new) + edge append are
1020
+ separate filesystem operations.`
1021
+
1022
+ **Arguments:**
1023
+
1024
+ | Name | Type | Required | Description |
1025
+ | ---- | ---- | -------- | ----------- |
1026
+ | `source_id` | string | ✓ | Source node ID |
1027
+ | `source_product_id` | string | | Product ID of the source node |
1028
+ | `target_id` | string | ✓ | Target node ID |
1029
+ | `target_product_id` | string | | Product ID of the target node |
1030
+ | `type` | `shares_persona` \| `shares_competitor` \| `shares_metric` \| `depends_on_product` \| `cannibalises` \| `succeeds` | ✓ | Cross-product relationship type |
1031
+
1032
+ **Returns:**
1033
+
1034
+ JSON: `{ edge, portfolio_file }`.
1035
+
1036
+ **Throws:**
1037
+
1038
+ - Returns a textError when parameters are missing or invalid, or
1039
+ when the workspace is not initialised.
1040
+
1041
+ **See also:** `list_portfolios`, `list_portfolio_cross_edges`, `migrate_cross_edges`
1042
+
1043
+
1044
+ ### `create_product`
1045
+
1046
+ 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`.
1047
+
1048
+ **Atomicity:** `non-atomic. File write + workspace.json patch + optional
1049
+ portfolio edge are separate mutations.`
1050
+
1051
+ **Arguments:**
1052
+
1053
+ | Name | Type | Required | Description |
1054
+ | ---- | ---- | -------- | ----------- |
1055
+ | `description` | string | | Optional product description |
1056
+ | `name` | string | ✓ | Product display title (required, non-empty). |
1057
+ | `portfolio_id` | string | | Optional portfolio node id in the current store. When provided, a `portfolio_contains_product` edge is created in the current graph. |
1058
+ | `slug` | string | | Optional slug for the .upg filename. Defaults to a slug derived from `name`. Collisions append `-2`, `-3`, … |
1059
+ | `stage` | string | | Product lifecycle stage. See UPGProductStage in @unified-product-graph/core. |
1060
+
1061
+ **Returns:**
1062
+
1063
+ JSON: `{ message, ...result }`. `result` carries `id`, `title`,
1064
+ `slug`, `file_path`, and the optional portfolio edge.
1065
+
1066
+ **Throws:**
1067
+
1068
+ - Returns a textError when the workspace is uninitialised
1069
+ (`WorkspaceNotInitialisedError`) or the name is invalid
1070
+ (`InvalidProductNameError`).
1071
+
1072
+ **See also:** `init_workspace`
1073
+
1074
+
1075
+ ### `get_organization`
1076
+
1077
+ 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.
1078
+
1079
+ **Atomicity:** `atomic (read-only)`
1080
+
1081
+ _No arguments._
1082
+
1083
+ **Returns:**
1084
+
1085
+ JSON: `{ organization: UPGOrganization | null, portfolio_file? }`.
1086
+ Returns `{ organization: null }` when no portfolio document exists yet.
1087
+
1088
+ **See also:** `list_portfolios`
1089
+
1090
+
1091
+ ### `get_workspace_info`
1092
+
1093
+ Workspace info: which product is loaded, what other products are available, current workspace mode.
1094
+
1095
+ **Atomicity:** `atomic (read-only)`
1096
+
1097
+ _No arguments._
1098
+
1099
+ **Returns:**
1100
+
1101
+ JSON: `{ mode, workspace_path?, current_product?, current_file?,
1102
+ products }`. The shape depends on whether `.upg/workspace.json` exists.
1103
+
1104
+ **See also:** `init_workspace`
1105
+
1106
+
1107
+ ### `init_workspace`
1108
+
1109
+ Initialise a UPG workspace. Creates `.upg/` and moves the current .upg file into it. Unlocks multi-product management.
1110
+
1111
+ **Atomicity:** `non-atomic. The operation creates a directory and (optionally)
1112
+ moves a file as separate filesystem mutations.`
1113
+
1114
+ **Arguments:**
1115
+
1116
+ | Name | Type | Required | Description |
1117
+ | ---- | ---- | -------- | ----------- |
1118
+ | `move_existing` | boolean | | Move existing .upg files into the workspace (default true) |
1119
+
1120
+ **Returns:**
1121
+
1122
+ JSON: `{ message, ...result }`. `result` carries the workspace
1123
+ path and the moved file's new location.
1124
+
1125
+ **Throws:**
1126
+
1127
+ - Returns a textError when the workspace already exists
1128
+ (`WorkspaceAlreadyExistsError`) or another filesystem error occurs.
1129
+
1130
+ **Warnings (non-error surfaces):**
1131
+
1132
+ - One-time setup operation. Idempotent failure on retry: if the
1133
+ workspace already exists, raises `WorkspaceAlreadyExistsError`. Pair
1134
+ with `get_workspace_info` to check state before re-running.
1135
+
1136
+ **See also:** `create_product`, `switch_product`, `get_workspace_info`
1137
+
1138
+
1139
+ ### `list_local_products`
1140
+
1141
+ Find every .upg file in the current directory and its immediate subdirectories.
1142
+
1143
+ **Atomicity:** `atomic (read-only)`
1144
+
1145
+ _No arguments._
1146
+
1147
+ **Returns:**
1148
+
1149
+ JSON: `{ products: Array<{ file, title, stage, nodes, edges }> }`.
1150
+
1151
+ **See also:** `switch_product`, `get_workspace_info`
1152
+
1153
+
1154
+ ### `list_portfolio_cross_edges`
1155
+
1156
+ List all cross-product edges stored in the portfolio document (`.upg/portfolio.upg`). Empty list when the portfolio document is absent.
1157
+
1158
+ **Atomicity:** `atomic (read-only)`
1159
+
1160
+ _No arguments._
1161
+
1162
+ **Returns:**
1163
+
1164
+ JSON: `{ cross_edges: UPGCrossEdge[], total, portfolio_file? }`.
1165
+
1166
+ **See also:** `create_cross_product_edge`
1167
+
1168
+
1169
+ ### `list_portfolios`
1170
+
1171
+ 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.
1172
+
1173
+ **Atomicity:** `atomic (read-only)`
1174
+
1175
+ _No arguments._
1176
+
1177
+ **Returns:**
1178
+
1179
+ JSON: `{ portfolios: Array<{ id, title, description?,
1180
+ parent_portfolio_id?, hierarchy_model?, products? }>, total }`.
1181
+
1182
+ **See also:** `create_cross_product_edge`, `get_organization`
1183
+
1184
+
1185
+ ### `migrate_cross_edges`
1186
+
1187
+ 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.
1188
+
1189
+ **Atomicity:** `non-atomic. Portfolio write + product file save are separate.`
1190
+
1191
+ **Arguments:**
1192
+
1193
+ | Name | Type | Required | Description |
1194
+ | ---- | ---- | -------- | ----------- |
1195
+ | `dry_run` | boolean | | When true (default), report what would be migrated without writing anything. |
1196
+ | `source_product_id` | string | ✓ | Product ID that owns the current document's nodes. Used to build qualified source IDs ({product_id}/{node_id}). |
1197
+ | `target_product_id` | string | | 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. |
1198
+
1199
+ **Returns:**
1200
+
1201
+ JSON: `{ migrated, skipped, dry_run, portfolio_file? }`.
1202
+
1203
+ **Throws:**
1204
+
1205
+ - Returns a textError when `source_product_id` is missing or when the
1206
+ workspace is not initialised (in non-dry-run mode).
1207
+
1208
+ **Warnings (non-error surfaces):**
1209
+
1210
+ - Default is `dry_run: true`. Pass `dry_run: false` to commit. Idempotent
1211
+ on retry: a second `dry_run: false` after a successful migration finds zero
1212
+ inline cross-edges and reports `migrated: []`.
1213
+
1214
+ **See also:** `create_cross_product_edge`, `list_portfolio_cross_edges`, `list_cross_edge_types`, `init_workspace`
1215
+
1216
+
1217
+ ### `switch_product`
1218
+
1219
+ 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").
1220
+
1221
+ **Atomicity:** `non-atomic. Flushes the current store, stops watching, and
1222
+ loads the new file as separate filesystem operations.`
1223
+
1224
+ **Arguments:**
1225
+
1226
+ | Name | Type | Required | Description |
1227
+ | ---- | ---- | -------- | ----------- |
1228
+ | `file` | string | ✓ | Path to the .upg file (relative, absolute, or a bare product name in workspace mode). |
1229
+
1230
+ **Returns:**
1231
+
1232
+ JSON: `{ message, file, product: { title, stage }, entities }`.
1233
+
1234
+ **Throws:**
1235
+
1236
+ - Returns a textError when the file cannot be resolved or the load
1237
+ fails (file watcher / parse error).
1238
+
1239
+ **Warnings (non-error surfaces):**
1240
+
1241
+ - Mutates server-side workspace state. After an MCP reconnect the
1242
+ server reverts to the workspace default. Call `get_workspace_info`
1243
+ before any read/mutation to confirm the active product.
1244
+
1245
+ **See also:** `get_workspace_info`, `list_local_products`, `init_workspace`
1246
+
1247
+
1248
+ ## Schema
1249
+
1250
+ _Entity schema introspection. Same constraints the LSP enforces._
1251
+
1252
+ - [`get_entity_schema`](#get-entity-schema)
1253
+
1254
+ ### `get_entity_schema`
1255
+
1256
+ Return expected properties, valid statuses, valid edge types, and domain for an entity type. Lets agents construct valid entities without skill prompts.
1257
+
1258
+ **Atomicity:** `atomic (read-only)`
1259
+
1260
+ **Arguments:**
1261
+
1262
+ | Name | Type | Required | Description |
1263
+ | ---- | ---- | -------- | ----------- |
1264
+ | `type` | string | ✓ | Entity type (e.g. "hypothesis", "persona", "opportunity") |
1265
+
1266
+ **Returns:**
1267
+
1268
+ JSON: `{ type, alias_of?, domain, expected_properties, edges_out,
1269
+ edges_in, phases?, initial_phase?, terminal_phases?, domain_guide? }`.
1270
+
1271
+ **Throws:**
1272
+
1273
+ - Returns a textError when `type` is missing or unknown.
1274
+
1275
+ **See also:** `get_entity_meta`, `list_entity_types`, `get_valid_children`, `get_lifecycle`, `get_domain_guide`, `list_edge_types`, `create_node`
1276
+
1277
+
1278
+ ## Spec Introspection
1279
+
1280
+ _Canonical playbooks, approaches, domain guides, frameworks, edge catalogue, regions, lenses, type labels, hierarchy, version, cross-edges, entity meta, anti-patterns, benchmarks, bare-verb approach handlers, migrations, lifecycles, scales, framework categories/patterns, and domain rings. All from `@unified-product-graph/core`._
1281
+
1282
+ - [`get_anti_pattern`](#get-anti-pattern)
1283
+ - [`get_approach`](#get-approach)
1284
+ - [`get_domain_guide`](#get-domain-guide)
1285
+ - [`get_domain_ring`](#get-domain-ring)
1286
+ - [`get_edge_type`](#get-edge-type)
1287
+ - [`get_entity_meta`](#get-entity-meta)
1288
+ - [`get_framework`](#get-framework)
1289
+ - [`get_lens`](#get-lens)
1290
+ - [`get_lifecycle`](#get-lifecycle)
1291
+ - [`get_playbook`](#get-playbook)
1292
+ - [`get_region`](#get-region)
1293
+ - [`get_region_for_entity_type`](#get-region-for-entity-type)
1294
+ - [`get_scale`](#get-scale)
1295
+ - [`get_spec_version`](#get-spec-version)
1296
+ - [`get_type_label`](#get-type-label)
1297
+ - [`get_valid_children`](#get-valid-children)
1298
+ - [`inspect`](#inspect)
1299
+ - [`list_anti_patterns`](#list-anti-patterns)
1300
+ - [`list_approaches`](#list-approaches)
1301
+ - [`list_benchmarks`](#list-benchmarks)
1302
+ - [`list_cross_edge_types`](#list-cross-edge-types)
1303
+ - [`list_domain_rings`](#list-domain-rings)
1304
+ - [`list_domains`](#list-domains)
1305
+ - [`list_edge_migrations`](#list-edge-migrations)
1306
+ - [`list_edge_types`](#list-edge-types)
1307
+ - [`list_entity_types`](#list-entity-types)
1308
+ - [`list_framework_categories`](#list-framework-categories)
1309
+ - [`list_framework_structure_patterns`](#list-framework-structure-patterns)
1310
+ - [`list_frameworks`](#list-frameworks)
1311
+ - [`list_lenses`](#list-lenses)
1312
+ - [`list_lifecycles`](#list-lifecycles)
1313
+ - [`list_playbooks`](#list-playbooks)
1314
+ - [`list_product_stages`](#list-product-stages)
1315
+ - [`list_regions`](#list-regions)
1316
+ - [`list_scales`](#list-scales)
1317
+ - [`list_split_migrations`](#list-split-migrations)
1318
+ - [`list_type_labels`](#list-type-labels)
1319
+ - [`list_type_migrations`](#list-type-migrations)
1320
+ - [`plan`](#plan)
1321
+ - [`prioritise`](#prioritise)
1322
+ - [`reflect`](#reflect)
1323
+ - [`resolve_edge_for_pair`](#resolve-edge-for-pair)
1324
+ - [`trace`](#trace)
1325
+
1326
+ ### `get_anti_pattern`
1327
+
1328
+ 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.
1329
+
1330
+ **Atomicity:** `atomic (read-only)`
1331
+
1332
+ **Arguments:**
1333
+
1334
+ | Name | Type | Required | Description |
1335
+ | ---- | ---- | -------- | ----------- |
1336
+ | `id` | string | ✓ | Anti-pattern id (kebab-case slug). |
1337
+
1338
+ **Returns:**
1339
+
1340
+ JSON: `UPGCuratedAntiPattern`
1341
+
1342
+ **Throws:**
1343
+
1344
+ - textError when `id` is missing or unknown.
1345
+
1346
+ **See also:** `list_anti_patterns`, `get_anti_pattern_violations_for`, `inspect`, `validate_graph`
1347
+
1348
+
1349
+ ### `get_approach`
1350
+
1351
+ Return one `UPGApproach` by id. Valid ids: `plan`, `inspect`, `prioritise`, `trace`, `reflect` (same names as the verb-led MCP tools).
1352
+
1353
+ **Atomicity:** `atomic (read-only)`
1354
+
1355
+ **Arguments:**
1356
+
1357
+ | Name | Type | Required | Description |
1358
+ | ---- | ---- | -------- | ----------- |
1359
+ | `id` | `plan` \| `inspect` \| `prioritise` \| `trace` \| `reflect` | ✓ | Approach id. One of: plan, inspect, prioritise, trace, reflect. |
1360
+
1361
+ **Returns:**
1362
+
1363
+ JSON: the full `UPGApproach` record.
1364
+
1365
+ **Throws:**
1366
+
1367
+ - textError when `id` is missing or unknown.
1368
+
1369
+ **See also:** `list_approaches`, `plan`, `inspect`, `prioritise`, `trace`, `reflect`
1370
+
1371
+
1372
+ ### `get_domain_guide`
1373
+
1374
+ Return the full `UPGDomainUsageGuide` for a domain: anchor entity, creation sequence, named patterns (entity + edge chains), required cross-domain bridges, anti-patterns.
1375
+
1376
+ **Atomicity:** `atomic (read-only)`
1377
+
1378
+ **Arguments:**
1379
+
1380
+ | Name | Type | Required | Description |
1381
+ | ---- | ---- | -------- | ----------- |
1382
+ | `domain_id` | string | ✓ | Canonical domain id (e.g. "user", "market_intelligence", "growth"). |
1383
+
1384
+ **Returns:**
1385
+
1386
+ JSON: the full `UPGDomainUsageGuide` record.
1387
+
1388
+ **Throws:**
1389
+
1390
+ - textError when `domain_id` is missing or unknown.
1391
+
1392
+ **See also:** `list_domains`, `get_domain_ring`, `list_anti_patterns`, `get_playbook`
1393
+
1394
+
1395
+ ### `get_domain_ring`
1396
+
1397
+ 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.
1398
+
1399
+ **Atomicity:** `atomic (read-only)`
1400
+
1401
+ **Arguments:**
1402
+
1403
+ | Name | Type | Required | Description |
1404
+ | ---- | ---- | -------- | ----------- |
1405
+ | `id` | string | ✓ | Ring id. One of: nucleus, understand, define, build, grow, operate, extend. |
1406
+
1407
+ **Returns:**
1408
+
1409
+ JSON: the full `UPGDomainRing` record.
1410
+
1411
+ **See also:** `list_domain_rings`, `list_domains`, `get_domain_guide`
1412
+
1413
+
1414
+ ### `get_edge_type`
1415
+
1416
+ Return one edge catalogue entry by edge type key (e.g. "persona_pursues_job", "feature_addresses_need").
1417
+
1418
+ **Atomicity:** `atomic (read-only)`
1419
+
1420
+ **Arguments:**
1421
+
1422
+ | Name | Type | Required | Description |
1423
+ | ---- | ---- | -------- | ----------- |
1424
+ | `type` | string | ✓ | Edge type key from UPG_EDGE_CATALOG. |
1425
+
1426
+ **Returns:**
1427
+
1428
+ JSON: `{ type, forward_verb, reverse_verb, classification, source_type, target_type }`
1429
+
1430
+ **Throws:**
1431
+
1432
+ - textError when `type` is missing or unknown.
1433
+
1434
+ **See also:** `list_edge_types`, `resolve_edge_for_pair`, `list_edge_migrations`, `rename_edge_type`
1435
+
1436
+
1437
+ ### `get_entity_meta`
1438
+
1439
+ 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`.
1440
+
1441
+ **Atomicity:** `atomic (read-only)`
1442
+
1443
+ **Arguments:**
1444
+
1445
+ | Name | Type | Required | Description |
1446
+ | ---- | ---- | -------- | ----------- |
1447
+ | `name` | string | ✓ | Canonical entity type name. |
1448
+
1449
+ **Returns:**
1450
+
1451
+ JSON: `EntityTypeMeta & { domain_id: string | null }`
1452
+
1453
+ **Throws:**
1454
+
1455
+ - textError when `name` is missing or unknown.
1456
+
1457
+ **See also:** `list_entity_types`, `get_type_label`, `get_entity_schema`, `list_type_migrations`
1458
+
1459
+
1460
+ ### `get_framework`
1461
+
1462
+ Return one `UPGFramework` by id (e.g. "rice-scoring", "lean-canvas"). Includes all four layers: data, structure, presentation, education.
1463
+
1464
+ **Atomicity:** `atomic (read-only)`
1465
+
1466
+ **Arguments:**
1467
+
1468
+ | Name | Type | Required | Description |
1469
+ | ---- | ---- | -------- | ----------- |
1470
+ | `id` | string | ✓ | Framework id (kebab-case). |
1471
+
1472
+ **Returns:**
1473
+
1474
+ JSON: the full `UPGFramework` record.
1475
+
1476
+ **Throws:**
1477
+
1478
+ - textError when `id` is missing or unknown.
1479
+
1480
+ **See also:** `list_frameworks`, `prioritise`, `get_playbook`, `get_approach`
1481
+
1482
+
1483
+ ### `get_lens`
1484
+
1485
+ 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.
1486
+
1487
+ **Atomicity:** `atomic (read-only)`
1488
+
1489
+ **Arguments:**
1490
+
1491
+ | Name | Type | Required | Description |
1492
+ | ---- | ---- | -------- | ----------- |
1493
+ | `id` | string | ✓ | Lens id (e.g. "product", "ux_design", "full"). |
1494
+
1495
+ **Returns:**
1496
+
1497
+ JSON: `{ ...UPGLens, visible_types: string[] }`
1498
+
1499
+ **Throws:**
1500
+
1501
+ - textError when `id` is missing or unknown.
1502
+
1503
+ **See also:** `list_lenses`, `get_playbook`, `get_framework`, `list_entity_types`
1504
+
1505
+
1506
+ ### `get_lifecycle`
1507
+
1508
+ 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.
1509
+
1510
+ **Atomicity:** `atomic (read-only)`
1511
+
1512
+ **Arguments:**
1513
+
1514
+ | Name | Type | Required | Description |
1515
+ | ---- | ---- | -------- | ----------- |
1516
+ | `entity_type` | string | ✓ | Canonical entity type name (e.g. "feature", "hypothesis", "opportunity"). |
1517
+
1518
+ **Returns:**
1519
+
1520
+ JSON: the full `UPGLifecycle` record, or a descriptive message.
1521
+
1522
+ **See also:** `list_lifecycles`, `get_entity_meta`, `get_entity_schema`
1523
+
1524
+
1525
+ ### `get_playbook`
1526
+
1527
+ 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`.
1528
+
1529
+ **Atomicity:** `atomic (read-only)`
1530
+
1531
+ **Arguments:**
1532
+
1533
+ | Name | Type | Required | Description |
1534
+ | ---- | ---- | -------- | ----------- |
1535
+ | `id` | string | ✓ | Playbook id (namespace-prefixed: playbook:*). |
1536
+
1537
+ **Returns:**
1538
+
1539
+ JSON: the full `UPGPlaybook` record.
1540
+
1541
+ **Throws:**
1542
+
1543
+ - textError when `id` is missing or unknown.
1544
+
1545
+ **Examples:**
1546
+
1547
+ // Fetch the full Users & Needs playbook to guide entity creation
1548
+ // Input:
1549
+ { "id": "playbook:users-needs" }
1550
+ // Output (truncated):
1551
+ {
1552
+ "id": "playbook:users-needs",
1553
+ "name": "Users & Needs",
1554
+ "region": "users_needs",
1555
+ "is_canonical": true,
1556
+ "target_anchor_entity": "persona",
1557
+ "creation_sequence": ["persona", "job", "need", "pain_point"],
1558
+ "steps": [
1559
+ { "kind": "create", "type": "persona", "prompt": "Who are the primary users?" },
1560
+ { "kind": "create", "type": "job", "prompt": "What jobs do they need to accomplish?" }
1561
+ ]
1562
+ }
1563
+
1564
+ **See also:** `list_playbooks`, `get_approach`, `get_framework`, `get_region`
1565
+
1566
+
1567
+ ### `get_region`
1568
+
1569
+ 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.
1570
+
1571
+ **Atomicity:** `atomic (read-only)`
1572
+
1573
+ **Arguments:**
1574
+
1575
+ | Name | Type | Required | Description |
1576
+ | ---- | ---- | -------- | ----------- |
1577
+ | `id` | string | ✓ | Region id (e.g. "strategy_outcomes", "users_needs", "product_delivery"). See UPG_REGIONS for the full list of 10. |
1578
+
1579
+ **Returns:**
1580
+
1581
+ JSON: the full `UPGRegion` record.
1582
+
1583
+ **Throws:**
1584
+
1585
+ - textError when `id` is missing or unknown.
1586
+
1587
+ **See also:** `list_regions`, `get_region_for_entity_type`, `get_playbook`, `list_lenses`
1588
+
1589
+
1590
+ ### `get_region_for_entity_type`
1591
+
1592
+ 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.
1593
+
1594
+ **Atomicity:** `atomic (read-only)`
1595
+
1596
+ **Arguments:**
1597
+
1598
+ | Name | Type | Required | Description |
1599
+ | ---- | ---- | -------- | ----------- |
1600
+ | `entity_type` | string | ✓ | Canonical entity type (e.g. "persona", "feature", "metric"). |
1601
+
1602
+ **Returns:**
1603
+
1604
+ JSON: the full `UPGRegion` record.
1605
+
1606
+ **Throws:**
1607
+
1608
+ - textError when `entity_type` is missing or no region contains it.
1609
+
1610
+ **See also:** `get_region`, `list_regions`, `get_entity_meta`, `list_entity_types`
1611
+
1612
+
1613
+ ### `get_scale`
1614
+
1615
+ 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.
1616
+
1617
+ **Atomicity:** `atomic (read-only)`
1618
+
1619
+ **Arguments:**
1620
+
1621
+ | Name | Type | Required | Description |
1622
+ | ---- | ---- | -------- | ----------- |
1623
+ | `id` | string | ✓ | Scale id (e.g. "reach_5", "frequency_5", "severity_5", "importance_5", "confidence_binary"). |
1624
+
1625
+ **Returns:**
1626
+
1627
+ JSON: the full `UPGScaleDefinition` record including all points.
1628
+
1629
+ **See also:** `list_scales`, `get_entity_schema`
1630
+
1631
+
1632
+ ### `get_spec_version`
1633
+
1634
+ 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.
1635
+
1636
+ **Atomicity:** `atomic (read-only)`
1637
+
1638
+ _No arguments._
1639
+
1640
+ **Returns:**
1641
+
1642
+ JSON: `{ upg_version, markdown_format_version, entity_count, edge_count, domain_count, region_count }`
1643
+
1644
+ **See also:** `get_workspace_info`, `list_entity_types`, `list_edge_types`, `list_regions`
1645
+
1646
+
1647
+ ### `get_type_label`
1648
+
1649
+ 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`.
1650
+
1651
+ **Atomicity:** `atomic (read-only)`
1652
+
1653
+ **Arguments:**
1654
+
1655
+ | Name | Type | Required | Description |
1656
+ | ---- | ---- | -------- | ----------- |
1657
+ | `designation` | string | | Optional designation key (e.g. "pain", "gap", "desire") for types that use the designation pattern. |
1658
+ | `entity_type` | string | ✓ | Canonical entity type id. |
1659
+ | `framework_id` | string | | Optional framework id (e.g. "lean_canvas", "ost", "design_thinking"). When set, resolved_label uses the framework-specific label. |
1660
+
1661
+ **Returns:**
1662
+
1663
+ JSON: `{ ...UPGTypeLabel, resolved_label: string }`
1664
+
1665
+ **Throws:**
1666
+
1667
+ - textError when `entity_type` is missing or unknown.
1668
+
1669
+ **See also:** `list_type_labels`, `get_entity_meta`, `list_frameworks`
1670
+
1671
+
1672
+ ### `get_valid_children`
1673
+
1674
+ 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`.
1675
+
1676
+ **Atomicity:** `atomic (read-only)`
1677
+
1678
+ **Arguments:**
1679
+
1680
+ | Name | Type | Required | Description |
1681
+ | ---- | ---- | -------- | ----------- |
1682
+ | `parent_type` | string | ✓ | Canonical parent entity type. |
1683
+
1684
+ **Returns:**
1685
+
1686
+ JSON: `{ parent_type, valid_children: string[] }`
1687
+
1688
+ **Throws:**
1689
+
1690
+ - textError when `parent_type` is missing.
1691
+
1692
+ **See also:** `get_entity_schema`, `list_entity_types`, `get_entity_meta`, `create_node`
1693
+
1694
+
1695
+ ### `inspect`
1696
+
1697
+ [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.
1698
+
1699
+ **Atomicity:** `atomic (read-only)`
1700
+
1701
+ **Arguments:**
1702
+
1703
+ | Name | Type | Required | Description |
1704
+ | ---- | ---- | -------- | ----------- |
1705
+ | `entities` | array | | Optional entity_id[]. Narrows inspection scope to a specific candidate set. Composable with region. |
1706
+ | `region` | string | | Optional UPGRegionId. Narrows inspection scope to a single region. |
1707
+
1708
+ **Returns:**
1709
+
1710
+ JSON envelope: `{ approach_id, scope, generated_at, approach,
1711
+ params, violations, summary, execution_mode: "execution_v0_4_0" }`
1712
+
1713
+ **See also:** `get_approach`, `list_anti_patterns`, `get_anti_pattern`, `get_anti_pattern_violations_for`, `validate_graph`, `plan`, `reflect`
1714
+
1715
+
1716
+ ### `list_anti_patterns`
1717
+
1718
+ 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`).
1719
+
1720
+ **Atomicity:** `atomic (read-only)`
1721
+
1722
+ **Arguments:**
1723
+
1724
+ | Name | Type | Required | Description |
1725
+ | ---- | ---- | -------- | ----------- |
1726
+ | `cursor` | string | | Opaque pagination cursor. Pass next_cursor from a previous response. |
1727
+ | `limit` | number | | Page size (default 50, max 200). |
1728
+ | `severity` | `high` \| `medium` \| `low` | | Exact-match UPGAntiPatternSeverity. |
1729
+ | `stage` | `concept` \| `validation` \| `build` \| `beta` \| `launch` \| `growth` \| `mature` \| `maintenance` \| `sunset` | | Keeps anti-patterns whose stages[] includes the given UPGProductStage. |
1730
+
1731
+ **Returns:**
1732
+
1733
+ JSON: `{ total, count, next_cursor?, anti_patterns: UPGCuratedAntiPattern[] }`
1734
+
1735
+ **See also:** `get_anti_pattern`, `get_anti_pattern_violations_for`, `validate_graph`, `inspect`, `get_domain_guide`
1736
+
1737
+
1738
+ ### `list_approaches`
1739
+
1740
+ 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.
1741
+
1742
+ **Atomicity:** `atomic (read-only)`
1743
+
1744
+ **Arguments:**
1745
+
1746
+ | Name | Type | Required | Description |
1747
+ | ---- | ---- | -------- | ----------- |
1748
+ | `framework_id` | string | | Exact-match framework id. Narrows to approaches whose framework_id_examples include it (discoverability surface; full reverse lookup is on UPGFramework.approach_ids). |
1749
+
1750
+ **Returns:**
1751
+
1752
+ JSON: `{ count, approaches: UPGApproach[] }`
1753
+
1754
+ **See also:** `get_approach`, `plan`, `inspect`, `prioritise`, `trace`, `reflect`, `list_playbooks`
1755
+
1756
+
1757
+ ### `list_benchmarks`
1758
+
1759
+ 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.
1760
+
1761
+ **Atomicity:** `atomic (read-only)`
1762
+
1763
+ **Arguments:**
1764
+
1765
+ | Name | Type | Required | Description |
1766
+ | ---- | ---- | -------- | ----------- |
1767
+ | `domain` | string | | Optional atomic-domain id filter. Semantics depend on kind (see tool description). |
1768
+ | `kind` | `count` \| `relationship` \| `ratio` \| `domain_activation` | ✓ | Required. Which benchmark catalog to return. |
1769
+ | `stage` | `concept` \| `validation` \| `build` \| `beta` \| `launch` \| `growth` \| `mature` \| `maintenance` \| `sunset` | | Optional UPGProductStage filter. Semantics depend on kind (see tool description). |
1770
+
1771
+ **Returns:**
1772
+
1773
+ JSON: `{ kind, total, count, benchmarks: ... }`
1774
+
1775
+ **Throws:**
1776
+
1777
+ - textError when `kind` is missing or not one of the four supported values.
1778
+
1779
+ **See also:** `get_graph_digest`, `list_product_stages`, `list_domains`, `list_anti_patterns`
1780
+
1781
+
1782
+ ### `list_cross_edge_types`
1783
+
1784
+ 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`.
1785
+
1786
+ **Atomicity:** `atomic (read-only)`
1787
+
1788
+ _No arguments._
1789
+
1790
+ **Returns:**
1791
+
1792
+ JSON: `{ count, types: readonly UPGCrossEdgeType[] }`
1793
+
1794
+ **See also:** `list_edge_types`, `list_portfolio_cross_edges`, `migrate_cross_edges`
1795
+
1796
+
1797
+ ### `list_domain_rings`
1798
+
1799
+ 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.
1800
+
1801
+ **Atomicity:** `atomic (read-only)`
1802
+
1803
+ _No arguments._
1804
+
1805
+ **Returns:**
1806
+
1807
+ JSON: `{ rings: UPGDomainRing[], total: number }`
1808
+
1809
+ **See also:** `get_domain_ring`, `list_domains`, `get_domain_guide`
1810
+
1811
+
1812
+ ### `list_domains`
1813
+
1814
+ 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.
1815
+
1816
+ **Atomicity:** `atomic (read-only)`
1817
+
1818
+ **Arguments:**
1819
+
1820
+ | Name | Type | Required | Description |
1821
+ | ---- | ---- | -------- | ----------- |
1822
+ | `with_guide_only` | boolean | | 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). |
1823
+
1824
+ **Returns:**
1825
+
1826
+ JSON: `{ count, domains: Array<{ domain_id, anchor_entity, creation_sequence } | { domain_id, label, description, types, has_guide }> }`
1827
+
1828
+ **See also:** `get_domain_guide`, `list_domain_rings`, `get_domain_ring`, `list_regions`, `list_entity_types`
1829
+
1830
+
1831
+ ### `list_edge_migrations`
1832
+
1833
+ 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`.
1834
+
1835
+ **Atomicity:** `atomic (read-only)`
1836
+
1837
+ **Arguments:**
1838
+
1839
+ | Name | Type | Required | Description |
1840
+ | ---- | ---- | -------- | ----------- |
1841
+ | `from_edge` | string | | Exact-match filter on the deprecated edge key (e.g. "persona_has_jtbd"). |
1842
+
1843
+ **Returns:**
1844
+
1845
+ JSON: `{ migrations: [{ kind, from, to?, since }], total: number }`
1846
+
1847
+ **See also:** `list_type_migrations`, `list_split_migrations`, `rename_edge_type`, `list_edge_types`, `validate_graph`
1848
+
1849
+
1850
+ ### `list_edge_types`
1851
+
1852
+ 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.
1853
+
1854
+ **Atomicity:** `atomic (read-only)`
1855
+
1856
+ **Arguments:**
1857
+
1858
+ | Name | Type | Required | Description |
1859
+ | ---- | ---- | -------- | ----------- |
1860
+ | `source_type` | string | | Exact-match filter on UPGEdgeDefinition.source_type. Pass "node" to find polymorphic edges with a wildcard source. |
1861
+ | `target_type` | string | | Exact-match filter on UPGEdgeDefinition.target_type. |
1862
+
1863
+ **Returns:**
1864
+
1865
+ JSON: `{ count, edges: Array<{ type, forward_verb, reverse_verb, classification, source_type, target_type }> }`
1866
+
1867
+ **See also:** `get_edge_type`, `resolve_edge_for_pair`, `list_cross_edge_types`, `list_edge_migrations`, `create_edge`
1868
+
1869
+
1870
+ ### `list_entity_types`
1871
+
1872
+ 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).
1873
+
1874
+ **Atomicity:** `atomic (read-only)`
1875
+
1876
+ **Arguments:**
1877
+
1878
+ | Name | Type | Required | Description |
1879
+ | ---- | ---- | -------- | ----------- |
1880
+ | `cursor` | string | | Opaque pagination cursor. Pass next_cursor from a previous response. |
1881
+ | `deprecated` | boolean | | true → only deprecated types; false → exclude deprecated and removed types (the active set). Composes with maturity via AND. |
1882
+ | `domain` | string | | Exact-match atomic-domain id (e.g. "user", "market_intelligence"). |
1883
+ | `limit` | number | | Page size (default 50, max 200). |
1884
+ | `maturity` | `draft` \| `proposed` \| `stable` \| `deprecated` \| `removed` | | Exact-match UPGEntityTypeMaturity. |
1885
+
1886
+ **Returns:**
1887
+
1888
+ JSON: `{ total, count, next_cursor?, types: Array<EntityTypeMeta & { domain_id: string | null }> }`
1889
+
1890
+ **See also:** `get_entity_meta`, `get_entity_schema`, `list_type_labels`, `list_type_migrations`, `list_domains`
1891
+
1892
+
1893
+ ### `list_framework_categories`
1894
+
1895
+ 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`.
1896
+
1897
+ **Atomicity:** `atomic (read-only)`
1898
+
1899
+ _No arguments._
1900
+
1901
+ **Returns:**
1902
+
1903
+ JSON: `{ categories: string[], total: number }`
1904
+
1905
+ **See also:** `list_frameworks`, `list_framework_structure_patterns`
1906
+
1907
+
1908
+ ### `list_framework_structure_patterns`
1909
+
1910
+ List valid framework structure-pattern values from `UPG_STRUCTURE_PATTERNS`. Visual topological shapes: tree, table, matrix, funnel, collection, quadrant, flow. Mirrors `UPGFramework.structure.pattern`.
1911
+
1912
+ **Atomicity:** `atomic (read-only)`
1913
+
1914
+ _No arguments._
1915
+
1916
+ **Returns:**
1917
+
1918
+ JSON: `{ patterns: string[], total: number }`
1919
+
1920
+ **See also:** `list_frameworks`, `list_framework_categories`, `get_framework`
1921
+
1922
+
1923
+ ### `list_frameworks`
1924
+
1925
+ 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.
1926
+
1927
+ **Atomicity:** `atomic (read-only)`
1928
+
1929
+ **Arguments:**
1930
+
1931
+ | Name | Type | Required | Description |
1932
+ | ---- | ---- | -------- | ----------- |
1933
+ | `category` | string | | Exact-match filter on UPGFramework.category (e.g. "strategy", "prioritization"). |
1934
+ | `cursor` | string | | Opaque pagination cursor. Pass next_cursor from a previous response. |
1935
+ | `limit` | number | | Page size (default 50, max 200). |
1936
+
1937
+ **Returns:**
1938
+
1939
+ JSON: `{ total, count, next_cursor?, frameworks: UPGFramework[] }`
1940
+
1941
+ **See also:** `get_framework`, `list_framework_categories`, `list_framework_structure_patterns`, `prioritise`, `list_approaches`
1942
+
1943
+
1944
+ ### `list_lenses`
1945
+
1946
+ 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.
1947
+
1948
+ **Atomicity:** `atomic (read-only)`
1949
+
1950
+ _No arguments._
1951
+
1952
+ **Returns:**
1953
+
1954
+ JSON: `{ count, lenses: Array<{ id, name, description, icon, audience, perspective, framework_id?, playbook_id?, visible_domain_count, intelligence_prompt_count }> }`
1955
+
1956
+ **See also:** `get_lens`, `list_regions`, `list_playbooks`, `list_frameworks`
1957
+
1958
+
1959
+ ### `list_lifecycles`
1960
+
1961
+ 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).
1962
+
1963
+ **Atomicity:** `atomic (read-only)`
1964
+
1965
+ **Arguments:**
1966
+
1967
+ | Name | Type | Required | Description |
1968
+ | ---- | ---- | -------- | ----------- |
1969
+ | `entity_type` | string | | Exact-match entity type name (e.g. "feature", "hypothesis"). Returns at most one lifecycle. |
1970
+ | `lifecycle_only` | boolean | | When true, omit free_types and planned_types from response. |
1971
+
1972
+ **Returns:**
1973
+
1974
+ JSON: `{ lifecycles, total, free_types: string[], planned_types: string[] }`
1975
+
1976
+ **See also:** `get_lifecycle`, `list_entity_types`, `get_entity_meta`
1977
+
1978
+
1979
+ ### `list_playbooks`
1980
+
1981
+ 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).
1982
+
1983
+ **Atomicity:** `atomic (read-only)`
1984
+
1985
+ **Arguments:**
1986
+
1987
+ | Name | Type | Required | Description |
1988
+ | ---- | ---- | -------- | ----------- |
1989
+ | `canonical_only` | boolean | | When true, return only the canonical playbook per region (W1 invariant restated). |
1990
+ | `framework_id` | string | | Exact-match UPGFramework.id (e.g. "business-model-canvas", "pirate-metrics-aarrr"). |
1991
+ | `region` | string | | Exact-match UPGRegionId (e.g. "users_needs", "business_gtm_growth"). |
1992
+
1993
+ **Returns:**
1994
+
1995
+ JSON: `{ count, playbooks: UPGPlaybook[] }`
1996
+
1997
+ **Examples:**
1998
+
1999
+ // List all canonical playbooks to see what bootstrap paths are available
2000
+ // Input:
2001
+ { "canonical_only": true }
2002
+ // Output (truncated):
2003
+ {
2004
+ "count": 10,
2005
+ "playbooks": [
2006
+ {
2007
+ "id": "playbook:users-needs",
2008
+ "name": "Users & Needs",
2009
+ "region": "users_needs",
2010
+ "is_canonical": true,
2011
+ "target_anchor_entity": "persona",
2012
+ "creation_sequence": ["persona", "job", "need", "pain_point"]
2013
+ },
2014
+ { "id": "playbook:strategy-outcomes", "region": "strategy_outcomes", "is_canonical": true, "..." }
2015
+ ]
2016
+ }
2017
+
2018
+ **See also:** `get_playbook`, `list_regions`, `list_approaches`, `list_frameworks`
2019
+
2020
+
2021
+ ### `list_product_stages`
2022
+
2023
+ 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.
2024
+
2025
+ **Atomicity:** `atomic (read-only)`
2026
+
2027
+ _No arguments._
2028
+
2029
+ **Returns:**
2030
+
2031
+ JSON: `{ count, stages: readonly UPGProductStage[] }`
2032
+
2033
+ **See also:** `list_benchmarks`, `list_anti_patterns`, `list_domain_rings`, `create_product`
2034
+
2035
+
2036
+ ### `list_regions`
2037
+
2038
+ 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.
2039
+
2040
+ **Atomicity:** `atomic (read-only)`
2041
+
2042
+ _No arguments._
2043
+
2044
+ **Returns:**
2045
+
2046
+ JSON: `{ count, regions: Array<{ id, label, order, shape, mental_model, anchor_type, composes_atomic_domains, entity_count, intra_edge_count, boundary_edge_count }> }`
2047
+
2048
+ **See also:** `get_region`, `get_region_for_entity_type`, `list_domains`, `list_playbooks`
2049
+
2050
+
2051
+ ### `list_scales`
2052
+
2053
+ 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.
2054
+
2055
+ **Atomicity:** `atomic (read-only)`
2056
+
2057
+ _No arguments._
2058
+
2059
+ **Returns:**
2060
+
2061
+ JSON: `{ scales: UPGScaleDefinition[], total: number }`
2062
+
2063
+ **See also:** `get_scale`, `get_entity_schema`
2064
+
2065
+
2066
+ ### `list_split_migrations`
2067
+
2068
+ 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.
2069
+
2070
+ **Atomicity:** `atomic (read-only)`
2071
+
2072
+ _No arguments._
2073
+
2074
+ **Returns:**
2075
+
2076
+ JSON: `{ splits: [...], total: number }`
2077
+
2078
+ **See also:** `list_type_migrations`, `list_edge_migrations`, `migrate_type`, `validate_graph`
2079
+
2080
+
2081
+ ### `list_type_labels`
2082
+
2083
+ 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.
2084
+
2085
+ **Atomicity:** `atomic (read-only)`
2086
+
2087
+ **Arguments:**
2088
+
2089
+ | Name | Type | Required | Description |
2090
+ | ---- | ---- | -------- | ----------- |
2091
+ | `cursor` | string | | Opaque pagination cursor. Pass next_cursor from a previous response. |
2092
+ | `limit` | number | | Page size (default 100, max 500). |
2093
+
2094
+ **Returns:**
2095
+
2096
+ JSON: `{ total, count, next_cursor?, labels: UPGTypeLabel[] }`
2097
+
2098
+ **See also:** `get_type_label`, `list_entity_types`, `get_entity_meta`
2099
+
2100
+
2101
+ ### `list_type_migrations`
2102
+
2103
+ 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`.
2104
+
2105
+ **Atomicity:** `atomic (read-only)`
2106
+
2107
+ **Arguments:**
2108
+
2109
+ | Name | Type | Required | Description |
2110
+ | ---- | ---- | -------- | ----------- |
2111
+ | `from_type` | string | | Exact-match filter on the deprecated type name (e.g. "pain_point", "hypothesis"). |
2112
+
2113
+ **Returns:**
2114
+
2115
+ JSON: `{ migrations: [{ from, to, since }], total: number }`
2116
+
2117
+ **See also:** `list_edge_migrations`, `list_split_migrations`, `migrate_type`, `migrate_properties`, `validate_graph`, `list_entity_types`
2118
+
2119
+
2120
+ ### `plan`
2121
+
2122
+ 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.
2123
+
2124
+ **Atomicity:** `atomic (read-only)`
2125
+
2126
+ **Arguments:**
2127
+
2128
+ | Name | Type | Required | Description |
2129
+ | ---- | ---- | -------- | ----------- |
2130
+ | `region` | string | | Optional UPGRegionId. Narrows planning scope to a single region (e.g. "users_needs", "business_gtm_growth"). Omit for whole-graph planning. |
2131
+
2132
+ **Returns:**
2133
+
2134
+ JSON envelope: `{ approach_id, scope, generated_at, approach,
2135
+ params, missing_entities, coverage_score, expected_count, covered_count,
2136
+ execution_mode: "execution_v0_4_0" }`.
2137
+
2138
+ **Examples:**
2139
+
2140
+ // Input: { "region": "users_needs" }
2141
+ // Output (truncated):
2142
+ {
2143
+ "approach_id": "plan",
2144
+ "scope": "users_needs",
2145
+ "missing_entities": [
2146
+ { "entity_type": "job", "domain": "user", "position_in_sequence": 1,
2147
+ "typical_parent_type": "persona",
2148
+ "hint": "Add job (step 2 in the user sequence; typically attached under persona)." }
2149
+ ],
2150
+ "coverage_score": 0.5,
2151
+ "execution_mode": "execution_v0_4_0"
2152
+ }
2153
+
2154
+ **See also:** `get_approach`, `list_playbooks`, `get_region`, `inspect`, `prioritise`
2155
+
2156
+
2157
+ ### `prioritise`
2158
+
2159
+ [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 }`.
2160
+
2161
+ **Atomicity:** `atomic (read-only)`
2162
+
2163
+ **Arguments:**
2164
+
2165
+ | Name | Type | Required | Description |
2166
+ | ---- | ---- | -------- | ----------- |
2167
+ | `candidates` | array | ✓ | Required. entity_id[] to rank. |
2168
+ | `framework_id` | string | ✓ | Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf"). |
2169
+
2170
+ **Returns:**
2171
+
2172
+ JSON envelope: `{ approach_id, scope, generated_at, approach,
2173
+ params, framework_resolved, ranked?, required_properties?,
2174
+ hint?, execution_mode }`. Execution mode is `"execution_v0_4_0"` when
2175
+ the framework has an expression, `"definition_lookup_v0_4_0"` otherwise.
2176
+
2177
+ **Throws:**
2178
+
2179
+ - textError when `candidates` or `framework_id` are missing/empty,
2180
+ or when `framework_id` is not in `UPG_FRAMEWORKS`.
2181
+
2182
+ **See also:** `get_approach`, `list_frameworks`, `get_framework`, `plan`, `trace`
2183
+
2184
+
2185
+ ### `reflect`
2186
+
2187
+ [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.
2188
+
2189
+ **Atomicity:** `atomic (read-only)`
2190
+
2191
+ **Arguments:**
2192
+
2193
+ | Name | Type | Required | Description |
2194
+ | ---- | ---- | -------- | ----------- |
2195
+ | `mode` | `assumptions` \| `alternatives` \| `blind-spots` \| `load-bearing` | | Optional. One of: assumptions, alternatives, blind-spots, load-bearing. Omit for open reflection. |
2196
+ | `scope` | string,null | | Optional. Region id, entity id, or null for whole-graph. |
2197
+
2198
+ **Returns:**
2199
+
2200
+ JSON envelope: `{ approach_id, scope, generated_at, approach,
2201
+ params, prompts, execution_mode: "execution_v0_4_0" }`
2202
+
2203
+ **Throws:**
2204
+
2205
+ - textError when `mode` is provided but not one of the 4 canonical
2206
+ nouns.
2207
+
2208
+ **See also:** `get_approach`, `inspect`, `plan`, `get_anti_pattern`
2209
+
2210
+
2211
+ ### `resolve_edge_for_pair`
2212
+
2213
+ 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.
2214
+
2215
+ **Atomicity:** `atomic (read-only)`
2216
+
2217
+ **Arguments:**
2218
+
2219
+ | Name | Type | Required | Description |
2220
+ | ---- | ---- | -------- | ----------- |
2221
+ | `source_type` | string | ✓ | Parent / source entity type. |
2222
+ | `target_type` | string | ✓ | Child / target entity type. |
2223
+
2224
+ **Returns:**
2225
+
2226
+ JSON: `{ source_type, target_type, edge_type: string | null,
2227
+ anchor_hint?, alternate_anchors?, adjacent_edges? }`
2228
+
2229
+ **Throws:**
2230
+
2231
+ - textError when `source_type` or `target_type` is missing.
2232
+
2233
+ **Warnings (non-error surfaces):**
2234
+
2235
+ - Returns `edge_type: null` when no canonical pair is registered.
2236
+ Adapters MUST fall back to a polymorphic edge or skip the relationship,
2237
+ not synthesise a non-canonical key.
2238
+
2239
+ **See also:** `list_edge_types`, `get_edge_type`, `create_edge`, `trace`
2240
+
2241
+
2242
+ ### `trace`
2243
+
2244
+ [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".
2245
+
2246
+ **Atomicity:** `atomic (read-only)`
2247
+
2248
+ **Arguments:**
2249
+
2250
+ | Name | Type | Required | Description |
2251
+ | ---- | ---- | -------- | ----------- |
2252
+ | `anchor` | string | ✓ | Required. entity_id where the traversal starts. |
2253
+ | `edges_override` | array | | Optional. Per-hop edge override array. Length must match path length; element null means "use canonical edge for this pair". |
2254
+ | `path` | array | ✓ | Required. UPGEntityType[] type-shorthand path. Each step walks via the canonical edge for the source→target pair. |
2255
+
2256
+ **Returns:**
2257
+
2258
+ JSON envelope: `{ approach_id, scope, generated_at, approach,
2259
+ params, trail, reached, error?, halted_at_depth?,
2260
+ execution_mode: "execution_v0_4_0" }`
2261
+
2262
+ **Throws:**
2263
+
2264
+ - textError when `anchor` or `path` are missing/invalid.
2265
+
2266
+ **Examples:**
2267
+
2268
+ // Input: { "anchor": "persona_01", "path": ["job", "feature"] }
2269
+ // Output (truncated):
2270
+ {
2271
+ "approach_id": "trace",
2272
+ "trail": [
2273
+ { "depth": 0, "entity_id": "persona_01", "edge_type_in": null },
2274
+ { "depth": 1, "entity_id": "job_01", "edge_type_in": "persona_pursues_job" }
2275
+ ],
2276
+ "reached": ["persona_01", "job_01"],
2277
+ "execution_mode": "execution_v0_4_0"
2278
+ }
2279
+
2280
+ **See also:** `get_approach`, `resolve_edge_for_pair`, `query`, `get_node`, `plan`, `prioritise`
2281
+
2282
+
2283
+ ## Cloud Sync
2284
+
2285
+ _Read sync state, pull cloud changes, push local graph._
2286
+
2287
+ - [`apply_pull_changeset`](#apply-pull-changeset)
2288
+ - [`get_sync_state`](#get-sync-state)
2289
+ - [`push_to_cloud`](#push-to-cloud)
2290
+
2291
+ ### `apply_pull_changeset`
2292
+
2293
+ 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).
2294
+
2295
+ **Atomicity:** `non-atomic. Node/edge mutations apply incrementally; a partial
2296
+ failure mid-application leaves the graph in a half-merged state. The
2297
+ `.upg-sync` file is updated after the merge sweep so its hashes reflect
2298
+ whatever landed.`
2299
+
2300
+ **Arguments:**
2301
+
2302
+ | Name | Type | Required | Description |
2303
+ | ---- | ---- | -------- | ----------- |
2304
+ | `cloud_edges` | array | ✓ | All cloud edges (from export_upg_document) |
2305
+ | `cloud_endpoint` | string | | Cloud endpoint URL (e.g. https://cloud.unifiedproductgraph.org) |
2306
+ | `cloud_nodes` | array | ✓ | All cloud nodes (from export_upg_document) |
2307
+ | `cloud_product_id` | string | ✓ | Cloud product ID |
2308
+ | `strategy` | `cloud_wins` \| `local_wins` \| `merge` | | Conflict resolution: cloud_wins (default), local_wins, or merge (report conflicts without resolving) |
2309
+
2310
+ **Returns:**
2311
+
2312
+ JSON: `{ nodes_created, nodes_updated, nodes_deleted,
2313
+ edges_created, edges_deleted, strategy, conflicts?, message? }`.
2314
+
2315
+ **Throws:**
2316
+
2317
+ - Returns a textError when `cloud_nodes`, `cloud_edges`, or
2318
+ `cloud_product_id` is missing, or when sync-state I/O fails.
2319
+
2320
+ **Warnings (non-error surfaces):**
2321
+
2322
+ - Mutates the active product. Always call `get_workspace_info`
2323
+ first to confirm the right product is loaded; otherwise cloud changes
2324
+ land in the wrong file. `merge` strategy returns conflicts without
2325
+ applying them; the caller must re-run with `cloud_wins`/`local_wins`
2326
+ to commit.
2327
+
2328
+ **See also:** `push_to_cloud`, `get_sync_state`, `get_workspace_info`, `get_changes`
2329
+
2330
+
2331
+ ### `get_sync_state`
2332
+
2333
+ 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.
2334
+
2335
+ **Atomicity:** `atomic (read-only)`
2336
+
2337
+ _No arguments._
2338
+
2339
+ **Returns:**
2340
+
2341
+ JSON: `{ synced: false, message }` or
2342
+ `{ synced: true, cloud_endpoint, product_id, last_synced_at,
2343
+ mapped_nodes, mapped_edges, last_snapshot_hash }`.
2344
+
2345
+ **See also:** `push_to_cloud`, `apply_pull_changeset`, `get_workspace_info`, `get_changes`
2346
+
2347
+
2348
+ ### `push_to_cloud`
2349
+
2350
+ 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).
2351
+
2352
+ **Atomicity:** `non-atomic. Performs an HTTP round-trip and then writes the
2353
+ sync file as a separate filesystem mutation. A partial failure (e.g.
2354
+ cloud accepted some entities, then network broke) is reflected in the
2355
+ `errors` array; the sync file is only updated when the import call
2356
+ succeeds.`
2357
+
2358
+ **Arguments:**
2359
+
2360
+ | Name | Type | Required | Description |
2361
+ | ---- | ---- | -------- | ----------- |
2362
+ | `api_key` | string | | UPG Cloud API key. Auto-discovered from .mcp.json upg-cloud entry if omitted. |
2363
+ | `cloud_endpoint` | string | | Cloud base URL. Auto-discovered from .mcp.json upg-cloud entry if omitted. |
2364
+ | `product_id` | string | | Optional. Push to an existing cloud product instead of creating new. |
2365
+ | `strategy` | `create_new` \| `merge` \| `replace` | | Import strategy. Default: create_new |
2366
+
2367
+ **Returns:**
2368
+
2369
+ JSON: `{ success, product_id, nodes_created, edges_created,
2370
+ errors, sync_file_updated }`.
2371
+
2372
+ **Throws:**
2373
+
2374
+ - Returns a textError when credentials cannot be resolved, the cloud
2375
+ returns a non-2xx response, or the sync file write fails.
2376
+
2377
+ **Warnings (non-error surfaces):**
2378
+
2379
+ - Pushes the **currently-loaded** product. Call
2380
+ `get_workspace_info` first to confirm. Auto-discovers credentials
2381
+ from `.mcp.json`'s `upg-cloud` server entry; falls back to explicit
2382
+ `cloud_endpoint` + `api_key` arguments. Default `strategy: 'create_new'`
2383
+ creates a fresh cloud product on every call; pass `product_id` to
2384
+ target an existing one.
2385
+
2386
+ **See also:** `apply_pull_changeset`, `get_sync_state`, `get_workspace_info`
2387
+
2388
+
2389
+ ## Validation
2390
+
2391
+ _Schema-drift detection and full per-node drift reports._
2392
+
2393
+ - [`get_anti_pattern_violations_for`](#get-anti-pattern-violations-for)
2394
+ - [`validate_graph`](#validate-graph)
2395
+
2396
+ ### `get_anti_pattern_violations_for`
2397
+
2398
+ 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.
2399
+
2400
+ **Atomicity:** `atomic (read-only)`
2401
+
2402
+ **Arguments:**
2403
+
2404
+ | Name | Type | Required | Description |
2405
+ | ---- | ---- | -------- | ----------- |
2406
+ | `entity_id` | string | ✓ | Node id to look up. |
2407
+
2408
+ **Returns:**
2409
+
2410
+ JSON: `{ entity_id, type, violations: [...] }`.
2411
+
2412
+ **Throws:**
2413
+
2414
+ - textError when `entity_id` is missing or unknown.
2415
+
2416
+ **Warnings (non-error surfaces):**
2417
+
2418
+ - Phase 1 matches by entity TYPE, not specific id. Every entity of
2419
+ the same type shares the same violation set. Phase 1.x will tighten to
2420
+ per-id matching once `target_entities` carries ids.
2421
+
2422
+ **Examples:**
2423
+
2424
+ // Find all anti-pattern violations that implicate a specific feature node
2425
+ // Input:
2426
+ { "entity_id": "feature_04" }
2427
+ // Output (truncated):
2428
+ {
2429
+ "entity_id": "feature_04",
2430
+ "type": "feature",
2431
+ "violations": [
2432
+ {
2433
+ "anti_pattern_id": "features-without-hypotheses",
2434
+ "name": "Features Without Hypotheses",
2435
+ "severity": "high",
2436
+ "why_it_matters": "Building without a testable hypothesis means no way to evaluate success.",
2437
+ "remediation": "Link each feature to a hypothesis_claim via feature_tests_hypothesis."
2438
+ }
2439
+ ]
2440
+ }
2441
+
2442
+ **See also:** `validate_graph`, `list_anti_patterns`, `get_anti_pattern`, `inspect`
2443
+
2444
+
2445
+ ### `validate_graph`
2446
+
2447
+ 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`.
2448
+
2449
+ **Atomicity:** `atomic (read-only)`
2450
+
2451
+ **Arguments:**
2452
+
2453
+ | Name | Type | Required | Description |
2454
+ | ---- | ---- | -------- | ----------- |
2455
+ | `anti_pattern_ids` | array | | Restrict anti-pattern evaluation to a subset of catalog ids (e.g. ["features-without-hypotheses"]). |
2456
+ | `if_changed_since` | string | | Hash from a previous response. Returns { changed: false } if graph unchanged. |
2457
+ | `include_polymorphic_upgrades` | boolean | | 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. |
2458
+ | `limit` | number | | Max entries per class (default 100, max 1000) |
2459
+ | `scope` | `all` \| `entity_drift` \| `edge_drift` \| `property_drift` \| `top_level_drift` \| `lifecycle_drift` \| `self_referential` | | Which drift class(es) to include in the response (default "all"). Counts in `summary` are always returned for every class. |
2460
+ | `severity` | `high` \| `medium` \| `low` | | Filter anti-pattern violations to one severity tier. |
2461
+ | `skip_anti_patterns` | boolean | | Skip anti-pattern evaluation. Only returns schema drift. |
2462
+ | `skip_drift` | boolean | | Skip the schema-drift block. Only returns anti-pattern violations. |
2463
+
2464
+ **Returns:**
2465
+
2466
+ JSON: `{ valid, summary, entity_drift?, edge_drift?,
2467
+ property_drift?, top_level_drift?, lifecycle_drift?, self_referential?,
2468
+ anti_pattern_violations?, notes?, _hash }`. Per-class drift arrays appear
2469
+ only when the requested `scope` includes that class. Each array is capped
2470
+ at `limit` (default 100).
2471
+
2472
+ **Throws:**
2473
+
2474
+ - Returns a textError when `scope` or `severity` is not one of the
2475
+ recognised values.
2476
+
2477
+ **Warnings (non-error surfaces):**
2478
+
2479
+ - Top-level `valid` is true ONLY when both drift is empty AND no
2480
+ anti-pattern violations fired. Set `skip_anti_patterns: true` for a
2481
+ pure spec-shape check; `skip_drift: true` for catalog-only.
2482
+
2483
+ **Examples:**
2484
+
2485
+ // Run a full graph health check (schema drift + anti-pattern violations)
2486
+ // Input:
2487
+ {}
2488
+ // Output (truncated):
2489
+ {
2490
+ "valid": false,
2491
+ "summary": {
2492
+ "entity_drift": 2,
2493
+ "edge_drift": 0,
2494
+ "property_drift": 1,
2495
+ "anti_pattern_violations_high": 1,
2496
+ "anti_pattern_violations_medium": 2,
2497
+ "anti_pattern_violations_low": 0,
2498
+ "spec_version": "0.5.0",
2499
+ "scope": "all"
2500
+ },
2501
+ "entity_drift": [
2502
+ { "id": "pain_01", "type": "pain_point", "title": "Slow onboarding", "suggested_migration": { "kind": "rename", "to": "need" } }
2503
+ ],
2504
+ "anti_pattern_violations": [
2505
+ { "anti_pattern_id": "features-without-hypotheses", "severity": "high", "remediation": "Add hypothesis_claim nodes linked to features via feature_tests_hypothesis" }
2506
+ ],
2507
+ "_hash": "sha256-abc123"
2508
+ }
2509
+
2510
+ **See also:** `migrate_type`, `migrate_properties`, `rename_edge_type`, `get_anti_pattern_violations_for`, `list_anti_patterns`, `list_type_migrations`, `list_edge_migrations`, `inspect`
2511
+
2512
+
2513
+ ## Migrations
2514
+
2515
+ _Status value migration across the graph._
2516
+
2517
+ - [`migrate_status`](#migrate-status)
2518
+
2519
+ ### `migrate_status`
2520
+
2521
+ 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.
2522
+
2523
+ **Atomicity:** `per-node. Status writes go through `store.updateNode`
2524
+ one at a time. Dry-run is read-only.`
2525
+
2526
+ **Arguments:**
2527
+
2528
+ | Name | Type | Required | Description |
2529
+ | ---- | ---- | -------- | ----------- |
2530
+ | `dry_run` | boolean | | Preview changes without applying (default true). Pass false to commit. |
2531
+ | `entity_type` | string | | Optional. Restrict the rewrite to nodes of this canonical entity type (e.g. "service", "feature"). |
2532
+ | `from_status` | string | | Optional. Restrict the rewrite to nodes whose current status equals this exact value. When provided, `to_status` is required and the registry is bypassed. |
2533
+ | `to_status` | string | | Required when `from_status` is provided. The canonical phase id to write. |
2534
+
2535
+ **Returns:**
2536
+
2537
+ JSON: `MigrateStatusResult`.
2538
+
2539
+ **Throws:**
2540
+
2541
+ - Returns a textError when `from_status` is provided without
2542
+ `to_status`, or when `entity_type` is provided but isn't a string.
2543
+
2544
+ **Warnings (non-error surfaces):**
2545
+
2546
+ - Default is `dry_run: true`. Pass `dry_run: false` to commit.
2547
+ Idempotent on retry — re-running after a successful commit reports
2548
+ zero changes (canonical statuses pass the validity check).
2549
+
2550
+ **See also:** `migrate_type`, `migrate_properties`, `validate_graph`, `list_lifecycles`
2551
+
2552
+
2553
+ ## Skills Introspection
2554
+
2555
+ _Verify source-vs-deployed integrity of UPG `/upg-*` skills before recommending them._
2556
+
2557
+ - [`skill_audit`](#skill-audit)
2558
+
2559
+ ### `skill_audit`
2560
+
2561
+ 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.
2562
+
2563
+ **Atomicity:** `atomic (read-only filesystem stat + read)`
2564
+
2565
+ **Arguments:**
2566
+
2567
+ | Name | Type | Required | Description |
2568
+ | ---- | ---- | -------- | ----------- |
2569
+ | `name` | string | | Optional skill name (e.g. "upg-trace"). If omitted, audits every canonical skill. |
2570
+
2571
+ **Returns:**
2572
+
2573
+ `{ skills: SkillAuditRecord[] }`
2574
+