codingest 0.1.5__tar.gz → 0.1.7__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 (84) hide show
  1. {codingest-0.1.5 → codingest-0.1.7}/Cargo.lock +8 -8
  2. {codingest-0.1.5 → codingest-0.1.7}/Cargo.toml +9 -5
  3. {codingest-0.1.5 → codingest-0.1.7}/PKG-INFO +9 -5
  4. {codingest-0.1.5 → codingest-0.1.7}/README.md +7 -3
  5. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/Cargo.toml +1 -1
  6. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/bin/codingest_bench.rs +5 -0
  7. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/bin/codingest_stats.rs +166 -24
  8. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/call_edges.rs +194 -19
  9. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/load/entity_frames.rs +34 -1
  10. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/other_edges.rs +140 -15
  11. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/routes/django.rs +136 -11
  12. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/docs/mod.rs +255 -29
  13. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/python.rs +726 -2
  14. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/shared.rs +88 -1
  15. codingest-0.1.7/crates/codingest/src/parsers/typescript.rs +2764 -0
  16. codingest-0.1.7/crates/codingest/tests/goldens/README.md +146 -0
  17. codingest-0.1.7/crates/codingest/tests/goldens/docs_mdx.sha256 +1 -0
  18. codingest-0.1.7/crates/codingest/tests/goldens/py_nested_defs.sha256 +1 -0
  19. codingest-0.1.7/crates/codingest/tests/goldens/ts_closure_scope.sha256 +1 -0
  20. codingest-0.1.7/crates/codingest/tests/goldens/ts_hof_binding.sha256 +1 -0
  21. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/parity.rs +42 -0
  22. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/Cargo.toml +1 -1
  23. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-mcp/Cargo.toml +1 -1
  24. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-py/Cargo.toml +3 -3
  25. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-py/src/lib.rs +1 -1
  26. {codingest-0.1.5 → codingest-0.1.7}/pyproject.toml +2 -2
  27. codingest-0.1.5/crates/codingest/src/parsers/typescript.rs +0 -1281
  28. codingest-0.1.5/crates/codingest/tests/goldens/README.md +0 -80
  29. {codingest-0.1.5 → codingest-0.1.7}/LICENSE +0 -0
  30. {codingest-0.1.5 → codingest-0.1.7}/codingest/__init__.py +0 -0
  31. {codingest-0.1.5 → codingest-0.1.7}/codingest/__init__.pyi +0 -0
  32. {codingest-0.1.5 → codingest-0.1.7}/codingest/cli.py +0 -0
  33. {codingest-0.1.5 → codingest-0.1.7}/codingest/mcp_server.py +0 -0
  34. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/js_workspace.rs +0 -0
  35. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/load/edge_frames.rs +0 -0
  36. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/load.rs +0 -0
  37. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/mod.rs +0 -0
  38. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/routes/fastapi.rs +0 -0
  39. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/routes/flask.rs +0 -0
  40. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/routes/mod.rs +0 -0
  41. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/semantic_edges.rs +0 -0
  42. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/builder/type_edges.rs +0 -0
  43. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/cross_lang.rs +0 -0
  44. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/docs/rst.rs +0 -0
  45. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/lib.rs +0 -0
  46. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/manifest/mod.rs +0 -0
  47. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/models.rs +0 -0
  48. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/agc.rs +0 -0
  49. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/cpp.rs +0 -0
  50. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/csharp.rs +0 -0
  51. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/css.rs +0 -0
  52. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/dart.rs +0 -0
  53. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/go.rs +0 -0
  54. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/html.rs +0 -0
  55. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/java.rs +0 -0
  56. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/mod.rs +0 -0
  57. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/php.rs +0 -0
  58. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/registry.rs +0 -0
  59. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/rust_lang.rs +0 -0
  60. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/parsers/swift.rs +0 -0
  61. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/repo.rs +0 -0
  62. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/src/rev.rs +0 -0
  63. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/agc_apollo.rs +0 -0
  64. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/agc_basic.sha256 +0 -0
  65. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/cross_ts_py.sha256 +0 -0
  66. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/dup_minified_assets.sha256 +0 -0
  67. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/py_basic.sha256 +0 -0
  68. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/py_inheritance.sha256 +0 -0
  69. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/rust_xfile.sha256 +0 -0
  70. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/ts_callback.sha256 +0 -0
  71. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest/tests/goldens/ts_monorepo.sha256 +0 -0
  72. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/skills/codingest-code-review/SKILL.md +0 -0
  73. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/skills/codingest-code-review/references/mcp-upgrade.md +0 -0
  74. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/skills/codingest-code-review/references/public-repositories.md +0 -0
  75. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/skills/codingest-code-review/references/queries.md +0 -0
  76. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/code_tree_cli.rs +0 -0
  77. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/lib.rs +0 -0
  78. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/main.rs +0 -0
  79. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/query.rs +0 -0
  80. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/skill.rs +0 -0
  81. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/src/skill_assets.rs +0 -0
  82. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-cli/tests/exit_codes.rs +0 -0
  83. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-mcp/src/lib.rs +0 -0
  84. {codingest-0.1.5 → codingest-0.1.7}/crates/codingest-mcp/src/main.rs +0 -0
@@ -313,7 +313,7 @@ dependencies = [
313
313
 
314
314
  [[package]]
315
315
  name = "codingest"
316
- version = "0.1.5"
316
+ version = "0.1.7"
317
317
  dependencies = [
318
318
  "aho-corasick",
319
319
  "base64",
@@ -345,7 +345,7 @@ dependencies = [
345
345
 
346
346
  [[package]]
347
347
  name = "codingest-cli"
348
- version = "0.1.5"
348
+ version = "0.1.7"
349
349
  dependencies = [
350
350
  "anyhow",
351
351
  "clap",
@@ -357,7 +357,7 @@ dependencies = [
357
357
 
358
358
  [[package]]
359
359
  name = "codingest-mcp"
360
- version = "0.1.5"
360
+ version = "0.1.7"
361
361
  dependencies = [
362
362
  "anyhow",
363
363
  "codingest",
@@ -366,7 +366,7 @@ dependencies = [
366
366
 
367
367
  [[package]]
368
368
  name = "codingest-py"
369
- version = "0.1.5"
369
+ version = "0.1.7"
370
370
  dependencies = [
371
371
  "codingest",
372
372
  "codingest-cli",
@@ -1393,9 +1393,9 @@ dependencies = [
1393
1393
 
1394
1394
  [[package]]
1395
1395
  name = "kglite"
1396
- version = "0.15.5"
1396
+ version = "0.15.6"
1397
1397
  source = "registry+https://github.com/rust-lang/crates.io-index"
1398
- checksum = "4664fc457e5dd762f5775977f06eb5377634556fffae45e4a86e2eb1e1eb0bfb"
1398
+ checksum = "a8cf3016156af6dafbc1041fa443e94002686b6deeee0fea38f9b712c50c96f8"
1399
1399
  dependencies = [
1400
1400
  "bzip2",
1401
1401
  "chrono",
@@ -1428,9 +1428,9 @@ dependencies = [
1428
1428
 
1429
1429
  [[package]]
1430
1430
  name = "kglite-mcp-server"
1431
- version = "0.15.5"
1431
+ version = "0.15.6"
1432
1432
  source = "registry+https://github.com/rust-lang/crates.io-index"
1433
- checksum = "e5e5d2207a45eb77c90e60a772bc8cc60ceac3d7c6f3ad2b948e8079355e1df4"
1433
+ checksum = "afaf8e38626f3991d7d7fca93df9230ec7160af894e855bc5cbe49e6c0342ab3"
1434
1434
  dependencies = [
1435
1435
  "anyhow",
1436
1436
  "bytes",
@@ -3,7 +3,7 @@ members = ["crates/codingest", "crates/codingest-cli", "crates/codingest-mcp", "
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.5"
6
+ version = "0.1.7"
7
7
  edition = "2021"
8
8
  rust-version = "1.88"
9
9
  license = "MIT"
@@ -12,8 +12,12 @@ repository = "https://github.com/kkollsga/codingest"
12
12
  [workspace.dependencies]
13
13
  base64 = "0.22"
14
14
  sha2 = "0.11"
15
- # kglite engine + MCP server — imported as crates.io libraries. The 0.15.5
16
- # floor adds the two workspace controls codingest asked upstream for:
15
+ # kglite engine + MCP server — imported as crates.io libraries. The 0.15.6
16
+ # floor fixes mixed-selection vector dispatch, mixed-metric exact search,
17
+ # community-detection modularity scoring, sampled-centrality validation, and
18
+ # persisted HNSW validation, while adding the bulk node-degree primitive and
19
+ # graph-algorithm/vector-search performance improvements. It builds on the two
20
+ # workspace controls codingest asked upstream for in 0.15.5:
17
21
  # `workspace.sandbox_root`, a real containment boundary that confines
18
22
  # `set_root_dir` to a subtree instead of trusting the caller's path, and
19
23
  # `workspace.adopt_client_roots`. Both land on top of the generic
@@ -25,8 +29,8 @@ sha2 = "0.11"
25
29
  # and no disk storage. Neither patch changes `kglite::api`, graph output,
26
30
  # property encoding, or `.kgl` serialization.
27
31
  # Keep both libraries aligned so the graph and server share one engine release.
28
- kglite = { version = "0.15.5", default-features = false }
29
- kglite-mcp-server = { version = "0.15.5", default-features = false }
32
+ kglite = { version = "0.15.6", default-features = false }
33
+ kglite-mcp-server = { version = "0.15.6", default-features = false }
30
34
 
31
35
  # Mirror KGLite's release codegen so the standalone builder has
32
36
  # performance parity with the in-tree component (see KGLite
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codingest
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Programming Language :: Python :: 3
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.14
12
12
  Classifier: Programming Language :: Rust
13
13
  Classifier: Topic :: Software Development
14
14
  Classifier: Topic :: Software Development :: Libraries
15
- Requires-Dist: kglite>=0.15.5,<0.16
15
+ Requires-Dist: kglite>=0.15.6,<0.16
16
16
  License-File: LICENSE
17
17
  Summary: Build fast, queryable code graphs for AI agents, MCP code review, and local or GitHub repository analysis.
18
18
  Keywords: ai-agents,mcp,code-review,knowledge-graph,code-analysis,github,tree-sitter,cypher
@@ -60,6 +60,10 @@ a real `kglite.KnowledgeGraph`, ready for Cypher queries.
60
60
 
61
61
  - **Agent-ready MCP:** give an MCP client `graph_overview`, `cypher_query`,
62
62
  `read_code_source`, repository switching, and automatic graph refresh.
63
+ [opencode is set up and verified end to end](https://codingest.readthedocs.io/en/latest/mcp.html#using-codingest-mcp-with-opencode)
64
+ — including a config that needs no absolute paths — and `codingest skill
65
+ install` packages the review skill for Claude Code and Codex, which opencode
66
+ also discovers with no extra step.
63
67
  - **Fast local review:** map definitions, callers, dependencies, routes,
64
68
  inheritance, and affected tests without uploading or executing the project.
65
69
  - **Open-source intelligence:** clone and analyse a GitHub repository with one
@@ -187,7 +191,7 @@ codingest skill install # code-review Agent Skill
187
191
  ```
188
192
 
189
193
  The wheel bundles every grammar plus the `codingest` and `codingest-mcp`
190
- commands. KGLite ≥0.15.5 is installed automatically as the query/storage
194
+ commands. KGLite ≥0.15.6 is installed automatically as the query/storage
191
195
  engine; its MCP server and the transitive `mcp-methods` framework power the
192
196
  builder-aware Codingest server. Nothing else needs to be installed.
193
197
 
@@ -200,7 +204,7 @@ Python prerequisites.
200
204
  ```toml
201
205
  [dependencies]
202
206
  codingest = "0.1"
203
- kglite = "0.15.5"
207
+ kglite = "0.15.6"
204
208
  ```
205
209
 
206
210
  ```rust
@@ -272,7 +276,7 @@ diagnostic.
272
276
  ## Dependency policy
273
277
 
274
278
  `kglite` and `kglite-mcp-server` use matching crates.io requirements with a
275
- 0.15.5 minimum and a shared lockfile. This keeps the builder, persistence
279
+ 0.15.6 minimum and a shared lockfile. This keeps the builder, persistence
276
280
  handoff, and embedded MCP server on one compatible engine patch line.
277
281
 
278
282
  ## Parity with the (now-removed) in-tree component
@@ -33,6 +33,10 @@ a real `kglite.KnowledgeGraph`, ready for Cypher queries.
33
33
 
34
34
  - **Agent-ready MCP:** give an MCP client `graph_overview`, `cypher_query`,
35
35
  `read_code_source`, repository switching, and automatic graph refresh.
36
+ [opencode is set up and verified end to end](https://codingest.readthedocs.io/en/latest/mcp.html#using-codingest-mcp-with-opencode)
37
+ — including a config that needs no absolute paths — and `codingest skill
38
+ install` packages the review skill for Claude Code and Codex, which opencode
39
+ also discovers with no extra step.
36
40
  - **Fast local review:** map definitions, callers, dependencies, routes,
37
41
  inheritance, and affected tests without uploading or executing the project.
38
42
  - **Open-source intelligence:** clone and analyse a GitHub repository with one
@@ -160,7 +164,7 @@ codingest skill install # code-review Agent Skill
160
164
  ```
161
165
 
162
166
  The wheel bundles every grammar plus the `codingest` and `codingest-mcp`
163
- commands. KGLite ≥0.15.5 is installed automatically as the query/storage
167
+ commands. KGLite ≥0.15.6 is installed automatically as the query/storage
164
168
  engine; its MCP server and the transitive `mcp-methods` framework power the
165
169
  builder-aware Codingest server. Nothing else needs to be installed.
166
170
 
@@ -173,7 +177,7 @@ Python prerequisites.
173
177
  ```toml
174
178
  [dependencies]
175
179
  codingest = "0.1"
176
- kglite = "0.15.5"
180
+ kglite = "0.15.6"
177
181
  ```
178
182
 
179
183
  ```rust
@@ -245,7 +249,7 @@ diagnostic.
245
249
  ## Dependency policy
246
250
 
247
251
  `kglite` and `kglite-mcp-server` use matching crates.io requirements with a
248
- 0.15.5 minimum and a shared lockfile. This keeps the builder, persistence
252
+ 0.15.6 minimum and a shared lockfile. This keeps the builder, persistence
249
253
  handoff, and embedded MCP server on one compatible engine patch line.
250
254
 
251
255
  ## Parity with the (now-removed) in-tree component
@@ -10,7 +10,7 @@ keywords = ["knowledge-graph", "code-analysis", "tree-sitter", "cypher", "kglite
10
10
  categories = ["development-tools", "parser-implementations"]
11
11
 
12
12
  # Requires the kglite 0.15 engine API (`kglite::api::code_entities`); the
13
- # workspace pins the current 0.15 patch floor (0.15.5).
13
+ # workspace pins the current 0.15 patch floor (0.15.6).
14
14
 
15
15
  [lib]
16
16
  name = "codingest"
@@ -486,6 +486,11 @@ fn main() {
486
486
  "corpus_files": corpus.sha256.as_ref().map(|_| corpus.files),
487
487
  "corpus_bytes": corpus.sha256.as_ref().map(|_| corpus.bytes),
488
488
  "corpus_sha256": corpus.sha256,
489
+ // Fixed build configuration, echoed so a recorded row states it.
490
+ // Unlike codingest_stats, this harness has always built docs-ON,
491
+ // so the docs pass IS in its build times and node counts.
492
+ "include_tests": false,
493
+ "include_docs": true,
489
494
  "nodes": nodes,
490
495
  "edges": edges,
491
496
  "build_a_secs": (build_a_secs * 1000.0).round() / 1000.0,
@@ -15,10 +15,21 @@
15
15
  //! Usage:
16
16
  //! cargo run -p codingest --bin codingest_stats --release -- <path>
17
17
  //! cargo run -p codingest --bin codingest_stats --release -- <path> --include-tests
18
+ //! cargo run -p codingest --bin codingest_stats --release -- <path> --include-docs
18
19
  //! cargo run -p codingest --bin codingest_stats --release -- <path> --function-metrics
19
20
  //! cargo run -p codingest --bin codingest_stats --release -- <path> --edge-breakdown
20
21
  //! cargo run -p codingest --bin codingest_stats --release -- <path> --dump-calls a,b,c
21
22
  //!
23
+ //! `--include-docs` turns the docs pass on. It is **off by default**, which is
24
+ //! the configuration every historical row in `dev-docs/bench/results/` was
25
+ //! measured at, so the default is the comparable one; without the flag `:Doc`,
26
+ //! `:MENTIONS` and `:DOCUMENTS` are structurally absent and no docs-pass
27
+ //! regression — in node counts or in build time — can be seen here. Both the
28
+ //! tests and the docs configuration are echoed into the emitted JSON so a
29
+ //! recorded row states the configuration it was taken under, and an unknown
30
+ //! flag is a usage error rather than a silently-ignored typo that would report
31
+ //! a measurement under a mode it never ran in.
32
+ //!
22
33
  //! `--edge-breakdown` appends a per-connection-type edge histogram to the JSON
23
34
  //! object, with `IMPORTS` split by endpoint node type (`IMPORTS(File->File)`
24
35
  //! vs `IMPORTS(File->Module)`) — the two are the same connection type but
@@ -231,37 +242,76 @@ fn dump_calls(graph: &DirGraph, names: &BTreeSet<String>) -> Vec<CallRow> {
231
242
  rows
232
243
  }
233
244
 
245
+ const USAGE: &str = "usage: codingest_stats <path> [--include-tests] [--include-docs] \
246
+ [--function-metrics] [--edge-breakdown] [--dump-calls name1,name2,...]";
247
+
248
+ /// Everything the command line can say, parsed once so the configuration a run
249
+ /// was taken under is a value that can be echoed and tested rather than a
250
+ /// scatter of `any(|a| a == …)` predicates.
251
+ #[derive(Debug, Default, PartialEq, Eq)]
252
+ struct Options {
253
+ include_tests: bool,
254
+ include_docs: bool,
255
+ function_metrics: bool,
256
+ edge_breakdown: bool,
257
+ dump_calls: Option<BTreeSet<String>>,
258
+ }
259
+
260
+ /// Parse the arguments after `<path>`. Rejects anything unrecognised: a
261
+ /// typo'd `--include-dcos` must not report a docs-off measurement as a docs-on
262
+ /// one, which is the same reason `codingest_bench` rejects unknown flags.
263
+ fn parse_options(args: &[String]) -> Result<Options, String> {
264
+ let mut out = Options::default();
265
+ let mut i = 0;
266
+ while i < args.len() {
267
+ match args[i].as_str() {
268
+ "--include-tests" => out.include_tests = true,
269
+ "--include-docs" => out.include_docs = true,
270
+ "--function-metrics" => out.function_metrics = true,
271
+ "--edge-breakdown" => out.edge_breakdown = true,
272
+ "--dump-calls" => {
273
+ let list = args.get(i + 1).ok_or_else(|| {
274
+ "--dump-calls requires a comma-separated name list".to_string()
275
+ })?;
276
+ out.dump_calls = Some(
277
+ list.split(',')
278
+ .map(str::trim)
279
+ .filter(|s| !s.is_empty())
280
+ .map(str::to_string)
281
+ .collect(),
282
+ );
283
+ i += 1;
284
+ }
285
+ other => return Err(format!("unknown argument `{other}`")),
286
+ }
287
+ i += 1;
288
+ }
289
+ Ok(out)
290
+ }
291
+
234
292
  fn main() {
235
293
  let mut args = std::env::args().skip(1);
236
294
  let path = match args.next() {
237
295
  Some(p) => p,
238
296
  None => {
239
- eprintln!(
240
- "usage: codingest_stats <path> [--include-tests] [--function-metrics] \
241
- [--edge-breakdown] [--dump-calls name1,name2,...]"
242
- );
297
+ eprintln!("{USAGE}");
243
298
  std::process::exit(2);
244
299
  }
245
300
  };
246
- let options: Vec<_> = args.collect();
247
- let include_tests = options.iter().any(|a| a == "--include-tests");
248
- let emit_function_metrics = options.iter().any(|a| a == "--function-metrics");
249
- let emit_edge_breakdown = options.iter().any(|a| a == "--edge-breakdown");
250
- let dump_call_names: Option<BTreeSet<String>> = options
251
- .iter()
252
- .position(|a| a == "--dump-calls")
253
- .map(|i| match options.get(i + 1) {
254
- Some(list) => list
255
- .split(',')
256
- .map(str::trim)
257
- .filter(|s| !s.is_empty())
258
- .map(str::to_string)
259
- .collect(),
260
- None => {
261
- eprintln!("--dump-calls requires a comma-separated name list");
262
- std::process::exit(2);
263
- }
264
- });
301
+ let rest: Vec<String> = args.collect();
302
+ let options = match parse_options(&rest) {
303
+ Ok(options) => options,
304
+ Err(e) => {
305
+ eprintln!("{e}");
306
+ eprintln!("{USAGE}");
307
+ std::process::exit(2);
308
+ }
309
+ };
310
+ let include_tests = options.include_tests;
311
+ let include_docs = options.include_docs;
312
+ let emit_function_metrics = options.function_metrics;
313
+ let emit_edge_breakdown = options.edge_breakdown;
314
+ let dump_call_names = options.dump_calls.clone();
265
315
 
266
316
  let t = Instant::now();
267
317
  let (graph, stats) = match codingest::builder::run_with_options_stats(
@@ -270,7 +320,7 @@ fn main() {
270
320
  include_tests,
271
321
  None,
272
322
  None,
273
- false,
323
+ include_docs,
274
324
  ) {
275
325
  Ok(pair) => pair,
276
326
  Err(e) => {
@@ -306,6 +356,7 @@ fn main() {
306
356
  let mut out = serde_json::json!({
307
357
  "path": path,
308
358
  "include_tests": include_tests,
359
+ "include_docs": include_docs,
309
360
  "build_secs": (build_secs * 1000.0).round() / 1000.0,
310
361
  "nodes": graph.graph.node_count(),
311
362
  "edges": graph.graph.edge_count(),
@@ -343,6 +394,97 @@ mod tests {
343
394
  .expect("corpus build")
344
395
  }
345
396
 
397
+ fn opts(args: &[&str]) -> Result<Options, String> {
398
+ parse_options(&args.iter().map(|a| a.to_string()).collect::<Vec<_>>())
399
+ }
400
+
401
+ #[test]
402
+ fn parse_options_defaults_to_the_historical_configuration() {
403
+ // Every row in dev-docs/bench/results/ predating --include-docs was
404
+ // measured tests-off/docs-off; the default must stay that way or old
405
+ // and new rows silently stop being comparable.
406
+ let parsed = opts(&[]).expect("empty argv parses");
407
+ assert_eq!(parsed, Options::default());
408
+ assert!(!parsed.include_tests);
409
+ assert!(!parsed.include_docs);
410
+ }
411
+
412
+ #[test]
413
+ fn parse_options_reads_every_flag_including_docs() {
414
+ let parsed = opts(&[
415
+ "--include-tests",
416
+ "--include-docs",
417
+ "--edge-breakdown",
418
+ "--function-metrics",
419
+ "--dump-calls",
420
+ "run, helper ,,other",
421
+ ])
422
+ .expect("all flags parse");
423
+ assert!(parsed.include_tests);
424
+ assert!(parsed.include_docs);
425
+ assert!(parsed.edge_breakdown);
426
+ assert!(parsed.function_metrics);
427
+ assert_eq!(
428
+ parsed.dump_calls,
429
+ Some(BTreeSet::from([
430
+ "run".to_string(),
431
+ "helper".to_string(),
432
+ "other".to_string()
433
+ ])),
434
+ "the name list is split, trimmed and empties dropped"
435
+ );
436
+ }
437
+
438
+ #[test]
439
+ fn parse_options_rejects_an_unknown_flag_but_not_a_dump_calls_value() {
440
+ // A typo must be a usage error: silently ignoring it would report a
441
+ // measurement under a configuration the run never used.
442
+ assert_eq!(
443
+ opts(&["--include-dcos"]),
444
+ Err("unknown argument `--include-dcos`".to_string())
445
+ );
446
+ assert_eq!(
447
+ opts(&["--dump-calls"]),
448
+ Err("--dump-calls requires a comma-separated name list".to_string())
449
+ );
450
+ // The value that follows --dump-calls is consumed, not re-inspected as
451
+ // a flag — even when it looks like one.
452
+ let parsed = opts(&["--dump-calls", "--include-docs", "--include-tests"])
453
+ .expect("the value after --dump-calls is a value");
454
+ assert!(!parsed.include_docs, "the value was not read as a flag");
455
+ assert!(parsed.include_tests);
456
+ }
457
+
458
+ #[test]
459
+ fn include_docs_is_what_makes_doc_nodes_visible() {
460
+ // The gap this flag closes: with the docs pass off — the hard-coded
461
+ // behaviour before it existed — a :Doc node cannot appear in any
462
+ // codingest_stats number, so no docs-pass regression can fail a gate.
463
+ fn doc_nodes(include_docs: bool) -> usize {
464
+ let graph = codingest::builder::run_with_options(
465
+ &corpus("docs_mdx"),
466
+ false,
467
+ true,
468
+ None,
469
+ None,
470
+ include_docs,
471
+ )
472
+ .expect("corpus build");
473
+ let params = HashMap::new();
474
+ let options = session::ExecuteOptions::eager(&params);
475
+ session::execute_read(&graph, "MATCH (d:Doc) RETURN d.name", &options)
476
+ .expect("query docs")
477
+ .result
478
+ .rows
479
+ .len()
480
+ }
481
+ assert_eq!(doc_nodes(false), 0, "docs off means no Doc node exists");
482
+ assert!(
483
+ doc_nodes(true) > 0,
484
+ "docs on must surface the docs_mdx corpus's Doc nodes"
485
+ );
486
+ }
487
+
346
488
  #[test]
347
489
  fn short_name_takes_the_rightmost_separator_of_any_flavour() {
348
490
  assert_eq!(short_name("pkg.util.helper"), "helper");