@unified-product-graph/core 0.22.2 → 0.23.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/README.md +1 -1
- package/dist/index.cjs +41 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -22
- package/dist/index.d.ts +44 -22
- package/dist/index.js +41 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/spec/examples/cms-field-guide.upg +24 -24
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
The TypeScript core for the **Unified Product Graph (UPG)**, an open specification that gives product tools a shared vocabulary for entities, edges, and frameworks.
|
|
9
9
|
|
|
10
|
-
320 entity types across 37 domains.
|
|
10
|
+
320 entity types across 37 domains. 1042 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
package/dist/index.cjs
CHANGED
|
@@ -750,7 +750,7 @@ var UPG_DOMAINS = [
|
|
|
750
750
|
{
|
|
751
751
|
id: "foundations",
|
|
752
752
|
label: "Foundations",
|
|
753
|
-
description: "The shared specifications and foundational primitives a product organisation stewards or implements everywhere. Specifications are governed specs (query languages, protocols, data formats, encodings) like
|
|
753
|
+
description: "The shared specifications and foundational primitives a product organisation stewards or implements everywhere. Specifications are governed specs (query languages, protocols, data formats, encodings) like NQL or Structured Text; primitives are the compositional units those specs define (a block, a reference, a query value). Both are registry-hostable canonicals that products implement, expose, or conform to. Distinct from a product feature: a specification has no single owner, no P&L, and no buyer; it is the rulebook many products point at.",
|
|
754
754
|
types: ["specification", "primitive", "operating_lifecycle", "operating_stage"]
|
|
755
755
|
}
|
|
756
756
|
];
|
|
@@ -1684,6 +1684,17 @@ var UPG_EDGE_CATALOG = {
|
|
|
1684
1684
|
bug_affects_feature: { forward_verb: "affects", reverse_verb: "affected_by", classification: "cross-domain", source_type: "bug", target_type: "feature" },
|
|
1685
1685
|
roadmap_item_references_feature: { forward_verb: "references", reverse_verb: "referenced_by", classification: "cross-domain", source_type: "roadmap_item", target_type: "feature" },
|
|
1686
1686
|
feature_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "feature", target_type: "task" },
|
|
1687
|
+
// Epic-level mirrors of the feature→task / feature→bug hierarchy (feedback
|
|
1688
|
+
// df99026a). A `feature` is the flat planning root, but an `epic` is a body of
|
|
1689
|
+
// work under it, and real imported ticket sets (Linear/Jira) carry bugs and
|
|
1690
|
+
// pure engineering tasks that belong to ONE epic, not the feature as a whole.
|
|
1691
|
+
// Without these, such tickets had to be mislabelled `user_story` (the only
|
|
1692
|
+
// epic child) to keep the epic grouping. These are additive twins of the
|
|
1693
|
+
// feature-level edges (same verbs/classification), so `pickCanonicalEdge` /
|
|
1694
|
+
// `resolveContainmentEdge('epic', 'task'|'bug')` and the adapter import path
|
|
1695
|
+
// resolve them with no further wiring.
|
|
1696
|
+
epic_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "epic", target_type: "task" },
|
|
1697
|
+
epic_affected_by_bug: { forward_verb: "affected_by", reverse_verb: "affects", classification: "hierarchy", source_type: "epic", target_type: "bug" },
|
|
1687
1698
|
task_has_subtask: { forward_verb: "has_subtask", reverse_verb: "is_subtask_of", classification: "hierarchy", source_type: "task", target_type: "task" },
|
|
1688
1699
|
// ── Planning cadence (0.20.0) ────────────────────────────────────────────────
|
|
1689
1700
|
// The cadence axis. E1 self-nests the interval (a program-increment contains
|
|
@@ -3460,6 +3471,15 @@ var UPG_EDGE_CATALOG = {
|
|
|
3460
3471
|
// by another team's graph within the portfolio (0.17.3 locked predicate).
|
|
3461
3472
|
objective_depends_on_dependency: { forward_verb: "depends_on", reverse_verb: "dependency_of", classification: "cross-domain", source_type: "objective", target_type: "dependency", cross_product_eligible: true },
|
|
3462
3473
|
dependency_blocks_objective: { forward_verb: "blocks", reverse_verb: "blocked_by", classification: "cross-domain", source_type: "dependency", target_type: "objective", cross_product_eligible: true },
|
|
3474
|
+
// The RESOLVING side, distinct from the two blocking edges above.
|
|
3475
|
+
// `dependency_blocks_objective` names an objective the dependency holds up (the
|
|
3476
|
+
// waiting side); this names the objective whose completion CLEARS the
|
|
3477
|
+
// dependency (the providing side). In a portfolio, a dependency in one
|
|
3478
|
+
// product's graph points at the objective in another product's graph that
|
|
3479
|
+
// delivers the awaited work, instead of recording it only in the free-text
|
|
3480
|
+
// `resolution` property. cross_product_eligible — the resolving objective
|
|
3481
|
+
// lives in another product's graph.
|
|
3482
|
+
dependency_resolved_by_objective: { forward_verb: "resolved_by", reverse_verb: "resolves", classification: "cross-domain", source_type: "dependency", target_type: "objective", cross_product_eligible: true },
|
|
3463
3483
|
// objective / initiative → strategic_question. A planning doc's "Risks & Open
|
|
3464
3484
|
// Questions" section names unresolved coordination questions the plan is
|
|
3465
3485
|
// exposed to (who owns a capability across teams after a reorg). The
|
|
@@ -3987,7 +4007,10 @@ var UPG_VALID_CHILDREN = {
|
|
|
3987
4007
|
// task_implements_user_story edge.
|
|
3988
4008
|
feature: ["epic", "bug", "task"],
|
|
3989
4009
|
feature_area: ["feature", "feature_area"],
|
|
3990
|
-
epic
|
|
4010
|
+
// epic owns user_story (the templated promise) and, mirroring feature, may
|
|
4011
|
+
// also directly contain bug/task — heterogeneous imported tickets that belong
|
|
4012
|
+
// to one epic rather than the feature as a whole (feedback df99026a).
|
|
4013
|
+
epic: ["user_story", "bug", "task"],
|
|
3991
4014
|
user_story: ["acceptance_criterion"],
|
|
3992
4015
|
task: ["task"],
|
|
3993
4016
|
// planning_cycle (0.20.0): the cadence axis self-nests (a program-increment
|
|
@@ -10717,9 +10740,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10717
10740
|
// ClassificationValueProperties: ClassificationValue: a value on a classification axis.
|
|
10718
10741
|
classification_value: {
|
|
10719
10742
|
rationale: { type: "string", description: "Short paragraph: why this value earns its own row/column. Longer narrative belongs in `summary_md` or attached `content_piece` nodes." },
|
|
10720
|
-
exemplars: { type: "string[]", description: "Free-text examples of products occupying this value. For queryable occupancy, prefer `competitor` nodes with `classified_as` edges. @example ['
|
|
10743
|
+
exemplars: { type: "string[]", description: "Free-text examples of products occupying this value. For queryable occupancy, prefer `competitor` nodes with `classified_as` edges. @example ['Nimbus', 'Larch', 'Prism']" },
|
|
10721
10744
|
commitments: { type: "object[]", description: "The N (typically 2\u20134) load-bearing commitments that define this category. Each commitment is a structural axis; removing it changes the category. Pairs with `capabilities`: commitments are the *load-bearing definition*; capabilities are the *surface offering*. @example [ { name: 'Typed content graph', description: 'Schemas in code; references as edges; content is structured data.' }, { name: 'Real-time backend', description: 'Live queries, CRDT collaboration, sub-second propagation.' }, { name: 'Embeddable studio', description: 'Editor is a library hosted inside the team app, not an external portal.' }, ]" },
|
|
10722
|
-
capabilities: { type: "object[]", description: "Structured capability bullets across the six canonical surfaces. Each surface appears at most once per `classification_value`. Pairs with `commitments`: capabilities describe what the category *offers*; commitments describe what *defines* it. @example [ { surface: 'delivery', bullets: ['
|
|
10745
|
+
capabilities: { type: "object[]", description: "Structured capability bullets across the six canonical surfaces. Each surface appears at most once per `classification_value`. Pairs with `commitments`: capabilities describe what the category *offers*; commitments describe what *defines* it. @example [ { surface: 'delivery', bullets: ['NQL', 'GraphQL', 'REST', 'Live Sync API', 'Asset CDN'] }, { surface: 'extensibility', bullets: ['Custom input components', 'editor plugins'] }, ]" }
|
|
10723
10746
|
},
|
|
10724
10747
|
// CodeRepositoryProperties: Code repository.
|
|
10725
10748
|
code_repository: {
|
|
@@ -10754,9 +10777,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10754
10777
|
analysis_type: { type: "string", enum: ["feature_comparison", "positioning", "swot", "pricing"], description: "Type of analysis. `feature_comparison` = side-by-side matrix. `positioning` = competitor positioning relative to each other. `swot` = strengths, weaknesses, opportunities, threats. `pricing` = pricing structure comparison." },
|
|
10755
10778
|
analysis_date: { type: "string", description: 'ISO date conducted. Competitive intelligence decays quickly; track snapshot age. @example "2026-03-15"' },
|
|
10756
10779
|
framework_id: { type: "string", description: 'Framework ID (references `UPGFramework.id`). @example "porter-five-forces", "swot-analysis", "competitive-matrix"' },
|
|
10757
|
-
empty_cells: { type: "object[]", description: "Empty cells in a 2-axis classification matrix that earn explicit commentary. Only the strategically interesting cells. Opportunity-kind cells are the most valuable; they identify unoccupied strategic space. Each entry references two `classification_value` nodes by id (one from each `classification_axis` child of this `competitive_analysis`). `validate_graph` enforces that the refs resolve to `classification_value` nodes whose parents are distinct `classification_axis` instances. See `ClassificationValueProperties.commitments` for the inverse \"occupied cell\" structural-definition shape. @example [ { axis_a_value_ref: 'val-git-based', axis_b_value_ref: 'val-
|
|
10780
|
+
empty_cells: { type: "object[]", description: "Empty cells in a 2-axis classification matrix that earn explicit commentary. Only the strategically interesting cells. Opportunity-kind cells are the most valuable; they identify unoccupied strategic space. Each entry references two `classification_value` nodes by id (one from each `classification_axis` child of this `competitive_analysis`). `validate_graph` enforces that the refs resolve to `classification_value` nodes whose parents are distinct `classification_axis` instances. See `ClassificationValueProperties.commitments` for the inverse \"occupied cell\" structural-definition shape. @example [ { axis_a_value_ref: 'val-git-based', axis_b_value_ref: 'val-structured-text', rationale_kind: 'opportunity', rationale_md: 'No technical reason; only adoption inertia. Watching brief.' }, { axis_a_value_ref: 'val-composable', axis_b_value_ref: 'val-wysiwyg', rationale_kind: 'structural', rationale_md: 'Composable rejects HTML blobs; WYSIWYG requires them.' }, ]" },
|
|
10758
10781
|
last_updated: { type: "string", description: 'Provenance: ISO date-time this record was last observed or refreshed. Distinct from `analysis_date` (when the analysis was conducted). @example "2026-06-13"' },
|
|
10759
|
-
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://
|
|
10782
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://docs.larch.example/changelog/"' },
|
|
10760
10783
|
confidence: {
|
|
10761
10784
|
type: "assessment",
|
|
10762
10785
|
scale_id: "confidence_5",
|
|
@@ -10784,7 +10807,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10784
10807
|
strengths: { type: "string[]", description: "Bulleted factual strengths. Each item is a short statement, not prose. @example ['Real-time multiplayer canvas', 'Generous free tier']" },
|
|
10785
10808
|
weaknesses: { type: "string[]", description: "Bulleted factual weaknesses: gaps, friction, or capabilities materially below market. @example ['Weak API query capabilities', 'No mobile companion']" },
|
|
10786
10809
|
last_updated: { type: "string", description: 'Provenance: ISO date-time this record was last observed or refreshed. Lets a stale record be told apart from a fresh one. @example "2026-06-13"' },
|
|
10787
|
-
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://
|
|
10810
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://docs.larch.example/pricing/"' },
|
|
10788
10811
|
confidence: {
|
|
10789
10812
|
type: "assessment",
|
|
10790
10813
|
scale_id: "confidence_5",
|
|
@@ -10806,7 +10829,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10806
10829
|
quality: { type: "string", enum: ["better", "same", "worse", "missing"], description: "Quality comparison. `better` = ours is meaningfully superior. `same` = roughly equivalent. `worse` = theirs is meaningfully superior. `missing` = we have no equivalent." },
|
|
10807
10830
|
parity_status: { type: "string", enum: ["ahead", "behind", "parity", "unique_to_us", "unique_to_them"], description: "Parity. More granular than `quality`; captures whether the gap is offensive or defensive. `ahead` = we lead. `behind` = they lead. `parity` = equivalent. `unique_to_us` / `unique_to_them` = only one side offers it." },
|
|
10808
10831
|
last_updated: { type: "string", description: 'ISO date this assessment was last updated. Competitor feature landscapes change quickly. @example "2026-02-15"' },
|
|
10809
|
-
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://
|
|
10832
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://docs.larch.example/changelog/"' },
|
|
10810
10833
|
confidence: {
|
|
10811
10834
|
type: "assessment",
|
|
10812
10835
|
scale_id: "confidence_5",
|
|
@@ -10832,7 +10855,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10832
10855
|
from_value: { type: "string", description: "Reclassification only. The prior `classification_value` id the competitor was classified as before this move (the superseded cell). Absent for a first-time classification (nothing was superseded)." },
|
|
10833
10856
|
to_value: { type: "string", description: "Reclassification only. The new `classification_value` id the competitor is classified as after this move (the cell the new classify edge points at)." },
|
|
10834
10857
|
last_updated: { type: "string", description: 'Provenance: ISO date-time this record was last observed or refreshed. @example "2026-06-13"' },
|
|
10835
|
-
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://
|
|
10858
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://docs.larch.example/changelog/"' },
|
|
10836
10859
|
confidence: {
|
|
10837
10860
|
type: "assessment",
|
|
10838
10861
|
scale_id: "confidence_5",
|
|
@@ -10972,7 +10995,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10972
10995
|
},
|
|
10973
10996
|
// DashboardProperties: Dashboard entity.
|
|
10974
10997
|
dashboard: {
|
|
10975
|
-
tool: { type: "string", enum: ["looker", "amplitude", "mixpanel", "posthog", "custom"], description: "Analytics tool hosting this dashboard" },
|
|
10998
|
+
tool: { type: "string", enum: ["looker", "amplitude", "mixpanel", "posthog", "omni", "custom"], description: "Analytics tool hosting this dashboard" },
|
|
10976
10999
|
url: { type: "string", description: "URL to the live dashboard", modifier: "volatile" },
|
|
10977
11000
|
audience: { type: "string", description: "Intended audience for this dashboard" },
|
|
10978
11001
|
element_count: { type: "number", description: "Number of widgets or panels on the dashboard", modifier: "derived" },
|
|
@@ -12263,7 +12286,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12263
12286
|
// OperatingLifecycleProperties: An operating_lifecycle: a canonical, ordered (often cyclic) operating process
|
|
12264
12287
|
operating_lifecycle: {
|
|
12265
12288
|
cyclic: { type: "boolean", description: "True if the process loops (e.g. Analyze \u2192 Extend \u2192 Plan). The sequence is fully expressed by the stages' `stage_order`; `cyclic` adds the wrap from the last stage back to the first." },
|
|
12266
|
-
source: { type: "string", description: 'Origin of the canonical model (e.g. "
|
|
12289
|
+
source: { type: "string", description: 'Origin of the canonical model (e.g. "a published content-ops lifecycle"). Optional provenance; promote to an edge if it names a real `specification`/`document`.' }
|
|
12267
12290
|
},
|
|
12268
12291
|
// OperatingStageProperties: An operating_stage: one ordered stage of an `operating_lifecycle`. A product's
|
|
12269
12292
|
operating_stage: {
|
|
@@ -23131,6 +23154,12 @@ var PRODUCT_SPEC_GUIDE = {
|
|
|
23131
23154
|
entity_types: ["feature_area", "feature", "epic", "user_story", "task"],
|
|
23132
23155
|
edge_chain: ["feature_area_contains_feature", "feature_decomposed_into_epic", "epic_specified_by_user_story", "task_implements_user_story"]
|
|
23133
23156
|
},
|
|
23157
|
+
{
|
|
23158
|
+
name: "Epic Work Items",
|
|
23159
|
+
description: "A bug or a pure engineering/infra task that belongs to one specific epic (not the feature as a whole) attaches directly to that epic, mirroring how task and bug attach at the feature level. Use this when importing a real tracker (Linear/Jira) whose tickets are heterogeneous: keep genuine stories as user_story, but let bugs, spikes, and engineering tasks nest under their epic with their true type instead of being mislabelled to preserve the grouping.",
|
|
23160
|
+
entity_types: ["epic", "task", "bug"],
|
|
23161
|
+
edge_chain: ["epic_decomposes_into_task", "epic_affected_by_bug"]
|
|
23162
|
+
},
|
|
23134
23163
|
{
|
|
23135
23164
|
name: "Planning Cadence",
|
|
23136
23165
|
description: "The cadence axis. Create a planning_cycle (a sprint, iteration, quarter, or PI), nest finer cycles inside a coarse one, schedule the stories that flow through it, and scope the objectives the cycle serves. Scheduling and scoping are deliberate links, not containment: a story keeps its feature/epic parent.",
|
|
@@ -29947,7 +29976,7 @@ function isCanonical(text) {
|
|
|
29947
29976
|
}
|
|
29948
29977
|
|
|
29949
29978
|
// src/index.ts
|
|
29950
|
-
var UPG_VERSION = "0.
|
|
29979
|
+
var UPG_VERSION = "0.23.1";
|
|
29951
29980
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
29952
29981
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
29953
29982
|
var UPG_TYPES = getTypes();
|