@unified-product-graph/core 0.19.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,36 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.20.1] - 2026-07-04
11
+
12
+ **Three edges close the alignment-sheet seam: an initiative can now reach directly into the OKR ladder and the roadmap, and a strategic pillar gets its own north-star metric.** Composed flat region surfaces spanning org/team/product altitudes surfaced two gaps in the strategic cascade: an initiative's only path to a key result or a roadmap theme ran through its parent `strategic_theme`, and `strategic_pillar` — durable, multi-year, org-wide — had no measuring metric of its own, unlike every other rung of the cascade (`objective_measured_by_metric`, `key_result_quantified_by_metric`). Additive; no breaking change.
13
+
14
+ ### Added
15
+ - **`initiative_advances_key_result`** — `initiative` → `key_result`, causal (intra-strategy execution, not cross-domain), cross-product-eligible. Extends the curated OKR laddering DAG that `objective_measured_by_metric` / `key_result_quantified_by_metric` are already part of.
16
+ - **`initiative_delivered_via_roadmap_theme`** — `initiative` → `roadmap_theme`, semantic, within-graph. Mirrors `strategic_theme_realised_by_roadmap_theme`: a cross-reference between the strategy spine and the roadmap spine, not containment, so initiatives can reach the roadmap without routing through their parent theme.
17
+ - **`strategic_pillar_measured_by_metric`** — `strategic_pillar` → `metric`, hierarchy, cross-product-eligible. Mirrors `objective_measured_by_metric` one level up the cascade; `metric` joins `strategic_pillar`'s valid children.
18
+
19
+ ---
20
+
21
+ ## [0.20.0] - 2026-07-04
22
+
23
+ **A cadence layer for the product-delivery region: one self-nesting `planning_cycle` entity models sprints, iterations, quarters, program increments, and cooldowns, so a Jira or Linear export round-trips its time-boxes losslessly.** The delivery region could describe what ships (feature → epic → story → task) and what governs it (release, roadmap, theme), but had no home for the interval work flows *through*. This release mints `planning_cycle` — a named, dated, self-nesting interval discriminated by `cadence_kind` (period / iteration / buffer) rather than a type per methodology — plus the edges that schedule work into it and scope objectives and themes to it. It also closes the smaller structural gaps a Jira ∪ Linear import hits: polymorphic issue links, a dual-band `workflow_state` that preserves an imported tool's raw states without displacing the canonical `status`, and the task-level planning fields lifted onto `user_story`. Additive; no breaking change.
24
+
25
+ ### Added
26
+ - **`planning_cycle` entity** (`proposed`) — the cadence axis. A self-nesting container in the `product_spec` domain / `product_delivery` region, `portfolio_shared`, lifecycle `planned → active → closed`. Properties: `cadence_kind` (period / iteration / buffer), `cadence_label`, `starts_on`, `ends_on`, `sequence`, `goal`, `appetite`. One type with a `cadence_kind` discriminator spans sprint / iteration / quarter / program-increment / cooldown; self-nesting handles the granularity ladder (a PI contains iterations; a cycle contains its cooldown).
27
+ - **Core cadence edges** — `planning_cycle_contains_planning_cycle` (self-nesting containment), `objective_scoped_to_planning_cycle` and `strategic_theme_scoped_to_planning_cycle` (OKR / theme cycle-scoping, provisional cross-scope), `planning_cycle_schedules_user_story` (deliberate-only work scheduling — a story keeps its feature/epic containment parent), and `product_runs_planning_cycle` (top-level attach).
28
+ - **Polymorphic work-item issue links** — `work_item_blocks_work_item`, `work_item_relates_to_work_item`, `work_item_duplicates_work_item` over the work-item set {feature, epic, user_story, task, bug}, all deliberate-only.
29
+ - **Dual-band `workflow_state`** — a freeform `workflow_state` plus an optional `workflow_state_category` on the work-item entities, so an imported custom workflow round-trips losslessly without displacing canonical `status` as the reasoning axis.
30
+ - **`planning-cycle-without-scheduled-work`** anti-pattern (low severity) — a cadence box that neither schedules work nor nests a sub-cycle.
31
+
32
+ ### Changed
33
+ - **`user_story` gains `priority`, `effort`, `assignee`, `due_date`** — the story is now a first-class plannable unit alongside `task`, matching the tools where the story / issue is the estimated-and-assigned atom.
34
+
35
+ ### Deprecated
36
+ - **`strategic_theme.time_horizon`** — promote the bounded period to a `planning_cycle` node linked with `strategic_theme_scoped_to_planning_cycle`. The property is kept and readable; removal is a later major. `strategic_pillar.time_horizon` (a durable, open-ended pillar horizon, not a dated cycle) stays as-is.
37
+
38
+ ---
39
+
10
40
  ## [0.19.0] - 2026-07-04
11
41
 
12
42
  **The MCP surface consolidates from 139 tools to 93 (+ 5 prompts): the static-spec introspection cluster folds into four faceted reads, and five procedural routers become skills.** The spec catalogue had accreted 45 near-identical `list_*` / `get_*` reads plus five routers — a surface an agent had to scan linearly before it could choose. This release collapses the reads into four facets and retires the routers, leaving the 89 write and live-graph-read verbs untouched. A parity gate (`retired-tools.json` + a byte-equality test wired as a prepublish stage) proves every retired tool's data is still reachable and identical through the facets, on both the local and cloud servers — which licenses a clean break with no deprecation window. Both servers' instructions gain a methodology preamble (the SEE → THINK → ACT → LEARN working loop) so the surface conveys *how to work*, not only *what exists*. Breaking. Local 139 → 93; cloud 100 → 54.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  The TypeScript core for the **Unified Product Graph (UPG)**, an open specification that gives product tools a shared vocabulary for entities, edges, and frameworks.
9
9
 
10
- 319 entity types across 37 domains. 1026 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
10
+ 320 entity types across 37 domains. 1037 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
11
11
 
12
12
  ## Install
13
13
 
package/dist/index.cjs CHANGED
@@ -347,7 +347,7 @@ var UPG_DOMAINS = [
347
347
  {
348
348
  id: "product_spec",
349
349
  label: "Product Specification",
350
- description: "What you are building and shipping. Feature areas group related capabilities. Features, epics, and user stories break work down. Acceptance criteria define done. Tasks and bugs track execution. Releases and changelogs mark what shipped. Roadmaps and roadmap items plan what comes next. Roadmap themes group roadmap work around the customer problem it solves, one level down from the strategic themes in Strategy. Translates Strategy into Engineering and tracks delivery through Program Management.",
350
+ description: "What you are building and shipping. Feature areas group related capabilities. Features, epics, and user stories break work down. Acceptance criteria define done. Tasks and bugs track execution. Releases and changelogs mark what shipped. Roadmaps and roadmap items plan what comes next. Roadmap themes group roadmap work around the customer problem it solves, one level down from the strategic themes in Strategy. Planning cycles are the cadence axis: the named, dated, self-nesting intervals (sprint, iteration, quarter, program increment, cooldown) that work is scheduled through. Translates Strategy into Engineering and tracks delivery through Program Management.",
351
351
  types: [
352
352
  "feature",
353
353
  "feature_area",
@@ -360,7 +360,8 @@ var UPG_DOMAINS = [
360
360
  "roadmap",
361
361
  "roadmap_item",
362
362
  "roadmap_theme",
363
- "changelog"
363
+ "changelog",
364
+ "planning_cycle"
364
365
  ]
365
366
  },
366
367
  {
@@ -938,6 +939,14 @@ var UPG_ENTITY_META = [
938
939
  { name: "theme", type_id: "ent_080", maturity: "deprecated", since: "0.1.0", deprecated_in: "0.9.0", replacement: "roadmap_theme" },
939
940
  { name: "roadmap_theme", type_id: "ent_351", maturity: "stable", since: "0.1.0" },
940
941
  { name: "changelog", type_id: "ent_081", maturity: "stable", since: "0.1.0" },
942
+ // (0.20.0) The cadence axis: a named, dated interval that work flows through
943
+ // and which nests (a program-increment contains iterations; a cycle contains
944
+ // its cooldown). One self-nesting type with a `cadence_kind` discriminator
945
+ // spans sprint / iteration / quarter / PI / cooldown rather than minting a
946
+ // type per methodology. `portfolio_shared` because a coarse cycle is an
947
+ // org-shared interval an objective in a rollup graph and a story in a product
948
+ // graph both schedule into.
949
+ { name: "planning_cycle", type_id: "ent_358", maturity: "proposed", since: "0.20.0", portfolio_shared: true },
941
950
  // ── Engineering ──
942
951
  { name: "bounded_context", type_id: "ent_082", maturity: "stable", since: "0.1.0" },
943
952
  { name: "service", type_id: "ent_083", maturity: "stable", since: "0.1.0" },
@@ -1554,6 +1563,10 @@ var UPG_EDGE_CATALOG = {
1554
1563
  strategic_pillar_enables_capability: { forward_verb: "enables", reverse_verb: "enabled_by", classification: "hierarchy", source_type: "strategic_pillar", target_type: "capability" },
1555
1564
  strategic_pillar_delivers_value_stream: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "hierarchy", source_type: "strategic_pillar", target_type: "value_stream" },
1556
1565
  strategic_pillar_decided_via_decision: { forward_verb: "decided_via", reverse_verb: "decided_for", classification: "hierarchy", source_type: "strategic_pillar", target_type: "decision" },
1566
+ // 0.20.1: the durable pillar's own north-star, mirroring objective_measured_by_metric
1567
+ // one level up the cascade. A strategic_pillar is multi-year and org-wide, so its
1568
+ // measuring metric is portfolio-shared just like the pillar itself.
1569
+ strategic_pillar_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "strategic_pillar", target_type: "metric", cross_product_eligible: true },
1557
1570
  strategic_theme_pursues_initiative: { forward_verb: "pursues", reverse_verb: "pursued_under", classification: "hierarchy", source_type: "strategic_theme", target_type: "initiative", cross_product_eligible: true },
1558
1571
  // v0.9.0 (UPG-660): the soft bridge from the annual strategy focus area to the
1559
1572
  // roadmap grouping that realises it. Semantic, NOT hierarchy: strategic_theme and
@@ -1586,6 +1599,17 @@ var UPG_EDGE_CATALOG = {
1586
1599
  strategic_theme_measured_by_key_result: { forward_verb: "measured_by", reverse_verb: "measures", classification: "causal", source_type: "strategic_theme", target_type: "key_result", cross_product_eligible: true },
1587
1600
  strategic_theme_contains_objective: { forward_verb: "contains", reverse_verb: "rolls_up_to", classification: "hierarchy", source_type: "strategic_theme", target_type: "objective", cross_product_eligible: true },
1588
1601
  initiative_assumes_assumption: { forward_verb: "assumes", reverse_verb: "assumed_by", classification: "hierarchy", source_type: "initiative", target_type: "assumption" },
1602
+ // 0.20.1: the G6 alignment-sheet bridge edges — an initiative reaches directly
1603
+ // into the OKR ladder and the roadmap, rather than only via its parent
1604
+ // strategic_theme. `advances_key_result` is intra-strategy execution (both
1605
+ // ends sit in the strategy domain), so causal not cross-domain; the KR is
1606
+ // portfolio-shared like the initiative, so the pair is cross-product-eligible.
1607
+ initiative_advances_key_result: { forward_verb: "advances", reverse_verb: "advanced_by", classification: "causal", source_type: "initiative", target_type: "key_result", cross_product_eligible: true },
1608
+ // `delivered_via_roadmap_theme` mirrors strategic_theme_realised_by_roadmap_theme:
1609
+ // a semantic cross-reference between the strategy spine and the roadmap spine,
1610
+ // not containment. roadmap_theme is not portfolio-shared, so this stays
1611
+ // within-graph (no cross_product_eligible flag).
1612
+ initiative_delivered_via_roadmap_theme: { forward_verb: "delivered_via", reverse_verb: "delivers", classification: "semantic", source_type: "initiative", target_type: "roadmap_theme" },
1589
1613
  initiative_drives_outcome: { forward_verb: "drives", reverse_verb: "driven_by", classification: "cross-domain", source_type: "initiative", target_type: "outcome", cross_product_eligible: true },
1590
1614
  capability_enables_value_stream: { forward_verb: "enables", reverse_verb: "enabled_by", classification: "cross-domain", source_type: "capability", target_type: "value_stream" },
1591
1615
  // v0.5.2 (UPG-528): three capability-anchored edges that complete the
@@ -1621,6 +1645,8 @@ var UPG_EDGE_CATALOG = {
1621
1645
  product_builds_feature: { forward_verb: "builds", reverse_verb: "built_by", classification: "hierarchy", source_type: "product", target_type: "feature" },
1622
1646
  product_ships_via_release: { forward_verb: "ships_via", reverse_verb: "ships", classification: "hierarchy", source_type: "product", target_type: "release" },
1623
1647
  product_plans_via_roadmap: { forward_verb: "plans_via", reverse_verb: "plans_for", classification: "hierarchy", source_type: "product", target_type: "roadmap" },
1648
+ // (0.20.0) top-level attach for the cadence axis, mirroring product_plans_via_roadmap.
1649
+ product_runs_planning_cycle: { forward_verb: "runs", reverse_verb: "run_by", classification: "hierarchy", source_type: "product", target_type: "planning_cycle" },
1624
1650
  product_categorises_by_roadmap_theme: { forward_verb: "categorises_by", reverse_verb: "categorises", classification: "hierarchy", source_type: "product", target_type: "roadmap_theme" },
1625
1651
  feature_area_contains_feature: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature" },
1626
1652
  feature_area_contains_feature_area: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature_area" },
@@ -1659,6 +1685,33 @@ var UPG_EDGE_CATALOG = {
1659
1685
  roadmap_item_references_feature: { forward_verb: "references", reverse_verb: "referenced_by", classification: "cross-domain", source_type: "roadmap_item", target_type: "feature" },
1660
1686
  feature_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "feature", target_type: "task" },
1661
1687
  task_has_subtask: { forward_verb: "has_subtask", reverse_verb: "is_subtask_of", classification: "hierarchy", source_type: "task", target_type: "task" },
1688
+ // ── Planning cadence (0.20.0) ────────────────────────────────────────────────
1689
+ // The cadence axis. E1 self-nests the interval (a program-increment contains
1690
+ // iterations; a cycle contains its cooldown), mirroring team_contains_team.
1691
+ planning_cycle_contains_planning_cycle: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "planning_cycle", target_type: "planning_cycle" },
1692
+ // E3: scheduling work into a cycle is a deliberate authoring act, not a
1693
+ // containment nesting — user_story keeps its feature/epic parent and is merely
1694
+ // referenced by the cycle. deliberate_only so the generic-inference chokepoints
1695
+ // never auto-materialise it.
1696
+ planning_cycle_schedules_user_story: { forward_verb: "schedules", reverse_verb: "scheduled_in", classification: "semantic", source_type: "planning_cycle", target_type: "user_story", deliberate_only: true },
1697
+ // E2: the OKR cycle-scoping anchor. objective -> planning_cycle. planning_cycle
1698
+ // is portfolio_shared, so this passes the cross-scope gate and ships as
1699
+ // PROVISIONAL (allowed cross-product with a write-time warning, self-maintaining,
1700
+ // never added to the curated set) rather than carrying a cross_product_eligible
1701
+ // flag, which would make it curated. Eligibility is permission, not obligation.
1702
+ objective_scoped_to_planning_cycle: { forward_verb: "scoped_to", reverse_verb: "scopes", classification: "semantic", source_type: "objective", target_type: "planning_cycle" },
1703
+ // F: the promoted `strategic_theme.time_horizon`. Same shape as E2 — a theme's
1704
+ // bounded period becomes a scoping edge onto a shared, dated, nestable interval.
1705
+ // Provisional (planning_cycle portfolio_shared). The property stays @deprecated.
1706
+ strategic_theme_scoped_to_planning_cycle: { forward_verb: "scoped_to", reverse_verb: "scopes", classification: "semantic", source_type: "strategic_theme", target_type: "planning_cycle" },
1707
+ // Gap 2 — polymorphic issue links over the work-item set {feature, epic,
1708
+ // user_story, task, bug}. Endpoint-polymorphic (node -> node, registered in
1709
+ // UPG_POLYMORPHIC_EDGE_KEYS) so one edge covers every level teams link at; the
1710
+ // `work_item_` key names the intended semantic domain. All deliberate_only: an
1711
+ // issue link is authored, never inferred from hierarchy.
1712
+ work_item_blocks_work_item: { forward_verb: "blocks", reverse_verb: "blocked_by", classification: "causal", source_type: "node", target_type: "node", deliberate_only: true },
1713
+ work_item_relates_to_work_item: { forward_verb: "relates_to", reverse_verb: "relates_to", classification: "semantic", source_type: "node", target_type: "node", deliberate_only: true },
1714
+ work_item_duplicates_work_item: { forward_verb: "duplicates", reverse_verb: "duplicated_by", classification: "semantic", source_type: "node", target_type: "node", deliberate_only: true },
1662
1715
  // 2.3 Legal Domain
1663
1716
  product_owned_by_legal_entity: { forward_verb: "owned_by", reverse_verb: "owns", classification: "hierarchy", source_type: "product", target_type: "legal_entity" },
1664
1717
  legal_entity_protects_ip_asset: { forward_verb: "protects", reverse_verb: "protected_by", classification: "hierarchy", source_type: "legal_entity", target_type: "ip_asset" },
@@ -3442,7 +3495,13 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
3442
3495
  // Framework exercises (an exercise can include any entity type)
3443
3496
  "framework_exercise_includes_node",
3444
3497
  // Universal classification (any node classified against a classification_value)
3445
- "node_classified_as_classification_value"
3498
+ "node_classified_as_classification_value",
3499
+ // Work-item issue links (0.20.0): endpoint-polymorphic over the work-item set
3500
+ // {feature, epic, user_story, task, bug}. The `work_item_` key names the
3501
+ // intended semantic domain; the endpoints are the `node` wildcard.
3502
+ "work_item_blocks_work_item",
3503
+ "work_item_relates_to_work_item",
3504
+ "work_item_duplicates_work_item"
3446
3505
  ];
3447
3506
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
3448
3507
  function isPolymorphicEdge(key) {
@@ -3632,6 +3691,7 @@ var UPG_VALID_CHILDREN = {
3632
3691
  "release",
3633
3692
  "roadmap",
3634
3693
  "roadmap_theme",
3694
+ "planning_cycle",
3635
3695
  // Engineering
3636
3696
  "bounded_context",
3637
3697
  "code_repository",
@@ -3809,7 +3869,11 @@ var UPG_VALID_CHILDREN = {
3809
3869
  // That success-measurement asymmetry is the load-bearing distinction.
3810
3870
  vision: ["mission"],
3811
3871
  mission: ["strategic_pillar"],
3812
- strategic_pillar: ["strategic_theme", "capability", "value_stream", "decision"],
3872
+ // 0.20.1: metric added as a strategic_pillar child, backing
3873
+ // strategic_pillar_measured_by_metric (hierarchy) — the pillar's own
3874
+ // north-star, mirrored from objective: ['key_result', 'metric', ...] one
3875
+ // level down the cascade.
3876
+ strategic_pillar: ["strategic_theme", "capability", "value_stream", "decision", "metric"],
3813
3877
  // v0.5.4 (UPG-511): objectives are the specific quarterly bets *within* a
3814
3878
  // strategic theme. The theme is the multi-quarter focus area; objectives are
3815
3879
  // subordinate. Pairs with `strategic_theme_contains_objective` in the edge
@@ -3900,6 +3964,13 @@ var UPG_VALID_CHILDREN = {
3900
3964
  epic: ["user_story"],
3901
3965
  user_story: ["acceptance_criterion"],
3902
3966
  task: ["task"],
3967
+ // planning_cycle (0.20.0): the cadence axis self-nests (a program-increment
3968
+ // contains iterations; a cycle contains its cooldown) via
3969
+ // planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
3970
+ // child: scheduling work into a cycle is the semantic, deliberate-only
3971
+ // planning_cycle_schedules_user_story edge, so a story keeps its feature/epic
3972
+ // containment parent and is merely referenced by the cycle.
3973
+ planning_cycle: ["planning_cycle"],
3903
3974
  // ── Engineering hierarchy ───────────────────────────────────────────────────
3904
3975
  bounded_context: [
3905
3976
  "service",
@@ -6543,6 +6614,16 @@ var SPECIFICATION_LIFECYCLE = {
6543
6614
  }
6544
6615
  ]
6545
6616
  };
6617
+ var PLANNING_CYCLE_LIFECYCLE = {
6618
+ entity_type: "planning_cycle",
6619
+ initial_phase: "planned",
6620
+ terminal_phases: ["closed"],
6621
+ phases: [
6622
+ { id: "planned", label: "Planned", description: "The cycle is being shaped: dates set, goal written, work scheduled into it. It has not started yet.", transitions_to: ["active"] },
6623
+ { id: "active", label: "Active", description: "The team is inside the interval; work is flowing through it.", transitions_to: ["closed"] },
6624
+ { id: "closed", label: "Closed", description: "The interval has ended. Scheduled work has shipped, carried over, or been dropped. May reopen if the team extends or reruns it.", transitions_to: ["active"] }
6625
+ ]
6626
+ };
6546
6627
  var UPG_LIFECYCLES = [
6547
6628
  // Product (root)
6548
6629
  PRODUCT_LIFECYCLE,
@@ -6586,6 +6667,7 @@ var UPG_LIFECYCLES = [
6586
6667
  TASK_LIFECYCLE,
6587
6668
  BUG_LIFECYCLE,
6588
6669
  ROADMAP_ITEM_LIFECYCLE,
6670
+ PLANNING_CYCLE_LIFECYCLE,
6589
6671
  IP_ASSET_LIFECYCLE,
6590
6672
  CONTRACT_LIFECYCLE,
6591
6673
  DESIGN_CONCEPT_LIFECYCLE,
@@ -10768,7 +10850,9 @@ var UPG_PROPERTY_SCHEMA = {
10768
10850
  priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Urgency relative to other work. Independent of `bug_severity` (a critical bug can have low priority if rare)." },
10769
10851
  assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
10770
10852
  due_date: { type: "string", description: "ISO date due. Often tied to a release gate or SLA." },
10771
- labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." }
10853
+ labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." },
10854
+ workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
10855
+ workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning (a source "In Review" and "QA" might both map to a verification phase). Optional companion to `workflow_state`: it lets a graph reason over an imported custom workflow without promoting the raw label to `status`.' }
10772
10856
  },
10773
10857
  // BuildArtifactProperties: Build artifact.
10774
10858
  build_artifact: {
@@ -11422,7 +11506,9 @@ var UPG_PROPERTY_SCHEMA = {
11422
11506
  priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Task-level priority" },
11423
11507
  owner: { type: "string", description: "Responsible person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
11424
11508
  start_date: { type: "string", description: "ISO date work begins" },
11425
- target_date: { type: "string", description: "ISO date work completes" }
11509
+ target_date: { type: "string", description: "ISO date work completes" },
11510
+ workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
11511
+ workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning (a source "In Review" and "QA" might both map to a verification phase). Optional companion to `workflow_state`: it lets a graph reason over an imported custom workflow without promoting the raw label to `status`.' }
11426
11512
  },
11427
11513
  // ErrorBudgetProperties: Error budget.
11428
11514
  error_budget: {
@@ -11602,7 +11688,9 @@ var UPG_PROPERTY_SCHEMA = {
11602
11688
  owner: { type: "string", description: "Responsible person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
11603
11689
  start_date: { type: "string", description: "ISO date work begins" },
11604
11690
  target_date: { type: "string", description: "ISO date work completes" },
11605
- health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" }
11691
+ health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" },
11692
+ workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
11693
+ workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning (a source "In Review" and "QA" might both map to a verification phase). Optional companion to `workflow_state`: it lets a graph reason over an imported custom workflow without promoting the raw label to `status`.' }
11606
11694
  },
11607
11695
  // FeatureAreaProperties: A structural grouping of related features within a product.
11608
11696
  feature_area: {
@@ -12522,6 +12610,16 @@ var UPG_PROPERTY_SCHEMA = {
12522
12610
  conversion_rate: { type: "number", description: "Percentage of deals that advance from this stage", modifier: "snapshot" },
12523
12611
  avg_days_in_stage: { type: "number", description: "Average number of days deals spend in this stage" }
12524
12612
  },
12613
+ // PlanningCycleProperties: Planning cycle: a named, dated interval work flows through, which nests.
12614
+ planning_cycle: {
12615
+ cadence_kind: { type: "string", enum: ["period", "iteration", "buffer"], description: "Methodology-neutral granularity of this interval. `period` is a coarse container (quarter / PI / OKR-cycle scale); `iteration` is a fine execution box (sprint / cycle); `buffer` is between-box slack (cooldown). Required: it is the discriminator that lets one type stand in for every methodology." },
12616
+ cadence_label: { type: "string", description: 'The source methodology term verbatim ("sprint", "cycle", "PI", "quarter", "cooldown"). The dual-band label: `cadence_kind` is the canonical granularity reasoned over; `cadence_label` preserves what the team actually calls it.' },
12617
+ starts_on: { type: "string", description: "ISO date the interval opens. A cycle is concretely dated, unlike a coarse `time_horizon` label." },
12618
+ ends_on: { type: "string", description: "ISO date the interval closes." },
12619
+ sequence: { type: "number", description: "The cycle / iteration number (e.g. Sprint 47, PI 3)." },
12620
+ goal: { type: "string", description: "The interval's goal or focus: what this cadence box is for." },
12621
+ appetite: { type: "string", description: 'Shape Up appetite: the fixed time budget a cycle is willing to spend on a bet (e.g. "6 weeks", "2 weeks").' }
12622
+ },
12525
12623
  // PlaybookProperties: Customer success playbook.
12526
12624
  playbook: {
12527
12625
  playbook_type: { type: "string", enum: ["onboarding", "expansion", "renewal", "rescue", "other"], description: "Scenario this playbook addresses" },
@@ -13073,7 +13171,7 @@ var UPG_PROPERTY_SCHEMA = {
13073
13171
  // StrategicThemeProperties: StrategicTheme entity.
13074
13172
  strategic_theme: {
13075
13173
  owner: { type: "string", description: "Owning person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
13076
- time_horizon: { type: "string", description: 'Bounded period this theme is active (a theme is time-bound, within a pillar). @example "Q1 2026", "FY26"' },
13174
+ time_horizon: { type: "string", description: 'Bounded period this theme is active (a theme is time-bound, within a pillar). @deprecated since 0.20.0. Promote the period to a `planning_cycle` node and link it with the `strategic_theme_scoped_to_planning_cycle` edge, which points at a shared, dated, nestable interval instead of a drifting per-theme string. Kept (not removed) for back-compat; removal is a later major. `strategic_pillar.time_horizon` stays as-is (a durable pillar horizon is genuinely open-ended, not a dated cycle). @example "Q1 2026", "FY26"' },
13077
13175
  description: { type: "string", description: "Short narrative of this time-bound thrust within its pillar" },
13078
13176
  scope: { type: "string", description: "What the theme explicitly includes or excludes" }
13079
13177
  },
@@ -13169,7 +13267,9 @@ var UPG_PROPERTY_SCHEMA = {
13169
13267
  effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
13170
13268
  priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other tasks" },
13171
13269
  due_date: { type: "string", description: "ISO date due. Typically bounded by the containing story's due date." },
13172
- labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." }
13270
+ labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." },
13271
+ workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
13272
+ workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning (a source "In Review" and "QA" might both map to a verification phase). Optional companion to `workflow_state`: it lets a graph reason over an imported custom workflow without promoting the raw label to `status`.' }
13173
13273
  },
13174
13274
  // TeamProperties: Team entity.
13175
13275
  team: {
@@ -13384,7 +13484,13 @@ var UPG_PROPERTY_SCHEMA = {
13384
13484
  as_a: { type: "string", description: '"As a [persona], \u2026". Free-text persona name or slug ref.' },
13385
13485
  i_want_to: { type: "string", description: "Capability or action the persona wants." },
13386
13486
  so_that: { type: "string", description: "Benefit or outcome the persona expects." },
13387
- text: { type: "string", description: "Free-form story text. Used as a single-line rendered view." }
13487
+ text: { type: "string", description: "Free-form story text. Used as a single-line rendered view." },
13488
+ assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
13489
+ effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
13490
+ priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other stories. Lifted onto user_story (0.20.0) so the story is a first-class plannable unit alongside task, matching Jira/Linear where the story/issue is the estimated-and-assigned atom." },
13491
+ due_date: { type: "string", description: "ISO date due. Typically bounded by the release or planning_cycle the story is scheduled into." },
13492
+ workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
13493
+ workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning (a source "In Review" and "QA" might both map to a verification phase). Optional companion to `workflow_state`: it lets a graph reason over an imported custom workflow without promoting the raw label to `status`.' }
13388
13494
  },
13389
13495
  // ValueObjectProperties: DDD value object.
13390
13496
  value_object: {
@@ -20030,6 +20136,7 @@ var ENTITY_EMOJI = {
20030
20136
  persona: "\u{1F3AD}",
20031
20137
  pipeline_sales: "\u{1FA88}",
20032
20138
  pipeline_stage: "\u{1F9FD}",
20139
+ planning_cycle: "\u{1F5D3}\uFE0F",
20033
20140
  playbook: "\u{1F4D2}",
20034
20141
  portfolio: "\u{1F9F8}",
20035
20142
  positioning: "\u{1F3AA}",
@@ -20721,6 +20828,7 @@ var STANDARD_LABELS = {
20721
20828
  roadmap_item: { alt_labels: ["roadmap entry", "planned item"] },
20722
20829
  roadmap_theme: { alt_labels: ["product theme", "roadmap theme"] },
20723
20830
  // UPG-660: renamed from bare 'theme' (N6 lineage)
20831
+ planning_cycle: { alt_labels: ["sprint", "iteration", "cycle", "program increment", "pi", "cadence", "time-box", "cooldown", "quarter"] },
20724
20832
  // Engineering layer
20725
20833
  bounded_context: { alt_labels: ["context", "domain boundary", "module boundary"] },
20726
20834
  service: { alt_labels: ["microservice", "backend service", "api service"] },
@@ -23242,13 +23350,19 @@ var PRODUCT_SPEC_GUIDE = {
23242
23350
  // registered to product_spec but missing from the navigation order).
23243
23351
  // `changelog` lives here because it is a structural product-shipping
23244
23352
  // artefact; content domain references it only via cross-domain bridges.
23245
- creation_sequence: ["feature_area", "feature", "epic", "user_story", "acceptance_criterion", "task", "bug", "release", "roadmap", "roadmap_item", "roadmap_theme", "changelog"],
23353
+ creation_sequence: ["feature_area", "feature", "epic", "user_story", "acceptance_criterion", "task", "bug", "release", "roadmap", "roadmap_item", "roadmap_theme", "changelog", "planning_cycle"],
23246
23354
  patterns: [
23247
23355
  {
23248
23356
  name: "Feature Decomposition",
23249
23357
  description: "Features group into areas, decompose into epics, epics specify user stories (the templated promise), and tasks implement them as the engineering work",
23250
23358
  entity_types: ["feature_area", "feature", "epic", "user_story", "task"],
23251
23359
  edge_chain: ["feature_area_contains_feature", "feature_decomposed_into_epic", "epic_specified_by_user_story", "task_implements_user_story"]
23360
+ },
23361
+ {
23362
+ name: "Planning Cadence",
23363
+ description: "The cadence axis. Create a planning_cycle (a sprint, iteration, quarter, or PI), nest finer cycles inside a coarse one, schedule the stories that flow through it, and scope the objectives the cycle serves. Scheduling and scoping are deliberate links, not containment: a story keeps its feature/epic parent.",
23364
+ entity_types: ["planning_cycle", "user_story", "objective"],
23365
+ edge_chain: ["planning_cycle_contains_planning_cycle", "planning_cycle_schedules_user_story", "objective_scoped_to_planning_cycle"]
23252
23366
  }
23253
23367
  ],
23254
23368
  required_bridges: [
@@ -26517,6 +26631,40 @@ var UPG_ANTI_PATTERNS = [
26517
26631
  severity: "high",
26518
26632
  source: { kind: "practitioner", attribution: "John Cutler, Outcomes over Output" }
26519
26633
  },
26634
+ {
26635
+ id: "planning-cycle-without-scheduled-work",
26636
+ name: "Planning cycle with no scheduled work",
26637
+ description: "A planning_cycle exists but neither schedules any user_story nor contains a finer sub-cycle. An empty cadence box is a date range with nothing flowing through it: a sprint or iteration nobody planned work into, or a coarse period that was never broken down.",
26638
+ structured_condition: {
26639
+ operator: "and",
26640
+ checks: [
26641
+ { check: { type: "entity_count", entity_type: "planning_cycle", comparison: "nonzero" } },
26642
+ {
26643
+ check: {
26644
+ type: "relationship",
26645
+ source_type: "planning_cycle",
26646
+ edge_type: "planning_cycle_schedules_user_story",
26647
+ target_type: "user_story",
26648
+ comparison: "not_exists"
26649
+ }
26650
+ },
26651
+ {
26652
+ check: {
26653
+ type: "relationship",
26654
+ source_type: "planning_cycle",
26655
+ edge_type: "planning_cycle_contains_planning_cycle",
26656
+ target_type: "planning_cycle",
26657
+ comparison: "not_exists"
26658
+ }
26659
+ }
26660
+ ]
26661
+ },
26662
+ why_it_matters: "A cadence layer only earns its keep when work is planned through it. An interval with no scheduled stories and no nested cycles adds ceremony without telling anyone what the period is for.",
26663
+ remediation: "Schedule the stories the cycle will carry via `planning_cycle_schedules_user_story`, or break a coarse period into finer cycles via `planning_cycle_contains_planning_cycle`. If neither applies, the interval is not yet a real cadence box.",
26664
+ stages: ["build", "beta", "launch", "growth", "mature"],
26665
+ severity: "low",
26666
+ since: "0.20.0"
26667
+ },
26520
26668
  // ── Market intelligence layer ───────────────────────────────────────────
26521
26669
  {
26522
26670
  id: "competitors-missing-past-validation",
@@ -27345,6 +27493,16 @@ var UPG_REGIONS = [
27345
27493
  edge_id: "outcome_delivered_via_feature_area",
27346
27494
  crosses_into: "product_delivery"
27347
27495
  },
27496
+ {
27497
+ direction: "export",
27498
+ edge_id: "objective_scoped_to_planning_cycle",
27499
+ crosses_into: "product_delivery"
27500
+ },
27501
+ {
27502
+ direction: "export",
27503
+ edge_id: "strategic_theme_scoped_to_planning_cycle",
27504
+ crosses_into: "product_delivery"
27505
+ },
27348
27506
  {
27349
27507
  direction: "import",
27350
27508
  edge_id: "feature_drives_key_result",
@@ -28156,6 +28314,11 @@ var UPG_REGIONS = [
28156
28314
  {
28157
28315
  type: "status_report",
28158
28316
  role: "leaf"
28317
+ },
28318
+ {
28319
+ type: "planning_cycle",
28320
+ role: "container",
28321
+ notes: "self-nesting cadence axis"
28159
28322
  }
28160
28323
  ],
28161
28324
  anchor: {
@@ -28179,7 +28342,9 @@ var UPG_REGIONS = [
28179
28342
  "roadmap_theme_spans_feature_area",
28180
28343
  "milestone_gates_release",
28181
28344
  "roadmap_item_references_feature",
28182
- "feature_request_voted_on_by_feedback_vote"
28345
+ "feature_request_voted_on_by_feedback_vote",
28346
+ "planning_cycle_contains_planning_cycle",
28347
+ "planning_cycle_schedules_user_story"
28183
28348
  ],
28184
28349
  boundary_edges: [
28185
28350
  {
@@ -28187,6 +28352,16 @@ var UPG_REGIONS = [
28187
28352
  edge_id: "outcome_delivered_by_feature",
28188
28353
  crosses_into: "strategy_outcomes"
28189
28354
  },
28355
+ {
28356
+ direction: "import",
28357
+ edge_id: "objective_scoped_to_planning_cycle",
28358
+ crosses_into: "strategy_outcomes"
28359
+ },
28360
+ {
28361
+ direction: "import",
28362
+ edge_id: "strategic_theme_scoped_to_planning_cycle",
28363
+ crosses_into: "strategy_outcomes"
28364
+ },
28190
28365
  {
28191
28366
  direction: "import",
28192
28367
  edge_id: "outcome_delivered_via_feature_area",
@@ -29999,7 +30174,7 @@ function isCanonical(text) {
29999
30174
  }
30000
30175
 
30001
30176
  // src/index.ts
30002
- var UPG_VERSION = "0.19.0";
30177
+ var UPG_VERSION = "0.20.1";
30003
30178
  var UPG_FORMAT_VERSION = "0.4.0";
30004
30179
  var MARKDOWN_FORMAT_VERSION = "0.1";
30005
30180
  var UPG_TYPES = getTypes();