@unified-product-graph/core 0.17.1 → 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 +52 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +52 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2312,6 +2312,16 @@ var UPG_EDGE_CATALOG = {
|
|
|
2312
2312
|
// UPG_EDGE_MIGRATIONS['0.9.9'].
|
|
2313
2313
|
product_organised_into_department: { forward_verb: "organised_into", reverse_verb: "organises", classification: "hierarchy", source_type: "product", target_type: "department" },
|
|
2314
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" },
|
|
2315
2325
|
department_includes_stakeholder: { forward_verb: "includes", reverse_verb: "included_in", classification: "hierarchy", source_type: "department", target_type: "stakeholder" },
|
|
2316
2326
|
// NOTE: `person` is a containment-free entity type (see
|
|
2317
2327
|
// UPG_CONTAINMENT_FREE_TYPES in grammar/hierarchy.ts). It is referenced
|
|
@@ -3421,7 +3431,13 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
3421
3431
|
"product_implements_design_system",
|
|
3422
3432
|
// Org-link (0.17.0): dual-registered ownership edges (see UPGCrossEdgeType above).
|
|
3423
3433
|
"node_owned_by_team",
|
|
3424
|
-
"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"
|
|
3425
3441
|
];
|
|
3426
3442
|
var REGISTRY_PRODUCT_ID = "registry";
|
|
3427
3443
|
var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
|
|
@@ -3875,7 +3891,11 @@ var UPG_VALID_CHILDREN = {
|
|
|
3875
3891
|
"skill",
|
|
3876
3892
|
"ceremony",
|
|
3877
3893
|
"capacity_plan",
|
|
3878
|
-
"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"
|
|
3879
3899
|
],
|
|
3880
3900
|
// ── Data & Analytics hierarchy ───────────────────────────────────────────────
|
|
3881
3901
|
data_source: ["metric", "data_pipeline", "data_lineage", "event_schema"],
|
|
@@ -10868,6 +10888,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10868
10888
|
// ConstraintProperties: Constraint: a named limitation or boundary on product creation.
|
|
10869
10889
|
constraint: {
|
|
10870
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)." },
|
|
10871
10892
|
constraint_status: { type: "string", enum: ["binding", "advisory", "lifted"], description: "Whether binding, advisory, or lifted" },
|
|
10872
10893
|
rule_strength: { type: "string", enum: ["must", "must_not", "exception", "warning", "guideline"], description: "Enforcement strictness. Reuses the governance/guideline rule vocabulary." },
|
|
10873
10894
|
source: { type: "string", description: "Free-text origin: policy document, regulation, stakeholder, technical doc." },
|
|
@@ -23057,7 +23078,8 @@ var STRATEGY_GUIDE = {
|
|
|
23057
23078
|
anti_patterns: [
|
|
23058
23079
|
{ description: "Outcomes without metrics. Measurement is the signal an outcome happened." },
|
|
23059
23080
|
{ description: "Objectives without key results: an objective without measurement is a wish" },
|
|
23060
|
-
{ 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)." }
|
|
23061
23083
|
]
|
|
23062
23084
|
};
|
|
23063
23085
|
var PRODUCT_SPEC_GUIDE = {
|
|
@@ -23608,6 +23630,12 @@ var TEAM_ORG_GUIDE = {
|
|
|
23608
23630
|
description: "Teams are staffed with roles, target OKRs, reflect in retrospectives, and are blocked by dependencies",
|
|
23609
23631
|
entity_types: ["team", "role", "team_okr", "retrospective", "dependency"],
|
|
23610
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"]
|
|
23611
23639
|
}
|
|
23612
23640
|
],
|
|
23613
23641
|
required_bridges: [
|
|
@@ -23618,6 +23646,7 @@ var TEAM_ORG_GUIDE = {
|
|
|
23618
23646
|
{ description: "Teams without OKRs: every team needs clear goals" },
|
|
23619
23647
|
{ description: "Dependencies without both teams linked: a dependency must connect blocker and blocked" },
|
|
23620
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." },
|
|
23621
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." }
|
|
23622
23651
|
]
|
|
23623
23652
|
};
|
|
@@ -26340,7 +26369,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26340
26369
|
{
|
|
26341
26370
|
id: "competitors-missing-past-validation",
|
|
26342
26371
|
name: "Competitor catalogue empty past validation",
|
|
26343
|
-
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.",
|
|
26344
26373
|
structured_condition: {
|
|
26345
26374
|
check: {
|
|
26346
26375
|
type: "benchmark",
|
|
@@ -26350,14 +26379,18 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26350
26379
|
},
|
|
26351
26380
|
why_it_matters: "Without competitors in the graph, positioning, differentiation, and switching-cost analysis lack referents. The team is reasoning in a vacuum.",
|
|
26352
26381
|
remediation: "Catalogue the 3\u20135 closest alternatives users would pick today. Use `/upg-compete` to author.",
|
|
26353
|
-
|
|
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"],
|
|
26354
26387
|
severity: "medium",
|
|
26355
26388
|
source: { kind: "industry_practice", category: "product_strategy" }
|
|
26356
26389
|
},
|
|
26357
26390
|
{
|
|
26358
26391
|
id: "persona-count-below-stage-benchmark",
|
|
26359
26392
|
name: "Persona count below stage benchmark",
|
|
26360
|
-
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.",
|
|
26361
26394
|
structured_condition: {
|
|
26362
26395
|
check: {
|
|
26363
26396
|
type: "benchmark",
|
|
@@ -26365,9 +26398,13 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26365
26398
|
comparison: "below_min"
|
|
26366
26399
|
}
|
|
26367
26400
|
},
|
|
26368
|
-
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.",
|
|
26369
26402
|
remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-new-persona`.",
|
|
26370
|
-
|
|
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"],
|
|
26371
26408
|
severity: "medium",
|
|
26372
26409
|
source: { kind: "practitioner", attribution: "Alan Cooper, The Inmates Are Running the Asylum" }
|
|
26373
26410
|
},
|
|
@@ -26392,7 +26429,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26392
26429
|
{
|
|
26393
26430
|
id: "single-domain-graph",
|
|
26394
26431
|
name: "Single-domain graph",
|
|
26395
|
-
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.",
|
|
26396
26433
|
structured_condition: {
|
|
26397
26434
|
operator: "and",
|
|
26398
26435
|
checks: [
|
|
@@ -26402,7 +26439,11 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26402
26439
|
},
|
|
26403
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?".',
|
|
26404
26441
|
remediation: "Identify the next adjacent domain (usually `user`, `validation`, or `product_spec`) and add 2\u20133 anchor entities to bridge.",
|
|
26405
|
-
|
|
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"],
|
|
26406
26447
|
severity: "medium",
|
|
26407
26448
|
source: { kind: "fundamental" }
|
|
26408
26449
|
},
|
|
@@ -29800,7 +29841,7 @@ function isCanonical(text) {
|
|
|
29800
29841
|
}
|
|
29801
29842
|
|
|
29802
29843
|
// src/index.ts
|
|
29803
|
-
var UPG_VERSION = "0.17.
|
|
29844
|
+
var UPG_VERSION = "0.17.2";
|
|
29804
29845
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
29805
29846
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
29806
29847
|
var UPG_TYPES = getTypes();
|