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 +22 -0
- package/README.md +132 -5
- package/dist/index.d.ts +89 -0
- package/dist/index.js +875 -1
- package/dist/index.js.map +1 -1
- package/manifest.json +3 -2
- package/package.json +4 -4
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 `
|
|
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.
|
|
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.
|
|
176
|
-
"version": "0.
|
|
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.
|
|
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;
|