@unified-product-graph/core 0.16.1 → 0.17.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/dist/index.cjs +143 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +123 -10
- package/dist/index.d.ts +123 -10
- package/dist/index.js +134 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3174,7 +3174,10 @@ 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";
|
|
3180
3183
|
|
|
@@ -26295,6 +26298,54 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26295
26298
|
severity: "medium",
|
|
26296
26299
|
source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (product discovery)" }
|
|
26297
26300
|
},
|
|
26301
|
+
// ── Operating-function layer (0.17.0): member_kind: operating_function ─────
|
|
26302
|
+
// Carry concern 'operating' (UPG_ANTI_PATTERN_CONCERNS) and are evaluated ONLY
|
|
26303
|
+
// for operating_function graphs — a function a team operates (revenue / success
|
|
26304
|
+
// / finance / people / marketing), not a product it ships. The product-spine
|
|
26305
|
+
// patterns above are not evaluated for that kind (category errors); these assert
|
|
26306
|
+
// the function spine instead.
|
|
26307
|
+
{
|
|
26308
|
+
id: "operating-function-without-north-star",
|
|
26309
|
+
since: "0.17.0",
|
|
26310
|
+
name: "Operating function without a north-star metric",
|
|
26311
|
+
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.",
|
|
26312
|
+
structured_condition: {
|
|
26313
|
+
operator: "and",
|
|
26314
|
+
checks: [
|
|
26315
|
+
{ check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
|
|
26316
|
+
{ check: { type: "entity_count", entity_type: "metric", filter: { property: "designation", value: "north_star" }, comparison: "zero" } }
|
|
26317
|
+
]
|
|
26318
|
+
},
|
|
26319
|
+
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.",
|
|
26320
|
+
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`.",
|
|
26321
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26322
|
+
severity: "high",
|
|
26323
|
+
source: { kind: "fundamental" }
|
|
26324
|
+
},
|
|
26325
|
+
{
|
|
26326
|
+
id: "operating-function-without-operating-content",
|
|
26327
|
+
since: "0.17.0",
|
|
26328
|
+
name: "Operating function without operating content",
|
|
26329
|
+
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.",
|
|
26330
|
+
structured_condition: {
|
|
26331
|
+
operator: "and",
|
|
26332
|
+
checks: [
|
|
26333
|
+
{ check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
|
|
26334
|
+
{ check: { type: "domain_population", domain_id: "sales", comparison: "zero" } },
|
|
26335
|
+
{ check: { type: "domain_population", domain_id: "go_to_market", comparison: "zero" } },
|
|
26336
|
+
{ check: { type: "domain_population", domain_id: "customer_success", comparison: "zero" } },
|
|
26337
|
+
{ check: { type: "domain_population", domain_id: "growth", comparison: "zero" } },
|
|
26338
|
+
{ check: { type: "domain_population", domain_id: "marketing", comparison: "zero" } },
|
|
26339
|
+
{ check: { type: "domain_population", domain_id: "business_model", comparison: "zero" } },
|
|
26340
|
+
{ check: { type: "domain_population", domain_id: "pricing", comparison: "zero" } }
|
|
26341
|
+
]
|
|
26342
|
+
},
|
|
26343
|
+
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.",
|
|
26344
|
+
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`.",
|
|
26345
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26346
|
+
severity: "medium",
|
|
26347
|
+
source: { kind: "fundamental" }
|
|
26348
|
+
},
|
|
26298
26349
|
// ── Foundations layer (0.9.13): portfolio-scoped, registry-aware ──────────
|
|
26299
26350
|
// These read the shared registry + cross-product edges, so they are evaluated
|
|
26300
26351
|
// by portfolio_validate, not the single-graph evaluator. They carry no
|
|
@@ -26335,6 +26386,23 @@ var UPG_ANTI_PATTERNS = [
|
|
|
26335
26386
|
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
26336
26387
|
severity: "low",
|
|
26337
26388
|
source: { kind: "fundamental" }
|
|
26389
|
+
},
|
|
26390
|
+
// ── Operating-function org link (0.17.0): portfolio-scoped ─────────────────
|
|
26391
|
+
// Cross-product detector (in portfolio_validate): an operating_function graph
|
|
26392
|
+
// should reference the org unit it operates under, which lives once in the
|
|
26393
|
+
// rollup's team_org map. Carries no structured_condition — the org link is a
|
|
26394
|
+
// cross-product edge in portfolio.upg, not a single-graph shape.
|
|
26395
|
+
{
|
|
26396
|
+
id: "operating-function-without-org-link",
|
|
26397
|
+
since: "0.17.0",
|
|
26398
|
+
scope: "portfolio",
|
|
26399
|
+
name: "Operating function without an org link",
|
|
26400
|
+
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.",
|
|
26401
|
+
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.",
|
|
26402
|
+
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.",
|
|
26403
|
+
stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
|
|
26404
|
+
severity: "medium",
|
|
26405
|
+
source: { kind: "fundamental" }
|
|
26338
26406
|
}
|
|
26339
26407
|
];
|
|
26340
26408
|
function getAntiPatternById(id) {
|
|
@@ -26347,6 +26415,45 @@ function getAntiPatternsBySeverity(severity) {
|
|
|
26347
26415
|
return UPG_ANTI_PATTERNS.filter((ap) => ap.severity === severity);
|
|
26348
26416
|
}
|
|
26349
26417
|
|
|
26418
|
+
// src/intelligence/validation-profiles.ts
|
|
26419
|
+
var UPG_ANTI_PATTERN_CONCERNS = {
|
|
26420
|
+
// universal — kind-independent graph hygiene + strategy discipline
|
|
26421
|
+
"objective-without-key-results": "universal",
|
|
26422
|
+
"single-domain-graph": "universal",
|
|
26423
|
+
"orphan-loose-thoughts": "universal",
|
|
26424
|
+
// operating — only evaluated for operating_function graphs
|
|
26425
|
+
"operating-function-without-north-star": "operating",
|
|
26426
|
+
"operating-function-without-operating-content": "operating"
|
|
26427
|
+
// everything else defaults to 'product_spine'
|
|
26428
|
+
};
|
|
26429
|
+
function concernFor(antiPatternId) {
|
|
26430
|
+
return UPG_ANTI_PATTERN_CONCERNS[antiPatternId] ?? "product_spine";
|
|
26431
|
+
}
|
|
26432
|
+
var UPG_VALIDATION_PROFILES = {
|
|
26433
|
+
product: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["product_spine", "universal"] },
|
|
26434
|
+
watched: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: [] },
|
|
26435
|
+
org_rollup: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["universal"] },
|
|
26436
|
+
operating_function: { evaluate_concerns: ["universal", "operating"], gating_concerns: ["universal", "operating"] }
|
|
26437
|
+
};
|
|
26438
|
+
function validationProfileFor(kind) {
|
|
26439
|
+
return UPG_VALIDATION_PROFILES[kind ?? "product"] ?? UPG_VALIDATION_PROFILES.product;
|
|
26440
|
+
}
|
|
26441
|
+
function concernEvaluatedFor(kind, concern) {
|
|
26442
|
+
return validationProfileFor(kind).evaluate_concerns.includes(concern);
|
|
26443
|
+
}
|
|
26444
|
+
function concernGatesFor(kind, concern) {
|
|
26445
|
+
return validationProfileFor(kind).gating_concerns.includes(concern);
|
|
26446
|
+
}
|
|
26447
|
+
var COVERAGE_ANTI_PATTERNS = /* @__PURE__ */ new Set([
|
|
26448
|
+
"single-domain-graph",
|
|
26449
|
+
"persona-count-below-stage-benchmark",
|
|
26450
|
+
"competitors-missing-past-validation"
|
|
26451
|
+
]);
|
|
26452
|
+
var THIN_GRAPH_THRESHOLD = 8;
|
|
26453
|
+
function isThinCoverageAdvisory(antiPatternId, totalEntityCount) {
|
|
26454
|
+
return COVERAGE_ANTI_PATTERNS.has(antiPatternId) && totalEntityCount < THIN_GRAPH_THRESHOLD;
|
|
26455
|
+
}
|
|
26456
|
+
|
|
26350
26457
|
// src/intelligence/evaluator.ts
|
|
26351
26458
|
var SEVERITY_ORDER = {
|
|
26352
26459
|
high: 0,
|
|
@@ -26364,8 +26471,10 @@ function evaluateAntiPatterns(inputs, options) {
|
|
|
26364
26471
|
if (inputs.productStage && !ap.stages.includes(inputs.productStage)) {
|
|
26365
26472
|
continue;
|
|
26366
26473
|
}
|
|
26474
|
+
const concern = concernFor(ap.id);
|
|
26475
|
+
if (!concernEvaluatedFor(inputs.memberKind, concern)) continue;
|
|
26367
26476
|
if (evaluateCondition(ap.structured_condition, inputs)) {
|
|
26368
|
-
fires.push(buildViolation(ap));
|
|
26477
|
+
fires.push(buildViolation(ap, concern));
|
|
26369
26478
|
}
|
|
26370
26479
|
}
|
|
26371
26480
|
fires.sort((a, b) => {
|
|
@@ -26434,11 +26543,12 @@ function compareNumber(value, comparison, threshold) {
|
|
|
26434
26543
|
}
|
|
26435
26544
|
}
|
|
26436
26545
|
function evaluateEntityCount(check, inputs) {
|
|
26437
|
-
const
|
|
26546
|
+
const filter = check.filter;
|
|
26438
26547
|
let count = 0;
|
|
26439
|
-
if (
|
|
26440
|
-
|
|
26441
|
-
|
|
26548
|
+
if (filter && typeof filter.property === "string" && typeof filter.value === "string") {
|
|
26549
|
+
count = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.property]?.[filter.value] ?? 0;
|
|
26550
|
+
} else if (filter && typeof filter.status === "string") {
|
|
26551
|
+
count = inputs.countsByTypeAndStatus?.[check.entity_type]?.[filter.status] ?? 0;
|
|
26442
26552
|
} else {
|
|
26443
26553
|
count = inputs.countsByType[check.entity_type] ?? 0;
|
|
26444
26554
|
}
|
|
@@ -26505,11 +26615,12 @@ function evaluateDomainPopulation(check, inputs) {
|
|
|
26505
26615
|
function evaluateOrphanCount(check, inputs) {
|
|
26506
26616
|
return compareNumber(inputs.orphanCount, check.comparison, check.threshold);
|
|
26507
26617
|
}
|
|
26508
|
-
function buildViolation(ap) {
|
|
26618
|
+
function buildViolation(ap, concern) {
|
|
26509
26619
|
return {
|
|
26510
26620
|
anti_pattern_id: ap.id,
|
|
26511
26621
|
name: ap.name,
|
|
26512
26622
|
severity: ap.severity,
|
|
26623
|
+
concern,
|
|
26513
26624
|
target_entities: collectTargetEntities(ap.structured_condition),
|
|
26514
26625
|
description: ap.description,
|
|
26515
26626
|
why_it_matters: ap.why_it_matters,
|
|
@@ -29337,7 +29448,7 @@ function serializeSingleWithHeader(doc, opts) {
|
|
|
29337
29448
|
const summary = deriveSummary(product.description);
|
|
29338
29449
|
if (summary) header.summary = summary;
|
|
29339
29450
|
header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
|
|
29340
|
-
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched") {
|
|
29451
|
+
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched" || doc.member_kind === "operating_function") {
|
|
29341
29452
|
header.member_kind = doc.member_kind;
|
|
29342
29453
|
}
|
|
29343
29454
|
header.provenance = buildProvenance(doc, opts);
|
|
@@ -29363,10 +29474,12 @@ function serializePortfolioWithHeader(doc, opts) {
|
|
|
29363
29474
|
const memberKindOf = (p) => p.member_kind ?? "product";
|
|
29364
29475
|
const watchedCount = members.filter((p) => memberKindOf(p) === "watched").length;
|
|
29365
29476
|
const rollupCount = members.filter((p) => memberKindOf(p) === "org_rollup").length;
|
|
29477
|
+
const operatingFunctionCount = members.filter((p) => memberKindOf(p) === "operating_function").length;
|
|
29366
29478
|
header.counts = {
|
|
29367
29479
|
products: members.filter((p) => memberKindOf(p) === "product").length,
|
|
29368
29480
|
...watchedCount > 0 ? { watched_products: watchedCount } : {},
|
|
29369
29481
|
...rollupCount > 0 ? { org_rollups: rollupCount } : {},
|
|
29482
|
+
...operatingFunctionCount > 0 ? { operating_functions: operatingFunctionCount } : {},
|
|
29370
29483
|
product_areas: doc.product_areas?.length ?? 0,
|
|
29371
29484
|
portfolios: doc.portfolios?.length ?? 0,
|
|
29372
29485
|
cross_edges: doc.cross_edges?.length ?? 0
|
|
@@ -29425,7 +29538,9 @@ function normalizeDocument(obj) {
|
|
|
29425
29538
|
edges: raw.edges ?? []
|
|
29426
29539
|
};
|
|
29427
29540
|
const memberKind = header?.member_kind ?? raw.member_kind;
|
|
29428
|
-
if (memberKind === "org_rollup" || memberKind === "watched"
|
|
29541
|
+
if (memberKind === "org_rollup" || memberKind === "watched" || memberKind === "operating_function") {
|
|
29542
|
+
out.member_kind = memberKind;
|
|
29543
|
+
}
|
|
29429
29544
|
if (_integrity) out._integrity = _integrity;
|
|
29430
29545
|
return out;
|
|
29431
29546
|
}
|
|
@@ -29438,7 +29553,7 @@ function isCanonical(text) {
|
|
|
29438
29553
|
}
|
|
29439
29554
|
|
|
29440
29555
|
// src/index.ts
|
|
29441
|
-
var UPG_VERSION = "0.
|
|
29556
|
+
var UPG_VERSION = "0.17.0";
|
|
29442
29557
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
29443
29558
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
29444
29559
|
var UPG_TYPES = getTypes();
|
|
@@ -29508,6 +29623,7 @@ export {
|
|
|
29508
29623
|
BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
|
|
29509
29624
|
CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
|
|
29510
29625
|
CONTENT_DEPTH_WARNING_RULES,
|
|
29626
|
+
COVERAGE_ANTI_PATTERNS,
|
|
29511
29627
|
DEFAULT_ENTITY_EMOJI,
|
|
29512
29628
|
DEFERRED_PROPOSED_BY_CONTRACT,
|
|
29513
29629
|
DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
|
|
@@ -29527,8 +29643,10 @@ export {
|
|
|
29527
29643
|
REFLECT_MODES,
|
|
29528
29644
|
REGISTRY_PRODUCT_ID,
|
|
29529
29645
|
STRATEGY_OUTCOMES_PLAYBOOK,
|
|
29646
|
+
THIN_GRAPH_THRESHOLD,
|
|
29530
29647
|
UPG_ACTIVE_TYPES,
|
|
29531
29648
|
UPG_ANTI_PATTERNS,
|
|
29649
|
+
UPG_ANTI_PATTERN_CONCERNS,
|
|
29532
29650
|
UPG_APPROACHES,
|
|
29533
29651
|
UPG_APPROACHES_BY_ID,
|
|
29534
29652
|
UPG_AREA_TAXONOMY,
|
|
@@ -29589,6 +29707,7 @@ export {
|
|
|
29589
29707
|
UPG_TYPE_LABELS,
|
|
29590
29708
|
UPG_TYPE_LABELS_MAP,
|
|
29591
29709
|
UPG_TYPE_NAMES,
|
|
29710
|
+
UPG_VALIDATION_PROFILES,
|
|
29592
29711
|
UPG_VALID_CHILDREN,
|
|
29593
29712
|
UPG_VERSION,
|
|
29594
29713
|
UPG_WILDCARD_ENDPOINT,
|
|
@@ -29601,6 +29720,9 @@ export {
|
|
|
29601
29720
|
collectSlugsForType,
|
|
29602
29721
|
compareVersions,
|
|
29603
29722
|
computeBodyChecksum,
|
|
29723
|
+
concernEvaluatedFor,
|
|
29724
|
+
concernFor,
|
|
29725
|
+
concernGatesFor,
|
|
29604
29726
|
describeTreePattern,
|
|
29605
29727
|
edgeCarriesProperties,
|
|
29606
29728
|
evaluateAntiPatterns,
|
|
@@ -29675,6 +29797,7 @@ export {
|
|
|
29675
29797
|
isRegisteredPolymorphicEdge,
|
|
29676
29798
|
isRuntimeStateShapedProperty,
|
|
29677
29799
|
isSubSequenceStep,
|
|
29800
|
+
isThinCoverageAdvisory,
|
|
29678
29801
|
isUPGDocument,
|
|
29679
29802
|
listPropertiesByModifier,
|
|
29680
29803
|
listStatusMigrations,
|
|
@@ -29700,6 +29823,7 @@ export {
|
|
|
29700
29823
|
validateProductStageStrict,
|
|
29701
29824
|
validateUPGDocument,
|
|
29702
29825
|
validateUPGFramework,
|
|
29826
|
+
validationProfileFor,
|
|
29703
29827
|
walkMigrationChainToCanonical
|
|
29704
29828
|
};
|
|
29705
29829
|
//# sourceMappingURL=index.js.map
|