@unified-product-graph/core 0.10.6 → 0.10.8

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/CHANGELOG.md CHANGED
@@ -7,6 +7,114 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.10.8] - 2026-06-14
11
+
12
+ **Operational tooling for the property layer: coverage audit, write pre-flight, freshness query.** The 218-edge confidence backfill (0.10.6) needed five manual motions the MCP did not expose, mostly `jq` over `portfolio.upg`. This release lands the three near-term ones from Data's tooling-gaps brief. Read-path plus a non-mutating write flag; no schema or data migration.
13
+
14
+ ### Added
15
+ - **`audit_property_coverage` (new tool, 127 total; local-only).** Given an `edge_type` and the `required_keys` that should be present, returns the portfolio cross-edges that lack any of them (`missing: [{ edge_id, source, target, source_title?, target_title?, missing_keys }]`) plus, by default, the edges whose present values fail the type property schema (`malformed`). The completeness check that distinguishes "I ran the writes" from "the data is actually backfilled," with resolved titles, without a shell.
16
+ - **`dry_run` flag on `create_cross_product_edge` and `batch_create_cross_product_edges`.** Forecasts the write (`would: create | update | unchanged`, and `would_counts` for the batch) without mutating the portfolio document. The pre-flight that makes a large backfill safe to reason about before it runs. Backed by a non-mutating `previewCrossEdge` on the portfolio store that mirrors `addCrossEdge`'s create/update/unchanged decision (including the property-upsert merge) exactly.
17
+
18
+ ### Changed
19
+ - **`list_portfolio_cross_edges` gained a freshness filter.** `older_than_days` (relative to now) and `assessed_before` (absolute ISO date) return the stale set: edges whose `properties.assessed_on` is older than the cutoff, or absent entirely (never assessed counts as stalest). The read path for "which cells need re-checking" — the trigger of a self-updating competitive tier.
20
+
21
+ ## [0.10.7] - 2026-06-14
22
+
23
+ **Makes the classification landscape renderable: a portfolio tree, title resolution, and projected/paginated cross-edge reads.** 0.10.6 made classifications *queryable* (traversal, distribution, idempotent upsert). But nothing on the MCP surface rendered the result as a tree, and the nearest read overflowed the transport cap and returned opaque ids — the trees were only producible by dropping to `jq` over a dumped portfolio file, a path no fresh agent can find. This release closes that discoverability gap. Read-path only; no schema or data migration.
24
+
25
+ ### Added
26
+ - **`get_portfolio_tree` (new tool, 126 total; local-only).** The portfolio-grain counterpart to `get_tree` (which stays product-scoped). Two shapes: `landscape` (a classification axis to its values to the nodes classified at each, every leaf carrying `confidence` / `assessed_on`; anchorable at one axis or value via `from_id`, or the whole portfolio) and `competitor_profile` (one node's position on every axis it has been graded against). Titles resolve from the registry and `instance_of` registrations, so output names entities (`Sitecore`) rather than `p_…/n_…`. The whole-portfolio landscape is counts-only by default (members inline when anchored, or on `include_members: true`) so it stays under the transport cap. Values whose axis is not wired surface in an explicit `unaxed` bucket rather than vanishing.
27
+ - **`list_portfolio_cross_edges` is now agent-usable.** `resolve_titles` (default on) adds `source_title` / `target_title`; `property_include` trims heavy edge properties (e.g. to `confidence` alone); `limit` / `offset` page the flat list with `returned` / `has_more`. A payload guard estimates from edge rows rather than refusing a routine read. The 218-edge matrix now reads back under the cap with titles, without a shell.
28
+
29
+ ### Fixed
30
+ - **`portfolio_digest`'s classification distribution resolves axes by tag as well as edge.** Axis grouping previously used only `classification_axis_includes_classification_value` registry edges, so a graph that linked values to axes by an `axis:<slug>` tag (the common case) reported every value as unaxed. The shared resolver now tries the registry edge first, then the tag, and the digest carries a `render_with: get_portfolio_tree` pointer so the landscape is discoverable, not just present.
31
+
32
+ ### Changed
33
+ - The competitive lens surfaces `get_portfolio_tree` when a classification landscape exists.
34
+
35
+ ## [0.10.6] - 2026-06-13
36
+
37
+ **Closes the classification read/write loop: query traversal, a digest distribution, and idempotent property upsert.** With the classification surface in place (0.10.3) and carrying validated properties (0.10.4), three gaps remained between *writing* a classification and *reading it back*. This release closes them.
38
+
39
+ ### Fixed
40
+ - **`portfolio_query` follows a classify cross-edge to its registry target.** A classify edge crosses from a product (or watched) graph into the registry, so a per-product reader never opened the document holding the target and the query reported `total_edges: 0`. When a cross-edge type is named in `traverse[]`, the query now appends the matching portfolio cross-edges and resolves their registry targets as terminal nodes (`registry/{id}` with its title). Within-graph queries that name no cross type are byte-identical to before.
41
+ - **Cross-product edge writers no longer drop properties on an idempotent hit.** `create_cross_product_edge` and `batch_create_cross_product_edges` previously no-op'd when an edge already existed, silently discarding any new `properties` — which blocked backfilling confidence/evidence onto edges created earlier without them. They now **upsert**: an existing edge with new properties is merged (existing id preserved, no duplicate) and reported as `updated`; an identical re-write is `unchanged`. `create_classification_edge` on a qualified cross-product competitor source now resolves the owning product read-only and types the edge as `competitor_classified_as_classification_value` rather than the polymorphic fallback.
42
+
43
+ ### Added
44
+ - **`portfolio_digest` carries a classification distribution.** Alongside the structural counts, the digest now reports, per classification axis, how many members fall on each value — the shape of the competitive field at a glance. Best-effort: a missing or legacy portfolio document never breaks the digest.
45
+
46
+ No entity, edge, or cross-edge-type change (entities 316, edges 985, cross-edge types 24, local tools 125).
47
+
48
+ ---
49
+
50
+ ## [0.10.5] - 2026-06-13
51
+
52
+ **CLI parity for classification and edge-carried properties.** The classification/edge-property capability had no `upg` command-line surface; a product creator working a competitive analysis in a terminal could not reach what an agent could.
53
+
54
+ ### Added
55
+ - **`upg portfolio classify <node-id> <classification-value-id>`** with `--confidence`, `--assessed-on`, `--rationale`, `--evidence`, and `--node-product`. Routes within-graph vs cross-product automatically and picks the specialised competitor edge type over the polymorphic one when the source is a competitor.
56
+ - **`--properties <json>` on `connect` and `portfolio connect`.** Properties are validated against the edge type's `property_schema` (unknown key or off-scale value rejected at exit) before the edge is written; a no-op when the type declares no schema.
57
+ - **`--source-product` and `--group-by` on `portfolio edges`** for filtering and grouping the cross-edge listing.
58
+
59
+ No entity, edge, or tool-count change (entities 316, edges 985, local tools 125).
60
+
61
+ ---
62
+
63
+ ## [0.10.4] - 2026-06-13
64
+
65
+ **Edge-carried properties become a typed, validated schema; classification edges carry confidence and provenance.** 0.10.0's parity edge already carried its assessment on the edge; this release generalises that into a first-class mechanism and applies it to classification.
66
+
67
+ ### Added
68
+ - **`property_schema` on edge definitions.** An edge type may declare the properties it is allowed to carry, each with its own type (enum, assessment on a named scale, date, provenance mixin). `validateEdgeProperties(type, props)` validates edge-property writes against it exactly as node properties are validated against an entity shape, and `getEdgePropertySchema(type)` exposes the schema for introspection.
69
+ - **`CLASSIFICATION_EDGE_PROPERTY_SCHEMA`.** Classify edges carry a `confidence` assessment on the `confidence_5` scale, an `assessed_on` date, a free-text `rationale`, and `evidence`. A `CLASSIFICATION_CONFIDENCE_MAP` resolves `low`/`medium`/`high` to `confidence_5` assessment values, so a write can name a level and store a structured score.
70
+
71
+ A classification placement is now structured data rather than a sentence in a notes field: *"every competitor placed as agentic with at least medium confidence"* is a filter, not a re-read.
72
+
73
+ ---
74
+
75
+ ## [0.10.3] - 2026-06-13
76
+
77
+ **Classification completion: a polymorphic node classifier and a registry-tier axis model.**
78
+
79
+ ### Added
80
+ - **`classification_axis` and `classification_value` registry entities.** A classification axis owns a set of values (*AI maturity* → *agentic*, *integrated*, *bolt-on*), defined once in the shared registry so every product and watched graph in a portfolio is positioned against the same vocabulary. Linked by `classification_axis_includes_classification_value`.
81
+ - **`node_classified_as_classification_value`**, the polymorphic classify cross-edge for any node, alongside the specialised `competitor_classified_as_classification_value` for the common case of placing a rival.
82
+
83
+ ---
84
+
85
+ ## [0.10.2] - 2026-06-13
86
+
87
+ **A number-to-assessment reshape migration and the registry-canonical classification cross-edge.**
88
+
89
+ ### Added
90
+ - **Value-aware property migration.** A reshape migration lifts a bare numeric property to a structured assessment (`{ value, label, scale_id }`), and migration-drift detection became value-aware so a graph carrying the old shape is flagged and repaired on load rather than silently passing.
91
+ - **Registry-canonical classification cross-edge.** A classify edge can target a canonical `classification_value` in the registry (`{source}/{node} → registry/{value}`), the cross-product form the portfolio tier reads.
92
+
93
+ ---
94
+
95
+ ## [0.10.1] - 2026-06-13
96
+
97
+ **The competitive-intelligence write surface.**
98
+
99
+ ### Added
100
+ - **Parity-edge writer.** Tooling to create `feature_rivals_competitor_feature` edges carrying the parity assessment (status, relative quality, gap flag, assessed-on, evidence, confidence) on the edge.
101
+ - **`member_kind` setter.** Set a portfolio member's kind (`product`, `org_rollup`, `watched`) so a watched competitor graph can be marked as not-to-be-scored against product-management expectations.
102
+
103
+ ---
104
+
105
+ ## [0.10.0] - 2026-06-13
106
+
107
+ **The Competitive Intelligence tier.** Extends the portfolio tier from what an organisation *owns* to what it *watches*: one new entity type, a parity edge family, a role lens, a member kind for graphs you do not own, and a property-type addition for structured competitive records.
108
+
109
+ ### Added
110
+ - **`competitor_signal` entity** (Region 4, Market & Competitive): a single dated competitor move — launch, pricing change, acquisition, partnership, market entry — emitted by a `competitor` via `competitor_emits_competitor_signal`.
111
+ - **Parity and signal cross-edges.** `feature_rivals_competitor_feature` (parity carried on the edge), `competitor_signal_maps_to_feature`, and `competitor_signal_surfaces_opportunity`, dual-registered as within-graph catalog edges and cross-product edges.
112
+ - **`competitive` lens and a competitive-intelligence playbook.** The `competitive` lens foregrounds the single `market_intelligence` domain — rivals, their offerings, their dated moves, and where the product leads or trails.
113
+ - **`member_kind` on portfolio members** (`product` | `org_rollup` | `watched`). A `watched` graph sits inside the same portfolio as the products it competes with without dragging their health: `portfolio_validate` and the coverage scorers scope themselves by `member_kind`.
114
+ - **`object[]` property type and a provenance mixin.** A `competitive_analysis` can carry structured `commitments`/`capabilities` lists, and every competitive record carries `source`, `last_updated`, `observed_by`, and a `confidence` assessment, so a stale machine-polled signal is distinguishable from a fresh hand-verified one.
115
+
116
+ ---
117
+
10
118
  ## [0.9.25] - 2026-06-11
11
119
 
12
120
  **The real fix for the duplicate-write bug: `upg mcp run` was starting TWO servers.** Every mutating call made through the CLI launch (`cli mcp run`, which is how most MCP clients start the server) was applied twice. Root cause: mcp-server's `index.js` is both the library entry (the CLI imports `runMcpServer`) and the bin (it auto-starts when it is the process entrypoint). The CLI bundles that module into its single-file `cli.cjs`, and a bundler rewrites `import.meta.url` to the bundle's own path — so the realpath-only entrypoint guard matched `process.argv[1]` and the inlined branch auto-started a SECOND server alongside the CLI's own `runMcpServer()`. Two servers shared one stdin, so every request was handled by both and every write duplicated. The 0.9.22 to 0.9.24 idempotency work was at the dispatch layer and could not help: the duplication is two whole server processes, whose per-instance ledgers cannot see each other. `node dist/index.js` and `npx @unified-product-graph/mcp-server` launch a single server and were never affected, which is why direct-launch users never saw it.
package/dist/index.cjs CHANGED
@@ -21782,6 +21782,11 @@ var UPG_LENSES = [
21782
21782
  condition: "competitor_signals.length === 0",
21783
21783
  structured_condition: { check: { type: "entity_count", entity_type: "competitor_signal", comparison: "zero" } },
21784
21784
  message: "No competitor signals captured. Log rivals' dated moves (launches, pricing changes) and map each onto the feature it threatens."
21785
+ },
21786
+ {
21787
+ condition: "classification_axes.length > 0",
21788
+ structured_condition: { check: { type: "entity_count", entity_type: "classification_axis", comparison: "nonzero" } },
21789
+ message: `This portfolio has a classification landscape. Render it with get_portfolio_tree({ shape: "landscape" }) (axis to value to graded competitors), or one rival's position with get_portfolio_tree({ shape: "competitor_profile", from_id }).`
21785
21790
  }
21786
21791
  ],
21787
21792
  audience: "Product marketers, strategists, and founders sizing up the field",
@@ -28738,7 +28743,7 @@ function isCanonical(text) {
28738
28743
  }
28739
28744
 
28740
28745
  // src/index.ts
28741
- var UPG_VERSION = "0.10.6";
28746
+ var UPG_VERSION = "0.10.8";
28742
28747
  var UPG_FORMAT_VERSION = "0.4.0";
28743
28748
  var MARKDOWN_FORMAT_VERSION = "0.1";
28744
28749
  var UPG_TYPES = getTypes();