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

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 +22 -0
  2. package/README.md +1 -1
  3. package/TOOLS.md +79 -16
  4. package/dist/index.js +1440 -290
  5. package/dist/index.js.map +1 -1
  6. package/dist/tools-manifest.json +197 -82
  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",
@@ -551,8 +511,8 @@ var UPG_DOMAINS = [
551
511
  {
552
512
  id: "workspace",
553
513
  label: "Workspace",
554
- description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure.",
555
- types: ["workspace"]
514
+ description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure. A framework exercise is a structured workspace: one run of a framework (MoSCoW, RICE, Kano, \u2026) applied to a chosen set of entities, with each entity's result recorded on the exercise-to-entity edge rather than the entity itself.",
515
+ types: ["workspace", "framework_exercise"]
556
516
  }
557
517
  ];
558
518
  var UPG_ENTITY_TO_DOMAIN = Object.freeze(
@@ -973,7 +933,8 @@ var UPG_ENTITY_META = [
973
933
  { name: "integration_partner", type_id: "ent_311", maturity: "stable", since: "0.1.0" },
974
934
  { name: "partner_revenue_share", type_id: "ent_312", maturity: "stable", since: "0.1.0" },
975
935
  // ── Workspace ──
976
- { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" }
936
+ { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
937
+ { name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" }
977
938
  ];
978
939
  var UPG_ENTITY_META_BY_NAME = new Map(
979
940
  UPG_ENTITY_META.map((m) => [m.name, m])
@@ -993,7 +954,7 @@ var UPG_EDGE_CATALOG = {
993
954
  // product → persona is the most fundamental relationship in the
994
955
  // user domain ("who is this product for?") and was the only way to
995
956
  // anchor a fresh persona to its product before this edge existed. Without
996
- // it, the natural agent path through `/upg-persona` produced an orphan
957
+ // it, the natural agent path through `/upg-new-persona` produced an orphan
997
958
  // persona only attached laterally via `ideal_customer_profile_maps_to_persona`
998
959
  // or `positioning_resonates_with_persona`. Semantic, not hierarchy: a
999
960
  // product doesn't "contain" personas; it targets them.
@@ -2113,6 +2074,18 @@ var UPG_EDGE_CATALOG = {
2113
2074
  // universal across the DDD type family. Verbs follow DDD literature:
2114
2075
  // aggregate belongs_to context, context contains aggregates.
2115
2076
  node_belongs_to_bounded_context: { forward_verb: "belongs_to", reverse_verb: "contains", classification: "cross-domain", source_type: "node", target_type: "bounded_context" },
2077
+ // ── Cross-domain: Framework exercises ────────────────────────────────────────
2078
+ // A framework_exercise is one run of a framework (MoSCoW, RICE, Kano, …) over a
2079
+ // set of entities. It `includes` each entity it touches; the framework's
2080
+ // per-entity result — a MoSCoW bucket, a RICE score, a canvas slot, a funnel
2081
+ // stage — rides on this edge's `properties`, NOT on the entity node. A value
2082
+ // that exists only within a specific exercise is a fact about the relationship,
2083
+ // not the entity, so it lives on the edge (same principle as owner-as-edge).
2084
+ // Polymorphic (`target_type: 'node'`): an exercise can include any entity type,
2085
+ // which closes the feature-only limitation structurally. `carries_properties`
2086
+ // opts this edge into the gated edge-property model. See ADR
2087
+ // 2026-06-02-framework-exercises.
2088
+ framework_exercise_includes_node: { forward_verb: "includes", reverse_verb: "included_in", classification: "cross-domain", source_type: "framework_exercise", target_type: "node", carries_properties: true },
2116
2089
  // ── New edges replacing deleted string properties ────────────────────
2117
2090
  // Marketing
2118
2091
  marketing_strategy_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "cross-domain", source_type: "marketing_strategy", target_type: "outcome" },
@@ -2208,6 +2181,13 @@ var UPG_EDGE_CATALOG = {
2208
2181
  // release strategy to the concrete deployments it governs.
2209
2182
  incident_affects_feature: { forward_verb: "affects", reverse_verb: "affected_by", classification: "cross-domain", source_type: "incident", target_type: "feature" },
2210
2183
  release_strategy_used_by_deployment: { forward_verb: "used_by", reverse_verb: "uses", classification: "cross-domain", source_type: "release_strategy", target_type: "deployment" },
2184
+ // v0.8.2 (UPG-615): ITIL/ITSM incident management explicitly links an incident
2185
+ // to the customer-facing support tickets/cases it spawns — when a service
2186
+ // breaks, customers raise tickets; the incident is the cause, the ticket the
2187
+ // effect. Closes the otherwise-unmediated "Customer Support" island in
2188
+ // `playbook:operations-quality`, binding `support_ticket` to the incident/ops
2189
+ // spine. Source: ITIL v4 incident management; DORA/SRE customer-facing impact.
2190
+ incident_generates_support_ticket: { forward_verb: "generates", reverse_verb: "generated_by", classification: "cross-domain", source_type: "incident", target_type: "support_ticket" },
2211
2191
  // Cluster C: User Research linkage matrix.
2212
2192
  // research_study → {participant, research_question, survey_response,
2213
2193
  // interview_guide} containment edges already exist (49/49 in Wave 4).
@@ -2735,7 +2715,9 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
2735
2715
  "node_owned_by_department",
2736
2716
  "node_owned_by_person",
2737
2717
  // Universal architecture references
2738
- "node_belongs_to_bounded_context"
2718
+ "node_belongs_to_bounded_context",
2719
+ // Framework exercises (an exercise can include any entity type)
2720
+ "framework_exercise_includes_node"
2739
2721
  ];
2740
2722
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
2741
2723
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -5658,6 +5640,31 @@ var TEMPLATE_LIFECYCLES = [
5658
5640
  // ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
5659
5641
  fromTemplate("deal", SALES_DEAL_TEMPLATE)
5660
5642
  ];
5643
+ var FRAMEWORK_EXERCISE_LIFECYCLE = {
5644
+ entity_type: "framework_exercise",
5645
+ initial_phase: "draft",
5646
+ terminal_phases: ["archived"],
5647
+ phases: [
5648
+ {
5649
+ id: "draft",
5650
+ label: "Draft",
5651
+ description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
5652
+ transitions_to: ["active", "archived"]
5653
+ },
5654
+ {
5655
+ id: "active",
5656
+ label: "Active",
5657
+ description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
5658
+ transitions_to: ["archived"]
5659
+ },
5660
+ {
5661
+ id: "archived",
5662
+ label: "Archived",
5663
+ description: "A past run, retained for provenance. Still queryable but superseded by a newer exercise and hidden from default views. Can be revived to active.",
5664
+ transitions_to: ["active"]
5665
+ }
5666
+ ]
5667
+ };
5661
5668
  var UPG_LIFECYCLES = [
5662
5669
  // Product (root)
5663
5670
  PRODUCT_LIFECYCLE,
@@ -5759,6 +5766,8 @@ var UPG_LIFECYCLES = [
5759
5766
  // Product & Growth
5760
5767
  VARIANT_LIFECYCLE,
5761
5768
  GROWTH_CAMPAIGN_LIFECYCLE,
5769
+ // Workspace
5770
+ FRAMEWORK_EXERCISE_LIFECYCLE,
5762
5771
  // ── Template-generated lifecycles ─────────────────────────────────
5763
5772
  ...TEMPLATE_LIFECYCLES
5764
5773
  ];
@@ -9236,6 +9245,11 @@ var UPG_PROPERTY_SCHEMA = {
9236
9245
  },
9237
9246
  methodology: { type: "string", description: "Forecasting methodology used" }
9238
9247
  },
9248
+ // FrameworkExerciseProperties: Framework exercise: one run of a framework over a set of entities.
9249
+ framework_exercise: {
9250
+ framework_id: { type: "string", description: "Which framework this exercise runs: a framework id (e.g. 'moscow', 'rice-scoring', 'kano-model'). Resolves against the framework catalog." },
9251
+ inputs_snapshot: { type: "object", description: "Optional frozen copy of the framework's input spec at apply time, so a historical exercise still renders correctly if the framework definition later evolves (inputs added, removed, or rescaled)." }
9252
+ },
9239
9253
  // FunnelProperties: Funnel entity.
9240
9254
  funnel: {
9241
9255
  funnel_type: { type: "string", enum: ["acquisition", "activation", "retention", "revenue", "referral", "custom"], description: "Which stage of the customer lifecycle this funnel measures" },
@@ -11261,6 +11275,9 @@ var UPG_FRAMEWORKS = [
11261
11275
  },
11262
11276
  {
11263
11277
  "id": "value-proposition-canvas",
11278
+ "approach_ids": [
11279
+ "trace"
11280
+ ],
11264
11281
  "name": "Value Proposition Canvas",
11265
11282
  "version": "1.0.0",
11266
11283
  "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 +11382,9 @@ var UPG_FRAMEWORKS = [
11365
11382
  },
11366
11383
  {
11367
11384
  "id": "persona-canvas",
11385
+ "approach_ids": [
11386
+ "trace"
11387
+ ],
11368
11388
  "name": "Persona Canvas",
11369
11389
  "version": "1.0.0",
11370
11390
  "description": "Demographics, goals, frustrations, JTBD: a structured template for creating research-backed personas.",
@@ -11477,6 +11497,9 @@ var UPG_FRAMEWORKS = [
11477
11497
  },
11478
11498
  {
11479
11499
  "id": "empathy-map",
11500
+ "approach_ids": [
11501
+ "trace"
11502
+ ],
11480
11503
  "name": "Empathy Map",
11481
11504
  "version": "1.0.0",
11482
11505
  "description": "Visualise what a user says, thinks, does, and feels to build deeper empathy and uncover hidden needs.",
@@ -11637,6 +11660,7 @@ var UPG_FRAMEWORKS = [
11637
11660
  "property": "evolution_stage",
11638
11661
  "type": "enum",
11639
11662
  "required": true,
11663
+ "scope": "framework",
11640
11664
  "label": "Evolution Stage",
11641
11665
  "description": "Where this component sits on the evolution axis",
11642
11666
  "enum_values": [
@@ -11650,6 +11674,7 @@ var UPG_FRAMEWORKS = [
11650
11674
  "property": "visibility",
11651
11675
  "type": "number",
11652
11676
  "required": true,
11677
+ "scope": "framework",
11653
11678
  "label": "Visibility",
11654
11679
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11655
11680
  }
@@ -11659,6 +11684,7 @@ var UPG_FRAMEWORKS = [
11659
11684
  "property": "evolution_stage",
11660
11685
  "type": "enum",
11661
11686
  "required": true,
11687
+ "scope": "framework",
11662
11688
  "label": "Evolution Stage",
11663
11689
  "description": "Where this component sits on the evolution axis",
11664
11690
  "enum_values": [
@@ -11672,6 +11698,7 @@ var UPG_FRAMEWORKS = [
11672
11698
  "property": "visibility",
11673
11699
  "type": "number",
11674
11700
  "required": true,
11701
+ "scope": "framework",
11675
11702
  "label": "Visibility",
11676
11703
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11677
11704
  }
@@ -11681,6 +11708,7 @@ var UPG_FRAMEWORKS = [
11681
11708
  "property": "evolution_stage",
11682
11709
  "type": "enum",
11683
11710
  "required": true,
11711
+ "scope": "framework",
11684
11712
  "label": "Evolution Stage",
11685
11713
  "description": "Where this component sits on the evolution axis",
11686
11714
  "enum_values": [
@@ -11694,6 +11722,7 @@ var UPG_FRAMEWORKS = [
11694
11722
  "property": "visibility",
11695
11723
  "type": "number",
11696
11724
  "required": true,
11725
+ "scope": "framework",
11697
11726
  "label": "Visibility",
11698
11727
  "description": "Y-axis position (0=infrastructure, 1=anchor/user)"
11699
11728
  }
@@ -11703,6 +11732,7 @@ var UPG_FRAMEWORKS = [
11703
11732
  "property": "evolution_stage",
11704
11733
  "type": "enum",
11705
11734
  "required": true,
11735
+ "scope": "framework",
11706
11736
  "label": "Evolution Stage",
11707
11737
  "description": 'Where this component sits on the evolution axis (need anchor is usually at "product" or "commodity")',
11708
11738
  "enum_values": [
@@ -11716,6 +11746,7 @@ var UPG_FRAMEWORKS = [
11716
11746
  "property": "visibility",
11717
11747
  "type": "number",
11718
11748
  "required": true,
11749
+ "scope": "framework",
11719
11750
  "label": "Visibility",
11720
11751
  "description": "Y-axis position (0=infrastructure, 1=anchor/user); needs sit at 1.0"
11721
11752
  }
@@ -11756,6 +11787,9 @@ var UPG_FRAMEWORKS = [
11756
11787
  },
11757
11788
  {
11758
11789
  "id": "business-model-canvas",
11790
+ "approach_ids": [
11791
+ "plan"
11792
+ ],
11759
11793
  "name": "Business Model Canvas",
11760
11794
  "version": "1.0.0",
11761
11795
  "description": "Nine building blocks that describe how an organisation creates, delivers, and captures value.",
@@ -11895,6 +11929,9 @@ var UPG_FRAMEWORKS = [
11895
11929
  },
11896
11930
  {
11897
11931
  "id": "porter-five-forces",
11932
+ "approach_ids": [
11933
+ "inspect"
11934
+ ],
11898
11935
  "name": "Porter Five Forces",
11899
11936
  "version": "1.0.0",
11900
11937
  "description": "Analyse industry competitiveness through five forces: rivalry, new entrants, substitutes, buyer power, and supplier power.",
@@ -11984,6 +12021,9 @@ var UPG_FRAMEWORKS = [
11984
12021
  },
11985
12022
  {
11986
12023
  "id": "swot-analysis",
12024
+ "approach_ids": [
12025
+ "inspect"
12026
+ ],
11987
12027
  "name": "SWOT Analysis",
11988
12028
  "version": "1.0.0",
11989
12029
  "description": "Map Strengths, Weaknesses, Opportunities, and Threats in a 2x2 grid. Internal vs external, helpful vs harmful.",
@@ -12310,6 +12350,7 @@ var UPG_FRAMEWORKS = [
12310
12350
  "type": "assessment",
12311
12351
  "scale_id": "reach_5",
12312
12352
  "required": true,
12353
+ "scope": "framework",
12313
12354
  "label": "Reach",
12314
12355
  "description": "How many users will this impact per quarter?"
12315
12356
  },
@@ -12318,6 +12359,7 @@ var UPG_FRAMEWORKS = [
12318
12359
  "type": "assessment",
12319
12360
  "scale_id": "impact_5",
12320
12361
  "required": true,
12362
+ "scope": "framework",
12321
12363
  "label": "Impact",
12322
12364
  "description": "How much will this impact each user, on the impact scale?"
12323
12365
  },
@@ -12326,6 +12368,7 @@ var UPG_FRAMEWORKS = [
12326
12368
  "type": "assessment",
12327
12369
  "scale_id": "confidence_5",
12328
12370
  "required": true,
12371
+ "scope": "framework",
12329
12372
  "label": "Confidence",
12330
12373
  "description": "How confident are you in the reach, impact, and effort estimates?"
12331
12374
  },
@@ -12334,6 +12377,7 @@ var UPG_FRAMEWORKS = [
12334
12377
  "type": "assessment",
12335
12378
  "scale_id": "effort_5",
12336
12379
  "required": true,
12380
+ "scope": "framework",
12337
12381
  "label": "Effort",
12338
12382
  "description": "How much work is required to build and ship this, on the effort scale?"
12339
12383
  }
@@ -12420,6 +12464,9 @@ var UPG_FRAMEWORKS = [
12420
12464
  },
12421
12465
  {
12422
12466
  "id": "build-measure-learn",
12467
+ "approach_ids": [
12468
+ "reflect"
12469
+ ],
12423
12470
  "name": "Build-Measure-Learn",
12424
12471
  "version": "1.0.0",
12425
12472
  "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 +12602,7 @@ var UPG_FRAMEWORKS = [
12555
12602
  "property": "functional_response",
12556
12603
  "type": "enum",
12557
12604
  "required": true,
12605
+ "scope": "framework",
12558
12606
  "label": "Functional Response",
12559
12607
  "description": "How users feel when the feature IS present",
12560
12608
  "enum_values": [
@@ -12569,6 +12617,7 @@ var UPG_FRAMEWORKS = [
12569
12617
  "property": "dysfunctional_response",
12570
12618
  "type": "enum",
12571
12619
  "required": true,
12620
+ "scope": "framework",
12572
12621
  "label": "Dysfunctional Response",
12573
12622
  "description": "How users feel when the feature IS NOT present",
12574
12623
  "enum_values": [
@@ -12583,6 +12632,7 @@ var UPG_FRAMEWORKS = [
12583
12632
  "property": "delighter_count",
12584
12633
  "type": "number",
12585
12634
  "required": false,
12635
+ "scope": "framework",
12586
12636
  "label": "Delighter classifications",
12587
12637
  "description": "Count of survey responses classifying this feature as a delighter (attractive)"
12588
12638
  },
@@ -12590,6 +12640,7 @@ var UPG_FRAMEWORKS = [
12590
12640
  "property": "performance_count",
12591
12641
  "type": "number",
12592
12642
  "required": false,
12643
+ "scope": "framework",
12593
12644
  "label": "Performance classifications",
12594
12645
  "description": "Count of survey responses classifying this feature as performance (one-dimensional)"
12595
12646
  },
@@ -12597,6 +12648,7 @@ var UPG_FRAMEWORKS = [
12597
12648
  "property": "must_be_count",
12598
12649
  "type": "number",
12599
12650
  "required": false,
12651
+ "scope": "framework",
12600
12652
  "label": "Must-be classifications",
12601
12653
  "description": "Count of survey responses classifying this feature as must-be (basic)"
12602
12654
  },
@@ -12604,6 +12656,7 @@ var UPG_FRAMEWORKS = [
12604
12656
  "property": "indifferent_count",
12605
12657
  "type": "number",
12606
12658
  "required": false,
12659
+ "scope": "framework",
12607
12660
  "label": "Indifferent classifications",
12608
12661
  "description": "Count of survey responses classifying this feature as indifferent"
12609
12662
  }
@@ -12796,6 +12849,7 @@ var UPG_FRAMEWORKS = [
12796
12849
  "property": "moscow",
12797
12850
  "type": "enum",
12798
12851
  "required": true,
12852
+ "scope": "framework",
12799
12853
  "label": "MoSCoW priority",
12800
12854
  "description": "Which scope bucket this requirement falls into for the current release",
12801
12855
  "enum_values": [
@@ -12806,16 +12860,7 @@ var UPG_FRAMEWORKS = [
12806
12860
  ]
12807
12861
  }
12808
12862
  ]
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
- ]
12863
+ }
12819
12864
  },
12820
12865
  "structure": {
12821
12866
  "pattern": "table"
@@ -12979,6 +13024,9 @@ var UPG_FRAMEWORKS = [
12979
13024
  },
12980
13025
  {
12981
13026
  "id": "c4-model",
13027
+ "approach_ids": [
13028
+ "trace"
13029
+ ],
12982
13030
  "name": "C4 Model",
12983
13031
  "version": "1.0.0",
12984
13032
  "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 +13119,9 @@ var UPG_FRAMEWORKS = [
13071
13119
  },
13072
13120
  {
13073
13121
  "id": "adr-log",
13122
+ "approach_ids": [
13123
+ "inspect"
13124
+ ],
13074
13125
  "name": "ADR Log",
13075
13126
  "version": "1.0.0",
13076
13127
  "description": "Architecture Decision Records: log decisions with context, options, and rationale.",
@@ -13184,6 +13235,9 @@ var UPG_FRAMEWORKS = [
13184
13235
  },
13185
13236
  {
13186
13237
  "id": "atomic-design",
13238
+ "approach_ids": [
13239
+ "trace"
13240
+ ],
13187
13241
  "name": "Atomic Design",
13188
13242
  "version": "1.0.0",
13189
13243
  "description": "Atoms, Molecules, Organisms, Templates, Pages: a methodology for creating design systems from the smallest elements up.",
@@ -13281,6 +13335,9 @@ var UPG_FRAMEWORKS = [
13281
13335
  },
13282
13336
  {
13283
13337
  "id": "double-diamond",
13338
+ "approach_ids": [
13339
+ "plan"
13340
+ ],
13284
13341
  "name": "Double Diamond",
13285
13342
  "version": "1.0.0",
13286
13343
  "description": "Discover, Define, Develop, Deliver: a four-phase divergent/convergent design process.",
@@ -13371,6 +13428,9 @@ var UPG_FRAMEWORKS = [
13371
13428
  },
13372
13429
  {
13373
13430
  "id": "dora-metrics",
13431
+ "approach_ids": [
13432
+ "inspect"
13433
+ ],
13374
13434
  "name": "DORA Metrics",
13375
13435
  "version": "1.0.0",
13376
13436
  "description": "Four key metrics for software delivery performance: deployment frequency, lead time, change failure rate, and time to restore.",
@@ -13430,16 +13490,7 @@ var UPG_FRAMEWORKS = [
13430
13490
  ]
13431
13491
  }
13432
13492
  ]
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
- ]
13493
+ }
13443
13494
  },
13444
13495
  "structure": {
13445
13496
  "pattern": "collection"
@@ -13568,6 +13619,9 @@ var UPG_FRAMEWORKS = [
13568
13619
  },
13569
13620
  {
13570
13621
  "id": "pirate-metrics-aarrr",
13622
+ "approach_ids": [
13623
+ "trace"
13624
+ ],
13571
13625
  "name": "Pirate Metrics AARRR",
13572
13626
  "version": "1.0.0",
13573
13627
  "description": "Track user lifecycle across five stages: Acquisition, Activation, Retention, Revenue, and Referral.",
@@ -13601,7 +13655,7 @@ var UPG_FRAMEWORKS = [
13601
13655
  "required_properties": {
13602
13656
  "metric": [
13603
13657
  {
13604
- "property": "lifecycle_stage",
13658
+ "property": "metric_category",
13605
13659
  "type": "enum",
13606
13660
  "required": true,
13607
13661
  "label": "Lifecycle stage",
@@ -13615,16 +13669,7 @@ var UPG_FRAMEWORKS = [
13615
13669
  ]
13616
13670
  }
13617
13671
  ]
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
- ]
13672
+ }
13628
13673
  },
13629
13674
  "structure": {
13630
13675
  "pattern": "funnel",
@@ -13688,6 +13733,9 @@ var UPG_FRAMEWORKS = [
13688
13733
  },
13689
13734
  {
13690
13735
  "id": "north-star-metric",
13736
+ "approach_ids": [
13737
+ "plan"
13738
+ ],
13691
13739
  "name": "North Star Metric",
13692
13740
  "version": "1.0.0",
13693
13741
  "description": "One metric that best captures the core value you deliver. Supported by 3-5 input metrics that drive it.",
@@ -13721,7 +13769,7 @@ var UPG_FRAMEWORKS = [
13721
13769
  "required_properties": {
13722
13770
  "metric": [
13723
13771
  {
13724
- "property": "metric_role",
13772
+ "property": "designation",
13725
13773
  "type": "enum",
13726
13774
  "required": true,
13727
13775
  "label": "Metric role",
@@ -13739,16 +13787,7 @@ var UPG_FRAMEWORKS = [
13739
13787
  "description": "How strongly this input metric moves the North Star (input metrics only)"
13740
13788
  }
13741
13789
  ]
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
- ]
13790
+ }
13752
13791
  },
13753
13792
  "structure": {
13754
13793
  "pattern": "collection"
@@ -13784,6 +13823,9 @@ var UPG_FRAMEWORKS = [
13784
13823
  },
13785
13824
  {
13786
13825
  "id": "marketing-mix-4ps",
13826
+ "approach_ids": [
13827
+ "plan"
13828
+ ],
13787
13829
  "name": "Marketing Mix 4Ps",
13788
13830
  "version": "1.0.0",
13789
13831
  "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 +13919,9 @@ var UPG_FRAMEWORKS = [
13877
13919
  },
13878
13920
  {
13879
13921
  "id": "bullseye-framework",
13922
+ "approach_ids": [
13923
+ "plan"
13924
+ ],
13880
13925
  "name": "Bullseye Framework",
13881
13926
  "version": "1.0.0",
13882
13927
  "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 +14008,9 @@ var UPG_FRAMEWORKS = [
13963
14008
  },
13964
14009
  {
13965
14010
  "id": "product-led-growth-framework",
14011
+ "approach_ids": [
14012
+ "plan"
14013
+ ],
13966
14014
  "name": "PLG Framework",
13967
14015
  "version": "1.0.0",
13968
14016
  "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 +14193,9 @@ var UPG_FRAMEWORKS = [
14145
14193
  },
14146
14194
  {
14147
14195
  "id": "raci-matrix",
14196
+ "approach_ids": [
14197
+ "inspect"
14198
+ ],
14148
14199
  "name": "RACI Matrix",
14149
14200
  "version": "1.0.0",
14150
14201
  "description": "Assign roles: Responsible, Accountable, Consulted, Informed for each activity.",
@@ -14196,16 +14247,7 @@ var UPG_FRAMEWORKS = [
14196
14247
  "role": "item"
14197
14248
  }
14198
14249
  ],
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
- ]
14250
+ "required_properties": {}
14209
14251
  },
14210
14252
  "structure": {
14211
14253
  "pattern": "matrix"
@@ -14429,6 +14471,9 @@ var UPG_FRAMEWORKS = [
14429
14471
  },
14430
14472
  {
14431
14473
  "id": "team-health-check",
14474
+ "approach_ids": [
14475
+ "inspect"
14476
+ ],
14432
14477
  "name": "Team Health Check",
14433
14478
  "version": "1.0.0",
14434
14479
  "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 +14531,7 @@ var UPG_FRAMEWORKS = [
14486
14531
  "role": "item"
14487
14532
  }
14488
14533
  ],
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
- ]
14534
+ "required_properties": {}
14499
14535
  },
14500
14536
  "structure": {
14501
14537
  "pattern": "table"
@@ -14552,6 +14588,9 @@ var UPG_FRAMEWORKS = [
14552
14588
  },
14553
14589
  {
14554
14590
  "id": "raid-log",
14591
+ "approach_ids": [
14592
+ "inspect"
14593
+ ],
14555
14594
  "name": "RAID Log",
14556
14595
  "version": "1.0.0",
14557
14596
  "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 +14610,11 @@ var UPG_FRAMEWORKS = [
14571
14610
  "entityTypeId": "risk_register",
14572
14611
  "description": "Risk or assumption logged with likelihood, impact, owner, and mitigation strategy"
14573
14612
  },
14613
+ {
14614
+ "label": "Risk",
14615
+ "entityTypeId": "risk",
14616
+ "description": "Individual risk scored by probability and impact; severity = probability * impact"
14617
+ },
14574
14618
  {
14575
14619
  "label": "Dependency",
14576
14620
  "entityTypeId": "dependency",
@@ -14593,6 +14637,10 @@ var UPG_FRAMEWORKS = [
14593
14637
  "type": "risk_register",
14594
14638
  "role": "item"
14595
14639
  },
14640
+ {
14641
+ "type": "risk",
14642
+ "role": "scored_item"
14643
+ },
14596
14644
  {
14597
14645
  "type": "dependency",
14598
14646
  "role": "item"
@@ -14606,7 +14654,24 @@ var UPG_FRAMEWORKS = [
14606
14654
  "role": "item"
14607
14655
  }
14608
14656
  ],
14609
- "required_properties": {},
14657
+ "required_properties": {
14658
+ "risk": [
14659
+ {
14660
+ "property": "probability",
14661
+ "type": "number",
14662
+ "required": true,
14663
+ "label": "Probability",
14664
+ "description": "Likelihood the risk materialises (risk.probability assessment)"
14665
+ },
14666
+ {
14667
+ "property": "impact",
14668
+ "type": "number",
14669
+ "required": true,
14670
+ "label": "Impact",
14671
+ "description": "Consequence severity if the risk materialises (risk.impact assessment)"
14672
+ }
14673
+ ]
14674
+ },
14610
14675
  "computed_properties": [
14611
14676
  {
14612
14677
  "property": "severity",
@@ -14667,90 +14732,992 @@ var UPG_FRAMEWORKS = [
14667
14732
  "Formal programme management would create overhead without value"
14668
14733
  ]
14669
14734
  }
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
14735
  },
14733
- // ── experiment (CONSOLIDATED: absorbs ab_test, growth_experiment, pricing_experiment) ──
14734
14736
  {
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"
14737
+ "id": "ice-scoring",
14738
+ "approach_ids": [
14739
+ "prioritise"
14747
14740
  ],
14748
- framework_labels: {
14749
- ost: "Experiment",
14750
- design_thinking: "Test",
14751
- lean_startup: "Experiment"
14752
- },
14753
- designations: {
14741
+ "name": "ICE Scoring",
14742
+ "version": "1.0.0",
14743
+ "description": "Rate ideas by Impact, Confidence, and Ease on a 1-10 scale. Multiply for a composite score. Fast and lightweight.",
14744
+ "category": "prioritization",
14745
+ "origin": {
14746
+ "type": "practitioner",
14747
+ "attribution": "Sean Ellis",
14748
+ "description": "Created by Sean Ellis as a lightweight growth experiment scoring method. Widely adopted in growth teams.",
14749
+ "year": 2010,
14750
+ "license": "open_attribution"
14751
+ },
14752
+ "tags": [
14753
+ "prioritization",
14754
+ "table"
14755
+ ],
14756
+ "slots": [
14757
+ {
14758
+ "label": "Items to score",
14759
+ "entityTypeId": "feature",
14760
+ "description": "Features or experiments being evaluated"
14761
+ },
14762
+ {
14763
+ "label": "Impact",
14764
+ "entityTypeId": "outcome",
14765
+ "description": "How much will this move the needle?"
14766
+ },
14767
+ {
14768
+ "label": "Confidence",
14769
+ "entityTypeId": "assumption",
14770
+ "description": "How sure are we about the impact?"
14771
+ },
14772
+ {
14773
+ "label": "Ease",
14774
+ "entityTypeId": "feature",
14775
+ "description": "How easy is this to implement?"
14776
+ }
14777
+ ],
14778
+ "data": {
14779
+ "entity_types": [
14780
+ {
14781
+ "type": "feature",
14782
+ "role": "scored_item"
14783
+ },
14784
+ {
14785
+ "type": "outcome",
14786
+ "role": "item"
14787
+ },
14788
+ {
14789
+ "type": "assumption",
14790
+ "role": "item"
14791
+ }
14792
+ ],
14793
+ "required_properties": {
14794
+ "feature": [
14795
+ {
14796
+ "property": "impact",
14797
+ "type": "number",
14798
+ "required": true,
14799
+ "scope": "framework",
14800
+ "label": "Impact",
14801
+ "description": "Expected impact on the target metric (1-10)"
14802
+ },
14803
+ {
14804
+ "property": "confidence",
14805
+ "type": "number",
14806
+ "required": true,
14807
+ "scope": "framework",
14808
+ "label": "Confidence",
14809
+ "description": "Confidence in the impact estimate (1-10)"
14810
+ },
14811
+ {
14812
+ "property": "ease",
14813
+ "type": "number",
14814
+ "required": true,
14815
+ "scope": "framework",
14816
+ "label": "Ease",
14817
+ "description": "Ease of implementation (1-10)"
14818
+ }
14819
+ ]
14820
+ },
14821
+ "computed_properties": [
14822
+ {
14823
+ "property": "ice_score",
14824
+ "expression": "impact * confidence * ease",
14825
+ "entity_type": "feature",
14826
+ "label": "ICE Score",
14827
+ "format": "number"
14828
+ }
14829
+ ]
14830
+ },
14831
+ "structure": {
14832
+ "pattern": "table"
14833
+ },
14834
+ "presentation": {
14835
+ "layout": {
14836
+ "type": "table",
14837
+ "columns": [
14838
+ {
14839
+ "property": "title",
14840
+ "label": "Items to score",
14841
+ "sortable": true
14842
+ },
14843
+ {
14844
+ "property": "impact",
14845
+ "label": "Impact",
14846
+ "sortable": true
14847
+ },
14848
+ {
14849
+ "property": "confidence",
14850
+ "label": "Confidence",
14851
+ "sortable": true
14852
+ },
14853
+ {
14854
+ "property": "ease",
14855
+ "label": "Ease",
14856
+ "sortable": true
14857
+ }
14858
+ ]
14859
+ },
14860
+ "sort_by": {
14861
+ "property": "title",
14862
+ "direction": "asc"
14863
+ },
14864
+ "colour_by": "type",
14865
+ "card_fields": [
14866
+ "title",
14867
+ "description",
14868
+ "status"
14869
+ ]
14870
+ },
14871
+ "education": {
14872
+ "purpose": "Provide a lightweight scoring model for early-stage ideas when detailed effort estimates are unavailable. Faster than RICE and useful for brainstorm triage.",
14873
+ "core_question": "Which ideas should we investigate further based on their potential impact, confidence in our assumptions, and implementation ease?",
14874
+ "when_to_use": [
14875
+ "You have more ideas or features than capacity to build them",
14876
+ "Stakeholders disagree on what to build next",
14877
+ "You need a transparent, defensible prioritisation process"
14878
+ ],
14879
+ "when_not_to_use": [
14880
+ "You have a single obvious next step with no contention",
14881
+ "The backlog is small enough to sequence intuitively"
14882
+ ]
14883
+ }
14884
+ },
14885
+ {
14886
+ "id": "wsjf",
14887
+ "approach_ids": [
14888
+ "prioritise"
14889
+ ],
14890
+ "name": "WSJF (Weighted Shortest Job First)",
14891
+ "version": "1.0.0",
14892
+ "description": "Prioritise work by dividing Cost of Delay (user value + time criticality + risk reduction) by job duration to maximise economic throughput.",
14893
+ "category": "planning",
14894
+ "origin": {
14895
+ "type": "practitioner",
14896
+ "attribution": "Reinertsen / SAFe",
14897
+ "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.",
14898
+ "url": "https://www.scaledagileframework.com/wsjf/",
14899
+ "year": 2011,
14900
+ "license": "open_attribution"
14901
+ },
14902
+ "tags": [
14903
+ "planning",
14904
+ "table"
14905
+ ],
14906
+ "slots": [
14907
+ {
14908
+ "label": "Backlog Items",
14909
+ "entityTypeId": "feature",
14910
+ "description": "Backlog Items: feature entries to evaluate"
14911
+ },
14912
+ {
14913
+ "label": "User/Business Value",
14914
+ "entityTypeId": "metric",
14915
+ "description": "User/Business Value: metric entries to evaluate"
14916
+ },
14917
+ {
14918
+ "label": "Time Criticality",
14919
+ "entityTypeId": "metric",
14920
+ "description": "How much value decays if delivery is delayed (deadlines, competition, seasonal windows)"
14921
+ },
14922
+ {
14923
+ "label": "Risk Reduction / Opportunity Enablement",
14924
+ "entityTypeId": "metric",
14925
+ "description": "Risk Reduction / Opportunity Enablement: metric entries to evaluate"
14926
+ },
14927
+ {
14928
+ "label": "Job Size",
14929
+ "entityTypeId": "metric",
14930
+ "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
14931
+ }
14932
+ ],
14933
+ "data": {
14934
+ "entity_types": [
14935
+ {
14936
+ "type": "feature",
14937
+ "role": "scored_item"
14938
+ },
14939
+ {
14940
+ "type": "metric",
14941
+ "role": "item"
14942
+ }
14943
+ ],
14944
+ "required_properties": {
14945
+ "feature": [
14946
+ {
14947
+ "property": "user_value",
14948
+ "type": "number",
14949
+ "required": true,
14950
+ "scope": "framework",
14951
+ "label": "User/Business Value",
14952
+ "description": "Relative value to users and the business if delivered"
14953
+ },
14954
+ {
14955
+ "property": "time_criticality",
14956
+ "type": "number",
14957
+ "required": true,
14958
+ "scope": "framework",
14959
+ "label": "Time Criticality",
14960
+ "description": "How much value decays if delivery is delayed (deadlines, competition, seasonal windows)"
14961
+ },
14962
+ {
14963
+ "property": "risk_reduction",
14964
+ "type": "number",
14965
+ "required": true,
14966
+ "scope": "framework",
14967
+ "label": "Risk Reduction / Opportunity Enablement",
14968
+ "description": "Value from reducing risk or enabling future opportunities"
14969
+ },
14970
+ {
14971
+ "property": "job_size",
14972
+ "type": "number",
14973
+ "required": true,
14974
+ "scope": "framework",
14975
+ "label": "Job Size",
14976
+ "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
14977
+ }
14978
+ ]
14979
+ },
14980
+ "computed_properties": [
14981
+ {
14982
+ "property": "wsjf_score",
14983
+ "expression": "(user_value + time_criticality + risk_reduction) / job_size",
14984
+ "entity_type": "feature",
14985
+ "label": "WSJF Score",
14986
+ "format": "number"
14987
+ }
14988
+ ]
14989
+ },
14990
+ "structure": {
14991
+ "pattern": "table"
14992
+ },
14993
+ "presentation": {
14994
+ "layout": {
14995
+ "type": "table",
14996
+ "columns": [
14997
+ {
14998
+ "property": "title",
14999
+ "label": "Backlog Items",
15000
+ "sortable": true
15001
+ },
15002
+ {
15003
+ "property": "user_value",
15004
+ "label": "User/Business Value",
15005
+ "sortable": true
15006
+ },
15007
+ {
15008
+ "property": "time_criticality",
15009
+ "label": "Time Criticality",
15010
+ "sortable": true
15011
+ },
15012
+ {
15013
+ "property": "risk_reduction",
15014
+ "label": "Risk Reduction / Opportunity Enablement",
15015
+ "sortable": true
15016
+ },
15017
+ {
15018
+ "property": "job_size",
15019
+ "label": "Job Size",
15020
+ "sortable": true
15021
+ },
15022
+ {
15023
+ "property": "wsjf_score",
15024
+ "label": "WSJF Score",
15025
+ "sortable": true
15026
+ }
15027
+ ]
15028
+ },
15029
+ "sort_by": {
15030
+ "property": "title",
15031
+ "direction": "asc"
15032
+ },
15033
+ "colour_by": "type",
15034
+ "card_fields": [
15035
+ "title",
15036
+ "description",
15037
+ "status"
15038
+ ]
15039
+ },
15040
+ "education": {
15041
+ "purpose": "Prioritise work by dividing the Cost of Delay by job duration, ensuring the most time-sensitive, valuable items are done first.",
15042
+ "core_question": "Considering the cost of waiting, which items should we start now to maximise economic benefit?",
15043
+ "when_to_use": [
15044
+ "You need to coordinate work across multiple teams or time horizons",
15045
+ "Stakeholders need visibility into what is coming and when",
15046
+ "You want to balance commitments with flexibility"
15047
+ ],
15048
+ "when_not_to_use": [
15049
+ "The team is small enough that informal coordination works",
15050
+ "Plans would create false precision about uncertain outcomes"
15051
+ ]
15052
+ }
15053
+ },
15054
+ {
15055
+ "id": "cost-of-delay",
15056
+ "approach_ids": [
15057
+ "prioritise"
15058
+ ],
15059
+ "name": "Cost of Delay",
15060
+ "version": "1.0.0",
15061
+ "description": "Quantify the economic cost of not shipping a feature to drive priority decisions. Combines urgency with value.",
15062
+ "category": "prioritization",
15063
+ "origin": {
15064
+ "type": "practitioner",
15065
+ "attribution": "Don Reinertsen",
15066
+ "description": "Formalised in The Principles of Product Development Flow (Celeritas Publishing). Foundational to lean product economics.",
15067
+ "year": 2009,
15068
+ "license": "public_domain"
15069
+ },
15070
+ "tags": [
15071
+ "prioritization",
15072
+ "table"
15073
+ ],
15074
+ "slots": [
15075
+ {
15076
+ "label": "Items to evaluate",
15077
+ "entityTypeId": "feature",
15078
+ "description": "Features or initiatives being assessed"
15079
+ },
15080
+ {
15081
+ "label": "User-Business Value",
15082
+ "entityTypeId": "outcome",
15083
+ "description": "Revenue, retention, or strategic value"
15084
+ },
15085
+ {
15086
+ "label": "Time Criticality",
15087
+ "entityTypeId": "metric",
15088
+ "description": "How much value decays with delay"
15089
+ },
15090
+ {
15091
+ "label": "Risk Reduction",
15092
+ "entityTypeId": "risk",
15093
+ "description": "What risk does this mitigate?"
15094
+ }
15095
+ ],
15096
+ "data": {
15097
+ "entity_types": [
15098
+ {
15099
+ "type": "feature",
15100
+ "role": "scored_item"
15101
+ },
15102
+ {
15103
+ "type": "metric",
15104
+ "role": "item"
15105
+ },
15106
+ {
15107
+ "type": "outcome",
15108
+ "role": "item"
15109
+ },
15110
+ {
15111
+ "type": "risk",
15112
+ "role": "item"
15113
+ }
15114
+ ],
15115
+ "required_properties": {
15116
+ "feature": [
15117
+ {
15118
+ "property": "cost_of_delay",
15119
+ "type": "number",
15120
+ "required": true,
15121
+ "scope": "framework",
15122
+ "label": "Cost of Delay",
15123
+ "description": "Weekly revenue impact of not shipping"
15124
+ },
15125
+ {
15126
+ "property": "job_size",
15127
+ "type": "number",
15128
+ "required": true,
15129
+ "scope": "framework",
15130
+ "label": "Job Size",
15131
+ "description": "Weeks of development effort"
15132
+ }
15133
+ ]
15134
+ },
15135
+ "computed_properties": [
15136
+ {
15137
+ "property": "wsjf_score",
15138
+ "expression": "cost_of_delay / job_size",
15139
+ "entity_type": "feature",
15140
+ "label": "WSJF Score",
15141
+ "format": "number"
15142
+ }
15143
+ ]
15144
+ },
15145
+ "structure": {
15146
+ "pattern": "table"
15147
+ },
15148
+ "presentation": {
15149
+ "layout": {
15150
+ "type": "table",
15151
+ "columns": [
15152
+ {
15153
+ "property": "title",
15154
+ "label": "Items to evaluate",
15155
+ "sortable": true
15156
+ },
15157
+ {
15158
+ "property": "cost_of_delay",
15159
+ "label": "User-Business Value",
15160
+ "sortable": true
15161
+ },
15162
+ {
15163
+ "property": "job_size",
15164
+ "label": "Job Size",
15165
+ "sortable": true
15166
+ },
15167
+ {
15168
+ "property": "wsjf_score",
15169
+ "label": "CoD Score",
15170
+ "sortable": true
15171
+ }
15172
+ ]
15173
+ },
15174
+ "sort_by": {
15175
+ "property": "title",
15176
+ "direction": "asc"
15177
+ },
15178
+ "colour_by": "type",
15179
+ "card_fields": [
15180
+ "title",
15181
+ "description",
15182
+ "status"
15183
+ ]
15184
+ },
15185
+ "education": {
15186
+ "purpose": "Quantify the economic impact of not delivering a feature by a given date, making urgency visible and enabling time-sensitive prioritisation.",
15187
+ "core_question": "How much value are we losing every week this feature is not in production, and does that urgency justify fast-tracking it?",
15188
+ "when_to_use": [
15189
+ "You have more ideas or features than capacity to build them",
15190
+ "Stakeholders disagree on what to build next",
15191
+ "You need a transparent, defensible prioritisation process"
15192
+ ],
15193
+ "when_not_to_use": [
15194
+ "You have a single obvious next step with no contention",
15195
+ "The backlog is small enough to sequence intuitively"
15196
+ ]
15197
+ }
15198
+ },
15199
+ {
15200
+ "id": "five-whys",
15201
+ "approach_ids": [
15202
+ "reflect",
15203
+ "inspect"
15204
+ ],
15205
+ "name": "Five Whys",
15206
+ "version": "1.0.0",
15207
+ "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.',
15208
+ "category": "team_process",
15209
+ "origin": {
15210
+ "type": "practitioner",
15211
+ "attribution": "Sakichi Toyoda / Toyota Production System",
15212
+ "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.",
15213
+ "year": 1930,
15214
+ "license": "public_domain"
15215
+ },
15216
+ "tags": [
15217
+ "team_process",
15218
+ "reflection",
15219
+ "root_cause",
15220
+ "tree"
15221
+ ],
15222
+ "slots": [
15223
+ {
15224
+ "label": "Symptom",
15225
+ "entityTypeId": "need",
15226
+ "description": "The observed problem the analysis starts from."
15227
+ },
15228
+ {
15229
+ "label": "Why chain",
15230
+ "entityTypeId": "insight",
15231
+ "description": 'Each "why?" answer along the chain, typically five iterations deep.'
15232
+ },
15233
+ {
15234
+ "label": "Root cause",
15235
+ "entityTypeId": "insight",
15236
+ "description": "The terminal answer at the bottom of the chain: the underlying cause to address."
15237
+ }
15238
+ ],
15239
+ "data": {
15240
+ "entity_types": [
15241
+ {
15242
+ "type": "need",
15243
+ "role": "root"
15244
+ },
15245
+ {
15246
+ "type": "insight",
15247
+ "role": "branch"
15248
+ }
15249
+ ],
15250
+ "required_properties": {}
15251
+ },
15252
+ "structure": {
15253
+ "pattern": "tree"
15254
+ },
15255
+ "presentation": {
15256
+ "layout": {
15257
+ "type": "tree",
15258
+ "direction": "TB"
15259
+ },
15260
+ "sort_by": {
15261
+ "property": "title",
15262
+ "direction": "asc"
15263
+ },
15264
+ "card_fields": [
15265
+ "title",
15266
+ "description"
15267
+ ]
15268
+ },
15269
+ "education": {
15270
+ "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.',
15271
+ "core_question": "Why is this happening, and why is THAT happening, until we reach a cause we can act on?",
15272
+ "when_to_use": [
15273
+ "A problem keeps recurring after surface fixes",
15274
+ "Post-incident review where the obvious cause feels too obvious",
15275
+ "Designing a fix and you want to confirm you understand the actual driver"
15276
+ ],
15277
+ "when_not_to_use": [
15278
+ "The problem has multiple independent root causes (use a fishbone or richer RCA tool)",
15279
+ "You need quantitative attribution rather than a single-thread narrative",
15280
+ 'Five linear "whys" oversimplify a systems problem with feedback loops'
15281
+ ]
15282
+ }
15283
+ },
15284
+ {
15285
+ "id": "pre-mortem",
15286
+ "approach_ids": [
15287
+ "reflect"
15288
+ ],
15289
+ "name": "Pre-mortem",
15290
+ "version": "1.0.0",
15291
+ "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.",
15292
+ "category": "team_process",
15293
+ "origin": {
15294
+ "type": "practitioner",
15295
+ "attribution": "Gary Klein",
15296
+ "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.",
15297
+ "url": "https://hbr.org/2007/09/performing-a-project-premortem",
15298
+ "year": 2007,
15299
+ "license": "published_methodology"
15300
+ },
15301
+ "tags": [
15302
+ "team_process",
15303
+ "reflection",
15304
+ "risk",
15305
+ "collection"
15306
+ ],
15307
+ "slots": [
15308
+ {
15309
+ "label": "Imagined failures",
15310
+ "entityTypeId": "risk",
15311
+ "description": "Plausible failure modes named as if they had already occurred."
15312
+ },
15313
+ {
15314
+ "label": "Causes",
15315
+ "entityTypeId": "insight",
15316
+ "description": "For each imagined failure, the contributing causes the team can foresee."
15317
+ },
15318
+ {
15319
+ "label": "Mitigations",
15320
+ "entityTypeId": "initiative",
15321
+ "description": "Mitigation actions the team will take before failure can occur."
15322
+ }
15323
+ ],
15324
+ "data": {
15325
+ "entity_types": [
15326
+ {
15327
+ "type": "risk",
15328
+ "role": "bucket"
15329
+ },
15330
+ {
15331
+ "type": "insight",
15332
+ "role": "bucket"
15333
+ },
15334
+ {
15335
+ "type": "initiative",
15336
+ "role": "bucket"
15337
+ }
15338
+ ],
15339
+ "required_properties": {}
15340
+ },
15341
+ "structure": {
15342
+ "pattern": "collection"
15343
+ },
15344
+ "presentation": {
15345
+ "layout": {
15346
+ "type": "grid",
15347
+ "groupBy": "type"
15348
+ },
15349
+ "sort_by": {
15350
+ "property": "title",
15351
+ "direction": "asc"
15352
+ },
15353
+ "colour_by": "group",
15354
+ "card_fields": [
15355
+ "title",
15356
+ "description"
15357
+ ]
15358
+ },
15359
+ "education": {
15360
+ "purpose": "Surface project risks early by inverting hindsight: imagine the project has already failed and ask why, while there is still time to mitigate.",
15361
+ "core_question": "It is six months from now and the project has failed catastrophically. What happened, and why?",
15362
+ "when_to_use": [
15363
+ "Kicking off a project with significant downside or irreversible commitment",
15364
+ "A plan looks too clean and the team senses unspoken concerns",
15365
+ "Stakeholders disagree on risk; the exercise externalises and ranks them"
15366
+ ],
15367
+ "when_not_to_use": [
15368
+ "The work is small, reversible, and cheap to course-correct",
15369
+ "The team is in execution mode and reflective ceremonies will derail momentum",
15370
+ "Risk surfacing has become performative: the team names risks but never mitigates them"
15371
+ ]
15372
+ }
15373
+ },
15374
+ {
15375
+ "id": "red-team",
15376
+ "approach_ids": [
15377
+ "reflect",
15378
+ "inspect"
15379
+ ],
15380
+ "name": "Red Team",
15381
+ "version": "1.0.0",
15382
+ "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.",
15383
+ "category": "team_process",
15384
+ "origin": {
15385
+ "type": "practitioner",
15386
+ "attribution": "US Department of Defense (Cold War era); broadened by security and intelligence practice",
15387
+ "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.`,
15388
+ "year": 1960,
15389
+ "license": "public_domain"
15390
+ },
15391
+ "tags": [
15392
+ "team_process",
15393
+ "reflection",
15394
+ "adversarial",
15395
+ "collection"
15396
+ ],
15397
+ "slots": [
15398
+ {
15399
+ "label": "Target",
15400
+ "entityTypeId": "initiative",
15401
+ "description": "The plan, design, or proposal under adversarial review."
15402
+ },
15403
+ {
15404
+ "label": "Attack vectors",
15405
+ "entityTypeId": "risk",
15406
+ "description": "The angles the red team uses to probe weaknesses."
15407
+ },
15408
+ {
15409
+ "label": "Findings",
15410
+ "entityTypeId": "insight",
15411
+ "description": "Weaknesses, blind spots, or unstated assumptions surfaced by the review."
15412
+ }
15413
+ ],
15414
+ "data": {
15415
+ "entity_types": [
15416
+ {
15417
+ "type": "initiative",
15418
+ "role": "root"
15419
+ },
15420
+ {
15421
+ "type": "risk",
15422
+ "role": "bucket"
15423
+ },
15424
+ {
15425
+ "type": "insight",
15426
+ "role": "bucket"
15427
+ }
15428
+ ],
15429
+ "required_properties": {}
15430
+ },
15431
+ "structure": {
15432
+ "pattern": "collection"
15433
+ },
15434
+ "presentation": {
15435
+ "layout": {
15436
+ "type": "grid",
15437
+ "groupBy": "type"
15438
+ },
15439
+ "sort_by": {
15440
+ "property": "title",
15441
+ "direction": "asc"
15442
+ },
15443
+ "colour_by": "group",
15444
+ "card_fields": [
15445
+ "title",
15446
+ "description"
15447
+ ]
15448
+ },
15449
+ "education": {
15450
+ "purpose": "Stress-test a plan against an explicit adversary by assigning reviewers to attack rather than agree, so weaknesses surface before reality finds them.",
15451
+ "core_question": "If a competent adversary wanted this to fail, where would they push first, and would we hold?",
15452
+ "when_to_use": [
15453
+ "A high-stakes decision, launch, or security posture needs hardening",
15454
+ "Inside-out thinking is dominant and dissent has gone quiet",
15455
+ "Risk register is suspiciously short for the size of the bet"
15456
+ ],
15457
+ "when_not_to_use": [
15458
+ "Early-stage exploration where adversarial framing would crush a fragile idea prematurely",
15459
+ "Team trust is too low: red-teaming will read as personal attack rather than role-play",
15460
+ "The work is small enough that a lightweight devil's-advocate pass is sufficient"
15461
+ ]
15462
+ }
15463
+ },
15464
+ {
15465
+ "id": "devils-advocate",
15466
+ "approach_ids": [
15467
+ "reflect"
15468
+ ],
15469
+ "name": "Devil's Advocate",
15470
+ "version": "1.0.0",
15471
+ "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.",
15472
+ "category": "team_process",
15473
+ "origin": {
15474
+ "type": "practitioner",
15475
+ "attribution": "Roman Catholic Church (advocatus diaboli); broadened by decision-quality practice",
15476
+ "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.",
15477
+ "year": 1587,
15478
+ "license": "public_domain"
15479
+ },
15480
+ "tags": [
15481
+ "team_process",
15482
+ "reflection",
15483
+ "decision_quality",
15484
+ "collection"
15485
+ ],
15486
+ "slots": [
15487
+ {
15488
+ "label": "Proposal",
15489
+ "entityTypeId": "initiative",
15490
+ "description": "The plan or recommendation under consideration."
15491
+ },
15492
+ {
15493
+ "label": "Opposing arguments",
15494
+ "entityTypeId": "insight",
15495
+ "description": "The case against the proposal, voiced by the assigned contrarian regardless of personal view."
15496
+ },
15497
+ {
15498
+ "label": "Counter-evidence",
15499
+ "entityTypeId": "evidence",
15500
+ "description": "Data points the contrarian raises that the proposal does not yet account for."
15501
+ }
15502
+ ],
15503
+ "data": {
15504
+ "entity_types": [
15505
+ {
15506
+ "type": "initiative",
15507
+ "role": "root"
15508
+ },
15509
+ {
15510
+ "type": "insight",
15511
+ "role": "bucket"
15512
+ },
15513
+ {
15514
+ "type": "evidence",
15515
+ "role": "bucket"
15516
+ }
15517
+ ],
15518
+ "required_properties": {}
15519
+ },
15520
+ "structure": {
15521
+ "pattern": "collection"
15522
+ },
15523
+ "presentation": {
15524
+ "layout": {
15525
+ "type": "grid",
15526
+ "groupBy": "type"
15527
+ },
15528
+ "sort_by": {
15529
+ "property": "title",
15530
+ "direction": "asc"
15531
+ },
15532
+ "colour_by": "group",
15533
+ "card_fields": [
15534
+ "title",
15535
+ "description"
15536
+ ]
15537
+ },
15538
+ "education": {
15539
+ "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.",
15540
+ "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?",
15541
+ "when_to_use": [
15542
+ "A decision is heading toward consensus and you suspect groupthink",
15543
+ "Stakes are high and the team has not heard a serious counter-argument",
15544
+ "Cultural norms make raw dissent costly; assigning the role lowers the social cost"
15545
+ ],
15546
+ "when_not_to_use": [
15547
+ "Genuine disagreement already exists in the room (let it surface; do not theatricalise it)",
15548
+ "The decision is small enough that the ceremony costs more than the insight returned",
15549
+ "The assigned contrarian will be punished socially for the role; set the norms first or skip"
15550
+ ]
15551
+ }
15552
+ },
15553
+ {
15554
+ "id": "second-order-thinking",
15555
+ "approach_ids": [
15556
+ "reflect"
15557
+ ],
15558
+ "name": "Second-order Thinking",
15559
+ "version": "1.0.0",
15560
+ "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.',
15561
+ "category": "team_process",
15562
+ "origin": {
15563
+ "type": "practitioner",
15564
+ "attribution": "Howard Marks / Charlie Munger",
15565
+ "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.`,
15566
+ "url": "https://www.oaktreecapital.com/insights/memo/dare-to-be-great-ii",
15567
+ "year": 2011,
15568
+ "license": "published_methodology"
15569
+ },
15570
+ "tags": [
15571
+ "team_process",
15572
+ "reflection",
15573
+ "consequences",
15574
+ "tree"
15575
+ ],
15576
+ "slots": [
15577
+ {
15578
+ "label": "First-order move",
15579
+ "entityTypeId": "decision",
15580
+ "description": "The decision or move under consideration."
15581
+ },
15582
+ {
15583
+ "label": "Second-order consequences",
15584
+ "entityTypeId": "insight",
15585
+ "description": "Downstream effects that follow from the first-order move."
15586
+ },
15587
+ {
15588
+ "label": "Higher-order consequences",
15589
+ "entityTypeId": "insight",
15590
+ "description": "Third-, fourth-, fifth-order ripples: second-order consequences of the second-order consequences."
15591
+ }
15592
+ ],
15593
+ "data": {
15594
+ "entity_types": [
15595
+ {
15596
+ "type": "decision",
15597
+ "role": "root"
15598
+ },
15599
+ {
15600
+ "type": "insight",
15601
+ "role": "branch"
15602
+ }
15603
+ ],
15604
+ "required_properties": {}
15605
+ },
15606
+ "structure": {
15607
+ "pattern": "tree"
15608
+ },
15609
+ "presentation": {
15610
+ "layout": {
15611
+ "type": "tree",
15612
+ "direction": "TB"
15613
+ },
15614
+ "sort_by": {
15615
+ "property": "title",
15616
+ "direction": "asc"
15617
+ },
15618
+ "card_fields": [
15619
+ "title",
15620
+ "description"
15621
+ ]
15622
+ },
15623
+ "education": {
15624
+ "purpose": 'Resist first-order reasoning by chaining "and then what?" until non-obvious downstream consequences come into view.',
15625
+ "core_question": "If we make this move and it works, what does the world look like next, and is that the world we want?",
15626
+ "when_to_use": [
15627
+ "A decision has feedback loops, market reactions, or behavioural ripples",
15628
+ "The first-order case is compelling, which is exactly when downstream effects bite",
15629
+ "Considering an irreversible or large-scale commitment"
15630
+ ],
15631
+ "when_not_to_use": [
15632
+ "Routine, reversible, low-blast-radius decisions where deliberation costs more than mistakes",
15633
+ "Higher-order branches diverge into pure speculation with no anchor in evidence",
15634
+ "Time pressure makes a deeper trace expensive and the first-order call is good enough"
15635
+ ]
15636
+ }
15637
+ }
15638
+ ];
15639
+ var UPG_FRAMEWORKS_BY_ID = Object.fromEntries(
15640
+ UPG_FRAMEWORKS.map((fw) => [fw.id, fw])
15641
+ );
15642
+ var UPG_FRAMEWORKS_BY_CATEGORY = {};
15643
+ for (const fw of UPG_FRAMEWORKS) {
15644
+ if (!UPG_FRAMEWORKS_BY_CATEGORY[fw.category]) UPG_FRAMEWORKS_BY_CATEGORY[fw.category] = [];
15645
+ UPG_FRAMEWORKS_BY_CATEGORY[fw.category].push(fw);
15646
+ }
15647
+ var PRIORITY_LABELS = [
15648
+ // ── need (CONSOLIDATED: replaces pain_point + user_need) ─────────────────────
15649
+ {
15650
+ id: "need",
15651
+ canonical_label: "Need",
15652
+ alt_labels: [
15653
+ "pain point",
15654
+ "pain",
15655
+ "user need",
15656
+ "customer need",
15657
+ "problem",
15658
+ "struggle",
15659
+ "customer pain",
15660
+ "frustration",
15661
+ "gap",
15662
+ "unmet need",
15663
+ "user problem"
15664
+ ],
15665
+ framework_labels: {
15666
+ lean_canvas: "Problem",
15667
+ design_thinking: "Pain Point",
15668
+ ost: "Opportunity (need)",
15669
+ jtbd: "Struggle",
15670
+ vpc: "Customer Pain"
15671
+ },
15672
+ designations: {
15673
+ pain: "Pain Point",
15674
+ gap: "Need",
15675
+ desire: "Desire",
15676
+ constraint: "Constraint"
15677
+ }
15678
+ },
15679
+ // ── opportunity ──────────────────────────────────────────────────────────────
15680
+ {
15681
+ id: "opportunity",
15682
+ canonical_label: "Opportunity",
15683
+ alt_labels: ["product opportunity", "market opportunity", "user opportunity"],
15684
+ framework_labels: {
15685
+ ost: "Opportunity"
15686
+ }
15687
+ },
15688
+ // ── solution ─────────────────────────────────────────────────────────────────
15689
+ {
15690
+ id: "solution",
15691
+ canonical_label: "Solution",
15692
+ alt_labels: ["proposed solution", "solution idea", "concept", "approach"],
15693
+ framework_labels: {
15694
+ ost: "Solution",
15695
+ design_thinking: "Solution",
15696
+ lean_canvas: "Solution",
15697
+ rice: "Scored Solution"
15698
+ }
15699
+ },
15700
+ // ── experiment (CONSOLIDATED: absorbs ab_test, growth_experiment, pricing_experiment) ──
15701
+ {
15702
+ id: "experiment",
15703
+ canonical_label: "Experiment",
15704
+ alt_labels: [
15705
+ "test",
15706
+ "validation",
15707
+ "ab test",
15708
+ "a/b test",
15709
+ "split test",
15710
+ "growth experiment",
15711
+ "pricing experiment",
15712
+ "usability test",
15713
+ "discovery experiment"
15714
+ ],
15715
+ framework_labels: {
15716
+ ost: "Experiment",
15717
+ design_thinking: "Test",
15718
+ lean_startup: "Experiment"
15719
+ },
15720
+ designations: {
14754
15721
  discovery: "Discovery Experiment",
14755
15722
  ab_test: "A/B Test",
14756
15723
  growth: "Growth Experiment",
@@ -15614,7 +16581,10 @@ var STRATEGY_OUTCOMES_PLAYBOOK = {
15614
16581
  region: "strategy_outcomes",
15615
16582
  is_canonical: true,
15616
16583
  related_framework_ids: ["okr-framework", "three-horizons", "north-star-metric", "metrics-tree", "wardley-map"],
15617
- target_anchor_entity: "objective",
16584
+ // DT-PB-3: anchor is `outcome`, not `objective`. The creation_sequence
16585
+ // creates outcome (step 3) before objective (step 4), and outcome is the
16586
+ // strategy region's gravitational centre (objectives translate outcomes).
16587
+ target_anchor_entity: "outcome",
15618
16588
  creation_sequence: [
15619
16589
  seqStep(
15620
16590
  1,
@@ -15770,8 +16740,12 @@ var DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK = {
15770
16740
  seqStep(
15771
16741
  8,
15772
16742
  "Test",
15773
- ["experiment", "test_plan", "evidence"],
15774
- "Validate with targeted experiments. Close the loop between research and action."
16743
+ // DT-PB-1: was `experiment` which resolves to NO canonical edge with
16744
+ // hypothesis or test_plan, forcing an orphan. `experiment_run` is the
16745
+ // hypothesis-linked test unit (experiment_run_validates_hypothesis,
16746
+ // test_plan_ran_as_experiment_run, experiment_run_yields_evidence).
16747
+ ["experiment_run", "test_plan", "evidence"],
16748
+ "Validate with targeted experiment runs. Close the loop between research and action."
15775
16749
  )
15776
16750
  ]
15777
16751
  };
@@ -15818,8 +16792,8 @@ var MARKET_COMPETITIVE_PLAYBOOK = {
15818
16792
  seqStep(
15819
16793
  6,
15820
16794
  "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."
16795
+ ["competitive_battle_card"],
16796
+ "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
16797
  )
15824
16798
  ]
15825
16799
  };
@@ -16232,8 +17206,8 @@ var OPERATIONS_QUALITY_PLAYBOOK = {
16232
17206
  seqStep(
16233
17207
  5,
16234
17208
  "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."
17209
+ ["test_suite", "test_case", "regression_test", "qa_session", "feature", "bug"],
17210
+ "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
17211
  ),
16238
17212
  seqStep(
16239
17213
  6,
@@ -16941,6 +17915,9 @@ function getVisibleTypes(lens) {
16941
17915
  }
16942
17916
  return [...typeSet];
16943
17917
  }
17918
+ function getLensIds() {
17919
+ return UPG_LENSES.map((l) => l.id);
17920
+ }
16944
17921
  var UPG_DOMAIN_RINGS = [
16945
17922
  {
16946
17923
  id: "nucleus",
@@ -17834,7 +18811,7 @@ var PORTFOLIO_GUIDE = {
17834
18811
  var WORKSPACE_GUIDE = {
17835
18812
  domain_id: "workspace",
17836
18813
  anchor_entity: "workspace",
17837
- creation_sequence: ["workspace"],
18814
+ creation_sequence: ["workspace", "framework_exercise"],
17838
18815
  patterns: [],
17839
18816
  required_bridges: [],
17840
18817
  anti_patterns: [
@@ -20221,7 +21198,7 @@ var UPG_ANTI_PATTERNS = [
20221
21198
  ]
20222
21199
  },
20223
21200
  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.",
21201
+ 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
21202
  stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature"],
20226
21203
  severity: "high",
20227
21204
  source: { kind: "practitioner", attribution: "Clayton Christensen, Jobs to Be Done" }
@@ -20361,7 +21338,7 @@ var UPG_ANTI_PATTERNS = [
20361
21338
  ]
20362
21339
  },
20363
21340
  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.",
21341
+ 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
21342
  stages: ["validation", "build", "beta", "launch", "growth", "mature"],
20366
21343
  severity: "high",
20367
21344
  source: { kind: "book", citation: "Measure What Matters, John Doerr (2017)" }
@@ -20421,7 +21398,7 @@ var UPG_ANTI_PATTERNS = [
20421
21398
  }
20422
21399
  },
20423
21400
  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`.",
21401
+ remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-new-persona`.",
20425
21402
  stages: ["validation", "build", "beta", "launch", "growth", "mature"],
20426
21403
  severity: "medium",
20427
21404
  source: { kind: "practitioner", attribution: "Alan Cooper, The Inmates Are Running the Asylum" }
@@ -20439,7 +21416,7 @@ var UPG_ANTI_PATTERNS = [
20439
21416
  ]
20440
21417
  },
20441
21418
  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`.",
21419
+ remediation: "Spin up at least one `experiment_plan` or `hypothesis` per quarter's build batch. Use `/upg-new-discovery` or `/upg-new-hypothesis`.",
20443
21420
  stages: ["build", "beta", "launch", "growth"],
20444
21421
  severity: "high",
20445
21422
  source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (continuous discovery)" }
@@ -20677,25 +21654,54 @@ var UPG_PRODUCT_STAGE_COERCION_MAP = Object.freeze({
20677
21654
  // check happens before the coercion lookup.
20678
21655
  });
20679
21656
  var UPG_PRODUCT_STAGES_SET = new Set(UPG_PRODUCT_STAGES);
21657
+ function coerceProductStage(value) {
21658
+ if (value === void 0 || value === null) {
21659
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: false };
21660
+ }
21661
+ if (typeof value !== "string") {
21662
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: true };
21663
+ }
21664
+ if (UPG_PRODUCT_STAGES_SET.has(value)) {
21665
+ return { canonical: value, originalValue: value, wasCoerced: false, wasUnknown: false };
21666
+ }
21667
+ const lower = value.toLowerCase();
21668
+ const mapped = UPG_PRODUCT_STAGE_COERCION_MAP[lower];
21669
+ if (mapped) {
21670
+ return { canonical: mapped, originalValue: value, wasCoerced: true, wasUnknown: false };
21671
+ }
21672
+ return { canonical: void 0, originalValue: value, wasCoerced: false, wasUnknown: true };
21673
+ }
20680
21674
  var REFLECT_MODES = [
20681
21675
  "assumptions",
20682
21676
  "alternatives",
20683
21677
  "blind-spots",
20684
21678
  "load-bearing"
20685
21679
  ];
21680
+ function deriveFrameworkExamples() {
21681
+ const byApproach = {
21682
+ plan: [],
21683
+ inspect: [],
21684
+ prioritise: [],
21685
+ trace: [],
21686
+ reflect: []
21687
+ };
21688
+ for (const fw of UPG_FRAMEWORKS) {
21689
+ for (const approachId of fw.approach_ids ?? []) {
21690
+ if (approachId in byApproach) {
21691
+ byApproach[approachId].push(fw.id);
21692
+ }
21693
+ }
21694
+ }
21695
+ return byApproach;
21696
+ }
21697
+ var FRAMEWORK_EXAMPLES = deriveFrameworkExamples();
20686
21698
  var PLAN = {
20687
21699
  id: "plan",
20688
21700
  label: "Plan",
20689
21701
  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
21702
  question_answered: "what should I build next?",
20691
21703
  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
- ]
21704
+ framework_id_examples: FRAMEWORK_EXAMPLES.plan
20699
21705
  };
20700
21706
  var INSPECT = {
20701
21707
  id: "inspect",
@@ -20703,28 +21709,15 @@ var INSPECT = {
20703
21709
  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
21710
  question_answered: "what's broken?",
20705
21711
  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
- ]
21712
+ framework_id_examples: FRAMEWORK_EXAMPLES.inspect
20713
21713
  };
20714
21714
  var PRIORITISE = {
20715
21715
  id: "prioritise",
20716
21716
  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).`,
21717
+ 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
21718
  question_answered: "what's most important?",
20719
21719
  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
- ]
21720
+ framework_id_examples: FRAMEWORK_EXAMPLES.prioritise
20728
21721
  };
20729
21722
  var TRACE = {
20730
21723
  id: "trace",
@@ -20732,34 +21725,15 @@ var TRACE = {
20732
21725
  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
21726
  question_answered: "walk a meaningful path through existing graph",
20734
21727
  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
- ]
21728
+ framework_id_examples: FRAMEWORK_EXAMPLES.trace
20743
21729
  };
20744
21730
  var REFLECT = {
20745
21731
  id: "reflect",
20746
21732
  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.',
21733
+ 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
21734
  question_answered: "what should I be questioning?",
20749
21735
  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
- ]
21736
+ framework_id_examples: FRAMEWORK_EXAMPLES.reflect
20763
21737
  };
20764
21738
  var UPG_APPROACHES = [PLAN, INSPECT, PRIORITISE, TRACE, REFLECT];
20765
21739
  var UPG_APPROACHES_BY_ID = Object.fromEntries(
@@ -22637,7 +23611,7 @@ var NODE_KEY_ORDER = [
22637
23611
  "sort_order",
22638
23612
  "properties"
22639
23613
  ];
22640
- var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence"];
23614
+ var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "properties"];
22641
23615
  var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence"];
22642
23616
  var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
22643
23617
  function canonicalNode(node) {
@@ -22649,7 +23623,8 @@ function canonicalNode(node) {
22649
23623
  }
22650
23624
  function canonicalEdge(edge) {
22651
23625
  return orderedObject(edge, EDGE_KEY_ORDER, {
22652
- forceKeys: ["id", "source", "target", "type"]
23626
+ forceKeys: ["id", "source", "target", "type"],
23627
+ openKeys: ["properties"]
22653
23628
  });
22654
23629
  }
22655
23630
  function canonicalCrossEdge(edge) {
@@ -22687,7 +23662,7 @@ function portfolioBody(doc) {
22687
23662
  function computeBodyChecksum(doc) {
22688
23663
  const body = isPortfolio(doc) ? portfolioBody(doc) : singleBody(doc);
22689
23664
  const content = JSON.stringify(body);
22690
- return createHash2(INTEGRITY_HASH_PRIMITIVE).update(content).digest("hex").slice(0, INTEGRITY_DIGEST_HEX);
23665
+ return createHash(INTEGRITY_HASH_PRIMITIVE).update(content).digest("hex").slice(0, INTEGRITY_DIGEST_HEX);
22691
23666
  }
22692
23667
  function isPortfolio(doc) {
22693
23668
  return doc.type === "portfolio" || "cross_edges" in doc;
@@ -22753,7 +23728,7 @@ function serializePortfolioWithHeader(doc, opts) {
22753
23728
  header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
22754
23729
  return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
22755
23730
  }
22756
- var UPG_VERSION = "0.8.0";
23731
+ var UPG_VERSION = "0.8.4";
22757
23732
  var MARKDOWN_FORMAT_VERSION = "0.1";
22758
23733
  var UPG_TYPES = getTypes();
22759
23734
  var UPG_TYPES_SET = new Set(UPG_TYPES);
@@ -22813,6 +23788,52 @@ var UPG_DOMAIN_COUNT = UPG_DOMAINS.length;
22813
23788
  var UPG_EDGE_COUNT = UPG_EDGE_TYPES.length;
22814
23789
  var UPG_META_COUNT = UPG_ENTITY_META.length;
22815
23790
 
23791
+ // src/lib/server-context.ts
23792
+ function text(s) {
23793
+ return { content: [{ type: "text", text: s }] };
23794
+ }
23795
+ function textError(s) {
23796
+ return { content: [{ type: "text", text: s }], isError: true };
23797
+ }
23798
+ var CANONICAL_LENS_IDS = getLensIds();
23799
+ function isCanonicalLens(id) {
23800
+ return typeof id === "string" && CANONICAL_LENS_IDS.includes(id);
23801
+ }
23802
+ function createSessionContext() {
23803
+ return {
23804
+ lens: "product",
23805
+ skills_invoked: [],
23806
+ recommendations_given: [],
23807
+ focus_area: null,
23808
+ custom: {}
23809
+ };
23810
+ }
23811
+ function createQueryCache() {
23812
+ return { entries: /* @__PURE__ */ new Map(), counter: 0 };
23813
+ }
23814
+ function syncFilePath(upgPath) {
23815
+ const dir = path.dirname(upgPath);
23816
+ const base = path.basename(upgPath, ".upg");
23817
+ return path.join(dir, `${base}.upg-sync`);
23818
+ }
23819
+ async function readSyncState(upgPath) {
23820
+ const p = syncFilePath(upgPath);
23821
+ try {
23822
+ const raw = await fsp.readFile(p, "utf-8");
23823
+ return JSON.parse(raw);
23824
+ } catch {
23825
+ return null;
23826
+ }
23827
+ }
23828
+ async function writeSyncState(upgPath, state) {
23829
+ const p = syncFilePath(upgPath);
23830
+ await fsp.writeFile(p, JSON.stringify(state, null, 2) + "\n", "utf-8");
23831
+ }
23832
+ async function hashFile(filePath) {
23833
+ const content = await fsp.readFile(filePath, "utf-8");
23834
+ return createHash2("sha256").update(content).digest("hex");
23835
+ }
23836
+
22816
23837
  // src/tools/context.ts
22817
23838
  import { computeGraphDigest } from "@unified-product-graph/sdk";
22818
23839
  function lensAwareLabel(entityType, lensId) {
@@ -22870,7 +23891,7 @@ Lens: ${sessionContext.lens}`
22870
23891
  lines.push(` - [${sev}] ${b.title}`);
22871
23892
  }
22872
23893
  }
22873
- } else if (sessionContext.lens === "design") {
23894
+ } else if (sessionContext.lens === "ux_design") {
22874
23895
  const screens = nodes.filter((n) => n.type === "screen");
22875
23896
  const components = nodes.filter((n) => n.type === "design_component");
22876
23897
  const flows = nodes.filter((n) => n.type === "user_flow");
@@ -22985,7 +24006,7 @@ var getGraphDigest = (args, ctx) => {
22985
24006
  const blockedFeatures = allNodes.filter((n) => blockedFeatureIds.has(n.id)).map((n) => n.title);
22986
24007
  const openInvestigations = allNodes.filter((n) => n.type === "investigation" && n.status !== "resolved").length;
22987
24008
  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") {
24009
+ } else if (sessionContext.lens === "ux_design") {
22989
24010
  const screens = allNodes.filter((n) => n.type === "screen").length;
22990
24011
  const components = allNodes.filter((n) => n.type === "design_component").length;
22991
24012
  const flows = allNodes.filter((n) => n.type === "user_flow").length;
@@ -23103,7 +24124,12 @@ var updateSessionContext = (args, ctx) => {
23103
24124
  if (focusArea !== void 0) {
23104
24125
  sessionContext.focus_area = focusArea;
23105
24126
  }
23106
- if (lensArg && ["product", "engineering", "design", "growth"].includes(lensArg)) {
24127
+ if (lensArg !== void 0) {
24128
+ if (!isCanonicalLens(lensArg)) {
24129
+ return textError(
24130
+ `Invalid lens "${lensArg}". Canonical lenses: ${CANONICAL_LENS_IDS.join(", ")}`
24131
+ );
24132
+ }
23107
24133
  sessionContext.lens = lensArg;
23108
24134
  if (persistLens && store) {
23109
24135
  const doc = store.getDocument();
@@ -23913,7 +24939,7 @@ var updateNode = (args, ctx) => {
23913
24939
  const existingNode = store.getNode(nid);
23914
24940
  if (existingNode) {
23915
24941
  const sw = validateStatusAgainstLifecycle(existingNode.type, args.status);
23916
- if (sw) warnings.push(sw);
24942
+ if (sw) return textError(sw);
23917
24943
  }
23918
24944
  }
23919
24945
  let unknownProperties = [];
@@ -23946,10 +24972,18 @@ var updateNode = (args, ctx) => {
23946
24972
  });
23947
24973
  if (lengthWarnings.length > 0) warnings.push(...lengthWarnings);
23948
24974
  try {
23949
- const updated = store.updateNode(nid, patch);
24975
+ let updated = store.updateNode(nid, patch);
24976
+ let removedKeys;
24977
+ const unsetArg = args.unset_properties;
24978
+ if (Array.isArray(unsetArg) && unsetArg.length > 0) {
24979
+ const r = store.unsetNodeProperties(nid, unsetArg);
24980
+ updated = r.node;
24981
+ if (r.removed.length > 0) removedKeys = r.removed;
24982
+ }
23950
24983
  const result = { node: updated };
23951
24984
  if (warnings.length > 0) result.warning = warnings.join(" | ");
23952
24985
  if (unknownProperties.length > 0) result.unknown_properties = unknownProperties;
24986
+ if (removedKeys && removedKeys.length > 0) result.unset = removedKeys;
23953
24987
  return text(JSON.stringify(result, null, 2));
23954
24988
  } catch (err) {
23955
24989
  return textError(err.message);
@@ -24271,7 +25305,7 @@ var deduplicateNodes = (args, ctx) => {
24271
25305
  // src/tools/edges.ts
24272
25306
  import { edgeId as edgeId2 } from "@unified-product-graph/sdk";
24273
25307
  import { inferEdgeTypeWithTier } from "@unified-product-graph/sdk";
24274
- import { validateEdgeTypePair } from "@unified-product-graph/sdk";
25308
+ import { validateExplicitEdgeType } from "@unified-product-graph/sdk";
24275
25309
  import { buildResolverHints } from "@unified-product-graph/sdk";
24276
25310
  import {
24277
25311
  createEdge as createEdgeLib,
@@ -24300,7 +25334,8 @@ var createEdge = (args, ctx) => {
24300
25334
  target_id: args.target_id,
24301
25335
  target_title: args.target_title,
24302
25336
  target_type: args.target_type,
24303
- type: args.type
25337
+ type: args.type,
25338
+ properties: args.properties
24304
25339
  });
24305
25340
  if ("error" in result) {
24306
25341
  if (result.no_canonical_edge_for) {
@@ -24378,13 +25413,13 @@ var batchCreateEdges = (args, ctx) => {
24378
25413
  );
24379
25414
  }
24380
25415
  if (e.type) {
24381
- const pairCheck = validateEdgeTypePair(
25416
+ const typeCheck = validateExplicitEdgeType(
24382
25417
  e.type,
24383
25418
  sourceNode.type,
24384
25419
  targetNode.type
24385
25420
  );
24386
- if (!pairCheck.valid) {
24387
- return textError(`Edge at index ${i}: ${pairCheck.reason}`);
25421
+ if (typeCheck.errors.length > 0) {
25422
+ return textError(`Edge at index ${i}: ${typeCheck.errors.join(" ")}`);
24388
25423
  }
24389
25424
  resolvedEdgeTypes.push(e.type);
24390
25425
  } else {
@@ -24832,10 +25867,11 @@ var listLocalProducts = (_args, _ctx) => {
24832
25867
  try {
24833
25868
  const raw = fs.readFileSync(filePath, "utf-8");
24834
25869
  const doc = JSON.parse(raw);
25870
+ const coerced = coerceProductStage(doc.product?.stage);
24835
25871
  products.push({
24836
25872
  file: path3.relative(cwd, filePath),
24837
25873
  title: doc.product?.title ?? "(untitled)",
24838
- stage: doc.product?.stage ?? "unknown",
25874
+ stage: coerced.canonical ?? null,
24839
25875
  nodes: Array.isArray(doc.nodes) ? doc.nodes.length : 0,
24840
25876
  edges: Array.isArray(doc.edges) ? doc.edges.length : 0
24841
25877
  });
@@ -25198,6 +26234,62 @@ var getEntitySchema = (args, _ctx) => {
25198
26234
  }
25199
26235
  };
25200
26236
 
26237
+ // src/tools/frameworks.ts
26238
+ import {
26239
+ applyFramework as applyFrameworkLib,
26240
+ scoreEntity as scoreEntityLib
26241
+ } from "@unified-product-graph/sdk";
26242
+ var applyFramework = (args, ctx) => {
26243
+ const { store } = ctx;
26244
+ const frameworkId = args.framework_id;
26245
+ if (!frameworkId) {
26246
+ return textError('Missing required parameter: framework_id (e.g. "moscow", "rice-scoring")');
26247
+ }
26248
+ try {
26249
+ const result = applyFrameworkLib(store, {
26250
+ framework_id: frameworkId,
26251
+ title: args.title,
26252
+ entity_ids: args.entity_ids ?? [],
26253
+ status: args.status
26254
+ });
26255
+ return text(
26256
+ JSON.stringify(
26257
+ {
26258
+ exercise_id: result.exercise.id,
26259
+ exercise: result.exercise,
26260
+ included: result.edges.map((e) => ({ edge_id: e.id, entity_id: e.target })),
26261
+ warnings: result.warnings
26262
+ },
26263
+ null,
26264
+ 2
26265
+ )
26266
+ );
26267
+ } catch (err) {
26268
+ return textError(err.message);
26269
+ }
26270
+ };
26271
+ var scoreEntity = (args, ctx) => {
26272
+ const { store } = ctx;
26273
+ const exerciseId = args.exercise_id;
26274
+ const entityId = args.entity_id;
26275
+ const values = args.values;
26276
+ if (!exerciseId) return textError("Missing required parameter: exercise_id");
26277
+ if (!entityId) return textError("Missing required parameter: entity_id");
26278
+ if (!values || typeof values !== "object" || Array.isArray(values)) {
26279
+ return textError(
26280
+ 'Missing required parameter: values (object of input \u2192 value, e.g. { "moscow": "must" })'
26281
+ );
26282
+ }
26283
+ const result = scoreEntityLib(store, {
26284
+ exercise_id: exerciseId,
26285
+ entity_id: entityId,
26286
+ values,
26287
+ replace: args.replace
26288
+ });
26289
+ if ("error" in result) return textError(result.error);
26290
+ return text(JSON.stringify({ edge: result.edge, warnings: result.warnings }, null, 2));
26291
+ };
26292
+
25201
26293
  // src/tools/spec.ts
25202
26294
  import { buildResolverHints as buildResolverHints2 } from "@unified-product-graph/sdk";
25203
26295
  import {
@@ -25211,7 +26303,7 @@ import {
25211
26303
  // src/tools/validation.ts
25212
26304
  import { computeSchemaDriftSummary } from "@unified-product-graph/sdk";
25213
26305
  import { collectAntiPatternInputs } from "@unified-product-graph/sdk";
25214
- import { validateEdgeTypePair as validateEdgeTypePair2 } from "@unified-product-graph/sdk";
26306
+ import { validateEdgeTypePair } from "@unified-product-graph/sdk";
25215
26307
  import { checkPropertyTypes as checkPropertyTypes2 } from "@unified-product-graph/sdk";
25216
26308
  var CANONICAL_NODE_FIELDS = /* @__PURE__ */ new Set([
25217
26309
  "id",
@@ -25503,7 +26595,7 @@ var validateGraph = (args, ctx) => {
25503
26595
  const sourceNode = nodeById.get(edge.source);
25504
26596
  const targetNode = nodeById.get(edge.target);
25505
26597
  if (!sourceNode || !targetNode) continue;
25506
- const pairCheck = validateEdgeTypePair2(
26598
+ const pairCheck = validateEdgeTypePair(
25507
26599
  edge.type,
25508
26600
  sourceNode.type,
25509
26601
  targetNode.type
@@ -25757,10 +26849,13 @@ function approachEnvelope(approachId, scope, payload) {
25757
26849
  }
25758
26850
  var plan = (args, ctx) => {
25759
26851
  const region = args.region;
25760
- const result = executePlan(ctx.store, region);
26852
+ const exhaustive = args.exhaustive;
26853
+ const result = executePlan(ctx.store, { region, exhaustive });
25761
26854
  return approachEnvelope("plan", region ?? null, {
25762
- params: { region: region ?? null },
26855
+ params: { region: region ?? null, exhaustive: exhaustive ?? false },
25763
26856
  region: result.region,
26857
+ plan_scope: result.scope,
26858
+ scoped_regions: result.scoped_regions,
25764
26859
  missing_entities: result.missing_entities,
25765
26860
  coverage_score: result.coverage_score,
25766
26861
  expected_count: result.expected_count,
@@ -25789,34 +26884,38 @@ var inspect = async (args, ctx) => {
25789
26884
  });
25790
26885
  };
25791
26886
  var prioritise = (args, ctx) => {
25792
- const candidates = args.candidates;
26887
+ const candidates = args.candidates ?? [];
25793
26888
  const frameworkId = args.framework_id;
25794
- if (!candidates || !Array.isArray(candidates) || candidates.length === 0) {
25795
- return textError("Missing required parameter: candidates (entity_id[])");
25796
- }
26889
+ const exerciseId = args.exercise_id;
25797
26890
  if (!frameworkId) {
25798
26891
  return textError(
25799
26892
  'Missing required parameter: framework_id (e.g. "rice-scoring", "ice-scoring", "wsjf")'
25800
26893
  );
25801
26894
  }
26895
+ if (!exerciseId && (!Array.isArray(candidates) || candidates.length === 0)) {
26896
+ return textError(
26897
+ "Provide candidates (entity_id[]), or an exercise_id whose includes edges supply the candidates and their scoring inputs."
26898
+ );
26899
+ }
25802
26900
  const framework = UPG_FRAMEWORKS_BY_ID[frameworkId];
25803
26901
  if (!framework) {
25804
26902
  return textError(
25805
26903
  `Unknown framework_id: "${frameworkId}". See list_frameworks for valid ids.`
25806
26904
  );
25807
26905
  }
25808
- const execResult = executePrioritise(framework, candidates, ctx.store);
26906
+ const execResult = executePrioritise(framework, candidates, ctx.store, { exerciseId });
26907
+ const params = { candidates, framework_id: frameworkId, ...exerciseId ? { exercise_id: exerciseId } : {} };
25809
26908
  if (execResult.kind === "execution") {
25810
26909
  return approachEnvelope("prioritise", candidates, {
25811
- params: { candidates, framework_id: frameworkId },
26910
+ params,
25812
26911
  framework_resolved: execResult.framework_used,
25813
26912
  ranked: execResult.ranked,
25814
26913
  required_properties: execResult.required_properties,
25815
- execution_mode: "execution_v0_4_0"
26914
+ execution_mode: exerciseId ? "exercise_v0_8_4" : "execution_v0_4_0"
25816
26915
  });
25817
26916
  }
25818
26917
  return approachEnvelope("prioritise", candidates, {
25819
- params: { candidates, framework_id: frameworkId },
26918
+ params,
25820
26919
  framework_resolved: execResult.framework_used,
25821
26920
  hint: execResult.hint,
25822
26921
  execution_mode: "definition_lookup_v0_4_0"
@@ -26782,9 +27881,14 @@ function auditOne(name) {
26782
27881
  };
26783
27882
  }
26784
27883
  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();
27884
+ const names = /* @__PURE__ */ new Set();
27885
+ for (const dir of [sourceSkillsDir(), deployedSkillsDir()]) {
27886
+ if (!existsSync2(dir)) continue;
27887
+ for (const d of readdirSync2(dir, { withFileTypes: true })) {
27888
+ if (d.isDirectory() || d.isSymbolicLink()) names.add(d.name);
27889
+ }
27890
+ }
27891
+ return [...names].sort();
26788
27892
  }
26789
27893
  var skillAudit = (args) => {
26790
27894
  const filter = typeof args?.name === "string" && args.name.length > 0 ? args.name : null;
@@ -26974,6 +28078,11 @@ var TOOL_DEFINITIONS = [
26974
28078
  properties: {
26975
28079
  type: "object",
26976
28080
  description: "Merged with existing properties"
28081
+ },
28082
+ unset_properties: {
28083
+ type: "array",
28084
+ items: { type: "string" },
28085
+ 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
28086
  }
26978
28087
  },
26979
28088
  required: ["node_id"]
@@ -27181,6 +28290,10 @@ var TOOL_DEFINITIONS = [
27181
28290
  type: {
27182
28291
  type: "string",
27183
28292
  description: "Edge type. Auto-inferred if omitted."
28293
+ },
28294
+ properties: {
28295
+ type: "object",
28296
+ description: "Edge-scoped properties. Only permitted on edge types that opt in (currently framework_exercise_includes_node); rejected on plain semantic edges."
27184
28297
  }
27185
28298
  },
27186
28299
  required: ["source_id"]
@@ -27456,17 +28569,18 @@ var TOOL_DEFINITIONS = [
27456
28569
  },
27457
28570
  {
27458
28571
  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.',
28572
+ 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
28573
  inputSchema: {
27461
28574
  type: "object",
27462
28575
  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.' }
28576
+ 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.` },
28577
+ 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
28578
  }
27465
28579
  }
27466
28580
  },
27467
28581
  {
27468
28582
  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.',
28583
+ 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
28584
  inputSchema: {
27471
28585
  type: "object",
27472
28586
  properties: {
@@ -27481,10 +28595,39 @@ var TOOL_DEFINITIONS = [
27481
28595
  inputSchema: {
27482
28596
  type: "object",
27483
28597
  properties: {
27484
- candidates: { type: "array", items: { type: "string" }, description: "Required. entity_id[] to rank." },
27485
- framework_id: { type: "string", description: 'Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf").' }
28598
+ candidates: { type: "array", items: { type: "string" }, description: "entity_id[] to rank. Optional when exercise_id is given (the exercise supplies them)." },
28599
+ framework_id: { type: "string", description: 'Required. UPGFramework.id of the scoring lens (e.g. "rice-scoring", "ice-scoring", "kano-model", "cost-of-delay", "wsjf").' },
28600
+ exercise_id: { type: "string", description: "Optional (0.8.4). A framework_exercise id: reads each candidate's scoring inputs from its includes-edge properties instead of node.properties, and bypasses the target-type guard so any entity type can be scored." }
27486
28601
  },
27487
- required: ["candidates", "framework_id"]
28602
+ required: ["framework_id"]
28603
+ }
28604
+ },
28605
+ {
28606
+ name: "apply_framework",
28607
+ description: "Apply a framework (MoSCoW, RICE, Kano, ...) to a set of entities: creates a framework_exercise node and an `includes` edge to each entity. The per-entity result is recorded on the edge via score_entity, never on the entity node, so the same entity can sit in many exercises and any entity type can be scored. Returns { exercise_id, exercise, included, warnings }.",
28608
+ inputSchema: {
28609
+ type: "object",
28610
+ properties: {
28611
+ framework_id: { type: "string", description: 'Required. UPGFramework.id (e.g. "moscow", "rice-scoring").' },
28612
+ title: { type: "string", description: 'Human label for the exercise (default "<Framework> exercise").' },
28613
+ entity_ids: { type: "array", items: { type: "string" }, description: "Entities to pull into the exercise (any type)." },
28614
+ status: { type: "string", description: "Lifecycle phase: draft | active | archived (default draft)." }
28615
+ },
28616
+ required: ["framework_id"]
28617
+ }
28618
+ },
28619
+ {
28620
+ name: "score_entity",
28621
+ description: "Record a framework's result for one entity on the exercise's includes edge (a MoSCoW bucket, a RICE score, a canvas slot). Auto-includes the entity if not already in scope. Merges into existing edge properties unless replace is set. Returns { edge, warnings }.",
28622
+ inputSchema: {
28623
+ type: "object",
28624
+ properties: {
28625
+ exercise_id: { type: "string", description: "Required. The framework_exercise id." },
28626
+ entity_id: { type: "string", description: "Required. The entity being scored." },
28627
+ values: { type: "object", description: 'Required. The result as { input: value }, e.g. { "moscow": "must" } or { "reach": 800, "impact": 3 }.' },
28628
+ replace: { type: "boolean", description: "Replace the edge properties instead of merging (default false)." }
28629
+ },
28630
+ required: ["exercise_id", "entity_id", "values"]
27488
28631
  }
27489
28632
  },
27490
28633
  {
@@ -27970,10 +29113,10 @@ var TOOL_DEFINITIONS = [
27970
29113
  inputSchema: {
27971
29114
  type: "object",
27972
29115
  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")' },
29116
+ skill_invoked: { type: "string", description: 'Register that this skill was just invoked (e.g. "upg-show-status")' },
29117
+ recommendation: { type: "string", description: 'Record a recommendation given to the user (e.g. "Run /upg-new-strategy to fill strategy gap")' },
27975
29118
  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." },
29119
+ 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
29120
  persist_lens: { type: "boolean", description: "If true, also save the lens to the .upg file so it persists across sessions" },
27978
29121
  custom: { type: "object", description: "Arbitrary key-value pairs for cross-skill state" }
27979
29122
  }
@@ -28140,6 +29283,8 @@ var HANDLERS = {
28140
29283
  batch_delete_nodes: batchDeleteNodes,
28141
29284
  batch_create_edges: batchCreateEdges,
28142
29285
  batch_delete_edges: batchDeleteEdges,
29286
+ apply_framework: applyFramework,
29287
+ score_entity: scoreEntity,
28143
29288
  repair_dangling_edges: repairDanglingEdges,
28144
29289
  export_edges: exportEdges,
28145
29290
  rename_edge_type: renameEdgeType,
@@ -28301,7 +29446,7 @@ function createServer(store) {
28301
29446
  {
28302
29447
  const doc = store.getDocument();
28303
29448
  const persistedLens = doc.product?.lens;
28304
- if (persistedLens && ["product", "engineering", "design", "growth"].includes(persistedLens)) {
29449
+ if (persistedLens && isCanonicalLens(persistedLens)) {
28305
29450
  sessionContext.lens = persistedLens;
28306
29451
  }
28307
29452
  }
@@ -28408,7 +29553,12 @@ async function runMcpServer() {
28408
29553
  options: {
28409
29554
  file: { type: "string", short: "f" },
28410
29555
  title: { type: "string", short: "t" }
28411
- }
29556
+ },
29557
+ // Tolerate stray positionals. When launched via `upg mcp run`, argv carries
29558
+ // the `mcp run` subcommand tokens; without this, parseArgs throws
29559
+ // ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL and the server never starts. The
29560
+ // standalone bin (`upg-mcp-server`) passes none, so this is harmless there.
29561
+ allowPositionals: true
28412
29562
  });
28413
29563
  let resolvedPath = await discoverUPGFile(values.file);
28414
29564
  if (!resolvedPath) {
@@ -28480,7 +29630,7 @@ async function runMcpServer() {
28480
29630
  Deprecated types found in your graph:
28481
29631
  ${lines.join("\n")}
28482
29632
  `);
28483
- process.stderr.write(`Run /upg-migrate to update them.
29633
+ process.stderr.write(`Run /upg-fix-types to update them.
28484
29634
 
28485
29635
  `);
28486
29636
  }