feat: add pks graph subcommand (deterministic JSON dependency-graph export) - #44
feat: add pks graph subcommand (deterministic JSON dependency-graph export)#44benleegusto wants to merge 3 commits into
pks graph subcommand (deterministic JSON dependency-graph export)#44Conversation
Adds a read-only `pks graph` command that serializes the whole-repo pack dependency graph — data pks already parses — as deterministic JSON: nodes (name + optional layer/owner) and directed edges tagged by kind (`declared`, `ignored`, `todo`). Fully ordered (nodes by name, edges by from/to/kind) so repeated runs on unchanged config are byte-identical. No analysis (no cycle/SCC detection or simulation) — that is left to downstream tools consuming the raw graph. No new dependencies (reuses serde/serde_json). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
End-to-end coverage mirroring the other subcommands' tests/*_test.rs: asserts declared, todo, and ignored edges over the simple_app / contains_package_todo / app_with_ignored_dependency fixtures, plus CLI-level determinism (two runs => byte-identical stdout). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the conventions MOD-122 established for `pks check -o json`: - Serialize compact via serde_json::to_writer (was pretty) - Model edge kind as an EdgeKind enum (declared/ignored/todo) - Add schema/graph-output.json (draft-07), mirroring schema/check-output.json (additionalProperties:false, required lists, $defs + $ref, enum) - Extract write_graph<W: Write> for a testable writer boundary; doc-comment references the schema file (like json.rs) Determinism preserved (ordered nodes/edges); tests updated for compact output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| ) -> anyhow::Result<()> { | ||
| // Compact, raw structured data (matches `pks check -o json`); consumers format as needed. | ||
| serde_json::to_writer(writer, &build(configuration))?; | ||
| Ok(()) |
There was a problem hiding this comment.
serde_json::to_writer emits no trailing newline. All other pks text output ends with \n, and a missing newline causes the shell prompt to render on the same line in some terminals. Consider:
let json = serde_json::to_string(&build(configuration))?;
writeln!(writer, "{json}")?;or use to_writer_pretty if pretty-printing is ever desired — though compact is fine for machine consumption.
| assert_eq!( | ||
| json_bytes(&configuration), | ||
| json_bytes(&configuration), | ||
| "graph JSON must be byte-identical across runs" |
There was a problem hiding this comment.
This test is a tautology: both calls run in the same process on the same Configuration object. Rust's HashMap randomizes iteration order per process startup (via RandomState), not per call, so the two json_bytes calls will always agree within one run — regardless of whether the sort is present. The test passes even if you strip every sort_by call from build().
The integration test graph_cli_output_is_deterministic in tests/graph_test.rs is the real check (separate processes, separate seeds). Consider dropping this unit test or replacing it with one that builds two separate Configuration instances from the same fixture path.
| "expected todo edge packs/foo -> packs/bar" | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
No test covers layer and owner appearing in node output when set. If a future refactor accidentally drops those fields or changes the skip_serializing_if predicate, nothing here would catch it. Consider adding a test that calls build() against a fixture that has a pack with layer or owner configured and asserts the node's fields are Some(...).
What
Adds a read-only
pks graphsubcommand that prints the whole-repo pack dependency graph as compact JSON:{"nodes":[{"name":"packs/foo","layer":"...","owner":"..."}],"edges":[{"from":"packs/foo","to":"packs/baz","kind":"declared"}]}Edges are tagged by
kind:declared— fromdependencies:in package.ymlignored— fromignored_dependencies:todo— a recorded violation in the source pack'spackage_todo.ymlWhy
Tools that want to analyze the dependency graph (cycle/SCC decomposition, keystone/leverage ranking, visualization) currently re-parse
package.yml/package_todo.ymland re-implement resolution, which drifts from howpksactually resolves dependencies. This exposes the graphpksalready builds as a stable, machine-readable artifact those tools can consume directly.Scope (deliberately minimal)
pkskeeps owning enforcement (validate) and constant resolution (list-definitions); this is just the graph.name, edges by(from, to, kind), serialized compactly, so repeated runs on unchanged config are byte-identical.serde/serde_jsonand the parsedPackSet.Conventions
Follows the JSON conventions established for
pks check -o json:schema/graph-output.json(draft-07,additionalProperties:false,required,$defs+$ref,kindenum), mirroringschema/check-output.json.serde_json::to_writer(raw data; consumers format as needed).EdgeKindenum (declared/ignored/todo); module doc-comment references the schema.Verified the emitted output validates against
schema/graph-output.json(draft-07) across thesimple_app,contains_package_todo, andapp_with_ignored_dependencyfixtures.Tests
src/packs/graph.rs): determinism, node/edge ordering, declared edges, todo edges.tests/graph_test.rs,assert_cmd): declared / todo / ignored edges end-to-end + CLI-level determinism (two runs => byte-identical stdout).cargo fmt,cargo clippy, and the fullcargo testsuite are green.Possible follow-ups (out of scope here)
pks references(per-edge call sites): a command that enumerates the actual references composing an edge — including legal/declared edges, not just violations — by exposing thereference_extractorpksalready runs. Motivation: forming a modularization plan needs every call site of an edge to migrate, andrg/grep miss references that use non-fully-qualified constant names (relative consts, nested-module lookups).check -o jsonsurfaces references only for violation edges;list-definitionsgives constant authority but not usages. This would close that gap and let downstream planning tools resolve call sites the waypksdoes rather than re-implementing (and drifting from) constant resolution.graphoutput againstschema/graph-output.json. Left out for now to match howchecktreats its schema (documentation, not a test-enforced gate) and to avoid adding a JSON-schema validator dev-dependency — flagging in case maintainers prefer an enforced guard.summaryobject:checkoutput includes asummary(counts +success); omitted here to keep the graph dump raw/lean (counts are trivially derivable), but happy to add for structural parity.from/toand nodes usename(graph vocabulary) rather thancheck'sreferencing_pack_name/defining_pack_name— open to aligning if consistency is preferred over graph idiom.Draft: opening for visibility; will socialize internally before requesting review.
🤖 Generated with Claude Code