dlin-cli 0.1.0__tar.gz → 0.1.1__tar.gz

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.
Files changed (107) hide show
  1. dlin_cli-0.1.1/CHANGELOG.md +11 -0
  2. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/Cargo.lock +1 -1
  3. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/Cargo.toml +1 -1
  4. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/PKG-INFO +32 -4
  5. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/README.md +31 -3
  6. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/cli.rs +73 -11
  7. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/filter.rs +439 -197
  8. dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot.snap +14 -0
  9. dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_bfs_pseudoendpoint.snap +10 -0
  10. dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_endpoints_fan_out.snap +17 -0
  11. dlin_cli-0.1.0/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_global.snap → dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_endpoints_leaf_model.snap +5 -5
  12. dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_multiple_focus_models.snap +26 -0
  13. dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_no_source_exposure.snap +10 -0
  14. dlin_cli-0.1.0/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_by_node_type.snap → dlin_cli-0.1.1/src/graph/snapshots/dlin__graph__filter__tests__collapse_snapshot_preserve_focus.snap +4 -7
  15. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/main.rs +17 -4
  16. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/mermaid.rs +5 -1
  17. dlin_cli-0.1.0/CHANGELOG.md +0 -5
  18. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/.github/workflows/check.yml +0 -0
  19. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/.github/workflows/publish-crate.yml +0 -0
  20. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/.github/workflows/publish-pypi.yml +0 -0
  21. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/.github/workflows/release.yml +0 -0
  22. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/.gitignore +0 -0
  23. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/LICENSE +0 -0
  24. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/assets/tui-demo.gif +0 -0
  25. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/dist-workspace.toml +0 -0
  26. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/pyproject.toml +0 -0
  27. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/error.rs +0 -0
  28. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/builder.rs +0 -0
  29. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/impact.rs +0 -0
  30. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/mod.rs +0 -0
  31. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/snapshots/dlin__graph__filter__tests__snapshot_transitive_node_type_filter.snap +0 -0
  32. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/snapshots/dlin__graph__filter__tests__snapshot_transitive_select_filter.snap +0 -0
  33. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/snapshots/dlin__graph__filter__tests__snapshot_transitive_select_with_node_type.snap +0 -0
  34. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/graph/types.rs +0 -0
  35. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/input.rs +0 -0
  36. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/lib.rs +0 -0
  37. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/cache.rs +0 -0
  38. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/columns.rs +0 -0
  39. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/discovery.rs +0 -0
  40. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/jinja.rs +0 -0
  41. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/manifest.rs +0 -0
  42. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/mod.rs +0 -0
  43. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/project.rs +0 -0
  44. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/sql.rs +0 -0
  45. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/parser/yaml_schema.rs +0 -0
  46. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/ascii.rs +0 -0
  47. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/dot.rs +0 -0
  48. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/html.rs +0 -0
  49. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/impact.rs +0 -0
  50. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/json.rs +0 -0
  51. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/layout.rs +0 -0
  52. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/list.rs +0 -0
  53. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/mod.rs +0 -0
  54. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/plain.rs +0 -0
  55. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__group_by_node_type.snap +0 -0
  56. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_all_edge_types.snap +0 -0
  57. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_direction_tb.snap +0 -0
  58. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_direction_tb_grouped.snap +0 -0
  59. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_group_by_directory.snap +0 -0
  60. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_lineage.snap +0 -0
  61. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__dot__tests__snapshot_transitive_edges.snap +0 -0
  62. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__html__tests__snapshot_html_json.snap +0 -0
  63. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__impact__tests__snapshot_impact_json.snap +0 -0
  64. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__impact__tests__snapshot_impact_json_with_sql.snap +0 -0
  65. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__impact__tests__snapshot_impact_text.snap +0 -0
  66. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__impact__tests__snapshot_impact_text_with_sql.snap +0 -0
  67. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__json__tests__snapshot_json_with_sql.snap +0 -0
  68. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__json__tests__snapshot_lineage.snap +0 -0
  69. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__json__tests__snapshot_node_metadata.snap +0 -0
  70. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__list__tests__snapshot_list_json.snap +0 -0
  71. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__list__tests__snapshot_list_plain.snap +0 -0
  72. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__group_by_node_type.snap +0 -0
  73. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__mixed_direct_and_transitive_edges.snap +0 -0
  74. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__show_columns_escapes_quotes.snap +0 -0
  75. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__show_columns_lineage.snap +0 -0
  76. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__show_columns_single_model.snap +0 -0
  77. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__show_columns_with_collapse.snap +0 -0
  78. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__show_columns_with_grouping.snap +0 -0
  79. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__snapshot_direction_tb.snap +0 -0
  80. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__snapshot_direction_tb_grouped.snap +0 -0
  81. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__snapshot_group_by_directory.snap +0 -0
  82. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__snapshot_lineage.snap +0 -0
  83. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__mermaid__tests__transitive_edge_rendering.snap +0 -0
  84. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__plain__tests__snapshot_plain.snap +0 -0
  85. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__summary__tests__snapshot_summary_json.snap +0 -0
  86. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/snapshots/dlin__render__summary__tests__snapshot_summary_text.snap +0 -0
  87. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/summary.rs +0 -0
  88. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/src/render/svg.rs +0 -0
  89. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/.dlin_cache/.gitignore +0 -0
  90. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/dbt_project.yml +0 -0
  91. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/macros/order_totals.sql +0 -0
  92. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/marts/combined_orders.sql +0 -0
  93. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/marts/customers.sql +0 -0
  94. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/marts/order_summary.sql +0 -0
  95. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/marts/orders.sql +0 -0
  96. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/marts/schema.yml +0 -0
  97. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/schema.yml +0 -0
  98. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/stg_customers.sql +0 -0
  99. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/stg_online_orders.sql +0 -0
  100. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/stg_orders.sql +0 -0
  101. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/stg_payments.sql +0 -0
  102. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/models/staging/stg_retail_orders.sql +0 -0
  103. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/seeds/countries.csv +0 -0
  104. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/target/manifest.json +0 -0
  105. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/target/run_results.json +0 -0
  106. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/fixtures/simple_project/tests/assert_orders_positive_amount.sql +0 -0
  107. {dlin_cli-0.1.0 → dlin_cli-0.1.1}/tests/integration_test.rs +0 -0
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1] - 2026-03-30
4
+
5
+ ### Features
6
+
7
+ - Add `--collapse=focal` mode for BFS-aware lineage simplification, and preserve focus models during collapse (#22, #23)
8
+
9
+ ## [0.1.0] - 2026-03-28
10
+
11
+ Initial release of **dlin**.
@@ -231,7 +231,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
231
231
 
232
232
  [[package]]
233
233
  name = "dlin"
234
- version = "0.1.0"
234
+ version = "0.1.1"
235
235
  dependencies = [
236
236
  "anyhow",
237
237
  "clap",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "dlin"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  edition = "2024"
5
5
  description = "A fast CLI tool for dbt model lineage analysis"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dlin-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -87,6 +87,30 @@ dlin list -o json --json-fields unique_id,file_path
87
87
  git diff --name-only main | dlin graph -o json
88
88
  ```
89
89
 
90
+ ## AI agent integration
91
+
92
+ No MCP server or tool configuration needed.
93
+ Just install dlin and add the following to your `AGENTS.md`, `CLAUDE.md`, or system prompt:
94
+
95
+ ````md
96
+ ## dbt project structure analysis
97
+
98
+ Use `dlin` to explore dbt model dependencies.
99
+ Do NOT grep/cat/find through SQL files.
100
+
101
+ ```bash
102
+ dlin summary # Project overview (start here)
103
+ dlin graph <model> -u 2 -d 1 -q # Upstream/downstream lineage
104
+ dlin impact <model> # Downstream impact with severity
105
+ dlin list -o json --json-fields unique_id,sql_content # Read SQL content
106
+ git diff --name-only main | dlin graph -q # Lineage of changed files
107
+ ```
108
+
109
+ For full option reference: `dlin --help`, `dlin graph --help`, etc.
110
+ ````
111
+
112
+ The key line is **"Do NOT grep/cat/find through SQL files"** — without it, agents default to familiar tools. `dlin --help` is designed for tool discovery, so the prompt can stay minimal.
113
+
90
114
  ## Features
91
115
 
92
116
  - **No dependencies**: single binary, no Python, no `manifest.json`
@@ -101,11 +125,15 @@ dlin outputs Mermaid flowcharts that render natively on GitHub, GitLab, Notion,
101
125
 
102
126
  ### Simplified graphs with `--collapse`
103
127
 
104
- Automatically remove intermediate nodes to see just the endpoints sources, leaf models, and exposures are kept; everything in between becomes transitive "(via N)" edges:
128
+ Automatically remove intermediate nodes to see just the endpoints (nodes with no predecessors or no successors); everything in between becomes transitive "(via N)" edges:
105
129
 
106
130
  ```sh
107
131
  # Collapse intermediate models — only endpoints remain
108
132
  dlin graph --collapse -o mermaid
133
+
134
+ # Focal mode: keep only sources, exposures, and specified focus models
135
+ # (ignores BFS window pseudo-endpoints — ideal with -u/-d limits)
136
+ dlin graph orders --collapse=focal -u 3 -o mermaid
109
137
  ```
110
138
 
111
139
  ```mermaid
@@ -135,7 +163,7 @@ flowchart LR
135
163
  class source_raw_payments source
136
164
  ```
137
165
 
138
- Combine with `--group-by` to collapse per group instead of globally for example, `--collapse --group-by directory` keeps the entry/exit models of each directory.
166
+ Positional focus models are always preserved during collapse, so `dlin graph orders --collapse` keeps `orders` even if it would otherwise be intermediate.
139
167
 
140
168
  ### Pipe to build focused diagrams
141
169
 
@@ -213,8 +241,8 @@ Combines well with `--collapse` to show rich detail on fewer endpoint nodes.
213
241
 
214
242
  ```sh
215
243
  dlin graph orders -u 2 -d 1 # focus on specific model
216
- dlin graph -o mermaid --collapse --group-by node-type # collapse per node type layer
217
244
  dlin graph -o mermaid --collapse --show-columns # columns in collapsed nodes
245
+ dlin graph orders --collapse=focal -u 3 -o mermaid # focal: sources + exposures + orders
218
246
  dlin graph -o mermaid --group-by directory # group by directory
219
247
  dlin graph -o mermaid --direction tb # top-to-bottom layout
220
248
  dlin graph --node-type source,exposure # filter by node type
@@ -65,6 +65,30 @@ dlin list -o json --json-fields unique_id,file_path
65
65
  git diff --name-only main | dlin graph -o json
66
66
  ```
67
67
 
68
+ ## AI agent integration
69
+
70
+ No MCP server or tool configuration needed.
71
+ Just install dlin and add the following to your `AGENTS.md`, `CLAUDE.md`, or system prompt:
72
+
73
+ ````md
74
+ ## dbt project structure analysis
75
+
76
+ Use `dlin` to explore dbt model dependencies.
77
+ Do NOT grep/cat/find through SQL files.
78
+
79
+ ```bash
80
+ dlin summary # Project overview (start here)
81
+ dlin graph <model> -u 2 -d 1 -q # Upstream/downstream lineage
82
+ dlin impact <model> # Downstream impact with severity
83
+ dlin list -o json --json-fields unique_id,sql_content # Read SQL content
84
+ git diff --name-only main | dlin graph -q # Lineage of changed files
85
+ ```
86
+
87
+ For full option reference: `dlin --help`, `dlin graph --help`, etc.
88
+ ````
89
+
90
+ The key line is **"Do NOT grep/cat/find through SQL files"** — without it, agents default to familiar tools. `dlin --help` is designed for tool discovery, so the prompt can stay minimal.
91
+
68
92
  ## Features
69
93
 
70
94
  - **No dependencies**: single binary, no Python, no `manifest.json`
@@ -79,11 +103,15 @@ dlin outputs Mermaid flowcharts that render natively on GitHub, GitLab, Notion,
79
103
 
80
104
  ### Simplified graphs with `--collapse`
81
105
 
82
- Automatically remove intermediate nodes to see just the endpoints sources, leaf models, and exposures are kept; everything in between becomes transitive "(via N)" edges:
106
+ Automatically remove intermediate nodes to see just the endpoints (nodes with no predecessors or no successors); everything in between becomes transitive "(via N)" edges:
83
107
 
84
108
  ```sh
85
109
  # Collapse intermediate models — only endpoints remain
86
110
  dlin graph --collapse -o mermaid
111
+
112
+ # Focal mode: keep only sources, exposures, and specified focus models
113
+ # (ignores BFS window pseudo-endpoints — ideal with -u/-d limits)
114
+ dlin graph orders --collapse=focal -u 3 -o mermaid
87
115
  ```
88
116
 
89
117
  ```mermaid
@@ -113,7 +141,7 @@ flowchart LR
113
141
  class source_raw_payments source
114
142
  ```
115
143
 
116
- Combine with `--group-by` to collapse per group instead of globally for example, `--collapse --group-by directory` keeps the entry/exit models of each directory.
144
+ Positional focus models are always preserved during collapse, so `dlin graph orders --collapse` keeps `orders` even if it would otherwise be intermediate.
117
145
 
118
146
  ### Pipe to build focused diagrams
119
147
 
@@ -191,8 +219,8 @@ Combines well with `--collapse` to show rich detail on fewer endpoint nodes.
191
219
 
192
220
  ```sh
193
221
  dlin graph orders -u 2 -d 1 # focus on specific model
194
- dlin graph -o mermaid --collapse --group-by node-type # collapse per node type layer
195
222
  dlin graph -o mermaid --collapse --show-columns # columns in collapsed nodes
223
+ dlin graph orders --collapse=focal -u 3 -o mermaid # focal: sources + exposures + orders
196
224
  dlin graph -o mermaid --group-by directory # group by directory
197
225
  dlin graph -o mermaid --direction tb # top-to-bottom layout
198
226
  dlin graph --node-type source,exposure # filter by node type
@@ -68,6 +68,14 @@ pub struct Cli {
68
68
  pub command: Command,
69
69
  }
70
70
 
71
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
72
+ pub enum CollapseMode {
73
+ /// Keep topological endpoints (in-degree=0 or out-degree=0) and focus models
74
+ Endpoints,
75
+ /// Keep only source/exposure nodes and focus models (ignores BFS window boundaries)
76
+ Focal,
77
+ }
78
+
71
79
  #[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
72
80
  pub enum GroupBy {
73
81
  /// Group nodes by node type (source, model, test, etc.)
@@ -202,6 +210,11 @@ Examples:
202
210
  dlin graph -o dot | dot -Tsvg > lineage.svg
203
211
  dlin graph -o mermaid --direction tb # top-to-bottom layout
204
212
 
213
+ # === Collapse (simplify by removing intermediate nodes) ===
214
+ dlin graph --collapse # keep only graph endpoints
215
+ dlin graph orders --collapse # endpoints + orders preserved
216
+ dlin graph orders --collapse=focal -u 3 # sources + exposures + orders only
217
+
205
218
  # === Column display (mermaid only) ===
206
219
  dlin graph -o mermaid --show-columns # show columns in node labels
207
220
  dlin graph -o mermaid --collapse --show-columns # rich detail on fewer nodes"
@@ -261,18 +274,24 @@ pub struct GraphArgs {
261
274
  #[arg(long)]
262
275
  pub no_transitive: bool,
263
276
 
264
- /// Collapse intermediate nodes, keeping only endpoints (nodes with no
265
- /// predecessors or no successors). Removed nodes are replaced by transitive
266
- /// edges with collapsed_through metadata (shown as "(via N)" in DOT/Mermaid).
267
- /// When combined with --group-by, endpoints are determined per group: a node
268
- /// is kept if it connects to a node outside its group, or has no
269
- /// predecessors/successors within its group.
270
- /// Without --group-by, global graph endpoints are kept.
271
- /// Tip: with --source manifest, tests may appear as downstream endpoints;
272
- /// combine with --node-type to exclude them if needed.
277
+ /// Collapse intermediate nodes, replacing them with transitive edges
278
+ /// shown as "(via N)" in DOT/Mermaid output.
279
+ ///
280
+ /// Without a value (--collapse), defaults to "endpoints" mode: keeps
281
+ /// nodes with no predecessors or no successors, plus focus models.
282
+ ///
283
+ /// With --collapse=focal: keeps only source/exposure nodes and focus
284
+ /// models as endpoints. Endpoint selection ignores BFS window boundaries
285
+ /// (-u/-d), so window-edge nodes are not treated as pseudo-endpoints,
286
+ /// but traversal still respects the -u/-d limits.
287
+ ///
288
+ /// Focus models are preserved even if they would otherwise be intermediate,
289
+ /// as long as they are not removed earlier by filters like --select or
290
+ /// --node-type.
291
+ ///
273
292
  /// Ignored when --no-transitive is set.
274
- #[arg(long)]
275
- pub collapse: bool,
293
+ #[arg(long, value_name = "MODE", default_missing_value = "endpoints", num_args = 0..=1, require_equals = true)]
294
+ pub collapse: Option<CollapseMode>,
276
295
 
277
296
  /// Group nodes using subgraph/cluster blocks in supported formats (dot, mermaid).
278
297
  /// Supported values: node-type (group by source, model, test, etc.),
@@ -1182,4 +1201,47 @@ mod tests {
1182
1201
  Cli::try_parse_from(["dlin", "--error-format", "json", "impact", "orders"]).unwrap();
1183
1202
  assert_eq!(cli.error_format, ErrorFormat::Json);
1184
1203
  }
1204
+
1205
+ // -- Collapse CLI parsing tests -------------------------------------------
1206
+
1207
+ #[test]
1208
+ fn test_collapse_none_by_default() {
1209
+ let args = unwrap_graph(Cli::try_parse_from(["dlin", "graph"]).unwrap());
1210
+ assert_eq!(args.collapse, None);
1211
+ }
1212
+
1213
+ #[test]
1214
+ fn test_collapse_bare_defaults_to_endpoints() {
1215
+ let args = unwrap_graph(Cli::try_parse_from(["dlin", "graph", "--collapse"]).unwrap());
1216
+ assert_eq!(args.collapse, Some(CollapseMode::Endpoints));
1217
+ }
1218
+
1219
+ #[test]
1220
+ fn test_collapse_explicit_endpoints() {
1221
+ let args =
1222
+ unwrap_graph(Cli::try_parse_from(["dlin", "graph", "--collapse=endpoints"]).unwrap());
1223
+ assert_eq!(args.collapse, Some(CollapseMode::Endpoints));
1224
+ }
1225
+
1226
+ #[test]
1227
+ fn test_collapse_focal() {
1228
+ let args =
1229
+ unwrap_graph(Cli::try_parse_from(["dlin", "graph", "--collapse=focal"]).unwrap());
1230
+ assert_eq!(args.collapse, Some(CollapseMode::Focal));
1231
+ }
1232
+
1233
+ #[test]
1234
+ fn test_collapse_invalid_mode_rejected() {
1235
+ let result = Cli::try_parse_from(["dlin", "graph", "--collapse=invalid"]);
1236
+ assert!(result.is_err());
1237
+ }
1238
+
1239
+ #[test]
1240
+ fn test_collapse_bare_does_not_consume_model() {
1241
+ // --collapse without = must not swallow the next positional as MODE
1242
+ let args =
1243
+ unwrap_graph(Cli::try_parse_from(["dlin", "graph", "--collapse", "orders"]).unwrap());
1244
+ assert_eq!(args.collapse, Some(CollapseMode::Endpoints));
1245
+ assert_eq!(args.model, vec!["orders".to_string()]);
1246
+ }
1185
1247
  }