@unified-product-graph/core 0.9.25 → 0.10.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 +170 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +203 -8
- package/dist/index.d.ts +203 -8
- package/dist/index.js +170 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var UPG_DOMAINS = [
|
|
|
52
52
|
id: "market_intelligence",
|
|
53
53
|
label: "Market Intelligence",
|
|
54
54
|
description: "The competitive landscape your product operates in. Competitors and their competitor features map the field. Market trends track industry shifts. Market segments define addressable audiences. Competitive analyses synthesize the full picture. Classification axes and values express the dimensional structure of the landscape (e.g. CMS Architecture \xD7 Editing Paradigm). Informs Strategy (positioning), Go-To-Market (battle cards), and Business Model (differentiation).",
|
|
55
|
-
types: ["competitor", "competitor_feature", "market_trend", "market_segment", "competitive_analysis", "classification_axis", "classification_value"]
|
|
55
|
+
types: ["competitor", "competitor_feature", "competitor_signal", "market_trend", "market_segment", "competitive_analysis", "classification_axis", "classification_value"]
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
id: "user_research",
|
|
@@ -587,6 +587,7 @@ var UPG_ENTITY_META = [
|
|
|
587
587
|
// ── Market Intelligence ──
|
|
588
588
|
{ name: "competitor", type_id: "ent_033", maturity: "stable", since: "0.1.0" },
|
|
589
589
|
{ name: "competitor_feature", type_id: "ent_034", maturity: "stable", since: "0.1.0" },
|
|
590
|
+
{ name: "competitor_signal", type_id: "ent_354", maturity: "proposed", since: "0.10.0" },
|
|
590
591
|
{ name: "market_trend", type_id: "ent_035", maturity: "stable", since: "0.1.0" },
|
|
591
592
|
{ name: "market_segment", type_id: "ent_036", maturity: "stable", since: "0.1.0" },
|
|
592
593
|
{ name: "competitive_analysis", type_id: "ent_037", maturity: "stable", since: "0.1.0" },
|
|
@@ -1098,6 +1099,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
1098
1099
|
// programming concern.
|
|
1099
1100
|
product_contains_competitive_analysis: { forward_verb: "contains", reverse_verb: "contained_by", classification: "hierarchy", source_type: "product", target_type: "competitive_analysis" },
|
|
1100
1101
|
competitor_offers_competitor_feature: { forward_verb: "offers", reverse_verb: "offered_by", classification: "hierarchy", source_type: "competitor", target_type: "competitor_feature" },
|
|
1102
|
+
// 0.10.0 (spec issue #38): parity / rivalry edge. Our `feature` rivals a
|
|
1103
|
+
// `competitor_feature`, carrying the parity assessment as EDGE metadata
|
|
1104
|
+
// (parity_status / quality / is_gap / assessed_on / evidence / confidence) so
|
|
1105
|
+
// "where are we behind, by area?" is one traversal, not a scan over the
|
|
1106
|
+
// free-text `our_equivalent`. `carries_properties` puts the assessment on the
|
|
1107
|
+
// edge; the node `parity_status` stays as a denormalised single-rival cache.
|
|
1108
|
+
// Dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so it also spans our
|
|
1109
|
+
// product graph and a watched competitor-intelligence graph. Distinct from
|
|
1110
|
+
// `competitor_feature_inspires_feature` (ideation lineage, not parity).
|
|
1111
|
+
feature_rivals_competitor_feature: { forward_verb: "rivals", reverse_verb: "is_rivalled_by", classification: "cross-domain", source_type: "feature", target_type: "competitor_feature", carries_properties: true },
|
|
1112
|
+
// 0.10.0 (spec issue #41): competitor_signal is a dated competitor move (feature
|
|
1113
|
+
// launch / pricing change / acquisition / partnership / market entry) emitted by a
|
|
1114
|
+
// competitor and mapped onto our portfolio. `emits` is within the watched graph;
|
|
1115
|
+
// `maps_to_feature` / `surfaces_opportunity` cross from the watched signal into our
|
|
1116
|
+
// product graph (dual-registered as cross-edges in UPG_CROSS_EDGE_TYPES).
|
|
1117
|
+
competitor_emits_competitor_signal: { forward_verb: "emits", reverse_verb: "emitted_by", classification: "hierarchy", source_type: "competitor", target_type: "competitor_signal" },
|
|
1118
|
+
competitor_signal_maps_to_feature: { forward_verb: "maps_to", reverse_verb: "targeted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "feature" },
|
|
1119
|
+
competitor_signal_surfaces_opportunity: { forward_verb: "surfaces", reverse_verb: "prompted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "opportunity" },
|
|
1101
1120
|
competitive_analysis_analyses_competitor: { forward_verb: "analyses", reverse_verb: "analysed_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "competitor" },
|
|
1102
1121
|
competitive_analysis_identifies_market_trend: { forward_verb: "identifies", reverse_verb: "identified_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_trend" },
|
|
1103
1122
|
competitive_analysis_scopes_market_segment: { forward_verb: "scopes", reverse_verb: "scoped_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_segment" },
|
|
@@ -3018,7 +3037,10 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
3018
3037
|
"api_contract_speaks_specification",
|
|
3019
3038
|
"product_exposes_primitive",
|
|
3020
3039
|
"feature_manipulates_primitive",
|
|
3021
|
-
"primitive_stored_as_data_type"
|
|
3040
|
+
"primitive_stored_as_data_type",
|
|
3041
|
+
"feature_rivals_competitor_feature",
|
|
3042
|
+
"competitor_signal_maps_to_feature",
|
|
3043
|
+
"competitor_signal_surfaces_opportunity"
|
|
3022
3044
|
];
|
|
3023
3045
|
var REGISTRY_PRODUCT_ID = "registry";
|
|
3024
3046
|
|
|
@@ -3306,7 +3328,7 @@ var UPG_VALID_CHILDREN = {
|
|
|
3306
3328
|
// each axis hosts its values; values are reusable across the row and column
|
|
3307
3329
|
// dossiers of a 2-axis matrix.
|
|
3308
3330
|
competitive_analysis: ["competitor", "market_trend", "market_segment", "classification_axis"],
|
|
3309
|
-
competitor: ["competitor_feature"],
|
|
3331
|
+
competitor: ["competitor_feature", "competitor_signal"],
|
|
3310
3332
|
classification_axis: ["classification_value"],
|
|
3311
3333
|
// ── UX Research hierarchy ───────────────────────────────────────────────────
|
|
3312
3334
|
research_study: [
|
|
@@ -6161,12 +6183,14 @@ var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
6161
6183
|
"job_step",
|
|
6162
6184
|
"desired_outcome",
|
|
6163
6185
|
"switching_cost",
|
|
6164
|
-
// ── Market Intelligence (
|
|
6186
|
+
// ── Market Intelligence (7 of 8): competitor landscape is reference data;
|
|
6187
|
+
// competitor_signal is an append-only dated event (no phase ladder);
|
|
6165
6188
|
// competitive_analysis shipped PUBLISHING lifecycle in Phase C;
|
|
6166
6189
|
// classification_axis and classification_value are reference taxonomy
|
|
6167
6190
|
// atoms with no state machine. ─
|
|
6168
6191
|
"competitor",
|
|
6169
6192
|
"competitor_feature",
|
|
6193
|
+
"competitor_signal",
|
|
6170
6194
|
"market_trend",
|
|
6171
6195
|
"market_segment",
|
|
6172
6196
|
"classification_axis",
|
|
@@ -9516,8 +9540,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9516
9540
|
classification_value: {
|
|
9517
9541
|
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." },
|
|
9518
9542
|
exemplars: { type: "string[]", description: "Free-text examples of products occupying this value. For queryable occupancy, prefer `competitor` nodes with `classified_as` edges. @example ['Sanity', 'Contentful', 'Hygraph']" },
|
|
9519
|
-
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.' }, ]" },
|
|
9520
|
-
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: ['GROQ', 'GraphQL', 'REST', 'Live Content API', 'Asset CDN'] }, { surface: 'extensibility', bullets: ['Custom input components', 'Studio plugins'] }, ]" }
|
|
9543
|
+
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.' }, ]" },
|
|
9544
|
+
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: ['GROQ', 'GraphQL', 'REST', 'Live Content API', 'Asset CDN'] }, { surface: 'extensibility', bullets: ['Custom input components', 'Studio plugins'] }, ]" }
|
|
9521
9545
|
},
|
|
9522
9546
|
// CodeRepositoryProperties: Code repository.
|
|
9523
9547
|
code_repository: {
|
|
@@ -9552,7 +9576,22 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9552
9576
|
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." },
|
|
9553
9577
|
analysis_date: { type: "string", description: 'ISO date conducted. Competitive intelligence decays quickly; track snapshot age. @example "2026-03-15"' },
|
|
9554
9578
|
framework_id: { type: "string", description: 'Framework ID (references `UPGFramework.id`). @example "porter-five-forces", "swot-analysis", "competitive-matrix"' },
|
|
9555
|
-
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-portable-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.' }, ]" }
|
|
9579
|
+
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-portable-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.' }, ]" },
|
|
9580
|
+
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"' },
|
|
9581
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://www.contentful.com/changelog/"' },
|
|
9582
|
+
confidence: {
|
|
9583
|
+
type: "assessment",
|
|
9584
|
+
scale_id: "confidence_5",
|
|
9585
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9586
|
+
properties: {
|
|
9587
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9588
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9589
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9590
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9591
|
+
},
|
|
9592
|
+
required: ["value", "label"]
|
|
9593
|
+
},
|
|
9594
|
+
observed_by: { type: "string", description: 'Provenance: agent or routine id that last wrote this. Absent when hand-authored, the signal that a human (not a poller) is the last writer. @example "competitor-watch-agent"' }
|
|
9556
9595
|
},
|
|
9557
9596
|
// CompetitiveBattleCardProperties: CompetitiveBattleCard entity.
|
|
9558
9597
|
competitive_battle_card: {
|
|
@@ -9565,7 +9604,22 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9565
9604
|
pricing_model: { type: "string", description: 'Pricing model (e.g. "freemium", "per-seat SaaS", "usage-based")' },
|
|
9566
9605
|
website: { type: "string", description: "Public website URL" },
|
|
9567
9606
|
strengths: { type: "string[]", description: "Bulleted factual strengths. Each item is a short statement, not prose. @example ['Real-time multiplayer canvas', 'Generous free tier']" },
|
|
9568
|
-
weaknesses: { type: "string[]", description: "Bulleted factual weaknesses: gaps, friction, or capabilities materially below market. @example ['Weak API query capabilities', 'No mobile companion']" }
|
|
9607
|
+
weaknesses: { type: "string[]", description: "Bulleted factual weaknesses: gaps, friction, or capabilities materially below market. @example ['Weak API query capabilities', 'No mobile companion']" },
|
|
9608
|
+
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"' },
|
|
9609
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://www.contentful.com/pricing/"' },
|
|
9610
|
+
confidence: {
|
|
9611
|
+
type: "assessment",
|
|
9612
|
+
scale_id: "confidence_5",
|
|
9613
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9614
|
+
properties: {
|
|
9615
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9616
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9617
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9618
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9619
|
+
},
|
|
9620
|
+
required: ["value", "label"]
|
|
9621
|
+
},
|
|
9622
|
+
observed_by: { type: "string", description: 'Provenance: agent or routine id that last wrote this. Absent when hand-authored, the signal that a human (not a poller) is the last writer. @example "competitor-watch-agent"' }
|
|
9569
9623
|
},
|
|
9570
9624
|
// CompetitorFeatureProperties: Competitor feature or capability.
|
|
9571
9625
|
competitor_feature: {
|
|
@@ -9573,7 +9627,43 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9573
9627
|
is_gap: { type: "boolean", description: "Gap in our offering. True when `our_equivalent` is absent or materially inferior." },
|
|
9574
9628
|
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." },
|
|
9575
9629
|
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." },
|
|
9576
|
-
last_updated: { type: "string", description: 'ISO date this assessment was last updated. Competitor feature landscapes change quickly. @example "2026-02-15"' }
|
|
9630
|
+
last_updated: { type: "string", description: 'ISO date this assessment was last updated. Competitor feature landscapes change quickly. @example "2026-02-15"' },
|
|
9631
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://www.contentful.com/changelog/"' },
|
|
9632
|
+
confidence: {
|
|
9633
|
+
type: "assessment",
|
|
9634
|
+
scale_id: "confidence_5",
|
|
9635
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9636
|
+
properties: {
|
|
9637
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9638
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9639
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9640
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9641
|
+
},
|
|
9642
|
+
required: ["value", "label"]
|
|
9643
|
+
},
|
|
9644
|
+
observed_by: { type: "string", description: 'Provenance: agent or routine id that last wrote this. Absent when hand-authored, the signal that a human (not a poller) is the last writer. @example "competitor-watch-agent"' }
|
|
9645
|
+
},
|
|
9646
|
+
// CompetitorSignalProperties: Competitor signal: a dated competitor move mapped onto our portfolio.
|
|
9647
|
+
competitor_signal: {
|
|
9648
|
+
observed_at: { type: "string", description: 'ISO date-time the move was observed. @example "2026-06-10"' },
|
|
9649
|
+
signal_type: { type: "string", enum: ["feature_launch", "pricing_change", "acquisition", "partnership", "market_entry"], description: "Kind of move. `feature_launch` = shipped a feature. `pricing_change` = a plan or tier change. `acquisition` / `partnership` / `market_entry` = strategic moves." },
|
|
9650
|
+
summary: { type: "string", description: "One-line factual summary of the move (what shipped, not marketing copy)." },
|
|
9651
|
+
impact: { type: "string", enum: ["high", "medium", "low"], description: "Expected impact on our position." },
|
|
9652
|
+
last_updated: { type: "string", description: 'Provenance: ISO date-time this record was last observed or refreshed. @example "2026-06-13"' },
|
|
9653
|
+
source: { type: "string", description: 'Provenance: where this was observed. A changelog, pricing, or docs URL, an analyst report, or a research note. @example "https://www.contentful.com/changelog/"' },
|
|
9654
|
+
confidence: {
|
|
9655
|
+
type: "assessment",
|
|
9656
|
+
scale_id: "confidence_5",
|
|
9657
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9658
|
+
properties: {
|
|
9659
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9660
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9661
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9662
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9663
|
+
},
|
|
9664
|
+
required: ["value", "label"]
|
|
9665
|
+
},
|
|
9666
|
+
observed_by: { type: "string", description: 'Provenance: agent or routine id that last wrote this. Absent when hand-authored, the signal that a human (not a poller) is the last writer. @example "competitor-watch-agent"' }
|
|
9577
9667
|
},
|
|
9578
9668
|
// ComplianceFrameworkProperties: Compliance framework.
|
|
9579
9669
|
compliance_framework: {
|
|
@@ -10798,7 +10888,21 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10798
10888
|
},
|
|
10799
10889
|
required: ["value", "label"]
|
|
10800
10890
|
},
|
|
10801
|
-
source: { type: "string", description: 'Source of the trend data: analyst report, research firm, observed behaviour. @example "Gartner Hype Cycle 2025", "Observed in user interviews Q1 2026"' }
|
|
10891
|
+
source: { type: "string", description: 'Source of the trend data: analyst report, research firm, observed behaviour. @example "Gartner Hype Cycle 2025", "Observed in user interviews Q1 2026"' },
|
|
10892
|
+
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"' },
|
|
10893
|
+
confidence: {
|
|
10894
|
+
type: "assessment",
|
|
10895
|
+
scale_id: "confidence_5",
|
|
10896
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
10897
|
+
properties: {
|
|
10898
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
10899
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
10900
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
10901
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
10902
|
+
},
|
|
10903
|
+
required: ["value", "label"]
|
|
10904
|
+
},
|
|
10905
|
+
observed_by: { type: "string", description: 'Provenance: agent or routine id that last wrote this. Absent when hand-authored, the signal that a human (not a poller) is the last writer. @example "competitor-watch-agent"' }
|
|
10802
10906
|
},
|
|
10803
10907
|
// MarketingCampaignPlanProperties: Marketing campaign plan.
|
|
10804
10908
|
marketing_campaign_plan: {
|
|
@@ -11574,7 +11678,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11574
11678
|
owner: { type: "string", description: "Owning person or team. Backstage marks this required; strongly recommended." },
|
|
11575
11679
|
lifecycle: { type: "string", enum: ["experimental", "production", "deprecated"], description: 'Service maturity. Answers "how mature is it?". `experimental` = early-stage. `production` = battle-tested. `deprecated` = being phased out.' },
|
|
11576
11680
|
tags: { type: "string[]", description: 'Free-form filter tags (e.g. ["payments", "critical-path", "team-alpha"]).' },
|
|
11577
|
-
links: { type: "object", description: "Named URLs for documentation, dashboards, runbooks." }
|
|
11681
|
+
links: { type: "object[]", description: "Named URLs for documentation, dashboards, runbooks." }
|
|
11578
11682
|
},
|
|
11579
11683
|
// ServiceBlueprintProperties: Service blueprint.
|
|
11580
11684
|
service_blueprint: {
|
|
@@ -11923,7 +12027,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11923
12027
|
test_plan: {
|
|
11924
12028
|
test_scope: { type: "string", description: 'What the plan covers (e.g. "checkout flow", "auth module", "release 2.4").' },
|
|
11925
12029
|
plan_type: { type: "string", enum: ["release", "regression", "integration", "acceptance", "smoke", "exploratory"], description: "Kind of verification effort this plan governs." },
|
|
11926
|
-
environments: { type: "
|
|
12030
|
+
environments: { type: "string[]", enum: ["local", "ci", "staging", "sandbox", "production_mirror"], description: "Environments the plan exercises (mirrors `TestEnvironmentProperties.env_type`)." },
|
|
11927
12031
|
entry_criteria: { type: "string", description: "Conditions that must hold before execution may begin." },
|
|
11928
12032
|
pass_criteria: { type: "string", description: "Exit / pass criteria determining whether the plan succeeds." }
|
|
11929
12033
|
},
|
|
@@ -18327,6 +18431,7 @@ function valueMatchesType(defType, val) {
|
|
|
18327
18431
|
case "boolean":
|
|
18328
18432
|
return kind === "boolean";
|
|
18329
18433
|
case "string[]":
|
|
18434
|
+
case "object[]":
|
|
18330
18435
|
return kind === "string[]";
|
|
18331
18436
|
case "object":
|
|
18332
18437
|
case "assessment":
|
|
@@ -18437,6 +18542,7 @@ var ENTITY_EMOJI = {
|
|
|
18437
18542
|
competitive_battle_card: "\u2694\uFE0F",
|
|
18438
18543
|
competitor: "\u{1F94A}",
|
|
18439
18544
|
competitor_feature: "\u{1F52C}",
|
|
18545
|
+
competitor_signal: "\u{1F4E1}",
|
|
18440
18546
|
compliance_framework: "\u{1F3DF}\uFE0F",
|
|
18441
18547
|
compliance_requirement: "\u26F2",
|
|
18442
18548
|
constraint: "\u{1F512}",
|
|
@@ -20183,8 +20289,8 @@ var MARKET_COMPETITIVE_PLAYBOOK = {
|
|
|
20183
20289
|
seqStep(
|
|
20184
20290
|
6,
|
|
20185
20291
|
"Moves",
|
|
20186
|
-
["competitive_battle_card"],
|
|
20187
|
-
"
|
|
20292
|
+
["competitor_signal", "competitive_battle_card"],
|
|
20293
|
+
"Capture rivals' dated moves as competitor_signals (launches, pricing changes, acquisitions) and map each onto the feature it threatens. Then look where competitors are weak and trends are strong: that intersection is where your moves live. Arm the team with battle cards that turn each competitor weakness into a position you can win. (Partnership moves belong in the business & GTM playbook, where `partnership` connects.)"
|
|
20188
20294
|
)
|
|
20189
20295
|
]
|
|
20190
20296
|
};
|
|
@@ -21290,7 +21396,36 @@ var UPG_LENSES = [
|
|
|
21290
21396
|
perspective: "Sees the product as something that needs to reach and resonate with people. Audience-aware, message-focused, always asking what makes someone care."
|
|
21291
21397
|
},
|
|
21292
21398
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21293
|
-
// 8.
|
|
21399
|
+
// 8. COMPETITIVE LENS - rivals, their dated moves, and where we stand
|
|
21400
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21401
|
+
{
|
|
21402
|
+
id: "competitive",
|
|
21403
|
+
name: "Competitive",
|
|
21404
|
+
description: "The competitive landscape: rivals, their offerings, dated moves, and where we stand",
|
|
21405
|
+
icon: "swords",
|
|
21406
|
+
visible_domains: ["market_intelligence"],
|
|
21407
|
+
// Parity (#38) and signal (#41) edges reach onto our side of the graph;
|
|
21408
|
+
// surface those endpoints so a parity row or a mapped signal shows both ends.
|
|
21409
|
+
always_show_types: ["feature", "capability", "opportunity"],
|
|
21410
|
+
playbook_id: "playbook:market-competitive",
|
|
21411
|
+
benchmark_domains: ["market_intelligence"],
|
|
21412
|
+
intelligence_prompts: [
|
|
21413
|
+
{
|
|
21414
|
+
condition: "competitors.length === 0",
|
|
21415
|
+
structured_condition: { check: { type: "entity_count", entity_type: "competitor", comparison: "zero" } },
|
|
21416
|
+
message: "No competitors mapped yet. Name the 3 to 7 closest alternatives, including the status quo and DIY."
|
|
21417
|
+
},
|
|
21418
|
+
{
|
|
21419
|
+
condition: "competitor_signals.length === 0",
|
|
21420
|
+
structured_condition: { check: { type: "entity_count", entity_type: "competitor_signal", comparison: "zero" } },
|
|
21421
|
+
message: "No competitor signals captured. Log rivals' dated moves (launches, pricing changes) and map each onto the feature it threatens."
|
|
21422
|
+
}
|
|
21423
|
+
],
|
|
21424
|
+
audience: "Product marketers, strategists, and founders sizing up the field",
|
|
21425
|
+
perspective: "Sees the product through its rivals: who competes, what they ship, the dated moves they make, and where we lead or trail."
|
|
21426
|
+
},
|
|
21427
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21428
|
+
// 9. FULL LENS - everything, canonical vocabulary (default)
|
|
21294
21429
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21295
21430
|
{
|
|
21296
21431
|
id: "full",
|
|
@@ -21510,7 +21645,7 @@ var USER_RESEARCH_GUIDE = {
|
|
|
21510
21645
|
var MARKET_INTELLIGENCE_GUIDE = {
|
|
21511
21646
|
domain_id: "market_intelligence",
|
|
21512
21647
|
anchor_entity: "competitive_analysis",
|
|
21513
|
-
creation_sequence: ["competitive_analysis", "competitor", "competitor_feature", "market_trend", "market_segment", "classification_axis", "classification_value"],
|
|
21648
|
+
creation_sequence: ["competitive_analysis", "competitor", "competitor_feature", "competitor_signal", "market_trend", "market_segment", "classification_axis", "classification_value"],
|
|
21514
21649
|
patterns: [
|
|
21515
21650
|
{
|
|
21516
21651
|
name: "Competitive Landscape Map",
|
|
@@ -26007,6 +26142,11 @@ var UPG_REGIONS = [
|
|
|
26007
26142
|
type: "competitor_feature",
|
|
26008
26143
|
role: "leaf"
|
|
26009
26144
|
},
|
|
26145
|
+
{
|
|
26146
|
+
type: "competitor_signal",
|
|
26147
|
+
role: "leaf",
|
|
26148
|
+
notes: "a dated competitor move (feature launch, pricing change, ...) emitted by a competitor"
|
|
26149
|
+
},
|
|
26010
26150
|
{
|
|
26011
26151
|
type: "market_trend",
|
|
26012
26152
|
role: "leaf"
|
|
@@ -26038,6 +26178,7 @@ var UPG_REGIONS = [
|
|
|
26038
26178
|
},
|
|
26039
26179
|
intra_edges: [
|
|
26040
26180
|
"competitor_offers_competitor_feature",
|
|
26181
|
+
"competitor_emits_competitor_signal",
|
|
26041
26182
|
"competitive_analysis_analyses_competitor",
|
|
26042
26183
|
"competitive_analysis_dimensioned_by_classification_axis",
|
|
26043
26184
|
"classification_axis_includes_classification_value",
|
|
@@ -28137,6 +28278,9 @@ function serializeSingleWithHeader(doc, opts) {
|
|
|
28137
28278
|
const summary = deriveSummary(product.description);
|
|
28138
28279
|
if (summary) header.summary = summary;
|
|
28139
28280
|
header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
|
|
28281
|
+
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched") {
|
|
28282
|
+
header.member_kind = doc.member_kind;
|
|
28283
|
+
}
|
|
28140
28284
|
header.provenance = buildProvenance(doc, opts);
|
|
28141
28285
|
header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
|
|
28142
28286
|
return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
|
|
@@ -28156,8 +28300,14 @@ function serializePortfolioWithHeader(doc, opts) {
|
|
|
28156
28300
|
};
|
|
28157
28301
|
const summary = deriveSummary(org.description);
|
|
28158
28302
|
if (summary) header.summary = summary;
|
|
28303
|
+
const members = doc.products ?? [];
|
|
28304
|
+
const memberKindOf = (p) => p.member_kind ?? "product";
|
|
28305
|
+
const watchedCount = members.filter((p) => memberKindOf(p) === "watched").length;
|
|
28306
|
+
const rollupCount = members.filter((p) => memberKindOf(p) === "org_rollup").length;
|
|
28159
28307
|
header.counts = {
|
|
28160
|
-
products:
|
|
28308
|
+
products: members.filter((p) => memberKindOf(p) === "product").length,
|
|
28309
|
+
...watchedCount > 0 ? { watched_products: watchedCount } : {},
|
|
28310
|
+
...rollupCount > 0 ? { org_rollups: rollupCount } : {},
|
|
28161
28311
|
product_areas: doc.product_areas?.length ?? 0,
|
|
28162
28312
|
portfolios: doc.portfolios?.length ?? 0,
|
|
28163
28313
|
cross_edges: doc.cross_edges?.length ?? 0
|
|
@@ -28211,6 +28361,8 @@ function normalizeDocument(obj) {
|
|
|
28211
28361
|
nodes: (raw.nodes ?? []).map((n) => repairNodeDrift(n)),
|
|
28212
28362
|
edges: raw.edges ?? []
|
|
28213
28363
|
};
|
|
28364
|
+
const memberKind = header?.member_kind ?? raw.member_kind;
|
|
28365
|
+
if (memberKind === "org_rollup" || memberKind === "watched") out.member_kind = memberKind;
|
|
28214
28366
|
if (_integrity) out._integrity = _integrity;
|
|
28215
28367
|
return out;
|
|
28216
28368
|
}
|
|
@@ -28223,7 +28375,7 @@ function isCanonical(text) {
|
|
|
28223
28375
|
}
|
|
28224
28376
|
|
|
28225
28377
|
// src/index.ts
|
|
28226
|
-
var UPG_VERSION = "0.
|
|
28378
|
+
var UPG_VERSION = "0.10.0";
|
|
28227
28379
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
28228
28380
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
28229
28381
|
var UPG_TYPES = getTypes();
|