@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.cjs
CHANGED
|
@@ -260,7 +260,7 @@ var UPG_DOMAINS = [
|
|
|
260
260
|
id: "market_intelligence",
|
|
261
261
|
label: "Market Intelligence",
|
|
262
262
|
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).",
|
|
263
|
-
types: ["competitor", "competitor_feature", "market_trend", "market_segment", "competitive_analysis", "classification_axis", "classification_value"]
|
|
263
|
+
types: ["competitor", "competitor_feature", "competitor_signal", "market_trend", "market_segment", "competitive_analysis", "classification_axis", "classification_value"]
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
266
|
id: "user_research",
|
|
@@ -795,6 +795,7 @@ var UPG_ENTITY_META = [
|
|
|
795
795
|
// ── Market Intelligence ──
|
|
796
796
|
{ name: "competitor", type_id: "ent_033", maturity: "stable", since: "0.1.0" },
|
|
797
797
|
{ name: "competitor_feature", type_id: "ent_034", maturity: "stable", since: "0.1.0" },
|
|
798
|
+
{ name: "competitor_signal", type_id: "ent_354", maturity: "proposed", since: "0.10.0" },
|
|
798
799
|
{ name: "market_trend", type_id: "ent_035", maturity: "stable", since: "0.1.0" },
|
|
799
800
|
{ name: "market_segment", type_id: "ent_036", maturity: "stable", since: "0.1.0" },
|
|
800
801
|
{ name: "competitive_analysis", type_id: "ent_037", maturity: "stable", since: "0.1.0" },
|
|
@@ -1306,6 +1307,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
1306
1307
|
// programming concern.
|
|
1307
1308
|
product_contains_competitive_analysis: { forward_verb: "contains", reverse_verb: "contained_by", classification: "hierarchy", source_type: "product", target_type: "competitive_analysis" },
|
|
1308
1309
|
competitor_offers_competitor_feature: { forward_verb: "offers", reverse_verb: "offered_by", classification: "hierarchy", source_type: "competitor", target_type: "competitor_feature" },
|
|
1310
|
+
// 0.10.0 (spec issue #38): parity / rivalry edge. Our `feature` rivals a
|
|
1311
|
+
// `competitor_feature`, carrying the parity assessment as EDGE metadata
|
|
1312
|
+
// (parity_status / quality / is_gap / assessed_on / evidence / confidence) so
|
|
1313
|
+
// "where are we behind, by area?" is one traversal, not a scan over the
|
|
1314
|
+
// free-text `our_equivalent`. `carries_properties` puts the assessment on the
|
|
1315
|
+
// edge; the node `parity_status` stays as a denormalised single-rival cache.
|
|
1316
|
+
// Dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so it also spans our
|
|
1317
|
+
// product graph and a watched competitor-intelligence graph. Distinct from
|
|
1318
|
+
// `competitor_feature_inspires_feature` (ideation lineage, not parity).
|
|
1319
|
+
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 },
|
|
1320
|
+
// 0.10.0 (spec issue #41): competitor_signal is a dated competitor move (feature
|
|
1321
|
+
// launch / pricing change / acquisition / partnership / market entry) emitted by a
|
|
1322
|
+
// competitor and mapped onto our portfolio. `emits` is within the watched graph;
|
|
1323
|
+
// `maps_to_feature` / `surfaces_opportunity` cross from the watched signal into our
|
|
1324
|
+
// product graph (dual-registered as cross-edges in UPG_CROSS_EDGE_TYPES).
|
|
1325
|
+
competitor_emits_competitor_signal: { forward_verb: "emits", reverse_verb: "emitted_by", classification: "hierarchy", source_type: "competitor", target_type: "competitor_signal" },
|
|
1326
|
+
competitor_signal_maps_to_feature: { forward_verb: "maps_to", reverse_verb: "targeted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "feature" },
|
|
1327
|
+
competitor_signal_surfaces_opportunity: { forward_verb: "surfaces", reverse_verb: "prompted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "opportunity" },
|
|
1309
1328
|
competitive_analysis_analyses_competitor: { forward_verb: "analyses", reverse_verb: "analysed_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "competitor" },
|
|
1310
1329
|
competitive_analysis_identifies_market_trend: { forward_verb: "identifies", reverse_verb: "identified_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_trend" },
|
|
1311
1330
|
competitive_analysis_scopes_market_segment: { forward_verb: "scopes", reverse_verb: "scoped_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_segment" },
|
|
@@ -3226,7 +3245,10 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
3226
3245
|
"api_contract_speaks_specification",
|
|
3227
3246
|
"product_exposes_primitive",
|
|
3228
3247
|
"feature_manipulates_primitive",
|
|
3229
|
-
"primitive_stored_as_data_type"
|
|
3248
|
+
"primitive_stored_as_data_type",
|
|
3249
|
+
"feature_rivals_competitor_feature",
|
|
3250
|
+
"competitor_signal_maps_to_feature",
|
|
3251
|
+
"competitor_signal_surfaces_opportunity"
|
|
3230
3252
|
];
|
|
3231
3253
|
var REGISTRY_PRODUCT_ID = "registry";
|
|
3232
3254
|
|
|
@@ -3514,7 +3536,7 @@ var UPG_VALID_CHILDREN = {
|
|
|
3514
3536
|
// each axis hosts its values; values are reusable across the row and column
|
|
3515
3537
|
// dossiers of a 2-axis matrix.
|
|
3516
3538
|
competitive_analysis: ["competitor", "market_trend", "market_segment", "classification_axis"],
|
|
3517
|
-
competitor: ["competitor_feature"],
|
|
3539
|
+
competitor: ["competitor_feature", "competitor_signal"],
|
|
3518
3540
|
classification_axis: ["classification_value"],
|
|
3519
3541
|
// ── UX Research hierarchy ───────────────────────────────────────────────────
|
|
3520
3542
|
research_study: [
|
|
@@ -6369,12 +6391,14 @@ var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
6369
6391
|
"job_step",
|
|
6370
6392
|
"desired_outcome",
|
|
6371
6393
|
"switching_cost",
|
|
6372
|
-
// ── Market Intelligence (
|
|
6394
|
+
// ── Market Intelligence (7 of 8): competitor landscape is reference data;
|
|
6395
|
+
// competitor_signal is an append-only dated event (no phase ladder);
|
|
6373
6396
|
// competitive_analysis shipped PUBLISHING lifecycle in Phase C;
|
|
6374
6397
|
// classification_axis and classification_value are reference taxonomy
|
|
6375
6398
|
// atoms with no state machine. ─
|
|
6376
6399
|
"competitor",
|
|
6377
6400
|
"competitor_feature",
|
|
6401
|
+
"competitor_signal",
|
|
6378
6402
|
"market_trend",
|
|
6379
6403
|
"market_segment",
|
|
6380
6404
|
"classification_axis",
|
|
@@ -9724,8 +9748,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9724
9748
|
classification_value: {
|
|
9725
9749
|
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." },
|
|
9726
9750
|
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']" },
|
|
9727
|
-
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.' }, ]" },
|
|
9728
|
-
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'] }, ]" }
|
|
9751
|
+
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.' }, ]" },
|
|
9752
|
+
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'] }, ]" }
|
|
9729
9753
|
},
|
|
9730
9754
|
// CodeRepositoryProperties: Code repository.
|
|
9731
9755
|
code_repository: {
|
|
@@ -9760,7 +9784,22 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9760
9784
|
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." },
|
|
9761
9785
|
analysis_date: { type: "string", description: 'ISO date conducted. Competitive intelligence decays quickly; track snapshot age. @example "2026-03-15"' },
|
|
9762
9786
|
framework_id: { type: "string", description: 'Framework ID (references `UPGFramework.id`). @example "porter-five-forces", "swot-analysis", "competitive-matrix"' },
|
|
9763
|
-
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.' }, ]" }
|
|
9787
|
+
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.' }, ]" },
|
|
9788
|
+
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"' },
|
|
9789
|
+
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/"' },
|
|
9790
|
+
confidence: {
|
|
9791
|
+
type: "assessment",
|
|
9792
|
+
scale_id: "confidence_5",
|
|
9793
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9794
|
+
properties: {
|
|
9795
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9796
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9797
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9798
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9799
|
+
},
|
|
9800
|
+
required: ["value", "label"]
|
|
9801
|
+
},
|
|
9802
|
+
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"' }
|
|
9764
9803
|
},
|
|
9765
9804
|
// CompetitiveBattleCardProperties: CompetitiveBattleCard entity.
|
|
9766
9805
|
competitive_battle_card: {
|
|
@@ -9773,7 +9812,22 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9773
9812
|
pricing_model: { type: "string", description: 'Pricing model (e.g. "freemium", "per-seat SaaS", "usage-based")' },
|
|
9774
9813
|
website: { type: "string", description: "Public website URL" },
|
|
9775
9814
|
strengths: { type: "string[]", description: "Bulleted factual strengths. Each item is a short statement, not prose. @example ['Real-time multiplayer canvas', 'Generous free tier']" },
|
|
9776
|
-
weaknesses: { type: "string[]", description: "Bulleted factual weaknesses: gaps, friction, or capabilities materially below market. @example ['Weak API query capabilities', 'No mobile companion']" }
|
|
9815
|
+
weaknesses: { type: "string[]", description: "Bulleted factual weaknesses: gaps, friction, or capabilities materially below market. @example ['Weak API query capabilities', 'No mobile companion']" },
|
|
9816
|
+
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"' },
|
|
9817
|
+
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/"' },
|
|
9818
|
+
confidence: {
|
|
9819
|
+
type: "assessment",
|
|
9820
|
+
scale_id: "confidence_5",
|
|
9821
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9822
|
+
properties: {
|
|
9823
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9824
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9825
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9826
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9827
|
+
},
|
|
9828
|
+
required: ["value", "label"]
|
|
9829
|
+
},
|
|
9830
|
+
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"' }
|
|
9777
9831
|
},
|
|
9778
9832
|
// CompetitorFeatureProperties: Competitor feature or capability.
|
|
9779
9833
|
competitor_feature: {
|
|
@@ -9781,7 +9835,43 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9781
9835
|
is_gap: { type: "boolean", description: "Gap in our offering. True when `our_equivalent` is absent or materially inferior." },
|
|
9782
9836
|
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." },
|
|
9783
9837
|
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." },
|
|
9784
|
-
last_updated: { type: "string", description: 'ISO date this assessment was last updated. Competitor feature landscapes change quickly. @example "2026-02-15"' }
|
|
9838
|
+
last_updated: { type: "string", description: 'ISO date this assessment was last updated. Competitor feature landscapes change quickly. @example "2026-02-15"' },
|
|
9839
|
+
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/"' },
|
|
9840
|
+
confidence: {
|
|
9841
|
+
type: "assessment",
|
|
9842
|
+
scale_id: "confidence_5",
|
|
9843
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9844
|
+
properties: {
|
|
9845
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9846
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9847
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9848
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9849
|
+
},
|
|
9850
|
+
required: ["value", "label"]
|
|
9851
|
+
},
|
|
9852
|
+
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"' }
|
|
9853
|
+
},
|
|
9854
|
+
// CompetitorSignalProperties: Competitor signal: a dated competitor move mapped onto our portfolio.
|
|
9855
|
+
competitor_signal: {
|
|
9856
|
+
observed_at: { type: "string", description: 'ISO date-time the move was observed. @example "2026-06-10"' },
|
|
9857
|
+
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." },
|
|
9858
|
+
summary: { type: "string", description: "One-line factual summary of the move (what shipped, not marketing copy)." },
|
|
9859
|
+
impact: { type: "string", enum: ["high", "medium", "low"], description: "Expected impact on our position." },
|
|
9860
|
+
last_updated: { type: "string", description: 'Provenance: ISO date-time this record was last observed or refreshed. @example "2026-06-13"' },
|
|
9861
|
+
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/"' },
|
|
9862
|
+
confidence: {
|
|
9863
|
+
type: "assessment",
|
|
9864
|
+
scale_id: "confidence_5",
|
|
9865
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
9866
|
+
properties: {
|
|
9867
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
9868
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
9869
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
9870
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
9871
|
+
},
|
|
9872
|
+
required: ["value", "label"]
|
|
9873
|
+
},
|
|
9874
|
+
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"' }
|
|
9785
9875
|
},
|
|
9786
9876
|
// ComplianceFrameworkProperties: Compliance framework.
|
|
9787
9877
|
compliance_framework: {
|
|
@@ -11006,7 +11096,21 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11006
11096
|
},
|
|
11007
11097
|
required: ["value", "label"]
|
|
11008
11098
|
},
|
|
11009
|
-
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"' }
|
|
11099
|
+
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"' },
|
|
11100
|
+
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"' },
|
|
11101
|
+
confidence: {
|
|
11102
|
+
type: "assessment",
|
|
11103
|
+
scale_id: "confidence_5",
|
|
11104
|
+
description: "Provenance: how sure we are, on the canonical confidence_5 scale. Carries both a numeric value and a high / medium / low label.",
|
|
11105
|
+
properties: {
|
|
11106
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
11107
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
11108
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
11109
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
11110
|
+
},
|
|
11111
|
+
required: ["value", "label"]
|
|
11112
|
+
},
|
|
11113
|
+
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"' }
|
|
11010
11114
|
},
|
|
11011
11115
|
// MarketingCampaignPlanProperties: Marketing campaign plan.
|
|
11012
11116
|
marketing_campaign_plan: {
|
|
@@ -11782,7 +11886,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11782
11886
|
owner: { type: "string", description: "Owning person or team. Backstage marks this required; strongly recommended." },
|
|
11783
11887
|
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.' },
|
|
11784
11888
|
tags: { type: "string[]", description: 'Free-form filter tags (e.g. ["payments", "critical-path", "team-alpha"]).' },
|
|
11785
|
-
links: { type: "object", description: "Named URLs for documentation, dashboards, runbooks." }
|
|
11889
|
+
links: { type: "object[]", description: "Named URLs for documentation, dashboards, runbooks." }
|
|
11786
11890
|
},
|
|
11787
11891
|
// ServiceBlueprintProperties: Service blueprint.
|
|
11788
11892
|
service_blueprint: {
|
|
@@ -12131,7 +12235,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12131
12235
|
test_plan: {
|
|
12132
12236
|
test_scope: { type: "string", description: 'What the plan covers (e.g. "checkout flow", "auth module", "release 2.4").' },
|
|
12133
12237
|
plan_type: { type: "string", enum: ["release", "regression", "integration", "acceptance", "smoke", "exploratory"], description: "Kind of verification effort this plan governs." },
|
|
12134
|
-
environments: { type: "
|
|
12238
|
+
environments: { type: "string[]", enum: ["local", "ci", "staging", "sandbox", "production_mirror"], description: "Environments the plan exercises (mirrors `TestEnvironmentProperties.env_type`)." },
|
|
12135
12239
|
entry_criteria: { type: "string", description: "Conditions that must hold before execution may begin." },
|
|
12136
12240
|
pass_criteria: { type: "string", description: "Exit / pass criteria determining whether the plan succeeds." }
|
|
12137
12241
|
},
|
|
@@ -18535,6 +18639,7 @@ function valueMatchesType(defType, val) {
|
|
|
18535
18639
|
case "boolean":
|
|
18536
18640
|
return kind === "boolean";
|
|
18537
18641
|
case "string[]":
|
|
18642
|
+
case "object[]":
|
|
18538
18643
|
return kind === "string[]";
|
|
18539
18644
|
case "object":
|
|
18540
18645
|
case "assessment":
|
|
@@ -18645,6 +18750,7 @@ var ENTITY_EMOJI = {
|
|
|
18645
18750
|
competitive_battle_card: "\u2694\uFE0F",
|
|
18646
18751
|
competitor: "\u{1F94A}",
|
|
18647
18752
|
competitor_feature: "\u{1F52C}",
|
|
18753
|
+
competitor_signal: "\u{1F4E1}",
|
|
18648
18754
|
compliance_framework: "\u{1F3DF}\uFE0F",
|
|
18649
18755
|
compliance_requirement: "\u26F2",
|
|
18650
18756
|
constraint: "\u{1F512}",
|
|
@@ -20391,8 +20497,8 @@ var MARKET_COMPETITIVE_PLAYBOOK = {
|
|
|
20391
20497
|
seqStep(
|
|
20392
20498
|
6,
|
|
20393
20499
|
"Moves",
|
|
20394
|
-
["competitive_battle_card"],
|
|
20395
|
-
"
|
|
20500
|
+
["competitor_signal", "competitive_battle_card"],
|
|
20501
|
+
"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.)"
|
|
20396
20502
|
)
|
|
20397
20503
|
]
|
|
20398
20504
|
};
|
|
@@ -21498,7 +21604,36 @@ var UPG_LENSES = [
|
|
|
21498
21604
|
perspective: "Sees the product as something that needs to reach and resonate with people. Audience-aware, message-focused, always asking what makes someone care."
|
|
21499
21605
|
},
|
|
21500
21606
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21501
|
-
// 8.
|
|
21607
|
+
// 8. COMPETITIVE LENS - rivals, their dated moves, and where we stand
|
|
21608
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21609
|
+
{
|
|
21610
|
+
id: "competitive",
|
|
21611
|
+
name: "Competitive",
|
|
21612
|
+
description: "The competitive landscape: rivals, their offerings, dated moves, and where we stand",
|
|
21613
|
+
icon: "swords",
|
|
21614
|
+
visible_domains: ["market_intelligence"],
|
|
21615
|
+
// Parity (#38) and signal (#41) edges reach onto our side of the graph;
|
|
21616
|
+
// surface those endpoints so a parity row or a mapped signal shows both ends.
|
|
21617
|
+
always_show_types: ["feature", "capability", "opportunity"],
|
|
21618
|
+
playbook_id: "playbook:market-competitive",
|
|
21619
|
+
benchmark_domains: ["market_intelligence"],
|
|
21620
|
+
intelligence_prompts: [
|
|
21621
|
+
{
|
|
21622
|
+
condition: "competitors.length === 0",
|
|
21623
|
+
structured_condition: { check: { type: "entity_count", entity_type: "competitor", comparison: "zero" } },
|
|
21624
|
+
message: "No competitors mapped yet. Name the 3 to 7 closest alternatives, including the status quo and DIY."
|
|
21625
|
+
},
|
|
21626
|
+
{
|
|
21627
|
+
condition: "competitor_signals.length === 0",
|
|
21628
|
+
structured_condition: { check: { type: "entity_count", entity_type: "competitor_signal", comparison: "zero" } },
|
|
21629
|
+
message: "No competitor signals captured. Log rivals' dated moves (launches, pricing changes) and map each onto the feature it threatens."
|
|
21630
|
+
}
|
|
21631
|
+
],
|
|
21632
|
+
audience: "Product marketers, strategists, and founders sizing up the field",
|
|
21633
|
+
perspective: "Sees the product through its rivals: who competes, what they ship, the dated moves they make, and where we lead or trail."
|
|
21634
|
+
},
|
|
21635
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21636
|
+
// 9. FULL LENS - everything, canonical vocabulary (default)
|
|
21502
21637
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
21503
21638
|
{
|
|
21504
21639
|
id: "full",
|
|
@@ -21718,7 +21853,7 @@ var USER_RESEARCH_GUIDE = {
|
|
|
21718
21853
|
var MARKET_INTELLIGENCE_GUIDE = {
|
|
21719
21854
|
domain_id: "market_intelligence",
|
|
21720
21855
|
anchor_entity: "competitive_analysis",
|
|
21721
|
-
creation_sequence: ["competitive_analysis", "competitor", "competitor_feature", "market_trend", "market_segment", "classification_axis", "classification_value"],
|
|
21856
|
+
creation_sequence: ["competitive_analysis", "competitor", "competitor_feature", "competitor_signal", "market_trend", "market_segment", "classification_axis", "classification_value"],
|
|
21722
21857
|
patterns: [
|
|
21723
21858
|
{
|
|
21724
21859
|
name: "Competitive Landscape Map",
|
|
@@ -26215,6 +26350,11 @@ var UPG_REGIONS = [
|
|
|
26215
26350
|
type: "competitor_feature",
|
|
26216
26351
|
role: "leaf"
|
|
26217
26352
|
},
|
|
26353
|
+
{
|
|
26354
|
+
type: "competitor_signal",
|
|
26355
|
+
role: "leaf",
|
|
26356
|
+
notes: "a dated competitor move (feature launch, pricing change, ...) emitted by a competitor"
|
|
26357
|
+
},
|
|
26218
26358
|
{
|
|
26219
26359
|
type: "market_trend",
|
|
26220
26360
|
role: "leaf"
|
|
@@ -26246,6 +26386,7 @@ var UPG_REGIONS = [
|
|
|
26246
26386
|
},
|
|
26247
26387
|
intra_edges: [
|
|
26248
26388
|
"competitor_offers_competitor_feature",
|
|
26389
|
+
"competitor_emits_competitor_signal",
|
|
26249
26390
|
"competitive_analysis_analyses_competitor",
|
|
26250
26391
|
"competitive_analysis_dimensioned_by_classification_axis",
|
|
26251
26392
|
"classification_axis_includes_classification_value",
|
|
@@ -28345,6 +28486,9 @@ function serializeSingleWithHeader(doc, opts) {
|
|
|
28345
28486
|
const summary = deriveSummary(product.description);
|
|
28346
28487
|
if (summary) header.summary = summary;
|
|
28347
28488
|
header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
|
|
28489
|
+
if (doc.member_kind === "org_rollup" || doc.member_kind === "watched") {
|
|
28490
|
+
header.member_kind = doc.member_kind;
|
|
28491
|
+
}
|
|
28348
28492
|
header.provenance = buildProvenance(doc, opts);
|
|
28349
28493
|
header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
|
|
28350
28494
|
return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
|
|
@@ -28364,8 +28508,14 @@ function serializePortfolioWithHeader(doc, opts) {
|
|
|
28364
28508
|
};
|
|
28365
28509
|
const summary = deriveSummary(org.description);
|
|
28366
28510
|
if (summary) header.summary = summary;
|
|
28511
|
+
const members = doc.products ?? [];
|
|
28512
|
+
const memberKindOf = (p) => p.member_kind ?? "product";
|
|
28513
|
+
const watchedCount = members.filter((p) => memberKindOf(p) === "watched").length;
|
|
28514
|
+
const rollupCount = members.filter((p) => memberKindOf(p) === "org_rollup").length;
|
|
28367
28515
|
header.counts = {
|
|
28368
|
-
products:
|
|
28516
|
+
products: members.filter((p) => memberKindOf(p) === "product").length,
|
|
28517
|
+
...watchedCount > 0 ? { watched_products: watchedCount } : {},
|
|
28518
|
+
...rollupCount > 0 ? { org_rollups: rollupCount } : {},
|
|
28369
28519
|
product_areas: doc.product_areas?.length ?? 0,
|
|
28370
28520
|
portfolios: doc.portfolios?.length ?? 0,
|
|
28371
28521
|
cross_edges: doc.cross_edges?.length ?? 0
|
|
@@ -28419,6 +28569,8 @@ function normalizeDocument(obj) {
|
|
|
28419
28569
|
nodes: (raw.nodes ?? []).map((n) => repairNodeDrift(n)),
|
|
28420
28570
|
edges: raw.edges ?? []
|
|
28421
28571
|
};
|
|
28572
|
+
const memberKind = header?.member_kind ?? raw.member_kind;
|
|
28573
|
+
if (memberKind === "org_rollup" || memberKind === "watched") out.member_kind = memberKind;
|
|
28422
28574
|
if (_integrity) out._integrity = _integrity;
|
|
28423
28575
|
return out;
|
|
28424
28576
|
}
|
|
@@ -28431,7 +28583,7 @@ function isCanonical(text) {
|
|
|
28431
28583
|
}
|
|
28432
28584
|
|
|
28433
28585
|
// src/index.ts
|
|
28434
|
-
var UPG_VERSION = "0.
|
|
28586
|
+
var UPG_VERSION = "0.10.0";
|
|
28435
28587
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
28436
28588
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
28437
28589
|
var UPG_TYPES = getTypes();
|