@unified-product-graph/core 0.16.2 → 0.17.1
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 +150 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -18
- package/dist/index.d.ts +141 -18
- package/dist/index.js +140 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3174,9 +3174,13 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
3174
3174
|
"screen_targets_competitor",
|
|
3175
3175
|
"feature_surfaces_product",
|
|
3176
3176
|
"feature_uses_design_component",
|
|
3177
|
-
"product_implements_design_system"
|
|
3177
|
+
"product_implements_design_system",
|
|
3178
|
+
// Org-link (0.17.0): dual-registered ownership edges (see UPGCrossEdgeType above).
|
|
3179
|
+
"node_owned_by_team",
|
|
3180
|
+
"node_owned_by_department"
|
|
3178
3181
|
];
|
|
3179
3182
|
var REGISTRY_PRODUCT_ID = "registry";
|
|
3183
|
+
var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
|
|
3180
3184
|
|
|
3181
3185
|
// src/registry/entity-type-resolution.ts
|
|
3182
3186
|
var TYPES = getTypes();
|
|
@@ -23369,7 +23373,8 @@ var TEAM_ORG_GUIDE = {
|
|
|
23369
23373
|
anti_patterns: [
|
|
23370
23374
|
{ description: "Teams without OKRs: every team needs clear goals" },
|
|
23371
23375
|
{ description: "Dependencies without both teams linked: a dependency must connect blocker and blocked" },
|
|
23372
|
-
{ description: "Retrospectives without action items: reflection without action is just venting" }
|
|
23376
|
+
{ description: "Retrospectives without action items: reflection without action is just venting" },
|
|
23377
|
+
{ 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." }
|
|
23373
23378
|
]
|
|
23374
23379
|
};
|
|
23375
23380
|
var PROGRAM_MGMT_GUIDE = {
|
|
@@ -23478,7 +23483,8 @@ var PORTFOLIO_GUIDE = {
|
|
|
23478
23483
|
],
|
|
23479
23484
|
anti_patterns: [
|
|
23480
23485
|
{ description: "Products without a product area: every product should be classified" },
|
|
23481
|
-
{ description: "Portfolios without strategic alignment: portfolio decisions should reflect strategy" }
|
|
23486
|
+
{ description: "Portfolios without strategic alignment: portfolio decisions should reflect strategy" },
|
|
23487
|
+
{ 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." }
|
|
23482
23488
|
]
|
|
23483
23489
|
};
|
|
23484
23490
|
var WORKSPACE_GUIDE = {
|
|
@@ -26295,6 +26301,54 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26295
26301
|
severity: "medium",
|
|
26296
26302
|
source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (product discovery)" }
|
|
26297
26303
|
},
|
|
26304
|
+
// ── Operating-function layer (0.17.0): member_kind: operating_function ─────
|
|
26305
|
+
// Carry concern 'operating' (UPG_ANTI_PATTERN_CONCERNS) and are evaluated ONLY
|
|
26306
|
+
// for operating_function graphs — a function a team operates (revenue / success
|
|
26307
|
+
// / finance / people / marketing), not a product it ships. The product-spine
|
|
26308
|
+
// patterns above are not evaluated for that kind (category errors); these assert
|
|
26309
|
+
// the function spine instead.
|
|
26310
|
+
{
|
|
26311
|
+
id: "operating-function-without-north-star",
|
|
26312
|
+
since: "0.17.0",
|
|
26313
|
+
name: "Operating function without a north-star metric",
|
|
26314
|
+
description: "An operating function graph has real content but no north-star metric to operate toward. A function (sales, finance, people, marketing) is run against one headline number it moves and that rolls up to the company tree. With no north-star metric the function has direction but nothing to steer by.",
|
|
26315
|
+
structured_condition: {
|
|
26316
|
+
operator: "and",
|
|
26317
|
+
checks: [
|
|
26318
|
+
{ check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
|
|
26319
|
+
{ check: { type: "entity_count", entity_type: "metric", filter: { property: "designation", value: "north_star" }, comparison: "zero" } }
|
|
26320
|
+
]
|
|
26321
|
+
},
|
|
26322
|
+
why_it_matters: "A function with no north-star metric cannot be steered, prioritised, or rolled into the company metric tree. Operating work becomes activity without a measured target.",
|
|
26323
|
+
remediation: "Add the one `metric` the function operates toward and mark it `designation: north_star`, then wire it to the company metric tree via `metric_decomposes_into_metric`.",
|
|
26324
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26325
|
+
severity: "high",
|
|
26326
|
+
source: { kind: "fundamental" }
|
|
26327
|
+
},
|
|
26328
|
+
{
|
|
26329
|
+
id: "operating-function-without-operating-content",
|
|
26330
|
+
since: "0.17.0",
|
|
26331
|
+
name: "Operating function without operating content",
|
|
26332
|
+
description: "An operating function graph has more than a few entities but none in any operating domain (sales, go-to-market, customer success, growth, marketing, business model, pricing). It carries direction and people but no operating substance: the work the function actually runs.",
|
|
26333
|
+
structured_condition: {
|
|
26334
|
+
operator: "and",
|
|
26335
|
+
checks: [
|
|
26336
|
+
{ check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
|
|
26337
|
+
{ check: { type: "domain_population", domain_id: "sales", comparison: "zero" } },
|
|
26338
|
+
{ check: { type: "domain_population", domain_id: "go_to_market", comparison: "zero" } },
|
|
26339
|
+
{ check: { type: "domain_population", domain_id: "customer_success", comparison: "zero" } },
|
|
26340
|
+
{ check: { type: "domain_population", domain_id: "growth", comparison: "zero" } },
|
|
26341
|
+
{ check: { type: "domain_population", domain_id: "marketing", comparison: "zero" } },
|
|
26342
|
+
{ check: { type: "domain_population", domain_id: "business_model", comparison: "zero" } },
|
|
26343
|
+
{ check: { type: "domain_population", domain_id: "pricing", comparison: "zero" } }
|
|
26344
|
+
]
|
|
26345
|
+
},
|
|
26346
|
+
why_it_matters: "A function graph that is all strategy and org chart with no operating domain is a stub: it states intent but models none of the work the function runs, so it answers no operating questions.",
|
|
26347
|
+
remediation: "Populate at least one operating domain appropriate to the function (e.g. Field \u2192 `sales` / `go_to_market` / `customer_success`; Finance \u2192 `business_model` / `pricing`). Start from the matching template via `/upg-new-from-template`.",
|
|
26348
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26349
|
+
severity: "medium",
|
|
26350
|
+
source: { kind: "fundamental" }
|
|
26351
|
+
},
|
|
26298
26352
|
// ── Foundations layer (0.9.13): portfolio-scoped, registry-aware ──────────
|
|
26299
26353
|
// These read the shared registry + cross-product edges, so they are evaluated
|
|
26300
26354
|
// by portfolio_validate, not the single-graph evaluator. They carry no
|
|
@@ -26335,6 +26389,23 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26335
26389
|
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
26336
26390
|
severity: "low",
|
|
26337
26391
|
source: { kind: "fundamental" }
|
|
26392
|
+
},
|
|
26393
|
+
// ── Operating-function org link (0.17.0): portfolio-scoped ─────────────────
|
|
26394
|
+
// Cross-product detector (in portfolio_validate): an operating_function graph
|
|
26395
|
+
// should reference the org unit it operates under, which lives once in the
|
|
26396
|
+
// rollup's team_org map. Carries no structured_condition — the org link is a
|
|
26397
|
+
// cross-product edge in portfolio.upg, not a single-graph shape.
|
|
26398
|
+
{
|
|
26399
|
+
id: "operating-function-without-org-link",
|
|
26400
|
+
since: "0.17.0",
|
|
26401
|
+
scope: "portfolio",
|
|
26402
|
+
name: "Operating function without an org link",
|
|
26403
|
+
description: "An operating_function graph does not reference the org unit it operates under. The department/team hierarchy lives once in the rollup (org_rollup) as team_org entities; a function should point its spine at the department or team that owns it (node_owned_by_department / node_owned_by_team), so the operating layer hangs off the canonical org map rather than re-stating it.",
|
|
26404
|
+
why_it_matters: "A function with no org link floats free of the org chart: its work cannot be rolled up by department, and the single source of truth for who owns what is bypassed.",
|
|
26405
|
+
remediation: "Add a cross-product node_owned_by_department (or node_owned_by_team) edge from the function spine to its department/team in the rollup. Mint the org unit once in the org_rollup graph; functions reference it.",
|
|
26406
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26407
|
+
severity: "medium",
|
|
26408
|
+
source: { kind: "fundamental" }
|
|
26338
26409
|
}
|
|
26339
26410
|
];
|
|
26340
26411
|
function getAntiPatternById(id) {
|
|
@@ -26347,6 +26418,45 @@ function getAntiPatternsBySeverity(severity) {
|
|
|
26347
26418
|
return UPG_ANTI_PATTERNS.filter((ap) => ap.severity === severity);
|
|
26348
26419
|
}
|
|
26349
26420
|
|
|
26421
|
+
// src/intelligence/validation-profiles.ts
|
|
26422
|
+
var UPG_ANTI_PATTERN_CONCERNS = {
|
|
26423
|
+
// universal — kind-independent graph hygiene + strategy discipline
|
|
26424
|
+
"objective-without-key-results": "universal",
|
|
26425
|
+
"single-domain-graph": "universal",
|
|
26426
|
+
"orphan-loose-thoughts": "universal",
|
|
26427
|
+
// operating — only evaluated for operating_function graphs
|
|
26428
|
+
"operating-function-without-north-star": "operating",
|
|
26429
|
+
"operating-function-without-operating-content": "operating"
|
|
26430
|
+
// everything else defaults to 'product_spine'
|
|
26431
|
+
};
|
|
26432
|
+
function concernFor(antiPatternId) {
|
|
26433
|
+
return UPG_ANTI_PATTERN_CONCERNS[antiPatternId] ?? "product_spine";
|
|
26434
|
+
}
|
|
26435
|
+
var UPG_VALIDATION_PROFILES = {
|
|
26436
|
+
product: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["product_spine", "universal"] },
|
|
26437
|
+
watched: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: [] },
|
|
26438
|
+
org_rollup: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["universal"] },
|
|
26439
|
+
operating_function: { evaluate_concerns: ["universal", "operating"], gating_concerns: ["universal", "operating"] }
|
|
26440
|
+
};
|
|
26441
|
+
function validationProfileFor(kind) {
|
|
26442
|
+
return UPG_VALIDATION_PROFILES[kind ?? "product"] ?? UPG_VALIDATION_PROFILES.product;
|
|
26443
|
+
}
|
|
26444
|
+
function concernEvaluatedFor(kind, concern) {
|
|
26445
|
+
return validationProfileFor(kind).evaluate_concerns.includes(concern);
|
|
26446
|
+
}
|
|
26447
|
+
function concernGatesFor(kind, concern) {
|
|
26448
|
+
return validationProfileFor(kind).gating_concerns.includes(concern);
|
|
26449
|
+
}
|
|
26450
|
+
var COVERAGE_ANTI_PATTERNS = /* @__PURE__ */ new Set([
|
|
26451
|
+
"single-domain-graph",
|
|
26452
|
+
"persona-count-below-stage-benchmark",
|
|
26453
|
+
"competitors-missing-past-validation"
|
|
26454
|
+
]);
|
|
26455
|
+
var THIN_GRAPH_THRESHOLD = 8;
|
|
26456
|
+
function isThinCoverageAdvisory(antiPatternId, totalEntityCount) {
|
|
26457
|
+
return COVERAGE_ANTI_PATTERNS.has(antiPatternId) && totalEntityCount < THIN_GRAPH_THRESHOLD;
|
|
26458
|
+
}
|
|
26459
|
+
|
|
26350
26460
|
// src/intelligence/evaluator.ts
|
|
26351
26461
|
var SEVERITY_ORDER = {
|
|
26352
26462
|
high: 0,
|
|
@@ -26364,8 +26474,10 @@ function evaluateAntiPatterns(inputs, options) {
|
|
|
26364
26474
|
if (inputs.productStage && !ap.stages.includes(inputs.productStage)) {
|
|
26365
26475
|
continue;
|
|
26366
26476
|
}
|
|
26477
|
+
const concern = concernFor(ap.id);
|
|
26478
|
+
if (!concernEvaluatedFor(inputs.memberKind, concern)) continue;
|
|
26367
26479
|
if (evaluateCondition(ap.structured_condition, inputs)) {
|
|
26368
|
-
fires.push(buildViolation(ap));
|
|
26480
|
+
fires.push(buildViolation(ap, concern));
|
|
26369
26481
|
}
|
|
26370
26482
|
}
|
|
26371
26483
|
fires.sort((a, b) => {
|
|
@@ -26434,11 +26546,12 @@ function compareNumber(value, comparison, threshold) {
|
|
|
26434
26546
|
}
|
|
26435
26547
|
}
|
|
26436
26548
|
function evaluateEntityCount(check, inputs) {
|
|
26437
|
-
const
|
|
26549
|
+
const filter = check.filter;
|
|
26438
26550
|
let count = 0;
|
|
26439
|
-
if (
|
|
26440
|
-
|
|
26441
|
-
|
|
26551
|
+
if (filter && typeof filter.property === "string" && typeof filter.value === "string") {
|
|
26552
|
+
count = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.property]?.[filter.value] ?? 0;
|
|
26553
|
+
} else if (filter && typeof filter.status === "string") {
|
|
26554
|
+
count = inputs.countsByTypeAndStatus?.[check.entity_type]?.[filter.status] ?? 0;
|
|
26442
26555
|
} else {
|
|
26443
26556
|
count = inputs.countsByType[check.entity_type] ?? 0;
|
|
26444
26557
|
}
|
|
@@ -26505,11 +26618,12 @@ function evaluateDomainPopulation(check, inputs) {
|
|
|
26505
26618
|
function evaluateOrphanCount(check, inputs) {
|
|
26506
26619
|
return compareNumber(inputs.orphanCount, check.comparison, check.threshold);
|
|
26507
26620
|
}
|
|
26508
|
-
function buildViolation(ap) {
|
|
26621
|
+
function buildViolation(ap, concern) {
|
|
26509
26622
|
return {
|
|
26510
26623
|
anti_pattern_id: ap.id,
|
|
26511
26624
|
name: ap.name,
|
|
26512
26625
|
severity: ap.severity,
|
|
26626
|
+
concern,
|
|
26513
26627
|
target_entities: collectTargetEntities(ap.structured_condition),
|
|
26514
26628
|
description: ap.description,
|
|
26515
26629
|
why_it_matters: ap.why_it_matters,
|
|
@@ -29337,7 +29451,7 @@ function serializeSingleWithHeader(doc, opts) {
|
|
|
29337
29451
|
const summary = deriveSummary(product.description);
|
|
29338
29452
|
if (summary) header.summary = summary;
|
|
29339
29453
|
header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
|
|
29340
|
-
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched") {
|
|
29454
|
+
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched" || doc.member_kind === "operating_function") {
|
|
29341
29455
|
header.member_kind = doc.member_kind;
|
|
29342
29456
|
}
|
|
29343
29457
|
header.provenance = buildProvenance(doc, opts);
|
|
@@ -29363,10 +29477,12 @@ function serializePortfolioWithHeader(doc, opts) {
|
|
|
29363
29477
|
const memberKindOf = (p) => p.member_kind ?? "product";
|
|
29364
29478
|
const watchedCount = members.filter((p) => memberKindOf(p) === "watched").length;
|
|
29365
29479
|
const rollupCount = members.filter((p) => memberKindOf(p) === "org_rollup").length;
|
|
29480
|
+
const operatingFunctionCount = members.filter((p) => memberKindOf(p) === "operating_function").length;
|
|
29366
29481
|
header.counts = {
|
|
29367
29482
|
products: members.filter((p) => memberKindOf(p) === "product").length,
|
|
29368
29483
|
...watchedCount > 0 ? { watched_products: watchedCount } : {},
|
|
29369
29484
|
...rollupCount > 0 ? { org_rollups: rollupCount } : {},
|
|
29485
|
+
...operatingFunctionCount > 0 ? { operating_functions: operatingFunctionCount } : {},
|
|
29370
29486
|
product_areas: doc.product_areas?.length ?? 0,
|
|
29371
29487
|
portfolios: doc.portfolios?.length ?? 0,
|
|
29372
29488
|
cross_edges: doc.cross_edges?.length ?? 0
|
|
@@ -29425,7 +29541,9 @@ function normalizeDocument(obj) {
|
|
|
29425
29541
|
edges: raw.edges ?? []
|
|
29426
29542
|
};
|
|
29427
29543
|
const memberKind = header?.member_kind ?? raw.member_kind;
|
|
29428
|
-
if (memberKind === "org_rollup" || memberKind === "watched"
|
|
29544
|
+
if (memberKind === "org_rollup" || memberKind === "watched" || memberKind === "operating_function") {
|
|
29545
|
+
out.member_kind = memberKind;
|
|
29546
|
+
}
|
|
29429
29547
|
if (_integrity) out._integrity = _integrity;
|
|
29430
29548
|
return out;
|
|
29431
29549
|
}
|
|
@@ -29438,7 +29556,7 @@ function isCanonical(text) {
|
|
|
29438
29556
|
}
|
|
29439
29557
|
|
|
29440
29558
|
// src/index.ts
|
|
29441
|
-
var UPG_VERSION = "0.
|
|
29559
|
+
var UPG_VERSION = "0.17.1";
|
|
29442
29560
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
29443
29561
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
29444
29562
|
var UPG_TYPES = getTypes();
|
|
@@ -29508,6 +29626,7 @@ export {
|
|
|
29508
29626
|
BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
|
|
29509
29627
|
CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
|
|
29510
29628
|
CONTENT_DEPTH_WARNING_RULES,
|
|
29629
|
+
COVERAGE_ANTI_PATTERNS,
|
|
29511
29630
|
DEFAULT_ENTITY_EMOJI,
|
|
29512
29631
|
DEFERRED_PROPOSED_BY_CONTRACT,
|
|
29513
29632
|
DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
|
|
@@ -29527,8 +29646,10 @@ export {
|
|
|
29527
29646
|
REFLECT_MODES,
|
|
29528
29647
|
REGISTRY_PRODUCT_ID,
|
|
29529
29648
|
STRATEGY_OUTCOMES_PLAYBOOK,
|
|
29649
|
+
THIN_GRAPH_THRESHOLD,
|
|
29530
29650
|
UPG_ACTIVE_TYPES,
|
|
29531
29651
|
UPG_ANTI_PATTERNS,
|
|
29652
|
+
UPG_ANTI_PATTERN_CONCERNS,
|
|
29532
29653
|
UPG_APPROACHES,
|
|
29533
29654
|
UPG_APPROACHES_BY_ID,
|
|
29534
29655
|
UPG_AREA_TAXONOMY,
|
|
@@ -29567,6 +29688,7 @@ export {
|
|
|
29567
29688
|
UPG_MIGRATIONS,
|
|
29568
29689
|
UPG_PLAYBOOKS,
|
|
29569
29690
|
UPG_POLYMORPHIC_EDGE_KEYS,
|
|
29691
|
+
UPG_PORTFOLIO_KINDS,
|
|
29570
29692
|
UPG_PRODUCT_STAGES,
|
|
29571
29693
|
UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
29572
29694
|
UPG_PROPERTY_MIGRATIONS,
|
|
@@ -29589,6 +29711,7 @@ export {
|
|
|
29589
29711
|
UPG_TYPE_LABELS,
|
|
29590
29712
|
UPG_TYPE_LABELS_MAP,
|
|
29591
29713
|
UPG_TYPE_NAMES,
|
|
29714
|
+
UPG_VALIDATION_PROFILES,
|
|
29592
29715
|
UPG_VALID_CHILDREN,
|
|
29593
29716
|
UPG_VERSION,
|
|
29594
29717
|
UPG_WILDCARD_ENDPOINT,
|
|
@@ -29601,6 +29724,9 @@ export {
|
|
|
29601
29724
|
collectSlugsForType,
|
|
29602
29725
|
compareVersions,
|
|
29603
29726
|
computeBodyChecksum,
|
|
29727
|
+
concernEvaluatedFor,
|
|
29728
|
+
concernFor,
|
|
29729
|
+
concernGatesFor,
|
|
29604
29730
|
describeTreePattern,
|
|
29605
29731
|
edgeCarriesProperties,
|
|
29606
29732
|
evaluateAntiPatterns,
|
|
@@ -29675,6 +29801,7 @@ export {
|
|
|
29675
29801
|
isRegisteredPolymorphicEdge,
|
|
29676
29802
|
isRuntimeStateShapedProperty,
|
|
29677
29803
|
isSubSequenceStep,
|
|
29804
|
+
isThinCoverageAdvisory,
|
|
29678
29805
|
isUPGDocument,
|
|
29679
29806
|
listPropertiesByModifier,
|
|
29680
29807
|
listStatusMigrations,
|
|
@@ -29700,6 +29827,7 @@ export {
|
|
|
29700
29827
|
validateProductStageStrict,
|
|
29701
29828
|
validateUPGDocument,
|
|
29702
29829
|
validateUPGFramework,
|
|
29830
|
+
validationProfileFor,
|
|
29703
29831
|
walkMigrationChainToCanonical
|
|
29704
29832
|
};
|
|
29705
29833
|
//# sourceMappingURL=index.js.map
|