@unified-product-graph/mcp-server 0.8.1 → 0.8.2

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 (61) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +1 -1
  3. package/TOOLS.md +19 -13
  4. package/dist/index.js +1286 -285
  5. package/dist/index.js.map +1 -1
  6. package/dist/tools-manifest.json +94 -75
  7. package/package.json +1 -1
  8. package/scripts/claudemd-snippet.md +7 -7
  9. package/scripts/install-skills.sh +2 -2
  10. package/skills/upg/SKILL.md +41 -41
  11. package/skills/{upg-gaps → upg-check-gaps}/SKILL.md +40 -43
  12. package/skills/{upg-schema-health → upg-check-schema}/SKILL.md +7 -7
  13. package/skills/{upg-schema-evolve → upg-check-schema-coverage}/SKILL.md +12 -12
  14. package/skills/{upg-schema-edges → upg-check-schema-edges}/SKILL.md +3 -3
  15. package/skills/{upg-schema-consolidate → upg-check-schema-merge}/SKILL.md +5 -5
  16. package/skills/upg-context/SKILL.md +96 -72
  17. package/skills/upg-context-intelligence/SKILL.md +23 -27
  18. package/skills/upg-design-system/SKILL.md +21 -26
  19. package/skills/{upg-verify → upg-find-untracked}/SKILL.md +7 -12
  20. package/skills/{upg-rollback → upg-fix-rollback}/SKILL.md +6 -12
  21. package/skills/{upg-migrate → upg-fix-types}/SKILL.md +5 -9
  22. package/skills/upg-link/SKILL.md +125 -0
  23. package/skills/{upg-discover → upg-new-discovery}/SKILL.md +42 -58
  24. package/skills/{upg-capture → upg-new-from-session}/SKILL.md +13 -15
  25. package/skills/{upg-template → upg-new-from-template}/SKILL.md +8 -12
  26. package/skills/{upg-init → upg-new-graph}/SKILL.md +50 -82
  27. package/skills/{upg-hypothesis → upg-new-hypothesis}/SKILL.md +27 -36
  28. package/skills/{upg-launch → upg-new-launch}/SKILL-DETAIL.md +36 -92
  29. package/skills/{upg-launch → upg-new-launch}/SKILL.md +8 -18
  30. package/skills/{upg-okr → upg-new-okr}/SKILL-DETAIL.md +28 -46
  31. package/skills/{upg-okr → upg-new-okr}/SKILL.md +3 -3
  32. package/skills/{upg-persona → upg-new-persona}/SKILL.md +35 -67
  33. package/skills/{upg-research → upg-new-research}/SKILL.md +25 -33
  34. package/skills/{upg-schema-update → upg-new-schema-type}/SKILL.md +2 -2
  35. package/skills/{upg-strategy → upg-new-strategy}/SKILL.md +21 -27
  36. package/skills/upg-prioritise/SKILL.md +4 -4
  37. package/skills/upg-reflect/SKILL.md +7 -7
  38. package/skills/{upg-feedback → upg-send-feedback}/SKILL.md +30 -51
  39. package/skills/{upg-diff → upg-show-diff}/SKILL.md +6 -12
  40. package/skills/{upg-inspect → upg-show-entity}/SKILL.md +7 -9
  41. package/skills/{upg-impact → upg-show-impact}/SKILL.md +11 -15
  42. package/skills/{upg-journey → upg-show-journey}/SKILL.md +31 -32
  43. package/skills/{upg-analytics → upg-show-metrics}/SKILL.md +9 -12
  44. package/skills/{upg-schema-changelog → upg-show-schema-changelog}/SKILL.md +5 -5
  45. package/skills/{upg-status → upg-show-status}/SKILL.md +39 -40
  46. package/skills/{upg-tree → upg-show-tree}/SKILL.md +15 -15
  47. package/skills/{upg-export → upg-sync-export}/SKILL.md +10 -13
  48. package/skills/{upg-import → upg-sync-import}/SKILL.md +7 -13
  49. package/skills/{upg-pull → upg-sync-pull}/SKILL-DETAIL.md +13 -17
  50. package/skills/{upg-pull → upg-sync-pull}/SKILL.md +3 -3
  51. package/skills/{upg-push → upg-sync-push}/SKILL-DETAIL.md +4 -10
  52. package/skills/{upg-push → upg-sync-push}/SKILL.md +4 -4
  53. package/skills/{upg-snapshot → upg-sync-snapshot}/SKILL.md +2 -6
  54. package/skills/upg-trace/SKILL.md +7 -7
  55. package/skills/{upg-workspace → upg-use-workspace}/SKILL.md +8 -14
  56. package/skills/{upg-run → upg-walk-playbook}/SKILL.md +10 -10
  57. package/skills/upg-walk-region/SKILL-DETAIL.md +320 -0
  58. package/skills/upg-walk-region/SKILL.md +89 -0
  59. package/skills/upg-connect/SKILL.md +0 -167
  60. package/skills/upg-explore/SKILL-DETAIL.md +0 -481
  61. package/skills/upg-explore/SKILL.md +0 -297
package/dist/index.js CHANGED
@@ -20,50 +20,10 @@ import {
20
20
  // src/lib/server-context.ts
21
21
  import * as fsp from "fs/promises";
22
22
  import * as path from "path";
23
- import { createHash } from "crypto";
24
- function text(s) {
25
- return { content: [{ type: "text", text: s }] };
26
- }
27
- function textError(s) {
28
- return { content: [{ type: "text", text: s }], isError: true };
29
- }
30
- function createSessionContext() {
31
- return {
32
- lens: "product",
33
- skills_invoked: [],
34
- recommendations_given: [],
35
- focus_area: null,
36
- custom: {}
37
- };
38
- }
39
- function createQueryCache() {
40
- return { entries: /* @__PURE__ */ new Map(), counter: 0 };
41
- }
42
- function syncFilePath(upgPath) {
43
- const dir = path.dirname(upgPath);
44
- const base = path.basename(upgPath, ".upg");
45
- return path.join(dir, `${base}.upg-sync`);
46
- }
47
- async function readSyncState(upgPath) {
48
- const p = syncFilePath(upgPath);
49
- try {
50
- const raw = await fsp.readFile(p, "utf-8");
51
- return JSON.parse(raw);
52
- } catch {
53
- return null;
54
- }
55
- }
56
- async function writeSyncState(upgPath, state) {
57
- const p = syncFilePath(upgPath);
58
- await fsp.writeFile(p, JSON.stringify(state, null, 2) + "\n", "utf-8");
59
- }
60
- async function hashFile(filePath) {
61
- const content = await fsp.readFile(filePath, "utf-8");
62
- return createHash("sha256").update(content).digest("hex");
63
- }
23
+ import { createHash as createHash2 } from "crypto";
64
24
 
65
25
  // ../upg-spec/dist/index.js
66
- import { createHash as createHash2 } from "crypto";
26
+ import { createHash } from "crypto";
67
27
  var UPG_DOMAINS = [
68
28
  {
69
29
  id: "strategy",
@@ -993,7 +953,7 @@ var UPG_EDGE_CATALOG = {
993
953
  // product → persona is the most fundamental relationship in the
994
954
  // user domain ("who is this product for?") and was the only way to
995
955
  // anchor a fresh persona to its product before this edge existed. Without
996
- // it, the natural agent path through `/upg-persona` produced an orphan
956
+ // it, the natural agent path through `/upg-new-persona` produced an orphan
997
957
  // persona only attached laterally via `ideal_customer_profile_maps_to_persona`
998
958
  // or `positioning_resonates_with_persona`. Semantic, not hierarchy: a
999
959
  // product doesn't "contain" personas; it targets them.
@@ -2208,6 +2168,13 @@ var UPG_EDGE_CATALOG = {
2208
2168
  // release strategy to the concrete deployments it governs.
2209
2169
  incident_affects_feature: { forward_verb: "affects", reverse_verb: "affected_by", classification: "cross-domain", source_type: "incident", target_type: "feature" },
2210
2170
  release_strategy_used_by_deployment: { forward_verb: "used_by", reverse_verb: "uses", classification: "cross-domain", source_type: "release_strategy", target_type: "deployment" },
2171
+ // v0.8.2 (UPG-615): ITIL/ITSM incident management explicitly links an incident
2172
+ // to the customer-facing support tickets/cases it spawns — when a service
2173
+ // breaks, customers raise tickets; the incident is the cause, the ticket the
2174
+ // effect. Closes the otherwise-unmediated "Customer Support" island in
2175
+ // `playbook:operations-quality`, binding `support_ticket` to the incident/ops
2176
+ // spine. Source: ITIL v4 incident management; DORA/SRE customer-facing impact.
2177
+ incident_generates_support_ticket: { forward_verb: "generates", reverse_verb: "generated_by", classification: "cross-domain", source_type: "incident", target_type: "support_ticket" },
2211
2178
  // Cluster C: User Research linkage matrix.
2212
2179
  // research_study → {participant, research_question, survey_response,
2213
2180
  // interview_guide} containment edges already exist (49/49 in Wave 4).
@@ -11261,6 +11228,9 @@ var UPG_FRAMEWORKS = [
11261
11228
  },
11262
11229
  {
11263
11230
  "id": "value-proposition-canvas",
11231
+ "approach_ids": [
11232
+ "trace"
11233
+ ],
11264
11234
  "name": "Value Proposition Canvas",
11265
11235
  "version": "1.0.0",
11266
11236
  "description": "Map customer jobs, pains, and gains on one side, then align product features, pain relievers, and gain creators on the other to achieve product-market fit.",
@@ -11365,6 +11335,9 @@ var UPG_FRAMEWORKS = [
11365
11335
  },
11366
11336
  {
11367
11337
  "id": "persona-canvas",
11338
+ "approach_ids": [
11339
+ "trace"
11340
+ ],
11368
11341
  "name": "Persona Canvas",
11369
11342
  "version": "1.0.0",
11370
11343
  "description": "Demographics, goals, frustrations, JTBD: a structured template for creating research-backed personas.",
@@ -11477,6 +11450,9 @@ var UPG_FRAMEWORKS = [
11477
11450
  },
11478
11451
  {
11479
11452
  "id": "empathy-map",
11453
+ "approach_ids": [
11454
+ "trace"
11455
+ ],
11480
11456
  "name": "Empathy Map",
11481
11457
  "version": "1.0.0",
11482
11458
  "description": "Visualise what a user says, thinks, does, and feels to build deeper empathy and uncover hidden needs.",
@@ -11637,6 +11613,7 @@ var UPG_FRAMEWORKS = [
11637
11613
  "property": "evolution_stage",
11638
11614
  "type": "enum",
11639
11615
  "required": true,
11616
+ "scope": "framework",
11640
11617
  "label": "Evolution Stage",
11641
11618
  "description": "Where this component sits on the evolution axis",
11642
11619
  "enum_values": [
@@ -11650,6 +11627,7 @@ var UPG_FRAMEWORKS = [
11650
11627
  "property": "visibility",
11651
11628
  "type": "number",
11652
11629
  "required": true,
11630
+ "scope": "framework",
11653
11631
  "label": "Visibility",
11654
11632
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11655
11633
  }
@@ -11659,6 +11637,7 @@ var UPG_FRAMEWORKS = [
11659
11637
  "property": "evolution_stage",
11660
11638
  "type": "enum",
11661
11639
  "required": true,
11640
+ "scope": "framework",
11662
11641
  "label": "Evolution Stage",
11663
11642
  "description": "Where this component sits on the evolution axis",
11664
11643
  "enum_values": [
@@ -11672,6 +11651,7 @@ var UPG_FRAMEWORKS = [
11672
11651
  "property": "visibility",
11673
11652
  "type": "number",
11674
11653
  "required": true,
11654
+ "scope": "framework",
11675
11655
  "label": "Visibility",
11676
11656
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11677
11657
  }
@@ -11681,6 +11661,7 @@ var UPG_FRAMEWORKS = [
11681
11661
  "property": "evolution_stage",
11682
11662
  "type": "enum",
11683
11663
  "required": true,
11664
+ "scope": "framework",
11684
11665
  "label": "Evolution Stage",
11685
11666
  "description": "Where this component sits on the evolution axis",
11686
11667
  "enum_values": [
@@ -11694,6 +11675,7 @@ var UPG_FRAMEWORKS = [
11694
11675
  "property": "visibility",
11695
11676
  "type": "number",
11696
11677
  "required": true,
11678
+ "scope": "framework",
11697
11679
  "label": "Visibility",
11698
11680
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11699
11681
  }
@@ -11703,6 +11685,7 @@ var UPG_FRAMEWORKS = [
11703
11685
  "property": "evolution_stage",
11704
11686
  "type": "enum",
11705
11687
  "required": true,
11688
+ "scope": "framework",
11706
11689
  "label": "Evolution Stage",
11707
11690
  "description": 'Where this component sits on the evolution axis (need anchor is usually at "product" or "commodity")',
11708
11691
  "enum_values": [
@@ -11716,6 +11699,7 @@ var UPG_FRAMEWORKS = [
11716
11699
  "property": "visibility",
11717
11700
  "type": "number",
11718
11701
  "required": true,
11702
+ "scope": "framework",
11719
11703
  "label": "Visibility",
11720
11704
  "description": "Y-axis position (0=infrastructure, 1=anchor/user); needs sit at 1.0"
11721
11705
  }
@@ -11756,6 +11740,9 @@ var UPG_FRAMEWORKS = [
11756
11740
  },
11757
11741
  {
11758
11742
  "id": "business-model-canvas",
11743
+ "approach_ids": [
11744
+ "plan"
11745
+ ],
11759
11746
  "name": "Business Model Canvas",
11760
11747
  "version": "1.0.0",
11761
11748
  "description": "Nine building blocks that describe how an organisation creates, delivers, and captures value.",
@@ -11895,6 +11882,9 @@ var UPG_FRAMEWORKS = [
11895
11882
  },
11896
11883
  {
11897
11884
  "id": "porter-five-forces",
11885
+ "approach_ids": [
11886
+ "inspect"
11887
+ ],
11898
11888
  "name": "Porter Five Forces",
11899
11889
  "version": "1.0.0",
11900
11890
  "description": "Analyse industry competitiveness through five forces: rivalry, new entrants, substitutes, buyer power, and supplier power.",
@@ -11984,6 +11974,9 @@ var UPG_FRAMEWORKS = [
11984
11974
  },
11985
11975
  {
11986
11976
  "id": "swot-analysis",
11977
+ "approach_ids": [
11978
+ "inspect"
11979
+ ],
11987
11980
  "name": "SWOT Analysis",
11988
11981
  "version": "1.0.0",
11989
11982
  "description": "Map Strengths, Weaknesses, Opportunities, and Threats in a 2x2 grid. Internal vs external, helpful vs harmful.",
@@ -12310,6 +12303,7 @@ var UPG_FRAMEWORKS = [
12310
12303
  "type": "assessment",
12311
12304
  "scale_id": "reach_5",
12312
12305
  "required": true,
12306
+ "scope": "framework",
12313
12307
  "label": "Reach",
12314
12308
  "description": "How many users will this impact per quarter?"
12315
12309
  },
@@ -12318,6 +12312,7 @@ var UPG_FRAMEWORKS = [
12318
12312
  "type": "assessment",
12319
12313
  "scale_id": "impact_5",
12320
12314
  "required": true,
12315
+ "scope": "framework",
12321
12316
  "label": "Impact",
12322
12317
  "description": "How much will this impact each user, on the impact scale?"
12323
12318
  },
@@ -12326,6 +12321,7 @@ var UPG_FRAMEWORKS = [
12326
12321
  "type": "assessment",
12327
12322
  "scale_id": "confidence_5",
12328
12323
  "required": true,
12324
+ "scope": "framework",
12329
12325
  "label": "Confidence",
12330
12326
  "description": "How confident are you in the reach, impact, and effort estimates?"
12331
12327
  },
@@ -12334,6 +12330,7 @@ var UPG_FRAMEWORKS = [
12334
12330
  "type": "assessment",
12335
12331
  "scale_id": "effort_5",
12336
12332
  "required": true,
12333
+ "scope": "framework",
12337
12334
  "label": "Effort",
12338
12335
  "description": "How much work is required to build and ship this, on the effort scale?"
12339
12336
  }
@@ -12420,6 +12417,9 @@ var UPG_FRAMEWORKS = [
12420
12417
  },
12421
12418
  {
12422
12419
  "id": "build-measure-learn",
12420
+ "approach_ids": [
12421
+ "reflect"
12422
+ ],
12423
12423
  "name": "Build-Measure-Learn",
12424
12424
  "version": "1.0.0",
12425
12425
  "description": "The core Lean Startup feedback loop: build a minimum viable product, measure its impact with actionable metrics, and learn whether to pivot or persevere.",
@@ -12555,6 +12555,7 @@ var UPG_FRAMEWORKS = [
12555
12555
  "property": "functional_response",
12556
12556
  "type": "enum",
12557
12557
  "required": true,
12558
+ "scope": "framework",
12558
12559
  "label": "Functional Response",
12559
12560
  "description": "How users feel when the feature IS present",
12560
12561
  "enum_values": [
@@ -12569,6 +12570,7 @@ var UPG_FRAMEWORKS = [
12569
12570
  "property": "dysfunctional_response",
12570
12571
  "type": "enum",
12571
12572
  "required": true,
12573
+ "scope": "framework",
12572
12574
  "label": "Dysfunctional Response",
12573
12575
  "description": "How users feel when the feature IS NOT present",
12574
12576
  "enum_values": [
@@ -12583,6 +12585,7 @@ var UPG_FRAMEWORKS = [
12583
12585
  "property": "delighter_count",
12584
12586
  "type": "number",
12585
12587
  "required": false,
12588
+ "scope": "framework",
12586
12589
  "label": "Delighter classifications",
12587
12590
  "description": "Count of survey responses classifying this feature as a delighter (attractive)"
12588
12591
  },
@@ -12590,6 +12593,7 @@ var UPG_FRAMEWORKS = [
12590
12593
  "property": "performance_count",
12591
12594
  "type": "number",
12592
12595
  "required": false,
12596
+ "scope": "framework",
12593
12597
  "label": "Performance classifications",
12594
12598
  "description": "Count of survey responses classifying this feature as performance (one-dimensional)"
12595
12599
  },
@@ -12597,6 +12601,7 @@ var UPG_FRAMEWORKS = [
12597
12601
  "property": "must_be_count",
12598
12602
  "type": "number",
12599
12603
  "required": false,
12604
+ "scope": "framework",
12600
12605
  "label": "Must-be classifications",
12601
12606
  "description": "Count of survey responses classifying this feature as must-be (basic)"
12602
12607
  },
@@ -12604,6 +12609,7 @@ var UPG_FRAMEWORKS = [
12604
12609
  "property": "indifferent_count",
12605
12610
  "type": "number",
12606
12611
  "required": false,
12612
+ "scope": "framework",
12607
12613
  "label": "Indifferent classifications",
12608
12614
  "description": "Count of survey responses classifying this feature as indifferent"
12609
12615
  }
@@ -12796,6 +12802,7 @@ var UPG_FRAMEWORKS = [
12796
12802
  "property": "moscow",
12797
12803
  "type": "enum",
12798
12804
  "required": true,
12805
+ "scope": "framework",
12799
12806
  "label": "MoSCoW priority",
12800
12807
  "description": "Which scope bucket this requirement falls into for the current release",
12801
12808
  "enum_values": [
@@ -12806,16 +12813,7 @@ var UPG_FRAMEWORKS = [
12806
12813
  ]
12807
12814
  }
12808
12815
  ]
12809
- },
12810
- "computed_properties": [
12811
- {
12812
- "property": "must_have_ratio",
12813
- "expression": "must_count / total_count",
12814
- "entity_type": "feature",
12815
- "label": "Must-have Ratio",
12816
- "format": "percentage"
12817
- }
12818
- ]
12816
+ }
12819
12817
  },
12820
12818
  "structure": {
12821
12819
  "pattern": "table"
@@ -12979,6 +12977,9 @@ var UPG_FRAMEWORKS = [
12979
12977
  },
12980
12978
  {
12981
12979
  "id": "c4-model",
12980
+ "approach_ids": [
12981
+ "trace"
12982
+ ],
12982
12983
  "name": "C4 Model",
12983
12984
  "version": "1.0.0",
12984
12985
  "description": "Visualise software architecture at four levels of abstraction: System Context, Container, Component, and Code. Each level zooms in to reveal more detail.",
@@ -13071,6 +13072,9 @@ var UPG_FRAMEWORKS = [
13071
13072
  },
13072
13073
  {
13073
13074
  "id": "adr-log",
13075
+ "approach_ids": [
13076
+ "inspect"
13077
+ ],
13074
13078
  "name": "ADR Log",
13075
13079
  "version": "1.0.0",
13076
13080
  "description": "Architecture Decision Records: log decisions with context, options, and rationale.",
@@ -13184,6 +13188,9 @@ var UPG_FRAMEWORKS = [
13184
13188
  },
13185
13189
  {
13186
13190
  "id": "atomic-design",
13191
+ "approach_ids": [
13192
+ "trace"
13193
+ ],
13187
13194
  "name": "Atomic Design",
13188
13195
  "version": "1.0.0",
13189
13196
  "description": "Atoms, Molecules, Organisms, Templates, Pages: a methodology for creating design systems from the smallest elements up.",
@@ -13281,6 +13288,9 @@ var UPG_FRAMEWORKS = [
13281
13288
  },
13282
13289
  {
13283
13290
  "id": "double-diamond",
13291
+ "approach_ids": [
13292
+ "plan"
13293
+ ],
13284
13294
  "name": "Double Diamond",
13285
13295
  "version": "1.0.0",
13286
13296
  "description": "Discover, Define, Develop, Deliver: a four-phase divergent/convergent design process.",
@@ -13371,6 +13381,9 @@ var UPG_FRAMEWORKS = [
13371
13381
  },
13372
13382
  {
13373
13383
  "id": "dora-metrics",
13384
+ "approach_ids": [
13385
+ "inspect"
13386
+ ],
13374
13387
  "name": "DORA Metrics",
13375
13388
  "version": "1.0.0",
13376
13389
  "description": "Four key metrics for software delivery performance: deployment frequency, lead time, change failure rate, and time to restore.",
@@ -13430,16 +13443,7 @@ var UPG_FRAMEWORKS = [
13430
13443
  ]
13431
13444
  }
13432
13445
  ]
13433
- },
13434
- "computed_properties": [
13435
- {
13436
- "property": "stability_index",
13437
- "expression": "change_failure_rate * mean_time_to_recovery",
13438
- "entity_type": "metric",
13439
- "label": "Stability Index",
13440
- "format": "number"
13441
- }
13442
- ]
13446
+ }
13443
13447
  },
13444
13448
  "structure": {
13445
13449
  "pattern": "collection"
@@ -13568,6 +13572,9 @@ var UPG_FRAMEWORKS = [
13568
13572
  },
13569
13573
  {
13570
13574
  "id": "pirate-metrics-aarrr",
13575
+ "approach_ids": [
13576
+ "trace"
13577
+ ],
13571
13578
  "name": "Pirate Metrics AARRR",
13572
13579
  "version": "1.0.0",
13573
13580
  "description": "Track user lifecycle across five stages: Acquisition, Activation, Retention, Revenue, and Referral.",
@@ -13601,7 +13608,7 @@ var UPG_FRAMEWORKS = [
13601
13608
  "required_properties": {
13602
13609
  "metric": [
13603
13610
  {
13604
- "property": "lifecycle_stage",
13611
+ "property": "metric_category",
13605
13612
  "type": "enum",
13606
13613
  "required": true,
13607
13614
  "label": "Lifecycle stage",
@@ -13615,16 +13622,7 @@ var UPG_FRAMEWORKS = [
13615
13622
  ]
13616
13623
  }
13617
13624
  ]
13618
- },
13619
- "computed_properties": [
13620
- {
13621
- "property": "conversion_rate",
13622
- "expression": "(stage_exits / stage_entries) * 100",
13623
- "entity_type": "metric",
13624
- "label": "Stage Conversion",
13625
- "format": "percentage"
13626
- }
13627
- ]
13625
+ }
13628
13626
  },
13629
13627
  "structure": {
13630
13628
  "pattern": "funnel",
@@ -13688,6 +13686,9 @@ var UPG_FRAMEWORKS = [
13688
13686
  },
13689
13687
  {
13690
13688
  "id": "north-star-metric",
13689
+ "approach_ids": [
13690
+ "plan"
13691
+ ],
13691
13692
  "name": "North Star Metric",
13692
13693
  "version": "1.0.0",
13693
13694
  "description": "One metric that best captures the core value you deliver. Supported by 3-5 input metrics that drive it.",
@@ -13721,7 +13722,7 @@ var UPG_FRAMEWORKS = [
13721
13722
  "required_properties": {
13722
13723
  "metric": [
13723
13724
  {
13724
- "property": "metric_role",
13725
+ "property": "designation",
13725
13726
  "type": "enum",
13726
13727
  "required": true,
13727
13728
  "label": "Metric role",
@@ -13739,16 +13740,7 @@ var UPG_FRAMEWORKS = [
13739
13740
  "description": "How strongly this input metric moves the North Star (input metrics only)"
13740
13741
  }
13741
13742
  ]
13742
- },
13743
- "computed_properties": [
13744
- {
13745
- "property": "nsm_impact",
13746
- "expression": "input_metric_value * leverage",
13747
- "entity_type": "metric",
13748
- "label": "NSM Impact",
13749
- "format": "number"
13750
- }
13751
- ]
13743
+ }
13752
13744
  },
13753
13745
  "structure": {
13754
13746
  "pattern": "collection"
@@ -13784,6 +13776,9 @@ var UPG_FRAMEWORKS = [
13784
13776
  },
13785
13777
  {
13786
13778
  "id": "marketing-mix-4ps",
13779
+ "approach_ids": [
13780
+ "plan"
13781
+ ],
13787
13782
  "name": "Marketing Mix 4Ps",
13788
13783
  "version": "1.0.0",
13789
13784
  "description": "The foundational marketing framework. Every marketing strategy must address four decisions: what to sell (Product), what to charge (Price), where to sell (Place), and how to promote (Promotion).",
@@ -13877,6 +13872,9 @@ var UPG_FRAMEWORKS = [
13877
13872
  },
13878
13873
  {
13879
13874
  "id": "bullseye-framework",
13875
+ "approach_ids": [
13876
+ "plan"
13877
+ ],
13880
13878
  "name": "Bullseye Framework",
13881
13879
  "version": "1.0.0",
13882
13880
  "description": "Test 19 traction channels systematically. Start with the outer ring (what's possible), narrow to the middle ring (what's probable), then focus on the inner ring (what's working). Run cheap tests across all channels to find your bullseye.",
@@ -13963,6 +13961,9 @@ var UPG_FRAMEWORKS = [
13963
13961
  },
13964
13962
  {
13965
13963
  "id": "product-led-growth-framework",
13964
+ "approach_ids": [
13965
+ "plan"
13966
+ ],
13966
13967
  "name": "PLG Framework",
13967
13968
  "version": "1.0.0",
13968
13969
  "description": "Product-led go-to-market motion. Free entry gives users access, the aha moment hooks them, they expand usage within their team, and monetisation captures value from power users.",
@@ -14145,6 +14146,9 @@ var UPG_FRAMEWORKS = [
14145
14146
  },
14146
14147
  {
14147
14148
  "id": "raci-matrix",
14149
+ "approach_ids": [
14150
+ "inspect"
14151
+ ],
14148
14152
  "name": "RACI Matrix",
14149
14153
  "version": "1.0.0",
14150
14154
  "description": "Assign roles: Responsible, Accountable, Consulted, Informed for each activity.",
@@ -14196,16 +14200,7 @@ var UPG_FRAMEWORKS = [
14196
14200
  "role": "item"
14197
14201
  }
14198
14202
  ],
14199
- "required_properties": {},
14200
- "computed_properties": [
14201
- {
14202
- "property": "coverage_score",
14203
- "expression": "assigned_count / total_activities",
14204
- "entity_type": "team",
14205
- "label": "RACI Coverage",
14206
- "format": "percentage"
14207
- }
14208
- ]
14203
+ "required_properties": {}
14209
14204
  },
14210
14205
  "structure": {
14211
14206
  "pattern": "matrix"
@@ -14429,6 +14424,9 @@ var UPG_FRAMEWORKS = [
14429
14424
  },
14430
14425
  {
14431
14426
  "id": "team-health-check",
14427
+ "approach_ids": [
14428
+ "inspect"
14429
+ ],
14432
14430
  "name": "Team Health Check",
14433
14431
  "version": "1.0.0",
14434
14432
  "description": "A facilitated team self-assessment across dimensions like mission, fun, learning, speed, and support, using traffic-light voting to surface strengths and improvement areas in a safe format.",
@@ -14486,16 +14484,7 @@ var UPG_FRAMEWORKS = [
14486
14484
  "role": "item"
14487
14485
  }
14488
14486
  ],
14489
- "required_properties": {},
14490
- "computed_properties": [
14491
- {
14492
- "property": "health_index",
14493
- "expression": "(green_count - red_count) / total_indicators",
14494
- "entity_type": "team",
14495
- "label": "Health Index",
14496
- "format": "number"
14497
- }
14498
- ]
14487
+ "required_properties": {}
14499
14488
  },
14500
14489
  "structure": {
14501
14490
  "pattern": "table"
@@ -14552,6 +14541,9 @@ var UPG_FRAMEWORKS = [
14552
14541
  },
14553
14542
  {
14554
14543
  "id": "raid-log",
14544
+ "approach_ids": [
14545
+ "inspect"
14546
+ ],
14555
14547
  "name": "RAID Log",
14556
14548
  "version": "1.0.0",
14557
14549
  "description": "A project management register tracking Risks, Assumptions, Issues, and Dependencies, the four categories most likely to derail a project if left unmanaged.",
@@ -14571,6 +14563,11 @@ var UPG_FRAMEWORKS = [
14571
14563
  "entityTypeId": "risk_register",
14572
14564
  "description": "Risk or assumption logged with likelihood, impact, owner, and mitigation strategy"
14573
14565
  },
14566
+ {
14567
+ "label": "Risk",
14568
+ "entityTypeId": "risk",
14569
+ "description": "Individual risk scored by probability and impact; severity = probability * impact"
14570
+ },
14574
14571
  {
14575
14572
  "label": "Dependency",
14576
14573
  "entityTypeId": "dependency",
@@ -14593,6 +14590,10 @@ var UPG_FRAMEWORKS = [
14593
14590
  "type": "risk_register",
14594
14591
  "role": "item"
14595
14592
  },
14593
+ {
14594
+ "type": "risk",
14595
+ "role": "scored_item"
14596
+ },
14596
14597
  {
14597
14598
  "type": "dependency",
14598
14599
  "role": "item"
@@ -14606,7 +14607,24 @@ var UPG_FRAMEWORKS = [
14606
14607
  "role": "item"
14607
14608
  }
14608
14609
  ],
14609
- "required_properties": {},
14610
+ "required_properties": {
14611
+ "risk": [
14612
+ {
14613
+ "property": "probability",
14614
+ "type": "number",
14615
+ "required": true,
14616
+ "label": "Probability",
14617
+ "description": "Likelihood the risk materialises (risk.probability assessment)"
14618
+ },
14619
+ {
14620
+ "property": "impact",
14621
+ "type": "number",
14622
+ "required": true,
14623
+ "label": "Impact",
14624
+ "description": "Consequence severity if the risk materialises (risk.impact assessment)"
14625
+ }
14626
+ ]
14627
+ },
14610
14628
  "computed_properties": [
14611
14629
  {
14612
14630
  "property": "severity",
@@ -14667,106 +14685,1008 @@ var UPG_FRAMEWORKS = [
14667
14685
  "Formal programme management would create overhead without value"
14668
14686
  ]
14669
14687
  }
14670
- }
14671
- ];
14672
- var UPG_FRAMEWORKS_BY_ID = Object.fromEntries(
14673
- UPG_FRAMEWORKS.map((fw) => [fw.id, fw])
14674
- );
14675
- var UPG_FRAMEWORKS_BY_CATEGORY = {};
14676
- for (const fw of UPG_FRAMEWORKS) {
14677
- if (!UPG_FRAMEWORKS_BY_CATEGORY[fw.category]) UPG_FRAMEWORKS_BY_CATEGORY[fw.category] = [];
14678
- UPG_FRAMEWORKS_BY_CATEGORY[fw.category].push(fw);
14679
- }
14680
- var PRIORITY_LABELS = [
14681
- // ── need (CONSOLIDATED: replaces pain_point + user_need) ─────────────────────
14682
- {
14683
- id: "need",
14684
- canonical_label: "Need",
14685
- alt_labels: [
14686
- "pain point",
14687
- "pain",
14688
- "user need",
14689
- "customer need",
14690
- "problem",
14691
- "struggle",
14692
- "customer pain",
14693
- "frustration",
14694
- "gap",
14695
- "unmet need",
14696
- "user problem"
14697
- ],
14698
- framework_labels: {
14699
- lean_canvas: "Problem",
14700
- design_thinking: "Pain Point",
14701
- ost: "Opportunity (need)",
14702
- jtbd: "Struggle",
14703
- vpc: "Customer Pain"
14704
- },
14705
- designations: {
14706
- pain: "Pain Point",
14707
- gap: "Need",
14708
- desire: "Desire",
14709
- constraint: "Constraint"
14710
- }
14711
- },
14712
- // ── opportunity ──────────────────────────────────────────────────────────────
14713
- {
14714
- id: "opportunity",
14715
- canonical_label: "Opportunity",
14716
- alt_labels: ["product opportunity", "market opportunity", "user opportunity"],
14717
- framework_labels: {
14718
- ost: "Opportunity"
14719
- }
14720
- },
14721
- // ── solution ─────────────────────────────────────────────────────────────────
14722
- {
14723
- id: "solution",
14724
- canonical_label: "Solution",
14725
- alt_labels: ["proposed solution", "solution idea", "concept", "approach"],
14726
- framework_labels: {
14727
- ost: "Solution",
14728
- design_thinking: "Solution",
14729
- lean_canvas: "Solution",
14730
- rice: "Scored Solution"
14731
- }
14732
14688
  },
14733
- // ── experiment (CONSOLIDATED: absorbs ab_test, growth_experiment, pricing_experiment) ──
14734
14689
  {
14735
- id: "experiment",
14736
- canonical_label: "Experiment",
14737
- alt_labels: [
14738
- "test",
14739
- "validation",
14740
- "ab test",
14741
- "a/b test",
14742
- "split test",
14743
- "growth experiment",
14744
- "pricing experiment",
14745
- "usability test",
14746
- "discovery experiment"
14690
+ "id": "ice-scoring",
14691
+ "approach_ids": [
14692
+ "prioritise"
14747
14693
  ],
14748
- framework_labels: {
14749
- ost: "Experiment",
14750
- design_thinking: "Test",
14751
- lean_startup: "Experiment"
14694
+ "name": "ICE Scoring",
14695
+ "version": "1.0.0",
14696
+ "description": "Rate ideas by Impact, Confidence, and Ease on a 1-10 scale. Multiply for a composite score. Fast and lightweight.",
14697
+ "category": "prioritization",
14698
+ "origin": {
14699
+ "type": "practitioner",
14700
+ "attribution": "Sean Ellis",
14701
+ "description": "Created by Sean Ellis as a lightweight growth experiment scoring method. Widely adopted in growth teams.",
14702
+ "year": 2010,
14703
+ "license": "open_attribution"
14752
14704
  },
14753
- designations: {
14754
- discovery: "Discovery Experiment",
14755
- ab_test: "A/B Test",
14756
- growth: "Growth Experiment",
14757
- pricing: "Pricing Experiment",
14758
- usability: "Usability Test"
14759
- }
14760
- },
14761
- // ── hypothesis ───────────────────────────────────────────────────────────────
14762
- {
14763
- id: "hypothesis",
14764
- canonical_label: "Hypothesis",
14765
- alt_labels: ["bet", "testable assumption", "leap of faith"],
14766
- framework_labels: {
14767
- lean_startup: "Hypothesis",
14768
- running_lean: "Riskiest Assumption",
14769
- lean_canvas: "Riskiest Assumption"
14705
+ "tags": [
14706
+ "prioritization",
14707
+ "table"
14708
+ ],
14709
+ "slots": [
14710
+ {
14711
+ "label": "Items to score",
14712
+ "entityTypeId": "feature",
14713
+ "description": "Features or experiments being evaluated"
14714
+ },
14715
+ {
14716
+ "label": "Impact",
14717
+ "entityTypeId": "outcome",
14718
+ "description": "How much will this move the needle?"
14719
+ },
14720
+ {
14721
+ "label": "Confidence",
14722
+ "entityTypeId": "assumption",
14723
+ "description": "How sure are we about the impact?"
14724
+ },
14725
+ {
14726
+ "label": "Ease",
14727
+ "entityTypeId": "feature",
14728
+ "description": "How easy is this to implement?"
14729
+ }
14730
+ ],
14731
+ "data": {
14732
+ "entity_types": [
14733
+ {
14734
+ "type": "feature",
14735
+ "role": "scored_item"
14736
+ },
14737
+ {
14738
+ "type": "outcome",
14739
+ "role": "item"
14740
+ },
14741
+ {
14742
+ "type": "assumption",
14743
+ "role": "item"
14744
+ }
14745
+ ],
14746
+ "required_properties": {
14747
+ "feature": [
14748
+ {
14749
+ "property": "impact",
14750
+ "type": "number",
14751
+ "required": true,
14752
+ "scope": "framework",
14753
+ "label": "Impact",
14754
+ "description": "Expected impact on the target metric (1-10)"
14755
+ },
14756
+ {
14757
+ "property": "confidence",
14758
+ "type": "number",
14759
+ "required": true,
14760
+ "scope": "framework",
14761
+ "label": "Confidence",
14762
+ "description": "Confidence in the impact estimate (1-10)"
14763
+ },
14764
+ {
14765
+ "property": "ease",
14766
+ "type": "number",
14767
+ "required": true,
14768
+ "scope": "framework",
14769
+ "label": "Ease",
14770
+ "description": "Ease of implementation (1-10)"
14771
+ }
14772
+ ]
14773
+ },
14774
+ "computed_properties": [
14775
+ {
14776
+ "property": "ice_score",
14777
+ "expression": "impact * confidence * ease",
14778
+ "entity_type": "feature",
14779
+ "label": "ICE Score",
14780
+ "format": "number"
14781
+ }
14782
+ ]
14783
+ },
14784
+ "structure": {
14785
+ "pattern": "table"
14786
+ },
14787
+ "presentation": {
14788
+ "layout": {
14789
+ "type": "table",
14790
+ "columns": [
14791
+ {
14792
+ "property": "title",
14793
+ "label": "Items to score",
14794
+ "sortable": true
14795
+ },
14796
+ {
14797
+ "property": "impact",
14798
+ "label": "Impact",
14799
+ "sortable": true
14800
+ },
14801
+ {
14802
+ "property": "confidence",
14803
+ "label": "Confidence",
14804
+ "sortable": true
14805
+ },
14806
+ {
14807
+ "property": "ease",
14808
+ "label": "Ease",
14809
+ "sortable": true
14810
+ }
14811
+ ]
14812
+ },
14813
+ "sort_by": {
14814
+ "property": "title",
14815
+ "direction": "asc"
14816
+ },
14817
+ "colour_by": "type",
14818
+ "card_fields": [
14819
+ "title",
14820
+ "description",
14821
+ "status"
14822
+ ]
14823
+ },
14824
+ "education": {
14825
+ "purpose": "Provide a lightweight scoring model for early-stage ideas when detailed effort estimates are unavailable. Faster than RICE and useful for brainstorm triage.",
14826
+ "core_question": "Which ideas should we investigate further based on their potential impact, confidence in our assumptions, and implementation ease?",
14827
+ "when_to_use": [
14828
+ "You have more ideas or features than capacity to build them",
14829
+ "Stakeholders disagree on what to build next",
14830
+ "You need a transparent, defensible prioritisation process"
14831
+ ],
14832
+ "when_not_to_use": [
14833
+ "You have a single obvious next step with no contention",
14834
+ "The backlog is small enough to sequence intuitively"
14835
+ ]
14836
+ }
14837
+ },
14838
+ {
14839
+ "id": "wsjf",
14840
+ "approach_ids": [
14841
+ "prioritise"
14842
+ ],
14843
+ "name": "WSJF (Weighted Shortest Job First)",
14844
+ "version": "1.0.0",
14845
+ "description": "Prioritise work by dividing Cost of Delay (user value + time criticality + risk reduction) by job duration to maximise economic throughput.",
14846
+ "category": "planning",
14847
+ "origin": {
14848
+ "type": "practitioner",
14849
+ "attribution": "Reinertsen / SAFe",
14850
+ "description": "Developed by Don Reinertsen and adopted as a core practice in the Scaled Agile Framework (SAFe). Combines urgency (Cost of Delay) with job size to produce an economic prioritisation sequence.",
14851
+ "url": "https://www.scaledagileframework.com/wsjf/",
14852
+ "year": 2011,
14853
+ "license": "open_attribution"
14854
+ },
14855
+ "tags": [
14856
+ "planning",
14857
+ "table"
14858
+ ],
14859
+ "slots": [
14860
+ {
14861
+ "label": "Backlog Items",
14862
+ "entityTypeId": "feature",
14863
+ "description": "Backlog Items: feature entries to evaluate"
14864
+ },
14865
+ {
14866
+ "label": "User/Business Value",
14867
+ "entityTypeId": "metric",
14868
+ "description": "User/Business Value: metric entries to evaluate"
14869
+ },
14870
+ {
14871
+ "label": "Time Criticality",
14872
+ "entityTypeId": "metric",
14873
+ "description": "How much value decays if delivery is delayed (deadlines, competition, seasonal windows)"
14874
+ },
14875
+ {
14876
+ "label": "Risk Reduction / Opportunity Enablement",
14877
+ "entityTypeId": "metric",
14878
+ "description": "Risk Reduction / Opportunity Enablement: metric entries to evaluate"
14879
+ },
14880
+ {
14881
+ "label": "Job Size",
14882
+ "entityTypeId": "metric",
14883
+ "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
14884
+ }
14885
+ ],
14886
+ "data": {
14887
+ "entity_types": [
14888
+ {
14889
+ "type": "feature",
14890
+ "role": "scored_item"
14891
+ },
14892
+ {
14893
+ "type": "metric",
14894
+ "role": "item"
14895
+ }
14896
+ ],
14897
+ "required_properties": {
14898
+ "feature": [
14899
+ {
14900
+ "property": "user_value",
14901
+ "type": "number",
14902
+ "required": true,
14903
+ "scope": "framework",
14904
+ "label": "User/Business Value",
14905
+ "description": "Relative value to users and the business if delivered"
14906
+ },
14907
+ {
14908
+ "property": "time_criticality",
14909
+ "type": "number",
14910
+ "required": true,
14911
+ "scope": "framework",
14912
+ "label": "Time Criticality",
14913
+ "description": "How much value decays if delivery is delayed (deadlines, competition, seasonal windows)"
14914
+ },
14915
+ {
14916
+ "property": "risk_reduction",
14917
+ "type": "number",
14918
+ "required": true,
14919
+ "scope": "framework",
14920
+ "label": "Risk Reduction / Opportunity Enablement",
14921
+ "description": "Value from reducing risk or enabling future opportunities"
14922
+ },
14923
+ {
14924
+ "property": "job_size",
14925
+ "type": "number",
14926
+ "required": true,
14927
+ "scope": "framework",
14928
+ "label": "Job Size",
14929
+ "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
14930
+ }
14931
+ ]
14932
+ },
14933
+ "computed_properties": [
14934
+ {
14935
+ "property": "wsjf_score",
14936
+ "expression": "(user_value + time_criticality + risk_reduction) / job_size",
14937
+ "entity_type": "feature",
14938
+ "label": "WSJF Score",
14939
+ "format": "number"
14940
+ }
14941
+ ]
14942
+ },
14943
+ "structure": {
14944
+ "pattern": "table"
14945
+ },
14946
+ "presentation": {
14947
+ "layout": {
14948
+ "type": "table",
14949
+ "columns": [
14950
+ {
14951
+ "property": "title",
14952
+ "label": "Backlog Items",
14953
+ "sortable": true
14954
+ },
14955
+ {
14956
+ "property": "user_value",
14957
+ "label": "User/Business Value",
14958
+ "sortable": true
14959
+ },
14960
+ {
14961
+ "property": "time_criticality",
14962
+ "label": "Time Criticality",
14963
+ "sortable": true
14964
+ },
14965
+ {
14966
+ "property": "risk_reduction",
14967
+ "label": "Risk Reduction / Opportunity Enablement",
14968
+ "sortable": true
14969
+ },
14970
+ {
14971
+ "property": "job_size",
14972
+ "label": "Job Size",
14973
+ "sortable": true
14974
+ },
14975
+ {
14976
+ "property": "wsjf_score",
14977
+ "label": "WSJF Score",
14978
+ "sortable": true
14979
+ }
14980
+ ]
14981
+ },
14982
+ "sort_by": {
14983
+ "property": "title",
14984
+ "direction": "asc"
14985
+ },
14986
+ "colour_by": "type",
14987
+ "card_fields": [
14988
+ "title",
14989
+ "description",
14990
+ "status"
14991
+ ]
14992
+ },
14993
+ "education": {
14994
+ "purpose": "Prioritise work by dividing the Cost of Delay by job duration, ensuring the most time-sensitive, valuable items are done first.",
14995
+ "core_question": "Considering the cost of waiting, which items should we start now to maximise economic benefit?",
14996
+ "when_to_use": [
14997
+ "You need to coordinate work across multiple teams or time horizons",
14998
+ "Stakeholders need visibility into what is coming and when",
14999
+ "You want to balance commitments with flexibility"
15000
+ ],
15001
+ "when_not_to_use": [
15002
+ "The team is small enough that informal coordination works",
15003
+ "Plans would create false precision about uncertain outcomes"
15004
+ ]
15005
+ }
15006
+ },
15007
+ {
15008
+ "id": "cost-of-delay",
15009
+ "approach_ids": [
15010
+ "prioritise"
15011
+ ],
15012
+ "name": "Cost of Delay",
15013
+ "version": "1.0.0",
15014
+ "description": "Quantify the economic cost of not shipping a feature to drive priority decisions. Combines urgency with value.",
15015
+ "category": "prioritization",
15016
+ "origin": {
15017
+ "type": "practitioner",
15018
+ "attribution": "Don Reinertsen",
15019
+ "description": "Formalised in The Principles of Product Development Flow (Celeritas Publishing). Foundational to lean product economics.",
15020
+ "year": 2009,
15021
+ "license": "public_domain"
15022
+ },
15023
+ "tags": [
15024
+ "prioritization",
15025
+ "table"
15026
+ ],
15027
+ "slots": [
15028
+ {
15029
+ "label": "Items to evaluate",
15030
+ "entityTypeId": "feature",
15031
+ "description": "Features or initiatives being assessed"
15032
+ },
15033
+ {
15034
+ "label": "User-Business Value",
15035
+ "entityTypeId": "outcome",
15036
+ "description": "Revenue, retention, or strategic value"
15037
+ },
15038
+ {
15039
+ "label": "Time Criticality",
15040
+ "entityTypeId": "metric",
15041
+ "description": "How much value decays with delay"
15042
+ },
15043
+ {
15044
+ "label": "Risk Reduction",
15045
+ "entityTypeId": "risk",
15046
+ "description": "What risk does this mitigate?"
15047
+ }
15048
+ ],
15049
+ "data": {
15050
+ "entity_types": [
15051
+ {
15052
+ "type": "feature",
15053
+ "role": "scored_item"
15054
+ },
15055
+ {
15056
+ "type": "metric",
15057
+ "role": "item"
15058
+ },
15059
+ {
15060
+ "type": "outcome",
15061
+ "role": "item"
15062
+ },
15063
+ {
15064
+ "type": "risk",
15065
+ "role": "item"
15066
+ }
15067
+ ],
15068
+ "required_properties": {
15069
+ "feature": [
15070
+ {
15071
+ "property": "cost_of_delay",
15072
+ "type": "number",
15073
+ "required": true,
15074
+ "scope": "framework",
15075
+ "label": "Cost of Delay",
15076
+ "description": "Weekly revenue impact of not shipping"
15077
+ },
15078
+ {
15079
+ "property": "job_size",
15080
+ "type": "number",
15081
+ "required": true,
15082
+ "scope": "framework",
15083
+ "label": "Job Size",
15084
+ "description": "Weeks of development effort"
15085
+ }
15086
+ ]
15087
+ },
15088
+ "computed_properties": [
15089
+ {
15090
+ "property": "wsjf_score",
15091
+ "expression": "cost_of_delay / job_size",
15092
+ "entity_type": "feature",
15093
+ "label": "WSJF Score",
15094
+ "format": "number"
15095
+ }
15096
+ ]
15097
+ },
15098
+ "structure": {
15099
+ "pattern": "table"
15100
+ },
15101
+ "presentation": {
15102
+ "layout": {
15103
+ "type": "table",
15104
+ "columns": [
15105
+ {
15106
+ "property": "title",
15107
+ "label": "Items to evaluate",
15108
+ "sortable": true
15109
+ },
15110
+ {
15111
+ "property": "cost_of_delay",
15112
+ "label": "User-Business Value",
15113
+ "sortable": true
15114
+ },
15115
+ {
15116
+ "property": "job_size",
15117
+ "label": "Job Size",
15118
+ "sortable": true
15119
+ },
15120
+ {
15121
+ "property": "wsjf_score",
15122
+ "label": "CoD Score",
15123
+ "sortable": true
15124
+ }
15125
+ ]
15126
+ },
15127
+ "sort_by": {
15128
+ "property": "title",
15129
+ "direction": "asc"
15130
+ },
15131
+ "colour_by": "type",
15132
+ "card_fields": [
15133
+ "title",
15134
+ "description",
15135
+ "status"
15136
+ ]
15137
+ },
15138
+ "education": {
15139
+ "purpose": "Quantify the economic impact of not delivering a feature by a given date, making urgency visible and enabling time-sensitive prioritisation.",
15140
+ "core_question": "How much value are we losing every week this feature is not in production, and does that urgency justify fast-tracking it?",
15141
+ "when_to_use": [
15142
+ "You have more ideas or features than capacity to build them",
15143
+ "Stakeholders disagree on what to build next",
15144
+ "You need a transparent, defensible prioritisation process"
15145
+ ],
15146
+ "when_not_to_use": [
15147
+ "You have a single obvious next step with no contention",
15148
+ "The backlog is small enough to sequence intuitively"
15149
+ ]
15150
+ }
15151
+ },
15152
+ {
15153
+ "id": "five-whys",
15154
+ "approach_ids": [
15155
+ "reflect",
15156
+ "inspect"
15157
+ ],
15158
+ "name": "Five Whys",
15159
+ "version": "1.0.0",
15160
+ "description": 'Iteratively ask "why?", typically five times, starting from a symptom; each answer becomes the subject of the next question. The chain of answers reveals the underlying root cause behind the surface problem.',
15161
+ "category": "team_process",
15162
+ "origin": {
15163
+ "type": "practitioner",
15164
+ "attribution": "Sakichi Toyoda / Toyota Production System",
15165
+ "description": "Developed within the Toyota Production System as a root-cause analysis technique. Popularised through Lean and Six Sigma practice; now a widely used incident-review and design-debug staple.",
15166
+ "year": 1930,
15167
+ "license": "public_domain"
15168
+ },
15169
+ "tags": [
15170
+ "team_process",
15171
+ "reflection",
15172
+ "root_cause",
15173
+ "tree"
15174
+ ],
15175
+ "slots": [
15176
+ {
15177
+ "label": "Symptom",
15178
+ "entityTypeId": "need",
15179
+ "description": "The observed problem the analysis starts from."
15180
+ },
15181
+ {
15182
+ "label": "Why chain",
15183
+ "entityTypeId": "insight",
15184
+ "description": 'Each "why?" answer along the chain, typically five iterations deep.'
15185
+ },
15186
+ {
15187
+ "label": "Root cause",
15188
+ "entityTypeId": "insight",
15189
+ "description": "The terminal answer at the bottom of the chain: the underlying cause to address."
15190
+ }
15191
+ ],
15192
+ "data": {
15193
+ "entity_types": [
15194
+ {
15195
+ "type": "need",
15196
+ "role": "root"
15197
+ },
15198
+ {
15199
+ "type": "insight",
15200
+ "role": "branch"
15201
+ }
15202
+ ],
15203
+ "required_properties": {}
15204
+ },
15205
+ "structure": {
15206
+ "pattern": "tree"
15207
+ },
15208
+ "presentation": {
15209
+ "layout": {
15210
+ "type": "tree",
15211
+ "direction": "TB"
15212
+ },
15213
+ "sort_by": {
15214
+ "property": "title",
15215
+ "direction": "asc"
15216
+ },
15217
+ "card_fields": [
15218
+ "title",
15219
+ "description"
15220
+ ]
15221
+ },
15222
+ "education": {
15223
+ "purpose": 'Move past surface symptoms by chaining "why?" questions until the underlying root cause surfaces, so fixes target the real driver rather than a downstream effect.',
15224
+ "core_question": "Why is this happening, and why is THAT happening, until we reach a cause we can act on?",
15225
+ "when_to_use": [
15226
+ "A problem keeps recurring after surface fixes",
15227
+ "Post-incident review where the obvious cause feels too obvious",
15228
+ "Designing a fix and you want to confirm you understand the actual driver"
15229
+ ],
15230
+ "when_not_to_use": [
15231
+ "The problem has multiple independent root causes (use a fishbone or richer RCA tool)",
15232
+ "You need quantitative attribution rather than a single-thread narrative",
15233
+ 'Five linear "whys" oversimplify a systems problem with feedback loops'
15234
+ ]
15235
+ }
15236
+ },
15237
+ {
15238
+ "id": "pre-mortem",
15239
+ "approach_ids": [
15240
+ "reflect"
15241
+ ],
15242
+ "name": "Pre-mortem",
15243
+ "version": "1.0.0",
15244
+ "description": "Imagine the project has already failed; work backward listing the plausible causes of the failure. Produce a risk register and matching mitigations before the work starts.",
15245
+ "category": "team_process",
15246
+ "origin": {
15247
+ "type": "practitioner",
15248
+ "attribution": "Gary Klein",
15249
+ "description": "Popularised by Gary Klein in Harvard Business Review (2007) as a prospective-hindsight technique. Inverts the post-mortem: imagine failure first, then list causes, while there is still time to act.",
15250
+ "url": "https://hbr.org/2007/09/performing-a-project-premortem",
15251
+ "year": 2007,
15252
+ "license": "published_methodology"
15253
+ },
15254
+ "tags": [
15255
+ "team_process",
15256
+ "reflection",
15257
+ "risk",
15258
+ "collection"
15259
+ ],
15260
+ "slots": [
15261
+ {
15262
+ "label": "Imagined failures",
15263
+ "entityTypeId": "risk",
15264
+ "description": "Plausible failure modes named as if they had already occurred."
15265
+ },
15266
+ {
15267
+ "label": "Causes",
15268
+ "entityTypeId": "insight",
15269
+ "description": "For each imagined failure, the contributing causes the team can foresee."
15270
+ },
15271
+ {
15272
+ "label": "Mitigations",
15273
+ "entityTypeId": "initiative",
15274
+ "description": "Mitigation actions the team will take before failure can occur."
15275
+ }
15276
+ ],
15277
+ "data": {
15278
+ "entity_types": [
15279
+ {
15280
+ "type": "risk",
15281
+ "role": "bucket"
15282
+ },
15283
+ {
15284
+ "type": "insight",
15285
+ "role": "bucket"
15286
+ },
15287
+ {
15288
+ "type": "initiative",
15289
+ "role": "bucket"
15290
+ }
15291
+ ],
15292
+ "required_properties": {}
15293
+ },
15294
+ "structure": {
15295
+ "pattern": "collection"
15296
+ },
15297
+ "presentation": {
15298
+ "layout": {
15299
+ "type": "grid",
15300
+ "groupBy": "type"
15301
+ },
15302
+ "sort_by": {
15303
+ "property": "title",
15304
+ "direction": "asc"
15305
+ },
15306
+ "colour_by": "group",
15307
+ "card_fields": [
15308
+ "title",
15309
+ "description"
15310
+ ]
15311
+ },
15312
+ "education": {
15313
+ "purpose": "Surface project risks early by inverting hindsight: imagine the project has already failed and ask why, while there is still time to mitigate.",
15314
+ "core_question": "It is six months from now and the project has failed catastrophically. What happened, and why?",
15315
+ "when_to_use": [
15316
+ "Kicking off a project with significant downside or irreversible commitment",
15317
+ "A plan looks too clean and the team senses unspoken concerns",
15318
+ "Stakeholders disagree on risk; the exercise externalises and ranks them"
15319
+ ],
15320
+ "when_not_to_use": [
15321
+ "The work is small, reversible, and cheap to course-correct",
15322
+ "The team is in execution mode and reflective ceremonies will derail momentum",
15323
+ "Risk surfacing has become performative: the team names risks but never mitigates them"
15324
+ ]
15325
+ }
15326
+ },
15327
+ {
15328
+ "id": "red-team",
15329
+ "approach_ids": [
15330
+ "reflect",
15331
+ "inspect"
15332
+ ],
15333
+ "name": "Red Team",
15334
+ "version": "1.0.0",
15335
+ "description": "Structured adversarial review. A designated group is assigned to attack a plan, design, or proposal from an outside-in stance, surfacing weaknesses the inside-out builders cannot see.",
15336
+ "category": "team_process",
15337
+ "origin": {
15338
+ "type": "practitioner",
15339
+ "attribution": "US Department of Defense (Cold War era); broadened by security and intelligence practice",
15340
+ "description": `Originated in Cold War-era military strategic exercises ("red" team takes the adversary role against the "blue" team's defence). Adopted by cybersecurity, intelligence analysis, and product teams as a structured contrarian-review practice.`,
15341
+ "year": 1960,
15342
+ "license": "public_domain"
15343
+ },
15344
+ "tags": [
15345
+ "team_process",
15346
+ "reflection",
15347
+ "adversarial",
15348
+ "collection"
15349
+ ],
15350
+ "slots": [
15351
+ {
15352
+ "label": "Target",
15353
+ "entityTypeId": "initiative",
15354
+ "description": "The plan, design, or proposal under adversarial review."
15355
+ },
15356
+ {
15357
+ "label": "Attack vectors",
15358
+ "entityTypeId": "risk",
15359
+ "description": "The angles the red team uses to probe weaknesses."
15360
+ },
15361
+ {
15362
+ "label": "Findings",
15363
+ "entityTypeId": "insight",
15364
+ "description": "Weaknesses, blind spots, or unstated assumptions surfaced by the review."
15365
+ }
15366
+ ],
15367
+ "data": {
15368
+ "entity_types": [
15369
+ {
15370
+ "type": "initiative",
15371
+ "role": "root"
15372
+ },
15373
+ {
15374
+ "type": "risk",
15375
+ "role": "bucket"
15376
+ },
15377
+ {
15378
+ "type": "insight",
15379
+ "role": "bucket"
15380
+ }
15381
+ ],
15382
+ "required_properties": {}
15383
+ },
15384
+ "structure": {
15385
+ "pattern": "collection"
15386
+ },
15387
+ "presentation": {
15388
+ "layout": {
15389
+ "type": "grid",
15390
+ "groupBy": "type"
15391
+ },
15392
+ "sort_by": {
15393
+ "property": "title",
15394
+ "direction": "asc"
15395
+ },
15396
+ "colour_by": "group",
15397
+ "card_fields": [
15398
+ "title",
15399
+ "description"
15400
+ ]
15401
+ },
15402
+ "education": {
15403
+ "purpose": "Stress-test a plan against an explicit adversary by assigning reviewers to attack rather than agree, so weaknesses surface before reality finds them.",
15404
+ "core_question": "If a competent adversary wanted this to fail, where would they push first, and would we hold?",
15405
+ "when_to_use": [
15406
+ "A high-stakes decision, launch, or security posture needs hardening",
15407
+ "Inside-out thinking is dominant and dissent has gone quiet",
15408
+ "Risk register is suspiciously short for the size of the bet"
15409
+ ],
15410
+ "when_not_to_use": [
15411
+ "Early-stage exploration where adversarial framing would crush a fragile idea prematurely",
15412
+ "Team trust is too low: red-teaming will read as personal attack rather than role-play",
15413
+ "The work is small enough that a lightweight devil's-advocate pass is sufficient"
15414
+ ]
15415
+ }
15416
+ },
15417
+ {
15418
+ "id": "devils-advocate",
15419
+ "approach_ids": [
15420
+ "reflect"
15421
+ ],
15422
+ "name": "Devil's Advocate",
15423
+ "version": "1.0.0",
15424
+ "description": "Designate one reviewer to formally take the opposing position regardless of personal view. The assigned-role contrarian defangs groupthink by making dissent legitimate and structured.",
15425
+ "category": "team_process",
15426
+ "origin": {
15427
+ "type": "practitioner",
15428
+ "attribution": "Roman Catholic Church (advocatus diaboli); broadened by decision-quality practice",
15429
+ "description": "Originated in 16th-century canonisation proceedings as the advocatus diaboli, an official assigned to argue against canonising a candidate. Adopted by decision-science and product-team practice as a structured antidote to groupthink.",
15430
+ "year": 1587,
15431
+ "license": "public_domain"
15432
+ },
15433
+ "tags": [
15434
+ "team_process",
15435
+ "reflection",
15436
+ "decision_quality",
15437
+ "collection"
15438
+ ],
15439
+ "slots": [
15440
+ {
15441
+ "label": "Proposal",
15442
+ "entityTypeId": "initiative",
15443
+ "description": "The plan or recommendation under consideration."
15444
+ },
15445
+ {
15446
+ "label": "Opposing arguments",
15447
+ "entityTypeId": "insight",
15448
+ "description": "The case against the proposal, voiced by the assigned contrarian regardless of personal view."
15449
+ },
15450
+ {
15451
+ "label": "Counter-evidence",
15452
+ "entityTypeId": "evidence",
15453
+ "description": "Data points the contrarian raises that the proposal does not yet account for."
15454
+ }
15455
+ ],
15456
+ "data": {
15457
+ "entity_types": [
15458
+ {
15459
+ "type": "initiative",
15460
+ "role": "root"
15461
+ },
15462
+ {
15463
+ "type": "insight",
15464
+ "role": "bucket"
15465
+ },
15466
+ {
15467
+ "type": "evidence",
15468
+ "role": "bucket"
15469
+ }
15470
+ ],
15471
+ "required_properties": {}
15472
+ },
15473
+ "structure": {
15474
+ "pattern": "collection"
15475
+ },
15476
+ "presentation": {
15477
+ "layout": {
15478
+ "type": "grid",
15479
+ "groupBy": "type"
15480
+ },
15481
+ "sort_by": {
15482
+ "property": "title",
15483
+ "direction": "asc"
15484
+ },
15485
+ "colour_by": "group",
15486
+ "card_fields": [
15487
+ "title",
15488
+ "description"
15489
+ ]
15490
+ },
15491
+ "education": {
15492
+ "purpose": "Make dissent a legitimate role rather than a personal stance. Assigning one reviewer to argue against the proposal forces the team to confront the strongest counter-case.",
15493
+ "core_question": "If we had to argue against this proposal (not because we believe it, but because the role demands it) what is the strongest case?",
15494
+ "when_to_use": [
15495
+ "A decision is heading toward consensus and you suspect groupthink",
15496
+ "Stakes are high and the team has not heard a serious counter-argument",
15497
+ "Cultural norms make raw dissent costly; assigning the role lowers the social cost"
15498
+ ],
15499
+ "when_not_to_use": [
15500
+ "Genuine disagreement already exists in the room (let it surface; do not theatricalise it)",
15501
+ "The decision is small enough that the ceremony costs more than the insight returned",
15502
+ "The assigned contrarian will be punished socially for the role; set the norms first or skip"
15503
+ ]
15504
+ }
15505
+ },
15506
+ {
15507
+ "id": "second-order-thinking",
15508
+ "approach_ids": [
15509
+ "reflect"
15510
+ ],
15511
+ "name": "Second-order Thinking",
15512
+ "version": "1.0.0",
15513
+ "description": 'After deciding a move, ask "and then what?" repeatedly. Trace second-, third-, and higher-order consequences to surface downstream effects that first-order reasoning misses.',
15514
+ "category": "team_process",
15515
+ "origin": {
15516
+ "type": "practitioner",
15517
+ "attribution": "Howard Marks / Charlie Munger",
15518
+ "description": `Howard Marks distinguished first-order vs second-order thinking in The Most Important Thing (2011) as the essential discipline of consequential decision-making. Charlie Munger's "and then what?" framing is the practical heuristic.`,
15519
+ "url": "https://www.oaktreecapital.com/insights/memo/dare-to-be-great-ii",
15520
+ "year": 2011,
15521
+ "license": "published_methodology"
15522
+ },
15523
+ "tags": [
15524
+ "team_process",
15525
+ "reflection",
15526
+ "consequences",
15527
+ "tree"
15528
+ ],
15529
+ "slots": [
15530
+ {
15531
+ "label": "First-order move",
15532
+ "entityTypeId": "decision",
15533
+ "description": "The decision or move under consideration."
15534
+ },
15535
+ {
15536
+ "label": "Second-order consequences",
15537
+ "entityTypeId": "insight",
15538
+ "description": "Downstream effects that follow from the first-order move."
15539
+ },
15540
+ {
15541
+ "label": "Higher-order consequences",
15542
+ "entityTypeId": "insight",
15543
+ "description": "Third-, fourth-, fifth-order ripples: second-order consequences of the second-order consequences."
15544
+ }
15545
+ ],
15546
+ "data": {
15547
+ "entity_types": [
15548
+ {
15549
+ "type": "decision",
15550
+ "role": "root"
15551
+ },
15552
+ {
15553
+ "type": "insight",
15554
+ "role": "branch"
15555
+ }
15556
+ ],
15557
+ "required_properties": {}
15558
+ },
15559
+ "structure": {
15560
+ "pattern": "tree"
15561
+ },
15562
+ "presentation": {
15563
+ "layout": {
15564
+ "type": "tree",
15565
+ "direction": "TB"
15566
+ },
15567
+ "sort_by": {
15568
+ "property": "title",
15569
+ "direction": "asc"
15570
+ },
15571
+ "card_fields": [
15572
+ "title",
15573
+ "description"
15574
+ ]
15575
+ },
15576
+ "education": {
15577
+ "purpose": 'Resist first-order reasoning by chaining "and then what?" until non-obvious downstream consequences come into view.',
15578
+ "core_question": "If we make this move and it works, what does the world look like next, and is that the world we want?",
15579
+ "when_to_use": [
15580
+ "A decision has feedback loops, market reactions, or behavioural ripples",
15581
+ "The first-order case is compelling, which is exactly when downstream effects bite",
15582
+ "Considering an irreversible or large-scale commitment"
15583
+ ],
15584
+ "when_not_to_use": [
15585
+ "Routine, reversible, low-blast-radius decisions where deliberation costs more than mistakes",
15586
+ "Higher-order branches diverge into pure speculation with no anchor in evidence",
15587
+ "Time pressure makes a deeper trace expensive and the first-order call is good enough"
15588
+ ]
15589
+ }
15590
+ }
15591
+ ];
15592
+ var UPG_FRAMEWORKS_BY_ID = Object.fromEntries(
15593
+ UPG_FRAMEWORKS.map((fw) => [fw.id, fw])
15594
+ );
15595
+ var UPG_FRAMEWORKS_BY_CATEGORY = {};
15596
+ for (const fw of UPG_FRAMEWORKS) {
15597
+ if (!UPG_FRAMEWORKS_BY_CATEGORY[fw.category]) UPG_FRAMEWORKS_BY_CATEGORY[fw.category] = [];
15598
+ UPG_FRAMEWORKS_BY_CATEGORY[fw.category].push(fw);
15599
+ }
15600
+ var PRIORITY_LABELS = [
15601
+ // ── need (CONSOLIDATED: replaces pain_point + user_need) ─────────────────────
15602
+ {
15603
+ id: "need",
15604
+ canonical_label: "Need",
15605
+ alt_labels: [
15606
+ "pain point",
15607
+ "pain",
15608
+ "user need",
15609
+ "customer need",
15610
+ "problem",
15611
+ "struggle",
15612
+ "customer pain",
15613
+ "frustration",
15614
+ "gap",
15615
+ "unmet need",
15616
+ "user problem"
15617
+ ],
15618
+ framework_labels: {
15619
+ lean_canvas: "Problem",
15620
+ design_thinking: "Pain Point",
15621
+ ost: "Opportunity (need)",
15622
+ jtbd: "Struggle",
15623
+ vpc: "Customer Pain"
15624
+ },
15625
+ designations: {
15626
+ pain: "Pain Point",
15627
+ gap: "Need",
15628
+ desire: "Desire",
15629
+ constraint: "Constraint"
15630
+ }
15631
+ },
15632
+ // ── opportunity ──────────────────────────────────────────────────────────────
15633
+ {
15634
+ id: "opportunity",
15635
+ canonical_label: "Opportunity",
15636
+ alt_labels: ["product opportunity", "market opportunity", "user opportunity"],
15637
+ framework_labels: {
15638
+ ost: "Opportunity"
15639
+ }
15640
+ },
15641
+ // ── solution ─────────────────────────────────────────────────────────────────
15642
+ {
15643
+ id: "solution",
15644
+ canonical_label: "Solution",
15645
+ alt_labels: ["proposed solution", "solution idea", "concept", "approach"],
15646
+ framework_labels: {
15647
+ ost: "Solution",
15648
+ design_thinking: "Solution",
15649
+ lean_canvas: "Solution",
15650
+ rice: "Scored Solution"
15651
+ }
15652
+ },
15653
+ // ── experiment (CONSOLIDATED: absorbs ab_test, growth_experiment, pricing_experiment) ──
15654
+ {
15655
+ id: "experiment",
15656
+ canonical_label: "Experiment",
15657
+ alt_labels: [
15658
+ "test",
15659
+ "validation",
15660
+ "ab test",
15661
+ "a/b test",
15662
+ "split test",
15663
+ "growth experiment",
15664
+ "pricing experiment",
15665
+ "usability test",
15666
+ "discovery experiment"
15667
+ ],
15668
+ framework_labels: {
15669
+ ost: "Experiment",
15670
+ design_thinking: "Test",
15671
+ lean_startup: "Experiment"
15672
+ },
15673
+ designations: {
15674
+ discovery: "Discovery Experiment",
15675
+ ab_test: "A/B Test",
15676
+ growth: "Growth Experiment",
15677
+ pricing: "Pricing Experiment",
15678
+ usability: "Usability Test"
15679
+ }
15680
+ },
15681
+ // ── hypothesis ───────────────────────────────────────────────────────────────
15682
+ {
15683
+ id: "hypothesis",
15684
+ canonical_label: "Hypothesis",
15685
+ alt_labels: ["bet", "testable assumption", "leap of faith"],
15686
+ framework_labels: {
15687
+ lean_startup: "Hypothesis",
15688
+ running_lean: "Riskiest Assumption",
15689
+ lean_canvas: "Riskiest Assumption"
14770
15690
  }
14771
15691
  },
14772
15692
  // ── metric (CONSOLIDATED: absorbs kpi, north_star_metric, input_metric, metric_definition) ──
@@ -15614,7 +16534,10 @@ var STRATEGY_OUTCOMES_PLAYBOOK = {
15614
16534
  region: "strategy_outcomes",
15615
16535
  is_canonical: true,
15616
16536
  related_framework_ids: ["okr-framework", "three-horizons", "north-star-metric", "metrics-tree", "wardley-map"],
15617
- target_anchor_entity: "objective",
16537
+ // DT-PB-3: anchor is `outcome`, not `objective`. The creation_sequence
16538
+ // creates outcome (step 3) before objective (step 4), and outcome is the
16539
+ // strategy region's gravitational centre (objectives translate outcomes).
16540
+ target_anchor_entity: "outcome",
15618
16541
  creation_sequence: [
15619
16542
  seqStep(
15620
16543
  1,
@@ -15770,8 +16693,12 @@ var DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK = {
15770
16693
  seqStep(
15771
16694
  8,
15772
16695
  "Test",
15773
- ["experiment", "test_plan", "evidence"],
15774
- "Validate with targeted experiments. Close the loop between research and action."
16696
+ // DT-PB-1: was `experiment` which resolves to NO canonical edge with
16697
+ // hypothesis or test_plan, forcing an orphan. `experiment_run` is the
16698
+ // hypothesis-linked test unit (experiment_run_validates_hypothesis,
16699
+ // test_plan_ran_as_experiment_run, experiment_run_yields_evidence).
16700
+ ["experiment_run", "test_plan", "evidence"],
16701
+ "Validate with targeted experiment runs. Close the loop between research and action."
15775
16702
  )
15776
16703
  ]
15777
16704
  };
@@ -15818,8 +16745,8 @@ var MARKET_COMPETITIVE_PLAYBOOK = {
15818
16745
  seqStep(
15819
16746
  6,
15820
16747
  "Moves",
15821
- ["partnership"],
15822
- "Look where competitors are weak and trends are strong. That intersection is where your moves live, including who to partner with."
16748
+ ["competitive_battle_card"],
16749
+ "Look where competitors are weak and trends are strong. That intersection is where your moves live: arm the team with battle cards that turn each competitor weakness into a position you can win. (Partnership moves belong in the business & GTM playbook, where `partnership` connects.)"
15823
16750
  )
15824
16751
  ]
15825
16752
  };
@@ -16232,8 +17159,8 @@ var OPERATIONS_QUALITY_PLAYBOOK = {
16232
17159
  seqStep(
16233
17160
  5,
16234
17161
  "Quality Gates",
16235
- ["test_suite", "test_case", "regression_test", "qa_session"],
16236
- "Establish what does not ship until tests pass. Define the test pyramid: unit, integration, end-to-end."
17162
+ ["test_suite", "test_case", "regression_test", "qa_session", "feature", "bug"],
17163
+ "Establish what does not ship until tests pass. Define the test pyramid: unit, integration, end-to-end. Quality is a delivery concern: gates guard the features they cover and the bugs they catch, which is also where incidents trace back (a shipped defect becomes a production incident)."
16237
17164
  ),
16238
17165
  seqStep(
16239
17166
  6,
@@ -16941,6 +17868,9 @@ function getVisibleTypes(lens) {
16941
17868
  }
16942
17869
  return [...typeSet];
16943
17870
  }
17871
+ function getLensIds() {
17872
+ return UPG_LENSES.map((l) => l.id);
17873
+ }
16944
17874
  var UPG_DOMAIN_RINGS = [
16945
17875
  {
16946
17876
  id: "nucleus",
@@ -20221,7 +21151,7 @@ var UPG_ANTI_PATTERNS = [
20221
21151
  ]
20222
21152
  },
20223
21153
  why_it_matters: "Without any chain link, every downstream artefact (need, opportunity, feature) loses its anchor. Features end up addressing demographics instead of struggles.",
20224
- remediation: "For each persona, connect it into the user chain via at least one of: `persona_pursues_job`, `persona_experiences_need`, `persona_aspires_to_desired_outcome`, or `persona_incurs_switching_cost`. Use `/upg-persona` or the JTBD canvas workflow.",
21154
+ remediation: "For each persona, connect it into the user chain via at least one of: `persona_pursues_job`, `persona_experiences_need`, `persona_aspires_to_desired_outcome`, or `persona_incurs_switching_cost`. Use `/upg-new-persona` or the JTBD canvas workflow.",
20225
21155
  stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature"],
20226
21156
  severity: "high",
20227
21157
  source: { kind: "practitioner", attribution: "Clayton Christensen, Jobs to Be Done" }
@@ -20361,7 +21291,7 @@ var UPG_ANTI_PATTERNS = [
20361
21291
  ]
20362
21292
  },
20363
21293
  why_it_matters: "OKRs without measurable key results cannot be tracked, debated, or learned from. The graph carries intent but not accountability.",
20364
- remediation: "For each `objective`, define 2\u20134 `key_result` entities and link via `objective_achieved_through_key_result`. Use `/upg-okr` to author.",
21294
+ remediation: "For each `objective`, define 2\u20134 `key_result` entities and link via `objective_achieved_through_key_result`. Use `/upg-new-okr` to author.",
20365
21295
  stages: ["validation", "build", "beta", "launch", "growth", "mature"],
20366
21296
  severity: "high",
20367
21297
  source: { kind: "book", citation: "Measure What Matters, John Doerr (2017)" }
@@ -20421,7 +21351,7 @@ var UPG_ANTI_PATTERNS = [
20421
21351
  }
20422
21352
  },
20423
21353
  why_it_matters: 'A graph with one persona past validation is usually carrying an unexamined "everyone is the same user" assumption.',
20424
- remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-persona`.",
21354
+ remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-new-persona`.",
20425
21355
  stages: ["validation", "build", "beta", "launch", "growth", "mature"],
20426
21356
  severity: "medium",
20427
21357
  source: { kind: "practitioner", attribution: "Alan Cooper, The Inmates Are Running the Asylum" }
@@ -20439,7 +21369,7 @@ var UPG_ANTI_PATTERNS = [
20439
21369
  ]
20440
21370
  },
20441
21371
  why_it_matters: "Build-only graphs commit the team to delivery without a learning loop. Every shipped feature becomes a permanent assumption.",
20442
- remediation: "Spin up at least one `experiment_plan` or `hypothesis` per quarter's build batch. Use `/upg-discover` or `/upg-hypothesis`.",
21372
+ remediation: "Spin up at least one `experiment_plan` or `hypothesis` per quarter's build batch. Use `/upg-new-discovery` or `/upg-new-hypothesis`.",
20443
21373
  stages: ["build", "beta", "launch", "growth"],
20444
21374
  severity: "high",
20445
21375
  source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (continuous discovery)" }
@@ -20677,25 +21607,54 @@ var UPG_PRODUCT_STAGE_COERCION_MAP = Object.freeze({
20677
21607
  // check happens before the coercion lookup.
20678
21608
  });
20679
21609
  var UPG_PRODUCT_STAGES_SET = new Set(UPG_PRODUCT_STAGES);
21610
+ function coerceProductStage(value) {
21611
+ if (value === void 0 || value === null) {
21612
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: false };
21613
+ }
21614
+ if (typeof value !== "string") {
21615
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: true };
21616
+ }
21617
+ if (UPG_PRODUCT_STAGES_SET.has(value)) {
21618
+ return { canonical: value, originalValue: value, wasCoerced: false, wasUnknown: false };
21619
+ }
21620
+ const lower = value.toLowerCase();
21621
+ const mapped = UPG_PRODUCT_STAGE_COERCION_MAP[lower];
21622
+ if (mapped) {
21623
+ return { canonical: mapped, originalValue: value, wasCoerced: true, wasUnknown: false };
21624
+ }
21625
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: true };
21626
+ }
20680
21627
  var REFLECT_MODES = [
20681
21628
  "assumptions",
20682
21629
  "alternatives",
20683
21630
  "blind-spots",
20684
21631
  "load-bearing"
20685
21632
  ];
21633
+ function deriveFrameworkExamples() {
21634
+ const byApproach = {
21635
+ plan: [],
21636
+ inspect: [],
21637
+ prioritise: [],
21638
+ trace: [],
21639
+ reflect: []
21640
+ };
21641
+ for (const fw of UPG_FRAMEWORKS) {
21642
+ for (const approachId of fw.approach_ids ?? []) {
21643
+ if (approachId in byApproach) {
21644
+ byApproach[approachId].push(fw.id);
21645
+ }
21646
+ }
21647
+ }
21648
+ return byApproach;
21649
+ }
21650
+ var FRAMEWORK_EXAMPLES = deriveFrameworkExamples();
20686
21651
  var PLAN = {
20687
21652
  id: "plan",
20688
21653
  label: "Plan",
20689
21654
  description: 'The path of arrival to "what should I build next?". Plan engages a region by surveying its entity coverage against canonical expectations and surfacing the missing scaffolding: the entities a healthy region carries that this graph does not. Cartographic sense: you are walking the coastline of a region and noting where the contour is incomplete, not deciding a strategy. Frameworks like Now/Next/Later, MoSCoW, and Wardley Mapping live within Plan as the named techniques for organising the gap-filling sequence.',
20690
21655
  question_answered: "what should I build next?",
20691
21656
  signature_hint: "({ region?: UPGRegionId }) \u2192 { missing_entities, coverage_score }",
20692
- framework_id_examples: [
20693
- "now-next-later",
20694
- "moscow",
20695
- "wardley-map",
20696
- "okr-framework",
20697
- "three-horizons"
20698
- ]
21657
+ framework_id_examples: FRAMEWORK_EXAMPLES.plan
20699
21658
  };
20700
21659
  var INSPECT = {
20701
21660
  id: "inspect",
@@ -20703,28 +21662,15 @@ var INSPECT = {
20703
21662
  description: 'The path of arrival to "what\'s broken?". Inspect engages a region or a set of entities by running canonical health checks (anti-pattern audits, drift reports, lint passes) and emitting a structured violation list with severity, kind, target entity, description, and fix hint. Cartographic sense: you are surveying the coastline for hazards before approach; the violations are the rocks marked on the chart. The named techniques inside Inspect are the audit catalogues themselves (`UPG_ANTI_PATTERNS` and the lint passes built on the structural rules).',
20704
21663
  question_answered: "what's broken?",
20705
21664
  signature_hint: "({ region?: UPGRegionId, entities?: entity_ids[] }) \u2192 { violations: [{ severity, kind, entity_id, description, fix_hint }] }",
20706
- framework_id_examples: [
20707
- "heuristic-evaluation",
20708
- "tech-debt-tracker",
20709
- "accessibility-maturity-model",
20710
- "cognitive-walkthrough",
20711
- "blameless-postmortem"
20712
- ]
21665
+ framework_id_examples: FRAMEWORK_EXAMPLES.inspect
20713
21666
  };
20714
21667
  var PRIORITISE = {
20715
21668
  id: "prioritise",
20716
21669
  label: "Prioritise",
20717
- description: `The path of arrival to "what's most important?". Prioritise engages an explicit candidate set (entity ids the caller passes in) and ranks it by an explicit framework: RICE, ICE, Kano, Cost of Delay. The framework_id is required because prioritisation without a declared scoring lens is incoherent. Cartographic sense: you have a set of charted destinations and you are computing the order of arrival from a chosen vantage. Different frameworks weight the same candidate set differently; the approach delegates the actual ranking math to the named technique (the framework definition).`,
21670
+ description: `The path of arrival to "what's most important?". Prioritise engages an explicit candidate set (entity ids the caller passes in) and ranks it by an explicit framework: RICE, Kano, MoSCoW. The framework_id is required because prioritisation without a declared scoring lens is incoherent. Cartographic sense: you have a set of charted destinations and you are computing the order of arrival from a chosen vantage. Different frameworks weight the same candidate set differently; the approach delegates the actual ranking math to the named technique (the framework definition).`,
20718
21671
  question_answered: "what's most important?",
20719
21672
  signature_hint: "({ candidates: entity_ids[], framework_id }) \u2192 { ranked: [{ entity_id, score, rationale }], framework_used }",
20720
- framework_id_examples: [
20721
- "rice-scoring",
20722
- "ice-scoring",
20723
- "kano-model",
20724
- "cost-of-delay",
20725
- "moscow",
20726
- "wsjf"
20727
- ]
21673
+ framework_id_examples: FRAMEWORK_EXAMPLES.prioritise
20728
21674
  };
20729
21675
  var TRACE = {
20730
21676
  id: "trace",
@@ -20732,34 +21678,15 @@ var TRACE = {
20732
21678
  description: 'The path of arrival to "walk a meaningful path through existing graph". Trace engages an anchor entity and follows a path expressed as a UPGEntityType[] shorthand. Example: `["persona", "job", "feature"]` walks persona\u2192job\u2192feature using the canonical edge for each pair (resolved via `resolve_edge_for_pair`). An optional `edges_override` array selects non-canonical edges per hop when a pair has multiple resolutions. Cartographic sense: you are tracing a route across charted terrain; anchor is the departure, path is the heading sequence, the canonical edges are the roads. No DSL invented; the shorthand IS the path expression.',
20733
21679
  question_answered: "walk a meaningful path through existing graph",
20734
21680
  signature_hint: "({ anchor: entity_id, path: UPGEntityType[], edges_override?: (string | null)[] }) \u2192 { trail: [{ depth, entity_id, edge_type_in }], reached: entity_id[] }",
20735
- framework_id_examples: [
20736
- "opportunity-solution-tree",
20737
- "strategic-cascade",
20738
- "metrics-tree",
20739
- "user-journey-map",
20740
- "impact-map",
20741
- "dependency-map"
20742
- ]
21681
+ framework_id_examples: FRAMEWORK_EXAMPLES.trace
20743
21682
  };
20744
21683
  var REFLECT = {
20745
21684
  id: "reflect",
20746
21685
  label: "Reflect",
20747
- description: 'The path of arrival to "what should I be questioning?". Reflect engages an optional scope (region, entity, or `null` for the whole graph) and emits structured prompts a thinker should consider: assumptions to test, alternatives to weigh, blind-spots to surface, load-bearing claims to verify. Mode is optional; absence is open reflection. Cartographic sense: before approaching the coastline, you are asking which features of your chart you have not actually verified; the prompts mark the parts of the map that may be conjecture. Five Whys, Pre-mortem, Red Team, and Devil\'s Advocate are the named techniques inside this approach.',
21686
+ description: 'The path of arrival to "what should I be questioning?". Reflect engages an optional scope (region, entity, or `null` for the whole graph) and emits structured prompts a thinker should consider: assumptions to test, alternatives to weigh, blind-spots to surface, load-bearing claims to verify. Mode is optional; absence is open reflection. Cartographic sense: before approaching the coastline, you are asking which features of your chart you have not actually verified; the prompts mark the parts of the map that may be conjecture. Retrospective and Build-Measure-Learn are the named reflective techniques in the canonical surface.',
20748
21687
  question_answered: "what should I be questioning?",
20749
21688
  signature_hint: "({ scope?: UPGRegionId | entity_id | null, mode?: 'assumptions' | 'alternatives' | 'blind-spots' | 'load-bearing' }) \u2192 { prompts: [{ kind, question, target_entities? }] }",
20750
- framework_id_examples: [
20751
- // Reflection classics: the five canonical reflect frameworks.
20752
- "five-whys",
20753
- "pre-mortem",
20754
- "red-team",
20755
- "devils-advocate",
20756
- "second-order-thinking",
20757
- // Reflective ceremonies + reflective JTBD lens already in the catalog.
20758
- "retrospective",
20759
- "four-forces-of-progress",
20760
- "assumption-canvas",
20761
- "win-loss-analysis"
20762
- ]
21689
+ framework_id_examples: FRAMEWORK_EXAMPLES.reflect
20763
21690
  };
20764
21691
  var UPG_APPROACHES = [PLAN, INSPECT, PRIORITISE, TRACE, REFLECT];
20765
21692
  var UPG_APPROACHES_BY_ID = Object.fromEntries(
@@ -22687,7 +23614,7 @@ function portfolioBody(doc) {
22687
23614
  function computeBodyChecksum(doc) {
22688
23615
  const body = isPortfolio(doc) ? portfolioBody(doc) : singleBody(doc);
22689
23616
  const content = JSON.stringify(body);
22690
- return createHash2(INTEGRITY_HASH_PRIMITIVE).update(content).digest("hex").slice(0, INTEGRITY_DIGEST_HEX);
23617
+ return createHash(INTEGRITY_HASH_PRIMITIVE).update(content).digest("hex").slice(0, INTEGRITY_DIGEST_HEX);
22691
23618
  }
22692
23619
  function isPortfolio(doc) {
22693
23620
  return doc.type === "portfolio" || "cross_edges" in doc;
@@ -22753,7 +23680,7 @@ function serializePortfolioWithHeader(doc, opts) {
22753
23680
  header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
22754
23681
  return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
22755
23682
  }
22756
- var UPG_VERSION = "0.8.0";
23683
+ var UPG_VERSION = "0.8.2";
22757
23684
  var MARKDOWN_FORMAT_VERSION = "0.1";
22758
23685
  var UPG_TYPES = getTypes();
22759
23686
  var UPG_TYPES_SET = new Set(UPG_TYPES);
@@ -22813,6 +23740,52 @@ var UPG_DOMAIN_COUNT = UPG_DOMAINS.length;
22813
23740
  var UPG_EDGE_COUNT = UPG_EDGE_TYPES.length;
22814
23741
  var UPG_META_COUNT = UPG_ENTITY_META.length;
22815
23742
 
23743
+ // src/lib/server-context.ts
23744
+ function text(s) {
23745
+ return { content: [{ type: "text", text: s }] };
23746
+ }
23747
+ function textError(s) {
23748
+ return { content: [{ type: "text", text: s }], isError: true };
23749
+ }
23750
+ var CANONICAL_LENS_IDS = getLensIds();
23751
+ function isCanonicalLens(id) {
23752
+ return typeof id === "string" && CANONICAL_LENS_IDS.includes(id);
23753
+ }
23754
+ function createSessionContext() {
23755
+ return {
23756
+ lens: "product",
23757
+ skills_invoked: [],
23758
+ recommendations_given: [],
23759
+ focus_area: null,
23760
+ custom: {}
23761
+ };
23762
+ }
23763
+ function createQueryCache() {
23764
+ return { entries: /* @__PURE__ */ new Map(), counter: 0 };
23765
+ }
23766
+ function syncFilePath(upgPath) {
23767
+ const dir = path.dirname(upgPath);
23768
+ const base = path.basename(upgPath, ".upg");
23769
+ return path.join(dir, `${base}.upg-sync`);
23770
+ }
23771
+ async function readSyncState(upgPath) {
23772
+ const p = syncFilePath(upgPath);
23773
+ try {
23774
+ const raw = await fsp.readFile(p, "utf-8");
23775
+ return JSON.parse(raw);
23776
+ } catch {
23777
+ return null;
23778
+ }
23779
+ }
23780
+ async function writeSyncState(upgPath, state) {
23781
+ const p = syncFilePath(upgPath);
23782
+ await fsp.writeFile(p, JSON.stringify(state, null, 2) + "\n", "utf-8");
23783
+ }
23784
+ async function hashFile(filePath) {
23785
+ const content = await fsp.readFile(filePath, "utf-8");
23786
+ return createHash2("sha256").update(content).digest("hex");
23787
+ }
23788
+
22816
23789
  // src/tools/context.ts
22817
23790
  import { computeGraphDigest } from "@unified-product-graph/sdk";
22818
23791
  function lensAwareLabel(entityType, lensId) {
@@ -22870,7 +23843,7 @@ Lens: ${sessionContext.lens}`
22870
23843
  lines.push(` - [${sev}] ${b.title}`);
22871
23844
  }
22872
23845
  }
22873
- } else if (sessionContext.lens === "design") {
23846
+ } else if (sessionContext.lens === "ux_design") {
22874
23847
  const screens = nodes.filter((n) => n.type === "screen");
22875
23848
  const components = nodes.filter((n) => n.type === "design_component");
22876
23849
  const flows = nodes.filter((n) => n.type === "user_flow");
@@ -22985,7 +23958,7 @@ var getGraphDigest = (args, ctx) => {
22985
23958
  const blockedFeatures = allNodes.filter((n) => blockedFeatureIds.has(n.id)).map((n) => n.title);
22986
23959
  const openInvestigations = allNodes.filter((n) => n.type === "investigation" && n.status !== "resolved").length;
22987
23960
  lensDigest = { open_bugs: openBugs, blockers: blockerEdges.length, in_flight_features: inFlightFeatures, active_debt: activeDebt, blocked_features: blockedFeatures, open_investigations: openInvestigations };
22988
- } else if (sessionContext.lens === "design") {
23961
+ } else if (sessionContext.lens === "ux_design") {
22989
23962
  const screens = allNodes.filter((n) => n.type === "screen").length;
22990
23963
  const components = allNodes.filter((n) => n.type === "design_component").length;
22991
23964
  const flows = allNodes.filter((n) => n.type === "user_flow").length;
@@ -23103,7 +24076,12 @@ var updateSessionContext = (args, ctx) => {
23103
24076
  if (focusArea !== void 0) {
23104
24077
  sessionContext.focus_area = focusArea;
23105
24078
  }
23106
- if (lensArg && ["product", "engineering", "design", "growth"].includes(lensArg)) {
24079
+ if (lensArg !== void 0) {
24080
+ if (!isCanonicalLens(lensArg)) {
24081
+ return textError(
24082
+ `Invalid lens "${lensArg}". Canonical lenses: ${CANONICAL_LENS_IDS.join(", ")}`
24083
+ );
24084
+ }
23107
24085
  sessionContext.lens = lensArg;
23108
24086
  if (persistLens && store) {
23109
24087
  const doc = store.getDocument();
@@ -23913,7 +24891,7 @@ var updateNode = (args, ctx) => {
23913
24891
  const existingNode = store.getNode(nid);
23914
24892
  if (existingNode) {
23915
24893
  const sw = validateStatusAgainstLifecycle(existingNode.type, args.status);
23916
- if (sw) warnings.push(sw);
24894
+ if (sw) return textError(sw);
23917
24895
  }
23918
24896
  }
23919
24897
  let unknownProperties = [];
@@ -23946,10 +24924,18 @@ var updateNode = (args, ctx) => {
23946
24924
  });
23947
24925
  if (lengthWarnings.length > 0) warnings.push(...lengthWarnings);
23948
24926
  try {
23949
- const updated = store.updateNode(nid, patch);
24927
+ let updated = store.updateNode(nid, patch);
24928
+ let removedKeys;
24929
+ const unsetArg = args.unset_properties;
24930
+ if (Array.isArray(unsetArg) && unsetArg.length > 0) {
24931
+ const r = store.unsetNodeProperties(nid, unsetArg);
24932
+ updated = r.node;
24933
+ if (r.removed.length > 0) removedKeys = r.removed;
24934
+ }
23950
24935
  const result = { node: updated };
23951
24936
  if (warnings.length > 0) result.warning = warnings.join(" | ");
23952
24937
  if (unknownProperties.length > 0) result.unknown_properties = unknownProperties;
24938
+ if (removedKeys && removedKeys.length > 0) result.unset = removedKeys;
23953
24939
  return text(JSON.stringify(result, null, 2));
23954
24940
  } catch (err) {
23955
24941
  return textError(err.message);
@@ -24271,7 +25257,7 @@ var deduplicateNodes = (args, ctx) => {
24271
25257
  // src/tools/edges.ts
24272
25258
  import { edgeId as edgeId2 } from "@unified-product-graph/sdk";
24273
25259
  import { inferEdgeTypeWithTier } from "@unified-product-graph/sdk";
24274
- import { validateEdgeTypePair } from "@unified-product-graph/sdk";
25260
+ import { validateExplicitEdgeType } from "@unified-product-graph/sdk";
24275
25261
  import { buildResolverHints } from "@unified-product-graph/sdk";
24276
25262
  import {
24277
25263
  createEdge as createEdgeLib,
@@ -24378,13 +25364,13 @@ var batchCreateEdges = (args, ctx) => {
24378
25364
  );
24379
25365
  }
24380
25366
  if (e.type) {
24381
- const pairCheck = validateEdgeTypePair(
25367
+ const typeCheck = validateExplicitEdgeType(
24382
25368
  e.type,
24383
25369
  sourceNode.type,
24384
25370
  targetNode.type
24385
25371
  );
24386
- if (!pairCheck.valid) {
24387
- return textError(`Edge at index ${i}: ${pairCheck.reason}`);
25372
+ if (typeCheck.errors.length > 0) {
25373
+ return textError(`Edge at index ${i}: ${typeCheck.errors.join(" ")}`);
24388
25374
  }
24389
25375
  resolvedEdgeTypes.push(e.type);
24390
25376
  } else {
@@ -24832,10 +25818,11 @@ var listLocalProducts = (_args, _ctx) => {
24832
25818
  try {
24833
25819
  const raw = fs.readFileSync(filePath, "utf-8");
24834
25820
  const doc = JSON.parse(raw);
25821
+ const coerced = coerceProductStage(doc.product?.stage);
24835
25822
  products.push({
24836
25823
  file: path3.relative(cwd, filePath),
24837
25824
  title: doc.product?.title ?? "(untitled)",
24838
- stage: doc.product?.stage ?? "unknown",
25825
+ stage: coerced.canonical ?? null,
24839
25826
  nodes: Array.isArray(doc.nodes) ? doc.nodes.length : 0,
24840
25827
  edges: Array.isArray(doc.edges) ? doc.edges.length : 0
24841
25828
  });
@@ -25211,7 +26198,7 @@ import {
25211
26198
  // src/tools/validation.ts
25212
26199
  import { computeSchemaDriftSummary } from "@unified-product-graph/sdk";
25213
26200
  import { collectAntiPatternInputs } from "@unified-product-graph/sdk";
25214
- import { validateEdgeTypePair as validateEdgeTypePair2 } from "@unified-product-graph/sdk";
26201
+ import { validateEdgeTypePair } from "@unified-product-graph/sdk";
25215
26202
  import { checkPropertyTypes as checkPropertyTypes2 } from "@unified-product-graph/sdk";
25216
26203
  var CANONICAL_NODE_FIELDS = /* @__PURE__ */ new Set([
25217
26204
  "id",
@@ -25503,7 +26490,7 @@ var validateGraph = (args, ctx) => {
25503
26490
  const sourceNode = nodeById.get(edge.source);
25504
26491
  const targetNode = nodeById.get(edge.target);
25505
26492
  if (!sourceNode || !targetNode) continue;
25506
- const pairCheck = validateEdgeTypePair2(
26493
+ const pairCheck = validateEdgeTypePair(
25507
26494
  edge.type,
25508
26495
  sourceNode.type,
25509
26496
  targetNode.type
@@ -25757,10 +26744,13 @@ function approachEnvelope(approachId, scope, payload) {
25757
26744
  }
25758
26745
  var plan = (args, ctx) => {
25759
26746
  const region = args.region;
25760
- const result = executePlan(ctx.store, region);
26747
+ const exhaustive = args.exhaustive;
26748
+ const result = executePlan(ctx.store, { region, exhaustive });
25761
26749
  return approachEnvelope("plan", region ?? null, {
25762
- params: { region: region ?? null },
26750
+ params: { region: region ?? null, exhaustive: exhaustive ?? false },
25763
26751
  region: result.region,
26752
+ plan_scope: result.scope,
26753
+ scoped_regions: result.scoped_regions,
25764
26754
  missing_entities: result.missing_entities,
25765
26755
  coverage_score: result.coverage_score,
25766
26756
  expected_count: result.expected_count,
@@ -26782,9 +27772,14 @@ function auditOne(name) {
26782
27772
  };
26783
27773
  }
26784
27774
  function allSkillNames() {
26785
- const dir = sourceSkillsDir();
26786
- if (!existsSync2(dir)) return [];
26787
- return readdirSync2(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
27775
+ const names = /* @__PURE__ */ new Set();
27776
+ for (const dir of [sourceSkillsDir(), deployedSkillsDir()]) {
27777
+ if (!existsSync2(dir)) continue;
27778
+ for (const d of readdirSync2(dir, { withFileTypes: true })) {
27779
+ if (d.isDirectory() || d.isSymbolicLink()) names.add(d.name);
27780
+ }
27781
+ }
27782
+ return [...names].sort();
26788
27783
  }
26789
27784
  var skillAudit = (args) => {
26790
27785
  const filter = typeof args?.name === "string" && args.name.length > 0 ? args.name : null;
@@ -26974,6 +27969,11 @@ var TOOL_DEFINITIONS = [
26974
27969
  properties: {
26975
27970
  type: "object",
26976
27971
  description: "Merged with existing properties"
27972
+ },
27973
+ unset_properties: {
27974
+ type: "array",
27975
+ items: { type: "string" },
27976
+ description: "Property keys to DELETE. Applied after the `properties` merge, so one call can set some keys and drop others. Writing `{ key: null }` only stores a literal null; use this to actually remove a key. Unknown keys are ignored."
26977
27977
  }
26978
27978
  },
26979
27979
  required: ["node_id"]
@@ -27456,17 +28456,18 @@ var TOOL_DEFINITIONS = [
27456
28456
  },
27457
28457
  {
27458
28458
  name: "plan",
27459
- description: 'Plan approach: path of arrival to "what should I build next?". Returns the Plan record + invocation params wrapped in `{ approach_id, scope, generated_at, approach, params }`. The LLM consumes `signature_hint` and synthesises `{ missing_entities, coverage_score }` against the live graph. Optional `region` narrows scope.',
28459
+ description: 'Plan approach: path of arrival to "what should I build next?". Returns the Plan record + invocation params wrapped in `{ approach_id, scope, generated_at, approach, params }`. The LLM consumes `signature_hint` and synthesises `{ missing_entities, coverage_score }` against the live graph. Optional `region` narrows scope; omit `region` to scope to the product\'s ACTIVE regions; pass `exhaustive:true` to score the full type universe (UPG-601).',
27460
28460
  inputSchema: {
27461
28461
  type: "object",
27462
28462
  properties: {
27463
- region: { type: "string", description: 'Optional UPGRegionId. Narrows planning scope to a single region (e.g. "users_needs", "business_gtm_growth"). Omit for whole-graph planning.' }
28463
+ region: { type: "string", description: `Optional UPGRegionId or atomic-domain id. Narrows planning scope to a single region (e.g. "users_needs", "business_gtm_growth"). Omit to scope to the product's active regions.` },
28464
+ exhaustive: { type: "boolean", description: "If true, score against the entire 312-type universe (every domain creation sequence). Off by default; whole-universe gap scoring is noisy for a focused product. Only applies when `region` is omitted." }
27464
28465
  }
27465
28466
  }
27466
28467
  },
27467
28468
  {
27468
28469
  name: "inspect",
27469
- description: '[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing inspection, invoke the /upg-inspect skill instead of calling this tool directly. Inspect approach: path of arrival to "what\'s broken?". Returns the Inspect record + invocation params in the family-resemblance envelope. The LLM consumes `signature_hint` and emits `{ violations: [{ severity, kind, entity_id, description, fix_hint }] }` against `UPG_ANTI_PATTERNS` + the live graph. Optional `region` or `entities[]` scope the audit.',
28470
+ description: '[LLM-mediated] This tool returns a routing envelope, not computed results. For user-facing inspection, invoke the /upg-show-entity 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.',
27470
28471
  inputSchema: {
27471
28472
  type: "object",
27472
28473
  properties: {
@@ -27970,10 +28971,10 @@ var TOOL_DEFINITIONS = [
27970
28971
  inputSchema: {
27971
28972
  type: "object",
27972
28973
  properties: {
27973
- skill_invoked: { type: "string", description: 'Register that this skill was just invoked (e.g. "upg-status")' },
27974
- recommendation: { type: "string", description: 'Record a recommendation given to the user (e.g. "Run /upg-strategy to fill strategy gap")' },
28974
+ skill_invoked: { type: "string", description: 'Register that this skill was just invoked (e.g. "upg-show-status")' },
28975
+ recommendation: { type: "string", description: 'Record a recommendation given to the user (e.g. "Run /upg-new-strategy to fill strategy gap")' },
27975
28976
  focus_area: { type: "string", description: 'Set the current focus area (e.g. "strategy", "validation", "user_research")' },
27976
- lens: { type: "string", enum: ["product", "engineering", "design", "growth"], description: "Switch the active lens. Changes what context, skills, and gaps are surfaced first." },
28977
+ lens: { type: "string", enum: [...CANONICAL_LENS_IDS], description: "Switch the active lens. Changes what context, skills, and gaps are surfaced first. Canonical lens ids (derived from core): product, ux_design, engineering, growth, business, research, marketing, full." },
27977
28978
  persist_lens: { type: "boolean", description: "If true, also save the lens to the .upg file so it persists across sessions" },
27978
28979
  custom: { type: "object", description: "Arbitrary key-value pairs for cross-skill state" }
27979
28980
  }
@@ -28301,7 +29302,7 @@ function createServer(store) {
28301
29302
  {
28302
29303
  const doc = store.getDocument();
28303
29304
  const persistedLens = doc.product?.lens;
28304
- if (persistedLens && ["product", "engineering", "design", "growth"].includes(persistedLens)) {
29305
+ if (persistedLens && isCanonicalLens(persistedLens)) {
28305
29306
  sessionContext.lens = persistedLens;
28306
29307
  }
28307
29308
  }
@@ -28480,7 +29481,7 @@ async function runMcpServer() {
28480
29481
  Deprecated types found in your graph:
28481
29482
  ${lines.join("\n")}
28482
29483
  `);
28483
- process.stderr.write(`Run /upg-migrate to update them.
29484
+ process.stderr.write(`Run /upg-fix-types to update them.
28484
29485
 
28485
29486
  `);
28486
29487
  }