@unified-product-graph/mcp-server 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,6 +63,7 @@ async function hashFile(filePath) {
63
63
  }
64
64
 
65
65
  // ../upg-spec/dist/index.js
66
+ import { createHash as createHash2 } from "crypto";
66
67
  var UPG_DOMAINS = [
67
68
  {
68
69
  id: "strategy",
@@ -12115,26 +12116,6 @@ var UPG_FRAMEWORKS = [
12115
12116
  "label": "Items to score",
12116
12117
  "entityTypeId": "feature",
12117
12118
  "description": "Features, opportunities, or solutions being evaluated"
12118
- },
12119
- {
12120
- "label": "Reach",
12121
- "entityTypeId": "feature",
12122
- "description": "How many users does this affect per quarter?"
12123
- },
12124
- {
12125
- "label": "Impact",
12126
- "entityTypeId": "feature",
12127
- "description": "How much does this move the target outcome?"
12128
- },
12129
- {
12130
- "label": "Confidence",
12131
- "entityTypeId": "feature",
12132
- "description": "How sure are we about these estimates?"
12133
- },
12134
- {
12135
- "label": "Effort",
12136
- "entityTypeId": "feature",
12137
- "description": "How many person-months to build?"
12138
12119
  }
12139
12120
  ],
12140
12121
  "data": {
@@ -12148,31 +12129,35 @@ var UPG_FRAMEWORKS = [
12148
12129
  "feature": [
12149
12130
  {
12150
12131
  "property": "reach",
12151
- "type": "number",
12132
+ "type": "assessment",
12133
+ "scale_id": "reach_5",
12152
12134
  "required": true,
12153
12135
  "label": "Reach",
12154
12136
  "description": "How many users will this impact per quarter?"
12155
12137
  },
12156
12138
  {
12157
12139
  "property": "impact",
12158
- "type": "number",
12140
+ "type": "assessment",
12141
+ "scale_id": "impact_5",
12159
12142
  "required": true,
12160
12143
  "label": "Impact",
12161
- "description": "How much will this impact each user? (1=minimal, 2=low, 3=medium, 4=high, 5=massive)"
12144
+ "description": "How much will this impact each user, on the impact scale?"
12162
12145
  },
12163
12146
  {
12164
12147
  "property": "confidence",
12165
- "type": "number",
12148
+ "type": "assessment",
12149
+ "scale_id": "confidence_5",
12166
12150
  "required": true,
12167
12151
  "label": "Confidence",
12168
- "description": "How confident are you in the estimates? (0.5=low, 0.8=medium, 1.0=high)"
12152
+ "description": "How confident are you in the reach, impact, and effort estimates?"
12169
12153
  },
12170
12154
  {
12171
12155
  "property": "effort",
12172
- "type": "number",
12156
+ "type": "assessment",
12157
+ "scale_id": "effort_5",
12173
12158
  "required": true,
12174
12159
  "label": "Effort",
12175
- "description": "Person-months of effort required"
12160
+ "description": "How much work is required to build and ship this, on the effort scale?"
12176
12161
  }
12177
12162
  ]
12178
12163
  },
@@ -12366,7 +12351,7 @@ var UPG_FRAMEWORKS = [
12366
12351
  {
12367
12352
  "label": "Performance",
12368
12353
  "entityTypeId": "feature",
12369
- "description": "More is better, with linear satisfaction increase"
12354
+ "description": "More is better: linear satisfaction increase"
12370
12355
  },
12371
12356
  {
12372
12357
  "label": "Delighters",
@@ -12615,24 +12600,9 @@ var UPG_FRAMEWORKS = [
12615
12600
  ],
12616
12601
  "slots": [
12617
12602
  {
12618
- "label": "Must Have",
12619
- "entityTypeId": "feature",
12620
- "description": "Non-negotiable requirements for this release"
12621
- },
12622
- {
12623
- "label": "Should Have",
12603
+ "label": "Requirements to categorise",
12624
12604
  "entityTypeId": "feature",
12625
- "description": "Important but not critical; a workaround exists"
12626
- },
12627
- {
12628
- "label": "Could Have",
12629
- "entityTypeId": "feature",
12630
- "description": "Nice to have; include if time permits"
12631
- },
12632
- {
12633
- "label": "Won't Have",
12634
- "entityTypeId": "feature",
12635
- "description": "Explicitly out of scope for now"
12605
+ "description": "Features or requirements sorted into the four MoSCoW buckets"
12636
12606
  }
12637
12607
  ],
12638
12608
  "data": {
@@ -12642,7 +12612,23 @@ var UPG_FRAMEWORKS = [
12642
12612
  "role": "scored_item"
12643
12613
  }
12644
12614
  ],
12645
- "required_properties": {},
12615
+ "required_properties": {
12616
+ "feature": [
12617
+ {
12618
+ "property": "moscow",
12619
+ "type": "enum",
12620
+ "required": true,
12621
+ "label": "MoSCoW priority",
12622
+ "description": "Which scope bucket this requirement falls into for the current release",
12623
+ "enum_values": [
12624
+ "must",
12625
+ "should",
12626
+ "could",
12627
+ "wont"
12628
+ ]
12629
+ }
12630
+ ]
12631
+ },
12646
12632
  "computed_properties": [
12647
12633
  {
12648
12634
  "property": "must_have_ratio",
@@ -12662,28 +12648,19 @@ var UPG_FRAMEWORKS = [
12662
12648
  "columns": [
12663
12649
  {
12664
12650
  "property": "title",
12665
- "label": "Must Have",
12651
+ "label": "Requirement",
12666
12652
  "sortable": true
12667
12653
  },
12668
12654
  {
12669
- "property": "title",
12670
- "label": "Should Have",
12671
- "sortable": true
12672
- },
12673
- {
12674
- "property": "title",
12675
- "label": "Could Have",
12676
- "sortable": true
12677
- },
12678
- {
12679
- "property": "title",
12680
- "label": "Won't Have",
12681
- "sortable": true
12655
+ "property": "moscow",
12656
+ "label": "Priority",
12657
+ "sortable": true,
12658
+ "format": "badge"
12682
12659
  }
12683
12660
  ]
12684
12661
  },
12685
12662
  "sort_by": {
12686
- "property": "title",
12663
+ "property": "moscow",
12687
12664
  "direction": "asc"
12688
12665
  },
12689
12666
  "colour_by": "type",
@@ -13234,24 +13211,9 @@ var UPG_FRAMEWORKS = [
13234
13211
  ],
13235
13212
  "slots": [
13236
13213
  {
13237
- "label": "Deployment Frequency",
13238
- "entityTypeId": "metric",
13239
- "description": "How often you deploy to production"
13240
- },
13241
- {
13242
- "label": "Lead Time for Changes",
13243
- "entityTypeId": "metric",
13244
- "description": "Time from commit to production"
13245
- },
13246
- {
13247
- "label": "Change Failure Rate",
13214
+ "label": "Delivery metric",
13248
13215
  "entityTypeId": "metric",
13249
- "description": "Percentage of deployments causing failures"
13250
- },
13251
- {
13252
- "label": "Time to Restore",
13253
- "entityTypeId": "metric",
13254
- "description": "How long to recover from a failure"
13216
+ "description": "One of the four DORA software-delivery performance metrics"
13255
13217
  }
13256
13218
  ],
13257
13219
  "data": {
@@ -13261,7 +13223,36 @@ var UPG_FRAMEWORKS = [
13261
13223
  "role": "item"
13262
13224
  }
13263
13225
  ],
13264
- "required_properties": {},
13226
+ "required_properties": {
13227
+ "metric": [
13228
+ {
13229
+ "property": "dora_metric",
13230
+ "type": "enum",
13231
+ "required": true,
13232
+ "label": "DORA metric",
13233
+ "description": "Which of the four DORA metrics this measures",
13234
+ "enum_values": [
13235
+ "deployment_frequency",
13236
+ "lead_time_for_changes",
13237
+ "change_failure_rate",
13238
+ "time_to_restore"
13239
+ ]
13240
+ },
13241
+ {
13242
+ "property": "performance_tier",
13243
+ "type": "enum",
13244
+ "required": false,
13245
+ "label": "Performance tier",
13246
+ "description": "Where this metric sits on the DORA elite-to-low benchmark",
13247
+ "enum_values": [
13248
+ "elite",
13249
+ "high",
13250
+ "medium",
13251
+ "low"
13252
+ ]
13253
+ }
13254
+ ]
13255
+ },
13265
13256
  "computed_properties": [
13266
13257
  {
13267
13258
  "property": "stability_index",
@@ -13417,29 +13408,9 @@ var UPG_FRAMEWORKS = [
13417
13408
  ],
13418
13409
  "slots": [
13419
13410
  {
13420
- "label": "Acquisition",
13421
- "entityTypeId": "metric",
13422
- "description": "How do users find you?"
13423
- },
13424
- {
13425
- "label": "Activation",
13426
- "entityTypeId": "metric",
13427
- "description": "Do users have a great first experience?"
13428
- },
13429
- {
13430
- "label": "Retention",
13431
- "entityTypeId": "metric",
13432
- "description": "Do users come back?"
13433
- },
13434
- {
13435
- "label": "Revenue",
13411
+ "label": "Lifecycle metric",
13436
13412
  "entityTypeId": "metric",
13437
- "description": "Can you monetise the behaviour?"
13438
- },
13439
- {
13440
- "label": "Referral",
13441
- "entityTypeId": "metric",
13442
- "description": "Do users tell others?"
13413
+ "description": "A metric tracking one stage of the customer lifecycle funnel"
13443
13414
  }
13444
13415
  ],
13445
13416
  "data": {
@@ -13449,7 +13420,24 @@ var UPG_FRAMEWORKS = [
13449
13420
  "role": "item"
13450
13421
  }
13451
13422
  ],
13452
- "required_properties": {},
13423
+ "required_properties": {
13424
+ "metric": [
13425
+ {
13426
+ "property": "lifecycle_stage",
13427
+ "type": "enum",
13428
+ "required": true,
13429
+ "label": "Lifecycle stage",
13430
+ "description": "Which AARRR funnel stage this metric measures",
13431
+ "enum_values": [
13432
+ "acquisition",
13433
+ "activation",
13434
+ "retention",
13435
+ "revenue",
13436
+ "referral"
13437
+ ]
13438
+ }
13439
+ ]
13440
+ },
13453
13441
  "computed_properties": [
13454
13442
  {
13455
13443
  "property": "conversion_rate",
@@ -13461,7 +13449,39 @@ var UPG_FRAMEWORKS = [
13461
13449
  ]
13462
13450
  },
13463
13451
  "structure": {
13464
- "pattern": "funnel"
13452
+ "pattern": "funnel",
13453
+ "stages": [
13454
+ {
13455
+ "id": "acquisition",
13456
+ "label": "Acquisition",
13457
+ "order": 0,
13458
+ "entity_type": "metric"
13459
+ },
13460
+ {
13461
+ "id": "activation",
13462
+ "label": "Activation",
13463
+ "order": 1,
13464
+ "entity_type": "metric"
13465
+ },
13466
+ {
13467
+ "id": "retention",
13468
+ "label": "Retention",
13469
+ "order": 2,
13470
+ "entity_type": "metric"
13471
+ },
13472
+ {
13473
+ "id": "revenue",
13474
+ "label": "Revenue",
13475
+ "order": 3,
13476
+ "entity_type": "metric"
13477
+ },
13478
+ {
13479
+ "id": "referral",
13480
+ "label": "Referral",
13481
+ "order": 4,
13482
+ "entity_type": "metric"
13483
+ }
13484
+ ]
13465
13485
  },
13466
13486
  "presentation": {
13467
13487
  "layout": {
@@ -13508,24 +13528,9 @@ var UPG_FRAMEWORKS = [
13508
13528
  ],
13509
13529
  "slots": [
13510
13530
  {
13511
- "label": "North Star",
13512
- "entityTypeId": "metric",
13513
- "description": "The single metric reflecting core value delivered"
13514
- },
13515
- {
13516
- "label": "Input Metric 1",
13517
- "entityTypeId": "metric",
13518
- "description": "A driver metric you can directly influence"
13519
- },
13520
- {
13521
- "label": "Input Metric 2",
13522
- "entityTypeId": "metric",
13523
- "description": "A driver metric you can directly influence"
13524
- },
13525
- {
13526
- "label": "Input Metric 3",
13531
+ "label": "Metric",
13527
13532
  "entityTypeId": "metric",
13528
- "description": "A driver metric you can directly influence"
13533
+ "description": "The North Star metric or one of its 3-5 input (driver) metrics"
13529
13534
  }
13530
13535
  ],
13531
13536
  "data": {
@@ -13535,7 +13540,28 @@ var UPG_FRAMEWORKS = [
13535
13540
  "role": "item"
13536
13541
  }
13537
13542
  ],
13538
- "required_properties": {},
13543
+ "required_properties": {
13544
+ "metric": [
13545
+ {
13546
+ "property": "metric_role",
13547
+ "type": "enum",
13548
+ "required": true,
13549
+ "label": "Metric role",
13550
+ "description": "Whether this is the single North Star or a driver that feeds it",
13551
+ "enum_values": [
13552
+ "north_star",
13553
+ "input"
13554
+ ]
13555
+ },
13556
+ {
13557
+ "property": "leverage",
13558
+ "type": "number",
13559
+ "required": false,
13560
+ "label": "Leverage",
13561
+ "description": "How strongly this input metric moves the North Star (input metrics only)"
13562
+ }
13563
+ ]
13564
+ },
13539
13565
  "computed_properties": [
13540
13566
  {
13541
13567
  "property": "nsm_impact",
@@ -13835,7 +13861,7 @@ var UPG_FRAMEWORKS = [
13835
13861
  ]
13836
13862
  },
13837
13863
  "education": {
13838
- "purpose": "Design the product itself as the primary growth driver (free tier, self-serve onboarding, in-product virality), reducing dependence on sales-led acquisition.",
13864
+ "purpose": "Design the product itself as the primary growth driver (free tier, self-serve onboarding, in-product virality) to reduce dependence on sales-led acquisition.",
13839
13865
  "core_question": "Can our product acquire, activate, and expand users without human intervention, and where in the loop do we still need sales?",
13840
13866
  "when_to_use": [
13841
13867
  "You are launching a new product, feature, or entering a new market",
@@ -14245,7 +14271,7 @@ var UPG_FRAMEWORKS = [
14245
14271
  {
14246
14272
  "label": "Team",
14247
14273
  "entityTypeId": "team",
14248
- "description": "Team conducting the health check: all members vote anonymously on each dimension"
14274
+ "description": "Team conducting the health check; all members vote anonymously on each dimension"
14249
14275
  },
14250
14276
  {
14251
14277
  "label": "Retrospective",
@@ -14334,7 +14360,7 @@ var UPG_FRAMEWORKS = [
14334
14360
  },
14335
14361
  "education": {
14336
14362
  "purpose": "Regularly assess team health across dimensions (psychological safety, autonomy, mission clarity, fun, speed, learning) to catch problems early and celebrate strengths.",
14337
- "core_question": "How is the team really doing? Where do they feel strong, where do they feel stuck, and what has changed since last check?",
14363
+ "core_question": "How is the team really doing: where do they feel strong, where do they feel stuck, and what has changed since last check?",
14338
14364
  "when_to_use": [
14339
14365
  "You need to improve team collaboration, clarity, or effectiveness",
14340
14366
  "Roles and responsibilities are unclear or causing friction",
@@ -14350,7 +14376,7 @@ var UPG_FRAMEWORKS = [
14350
14376
  "id": "raid-log",
14351
14377
  "name": "RAID Log",
14352
14378
  "version": "1.0.0",
14353
- "description": "A project management register tracking Risks, Assumptions, Issues, and Dependencies: the four categories most likely to derail a project if left unmanaged.",
14379
+ "description": "A project management register tracking Risks, Assumptions, Issues, and Dependencies, the four categories most likely to derail a project if left unmanaged.",
14354
14380
  "category": "program_mgmt",
14355
14381
  "origin": {
14356
14382
  "type": "practitioner",
@@ -22520,6 +22546,222 @@ var UPG_STRUCTURE_PATTERNS = [
22520
22546
  "quadrant",
22521
22547
  "flow"
22522
22548
  ];
22549
+ var UPG_CANONICAL_FORMAT_VERSION = "1.0.0";
22550
+ var INTEGRITY_HASH_PRIMITIVE = "sha256";
22551
+ var INTEGRITY_DIGEST_HEX = 32;
22552
+ var INTEGRITY_ALGORITHM = "sha256-128";
22553
+ function canonicalizeOpen(value) {
22554
+ if (Array.isArray(value)) return value.map(canonicalizeOpen);
22555
+ if (value !== null && typeof value === "object") {
22556
+ const out = {};
22557
+ for (const key of Object.keys(value).sort(byCodeUnit)) {
22558
+ out[key] = canonicalizeOpen(value[key]);
22559
+ }
22560
+ return out;
22561
+ }
22562
+ return value;
22563
+ }
22564
+ function byCodeUnit(a, b) {
22565
+ return a < b ? -1 : a > b ? 1 : 0;
22566
+ }
22567
+ function isEmpty(value) {
22568
+ if (value === null || value === void 0) return true;
22569
+ if (typeof value === "string") return value.length === 0;
22570
+ if (Array.isArray(value)) return value.length === 0;
22571
+ if (typeof value === "object") return Object.keys(value).length === 0;
22572
+ return false;
22573
+ }
22574
+ function orderedObject(source, keyOrder, opts = {}) {
22575
+ const force = new Set(opts.forceKeys ?? []);
22576
+ const open = new Set(opts.openKeys ?? []);
22577
+ const out = {};
22578
+ const emit = (key) => {
22579
+ if (!(key in source)) return;
22580
+ const raw = source[key];
22581
+ if (!force.has(key) && isEmpty(raw)) return;
22582
+ out[key] = open.has(key) ? canonicalizeOpen(raw) : raw;
22583
+ };
22584
+ for (const key of keyOrder) emit(key);
22585
+ for (const key of Object.keys(source).sort(byCodeUnit)) {
22586
+ if (!keyOrder.includes(key)) emit(key);
22587
+ }
22588
+ return out;
22589
+ }
22590
+ function repairNodeDrift(node) {
22591
+ const out = { ...node };
22592
+ if (typeof out.properties === "string") {
22593
+ out.properties = parseDriftString(out.properties, "object", `node ${node.id}.properties`);
22594
+ }
22595
+ if (typeof out.tags === "string") {
22596
+ out.tags = parseDriftString(out.tags, "array", `node ${node.id}.tags`);
22597
+ }
22598
+ return out;
22599
+ }
22600
+ function parseDriftString(raw, expect, where) {
22601
+ let parsed;
22602
+ try {
22603
+ parsed = JSON.parse(raw);
22604
+ } catch {
22605
+ throw new Error(`[upg fmt] ${where} is a string that is not valid JSON: ${truncate(raw)}`);
22606
+ }
22607
+ const ok = expect === "array" ? Array.isArray(parsed) : parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
22608
+ if (!ok) throw new Error(`[upg fmt] ${where} is a string that does not parse to a JSON ${expect}: ${truncate(raw)}`);
22609
+ return parsed;
22610
+ }
22611
+ function truncate(s) {
22612
+ return s.length > 60 ? s.slice(0, 57) + "..." : s;
22613
+ }
22614
+ function deriveSummary(description) {
22615
+ if (!description) return void 0;
22616
+ const firstLine = description.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
22617
+ if (!firstLine) return void 0;
22618
+ return firstLine.length > 200 ? firstLine.slice(0, 197) + "..." : firstLine;
22619
+ }
22620
+ function sortNodes(nodes) {
22621
+ return [...nodes].sort(
22622
+ (a, b) => byCodeUnit(a.type ?? "", b.type ?? "") || byCodeUnit(a.slug ?? a.title ?? "", b.slug ?? b.title ?? "") || byCodeUnit(a.id ?? "", b.id ?? "")
22623
+ );
22624
+ }
22625
+ function sortEdges(edges) {
22626
+ return [...edges].sort(
22627
+ (a, b) => byCodeUnit(a.source ?? "", b.source ?? "") || byCodeUnit(a.target ?? "", b.target ?? "") || byCodeUnit(a.type ?? "", b.type ?? "") || byCodeUnit(a.id ?? "", b.id ?? "")
22628
+ );
22629
+ }
22630
+ var NODE_KEY_ORDER = [
22631
+ "id",
22632
+ "type",
22633
+ "title",
22634
+ "slug",
22635
+ "aliases",
22636
+ "description",
22637
+ "tags",
22638
+ "status",
22639
+ "lifecycle_status",
22640
+ "source_id",
22641
+ "source_type",
22642
+ "mapping_confidence",
22643
+ "external_tool",
22644
+ "external_ref",
22645
+ "external_id",
22646
+ "sort_order",
22647
+ "properties"
22648
+ ];
22649
+ var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence"];
22650
+ var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence"];
22651
+ var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
22652
+ function canonicalNode(node) {
22653
+ const repaired = repairNodeDrift(node);
22654
+ if (Array.isArray(repaired.tags)) {
22655
+ repaired.tags = [...new Set(repaired.tags)].sort(byCodeUnit);
22656
+ }
22657
+ return orderedObject(repaired, NODE_KEY_ORDER, { forceKeys: ["id", "type", "title"], openKeys: ["properties"] });
22658
+ }
22659
+ function canonicalEdge(edge) {
22660
+ return orderedObject(edge, EDGE_KEY_ORDER, {
22661
+ forceKeys: ["id", "source", "target", "type"]
22662
+ });
22663
+ }
22664
+ function canonicalCrossEdge(edge) {
22665
+ return orderedObject(edge, CROSS_EDGE_KEY_ORDER, {
22666
+ forceKeys: ["id", "source", "target", "type"]
22667
+ });
22668
+ }
22669
+ function canonicalProduct(product) {
22670
+ return orderedObject(product, PRODUCT_KEY_ORDER, { forceKeys: ["id", "title"], openKeys: ["properties"] });
22671
+ }
22672
+ function singleBody(doc) {
22673
+ return {
22674
+ product: canonicalProduct(doc.product),
22675
+ nodes: sortNodes(doc.nodes ?? []).map(canonicalNode),
22676
+ edges: sortEdges(doc.edges ?? []).map(canonicalEdge)
22677
+ };
22678
+ }
22679
+ function portfolioBody(doc) {
22680
+ const products = [...doc.products ?? []].sort((a, b) => byCodeUnit(a.id ?? "", b.id ?? "")).map((p) => {
22681
+ const { nodes, edges, ...rest } = p;
22682
+ return {
22683
+ ...canonicalProduct(rest),
22684
+ nodes: sortNodes(nodes ?? []).map(canonicalNode),
22685
+ edges: sortEdges(edges ?? []).map(canonicalEdge)
22686
+ };
22687
+ });
22688
+ return {
22689
+ organization: orderedObject(doc.organization, ["id", "title", "description", "logo_url", "industry"], { forceKeys: ["id", "title"] }),
22690
+ product_areas: [...doc.product_areas ?? []].sort((a, b) => byCodeUnit(a.id ?? "", b.id ?? "")).map((a) => orderedObject(a, ["id", "title", "description", "parent_area_id", "strategic_priority", "products"], { forceKeys: ["id", "title"] })),
22691
+ portfolios: [...doc.portfolios ?? []].sort((a, b) => byCodeUnit(a.id ?? "", b.id ?? "")).map((p) => orderedObject(p, ["id", "title", "description", "parent_portfolio_id", "hierarchy_model", "products"], { forceKeys: ["id", "title"] })),
22692
+ products,
22693
+ cross_edges: sortEdges(doc.cross_edges ?? []).map(canonicalCrossEdge)
22694
+ };
22695
+ }
22696
+ function computeBodyChecksum(doc) {
22697
+ const body = isPortfolio(doc) ? portfolioBody(doc) : singleBody(doc);
22698
+ const content = JSON.stringify(body);
22699
+ return createHash2(INTEGRITY_HASH_PRIMITIVE).update(content).digest("hex").slice(0, INTEGRITY_DIGEST_HEX);
22700
+ }
22701
+ function isPortfolio(doc) {
22702
+ return doc.type === "portfolio" || "cross_edges" in doc;
22703
+ }
22704
+ function serializeCanonical(doc, opts = {}) {
22705
+ if (isPortfolio(doc)) return serializePortfolioWithHeader(doc, opts);
22706
+ return serializeSingleWithHeader(doc, opts);
22707
+ }
22708
+ function buildProvenance(doc, opts) {
22709
+ const source = opts.source ?? doc.source ?? { tool: "unknown" };
22710
+ return orderedObject(
22711
+ {
22712
+ tool: source.tool,
22713
+ tool_version: source.tool_version,
22714
+ workspace_id: source.workspace_id,
22715
+ exported_at: opts.exportedAt ?? doc.exported_at
22716
+ },
22717
+ ["tool", "tool_version", "workspace_id", "exported_at"],
22718
+ { forceKeys: ["tool"] }
22719
+ );
22720
+ }
22721
+ function serializeSingleWithHeader(doc, opts) {
22722
+ const body = singleBody(doc);
22723
+ const product = doc.product;
22724
+ const header = {
22725
+ format_version: UPG_CANONICAL_FORMAT_VERSION,
22726
+ spec_version: doc.upg_version,
22727
+ product: orderedObject(
22728
+ { id: product.id, title: product.title, stage: product.stage },
22729
+ ["id", "title", "stage"],
22730
+ { forceKeys: ["id", "title"] }
22731
+ )
22732
+ };
22733
+ const summary = deriveSummary(product.description);
22734
+ if (summary) header.summary = summary;
22735
+ header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
22736
+ header.provenance = buildProvenance(doc, opts);
22737
+ header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
22738
+ return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
22739
+ }
22740
+ function serializePortfolioWithHeader(doc, opts) {
22741
+ const body = portfolioBody(doc);
22742
+ const org = doc.organization;
22743
+ const header = {
22744
+ format_version: UPG_CANONICAL_FORMAT_VERSION,
22745
+ spec_version: doc.upg_version,
22746
+ kind: "portfolio",
22747
+ organization: orderedObject(
22748
+ { id: org.id, title: org.title },
22749
+ ["id", "title"],
22750
+ { forceKeys: ["id", "title"] }
22751
+ )
22752
+ };
22753
+ const summary = deriveSummary(org.description);
22754
+ if (summary) header.summary = summary;
22755
+ header.counts = {
22756
+ products: doc.products?.length ?? 0,
22757
+ product_areas: doc.product_areas?.length ?? 0,
22758
+ portfolios: doc.portfolios?.length ?? 0,
22759
+ cross_edges: doc.cross_edges?.length ?? 0
22760
+ };
22761
+ header.provenance = buildProvenance(doc, opts);
22762
+ header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
22763
+ return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
22764
+ }
22523
22765
  var UPG_VERSION = "0.7.3";
22524
22766
  var MARKDOWN_FORMAT_VERSION = "0.1";
22525
22767
  var UPG_TYPES = getTypes();
@@ -22769,6 +23011,62 @@ var getGraphDigest = (args, ctx) => {
22769
23011
  }
22770
23012
  return text(JSON.stringify({ ...digest, lens: sessionContext.lens, lens_digest: lensDigest, _hash: currentHash }, null, 2));
22771
23013
  };
23014
+ var YOUNG_GRAPH_THRESHOLD = 8;
23015
+ var start = (_args, ctx) => {
23016
+ const { store } = ctx;
23017
+ const allNodes = store.getAllNodes();
23018
+ const realNodes = allNodes.filter((n) => n.type !== "product");
23019
+ const nodeCount = realNodes.length;
23020
+ const product = store.getProduct();
23021
+ const presentTypes = new Set(allNodes.map((n) => n.type));
23022
+ const productInfo = product ? { title: product.title, stage: product.stage ?? null } : null;
23023
+ const state = nodeCount === 0 ? "empty" : nodeCount < YOUNG_GRAPH_THRESHOLD ? "young" : "established";
23024
+ if (state === "established") {
23025
+ return text(
23026
+ JSON.stringify(
23027
+ {
23028
+ graph_state: state,
23029
+ product: productInfo,
23030
+ node_count: nodeCount,
23031
+ recommendation: `Graph is established (${nodeCount} entities). Use plan for the missing-entities backlog, inspect for issues, and get_graph_digest for health.`,
23032
+ next_tools: ["plan", "inspect", "get_graph_digest"]
23033
+ },
23034
+ null,
23035
+ 2
23036
+ )
23037
+ );
23038
+ }
23039
+ const canonicalPlaybooks = UPG_PLAYBOOKS.filter((p) => p.is_canonical === true);
23040
+ const recommend = canonicalPlaybooks.find(
23041
+ (p) => p.target_anchor_entity ? !presentTypes.has(p.target_anchor_entity) : true
23042
+ ) ?? canonicalPlaybooks[0];
23043
+ const response = {
23044
+ graph_state: state,
23045
+ product: productInfo,
23046
+ node_count: nodeCount
23047
+ };
23048
+ if (recommend) {
23049
+ const anchor = recommend.target_anchor_entity;
23050
+ response.recommended_playbook = {
23051
+ id: recommend.id,
23052
+ name: recommend.name,
23053
+ region: recommend.region,
23054
+ target_anchor_entity: anchor ?? null,
23055
+ description: recommend.description
23056
+ };
23057
+ if (anchor) {
23058
+ response.first_action = {
23059
+ tool: "create_node",
23060
+ args: { type: anchor, title: `<your first ${anchor}>` }
23061
+ };
23062
+ }
23063
+ response.recommendation = state === "empty" ? `Your graph is empty. Begin with the "${recommend.name}" playbook${anchor ? `: create your first ${anchor}` : ""}. Open the full sequence with get_playbook("${recommend.id}").` : `You have ${nodeCount} ${nodeCount === 1 ? "entity" : "entities"}. Continue with the "${recommend.name}" playbook${anchor ? `: add a ${anchor}` : ""}. Use plan for the full missing-entities backlog.`;
23064
+ } else {
23065
+ response.recommendation = "No canonical playbooks available. Use plan to see the missing-entities backlog.";
23066
+ }
23067
+ response.next_tools = state === "empty" ? ["get_playbook", "create_node", "list_playbooks"] : ["plan", "get_playbook", "get_graph_digest"];
23068
+ return text(JSON.stringify(response, null, 2));
23069
+ };
22772
23070
  var getSessionContext = (_args, ctx) => {
22773
23071
  const { sessionContext } = ctx;
22774
23072
  const recommendationsToAvoid = Array.from(
@@ -23496,6 +23794,21 @@ function buildFirstUseHints(canonicalType) {
23496
23794
  if (edgesOut.length > 0) hints.canonical_edges_out = edgesOut;
23497
23795
  return hints;
23498
23796
  }
23797
+ function buildOrphanWarning(canonicalType) {
23798
+ let schema;
23799
+ try {
23800
+ schema = buildEntitySchema(canonicalType);
23801
+ } catch {
23802
+ return void 0;
23803
+ }
23804
+ const guide = schema.domain_guide;
23805
+ if (!guide || guide.position_in_sequence <= 0) return void 0;
23806
+ const anchor = guide.anchor_entity;
23807
+ if (!anchor || anchor === canonicalType) return void 0;
23808
+ const edge = resolveContainmentEdge(anchor, canonicalType);
23809
+ const via = edge ? ` (canonical edge: ${edge})` : "";
23810
+ return `Orphan: created ${canonicalType} with no parent. ${canonicalType} typically attaches under ${anchor}${via}. Pass parent_id on create, or wire it later with create_edge.`;
23811
+ }
23499
23812
  var createNode = async (args, ctx) => {
23500
23813
  const { store } = ctx;
23501
23814
  if (!args.type) return textError(`Missing required parameter: type`);
@@ -23562,6 +23875,10 @@ var createNode = async (args, ctx) => {
23562
23875
  const aggregatedWarnings = [];
23563
23876
  if (warning) aggregatedWarnings.push(warning);
23564
23877
  if (lengthWarnings.length > 0) aggregatedWarnings.push(...lengthWarnings);
23878
+ if (!args.parent_id && !result.edge) {
23879
+ const orphanWarning = buildOrphanWarning(result.node.type);
23880
+ if (orphanWarning) aggregatedWarnings.push(orphanWarning);
23881
+ }
23565
23882
  const libWarning = result.warning;
23566
23883
  const combinedWarning = libWarning ? aggregatedWarnings.length > 0 ? `${libWarning} | ${aggregatedWarnings.join(" | ")}` : libWarning : aggregatedWarnings.length > 0 ? aggregatedWarnings.join(" | ") : void 0;
23567
23884
  if (unknown_properties.length > 0 || combinedWarning || hints) {
@@ -26511,6 +26828,14 @@ var TOOL_DEFINITIONS = [
26511
26828
  }
26512
26829
  }
26513
26830
  },
26831
+ {
26832
+ name: "start",
26833
+ description: 'Zero-state on-ramp: "there is nothing here yet, where do I begin?". Reads the live graph and, for an empty or barely-started graph, recommends the first canonical playbook (from UPG_PLAYBOOKS) plus the exact create_node call for its anchor entity. Established graphs are routed to plan / inspect / get_graph_digest instead. Takes no arguments.',
26834
+ inputSchema: {
26835
+ type: "object",
26836
+ properties: {}
26837
+ }
26838
+ },
26514
26839
  {
26515
26840
  name: "list_nodes",
26516
26841
  description: "List entities with filtering, edge inclusion, count-only mode, and pagination. For graph-wide edge enumeration, prefer `export_edges` (flat) or `query` (traversal). `list_nodes(include_edges:true)` is for entity-scoped reads.",
@@ -27810,6 +28135,7 @@ var TOOL_DEFINITIONS = [
27810
28135
  var HANDLERS = {
27811
28136
  get_product_context: getProductContext,
27812
28137
  get_graph_digest: getGraphDigest,
28138
+ start,
27813
28139
  list_nodes: listNodes,
27814
28140
  get_node: getNode,
27815
28141
  get_nodes: getNodes,
@@ -28112,11 +28438,7 @@ async function runMcpServer() {
28112
28438
  edges: []
28113
28439
  };
28114
28440
  await fs3.mkdir(path6.dirname(defaultFile), { recursive: true });
28115
- await fs3.writeFile(
28116
- defaultFile,
28117
- JSON.stringify(blank, null, 2) + "\n",
28118
- "utf-8"
28119
- );
28441
+ await fs3.writeFile(defaultFile, serializeCanonical(blank), "utf-8");
28120
28442
  process.stderr.write(`Created new UPG file: ${defaultFile}
28121
28443
  `);
28122
28444
  resolvedPath = defaultFile;
@@ -28140,11 +28462,7 @@ async function runMcpServer() {
28140
28462
  edges: []
28141
28463
  };
28142
28464
  await fs3.mkdir(path6.dirname(resolvedPath), { recursive: true });
28143
- await fs3.writeFile(
28144
- resolvedPath,
28145
- JSON.stringify(blank, null, 2) + "\n",
28146
- "utf-8"
28147
- );
28465
+ await fs3.writeFile(resolvedPath, serializeCanonical(blank), "utf-8");
28148
28466
  process.stderr.write(`Created new UPG file: ${resolvedPath}
28149
28467
  `);
28150
28468
  }