@unified-product-graph/core 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.20.0] - 2026-07-04
11
+
12
+ **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.
13
+
14
+ ### Added
15
+ - **`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).
16
+ - **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).
17
+ - **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.
18
+ - **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.
19
+ - **`planning-cycle-without-scheduled-work`** anti-pattern (low severity) — a cadence box that neither schedules work nor nests a sub-cycle.
20
+
21
+ ### Changed
22
+ - **`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.
23
+
24
+ ### Deprecated
25
+ - **`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.
26
+
27
+ ---
28
+
10
29
  ## [0.19.0] - 2026-07-04
11
30
 
12
31
  **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. 1034 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" },
@@ -1621,6 +1630,8 @@ var UPG_EDGE_CATALOG = {
1621
1630
  product_builds_feature: { forward_verb: "builds", reverse_verb: "built_by", classification: "hierarchy", source_type: "product", target_type: "feature" },
1622
1631
  product_ships_via_release: { forward_verb: "ships_via", reverse_verb: "ships", classification: "hierarchy", source_type: "product", target_type: "release" },
1623
1632
  product_plans_via_roadmap: { forward_verb: "plans_via", reverse_verb: "plans_for", classification: "hierarchy", source_type: "product", target_type: "roadmap" },
1633
+ // (0.20.0) top-level attach for the cadence axis, mirroring product_plans_via_roadmap.
1634
+ product_runs_planning_cycle: { forward_verb: "runs", reverse_verb: "run_by", classification: "hierarchy", source_type: "product", target_type: "planning_cycle" },
1624
1635
  product_categorises_by_roadmap_theme: { forward_verb: "categorises_by", reverse_verb: "categorises", classification: "hierarchy", source_type: "product", target_type: "roadmap_theme" },
1625
1636
  feature_area_contains_feature: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature" },
1626
1637
  feature_area_contains_feature_area: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "feature_area", target_type: "feature_area" },
@@ -1659,6 +1670,33 @@ var UPG_EDGE_CATALOG = {
1659
1670
  roadmap_item_references_feature: { forward_verb: "references", reverse_verb: "referenced_by", classification: "cross-domain", source_type: "roadmap_item", target_type: "feature" },
1660
1671
  feature_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "feature", target_type: "task" },
1661
1672
  task_has_subtask: { forward_verb: "has_subtask", reverse_verb: "is_subtask_of", classification: "hierarchy", source_type: "task", target_type: "task" },
1673
+ // ── Planning cadence (0.20.0) ────────────────────────────────────────────────
1674
+ // The cadence axis. E1 self-nests the interval (a program-increment contains
1675
+ // iterations; a cycle contains its cooldown), mirroring team_contains_team.
1676
+ planning_cycle_contains_planning_cycle: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "planning_cycle", target_type: "planning_cycle" },
1677
+ // E3: scheduling work into a cycle is a deliberate authoring act, not a
1678
+ // containment nesting — user_story keeps its feature/epic parent and is merely
1679
+ // referenced by the cycle. deliberate_only so the generic-inference chokepoints
1680
+ // never auto-materialise it.
1681
+ 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 },
1682
+ // E2: the OKR cycle-scoping anchor. objective -> planning_cycle. planning_cycle
1683
+ // is portfolio_shared, so this passes the cross-scope gate and ships as
1684
+ // PROVISIONAL (allowed cross-product with a write-time warning, self-maintaining,
1685
+ // never added to the curated set) rather than carrying a cross_product_eligible
1686
+ // flag, which would make it curated. Eligibility is permission, not obligation.
1687
+ objective_scoped_to_planning_cycle: { forward_verb: "scoped_to", reverse_verb: "scopes", classification: "semantic", source_type: "objective", target_type: "planning_cycle" },
1688
+ // F: the promoted `strategic_theme.time_horizon`. Same shape as E2 — a theme's
1689
+ // bounded period becomes a scoping edge onto a shared, dated, nestable interval.
1690
+ // Provisional (planning_cycle portfolio_shared). The property stays @deprecated.
1691
+ strategic_theme_scoped_to_planning_cycle: { forward_verb: "scoped_to", reverse_verb: "scopes", classification: "semantic", source_type: "strategic_theme", target_type: "planning_cycle" },
1692
+ // Gap 2 — polymorphic issue links over the work-item set {feature, epic,
1693
+ // user_story, task, bug}. Endpoint-polymorphic (node -> node, registered in
1694
+ // UPG_POLYMORPHIC_EDGE_KEYS) so one edge covers every level teams link at; the
1695
+ // `work_item_` key names the intended semantic domain. All deliberate_only: an
1696
+ // issue link is authored, never inferred from hierarchy.
1697
+ work_item_blocks_work_item: { forward_verb: "blocks", reverse_verb: "blocked_by", classification: "causal", source_type: "node", target_type: "node", deliberate_only: true },
1698
+ 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 },
1699
+ work_item_duplicates_work_item: { forward_verb: "duplicates", reverse_verb: "duplicated_by", classification: "semantic", source_type: "node", target_type: "node", deliberate_only: true },
1662
1700
  // 2.3 Legal Domain
1663
1701
  product_owned_by_legal_entity: { forward_verb: "owned_by", reverse_verb: "owns", classification: "hierarchy", source_type: "product", target_type: "legal_entity" },
1664
1702
  legal_entity_protects_ip_asset: { forward_verb: "protects", reverse_verb: "protected_by", classification: "hierarchy", source_type: "legal_entity", target_type: "ip_asset" },
@@ -3442,7 +3480,13 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
3442
3480
  // Framework exercises (an exercise can include any entity type)
3443
3481
  "framework_exercise_includes_node",
3444
3482
  // Universal classification (any node classified against a classification_value)
3445
- "node_classified_as_classification_value"
3483
+ "node_classified_as_classification_value",
3484
+ // Work-item issue links (0.20.0): endpoint-polymorphic over the work-item set
3485
+ // {feature, epic, user_story, task, bug}. The `work_item_` key names the
3486
+ // intended semantic domain; the endpoints are the `node` wildcard.
3487
+ "work_item_blocks_work_item",
3488
+ "work_item_relates_to_work_item",
3489
+ "work_item_duplicates_work_item"
3446
3490
  ];
3447
3491
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
3448
3492
  function isPolymorphicEdge(key) {
@@ -3632,6 +3676,7 @@ var UPG_VALID_CHILDREN = {
3632
3676
  "release",
3633
3677
  "roadmap",
3634
3678
  "roadmap_theme",
3679
+ "planning_cycle",
3635
3680
  // Engineering
3636
3681
  "bounded_context",
3637
3682
  "code_repository",
@@ -3900,6 +3945,13 @@ var UPG_VALID_CHILDREN = {
3900
3945
  epic: ["user_story"],
3901
3946
  user_story: ["acceptance_criterion"],
3902
3947
  task: ["task"],
3948
+ // planning_cycle (0.20.0): the cadence axis self-nests (a program-increment
3949
+ // contains iterations; a cycle contains its cooldown) via
3950
+ // planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
3951
+ // child: scheduling work into a cycle is the semantic, deliberate-only
3952
+ // planning_cycle_schedules_user_story edge, so a story keeps its feature/epic
3953
+ // containment parent and is merely referenced by the cycle.
3954
+ planning_cycle: ["planning_cycle"],
3903
3955
  // ── Engineering hierarchy ───────────────────────────────────────────────────
3904
3956
  bounded_context: [
3905
3957
  "service",
@@ -6543,6 +6595,16 @@ var SPECIFICATION_LIFECYCLE = {
6543
6595
  }
6544
6596
  ]
6545
6597
  };
6598
+ var PLANNING_CYCLE_LIFECYCLE = {
6599
+ entity_type: "planning_cycle",
6600
+ initial_phase: "planned",
6601
+ terminal_phases: ["closed"],
6602
+ phases: [
6603
+ { 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"] },
6604
+ { id: "active", label: "Active", description: "The team is inside the interval; work is flowing through it.", transitions_to: ["closed"] },
6605
+ { 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"] }
6606
+ ]
6607
+ };
6546
6608
  var UPG_LIFECYCLES = [
6547
6609
  // Product (root)
6548
6610
  PRODUCT_LIFECYCLE,
@@ -6586,6 +6648,7 @@ var UPG_LIFECYCLES = [
6586
6648
  TASK_LIFECYCLE,
6587
6649
  BUG_LIFECYCLE,
6588
6650
  ROADMAP_ITEM_LIFECYCLE,
6651
+ PLANNING_CYCLE_LIFECYCLE,
6589
6652
  IP_ASSET_LIFECYCLE,
6590
6653
  CONTRACT_LIFECYCLE,
6591
6654
  DESIGN_CONCEPT_LIFECYCLE,
@@ -10768,7 +10831,9 @@ var UPG_PROPERTY_SCHEMA = {
10768
10831
  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
10832
  assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
10770
10833
  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." }
10834
+ labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." },
10835
+ 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.' },
10836
+ 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
10837
  },
10773
10838
  // BuildArtifactProperties: Build artifact.
10774
10839
  build_artifact: {
@@ -11422,7 +11487,9 @@ var UPG_PROPERTY_SCHEMA = {
11422
11487
  priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Task-level priority" },
11423
11488
  owner: { type: "string", description: "Responsible person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
11424
11489
  start_date: { type: "string", description: "ISO date work begins" },
11425
- target_date: { type: "string", description: "ISO date work completes" }
11490
+ target_date: { type: "string", description: "ISO date work completes" },
11491
+ 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.' },
11492
+ 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
11493
  },
11427
11494
  // ErrorBudgetProperties: Error budget.
11428
11495
  error_budget: {
@@ -11602,7 +11669,9 @@ var UPG_PROPERTY_SCHEMA = {
11602
11669
  owner: { type: "string", description: "Responsible person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
11603
11670
  start_date: { type: "string", description: "ISO date work begins" },
11604
11671
  target_date: { type: "string", description: "ISO date work completes" },
11605
- health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" }
11672
+ health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" },
11673
+ 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.' },
11674
+ 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
11675
  },
11607
11676
  // FeatureAreaProperties: A structural grouping of related features within a product.
11608
11677
  feature_area: {
@@ -12522,6 +12591,16 @@ var UPG_PROPERTY_SCHEMA = {
12522
12591
  conversion_rate: { type: "number", description: "Percentage of deals that advance from this stage", modifier: "snapshot" },
12523
12592
  avg_days_in_stage: { type: "number", description: "Average number of days deals spend in this stage" }
12524
12593
  },
12594
+ // PlanningCycleProperties: Planning cycle: a named, dated interval work flows through, which nests.
12595
+ planning_cycle: {
12596
+ 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." },
12597
+ 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.' },
12598
+ starts_on: { type: "string", description: "ISO date the interval opens. A cycle is concretely dated, unlike a coarse `time_horizon` label." },
12599
+ ends_on: { type: "string", description: "ISO date the interval closes." },
12600
+ sequence: { type: "number", description: "The cycle / iteration number (e.g. Sprint 47, PI 3)." },
12601
+ goal: { type: "string", description: "The interval's goal or focus: what this cadence box is for." },
12602
+ 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").' }
12603
+ },
12525
12604
  // PlaybookProperties: Customer success playbook.
12526
12605
  playbook: {
12527
12606
  playbook_type: { type: "string", enum: ["onboarding", "expansion", "renewal", "rescue", "other"], description: "Scenario this playbook addresses" },
@@ -13073,7 +13152,7 @@ var UPG_PROPERTY_SCHEMA = {
13073
13152
  // StrategicThemeProperties: StrategicTheme entity.
13074
13153
  strategic_theme: {
13075
13154
  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"' },
13155
+ 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
13156
  description: { type: "string", description: "Short narrative of this time-bound thrust within its pillar" },
13078
13157
  scope: { type: "string", description: "What the theme explicitly includes or excludes" }
13079
13158
  },
@@ -13169,7 +13248,9 @@ var UPG_PROPERTY_SCHEMA = {
13169
13248
  effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
13170
13249
  priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other tasks" },
13171
13250
  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." }
13251
+ labels: { type: "string[]", description: "Free-form classification tags. Applied uniformly across work item types." },
13252
+ 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.' },
13253
+ 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
13254
  },
13174
13255
  // TeamProperties: Team entity.
13175
13256
  team: {
@@ -13384,7 +13465,13 @@ var UPG_PROPERTY_SCHEMA = {
13384
13465
  as_a: { type: "string", description: '"As a [persona], \u2026". Free-text persona name or slug ref.' },
13385
13466
  i_want_to: { type: "string", description: "Capability or action the persona wants." },
13386
13467
  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." }
13468
+ text: { type: "string", description: "Free-form story text. Used as a single-line rendered view." },
13469
+ assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
13470
+ effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
13471
+ 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." },
13472
+ due_date: { type: "string", description: "ISO date due. Typically bounded by the release or planning_cycle the story is scheduled into." },
13473
+ 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.' },
13474
+ 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
13475
  },
13389
13476
  // ValueObjectProperties: DDD value object.
13390
13477
  value_object: {
@@ -20030,6 +20117,7 @@ var ENTITY_EMOJI = {
20030
20117
  persona: "\u{1F3AD}",
20031
20118
  pipeline_sales: "\u{1FA88}",
20032
20119
  pipeline_stage: "\u{1F9FD}",
20120
+ planning_cycle: "\u{1F5D3}\uFE0F",
20033
20121
  playbook: "\u{1F4D2}",
20034
20122
  portfolio: "\u{1F9F8}",
20035
20123
  positioning: "\u{1F3AA}",
@@ -20721,6 +20809,7 @@ var STANDARD_LABELS = {
20721
20809
  roadmap_item: { alt_labels: ["roadmap entry", "planned item"] },
20722
20810
  roadmap_theme: { alt_labels: ["product theme", "roadmap theme"] },
20723
20811
  // UPG-660: renamed from bare 'theme' (N6 lineage)
20812
+ planning_cycle: { alt_labels: ["sprint", "iteration", "cycle", "program increment", "pi", "cadence", "time-box", "cooldown", "quarter"] },
20724
20813
  // Engineering layer
20725
20814
  bounded_context: { alt_labels: ["context", "domain boundary", "module boundary"] },
20726
20815
  service: { alt_labels: ["microservice", "backend service", "api service"] },
@@ -23242,13 +23331,19 @@ var PRODUCT_SPEC_GUIDE = {
23242
23331
  // registered to product_spec but missing from the navigation order).
23243
23332
  // `changelog` lives here because it is a structural product-shipping
23244
23333
  // 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"],
23334
+ creation_sequence: ["feature_area", "feature", "epic", "user_story", "acceptance_criterion", "task", "bug", "release", "roadmap", "roadmap_item", "roadmap_theme", "changelog", "planning_cycle"],
23246
23335
  patterns: [
23247
23336
  {
23248
23337
  name: "Feature Decomposition",
23249
23338
  description: "Features group into areas, decompose into epics, epics specify user stories (the templated promise), and tasks implement them as the engineering work",
23250
23339
  entity_types: ["feature_area", "feature", "epic", "user_story", "task"],
23251
23340
  edge_chain: ["feature_area_contains_feature", "feature_decomposed_into_epic", "epic_specified_by_user_story", "task_implements_user_story"]
23341
+ },
23342
+ {
23343
+ name: "Planning Cadence",
23344
+ 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.",
23345
+ entity_types: ["planning_cycle", "user_story", "objective"],
23346
+ edge_chain: ["planning_cycle_contains_planning_cycle", "planning_cycle_schedules_user_story", "objective_scoped_to_planning_cycle"]
23252
23347
  }
23253
23348
  ],
23254
23349
  required_bridges: [
@@ -26517,6 +26612,40 @@ var UPG_ANTI_PATTERNS = [
26517
26612
  severity: "high",
26518
26613
  source: { kind: "practitioner", attribution: "John Cutler, Outcomes over Output" }
26519
26614
  },
26615
+ {
26616
+ id: "planning-cycle-without-scheduled-work",
26617
+ name: "Planning cycle with no scheduled work",
26618
+ 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.",
26619
+ structured_condition: {
26620
+ operator: "and",
26621
+ checks: [
26622
+ { check: { type: "entity_count", entity_type: "planning_cycle", comparison: "nonzero" } },
26623
+ {
26624
+ check: {
26625
+ type: "relationship",
26626
+ source_type: "planning_cycle",
26627
+ edge_type: "planning_cycle_schedules_user_story",
26628
+ target_type: "user_story",
26629
+ comparison: "not_exists"
26630
+ }
26631
+ },
26632
+ {
26633
+ check: {
26634
+ type: "relationship",
26635
+ source_type: "planning_cycle",
26636
+ edge_type: "planning_cycle_contains_planning_cycle",
26637
+ target_type: "planning_cycle",
26638
+ comparison: "not_exists"
26639
+ }
26640
+ }
26641
+ ]
26642
+ },
26643
+ 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.",
26644
+ 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.",
26645
+ stages: ["build", "beta", "launch", "growth", "mature"],
26646
+ severity: "low",
26647
+ since: "0.20.0"
26648
+ },
26520
26649
  // ── Market intelligence layer ───────────────────────────────────────────
26521
26650
  {
26522
26651
  id: "competitors-missing-past-validation",
@@ -27345,6 +27474,16 @@ var UPG_REGIONS = [
27345
27474
  edge_id: "outcome_delivered_via_feature_area",
27346
27475
  crosses_into: "product_delivery"
27347
27476
  },
27477
+ {
27478
+ direction: "export",
27479
+ edge_id: "objective_scoped_to_planning_cycle",
27480
+ crosses_into: "product_delivery"
27481
+ },
27482
+ {
27483
+ direction: "export",
27484
+ edge_id: "strategic_theme_scoped_to_planning_cycle",
27485
+ crosses_into: "product_delivery"
27486
+ },
27348
27487
  {
27349
27488
  direction: "import",
27350
27489
  edge_id: "feature_drives_key_result",
@@ -28156,6 +28295,11 @@ var UPG_REGIONS = [
28156
28295
  {
28157
28296
  type: "status_report",
28158
28297
  role: "leaf"
28298
+ },
28299
+ {
28300
+ type: "planning_cycle",
28301
+ role: "container",
28302
+ notes: "self-nesting cadence axis"
28159
28303
  }
28160
28304
  ],
28161
28305
  anchor: {
@@ -28179,7 +28323,9 @@ var UPG_REGIONS = [
28179
28323
  "roadmap_theme_spans_feature_area",
28180
28324
  "milestone_gates_release",
28181
28325
  "roadmap_item_references_feature",
28182
- "feature_request_voted_on_by_feedback_vote"
28326
+ "feature_request_voted_on_by_feedback_vote",
28327
+ "planning_cycle_contains_planning_cycle",
28328
+ "planning_cycle_schedules_user_story"
28183
28329
  ],
28184
28330
  boundary_edges: [
28185
28331
  {
@@ -28187,6 +28333,16 @@ var UPG_REGIONS = [
28187
28333
  edge_id: "outcome_delivered_by_feature",
28188
28334
  crosses_into: "strategy_outcomes"
28189
28335
  },
28336
+ {
28337
+ direction: "import",
28338
+ edge_id: "objective_scoped_to_planning_cycle",
28339
+ crosses_into: "strategy_outcomes"
28340
+ },
28341
+ {
28342
+ direction: "import",
28343
+ edge_id: "strategic_theme_scoped_to_planning_cycle",
28344
+ crosses_into: "strategy_outcomes"
28345
+ },
28190
28346
  {
28191
28347
  direction: "import",
28192
28348
  edge_id: "outcome_delivered_via_feature_area",
@@ -29999,7 +30155,7 @@ function isCanonical(text) {
29999
30155
  }
30000
30156
 
30001
30157
  // src/index.ts
30002
- var UPG_VERSION = "0.19.0";
30158
+ var UPG_VERSION = "0.20.0";
30003
30159
  var UPG_FORMAT_VERSION = "0.4.0";
30004
30160
  var MARKDOWN_FORMAT_VERSION = "0.1";
30005
30161
  var UPG_TYPES = getTypes();