pm-graph 2026.5.30 → 2026.6.2-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
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.6.2-1 - 2026-06-02
4
+
5
+ ### Added
6
+
7
+ - Deepen pm-graph with offline analytics + new export formats ([pm-graph-1dpk](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/features/pm-graph-1dpk.toon))
8
+ - Unit tests for analytics + new renderers ([pm-graph-f9c3](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-f9c3.toon))
9
+ - graph exporter: add graphml + plantuml formats ([pm-graph-79f2](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-79f2.toon))
10
+
11
+ ### Other
12
+
13
+ - pm-graph critical-path: longest blocking chain ([pm-graph-f1k9](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-f1k9.toon))
14
+ - pm-graph path: shortest dependency path \(BFS\) ([pm-graph-vtlu](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-vtlu.toon))
15
+ - pm-graph cycles: detect dependency cycles, CI exit code ([pm-graph-h0xs](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-h0xs.toon))
16
+ - pm-graph analyze: offline graph-health report ([pm-graph-d9tz](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-d9tz.toon))
17
+ - Decision: analytics operate on STRUCTURAL edges only ([pm-graph-89nw](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/tasks/pm-graph-89nw.toon))
18
+
19
+ ## 2026.06.02 - 2026-06-02
20
+
21
+ ### Added
22
+
23
+ - Add multi-format graph exporter \(pm graph export\): cypher, mermaid, dot, json-graph ([pm-graph-xii9](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/features/pm-graph-xii9.toon))
24
+
3
25
  ## 2026.05.28 - 2026-05-28
4
26
 
5
27
  ### Added
package/README.md CHANGED
@@ -4,6 +4,10 @@ Knowledge graph and dependency graph extension for [pm CLI](https://github.com/u
4
4
 
5
5
  The extension reads the current workspace through `pm list-all --json` and `pm deps <id> --json`, then turns items, parent links, `blocked_by` metadata, dependency metadata, tags, statuses, types, assignees, sprints, and releases into graph nodes and relationships.
6
6
 
7
+ It can sync that graph into Neo4j, or export it offline to **Mermaid**, **Graphviz DOT**, **JSON Graph**, **Cypher**, **GraphML**, or **PlantUML** via `pm graph export` — with neighborhood (`--root`/`--depth`) and edge-type (`--edges`) shaping.
8
+
9
+ It also ships **offline graph analytics** (no Neo4j required): `pm pm-graph analyze`, `cycles`, `path`, and `critical-path` run dependency-cycle detection, shortest-path, longest-chain, orphan/root/leaf and centrality analysis directly against your workspace.
10
+
7
11
  ## Quick Start
8
12
 
9
13
  **Step 1 — Install the extension:**
@@ -52,7 +56,7 @@ pm install github.com/unbraind/pm-graph --force
52
56
  | `NEO4J_DATABASE` | No | Target database (defaults to server default) |
53
57
  | `PM_GRAPH_PROJECT_KEY` | No | Override the project key (defaults to workspace directory name) |
54
58
 
55
- The commands `export` and `cypher` do not require Neo4j at all.
59
+ The commands `export`, `cypher`, `analyze`, `cycles`, `path`, and `critical-path`, plus the `pm graph export` exporter, do not require Neo4j at all.
56
60
 
57
61
  ## Commands
58
62
 
@@ -71,7 +75,7 @@ Example output:
71
75
  "ok": true,
72
76
  "source": "pm-graph",
73
77
  "neo4jConfigured": true,
74
- "version": "0.1.4"
78
+ "version": "0.2.0"
75
79
  }
76
80
  ```
77
81
 
@@ -125,6 +129,55 @@ Example output (abbreviated):
125
129
  }
126
130
  ```
127
131
 
132
+ ### `pm graph export`
133
+
134
+ Export the current workspace graph to a portable file format for diagramming or import into other graph tooling. Builds the graph from a single `pm list-all --json --include-body` call. **Does not require Neo4j.**
135
+
136
+ ```bash
137
+ pm graph export --format mermaid # Mermaid graph TD to stdout
138
+ pm graph export --format dot --output graph.dot # Graphviz DOT to a file
139
+ pm graph export --format json --output graph.json # JSON Graph (nodes/edges)
140
+ pm graph export --format cypher # parameterized Cypher (commented params)
141
+ pm graph export --format graphml --output graph.graphml # GraphML XML for yEd / Gephi / NetworkX
142
+ pm graph export --format plantuml --output graph.puml # PlantUML object diagram
143
+ ```
144
+
145
+ Shape the exported graph:
146
+
147
+ ```bash
148
+ # Dependency edges only (drop facet + tag edges)
149
+ pm graph export --format mermaid --edges deps
150
+
151
+ # Only tag relationships
152
+ pm graph export --format mermaid --edges tags
153
+
154
+ # 2-hop neighborhood around one item (undirected reachability)
155
+ pm graph export --format dot --root TASK-42 --depth 2
156
+
157
+ # Include closed/canceled items (excluded by default)
158
+ pm graph export --format json --include-closed
159
+ ```
160
+
161
+ | Flag | Values | Default | Description |
162
+ |---|---|---|---|
163
+ | `--format` | `cypher` \| `mermaid` \| `dot` \| `json` \| `graphml` \| `plantuml` | `json` | Output format. `mermaid` emits a `graph TD` flowchart; `dot` emits Graphviz `digraph`; `json` emits a JSON Graph (`nodes`/`edges`) document; `cypher` reuses the parameterized Neo4j import statements; `graphml` emits a valid GraphML XML document for yEd / Gephi / NetworkX; `plantuml` emits a `@startuml`…`@enduml` object diagram. |
164
+ | `--output <file>` | path | — | Write to this file instead of stdout. |
165
+ | `--root <id>` | item id | — | Restrict the graph to the neighborhood around this node. |
166
+ | `--depth <n>` | non-negative integer | unlimited | Max hops from `--root` (undirected). Only meaningful with `--root`. |
167
+ | `--include-closed` | flag | off | Include `closed`/`canceled` items (excluded by default). |
168
+ | `--edges <deps\|tags\|all>` | `deps` \| `tags` \| `all` | `all` | `deps` keeps dependency/structural edges (`BLOCKED_BY`, `CHILD_OF`, dependency kinds); `tags` keeps only `TAGGED_WITH`; `all` keeps everything including facet edges. |
169
+
170
+ Example output (`--format mermaid --edges deps`):
171
+
172
+ ```mermaid
173
+ graph TD
174
+ n_TASK_2["Build API [TASK-2] (in_progress)"]
175
+ n_TASK_1["Design schema [TASK-1] (closed)"]
176
+ n_TASK_2 -->|BLOCKED_BY| n_TASK_1
177
+ ```
178
+
179
+ `pm graph export` is provided through pm's exporter pipeline, so it is invoked as `pm graph export` (the `<name> export` form). It is fully offline and never touches Neo4j.
180
+
128
181
  ### `pm pm-graph sync`
129
182
 
130
183
  Sync the current workspace graph into Neo4j using the `NEO4J_*` environment variables.
@@ -172,8 +225,8 @@ Example output (Neo4j connected):
172
225
  "nodeCount": 18,
173
226
  "relationshipCount": 11,
174
227
  "lastSyncedAt": "2026-05-14T10:00:00.000Z",
175
- "syncVersion": "0.1.4",
176
- "version": "0.1.4"
228
+ "syncVersion": "0.2.0",
229
+ "version": "0.2.0"
177
230
  }
178
231
  ```
179
232
 
@@ -187,7 +240,7 @@ Example output (Neo4j not configured):
187
240
  "projectKey": "my-project",
188
241
  "workspace": "/path/to/workspace",
189
242
  "localItemCount": 15,
190
- "version": "0.1.4"
243
+ "version": "0.2.0"
191
244
  }
192
245
  ```
193
246
 
@@ -235,6 +288,80 @@ Example output:
235
288
  }
236
289
  ```
237
290
 
291
+ ## Offline Analytics
292
+
293
+ These commands analyze the workspace dependency graph **entirely offline** — no Neo4j required. They operate on **structural edges only** (`BLOCKED_BY`, `CHILD_OF`, and dependency edges such as `BLOCKS`/`RELATED`) between real items; facet edges (type/status/assignee/sprint/release) and tag edges are deliberately excluded so cycle, path, and centrality results stay meaningful.
294
+
295
+ Closed/canceled items are excluded by default; pass `--include-closed` to keep them. `analyze`, `cycles`, and `critical-path` also accept `--root <id>` / `--depth <n>` to scope the analysis to a neighborhood.
296
+
297
+ ### `pm pm-graph analyze`
298
+
299
+ Comprehensive graph-health report: dependency-cycle count, orphan items (no edges), root items (no incoming dependency), leaf items (no outgoing dependency), longest dependency chain, top-N degree-centrality items, connected-component count, and blocked-item count.
300
+
301
+ ```bash
302
+ pm pm-graph analyze --json
303
+ pm pm-graph analyze --root pm-ep18 --depth 2 --json
304
+ pm pm-graph analyze --include-closed --json
305
+ ```
306
+
307
+ Example output (abbreviated):
308
+
309
+ ```json
310
+ {
311
+ "ok": true,
312
+ "itemCount": 7,
313
+ "structuralEdgeCount": 6,
314
+ "cycleCount": 1,
315
+ "cycles": [["pm-oj3m", "pm-zc3a", "pm-oj3m"]],
316
+ "orphans": ["pm-op0k"],
317
+ "roots": ["pm-ep18"],
318
+ "leaves": ["pm-hd71"],
319
+ "longestChainLength": 4,
320
+ "longestChain": ["pm-ep18", "pm-k849", "pm-p2q3", "pm-hd71"],
321
+ "connectedComponents": 3,
322
+ "blockedItemCount": 5,
323
+ "topDegreeCentrality": [{ "id": "pm-ep18", "degree": 2, "inDegree": 0, "outDegree": 2 }]
324
+ }
325
+ ```
326
+
327
+ ### `pm pm-graph cycles`
328
+
329
+ Detect and list dependency cycles. Each cycle is printed as an ordered id path (first id equals last). **Exits with code 1 when any cycle exists** (so it can gate CI) and exits 0 when there are none.
330
+
331
+ ```bash
332
+ pm pm-graph cycles # human-readable; exit 1 if cycles found
333
+ pm pm-graph cycles --json # machine-readable
334
+ ```
335
+
336
+ ```jsonc
337
+ // no cycles -> exit 0
338
+ { "ok": true, "cycleCount": 0, "cycles": [] }
339
+ ```
340
+
341
+ ### `pm pm-graph path`
342
+
343
+ Shortest **directed** dependency path between two item ids via BFS over structural edges. Missing arguments yield a usage error (exit 2); an unknown id yields a not-found error (exit 3).
344
+
345
+ ```bash
346
+ pm pm-graph path pm-ep18 pm-hd71 --json
347
+ ```
348
+
349
+ ```json
350
+ { "ok": true, "from": "pm-ep18", "to": "pm-hd71", "found": true, "path": ["pm-ep18", "pm-hd71"], "length": 1 }
351
+ ```
352
+
353
+ ### `pm pm-graph critical-path`
354
+
355
+ The longest chain of blocking dependencies through the workspace — the critical path — as an ordered id list with its length. Cycle-safe.
356
+
357
+ ```bash
358
+ pm pm-graph critical-path --json
359
+ ```
360
+
361
+ ```json
362
+ { "ok": true, "length": 4, "path": ["pm-ep18", "pm-k849", "pm-p2q3", "pm-hd71"] }
363
+ ```
364
+
238
365
  ## Graph Model
239
366
 
240
367
  - **`PmItem`** nodes for real pm items, labelled with item type (e.g. `task`, `bug`).
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Exporter } from "@unbrained/pm-cli/sdk";
1
2
  type CommandContext = {
2
3
  command?: string;
3
4
  args?: string[];
@@ -11,7 +12,95 @@ type RegisterCommand = {
11
12
  };
12
13
  type ExtensionApi = {
13
14
  registerCommand(command: RegisterCommand): void;
15
+ registerExporter(name: string, exporter: Exporter): void;
14
16
  };
17
+ type GraphNode = {
18
+ id: string;
19
+ labels: string[];
20
+ properties: Record<string, unknown>;
21
+ };
22
+ type GraphRelationship = {
23
+ from: string;
24
+ to: string;
25
+ type: string;
26
+ properties: Record<string, unknown>;
27
+ };
28
+ type Graph = {
29
+ generatedAt: string;
30
+ workspace: string;
31
+ projectKey: string;
32
+ nodes: GraphNode[];
33
+ relationships: GraphRelationship[];
34
+ };
35
+ export type ExportFormat = "cypher" | "mermaid" | "dot" | "json" | "graphml" | "plantuml";
36
+ export type EdgeFilter = "deps" | "tags" | "all";
37
+ /**
38
+ * Render a valid GraphML XML document (consumable by yEd / Gephi / NetworkX).
39
+ * Declares string keys for node title/type/status/labels and edge type, then
40
+ * emits one <node> per graph node and one <edge> per relationship.
41
+ */
42
+ export declare function renderGraphml(graph: Graph): string;
43
+ /**
44
+ * Render a PlantUML object diagram (`@startuml`…`@enduml`) with one object per
45
+ * node and one arrow per relationship, the relationship type as the arrow
46
+ * label. Renders with PlantUML / Structurizr / many docs toolchains.
47
+ */
48
+ export declare function renderPlantuml(graph: Graph): string;
49
+ type StructuralEdge = {
50
+ from: string;
51
+ to: string;
52
+ type: string;
53
+ };
54
+ /**
55
+ * Detect all elementary directed cycles among structural edges using an
56
+ * iterative DFS with a recursion stack. Returns each cycle as an ordered id
57
+ * path whose first and last ids are equal (e.g. [E, F, E]). Cycles are
58
+ * de-duplicated by their canonical rotation so A->B->A and B->A->B collapse.
59
+ */
60
+ export declare function findCycles(nodes: string[], edges: StructuralEdge[]): string[][];
61
+ /**
62
+ * Shortest directed path from `from` to `to` over structural edges (BFS).
63
+ * Returns the ordered id path (inclusive of both endpoints) or null if no path
64
+ * exists. Returns [from] when from === to.
65
+ */
66
+ export declare function shortestPath(edges: StructuralEdge[], from: string, to: string): string[] | null;
67
+ /**
68
+ * Longest dependency chain (critical path) over structural edges. Uses a
69
+ * memoised DFS that is safe on cyclic graphs (nodes on the active recursion
70
+ * stack are skipped, so a cycle cannot inflate the chain infinitely). Returns
71
+ * the ordered id list of the longest simple chain found.
72
+ */
73
+ export declare function longestChain(nodes: string[], edges: StructuralEdge[]): string[];
74
+ type AnalyzeReport = {
75
+ workspace: string;
76
+ projectKey: string;
77
+ itemCount: number;
78
+ structuralEdgeCount: number;
79
+ cycleCount: number;
80
+ cycles: string[][];
81
+ orphanCount: number;
82
+ orphans: string[];
83
+ rootCount: number;
84
+ roots: string[];
85
+ leafCount: number;
86
+ leaves: string[];
87
+ longestChainLength: number;
88
+ longestChain: string[];
89
+ connectedComponents: number;
90
+ blockedItemCount: number;
91
+ blockedItems: string[];
92
+ topDegreeCentrality: Array<{
93
+ id: string;
94
+ degree: number;
95
+ inDegree: number;
96
+ outDegree: number;
97
+ }>;
98
+ };
99
+ /**
100
+ * Compute a comprehensive offline graph-health report from a shaped graph.
101
+ * All analytics operate on structural edges between item nodes only.
102
+ */
103
+ export declare function analyzeGraph(graph: Graph, topN?: number): AnalyzeReport;
15
104
  export declare function activate(api: ExtensionApi): void;
16
105
  declare const _default: {
17
106
  activate: typeof activate;