@unified-product-graph/mcp-server 0.8.15 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/TOOLS.md +194 -6
- package/dist/index.js +658 -46
- package/dist/index.js.map +1 -1
- package/dist/tools-manifest.json +345 -23
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -116,7 +116,7 @@ var UPG_DOMAINS = [
|
|
|
116
116
|
{
|
|
117
117
|
id: "product_spec",
|
|
118
118
|
label: "Product Specification",
|
|
119
|
-
description: "What you are building and shipping. Feature areas group related capabilities. Features, epics, and user stories break work down. Acceptance criteria define done. Tasks and bugs track execution. Releases and changelogs mark what shipped. Roadmaps and roadmap items plan what comes next.
|
|
119
|
+
description: "What you are building and shipping. Feature areas group related capabilities. Features, epics, and user stories break work down. Acceptance criteria define done. Tasks and bugs track execution. Releases and changelogs mark what shipped. Roadmaps and roadmap items plan what comes next. Roadmap themes group roadmap work around the customer problem it solves, one level down from the strategic themes in Strategy. Translates Strategy into Engineering and tracks delivery through Program Management.",
|
|
120
120
|
types: [
|
|
121
121
|
"feature",
|
|
122
122
|
"feature_area",
|
|
@@ -128,7 +128,7 @@ var UPG_DOMAINS = [
|
|
|
128
128
|
"bug",
|
|
129
129
|
"roadmap",
|
|
130
130
|
"roadmap_item",
|
|
131
|
-
"
|
|
131
|
+
"roadmap_theme",
|
|
132
132
|
"changelog"
|
|
133
133
|
]
|
|
134
134
|
},
|
|
@@ -556,8 +556,8 @@ var UPG_ENTITY_META = [
|
|
|
556
556
|
{ name: "need", type_id: "ent_313", maturity: "stable", since: "0.1.0" },
|
|
557
557
|
{ name: "switching_cost", type_id: "ent_022", maturity: "stable", since: "0.1.0" },
|
|
558
558
|
// ── Discovery ──
|
|
559
|
-
{ name: "opportunity", type_id: "ent_023", maturity: "stable", since: "0.1.0" },
|
|
560
|
-
{ name: "solution", type_id: "ent_024", maturity: "stable", since: "0.1.0" },
|
|
559
|
+
{ name: "opportunity", type_id: "ent_023", maturity: "stable", since: "0.1.0", default_frameworks: ["opportunity-sizing", "rice-scoring"] },
|
|
560
|
+
{ name: "solution", type_id: "ent_024", maturity: "stable", since: "0.1.0", default_frameworks: ["rice-scoring"] },
|
|
561
561
|
{ name: "feasibility_study", type_id: "ent_025", maturity: "stable", since: "0.1.0" },
|
|
562
562
|
{ name: "design_sprint", type_id: "ent_026", maturity: "stable", since: "0.1.0" },
|
|
563
563
|
// ── Validation ──
|
|
@@ -658,7 +658,8 @@ var UPG_ENTITY_META = [
|
|
|
658
658
|
{ name: "bug", type_id: "ent_077", maturity: "stable", since: "0.1.0" },
|
|
659
659
|
{ name: "roadmap", type_id: "ent_078", maturity: "stable", since: "0.1.0" },
|
|
660
660
|
{ name: "roadmap_item", type_id: "ent_079", maturity: "stable", since: "0.1.0" },
|
|
661
|
-
{ name: "theme", type_id: "ent_080", maturity: "
|
|
661
|
+
{ name: "theme", type_id: "ent_080", maturity: "deprecated", since: "0.1.0", deprecated_in: "0.9.0", replacement: "roadmap_theme" },
|
|
662
|
+
{ name: "roadmap_theme", type_id: "ent_351", maturity: "stable", since: "0.1.0" },
|
|
662
663
|
{ name: "changelog", type_id: "ent_081", maturity: "stable", since: "0.1.0" },
|
|
663
664
|
// ── Engineering ──
|
|
664
665
|
{ name: "bounded_context", type_id: "ent_082", maturity: "stable", since: "0.1.0" },
|
|
@@ -1165,6 +1166,12 @@ var UPG_EDGE_CATALOG = {
|
|
|
1165
1166
|
strategic_pillar_delivers_value_stream: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "hierarchy", source_type: "strategic_pillar", target_type: "value_stream" },
|
|
1166
1167
|
strategic_pillar_decided_via_decision: { forward_verb: "decided_via", reverse_verb: "decided_for", classification: "hierarchy", source_type: "strategic_pillar", target_type: "decision" },
|
|
1167
1168
|
strategic_theme_pursues_initiative: { forward_verb: "pursues", reverse_verb: "pursued_under", classification: "hierarchy", source_type: "strategic_theme", target_type: "initiative" },
|
|
1169
|
+
// v0.9.0 (UPG-660): the soft bridge from the annual strategy focus area to the
|
|
1170
|
+
// roadmap grouping that realises it. Semantic, NOT hierarchy: strategic_theme and
|
|
1171
|
+
// roadmap_theme sit on different spines (strategy cascade vs roadmap cascade), so
|
|
1172
|
+
// this is a cross-reference, not containment. Pairs with the theme → roadmap_theme
|
|
1173
|
+
// rename that removed the bare-'theme' collision (N6/UPG-652 lineage).
|
|
1174
|
+
strategic_theme_realised_by_roadmap_theme: { forward_verb: "realised_by", reverse_verb: "realises", classification: "semantic", source_type: "strategic_theme", target_type: "roadmap_theme" },
|
|
1168
1175
|
// v0.5.4 (UPG-511): three edges that lift strategic_theme from structural
|
|
1169
1176
|
// isolation to a conceptually central strategy node.
|
|
1170
1177
|
//
|
|
@@ -1222,7 +1229,7 @@ var UPG_EDGE_CATALOG = {
|
|
|
1222
1229
|
product_builds_feature: { forward_verb: "builds", reverse_verb: "built_by", classification: "hierarchy", source_type: "product", target_type: "feature" },
|
|
1223
1230
|
product_ships_via_release: { forward_verb: "ships_via", reverse_verb: "ships", classification: "hierarchy", source_type: "product", target_type: "release" },
|
|
1224
1231
|
product_plans_via_roadmap: { forward_verb: "plans_via", reverse_verb: "plans_for", classification: "hierarchy", source_type: "product", target_type: "roadmap" },
|
|
1225
|
-
|
|
1232
|
+
product_categorises_by_roadmap_theme: { forward_verb: "categorises_by", reverse_verb: "categorises", classification: "hierarchy", source_type: "product", target_type: "roadmap_theme" },
|
|
1226
1233
|
feature_area_contains_feature: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature" },
|
|
1227
1234
|
feature_area_contains_feature_area: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature_area" },
|
|
1228
1235
|
outcome_delivered_by_feature: { forward_verb: "delivered_by", reverse_verb: "delivers", classification: "cross-domain", source_type: "outcome", target_type: "feature" },
|
|
@@ -1246,12 +1253,12 @@ var UPG_EDGE_CATALOG = {
|
|
|
1246
1253
|
release_contains_bug: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "release", target_type: "bug" },
|
|
1247
1254
|
release_documented_in_changelog: { forward_verb: "documented_in", reverse_verb: "documents", classification: "hierarchy", source_type: "release", target_type: "changelog" },
|
|
1248
1255
|
roadmap_contains_roadmap_item: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "roadmap", target_type: "roadmap_item" },
|
|
1249
|
-
|
|
1256
|
+
roadmap_categorised_by_roadmap_theme: { forward_verb: "categorised_by", reverse_verb: "categorises", classification: "hierarchy", source_type: "roadmap", target_type: "roadmap_theme" },
|
|
1250
1257
|
roadmap_schedules_release: { forward_verb: "schedules", reverse_verb: "scheduled_in", classification: "hierarchy", source_type: "roadmap", target_type: "release" },
|
|
1251
|
-
|
|
1252
|
-
// feature_area is not contained by
|
|
1258
|
+
roadmap_theme_groups_feature: { forward_verb: "groups", reverse_verb: "grouped_in", classification: "hierarchy", source_type: "roadmap_theme", target_type: "feature" },
|
|
1259
|
+
// feature_area is not contained by roadmap_theme; roadmap themes span multiple
|
|
1253
1260
|
// areas cross-cuttingly. Containment path: product → feature_area.
|
|
1254
|
-
|
|
1261
|
+
roadmap_theme_spans_feature_area: { forward_verb: "spans", reverse_verb: "spanned_by", classification: "semantic", source_type: "roadmap_theme", target_type: "feature_area" },
|
|
1255
1262
|
// The legacy `story_task` collapsed into `task` (v0.4.0), so the implements
|
|
1256
1263
|
// relationship is the canonical `task_implements_user_story` above; there is
|
|
1257
1264
|
// no separate story_task edge. (v0.2.7 introduced the Statement/Implementation
|
|
@@ -2733,7 +2740,8 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
2733
2740
|
"shares_metric",
|
|
2734
2741
|
"depends_on_product",
|
|
2735
2742
|
"cannibalises",
|
|
2736
|
-
"succeeds"
|
|
2743
|
+
"succeeds",
|
|
2744
|
+
"hosts"
|
|
2737
2745
|
];
|
|
2738
2746
|
var TYPES = getTypes();
|
|
2739
2747
|
var TYPES_SET = new Set(TYPES);
|
|
@@ -2827,7 +2835,7 @@ var UPG_VALID_CHILDREN = {
|
|
|
2827
2835
|
"feature_area",
|
|
2828
2836
|
"release",
|
|
2829
2837
|
"roadmap",
|
|
2830
|
-
"
|
|
2838
|
+
"roadmap_theme",
|
|
2831
2839
|
// Engineering
|
|
2832
2840
|
"bounded_context",
|
|
2833
2841
|
"code_repository",
|
|
@@ -3223,8 +3231,8 @@ var UPG_VALID_CHILDREN = {
|
|
|
3223
3231
|
a11y_standard: ["a11y_guideline", "a11y_audit", "a11y_annotation"],
|
|
3224
3232
|
a11y_audit: ["a11y_issue"],
|
|
3225
3233
|
// ── Product Specification expansion ─────────────────────────────────────────
|
|
3226
|
-
roadmap: ["roadmap_item", "
|
|
3227
|
-
|
|
3234
|
+
roadmap: ["roadmap_item", "roadmap_theme", "release"],
|
|
3235
|
+
roadmap_theme: ["feature"],
|
|
3228
3236
|
// ── Unified Context Layer hierarchy ─────────────────────────────────────────
|
|
3229
3237
|
design_system: [
|
|
3230
3238
|
"design_component",
|
|
@@ -5820,7 +5828,7 @@ var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
5820
5828
|
// v0.7.0/UPG-571. ─
|
|
5821
5829
|
"acceptance_criterion",
|
|
5822
5830
|
"changelog",
|
|
5823
|
-
"
|
|
5831
|
+
"roadmap_theme",
|
|
5824
5832
|
"user_story",
|
|
5825
5833
|
// ── Strategy: metric is a measurement definition; metric_quality_assessment
|
|
5826
5834
|
// is a point-in-time snapshot; value_stream is a mapped flow;
|
|
@@ -6121,6 +6129,21 @@ var UPG_SCALES = {
|
|
|
6121
6129
|
}
|
|
6122
6130
|
};
|
|
6123
6131
|
var UPG_MIGRATIONS = {
|
|
6132
|
+
"0.9.0": [
|
|
6133
|
+
// (since v0.9.0, UPG-660) theme → roadmap_theme. `theme` was the only bare,
|
|
6134
|
+
// unqualified theme among four domain-prefixed siblings (strategic_theme,
|
|
6135
|
+
// content_theme, feedback_theme); it claimed the unqualified word and read as
|
|
6136
|
+
// interchangeable with strategic_theme (the N6/UPG-652 confusion). Renamed to
|
|
6137
|
+
// roadmap_theme so all four read consistently and the customer-problem roadmap
|
|
6138
|
+
// grouping is explicit. Lifecycle-free, identical property surface (theme_scope
|
|
6139
|
+
// / priority); no property migration required. Paired edge renames live in
|
|
6140
|
+
// UPG_EDGE_MIGRATIONS['0.9.0'].
|
|
6141
|
+
{
|
|
6142
|
+
from: "theme",
|
|
6143
|
+
to: "roadmap_theme",
|
|
6144
|
+
reason: 'theme renamed to roadmap_theme (UPG-660). The bare "theme" was the only domain-unprefixed theme among strategic_theme / content_theme / feedback_theme; it grabbed the unqualified word and read as interchangeable with strategic_theme. roadmap_theme makes the customer-problem roadmap grouping explicit and consistent across the four. Same property surface (theme_scope / priority); no property migration needed.'
|
|
6145
|
+
}
|
|
6146
|
+
],
|
|
6124
6147
|
"0.7.0": [
|
|
6125
6148
|
// (since v0.7.0, UPG-571) story_statement → user_story. The v0.2.7 split
|
|
6126
6149
|
// correctly separated the templated promise from the engineering work
|
|
@@ -7103,6 +7126,17 @@ var UPG_SPLIT_MIGRATIONS = {
|
|
|
7103
7126
|
]
|
|
7104
7127
|
};
|
|
7105
7128
|
var UPG_EDGE_MIGRATIONS = {
|
|
7129
|
+
"0.9.0": [
|
|
7130
|
+
// (since v0.9.0, UPG-660) theme → roadmap_theme. The four canonical edges that
|
|
7131
|
+
// touch the roadmap theme are renamed to the roadmap_theme form. Endpoint guards
|
|
7132
|
+
// reference the POST-migration (roadmap_theme) types; edge migration runs after
|
|
7133
|
+
// node migration, so by the time these rules apply the node has already been
|
|
7134
|
+
// renamed theme → roadmap_theme (UPG_MIGRATIONS['0.9.0']).
|
|
7135
|
+
{ kind: "rename", from: "product_categorises_by_theme", to: "product_categorises_by_roadmap_theme", requires_source_type: "product", requires_target_type: "roadmap_theme", reason: "theme \u2192 roadmap_theme (UPG-660). Product categorises by the roadmap theme; edge key updated to the renamed target type." },
|
|
7136
|
+
{ kind: "rename", from: "roadmap_categorised_by_theme", to: "roadmap_categorised_by_roadmap_theme", requires_source_type: "roadmap", requires_target_type: "roadmap_theme", reason: "theme \u2192 roadmap_theme (UPG-660). Roadmap is categorised by the roadmap theme; edge key updated to the renamed target type." },
|
|
7137
|
+
{ kind: "rename", from: "theme_groups_feature", to: "roadmap_theme_groups_feature", requires_source_type: "roadmap_theme", requires_target_type: "feature", reason: "theme \u2192 roadmap_theme (UPG-660). The roadmap theme groups features; edge key updated to the renamed source type." },
|
|
7138
|
+
{ kind: "rename", from: "theme_spans_feature_area", to: "roadmap_theme_spans_feature_area", requires_source_type: "roadmap_theme", requires_target_type: "feature_area", reason: "theme \u2192 roadmap_theme (UPG-660). The roadmap theme spans feature areas; edge key updated to the renamed source type." }
|
|
7139
|
+
],
|
|
7106
7140
|
"0.7.0": [
|
|
7107
7141
|
// (since v0.7.0, UPG-571) story_statement → user_story re-canon. The four
|
|
7108
7142
|
// canonical edges that touch the statement are renamed to the user_story
|
|
@@ -9919,7 +9953,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9919
9953
|
reach: {
|
|
9920
9954
|
type: "assessment",
|
|
9921
9955
|
scale_id: "reach_5",
|
|
9922
|
-
description: "How many users experience this problem",
|
|
9956
|
+
description: "How many users experience this problem. @deprecated 0.9.0:a framework-scoped scoring input, not an intrinsic property. Apply the `opportunity-sizing` framework; the value lives on the framework_exercise includes-edge. Removed in 0.9.1.",
|
|
9923
9957
|
properties: {
|
|
9924
9958
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9925
9959
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -9931,7 +9965,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9931
9965
|
frequency: {
|
|
9932
9966
|
type: "assessment",
|
|
9933
9967
|
scale_id: "frequency_5",
|
|
9934
|
-
description: "How often users experience this problem",
|
|
9968
|
+
description: "How often users experience this problem. @deprecated 0.9.0:framework-scoped (opportunity-sizing). Removed in 0.9.1.",
|
|
9935
9969
|
properties: {
|
|
9936
9970
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9937
9971
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -9943,7 +9977,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9943
9977
|
pain: {
|
|
9944
9978
|
type: "assessment",
|
|
9945
9979
|
scale_id: "pain_5",
|
|
9946
|
-
description: "How painful it is when unaddressed",
|
|
9980
|
+
description: "How painful it is when unaddressed. @deprecated 0.9.0:framework-scoped (opportunity-sizing). Removed in 0.9.1.",
|
|
9947
9981
|
properties: {
|
|
9948
9982
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9949
9983
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -9952,7 +9986,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9952
9986
|
},
|
|
9953
9987
|
required: ["value", "label"]
|
|
9954
9988
|
},
|
|
9955
|
-
opportunity_score: { type: "number", description: "Computed: normalized_reach x normalized_frequency x normalized_pain (0-1)" }
|
|
9989
|
+
opportunity_score: { type: "number", description: "Computed: normalized_reach x normalized_frequency x normalized_pain (0-1). @deprecated 0.9.0:computed by the `opportunity-sizing` framework on its application edge, not stored on the entity. Removed in 0.9.1." }
|
|
9956
9990
|
},
|
|
9957
9991
|
// OrganizationProperties: Organization entity.
|
|
9958
9992
|
organization: {
|
|
@@ -10366,6 +10400,11 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10366
10400
|
start_date: { type: "string", description: "ISO date work begins. More precise than `quarter` for continuous planning." },
|
|
10367
10401
|
target_date: { type: "string", description: "ISO date completion is expected. For shipped items, the actual completion date." }
|
|
10368
10402
|
},
|
|
10403
|
+
// RoadmapThemeProperties: Thematic grouping of roadmap work, around the customer problem it solves.
|
|
10404
|
+
roadmap_theme: {
|
|
10405
|
+
theme_scope: { type: "string", description: "Scope description" },
|
|
10406
|
+
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Priority" }
|
|
10407
|
+
},
|
|
10369
10408
|
// RoleProperties: Role entity.
|
|
10370
10409
|
role: {
|
|
10371
10410
|
responsibilities: { type: "string[]", description: "Key responsibilities of the role" },
|
|
@@ -10551,7 +10590,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10551
10590
|
reach: {
|
|
10552
10591
|
type: "assessment",
|
|
10553
10592
|
scale_id: "reach_5",
|
|
10554
|
-
description: "How many users this solution reaches (1 = few, 5 = most)",
|
|
10593
|
+
description: "How many users this solution reaches (1 = few, 5 = most). @deprecated 0.9.0:a framework-scoped scoring input, not an intrinsic property. Apply the `rice-scoring` framework; the value lives on the framework_exercise includes-edge. Removed in 0.9.1.",
|
|
10555
10594
|
properties: {
|
|
10556
10595
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
10557
10596
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -10563,7 +10602,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10563
10602
|
impact: {
|
|
10564
10603
|
type: "assessment",
|
|
10565
10604
|
scale_id: "impact_5",
|
|
10566
|
-
description: "Expected impact on the target outcome (1 = minimal, 5 = transformative)",
|
|
10605
|
+
description: "Expected impact on the target outcome (1 = minimal, 5 = transformative). @deprecated 0.9.0:framework-scoped (rice-scoring). Removed in 0.9.1.",
|
|
10567
10606
|
properties: {
|
|
10568
10607
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
10569
10608
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -10575,7 +10614,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10575
10614
|
confidence: {
|
|
10576
10615
|
type: "assessment",
|
|
10577
10616
|
scale_id: "confidence_5",
|
|
10578
|
-
description: "How confident the team is in this solution (1 = speculative, 5 = proven)",
|
|
10617
|
+
description: "How confident the team is in this solution (1 = speculative, 5 = proven). @deprecated 0.9.0:framework-scoped (rice-scoring). Removed in 0.9.1.",
|
|
10579
10618
|
properties: {
|
|
10580
10619
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
10581
10620
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -10587,7 +10626,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10587
10626
|
effort: {
|
|
10588
10627
|
type: "assessment",
|
|
10589
10628
|
scale_id: "effort_5",
|
|
10590
|
-
description: "Level of effort required to implement (1 = trivial, 5 = very large)",
|
|
10629
|
+
description: "Level of effort required to implement (1 = trivial, 5 = very large). @deprecated 0.9.0:effort is a framework-scoped scoring input (it varies by assessor and method), not an intrinsic property. Removed in 0.9.1.",
|
|
10591
10630
|
properties: {
|
|
10592
10631
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
10593
10632
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -10596,7 +10635,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10596
10635
|
},
|
|
10597
10636
|
required: ["value", "label"]
|
|
10598
10637
|
},
|
|
10599
|
-
rice_score: { type: "number", description: "Computed: (reach \xD7 impact \xD7 confidence) / effort" }
|
|
10638
|
+
rice_score: { type: "number", description: "Computed: (reach \xD7 impact \xD7 confidence) / effort. @deprecated 0.9.0:computed by the `rice-scoring` framework on its application edge, not stored on the entity. Removed in 0.9.1." }
|
|
10600
10639
|
},
|
|
10601
10640
|
// StakeholderProperties: Stakeholder entity.
|
|
10602
10641
|
stakeholder: {
|
|
@@ -10843,11 +10882,6 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10843
10882
|
skipped_count: { type: "number", description: "Number of tests that were skipped in the last run" },
|
|
10844
10883
|
flaky_count: { type: "number", description: "Number of tests that passed only on retry (flaky) in the last run" }
|
|
10845
10884
|
},
|
|
10846
|
-
// ThemeProperties: Thematic grouping of work.
|
|
10847
|
-
theme: {
|
|
10848
|
-
theme_scope: { type: "string", description: "Scope description" },
|
|
10849
|
-
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Priority" }
|
|
10850
|
-
},
|
|
10851
10885
|
// ThreatProperties: Threat.
|
|
10852
10886
|
threat: {
|
|
10853
10887
|
category: { type: "string", description: 'Attack or threat scenario. @example "injection", "misconfiguration", "social engineering", "supply chain"' },
|
|
@@ -11076,6 +11110,188 @@ function getPropertySchema(entityType) {
|
|
|
11076
11110
|
return UPG_PROPERTY_SCHEMA[entityType];
|
|
11077
11111
|
}
|
|
11078
11112
|
var UPG_FRAMEWORKS = [
|
|
11113
|
+
{
|
|
11114
|
+
"id": "opportunity-sizing",
|
|
11115
|
+
"approach_ids": [
|
|
11116
|
+
"prioritise"
|
|
11117
|
+
],
|
|
11118
|
+
"name": "Opportunity Sizing",
|
|
11119
|
+
"version": "1.0.0",
|
|
11120
|
+
"description": "Size opportunities by Reach, Frequency, and Pain to rank which problems are most worth solving before committing to solutions.",
|
|
11121
|
+
"category": "prioritization",
|
|
11122
|
+
"origin": {
|
|
11123
|
+
"type": "practitioner",
|
|
11124
|
+
"attribution": "Continuous discovery practice",
|
|
11125
|
+
"description": "A lightweight discovery-prioritisation method: weigh how many users hit a problem, how often, and how much it hurts, to rank opportunities before investing in solutions.",
|
|
11126
|
+
"url": "https://www.producttalk.org/2016/08/opportunity-solution-tree/",
|
|
11127
|
+
"year": 2016,
|
|
11128
|
+
"license": "open_attribution"
|
|
11129
|
+
},
|
|
11130
|
+
"tags": [
|
|
11131
|
+
"prioritization",
|
|
11132
|
+
"discovery",
|
|
11133
|
+
"table"
|
|
11134
|
+
],
|
|
11135
|
+
"slots": [
|
|
11136
|
+
{
|
|
11137
|
+
"label": "Opportunities to size",
|
|
11138
|
+
"entityTypeId": "opportunity",
|
|
11139
|
+
"description": "Opportunities scored on Reach, Frequency, and Pain."
|
|
11140
|
+
}
|
|
11141
|
+
],
|
|
11142
|
+
"data": {
|
|
11143
|
+
"entity_types": [
|
|
11144
|
+
{
|
|
11145
|
+
"type": "opportunity",
|
|
11146
|
+
"role": "scored_item"
|
|
11147
|
+
}
|
|
11148
|
+
],
|
|
11149
|
+
"required_properties": {
|
|
11150
|
+
"opportunity": [
|
|
11151
|
+
{
|
|
11152
|
+
"property": "reach",
|
|
11153
|
+
"type": "assessment",
|
|
11154
|
+
"scale_id": "reach_5",
|
|
11155
|
+
"required": true,
|
|
11156
|
+
"scope": "framework",
|
|
11157
|
+
"label": "Reach",
|
|
11158
|
+
"description": "How many users experience this problem?"
|
|
11159
|
+
},
|
|
11160
|
+
{
|
|
11161
|
+
"property": "frequency",
|
|
11162
|
+
"type": "assessment",
|
|
11163
|
+
"scale_id": "frequency_5",
|
|
11164
|
+
"required": true,
|
|
11165
|
+
"scope": "framework",
|
|
11166
|
+
"label": "Frequency",
|
|
11167
|
+
"description": "How often do they run into it?"
|
|
11168
|
+
},
|
|
11169
|
+
{
|
|
11170
|
+
"property": "pain",
|
|
11171
|
+
"type": "assessment",
|
|
11172
|
+
"scale_id": "pain_5",
|
|
11173
|
+
"required": true,
|
|
11174
|
+
"scope": "framework",
|
|
11175
|
+
"label": "Pain",
|
|
11176
|
+
"description": "How painful is it when left unaddressed?"
|
|
11177
|
+
}
|
|
11178
|
+
]
|
|
11179
|
+
},
|
|
11180
|
+
"computed_properties": [
|
|
11181
|
+
{
|
|
11182
|
+
"property": "opportunity_score",
|
|
11183
|
+
"expression": "reach * frequency * pain",
|
|
11184
|
+
"entity_type": "opportunity",
|
|
11185
|
+
"label": "Opportunity Score",
|
|
11186
|
+
"format": "number"
|
|
11187
|
+
}
|
|
11188
|
+
],
|
|
11189
|
+
"scoring_method": {
|
|
11190
|
+
"applies_to": [
|
|
11191
|
+
"opportunity"
|
|
11192
|
+
],
|
|
11193
|
+
"inputs": [
|
|
11194
|
+
{
|
|
11195
|
+
"property": "reach",
|
|
11196
|
+
"type": "assessment",
|
|
11197
|
+
"scale_id": "reach_5",
|
|
11198
|
+
"required": true,
|
|
11199
|
+
"scope": "framework",
|
|
11200
|
+
"label": "Reach",
|
|
11201
|
+
"description": "How many users experience this problem?"
|
|
11202
|
+
},
|
|
11203
|
+
{
|
|
11204
|
+
"property": "frequency",
|
|
11205
|
+
"type": "assessment",
|
|
11206
|
+
"scale_id": "frequency_5",
|
|
11207
|
+
"required": true,
|
|
11208
|
+
"scope": "framework",
|
|
11209
|
+
"label": "Frequency",
|
|
11210
|
+
"description": "How often do they run into it?"
|
|
11211
|
+
},
|
|
11212
|
+
{
|
|
11213
|
+
"property": "pain",
|
|
11214
|
+
"type": "assessment",
|
|
11215
|
+
"scale_id": "pain_5",
|
|
11216
|
+
"required": true,
|
|
11217
|
+
"scope": "framework",
|
|
11218
|
+
"label": "Pain",
|
|
11219
|
+
"description": "How painful is it when left unaddressed?"
|
|
11220
|
+
}
|
|
11221
|
+
],
|
|
11222
|
+
"computed": [
|
|
11223
|
+
{
|
|
11224
|
+
"property": "opportunity_score",
|
|
11225
|
+
"expression": "reach * frequency * pain",
|
|
11226
|
+
"label": "Opportunity Score",
|
|
11227
|
+
"format": "number"
|
|
11228
|
+
}
|
|
11229
|
+
]
|
|
11230
|
+
}
|
|
11231
|
+
},
|
|
11232
|
+
"structure": {
|
|
11233
|
+
"pattern": "table"
|
|
11234
|
+
},
|
|
11235
|
+
"presentation": {
|
|
11236
|
+
"layout": {
|
|
11237
|
+
"type": "table",
|
|
11238
|
+
"columns": [
|
|
11239
|
+
{
|
|
11240
|
+
"property": "title",
|
|
11241
|
+
"label": "Opportunities to size",
|
|
11242
|
+
"sortable": true
|
|
11243
|
+
},
|
|
11244
|
+
{
|
|
11245
|
+
"property": "reach",
|
|
11246
|
+
"label": "Reach",
|
|
11247
|
+
"sortable": true,
|
|
11248
|
+
"format": "number"
|
|
11249
|
+
},
|
|
11250
|
+
{
|
|
11251
|
+
"property": "frequency",
|
|
11252
|
+
"label": "Frequency",
|
|
11253
|
+
"sortable": true,
|
|
11254
|
+
"format": "number"
|
|
11255
|
+
},
|
|
11256
|
+
{
|
|
11257
|
+
"property": "pain",
|
|
11258
|
+
"label": "Pain",
|
|
11259
|
+
"sortable": true,
|
|
11260
|
+
"format": "number"
|
|
11261
|
+
},
|
|
11262
|
+
{
|
|
11263
|
+
"property": "opportunity_score",
|
|
11264
|
+
"label": "Opportunity Score",
|
|
11265
|
+
"sortable": true,
|
|
11266
|
+
"format": "score_pill"
|
|
11267
|
+
}
|
|
11268
|
+
]
|
|
11269
|
+
},
|
|
11270
|
+
"sort_by": {
|
|
11271
|
+
"property": "opportunity_score",
|
|
11272
|
+
"direction": "desc"
|
|
11273
|
+
},
|
|
11274
|
+
"colour_by": "score",
|
|
11275
|
+
"card_fields": [
|
|
11276
|
+
"title",
|
|
11277
|
+
"description",
|
|
11278
|
+
"status"
|
|
11279
|
+
]
|
|
11280
|
+
},
|
|
11281
|
+
"education": {
|
|
11282
|
+
"purpose": "Rank opportunities by how widely, how often, and how painfully a problem is felt, so discovery effort flows to the problems most worth solving.",
|
|
11283
|
+
"core_question": "Of the problems we could pursue, which affect the most users, most often, with the most pain?",
|
|
11284
|
+
"when_to_use": [
|
|
11285
|
+
"You have more opportunities than you can pursue",
|
|
11286
|
+
"You need to compare problems before committing to solutions",
|
|
11287
|
+
"You want a defensible, transparent way to choose what to explore"
|
|
11288
|
+
],
|
|
11289
|
+
"when_not_to_use": [
|
|
11290
|
+
"A single opportunity is already validated and obvious",
|
|
11291
|
+
"You have no signal yet on reach, frequency, or pain"
|
|
11292
|
+
]
|
|
11293
|
+
}
|
|
11294
|
+
},
|
|
11079
11295
|
{
|
|
11080
11296
|
"id": "opportunity-solution-tree",
|
|
11081
11297
|
"approach_ids": [
|
|
@@ -12332,7 +12548,7 @@ var UPG_FRAMEWORKS = [
|
|
|
12332
12548
|
],
|
|
12333
12549
|
"name": "RICE Scoring",
|
|
12334
12550
|
"version": "1.0.0",
|
|
12335
|
-
"description": "Score features, opportunities, and needs by Reach, Impact, Confidence, and Effort to produce a ranked priority list.",
|
|
12551
|
+
"description": "Score features, solutions, opportunities, and needs by Reach, Impact, Confidence, and Effort to produce a ranked priority list.",
|
|
12336
12552
|
"category": "prioritization",
|
|
12337
12553
|
"origin": {
|
|
12338
12554
|
"type": "practitioner",
|
|
@@ -12357,6 +12573,11 @@ var UPG_FRAMEWORKS = [
|
|
|
12357
12573
|
"entityTypeId": "opportunity",
|
|
12358
12574
|
"description": "Opportunities scored on the same RICE Scoring inputs as features."
|
|
12359
12575
|
},
|
|
12576
|
+
{
|
|
12577
|
+
"label": "Solutions to score",
|
|
12578
|
+
"entityTypeId": "solution",
|
|
12579
|
+
"description": "Solutions scored on the same RICE Scoring inputs as features."
|
|
12580
|
+
},
|
|
12360
12581
|
{
|
|
12361
12582
|
"label": "Needs to score",
|
|
12362
12583
|
"entityTypeId": "need",
|
|
@@ -12373,6 +12594,10 @@ var UPG_FRAMEWORKS = [
|
|
|
12373
12594
|
"type": "opportunity",
|
|
12374
12595
|
"role": "scored_item"
|
|
12375
12596
|
},
|
|
12597
|
+
{
|
|
12598
|
+
"type": "solution",
|
|
12599
|
+
"role": "scored_item"
|
|
12600
|
+
},
|
|
12376
12601
|
{
|
|
12377
12602
|
"type": "need",
|
|
12378
12603
|
"role": "scored_item"
|
|
@@ -12455,6 +12680,44 @@ var UPG_FRAMEWORKS = [
|
|
|
12455
12680
|
"description": "How much work is required to build and ship this, on the effort scale?"
|
|
12456
12681
|
}
|
|
12457
12682
|
],
|
|
12683
|
+
"solution": [
|
|
12684
|
+
{
|
|
12685
|
+
"property": "reach",
|
|
12686
|
+
"type": "assessment",
|
|
12687
|
+
"scale_id": "reach_5",
|
|
12688
|
+
"required": true,
|
|
12689
|
+
"scope": "framework",
|
|
12690
|
+
"label": "Reach",
|
|
12691
|
+
"description": "How many users will this impact per quarter?"
|
|
12692
|
+
},
|
|
12693
|
+
{
|
|
12694
|
+
"property": "impact",
|
|
12695
|
+
"type": "assessment",
|
|
12696
|
+
"scale_id": "impact_5",
|
|
12697
|
+
"required": true,
|
|
12698
|
+
"scope": "framework",
|
|
12699
|
+
"label": "Impact",
|
|
12700
|
+
"description": "How much will this impact each user, on the impact scale?"
|
|
12701
|
+
},
|
|
12702
|
+
{
|
|
12703
|
+
"property": "confidence",
|
|
12704
|
+
"type": "assessment",
|
|
12705
|
+
"scale_id": "confidence_5",
|
|
12706
|
+
"required": true,
|
|
12707
|
+
"scope": "framework",
|
|
12708
|
+
"label": "Confidence",
|
|
12709
|
+
"description": "How confident are you in the reach, impact, and effort estimates?"
|
|
12710
|
+
},
|
|
12711
|
+
{
|
|
12712
|
+
"property": "effort",
|
|
12713
|
+
"type": "assessment",
|
|
12714
|
+
"scale_id": "effort_5",
|
|
12715
|
+
"required": true,
|
|
12716
|
+
"scope": "framework",
|
|
12717
|
+
"label": "Effort",
|
|
12718
|
+
"description": "How much work is required to build and ship this, on the effort scale?"
|
|
12719
|
+
}
|
|
12720
|
+
],
|
|
12458
12721
|
"need": [
|
|
12459
12722
|
{
|
|
12460
12723
|
"property": "reach",
|
|
@@ -12509,6 +12772,13 @@ var UPG_FRAMEWORKS = [
|
|
|
12509
12772
|
"label": "RICE Score",
|
|
12510
12773
|
"format": "number"
|
|
12511
12774
|
},
|
|
12775
|
+
{
|
|
12776
|
+
"property": "rice_score",
|
|
12777
|
+
"expression": "(reach * impact * confidence) / effort",
|
|
12778
|
+
"entity_type": "solution",
|
|
12779
|
+
"label": "RICE Score",
|
|
12780
|
+
"format": "number"
|
|
12781
|
+
},
|
|
12512
12782
|
{
|
|
12513
12783
|
"property": "rice_score",
|
|
12514
12784
|
"expression": "(reach * impact * confidence) / effort",
|
|
@@ -12521,6 +12791,7 @@ var UPG_FRAMEWORKS = [
|
|
|
12521
12791
|
"applies_to": [
|
|
12522
12792
|
"feature",
|
|
12523
12793
|
"opportunity",
|
|
12794
|
+
"solution",
|
|
12524
12795
|
"need"
|
|
12525
12796
|
],
|
|
12526
12797
|
"inputs": [
|
|
@@ -16767,7 +17038,8 @@ var STANDARD_LABELS = {
|
|
|
16767
17038
|
product: { alt_labels: ["offering", "app", "service", "platform"] },
|
|
16768
17039
|
vision: { alt_labels: ["product vision", "north star vision", "long-term vision"] },
|
|
16769
17040
|
mission: { alt_labels: ["mission statement", "purpose"] },
|
|
16770
|
-
strategic_theme: { alt_labels: ["
|
|
17041
|
+
strategic_theme: { alt_labels: ["focus area", "strategic focus area"] },
|
|
17042
|
+
// N6: not 'theme'/'strategic pillar' (own types)
|
|
16771
17043
|
initiative: { alt_labels: ["strategic initiative", "program initiative", "workstream"] },
|
|
16772
17044
|
capability: { alt_labels: ["business capability", "organizational capability"] },
|
|
16773
17045
|
value_stream: { alt_labels: ["value chain", "stream"] },
|
|
@@ -16829,7 +17101,8 @@ var STANDARD_LABELS = {
|
|
|
16829
17101
|
fix: { alt_labels: ["bugfix", "patch", "remediation"] },
|
|
16830
17102
|
roadmap: { alt_labels: ["product roadmap", "release plan", "timeline"] },
|
|
16831
17103
|
roadmap_item: { alt_labels: ["roadmap entry", "planned item"] },
|
|
16832
|
-
|
|
17104
|
+
roadmap_theme: { alt_labels: ["product theme", "roadmap theme"] },
|
|
17105
|
+
// UPG-660: renamed from bare 'theme' (N6 lineage)
|
|
16833
17106
|
// Engineering layer
|
|
16834
17107
|
bounded_context: { alt_labels: ["context", "domain boundary", "module boundary"] },
|
|
16835
17108
|
service: { alt_labels: ["microservice", "backend service", "api service"] },
|
|
@@ -17028,7 +17301,8 @@ var STANDARD_LABELS = {
|
|
|
17028
17301
|
feedback_vote: { alt_labels: ["upvote", "vote", "user vote"] },
|
|
17029
17302
|
user_advisory_board: { alt_labels: ["cab", "customer advisory board", "advisory council"] },
|
|
17030
17303
|
beta_program: { alt_labels: ["beta", "early access", "preview program"] },
|
|
17031
|
-
feedback_theme: { alt_labels: ["feedback cluster", "
|
|
17304
|
+
feedback_theme: { alt_labels: ["feedback cluster", "feedback category"] },
|
|
17305
|
+
// N6: not bare 'theme'
|
|
17032
17306
|
// Pricing & Packaging layer
|
|
17033
17307
|
pricing_strategy: { alt_labels: ["pricing model", "monetization strategy"] },
|
|
17034
17308
|
package: { alt_labels: ["product package", "bundle", "sku"] },
|
|
@@ -17532,9 +17806,9 @@ var PRODUCT_DELIVERY_PLAYBOOK = {
|
|
|
17532
17806
|
),
|
|
17533
17807
|
seqStep(
|
|
17534
17808
|
6,
|
|
17535
|
-
"Themes & Changelog",
|
|
17536
|
-
["
|
|
17537
|
-
"Group
|
|
17809
|
+
"Roadmap Themes & Changelog",
|
|
17810
|
+
["roadmap_theme", "changelog"],
|
|
17811
|
+
"Group roadmap work into roadmap themes around the customer problem. Maintain a changelog the team and customers can read together."
|
|
17538
17812
|
)
|
|
17539
17813
|
]
|
|
17540
17814
|
};
|
|
@@ -18788,7 +19062,7 @@ var PRODUCT_SPEC_GUIDE = {
|
|
|
18788
19062
|
// registered to product_spec but missing from the navigation order).
|
|
18789
19063
|
// `changelog` lives here because it is a structural product-shipping
|
|
18790
19064
|
// artefact; content domain references it only via cross-domain bridges.
|
|
18791
|
-
creation_sequence: ["feature_area", "feature", "epic", "user_story", "acceptance_criterion", "task", "bug", "release", "roadmap", "roadmap_item", "
|
|
19065
|
+
creation_sequence: ["feature_area", "feature", "epic", "user_story", "acceptance_criterion", "task", "bug", "release", "roadmap", "roadmap_item", "roadmap_theme", "changelog"],
|
|
18792
19066
|
patterns: [
|
|
18793
19067
|
{
|
|
18794
19068
|
name: "Feature Decomposition",
|
|
@@ -23213,7 +23487,7 @@ var UPG_REGIONS = [
|
|
|
23213
23487
|
role: "leaf"
|
|
23214
23488
|
},
|
|
23215
23489
|
{
|
|
23216
|
-
type: "
|
|
23490
|
+
type: "roadmap_theme",
|
|
23217
23491
|
role: "container",
|
|
23218
23492
|
notes: "semantic spanner, not containment"
|
|
23219
23493
|
},
|
|
@@ -23249,10 +23523,10 @@ var UPG_REGIONS = [
|
|
|
23249
23523
|
"user_story_verified_by_acceptance_criterion",
|
|
23250
23524
|
"task_implements_user_story",
|
|
23251
23525
|
"roadmap_contains_roadmap_item",
|
|
23252
|
-
"
|
|
23526
|
+
"roadmap_categorised_by_roadmap_theme",
|
|
23253
23527
|
"roadmap_schedules_release",
|
|
23254
23528
|
"release_documented_in_changelog",
|
|
23255
|
-
"
|
|
23529
|
+
"roadmap_theme_spans_feature_area",
|
|
23256
23530
|
"milestone_gates_release",
|
|
23257
23531
|
"roadmap_item_references_feature",
|
|
23258
23532
|
"feature_request_voted_on_by_feedback_vote"
|
|
@@ -24372,7 +24646,7 @@ function serializePortfolioWithHeader(doc, opts) {
|
|
|
24372
24646
|
header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
|
|
24373
24647
|
return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
|
|
24374
24648
|
}
|
|
24375
|
-
var UPG_VERSION = "0.
|
|
24649
|
+
var UPG_VERSION = "0.9.0";
|
|
24376
24650
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
24377
24651
|
var UPG_TYPES = getTypes();
|
|
24378
24652
|
var UPG_TYPES_SET = new Set(UPG_TYPES);
|
|
@@ -26257,6 +26531,10 @@ import {
|
|
|
26257
26531
|
writePortfolioScopedNode as writePortfolioScopedNode2,
|
|
26258
26532
|
openPortfolioStoreIfExists,
|
|
26259
26533
|
assignProductToArea,
|
|
26534
|
+
updateProductArea,
|
|
26535
|
+
removeProductFromArea,
|
|
26536
|
+
deleteArea,
|
|
26537
|
+
moveProductToArea,
|
|
26260
26538
|
PortfolioRoutingError as PortfolioRoutingError2
|
|
26261
26539
|
} from "@unified-product-graph/sdk";
|
|
26262
26540
|
var listProductAreas = async (_args, _ctx) => {
|
|
@@ -26479,6 +26757,73 @@ var getChanges = (args, ctx) => {
|
|
|
26479
26757
|
)
|
|
26480
26758
|
);
|
|
26481
26759
|
};
|
|
26760
|
+
var updateAreaTool = async (args, _ctx) => {
|
|
26761
|
+
const areaId = args.area_id;
|
|
26762
|
+
if (!areaId) return textError("Missing required parameter: area_id");
|
|
26763
|
+
const hasField = args.title !== void 0 || args.description !== void 0 || args.strategic_priority !== void 0 || args.owner !== void 0 || "parent_area_id" in args;
|
|
26764
|
+
if (!hasField) {
|
|
26765
|
+
return textError(
|
|
26766
|
+
"Nothing to update: pass at least one of: title, description, strategic_priority, owner, parent_area_id."
|
|
26767
|
+
);
|
|
26768
|
+
}
|
|
26769
|
+
try {
|
|
26770
|
+
const result = await updateProductArea(process.cwd(), areaId, {
|
|
26771
|
+
title: args.title,
|
|
26772
|
+
description: args.description,
|
|
26773
|
+
strategic_priority: args.strategic_priority,
|
|
26774
|
+
owner: args.owner,
|
|
26775
|
+
// Tri-state: present (incl. null) re-parents/un-nests; absent leaves unchanged.
|
|
26776
|
+
..."parent_area_id" in args ? { parent_area_id: args.parent_area_id ?? null } : {}
|
|
26777
|
+
});
|
|
26778
|
+
return text(
|
|
26779
|
+
JSON.stringify({ message: `Updated area (${result.updated.join(", ")})`, ...result }, null, 2)
|
|
26780
|
+
);
|
|
26781
|
+
} catch (err) {
|
|
26782
|
+
if (err instanceof PortfolioRoutingError2) return textError(err.message);
|
|
26783
|
+
return textError(err.message);
|
|
26784
|
+
}
|
|
26785
|
+
};
|
|
26786
|
+
var removeProductFromAreaTool = async (args, _ctx) => {
|
|
26787
|
+
const productId = args.product_id;
|
|
26788
|
+
const areaId = args.area_id;
|
|
26789
|
+
if (!productId) return textError("Missing required parameter: product_id");
|
|
26790
|
+
if (!areaId) return textError("Missing required parameter: area_id");
|
|
26791
|
+
try {
|
|
26792
|
+
const result = await removeProductFromArea(process.cwd(), { product_id: productId, area_id: areaId });
|
|
26793
|
+
return text(JSON.stringify(result, null, 2));
|
|
26794
|
+
} catch (err) {
|
|
26795
|
+
if (err instanceof PortfolioRoutingError2) return textError(err.message);
|
|
26796
|
+
return textError(err.message);
|
|
26797
|
+
}
|
|
26798
|
+
};
|
|
26799
|
+
var deleteAreaTool = async (args, _ctx) => {
|
|
26800
|
+
const areaId = args.area_id;
|
|
26801
|
+
if (!areaId) return textError("Missing required parameter: area_id");
|
|
26802
|
+
try {
|
|
26803
|
+
const result = await deleteArea(process.cwd(), areaId, { force: args.force });
|
|
26804
|
+
return text(JSON.stringify({ message: `Deleted area ${areaId}`, ...result }, null, 2));
|
|
26805
|
+
} catch (err) {
|
|
26806
|
+
if (err instanceof PortfolioRoutingError2) return textError(err.message);
|
|
26807
|
+
return textError(err.message);
|
|
26808
|
+
}
|
|
26809
|
+
};
|
|
26810
|
+
var moveProductToAreaTool = async (args, _ctx) => {
|
|
26811
|
+
const productId = args.product_id;
|
|
26812
|
+
const toAreaId = args.to_area_id;
|
|
26813
|
+
if (!productId) return textError("Missing required parameter: product_id");
|
|
26814
|
+
if (!toAreaId) return textError("Missing required parameter: to_area_id");
|
|
26815
|
+
try {
|
|
26816
|
+
const result = await moveProductToArea(process.cwd(), {
|
|
26817
|
+
product_id: productId,
|
|
26818
|
+
to_area_id: toAreaId,
|
|
26819
|
+
from_area_id: args.from_area_id
|
|
26820
|
+
});
|
|
26821
|
+
return text(JSON.stringify(result, null, 2));
|
|
26822
|
+
} catch (err) {
|
|
26823
|
+
if (err instanceof PortfolioRoutingError2) return textError(err.message);
|
|
26824
|
+
return textError(err.message);
|
|
26825
|
+
}
|
|
26826
|
+
};
|
|
26482
26827
|
|
|
26483
26828
|
// src/tools/workspace.ts
|
|
26484
26829
|
import * as fs from "fs";
|
|
@@ -26491,7 +26836,9 @@ import {
|
|
|
26491
26836
|
openPortfolioStoreIfExists as openPortfolioStoreIfExists2,
|
|
26492
26837
|
registerProductOnPortfolio,
|
|
26493
26838
|
findProductFileById,
|
|
26494
|
-
attachProductToPortfolio
|
|
26839
|
+
attachProductToPortfolio,
|
|
26840
|
+
detachProductFromPortfolio,
|
|
26841
|
+
deleteCrossProductEdge
|
|
26495
26842
|
} from "@unified-product-graph/sdk";
|
|
26496
26843
|
import {
|
|
26497
26844
|
createProduct,
|
|
@@ -26505,6 +26852,25 @@ import {
|
|
|
26505
26852
|
var listLocalProducts = (_args, _ctx) => {
|
|
26506
26853
|
const cwd = process.cwd();
|
|
26507
26854
|
const products = [];
|
|
26855
|
+
const membership = /* @__PURE__ */ new Map();
|
|
26856
|
+
try {
|
|
26857
|
+
const pdoc = JSON.parse(fs.readFileSync(path3.join(cwd, ".upg", "portfolio.upg"), "utf-8"));
|
|
26858
|
+
for (const area of pdoc.product_areas ?? []) {
|
|
26859
|
+
for (const pid of area.products ?? []) {
|
|
26860
|
+
const m = membership.get(pid) ?? { areas: [], portfolios: [] };
|
|
26861
|
+
m.areas.push(area.title ?? area.id);
|
|
26862
|
+
membership.set(pid, m);
|
|
26863
|
+
}
|
|
26864
|
+
}
|
|
26865
|
+
for (const pf of pdoc.portfolios ?? []) {
|
|
26866
|
+
for (const pid of pf.products ?? []) {
|
|
26867
|
+
const m = membership.get(pid) ?? { areas: [], portfolios: [] };
|
|
26868
|
+
m.portfolios.push(pf.title ?? pf.id);
|
|
26869
|
+
membership.set(pid, m);
|
|
26870
|
+
}
|
|
26871
|
+
}
|
|
26872
|
+
} catch {
|
|
26873
|
+
}
|
|
26508
26874
|
const candidates = [];
|
|
26509
26875
|
const topEntries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
26510
26876
|
for (const entry of topEntries) {
|
|
@@ -26529,13 +26895,19 @@ var listLocalProducts = (_args, _ctx) => {
|
|
|
26529
26895
|
try {
|
|
26530
26896
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
26531
26897
|
const doc = JSON.parse(raw);
|
|
26898
|
+
if (!doc.product) continue;
|
|
26532
26899
|
const coerced = coerceProductStage(doc.product?.stage);
|
|
26900
|
+
const pid = doc.product?.id ?? null;
|
|
26901
|
+
const m = pid ? membership.get(pid) : void 0;
|
|
26533
26902
|
products.push({
|
|
26903
|
+
id: pid,
|
|
26534
26904
|
file: path3.relative(cwd, filePath),
|
|
26535
26905
|
title: doc.product?.title ?? "(untitled)",
|
|
26536
26906
|
stage: coerced.canonical ?? null,
|
|
26537
26907
|
nodes: Array.isArray(doc.nodes) ? doc.nodes.length : 0,
|
|
26538
|
-
edges: Array.isArray(doc.edges) ? doc.edges.length : 0
|
|
26908
|
+
edges: Array.isArray(doc.edges) ? doc.edges.length : 0,
|
|
26909
|
+
...m && m.areas.length > 0 ? { areas: m.areas } : {},
|
|
26910
|
+
...m && m.portfolios.length > 0 ? { portfolios: m.portfolios } : {}
|
|
26539
26911
|
});
|
|
26540
26912
|
} catch {
|
|
26541
26913
|
}
|
|
@@ -26926,6 +27298,126 @@ var attachProductToPortfolioTool = async (args, _ctx) => {
|
|
|
26926
27298
|
return textError(err.message);
|
|
26927
27299
|
}
|
|
26928
27300
|
};
|
|
27301
|
+
var detachProductFromPortfolioTool = async (args, _ctx) => {
|
|
27302
|
+
const productId = args.product_id;
|
|
27303
|
+
const portfolioId = args.portfolio_id;
|
|
27304
|
+
if (!productId) return textError("Missing required parameter: product_id");
|
|
27305
|
+
if (!portfolioId) return textError("Missing required parameter: portfolio_id");
|
|
27306
|
+
try {
|
|
27307
|
+
const result = await detachProductFromPortfolio(process.cwd(), {
|
|
27308
|
+
product_id: productId,
|
|
27309
|
+
portfolio_id: portfolioId
|
|
27310
|
+
});
|
|
27311
|
+
return text(JSON.stringify(result, null, 2));
|
|
27312
|
+
} catch (err) {
|
|
27313
|
+
return textError(err.message);
|
|
27314
|
+
}
|
|
27315
|
+
};
|
|
27316
|
+
var deleteCrossProductEdgeTool = async (args, _ctx) => {
|
|
27317
|
+
const edgeIdArg = args.edge_id;
|
|
27318
|
+
if (!edgeIdArg) return textError("Missing required parameter: edge_id");
|
|
27319
|
+
try {
|
|
27320
|
+
const result = await deleteCrossProductEdge(process.cwd(), edgeIdArg);
|
|
27321
|
+
return text(JSON.stringify(result, null, 2));
|
|
27322
|
+
} catch (err) {
|
|
27323
|
+
return textError(err.message);
|
|
27324
|
+
}
|
|
27325
|
+
};
|
|
27326
|
+
var batchCreateCrossProductEdges = async (args, _ctx) => {
|
|
27327
|
+
const edgesArg = args.edges;
|
|
27328
|
+
if (!Array.isArray(edgesArg) || edgesArg.length === 0) {
|
|
27329
|
+
return textError("Missing required parameter: edges (a non-empty array).");
|
|
27330
|
+
}
|
|
27331
|
+
if (edgesArg.length > 50) {
|
|
27332
|
+
return textError(`Too many edges: ${edgesArg.length}. Max 50 per batch_create_cross_product_edges call.`);
|
|
27333
|
+
}
|
|
27334
|
+
const cwd = process.cwd();
|
|
27335
|
+
const portfolioPath = resolvePortfolioPath(cwd);
|
|
27336
|
+
if (!portfolioPath) {
|
|
27337
|
+
return textError("No workspace found. Run `init_workspace` first to enable portfolio cross-product edges.");
|
|
27338
|
+
}
|
|
27339
|
+
const autoCreatePortfolio = args.auto_create_portfolio ?? false;
|
|
27340
|
+
const portfolioExisted = fs.existsSync(portfolioPath);
|
|
27341
|
+
if (!portfolioExisted && !autoCreatePortfolio) {
|
|
27342
|
+
return textError(
|
|
27343
|
+
'No portfolio document found at .upg/portfolio.upg. Cross-product edges express portfolio-level relationships and should be anchored to a portfolio that contains the products. Create a portfolio first (`create_node({type: "portfolio", title: "..."})`), or pass `auto_create_portfolio: true`.'
|
|
27344
|
+
);
|
|
27345
|
+
}
|
|
27346
|
+
const prepared = [];
|
|
27347
|
+
for (let i = 0; i < edgesArg.length; i++) {
|
|
27348
|
+
const e = edgesArg[i];
|
|
27349
|
+
const sourceIdArg = e.source_id;
|
|
27350
|
+
const targetIdArg = e.target_id;
|
|
27351
|
+
const edgeTypeArg = e.type;
|
|
27352
|
+
const sourceProductId = e.source_product_id;
|
|
27353
|
+
const targetProductId = e.target_product_id;
|
|
27354
|
+
if (!sourceIdArg) return textError(`edges[${i}]: missing source_id`);
|
|
27355
|
+
if (!targetIdArg) return textError(`edges[${i}]: missing target_id`);
|
|
27356
|
+
if (!edgeTypeArg) return textError(`edges[${i}]: missing type`);
|
|
27357
|
+
if (!UPG_CROSS_EDGE_TYPES.includes(edgeTypeArg)) {
|
|
27358
|
+
return textError(`edges[${i}]: invalid cross-product edge type "${edgeTypeArg}". Valid types: ${UPG_CROSS_EDGE_TYPES.join(", ")}`);
|
|
27359
|
+
}
|
|
27360
|
+
let qualifiedSource;
|
|
27361
|
+
if (sourceIdArg.includes("/")) qualifiedSource = sourceIdArg;
|
|
27362
|
+
else if (sourceProductId) qualifiedSource = `${sourceProductId}/${sourceIdArg}`;
|
|
27363
|
+
else return textError(`edges[${i}]: source_id "${sourceIdArg}" is a bare node id. Supply source_product_id or a qualified {product_id}/{node_id}.`);
|
|
27364
|
+
let qualifiedTarget;
|
|
27365
|
+
if (targetIdArg.includes("/")) qualifiedTarget = targetIdArg;
|
|
27366
|
+
else if (targetProductId) qualifiedTarget = `${targetProductId}/${targetIdArg}`;
|
|
27367
|
+
else return textError(`edges[${i}]: target_id "${targetIdArg}" is a bare node id. Supply target_product_id or a qualified {product_id}/{node_id}.`);
|
|
27368
|
+
prepared.push({
|
|
27369
|
+
id: edgeId3(),
|
|
27370
|
+
source: qualifiedSource,
|
|
27371
|
+
target: qualifiedTarget,
|
|
27372
|
+
type: edgeTypeArg,
|
|
27373
|
+
source_product_id: sourceProductId ?? qualifiedSource.split("/")[0],
|
|
27374
|
+
target_product_id: targetProductId ?? qualifiedTarget.split("/")[0]
|
|
27375
|
+
});
|
|
27376
|
+
}
|
|
27377
|
+
const portfolioStore = new UPGPortfolioStore();
|
|
27378
|
+
try {
|
|
27379
|
+
await portfolioStore.loadOrInit(portfolioPath);
|
|
27380
|
+
} catch (err) {
|
|
27381
|
+
return textError(`Failed to load portfolio document: ${err.message}`);
|
|
27382
|
+
}
|
|
27383
|
+
const registeredProducts = [];
|
|
27384
|
+
const portfolioDoc = portfolioStore.getDocument();
|
|
27385
|
+
if (portfolioDoc) {
|
|
27386
|
+
const productIds = /* @__PURE__ */ new Set();
|
|
27387
|
+
for (const e of prepared) {
|
|
27388
|
+
if (e.source_product_id) productIds.add(e.source_product_id);
|
|
27389
|
+
if (e.target_product_id) productIds.add(e.target_product_id);
|
|
27390
|
+
}
|
|
27391
|
+
for (const pid of productIds) {
|
|
27392
|
+
const lookup = findProductFileById(cwd, pid);
|
|
27393
|
+
const wasNew = registerProductOnPortfolio(portfolioDoc, {
|
|
27394
|
+
id: pid,
|
|
27395
|
+
...lookup ? { file_path: lookup.file_path, title: lookup.title } : {}
|
|
27396
|
+
});
|
|
27397
|
+
if (wasNew) registeredProducts.push({ id: pid, ...lookup ? { file_path: lookup.file_path, title: lookup.title } : {} });
|
|
27398
|
+
}
|
|
27399
|
+
if (registeredProducts.length > 0) portfolioStore.markDirty();
|
|
27400
|
+
}
|
|
27401
|
+
try {
|
|
27402
|
+
for (const e of prepared) portfolioStore.addCrossEdge(e);
|
|
27403
|
+
await portfolioStore.flush();
|
|
27404
|
+
} catch (err) {
|
|
27405
|
+
return textError(`Failed to write cross-product edges: ${err.message}`);
|
|
27406
|
+
}
|
|
27407
|
+
return text(
|
|
27408
|
+
JSON.stringify(
|
|
27409
|
+
{
|
|
27410
|
+
message: `Created ${prepared.length} cross-product edge(s)`,
|
|
27411
|
+
created: prepared,
|
|
27412
|
+
count: prepared.length,
|
|
27413
|
+
portfolio_file: path3.relative(cwd, portfolioPath),
|
|
27414
|
+
...registeredProducts.length > 0 ? { registered_products: registeredProducts } : {}
|
|
27415
|
+
},
|
|
27416
|
+
null,
|
|
27417
|
+
2
|
|
27418
|
+
)
|
|
27419
|
+
);
|
|
27420
|
+
};
|
|
26929
27421
|
|
|
26930
27422
|
// src/tools/schema.ts
|
|
26931
27423
|
var getEntitySchema = (args, _ctx) => {
|
|
@@ -29552,7 +30044,7 @@ var TOOL_DEFINITIONS = [
|
|
|
29552
30044
|
},
|
|
29553
30045
|
{
|
|
29554
30046
|
name: "list_cross_edge_types",
|
|
29555
|
-
description: "List the canonical cross-product edge types from `UPG_CROSS_EDGE_TYPES`: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`. Portfolio-level relationships across products. Distinct from the within-product `UPG_EDGE_CATALOG`.",
|
|
30047
|
+
description: "List the canonical cross-product edge types from `UPG_CROSS_EDGE_TYPES`: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`, `hosts`. Portfolio-level relationships across products. Distinct from the within-product `UPG_EDGE_CATALOG`.",
|
|
29556
30048
|
inputSchema: { type: "object", properties: {} }
|
|
29557
30049
|
},
|
|
29558
30050
|
{
|
|
@@ -29937,6 +30429,66 @@ var TOOL_DEFINITIONS = [
|
|
|
29937
30429
|
required: ["product_id", "area_id"]
|
|
29938
30430
|
}
|
|
29939
30431
|
},
|
|
30432
|
+
{
|
|
30433
|
+
name: "update_area",
|
|
30434
|
+
description: "Edit a product area in `.upg/portfolio.upg` (title, description, strategic_priority, owner) and/or re-parent it via `parent_area_id`. The mirror of `update_product` for the organisational axis. `parent_area_id` is tri-state: omit to leave unchanged, pass null to un-nest (top-level), or pass an area id to re-parent (rejected if it would create a cycle).",
|
|
30435
|
+
inputSchema: {
|
|
30436
|
+
type: "object",
|
|
30437
|
+
properties: {
|
|
30438
|
+
area_id: { type: "string", description: "Product area id to edit (from list_product_areas)" },
|
|
30439
|
+
title: { type: "string", description: "New area title" },
|
|
30440
|
+
description: { type: "string", description: "New area description" },
|
|
30441
|
+
strategic_priority: {
|
|
30442
|
+
type: "string",
|
|
30443
|
+
enum: ["urgent", "high", "medium", "low", "none"],
|
|
30444
|
+
description: "Strategic priority (canonical Priority scale)"
|
|
30445
|
+
},
|
|
30446
|
+
parent_area_id: {
|
|
30447
|
+
type: ["string", "null"],
|
|
30448
|
+
description: "Re-parent under this area id; null un-nests (top-level); omit to leave unchanged"
|
|
30449
|
+
},
|
|
30450
|
+
owner: { type: "string", description: "Person or team that owns this area" }
|
|
30451
|
+
},
|
|
30452
|
+
required: ["area_id"]
|
|
30453
|
+
}
|
|
30454
|
+
},
|
|
30455
|
+
{
|
|
30456
|
+
name: "remove_product_from_area",
|
|
30457
|
+
description: "Remove a product from a product area's `products[]` in `.upg/portfolio.upg` (the product stays registered on the portfolio and in any other container). The inverse of `assign_product_to_area`.",
|
|
30458
|
+
inputSchema: {
|
|
30459
|
+
type: "object",
|
|
30460
|
+
properties: {
|
|
30461
|
+
product_id: { type: "string", description: "Product id (from list_local_products)" },
|
|
30462
|
+
area_id: { type: "string", description: "Product area id (from list_product_areas)" }
|
|
30463
|
+
},
|
|
30464
|
+
required: ["product_id", "area_id"]
|
|
30465
|
+
}
|
|
30466
|
+
},
|
|
30467
|
+
{
|
|
30468
|
+
name: "delete_area",
|
|
30469
|
+
description: "Delete a product area from `.upg/portfolio.upg`. Guarded: refuses while the area still has products unless `force: true`. Child areas are un-nested (their parent link is cleared) so no parent reference dangles.",
|
|
30470
|
+
inputSchema: {
|
|
30471
|
+
type: "object",
|
|
30472
|
+
properties: {
|
|
30473
|
+
area_id: { type: "string", description: "Product area id to delete (from list_product_areas)" },
|
|
30474
|
+
force: { type: "boolean", description: "Delete even if the area still has products (default false)" }
|
|
30475
|
+
},
|
|
30476
|
+
required: ["area_id"]
|
|
30477
|
+
}
|
|
30478
|
+
},
|
|
30479
|
+
{
|
|
30480
|
+
name: "move_product_to_area",
|
|
30481
|
+
description: "Move a product to a different product area: remove it from `from_area_id` (or, when omitted, from every area it currently sits in) and add it to `to_area_id`. Convenience over remove_product_from_area + assign_product_to_area.",
|
|
30482
|
+
inputSchema: {
|
|
30483
|
+
type: "object",
|
|
30484
|
+
properties: {
|
|
30485
|
+
product_id: { type: "string", description: "Product id (from list_local_products)" },
|
|
30486
|
+
to_area_id: { type: "string", description: "Destination product area id (from list_product_areas)" },
|
|
30487
|
+
from_area_id: { type: "string", description: "Source area id to remove from; omit to remove from all areas" }
|
|
30488
|
+
},
|
|
30489
|
+
required: ["product_id", "to_area_id"]
|
|
30490
|
+
}
|
|
30491
|
+
},
|
|
29940
30492
|
{
|
|
29941
30493
|
name: "attach_product_to_portfolio",
|
|
29942
30494
|
description: "Place an existing product under a portfolio (adds it to the portfolio's `products[]` in `.upg/portfolio.upg`). Resolves the portfolio against the portfolio document and auto-registers the product on the portfolio registry. Use after `create_product`, or pass `portfolio_id` to `create_product` directly.",
|
|
@@ -29949,6 +30501,18 @@ var TOOL_DEFINITIONS = [
|
|
|
29949
30501
|
required: ["product_id", "portfolio_id"]
|
|
29950
30502
|
}
|
|
29951
30503
|
},
|
|
30504
|
+
{
|
|
30505
|
+
name: "detach_product_from_portfolio",
|
|
30506
|
+
description: "Remove a product from a portfolio's `products[]` in `.upg/portfolio.upg` (the product stays registered and in any other container). The inverse of `attach_product_to_portfolio`.",
|
|
30507
|
+
inputSchema: {
|
|
30508
|
+
type: "object",
|
|
30509
|
+
properties: {
|
|
30510
|
+
product_id: { type: "string", description: "Product id (from list_local_products)" },
|
|
30511
|
+
portfolio_id: { type: "string", description: "Portfolio id (from list_portfolios)" }
|
|
30512
|
+
},
|
|
30513
|
+
required: ["product_id", "portfolio_id"]
|
|
30514
|
+
}
|
|
30515
|
+
},
|
|
29952
30516
|
{
|
|
29953
30517
|
name: "list_portfolios",
|
|
29954
30518
|
description: "List portfolios from the portfolio document (`.upg/portfolio.upg`). Portfolios represent the strategic axis (where we invest). Returns an empty list when no portfolio document exists yet.",
|
|
@@ -29961,7 +30525,7 @@ var TOOL_DEFINITIONS = [
|
|
|
29961
30525
|
},
|
|
29962
30526
|
{
|
|
29963
30527
|
name: "create_cross_product_edge",
|
|
29964
|
-
description: "Create a cross-product relationship between two entities in different products within a portfolio graph. Types: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds
|
|
30528
|
+
description: "Create a cross-product relationship between two entities in different products within a portfolio graph. Types: `shares_persona`, `shares_competitor`, `shares_metric`, `depends_on_product`, `cannibalises`, `succeeds`, `hosts` (host product runs the hosted product inside itself, directed host to hosted).",
|
|
29965
30529
|
inputSchema: {
|
|
29966
30530
|
type: "object",
|
|
29967
30531
|
properties: {
|
|
@@ -29969,7 +30533,7 @@ var TOOL_DEFINITIONS = [
|
|
|
29969
30533
|
target_id: { type: "string", description: "Target node ID" },
|
|
29970
30534
|
type: {
|
|
29971
30535
|
type: "string",
|
|
29972
|
-
enum: ["shares_persona", "shares_competitor", "shares_metric", "depends_on_product", "cannibalises", "succeeds"],
|
|
30536
|
+
enum: ["shares_persona", "shares_competitor", "shares_metric", "depends_on_product", "cannibalises", "succeeds", "hosts"],
|
|
29973
30537
|
description: "Cross-product relationship type"
|
|
29974
30538
|
},
|
|
29975
30539
|
source_product_id: { type: "string", description: "Product ID of the source node" },
|
|
@@ -29978,6 +30542,47 @@ var TOOL_DEFINITIONS = [
|
|
|
29978
30542
|
required: ["source_id", "target_id", "type"]
|
|
29979
30543
|
}
|
|
29980
30544
|
},
|
|
30545
|
+
{
|
|
30546
|
+
name: "delete_cross_product_edge",
|
|
30547
|
+
description: "Delete a cross-product edge from `.upg/portfolio.upg` by id. The inverse of `create_cross_product_edge`. Returns `deleted: false` (not an error) when no edge with that id exists.",
|
|
30548
|
+
inputSchema: {
|
|
30549
|
+
type: "object",
|
|
30550
|
+
properties: {
|
|
30551
|
+
edge_id: { type: "string", description: "Cross-product edge id (from list_portfolio_cross_edges)" }
|
|
30552
|
+
},
|
|
30553
|
+
required: ["edge_id"]
|
|
30554
|
+
}
|
|
30555
|
+
},
|
|
30556
|
+
{
|
|
30557
|
+
name: "batch_create_cross_product_edges",
|
|
30558
|
+
description: "Create up to 50 cross-product edges in one atomic write (the portfolio-tier mirror of batch_create_edges). Every edge is validated and qualified before anything is written; if any is invalid the whole batch is rejected. Referenced products are auto-registered.",
|
|
30559
|
+
inputSchema: {
|
|
30560
|
+
type: "object",
|
|
30561
|
+
properties: {
|
|
30562
|
+
edges: {
|
|
30563
|
+
type: "array",
|
|
30564
|
+
description: "Cross-product edges to create (max 50). Each: { source_id, target_id, type, source_product_id?, target_product_id? }.",
|
|
30565
|
+
items: {
|
|
30566
|
+
type: "object",
|
|
30567
|
+
properties: {
|
|
30568
|
+
source_id: { type: "string", description: "Source node ID (bare or qualified {product_id}/{node_id})" },
|
|
30569
|
+
target_id: { type: "string", description: "Target node ID (bare or qualified {product_id}/{node_id})" },
|
|
30570
|
+
type: {
|
|
30571
|
+
type: "string",
|
|
30572
|
+
enum: ["shares_persona", "shares_competitor", "shares_metric", "depends_on_product", "cannibalises", "succeeds", "hosts"],
|
|
30573
|
+
description: "Cross-product relationship type"
|
|
30574
|
+
},
|
|
30575
|
+
source_product_id: { type: "string", description: "Product ID of the source node (qualifies a bare source_id)" },
|
|
30576
|
+
target_product_id: { type: "string", description: "Product ID of the target node (qualifies a bare target_id)" }
|
|
30577
|
+
},
|
|
30578
|
+
required: ["source_id", "target_id", "type"]
|
|
30579
|
+
}
|
|
30580
|
+
},
|
|
30581
|
+
auto_create_portfolio: { type: "boolean", description: "Create an empty portfolio document if none exists (default false)" }
|
|
30582
|
+
},
|
|
30583
|
+
required: ["edges"]
|
|
30584
|
+
}
|
|
30585
|
+
},
|
|
29981
30586
|
{
|
|
29982
30587
|
name: "list_portfolio_cross_edges",
|
|
29983
30588
|
description: "List all cross-product edges stored in the portfolio document (`.upg/portfolio.upg`). Empty list when the portfolio document is absent.",
|
|
@@ -30157,10 +30762,17 @@ var HANDLERS = {
|
|
|
30157
30762
|
get_area_context: getAreaContext,
|
|
30158
30763
|
create_area: createArea,
|
|
30159
30764
|
assign_product_to_area: assignProductToAreaTool,
|
|
30765
|
+
update_area: updateAreaTool,
|
|
30766
|
+
remove_product_from_area: removeProductFromAreaTool,
|
|
30767
|
+
delete_area: deleteAreaTool,
|
|
30768
|
+
move_product_to_area: moveProductToAreaTool,
|
|
30160
30769
|
list_portfolios: listPortfolios,
|
|
30161
30770
|
get_organization: getOrganization,
|
|
30162
30771
|
create_cross_product_edge: createCrossProductEdge,
|
|
30772
|
+
delete_cross_product_edge: deleteCrossProductEdgeTool,
|
|
30773
|
+
batch_create_cross_product_edges: batchCreateCrossProductEdges,
|
|
30163
30774
|
attach_product_to_portfolio: attachProductToPortfolioTool,
|
|
30775
|
+
detach_product_from_portfolio: detachProductFromPortfolioTool,
|
|
30164
30776
|
list_portfolio_cross_edges: listPortfolioCrossEdges,
|
|
30165
30777
|
migrate_cross_edges: migrateCrossEdges,
|
|
30166
30778
|
get_sync_state: getSyncState,
|