@unified-product-graph/core 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +59 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -11
- package/dist/index.d.ts +42 -11
- package/dist/index.js +58 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -98,6 +98,7 @@ __export(index_exports, {
|
|
|
98
98
|
UPG_MIGRATIONS: () => UPG_MIGRATIONS,
|
|
99
99
|
UPG_PLAYBOOKS: () => UPG_PLAYBOOKS,
|
|
100
100
|
UPG_POLYMORPHIC_EDGE_KEYS: () => UPG_POLYMORPHIC_EDGE_KEYS,
|
|
101
|
+
UPG_PORTFOLIO_KINDS: () => UPG_PORTFOLIO_KINDS,
|
|
101
102
|
UPG_PRODUCT_STAGES: () => UPG_PRODUCT_STAGES,
|
|
102
103
|
UPG_PRODUCT_STAGE_COERCION_MAP: () => UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
103
104
|
UPG_PROPERTY_MIGRATIONS: () => UPG_PROPERTY_MIGRATIONS,
|
|
@@ -2311,6 +2312,16 @@ var UPG_EDGE_CATALOG = {
|
|
|
2311
2312
|
// UPG_EDGE_MIGRATIONS['0.9.9'].
|
|
2312
2313
|
product_organised_into_department: { forward_verb: "organised_into", reverse_verb: "organises", classification: "hierarchy", source_type: "product", target_type: "department" },
|
|
2313
2314
|
department_contains_team: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "department", target_type: "team" },
|
|
2315
|
+
// Second-level team nesting (0.17.2). department_contains_team covers the first
|
|
2316
|
+
// level (department -> team); this covers a team nested inside another team in
|
|
2317
|
+
// the same department (a sub-team or squad under a parent team). Modelled
|
|
2318
|
+
// parent -> child like feature_area_contains_feature_area, so the org map builds
|
|
2319
|
+
// correctly in get_tree (which reads source as the parent); the reverse verb
|
|
2320
|
+
// belongs_to gives the upward "this team is part of that team" read. The
|
|
2321
|
+
// same-department expectation is advisory (a write-time warning), never a hard
|
|
2322
|
+
// block; cross-department reporting lines belong to person_reports_to_person at
|
|
2323
|
+
// the individual level.
|
|
2324
|
+
team_contains_team: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "team", target_type: "team" },
|
|
2314
2325
|
department_includes_stakeholder: { forward_verb: "includes", reverse_verb: "included_in", classification: "hierarchy", source_type: "department", target_type: "stakeholder" },
|
|
2315
2326
|
// NOTE: `person` is a containment-free entity type (see
|
|
2316
2327
|
// UPG_CONTAINMENT_FREE_TYPES in grammar/hierarchy.ts). It is referenced
|
|
@@ -3420,9 +3431,16 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
3420
3431
|
"product_implements_design_system",
|
|
3421
3432
|
// Org-link (0.17.0): dual-registered ownership edges (see UPGCrossEdgeType above).
|
|
3422
3433
|
"node_owned_by_team",
|
|
3423
|
-
"node_owned_by_department"
|
|
3434
|
+
"node_owned_by_department",
|
|
3435
|
+
// Cross-graph strategy & measurement (0.17.2): dual-registered OKR/measurement
|
|
3436
|
+
// hierarchy edges so the strategy spine can span files (see UPGCrossEdgeType above).
|
|
3437
|
+
"strategic_theme_contains_objective",
|
|
3438
|
+
"objective_achieved_through_key_result",
|
|
3439
|
+
"key_result_quantified_by_metric",
|
|
3440
|
+
"objective_measured_by_metric"
|
|
3424
3441
|
];
|
|
3425
3442
|
var REGISTRY_PRODUCT_ID = "registry";
|
|
3443
|
+
var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
|
|
3426
3444
|
|
|
3427
3445
|
// src/registry/entity-type-resolution.ts
|
|
3428
3446
|
var TYPES = getTypes();
|
|
@@ -3873,7 +3891,11 @@ var UPG_VALID_CHILDREN = {
|
|
|
3873
3891
|
"skill",
|
|
3874
3892
|
"ceremony",
|
|
3875
3893
|
"capacity_plan",
|
|
3876
|
-
"decision"
|
|
3894
|
+
"decision",
|
|
3895
|
+
// Self-nesting (0.17.2): a parent team contains its sub-teams / squads, one
|
|
3896
|
+
// level below department_contains_team. Backs team_contains_team. Mirrors
|
|
3897
|
+
// feature_area_contains_feature_area (same-type child listed last).
|
|
3898
|
+
"team"
|
|
3877
3899
|
],
|
|
3878
3900
|
// ── Data & Analytics hierarchy ───────────────────────────────────────────────
|
|
3879
3901
|
data_source: ["metric", "data_pipeline", "data_lineage", "event_schema"],
|
|
@@ -10866,6 +10888,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10866
10888
|
// ConstraintProperties: Constraint: a named limitation or boundary on product creation.
|
|
10867
10889
|
constraint: {
|
|
10868
10890
|
constraint_kind: { type: "string", enum: ["resource", "technical", "regulatory", "temporal", "compliance", "other"], description: "Limitation category" },
|
|
10891
|
+
constraint_origin: { type: "string", enum: ["internal", "external"], description: "Provenance: internal (a self-imposed principle or operating tenet) or external (a limit, requirement, or ceiling imposed from outside)." },
|
|
10869
10892
|
constraint_status: { type: "string", enum: ["binding", "advisory", "lifted"], description: "Whether binding, advisory, or lifted" },
|
|
10870
10893
|
rule_strength: { type: "string", enum: ["must", "must_not", "exception", "warning", "guideline"], description: "Enforcement strictness. Reuses the governance/guideline rule vocabulary." },
|
|
10871
10894
|
source: { type: "string", description: "Free-text origin: policy document, regulation, stakeholder, technical doc." },
|
|
@@ -23055,7 +23078,8 @@ var STRATEGY_GUIDE = {
|
|
|
23055
23078
|
anti_patterns: [
|
|
23056
23079
|
{ description: "Outcomes without metrics. Measurement is the signal an outcome happened." },
|
|
23057
23080
|
{ description: "Objectives without key results: an objective without measurement is a wish" },
|
|
23058
|
-
{ description: "Too many strategic themes: focus beats breadth, aim for 2-4 active themes" }
|
|
23081
|
+
{ description: "Too many strategic themes: focus beats breadth, aim for 2-4 active themes" },
|
|
23082
|
+
{ description: "Constraints without an origin: a qualitative guardrail lives on a constraint, so set constraint_origin to say where it comes from. internal marks a self-imposed principle or operating tenet the team commits to; external marks a limit, requirement, or ceiling imposed from outside (a regulation, a budget cap, a platform bound)." }
|
|
23059
23083
|
]
|
|
23060
23084
|
};
|
|
23061
23085
|
var PRODUCT_SPEC_GUIDE = {
|
|
@@ -23606,6 +23630,12 @@ var TEAM_ORG_GUIDE = {
|
|
|
23606
23630
|
description: "Teams are staffed with roles, target OKRs, reflect in retrospectives, and are blocked by dependencies",
|
|
23607
23631
|
entity_types: ["team", "role", "team_okr", "retrospective", "dependency"],
|
|
23608
23632
|
edge_chain: ["team_staffed_with_role", "team_targets_team_okr", "team_reflects_in_retrospective", "dependency_blocks_team"]
|
|
23633
|
+
},
|
|
23634
|
+
{
|
|
23635
|
+
name: "Org Nesting",
|
|
23636
|
+
description: "A department contains its teams; a parent team contains its sub-teams or squads one level deeper. Keep a sub-team in the same department as its parent team. Cross-department reporting lines are modelled at the individual level with person_reports_to_person, not by nesting a team under a parent in another department.",
|
|
23637
|
+
entity_types: ["department", "team"],
|
|
23638
|
+
edge_chain: ["department_contains_team", "team_contains_team"]
|
|
23609
23639
|
}
|
|
23610
23640
|
],
|
|
23611
23641
|
required_bridges: [
|
|
@@ -23615,7 +23645,9 @@ var TEAM_ORG_GUIDE = {
|
|
|
23615
23645
|
anti_patterns: [
|
|
23616
23646
|
{ description: "Teams without OKRs: every team needs clear goals" },
|
|
23617
23647
|
{ description: "Dependencies without both teams linked: a dependency must connect blocker and blocked" },
|
|
23618
|
-
{ description: "Retrospectives without action items: reflection without action is just venting" }
|
|
23648
|
+
{ description: "Retrospectives without action items: reflection without action is just venting" },
|
|
23649
|
+
{ description: "Nesting a sub-team under a parent team in a different department: a team_contains_team nesting should stay within one department (both teams sharing a department_contains_team parent). A cross-department nesting is almost always a modelling error; model a cross-department reporting line at the individual level with person_reports_to_person instead." },
|
|
23650
|
+
{ description: "Double-modelling the org as product_area and team_org: a product_area is the classification axis ON products (which area a product belongs to, the axis used to group products); a team_org department/team is the people structure (the org chart, who reports to whom). Use product_area to classify products and department/team for the org chart, and relate them through the team that owns an area rather than restating one as the other." }
|
|
23619
23651
|
]
|
|
23620
23652
|
};
|
|
23621
23653
|
var PROGRAM_MGMT_GUIDE = {
|
|
@@ -23724,7 +23756,8 @@ var PORTFOLIO_GUIDE = {
|
|
|
23724
23756
|
],
|
|
23725
23757
|
anti_patterns: [
|
|
23726
23758
|
{ description: "Products without a product area: every product should be classified" },
|
|
23727
|
-
{ description: "Portfolios without strategic alignment: portfolio decisions should reflect strategy" }
|
|
23759
|
+
{ description: "Portfolios without strategic alignment: portfolio decisions should reflect strategy" },
|
|
23760
|
+
{ description: "Confusing product_area with the org chart: product_area is the classification axis on products (how products are grouped), not the people structure. Model who reports to whom with team_org department/team; relate the two through the team that owns an area." }
|
|
23728
23761
|
]
|
|
23729
23762
|
};
|
|
23730
23763
|
var WORKSPACE_GUIDE = {
|
|
@@ -26336,7 +26369,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26336
26369
|
{
|
|
26337
26370
|
id: "competitors-missing-past-validation",
|
|
26338
26371
|
name: "Competitor catalogue empty past validation",
|
|
26339
|
-
description: "The graph has zero competitor entities at
|
|
26372
|
+
description: "The graph has zero competitor entities at build stage or later. Past validation, the absence of named competitors usually means alternatives haven't been thought through, not that none exist. A graph still at concept or validation is exempt: cataloguing the field can wait until you commit to building.",
|
|
26340
26373
|
structured_condition: {
|
|
26341
26374
|
check: {
|
|
26342
26375
|
type: "benchmark",
|
|
@@ -26346,14 +26379,18 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26346
26379
|
},
|
|
26347
26380
|
why_it_matters: "Without competitors in the graph, positioning, differentiation, and switching-cost analysis lack referents. The team is reasoning in a vacuum.",
|
|
26348
26381
|
remediation: "Catalogue the 3\u20135 closest alternatives users would pick today. Use `/upg-compete` to author.",
|
|
26349
|
-
|
|
26382
|
+
// Fires from build onward, matching the name (past the validation stage). A
|
|
26383
|
+
// concept or validation graph is still framing the problem; holding it to a
|
|
26384
|
+
// competitor benchmark there is the false alarm this anti-pattern caused on
|
|
26385
|
+
// legitimately-young graphs.
|
|
26386
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
26350
26387
|
severity: "medium",
|
|
26351
26388
|
source: { kind: "industry_practice", category: "product_strategy" }
|
|
26352
26389
|
},
|
|
26353
26390
|
{
|
|
26354
26391
|
id: "persona-count-below-stage-benchmark",
|
|
26355
26392
|
name: "Persona count below stage benchmark",
|
|
26356
|
-
description: "The graph has fewer personas than the stage-appropriate benchmark expects. Persona under-coverage signals the team has not segmented its audience.",
|
|
26393
|
+
description: "The graph has fewer personas than the stage-appropriate benchmark expects, from beta onward. Persona under-coverage at that point signals the team has not segmented its audience. Concept through build are exempt: the sound move there is to start with one beachhead persona and expand as you find fit.",
|
|
26357
26394
|
structured_condition: {
|
|
26358
26395
|
check: {
|
|
26359
26396
|
type: "benchmark",
|
|
@@ -26361,9 +26398,13 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26361
26398
|
comparison: "below_min"
|
|
26362
26399
|
}
|
|
26363
26400
|
},
|
|
26364
|
-
why_it_matters:
|
|
26401
|
+
why_it_matters: "A graph still on a single persona by beta is usually carrying an unexamined assumption that every user is the same.",
|
|
26365
26402
|
remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-new-persona`.",
|
|
26366
|
-
|
|
26403
|
+
// Fires from beta onward. The persona benchmark expects 2+ from validation,
|
|
26404
|
+
// but the canonical beachhead move is to start with one persona and expand as
|
|
26405
|
+
// fit is found, so holding a build-stage graph to a multi-persona bar is a
|
|
26406
|
+
// false alarm. By beta an unsegmented audience is a real signal.
|
|
26407
|
+
stages: ["beta", "launch", "growth", "mature"],
|
|
26367
26408
|
severity: "medium",
|
|
26368
26409
|
source: { kind: "practitioner", attribution: "Alan Cooper, The Inmates Are Running the Asylum" }
|
|
26369
26410
|
},
|
|
@@ -26388,7 +26429,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26388
26429
|
{
|
|
26389
26430
|
id: "single-domain-graph",
|
|
26390
26431
|
name: "Single-domain graph",
|
|
26391
|
-
description: "The graph has more than five entities but they all live in a single UPG domain. A real product spans multiple domains; a single-domain graph is usually a deep notebook in one corner with the rest of the picture missing.",
|
|
26432
|
+
description: "The graph has more than five entities but they all live in a single UPG domain, at beta stage or later. A real product spans multiple domains by then; a single-domain graph that far along is usually a deep notebook in one corner with the rest of the picture missing. Concept through build are exempt: a graph legitimately starts deep in one domain and broadens as it matures.",
|
|
26392
26433
|
structured_condition: {
|
|
26393
26434
|
operator: "and",
|
|
26394
26435
|
checks: [
|
|
@@ -26398,7 +26439,11 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26398
26439
|
},
|
|
26399
26440
|
why_it_matters: 'Single-domain coverage prevents cross-domain reasoning. The graph cannot answer questions like "which feature serves which persona?" or "which experiment validates which hypothesis?".',
|
|
26400
26441
|
remediation: "Identify the next adjacent domain (usually `user`, `validation`, or `product_spec`) and add 2\u20133 anchor entities to bridge.",
|
|
26401
|
-
|
|
26442
|
+
// Fires from beta onward. Early on a graph is legitimately deep in one domain
|
|
26443
|
+
// (a discovery notebook, a thin internal tool); only once it reaches beta is
|
|
26444
|
+
// single-domain coverage a smell rather than a normal starting shape. Extends
|
|
26445
|
+
// through growth and mature, where a single-domain graph is most telling.
|
|
26446
|
+
stages: ["beta", "launch", "growth", "mature"],
|
|
26402
26447
|
severity: "medium",
|
|
26403
26448
|
source: { kind: "fundamental" }
|
|
26404
26449
|
},
|
|
@@ -29796,7 +29841,7 @@ function isCanonical(text) {
|
|
|
29796
29841
|
}
|
|
29797
29842
|
|
|
29798
29843
|
// src/index.ts
|
|
29799
|
-
var UPG_VERSION = "0.17.
|
|
29844
|
+
var UPG_VERSION = "0.17.2";
|
|
29800
29845
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
29801
29846
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
29802
29847
|
var UPG_TYPES = getTypes();
|
|
@@ -29929,6 +29974,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
29929
29974
|
UPG_MIGRATIONS,
|
|
29930
29975
|
UPG_PLAYBOOKS,
|
|
29931
29976
|
UPG_POLYMORPHIC_EDGE_KEYS,
|
|
29977
|
+
UPG_PORTFOLIO_KINDS,
|
|
29932
29978
|
UPG_PRODUCT_STAGES,
|
|
29933
29979
|
UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
29934
29980
|
UPG_PROPERTY_MIGRATIONS,
|