Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ai-fast-graph-source-check-trigger
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
temporary branch-only CI trigger; remove after source-check completes
29 changes: 29 additions & 0 deletions .github/workflows/ai-fast-graph-source-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: temporary fast graph source check

on:
push:
branches: ["ai/fast-manifest-dependency-graph"]

permissions:
contents: read

jobs:
source-check:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30
with:
toolchain: 1.98.1
components: rustfmt
- name: Format
run: cargo fmt --all -- --check
- name: Refresh lock only inside the ephemeral runner
run: cargo generate-lockfile
- name: Compile all targets
run: cargo check --all-targets
- name: Run focused local graph tests
run: cargo test graph_local
50 changes: 47 additions & 3 deletions .graph-cli-flags.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ errors_env = "ZED_PKG_PARSE_ERRORS"
allow_unknown = false

[help]
url = "https://github.com/zed-pkg/zed-cli/blob/main/docs/package-dependency-graph.md"
url = "https://github.com/zed-pkg/zed-cli/blob/main/docs/dependency-graph-cli.md"

[flags.registry]
env = "ZED_PKG_REGISTRY"
Expand Down Expand Up @@ -52,14 +52,14 @@ env = "ZED_PKG_INTERACTIVE"
aliases = ["interactive"]
type = "bool"
default = "false"
help = "Global lifecycle confirmation mode; graph downloads remain non-interactive."
help = "Global lifecycle confirmation mode; graph commands remain non-interactive."

[flags.git_submodules]
env = "ZED_PKG_GIT_SUBMODULES"
aliases = ["git-submodules"]
type = "bool"
default = "false"
help = "Global Git submodule compatibility mode; graph downloads never mutate submodules."
help = "Global Git submodule compatibility mode; graph commands never mutate submodules."

[flags.no_mirrors]
env = "ZED_PKG_NO_MIRRORS"
Expand Down Expand Up @@ -132,3 +132,47 @@ aliases = ["metadata-json"]
type = "bool"
default = "false"
help = "Emit deterministic response metadata as JSON on stderr."

[commands.graph.commands.local]
help = "Resolve the complete prospective dependency graph for a local .zpkg.toml."

[commands.graph.commands.local.flags.manifest]
env = "ZED_PKG_GRAPH_MANIFEST"
aliases = ["manifest"]
type = "string"
default = ".zpkg.toml"
help = "Path to .zpkg.toml, or a directory containing it."

[commands.graph.commands.local.flags.output]
env = "ZED_PKG_GRAPH_OUTPUT"
aliases = ["output"]
type = "string"
help = "Output JSON path; omit or use - for stdout."

[commands.graph.commands.local.flags.pretty]
env = "ZED_PKG_GRAPH_PRETTY"
aliases = ["pretty"]
type = "bool"
default = "false"
help = "Pretty-print JSON instead of compact machine output."

[commands.graph.commands.local.flags.runtime_only]
env = "ZED_PKG_GRAPH_RUNTIME_ONLY"
aliases = ["runtime-only"]
type = "bool"
default = "false"
help = "Resolve runtime dependencies only; omit build dependencies."

[commands.graph.commands.local.flags.allow_artifact_fallback]
env = "ZED_PKG_GRAPH_ALLOW_ARTIFACT_FALLBACK"
aliases = ["allow-artifact-fallback"]
type = "bool"
default = "false"
help = "Permit verified package artifact downloads when declared graph metadata is unavailable."

[commands.graph.commands.local.flags.max_metadata_bytes]
env = "ZED_PKG_GRAPH_MAX_METADATA_BYTES"
aliases = ["max-metadata-bytes"]
type = "integer"
default = "33554432"
help = "Maximum bytes accepted from one immutable declared graph response."
92 changes: 92 additions & 0 deletions docs/dependency-graph-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Dependency graph CLI

`zed graph` has two deliberately different graph operations:

- `zed graph package <org>/<name>@<version>` downloads the registry's immutable graph artifact for one exact package version.
- `zed graph local` resolves the complete **prospective** graph for a local `.zpkg.toml` without installing packages or writing a project lockfile.

The local command is intended for AI agents, editors, CI admission checks, dependency auditors, and other tools that need the whole selected graph before they decide whether to mutate a checkout.

## Fast local resolution

From a package root:

```sh
zed graph local
```

Or point at a manifest explicitly:

```sh
zed graph local --manifest path/to/.zpkg.toml
```

Compact deterministic JSON is written to stdout by default. Use `--pretty` for humans or `--output graph.json` for an atomic no-clobber file write.

The default graph includes runtime and build dependencies because both can affect a successful package operation. Use `--runtime-only` when a consumer specifically wants the runtime projection.

## Why this is faster than install/tree inspection

The ordinary installer must be able to materialize artifacts. Historical dependency inspection paths also rely on a lockfile and/or manifests from already-materialized dependencies. Neither is ideal for a fresh `.zpkg.toml`.

`zed graph local` instead asks the registry for each selected package version's immutable `view=declared` graph document. That small document carries the dependency requirements needed for recursive solving, so the client does not download and extract every package archive merely to discover its manifest.

The solver keeps package, candidate, and declared-graph metadata in memory for the duration of the calculation. Version selection is deterministic and backtracks only on semantic constraint conflicts. Transport, authentication, malformed graph, identity mismatch, and integrity errors are operational failures and are never reinterpreted as reasons to select an older version.

## Output contract

The prospective output uses `zpkg/local-dependency-graph/v1`, not the authoritative resolved `zpkg/dependency-graph/v1` shape. A pre-lock analysis does not yet possess the registry-snapshot and lock provenance required by the resolved wire contract, so it must not pretend to be that artifact.

The JSON contains:

- `root`: the local package coordinate;
- `nodes`: one exact selected version per package, including source (`root`, `registry`, `workspace`, or `path_override`) and registry artifact SHA-256 when applicable;
- `edges`: exact selected `from`/`to` coordinates plus the original requirement and dependency kind;
- `complete: true`: emitted only after the whole active graph resolves successfully;
- `stats`: registry reads, declared-graph cache hits, and any explicit artifact fallback work;
- `analysis_digest`: SHA-256 over the semantic graph fields (`schema`, `complete`, `root`, `nodes`, `edges`). Performance counters are intentionally excluded so repeated equivalent analyses have the same digest.

Nodes and edges are sorted and deduplicated before serialization. Compact output is therefore suitable for hashing, caching, diffing, and direct model/tool ingestion.

## Workspace and override behavior

The command honors workspace members and `[overrides.path]` using the same local manifest sources as package resolution. Explicit path overrides take precedence over workspace candidates.

The ambient machine-wide local registry is intentionally not consulted by this command. A graph intended for automation should not silently change because an unrelated checkout was registered on one developer machine. Put local dependencies in the workspace or declare a path override when they are part of the intended graph.

## Artifact fallback

Fast mode fails closed when an HTTP registry does not expose immutable declared-graph metadata. For an older registry or a `file://` registry, explicitly permit the traditional verified artifact-manifest path:

```sh
zed graph local --allow-artifact-fallback
```

That mode may download and extract package artifacts into the ordinary Zed store. The JSON `stats.artifact_downloads` and `stats.artifact_manifest_fallbacks` fields make that visible to automation.

## Safety and bounds

The metadata path:

- requires HTTPS outside explicit loopback registries;
- does not follow HTTP redirects;
- applies a 30-second request timeout;
- accepts at most 32 MiB per graph document by default (`--max-metadata-bytes` can lower the bound, but cannot exceed the shared graph-contract limit);
- requires canonical JSON with a valid semantic graph digest and verifies a present digest response header against the document;
- verifies requested package identity against both registry version metadata and declared graph identity;
- rejects cross-registry edges rather than silently resolving them against the wrong registry;
- caps recursive provenance depth at 256 and active package coordinates at 10,000;
- preserves the shared graph-contract limits of 50,000 nodes and 500,000 edges;
- bounds conflict provenance shown in diagnostics.

Output files are created atomically beside their destination and refuse to clobber an existing file.

## AI/tooling pattern

A tooling process can treat successful stdout as one self-contained dependency snapshot:

```sh
zed graph local --manifest .zpkg.toml > /tmp/graph.json
```

For a successful fast-path calculation, `stats.artifact_downloads` should be `0`. Cache downstream analysis by `analysis_digest`, then use the exact `nodes` and `edges` arrays for traversal, impact analysis, cycle detection, policy checks, or context selection.
Loading
Loading