@unified-product-graph/core 0.9.16 → 0.9.18

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,38 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.9.18] - 2026-06-11
11
+
12
+ **The CLI help stops drifting, and an unknown `tree` filter stops lying.** The CLI rendered `--help` from a hand-maintained table divorced from each command's own option definitions, so adding a flag silently left its help stale (that is how `tree --pattern` shipped undocumented in 0.9.17). Separately, `upg tree <unknown>` quietly rendered the whole graph mislabeled as the filter. Both are now structural.
13
+
14
+ ### Added
15
+ - **A help-drift guard.** The command registry is now a single source of truth that the program, the help-safety test, and a new drift test all iterate. The guard asserts that every registered command has a help topic, every long option it declares is documented, and every subcommand is mentioned, so a new command or flag cannot escape help coverage.
16
+
17
+ ### Fixed
18
+ - **Help is back in sync with the commands.** Documented the options the guard flagged across eleven commands: `context --summary`, `verify --no-content-depth`, `query --edge-include` / `--limit`, `prioritise --framework` (which is required), `move --old-edge`, `dedupe --type` / `--dry-run`, `diff --stat`, `gaps --domain`, `import --output` / `--yes`, `init --file` / `--yes`, `install-skills --mode` / `--list` / `--remove`, and `score --slot-role`. The `spec` help now enumerates its full noun catalogue.
19
+ - **`upg tree <unknown-filter>` errors (exit 3)** instead of silently rendering the whole graph under a wrong label. A valid entity type with no instances reports cleanly, and filtering by a domain id is now actually implemented (the help had always claimed it).
20
+
21
+ No entity, domain, region, playbook, framework, edge, or tool-count change (entities 315, edges 980, local tools 121). The work is entirely in the CLI.
22
+
23
+ ---
24
+
25
+ ## [0.9.17] - 2026-06-11
26
+
27
+ **CLI-next: the CLI catches up to the spec, the tree learns its frameworks, and ordering finally means something.** The `upg` CLI had frozen at the 0.8.x command set while the MCP tool surface grew to 121; its `list`/`tree` ordered lexically and re-rendered shared subtrees into noise. This release closes all of it, and folds in the `get_tree` pattern-definition fixes from a post-ship report on a real 304-node graph.
28
+
29
+ ### Added
30
+ - **Meaning-aware node ordering** (`compareNodesWithinType`, SDK). The intra-type sort ladder: explicit order field (`<type>_order` / order / sequence / ...) -> semantic version (title or `version`) -> lifecycle/status phase position -> `created_at` -> numeric-aware locale. A release series now reads `v5.0.0, v5.1.0, ... v5.9.0, v5.10.0, ... v5.30.0, v6.0.0` instead of the lexical `v5.1.0, v5.10.0, ... v5.2.0`. `list`/`tree` and `get_tree` share it.
31
+ - **DAG-honest tree assembly.** A UPG graph is a DAG; a multi-parent node now renders its subtree ONCE and appears as a `shared` reference under its other parents, in both `get_tree` (`stats.shared_refs`) and the CLI `tree` (`↗ shown above`). Nothing is silently dropped, nothing re-explodes, cycles terminate. `assembleTree` moved to the SDK so the local server, cloud server, and CLI share one implementation.
32
+ - **A `delivery` tree pattern** for the roadmap/release region; `UPG_TREE_PATTERNS` is now 8.
33
+ - **CLI tool-parity surface**: the full 0.9.x MCP surface as commands. New groups `spec` (offline catalogue browser, 36 nouns), `query`, `registry`, `portfolio`, `area`, `migrate`; `tree --pattern <id>` (framework-aware trees via the shared assembler); and singletons `move`, `disconnect`, `dedupe`, `clone`, `context`, `log`, `prioritise`, `sync status`, `product update`, `batch`. Six MCP tools are deliberately not mirrored (recorded by ADR): the agent-cognitive `plan`/`reflect`/`trace`, the remote-mutating `apply_pull_changeset`/`push_to_cloud`, and the skill-dev `skill_audit`.
34
+
35
+ ### Changed
36
+ - **`get_tree` pattern definitions corrected (G1-G7)** against the real graph: child slots gain a `required` flag so gaps flag only genuinely-missing required children (no more ~50 false `epic` gaps); the `strategy` pattern reaches the polymorphically-parented bets and auto-flags a bet with no initiative; `okr` extends to the `metric` leaf and `ost` to `hypothesis`/`experiment_plan`; `validation` accepts `experiment` or `experiment_run`; anchor selection picks the candidate that surfaces the most of the pattern, not the first non-empty.
37
+
38
+ No entity, domain, region, playbook, framework, or local-tool count change (entities 315, edges 980, local tools 121, regions 11, frameworks 46). The work is in the CLI, the SDK, and the tree presentation layer.
39
+
40
+ ---
41
+
10
42
  ## [0.9.16] - 2026-06-11
11
43
 
12
44
  **get_tree reaches the cloud, the show-tree skill calls it, and the three "area" taxonomies get a documented cross-walk.** 0.9.15 shipped `get_tree` local-only and left the show-tree skill on its interim hardcoded chains; this release finishes the loop and closes the deferred A4 introspection gap.
package/dist/index.cjs CHANGED
@@ -18408,13 +18408,13 @@ function validateFrameworkScores(nodes, edges, warnings) {
18408
18408
  const divisors = divisorInputs(framework);
18409
18409
  const path = `$.edges[${i}].properties`;
18410
18410
  const scoreObj = score;
18411
- for (const req of requirements) {
18412
- const value = scoreObj[req.property];
18411
+ for (const req2 of requirements) {
18412
+ const value = scoreObj[req2.property];
18413
18413
  if (value === void 0 || value === null) continue;
18414
- const finding = checkScoreValue(req, value, divisors.has(req.property), framework.id);
18414
+ const finding = checkScoreValue(req2, value, divisors.has(req2.property), framework.id);
18415
18415
  if (finding) {
18416
18416
  warnings.push({
18417
- path: `${path}.${req.property}`,
18417
+ path: `${path}.${req2.property}`,
18418
18418
  message: finding,
18419
18419
  rule: "framework-score"
18420
18420
  });
@@ -18434,58 +18434,58 @@ function divisorInputs(framework) {
18434
18434
  return out;
18435
18435
  }
18436
18436
  var EMPTY_STRING_SET = /* @__PURE__ */ new Set();
18437
- function checkScoreValue(req, value, isDivisor, frameworkId) {
18438
- switch (req.type) {
18437
+ function checkScoreValue(req2, value, isDivisor, frameworkId) {
18438
+ switch (req2.type) {
18439
18439
  case "enum": {
18440
- const allowed = req.enum_values ?? [];
18440
+ const allowed = req2.enum_values ?? [];
18441
18441
  if (typeof value !== "string") {
18442
- return `${frameworkId} score "${req.property}" must be one of ${allowed.join(", ")}; got ${describeKind(value)}.`;
18442
+ return `${frameworkId} score "${req2.property}" must be one of ${allowed.join(", ")}; got ${describeKind(value)}.`;
18443
18443
  }
18444
18444
  if (allowed.length > 0 && !allowed.includes(value)) {
18445
- return `${frameworkId} score "${req.property}" has value "${value}" outside its allowed set: ${allowed.join(", ")}.`;
18445
+ return `${frameworkId} score "${req2.property}" has value "${value}" outside its allowed set: ${allowed.join(", ")}.`;
18446
18446
  }
18447
18447
  return null;
18448
18448
  }
18449
18449
  case "assessment": {
18450
18450
  const num = numericScore(value);
18451
18451
  if (num === null) {
18452
- return `${frameworkId} score "${req.property}" must be a number; got ${describeKind(value)}.`;
18452
+ return `${frameworkId} score "${req2.property}" must be a number; got ${describeKind(value)}.`;
18453
18453
  }
18454
- const scale = req.scale_id ? getScale(req.scale_id) : void 0;
18454
+ const scale = req2.scale_id ? getScale(req2.scale_id) : void 0;
18455
18455
  if (scale) {
18456
18456
  if (num < scale.min || num > scale.max) {
18457
- return `${frameworkId} score "${req.property}" is ${num}, outside the ${req.scale_id} scale range [${scale.min}, ${scale.max}].`;
18457
+ return `${frameworkId} score "${req2.property}" is ${num}, outside the ${req2.scale_id} scale range [${scale.min}, ${scale.max}].`;
18458
18458
  }
18459
18459
  } else if (num <= 0) {
18460
- return `${frameworkId} score "${req.property}" must be greater than 0; got ${num}.`;
18460
+ return `${frameworkId} score "${req2.property}" must be greater than 0; got ${num}.`;
18461
18461
  }
18462
18462
  if (isDivisor && num === 0) {
18463
- return `${frameworkId} score "${req.property}" must not be 0 (it is a divisor in the framework's formula).`;
18463
+ return `${frameworkId} score "${req2.property}" must not be 0 (it is a divisor in the framework's formula).`;
18464
18464
  }
18465
18465
  return null;
18466
18466
  }
18467
18467
  case "number": {
18468
18468
  const num = numericScore(value);
18469
18469
  if (num === null) {
18470
- return `${frameworkId} score "${req.property}" must be a number; got ${describeKind(value)}.`;
18470
+ return `${frameworkId} score "${req2.property}" must be a number; got ${describeKind(value)}.`;
18471
18471
  }
18472
18472
  if (isDivisor && num === 0) {
18473
- return `${frameworkId} score "${req.property}" must not be 0 (it is a divisor in the framework's formula).`;
18473
+ return `${frameworkId} score "${req2.property}" must not be 0 (it is a divisor in the framework's formula).`;
18474
18474
  }
18475
18475
  if (num < 0) {
18476
- return `${frameworkId} score "${req.property}" must not be negative; got ${num}.`;
18476
+ return `${frameworkId} score "${req2.property}" must not be negative; got ${num}.`;
18477
18477
  }
18478
18478
  return null;
18479
18479
  }
18480
18480
  case "boolean": {
18481
18481
  if (typeof value !== "boolean") {
18482
- return `${frameworkId} score "${req.property}" must be a boolean; got ${describeKind(value)}.`;
18482
+ return `${frameworkId} score "${req2.property}" must be a boolean; got ${describeKind(value)}.`;
18483
18483
  }
18484
18484
  return null;
18485
18485
  }
18486
18486
  case "string": {
18487
18487
  if (typeof value !== "string") {
18488
- return `${frameworkId} score "${req.property}" must be a string; got ${describeKind(value)}.`;
18488
+ return `${frameworkId} score "${req2.property}" must be a string; got ${describeKind(value)}.`;
18489
18489
  }
18490
18490
  return null;
18491
18491
  }
@@ -19871,33 +19871,38 @@ function buildTypeAliases() {
19871
19871
  var UPG_TYPE_ALIASES = buildTypeAliases();
19872
19872
 
19873
19873
  // src/presentation/tree-patterns.ts
19874
+ var req = (type) => ({ type, required: true });
19875
+ var opt = (type) => ({ type });
19874
19876
  var UPG_TREE_PATTERNS = [
19875
19877
  {
19876
19878
  id: "ost",
19877
19879
  label: "Opportunity Solution Tree",
19878
- description: "A desired outcome branching into the opportunities under it and the solutions that address them (Teresa Torres).",
19880
+ description: "A desired outcome branching into the opportunities under it, the solutions that address them, and the hypotheses + experiment plans that validate them (Teresa Torres).",
19879
19881
  framework_id: "opportunity-solution-tree",
19880
19882
  anchor_type: "outcome",
19881
19883
  fallback_anchors: ["desired_outcome", "opportunity"],
19882
19884
  child_map: {
19883
- outcome: ["opportunity"],
19884
- desired_outcome: ["opportunity"],
19885
- opportunity: ["solution"]
19885
+ outcome: [req("opportunity")],
19886
+ desired_outcome: [req("opportunity")],
19887
+ opportunity: [req("solution")],
19888
+ solution: [opt("hypothesis")],
19889
+ hypothesis: [opt("experiment_plan")]
19886
19890
  },
19887
- natural_depth: 3
19891
+ natural_depth: 5
19888
19892
  },
19889
19893
  {
19890
19894
  id: "okr",
19891
19895
  label: "Objectives and Key Results",
19892
- description: "Strategic themes containing objectives, each measured by its key results (John Doerr).",
19896
+ description: "Strategic themes containing objectives, each measured by its key results and the metric that quantifies them (John Doerr).",
19893
19897
  framework_id: "okr-framework",
19894
19898
  anchor_type: "strategic_theme",
19895
19899
  fallback_anchors: ["objective"],
19896
19900
  child_map: {
19897
- strategic_theme: ["objective"],
19898
- objective: ["key_result"]
19901
+ strategic_theme: [req("objective")],
19902
+ objective: [req("key_result")],
19903
+ key_result: [opt("metric")]
19899
19904
  },
19900
- natural_depth: 3
19905
+ natural_depth: 4
19901
19906
  },
19902
19907
  {
19903
19908
  id: "user",
@@ -19906,54 +19911,55 @@ var UPG_TREE_PATTERNS = [
19906
19911
  anchor_type: "persona",
19907
19912
  fallback_anchors: ["job"],
19908
19913
  child_map: {
19909
- persona: ["job", "need", "desired_outcome"],
19910
- job: ["need", "desired_outcome"]
19914
+ persona: [req("job"), opt("need"), opt("desired_outcome")],
19915
+ job: [opt("need"), opt("desired_outcome")]
19911
19916
  },
19912
19917
  natural_depth: 3
19913
19918
  },
19914
19919
  {
19915
19920
  id: "product",
19916
19921
  label: "Product breakdown",
19917
- description: "The product organised into feature areas, then features, epics, and user stories.",
19922
+ description: "The product organised into feature areas, then features, and the optional epic + user story tiers beneath them.",
19918
19923
  anchor_type: "product",
19919
19924
  fallback_anchors: ["feature_area", "feature"],
19920
19925
  child_map: {
19921
- product: ["feature_area"],
19922
- feature_area: ["feature"],
19923
- feature: ["epic"],
19924
- epic: ["user_story"]
19926
+ product: [opt("feature_area")],
19927
+ feature_area: [req("feature")],
19928
+ feature: [opt("epic")],
19929
+ epic: [opt("user_story")]
19925
19930
  },
19926
19931
  natural_depth: 5
19927
19932
  },
19928
19933
  {
19929
19934
  id: "validation",
19930
19935
  label: "Validation chain",
19931
- description: "A hypothesis through its experiment plan, experiment, run, and the learning it produced.",
19936
+ description: "A hypothesis through its experiment plan, the experiment (or its runs), and the learning it produced.",
19932
19937
  framework_id: "build-measure-learn",
19933
19938
  anchor_type: "hypothesis",
19934
19939
  fallback_anchors: ["experiment_plan", "experiment"],
19935
19940
  child_map: {
19936
- hypothesis: ["experiment_plan"],
19937
- experiment_plan: ["experiment"],
19938
- experiment: ["experiment_run"],
19939
- experiment_run: ["learning"]
19941
+ hypothesis: [req("experiment_plan")],
19942
+ experiment_plan: [req("experiment"), req("experiment_run")],
19943
+ experiment: [opt("experiment_run"), opt("learning")],
19944
+ experiment_run: [opt("learning")]
19940
19945
  },
19941
19946
  natural_depth: 5
19942
19947
  },
19943
19948
  {
19944
19949
  id: "strategy",
19945
19950
  label: "Strategy cascade",
19946
- description: "Vision and mission into the strategic themes (bets), the initiatives that pursue them, and the outcomes they drive. Themes anchor on vision OR the product, whichever the graph wired.",
19951
+ description: "Vision and mission into the strategic themes (bets), the initiatives that pursue them, and the outcomes they drive. Themes are polymorphically parented: they hang off vision, the product, or a strategic pillar, whichever the graph wired.",
19947
19952
  anchor_type: "vision",
19948
19953
  fallback_anchors: ["product", "strategic_theme"],
19949
19954
  child_map: {
19950
- vision: ["mission", "strategic_theme"],
19951
- product: ["strategic_theme"],
19952
- mission: ["strategic_theme"],
19953
- strategic_theme: ["initiative"],
19954
- initiative: ["outcome"]
19955
+ vision: [opt("mission"), opt("strategic_theme")],
19956
+ mission: [opt("strategic_pillar")],
19957
+ strategic_pillar: [opt("strategic_theme")],
19958
+ product: [opt("strategic_theme")],
19959
+ strategic_theme: [req("initiative")],
19960
+ initiative: [opt("outcome")]
19955
19961
  },
19956
- natural_depth: 4
19962
+ natural_depth: 5
19957
19963
  },
19958
19964
  {
19959
19965
  id: "feature_areas",
@@ -19962,9 +19968,26 @@ var UPG_TREE_PATTERNS = [
19962
19968
  anchor_type: "feature_area",
19963
19969
  fallback_anchors: ["feature"],
19964
19970
  child_map: {
19965
- feature_area: ["feature"]
19971
+ feature_area: [opt("feature")]
19966
19972
  },
19967
19973
  natural_depth: 2
19974
+ },
19975
+ {
19976
+ id: "delivery",
19977
+ label: "Delivery roadmap",
19978
+ description: "How product work is scheduled and shipped: the roadmap and its themes/items, the releases they schedule, and the features (with optional epic + user story tiers) they deliver.",
19979
+ anchor_type: "roadmap",
19980
+ fallback_anchors: ["product", "release"],
19981
+ child_map: {
19982
+ product: [opt("roadmap"), opt("release")],
19983
+ roadmap: [opt("roadmap_theme"), opt("roadmap_item"), opt("release")],
19984
+ roadmap_theme: [opt("feature")],
19985
+ roadmap_item: [opt("feature")],
19986
+ release: [opt("feature")],
19987
+ feature: [opt("epic")],
19988
+ epic: [opt("user_story")]
19989
+ },
19990
+ natural_depth: 5
19968
19991
  }
19969
19992
  ];
19970
19993
  var UPG_TREE_PATTERNS_BY_ID = Object.fromEntries(
@@ -28277,7 +28300,7 @@ function isCanonical(text) {
28277
28300
  }
28278
28301
 
28279
28302
  // src/index.ts
28280
- var UPG_VERSION = "0.9.16";
28303
+ var UPG_VERSION = "0.9.18";
28281
28304
  var UPG_FORMAT_VERSION = "0.4.0";
28282
28305
  var MARKDOWN_FORMAT_VERSION = "0.1";
28283
28306
  var UPG_TYPES = getTypes();