codecortex 0.18.0__tar.gz → 0.19.0__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 (129) hide show
  1. {codecortex-0.18.0/src/codecortex.egg-info → codecortex-0.19.0}/PKG-INFO +3 -2
  2. {codecortex-0.18.0 → codecortex-0.19.0}/README.md +2 -1
  3. {codecortex-0.18.0 → codecortex-0.19.0/src/codecortex.egg-info}/PKG-INFO +3 -2
  4. {codecortex-0.18.0 → codecortex-0.19.0}/src/codecortex.egg-info/SOURCES.txt +5 -0
  5. codecortex-0.19.0/src/codeintel/__init__.py +1 -0
  6. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/__main__.py +206 -20
  7. codecortex-0.19.0/src/codeintel/c4.py +829 -0
  8. codecortex-0.19.0/src/codeintel/commands/c4.py +182 -0
  9. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/http_server.py +16 -0
  10. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/mapper.py +62 -13
  11. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/providers/graph.py +69 -4
  12. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/server.py +163 -37
  13. codecortex-0.19.0/tests/test_c4.py +356 -0
  14. codecortex-0.19.0/tests/test_c4_model.py +127 -0
  15. codecortex-0.19.0/tests/test_c4_output.py +401 -0
  16. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_cli_commands.py +2 -0
  17. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_cli_help.py +92 -5
  18. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_graph_provider.py +60 -0
  19. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_mapper.py +58 -2
  20. codecortex-0.19.0/tests/test_mcp_server.py +190 -0
  21. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_never_raise.py +30 -4
  22. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_rbac.py +43 -0
  23. codecortex-0.18.0/src/codeintel/__init__.py +0 -1
  24. codecortex-0.18.0/tests/test_mcp_server.py +0 -78
  25. {codecortex-0.18.0 → codecortex-0.19.0}/LICENSE +0 -0
  26. {codecortex-0.18.0 → codecortex-0.19.0}/pyproject.toml +0 -0
  27. {codecortex-0.18.0 → codecortex-0.19.0}/setup.cfg +0 -0
  28. {codecortex-0.18.0 → codecortex-0.19.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
  29. {codecortex-0.18.0 → codecortex-0.19.0}/src/codecortex.egg-info/entry_points.txt +0 -0
  30. {codecortex-0.18.0 → codecortex-0.19.0}/src/codecortex.egg-info/requires.txt +0 -0
  31. {codecortex-0.18.0 → codecortex-0.19.0}/src/codecortex.egg-info/top_level.txt +0 -0
  32. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/agent_prompt.py +0 -0
  33. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/auth.py +0 -0
  34. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/cache.py +0 -0
  35. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/__init__.py +0 -0
  36. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/_common.py +0 -0
  37. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/doctor.py +0 -0
  38. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/gen_token.py +0 -0
  39. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/graph.py +0 -0
  40. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/index.py +0 -0
  41. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/install.py +0 -0
  42. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/map.py +0 -0
  43. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/prompt.py +0 -0
  44. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/query.py +0 -0
  45. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/reset.py +0 -0
  46. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/serve.py +0 -0
  47. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/serve_http.py +0 -0
  48. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/setup.py +0 -0
  49. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/commands/status.py +0 -0
  50. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/config.py +0 -0
  51. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/containment.py +0 -0
  52. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/doctor.py +0 -0
  53. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/gateway.py +0 -0
  54. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/graph_backend.py +0 -0
  55. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/graph_render.py +0 -0
  56. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/graph_resolution.py +0 -0
  57. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/grapher.py +0 -0
  58. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/indexer.py +0 -0
  59. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/injector.py +0 -0
  60. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/installer.py +0 -0
  61. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/loc.py +0 -0
  62. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/logconfig.py +0 -0
  63. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/metrics.py +0 -0
  64. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/onboarding.py +0 -0
  65. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/outcome.py +0 -0
  66. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/paths.py +0 -0
  67. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/policy.py +0 -0
  68. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/progress.py +0 -0
  69. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/provider.py +0 -0
  70. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/providers/__init__.py +0 -0
  71. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/providers/lsp.py +0 -0
  72. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/providers/none.py +0 -0
  73. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/providers/semantic.py +0 -0
  74. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/py.typed +0 -0
  75. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/redact.py +0 -0
  76. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/reindexer.py +0 -0
  77. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/reset.py +0 -0
  78. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/searcher.py +0 -0
  79. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/semantic_db.py +0 -0
  80. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/source_kind.py +0 -0
  81. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/term.py +0 -0
  82. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/verify.py +0 -0
  83. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/viewer/__init__.py +0 -0
  84. {codecortex-0.18.0 → codecortex-0.19.0}/src/codeintel/viewer/graph_template.html +0 -0
  85. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_agent_prompt.py +0 -0
  86. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_backend_reaper.py +0 -0
  87. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_cache.py +0 -0
  88. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_chunking.py +0 -0
  89. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_cold_process.py +0 -0
  90. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_config.py +0 -0
  91. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_corpus.py +0 -0
  92. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_docs_ci_claims.py +0 -0
  93. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_docs_deadcode_withdrawal.py +0 -0
  94. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_doctor.py +0 -0
  95. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_e2e.py +0 -0
  96. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_engine_adoption.py +0 -0
  97. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_enterprise.py +0 -0
  98. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_gateway.py +0 -0
  99. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_graph_failure_population.py +0 -0
  100. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_graph_real.py +0 -0
  101. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_graph_stdin.py +0 -0
  102. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_grapher.py +0 -0
  103. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_hardening.py +0 -0
  104. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_http_auth.py +0 -0
  105. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_http_server.py +0 -0
  106. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_incompleteness.py +0 -0
  107. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_index_progress.py +0 -0
  108. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_indexer_binary.py +0 -0
  109. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_installer.py +0 -0
  110. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_integration.py +0 -0
  111. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_loc_census.py +0 -0
  112. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_lsp_provider.py +0 -0
  113. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_lsp_real.py +0 -0
  114. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_mcp_handshake.py +0 -0
  115. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_model_dimension.py +0 -0
  116. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_onboarding.py +0 -0
  117. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_redaction_boundary.py +0 -0
  118. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_reindexer.py +0 -0
  119. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_rerank.py +0 -0
  120. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_reset.py +0 -0
  121. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_semantic_provider.py +0 -0
  122. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_source_kind.py +0 -0
  123. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_staleness.py +0 -0
  124. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_symbol_context.py +0 -0
  125. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_term.py +0 -0
  126. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_treesitter.py +0 -0
  127. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_verify.py +0 -0
  128. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_verify_call.py +0 -0
  129. {codecortex-0.18.0 → codecortex-0.19.0}/tests/test_version_skew.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.18.0
3
+ Version: 0.19.0
4
4
  Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
5
5
  Author: Shammai Hamilton
6
6
  License-Expression: MIT
@@ -279,7 +279,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
279
279
  ```text
280
280
  v claude: registered at /Users/you/.claude.json
281
281
 
282
- v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
282
+ v verified: codeintel 0.19.0 — 4 tools (code.query, code.status, code.doctor, code.map)
283
283
  ```
284
284
 
285
285
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -404,6 +404,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
404
404
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
405
405
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
406
406
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
407
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph — committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
407
408
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
408
409
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
409
410
 
@@ -242,7 +242,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
242
242
  ```text
243
243
  v claude: registered at /Users/you/.claude.json
244
244
 
245
- v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
245
+ v verified: codeintel 0.19.0 — 4 tools (code.query, code.status, code.doctor, code.map)
246
246
  ```
247
247
 
248
248
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -367,6 +367,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
367
367
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
368
368
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
369
369
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
370
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph — committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
370
371
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
371
372
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
372
373
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecortex
3
- Version: 0.18.0
3
+ Version: 0.19.0
4
4
  Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
5
5
  Author: Shammai Hamilton
6
6
  License-Expression: MIT
@@ -279,7 +279,7 @@ Then it launches the exact command it registered and drives a real MCP handshake
279
279
  ```text
280
280
  v claude: registered at /Users/you/.claude.json
281
281
 
282
- v verified: codeintel 0.18.0 — 4 tools (code.query, code.status, code.doctor, code.map)
282
+ v verified: codeintel 0.19.0 — 4 tools (code.query, code.status, code.doctor, code.map)
283
283
  ```
284
284
 
285
285
  If the command is not on `PATH`, or the server fails to start, install says so and exits non-zero
@@ -404,6 +404,7 @@ Full system docs live in [`docs/`](docs/) — start with the index:
404
404
  | `codeintel doctor [project_root] [--deep] [--json]` | Diagnose per-engine health + repo index status, with a fix for each gap |
405
405
  | `codeintel map [project_root]` | Generate the `CODE_INTEL.md` orientation file |
406
406
  | `codeintel graph [project_root] [--html] [--out FILE] [--limit N]` | Emit the call graph as `{nodes,edges}` JSON, or `--html` a self-contained interactive viewer — see [docs/graph-viewer.md](docs/graph-viewer.md) |
407
+ | `codeintel c4 [project_root] [--out DIR] [--depth N] [--scope PATH] [--include-tests] [--no-index] [--json]` | Write a LikeC4 architecture model (`.c4`) of the repo's file/directory structure and its import graph — committable, diffable, hand-editable source rather than a rendered picture. Indexes the repo first if it has no graph index. See [docs/c4.md](docs/c4.md) |
407
408
  | `codeintel reset [project_root] [--all] [--yes] [--json]` | Clear this repo's index — **both** semantic and graph — so it's as if never indexed; `--all` wipes every repo. Recovers from a corrupt/stale DB |
408
409
  | `codeintel gen-token` | Print a secure random bearer token (for `serve-http` / RBAC `auth.toml`) |
409
410
 
@@ -11,6 +11,7 @@ src/codeintel/__init__.py
11
11
  src/codeintel/__main__.py
12
12
  src/codeintel/agent_prompt.py
13
13
  src/codeintel/auth.py
14
+ src/codeintel/c4.py
14
15
  src/codeintel/cache.py
15
16
  src/codeintel/config.py
16
17
  src/codeintel/containment.py
@@ -46,6 +47,7 @@ src/codeintel/term.py
46
47
  src/codeintel/verify.py
47
48
  src/codeintel/commands/__init__.py
48
49
  src/codeintel/commands/_common.py
50
+ src/codeintel/commands/c4.py
49
51
  src/codeintel/commands/doctor.py
50
52
  src/codeintel/commands/gen_token.py
51
53
  src/codeintel/commands/graph.py
@@ -68,6 +70,9 @@ src/codeintel/viewer/__init__.py
68
70
  src/codeintel/viewer/graph_template.html
69
71
  tests/test_agent_prompt.py
70
72
  tests/test_backend_reaper.py
73
+ tests/test_c4.py
74
+ tests/test_c4_model.py
75
+ tests/test_c4_output.py
71
76
  tests/test_cache.py
72
77
  tests/test_chunking.py
73
78
  tests/test_cli_commands.py
@@ -0,0 +1 @@
1
+ __version__ = "0.19.0"
@@ -10,15 +10,16 @@ from codeintel import __version__
10
10
  # wanted. Each entry is (command, one-line description).
11
11
  _COMMAND_GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
12
12
  ("Understand your code", [
13
- ("query", "Ask one question — search, callers, callees, impact, chain, symbol, hotspots"),
13
+ ("query", "Ask one question — changed, impact, callers, chain, search"),
14
14
  ("map", "Write CODE_INTEL.md — a committable architecture overview"),
15
- ("graph", "Interactive call-graph viewer (--html), or the graph as JSON"),
15
+ ("graph", "Call graph of functions self-contained HTML, no install"),
16
+ ("c4", "Architecture map of files/folders — LikeC4 source, needs Node"),
16
17
  ]),
17
18
  ("Set up", [
18
- ("setup", "Prepare backends + index this repo (--all does everything automatable)"),
19
- ("index", "Index a project for semantic search"),
20
- ("install", "Register codeintel with the AI agents installed on this machine"),
21
- ("prompt", "Print a paste-to-your-agent setup prompt, tailored to this machine"),
19
+ ("setup", "Prepare backends + index this repo (--all does it all)"),
20
+ ("index", "Build the index every other command reads (semantic + graph)"),
21
+ ("install", "Register codeintel with the AI agents on this machine"),
22
+ ("prompt", "Print a paste-to-your-agent setup prompt for this machine"),
22
23
  ]),
23
24
  ("Check health", [
24
25
  ("doctor", "Per-engine health + index status, with the fix for each gap"),
@@ -42,6 +43,7 @@ _MODULES = {
42
43
  "query": "query",
43
44
  "map": "map",
44
45
  "graph": "graph",
46
+ "c4": "c4",
45
47
  "setup": "setup",
46
48
  "index": "index",
47
49
  "install": "install",
@@ -54,13 +56,30 @@ _MODULES = {
54
56
  "gen-token": "gen_token",
55
57
  }
56
58
 
59
+ # Deliberately NOT a second setup path. `_START_HERE` owns onboarding; these show what the tool is
60
+ # FOR once it works. The two blocks used to disagree — one said `index` first, the other
61
+ # `setup --all` — and neither of the numbered steps mentioned `install`, so following the "New
62
+ # here?" list end to end produced a working CLI and an agent that still greps. README's quickstart
63
+ # is `setup --all` -> `install` -> `query`; `_START_HERE` now matches it.
57
64
  _EXAMPLES = [
58
- ("codeintel setup --all .", "prepare backends and index this repo"),
59
- ("codeintel install", "register with the agents you have"),
60
- ("codeintel query --op callers --target my_function", "who calls it?"),
61
- ("codeintel doctor", "why is a query coming back empty?"),
65
+ ('codeintel query --op changed --target ""', "what do my edits break?"),
66
+ ("codeintel query --op callers --target foo", "who calls it?"),
67
+ ("codeintel graph . --html", "open the call graph"),
68
+ ("codeintel doctor", "why is a query empty?"),
62
69
  ]
63
70
 
71
+ # The ordered path out of an empty state. A first-time user's problem is not "which of 15 commands"
72
+ # but "what do I run first" — the command list answers the former and silently assumes the latter.
73
+ _START_HERE = [
74
+ ("codeintel setup --all .", "backends + index this repo"),
75
+ ("codeintel install", "connect it to your AI agent"),
76
+ ("codeintel doctor", "confirm it works"),
77
+ ]
78
+
79
+
80
+ HELP_WIDTH = 78 # the width every rendered help line must fit inside
81
+ HELP_GUTTER_CAP = 44 # ceiling on the derived comment column (see `gutter` below)
82
+
64
83
 
65
84
  def render_help() -> str:
66
85
  """The `codeintel` / `codeintel help` screen: grouped, colored, with real examples.
@@ -72,10 +91,31 @@ def render_help() -> str:
72
91
  width = max(len(name) for name in _COMMANDS)
73
92
  out = [
74
93
  c.bold("codeintel") + c.dim(f" {__version__}")
75
- + c.dim(" — code intelligence for AI agents: graph + LSP + semantic search"),
94
+ + c.dim(" — code intelligence for AI agents: graph, LSP, semantic"),
76
95
  "",
77
96
  c.dim("usage: ") + "codeintel <command> [options]",
78
97
  ]
98
+
99
+ # ONE comment column across both blocks, derived from the widest entry in either. Two blocks
100
+ # each self-aligning would ragged the screen into two gutters; the numbered prefix ("1. ") is
101
+ # part of the measured width so the two blocks' comments still line up.
102
+ # Capped. Derived-from-content is right, but an unbounded max lets ONE long invocation set the
103
+ # column for every short row: `codeintel doctor` was paying 38 blank columns and the line
104
+ # reached 96 chars, so an 80-column terminal wrapped the comment onto its own ragged row —
105
+ # losing the alignment the gutter exists to create. Past the cap a row keeps its comment one
106
+ # space away rather than aligned; a ragged row beats a wrapped screen.
107
+ gutter = min(max(max(len(cmd) + 3 for cmd, _why in _START_HERE),
108
+ max(len(cmd) for cmd, _why in _EXAMPLES)) + 2, HELP_GUTTER_CAP)
109
+ out.append("")
110
+ # A rule, not a new colour: cyan already means "this is a command name" on this screen, and
111
+ # diluting that costs more than it buys. The block needed separating from the four command
112
+ # groups below it — it was bold at the same weight and indent as every group heading, so the
113
+ # one block a first-time user needs looked like part of the reference list.
114
+ out.append(" " + c.rule(40))
115
+ out.append(" " + c.bold("New here?"))
116
+ for i, (cmd, why) in enumerate(_START_HERE, 1):
117
+ out.append(" " + f"{i}. {cmd}".ljust(gutter) + c.dim("# " + why))
118
+
79
119
  for group, items in _COMMAND_GROUPS:
80
120
  out.append("")
81
121
  out.append(" " + c.bold(group))
@@ -84,11 +124,11 @@ def render_help() -> str:
84
124
 
85
125
  out.append("")
86
126
  out.append(" " + c.bold("Examples"))
87
- # Width from the content, not a guessa hardcoded column silently loses its gutter the moment
88
- # one example grows past it, and ljust() will not pad below the string's own length.
89
- cmd_width = max(len(cmd) for cmd, _why in _EXAMPLES) + 2
127
+ # Same `gutter` as the New here? block abovewidth from the content, not a guess. A hardcoded
128
+ # column silently loses its gutter the moment one example grows past it, and ljust() will not
129
+ # pad below the string's own length.
90
130
  for cmd, why in _EXAMPLES:
91
- out.append(" " + cmd.ljust(cmd_width) + c.dim("# " + why))
131
+ out.append(" " + cmd.ljust(gutter) + c.dim("# " + why))
92
132
 
93
133
  out.append("")
94
134
  out.append(" " + c.dim("codeintel <command> --help") + " full options for one command")
@@ -118,7 +158,111 @@ def _unknown_command(name: str) -> int:
118
158
  return 2
119
159
 
120
160
 
121
- def main() -> None:
161
+ # `epilog` text for each subcommand: WHEN to reach for it, and one real invocation. The top-level
162
+ # screen (`render_help`) has grouped output, colour and examples; every `codeintel <cmd> --help`
163
+ # one level down was still stock argparse — flags listed, purpose unexplained, no example. A flag
164
+ # list answers "what can I pass"; it never answers "should I be running this at all".
165
+ #
166
+ # Kept as a table rather than inline `epilog=` kwargs so the wording of every screen is reviewable
167
+ # in one place, and so a command added without an entry degrades to today's behaviour (no epilog)
168
+ # rather than breaking.
169
+ _EPILOGS: dict[str, str] = {
170
+ "index": """examples:
171
+ codeintel index . index this repo (semantic + graph)
172
+ codeintel index ~/src/other-repo index a repo elsewhere
173
+
174
+ Run this first, and again after big changes. `codeintel status` shows index
175
+ age.""",
176
+ "query": """examples:
177
+ codeintel query --op changed
178
+ codeintel query --op search
179
+ codeintel query --op impact
180
+ codeintel query --op callers
181
+ codeintel query --op chain
182
+ codeintel query --op overview --target ""
183
+
184
+ `overview`, `changed` and `hotspots` ignore --target. Prefer this over grep:
185
+ results are ranked by graph importance, not by match order.""",
186
+ "map": """examples:
187
+ codeintel map . write CODE_INTEL.md
188
+ codeintel map . --inject also point CLAUDE.md / AGENTS.md at it
189
+
190
+ A committable, readable architecture overview — for agents that do not speak
191
+ MCP, and for reading before grepping. Re-run after `codeintel index`.""",
192
+ "graph": """examples:
193
+ codeintel graph . --html
194
+ codeintel graph . > graph.json the graph as {nodes,edges} JSON
195
+
196
+ Function-level "what calls what", with nothing to install. For file-level
197
+ architecture instead, see `codeintel c4`.""",
198
+ "c4": """examples:
199
+ codeintel c4 . write codeintel-c4/model.c4
200
+ codeintel c4 . --scope src model only src/
201
+ codeintel c4 . --depth 2 coarser: fewer, larger boxes
202
+ codeintel c4 . --json inspect the payload, write nothing
203
+
204
+ File/directory-level "what depends on what". Emits LikeC4 source you can
205
+ commit, diff and hand-edit — viewing it needs Node (`npx likec4 start
206
+ codeintel-c4`). For function-level calls, or if you have no Node, use
207
+ `codeintel graph --html` instead. Indexes the repo first if needed.""",
208
+ "status": """examples:
209
+ codeintel status
210
+
211
+ Shows which of the three engines are ready. If one is not, `codeintel doctor`
212
+ says how to fix it.""",
213
+ "doctor": """examples:
214
+ codeintel doctor
215
+ codeintel doctor --deep
216
+
217
+ Run this when a query comes back empty — it separates "not indexed" from
218
+ "engine missing" from "nothing to find".""",
219
+ "setup": """examples:
220
+ codeintel setup --all
221
+ codeintel setup
222
+
223
+ The one-shot path on a new machine.""",
224
+ "install": """examples:
225
+ codeintel install
226
+ codeintel install --dry-run show what would change, write nothing
227
+
228
+ Writes the MCP server config so an agent can call codeintel. Then restart the
229
+ agent (or start a new session) — a running host does not reload its MCP
230
+ config. `codeintel doctor` lists what got registered where.""",
231
+ "prompt": """examples:
232
+ codeintel prompt
233
+
234
+ For agents that cannot read an MCP config: paste the output into the
235
+ conversation.""",
236
+ "reset": """examples:
237
+ codeintel reset .
238
+ codeintel reset --all --yes wipe every repo, no prompt
239
+
240
+ Recovers from a corrupt or stale index. Re-index afterwards.""",
241
+ "serve": """examples:
242
+ codeintel serve start the MCP server on stdio
243
+
244
+ This is what an AI agent launches; you rarely run it by hand. `codeintel
245
+ install` wires it up.""",
246
+ "serve-http": """examples:
247
+ codeintel serve-http loopback only, no auth
248
+ codeintel serve-http --token "$(codeintel gen-token)"
249
+
250
+ Loopback-only unless --allow-remote. Use --token whenever the port is
251
+ reachable by anything else.""",
252
+ "gen-token": """examples:
253
+ codeintel gen-token print a random bearer token
254
+
255
+ For `serve-http --token`, or an RBAC auth.toml.""",
256
+ }
257
+
258
+
259
+ def build_parser() -> argparse.ArgumentParser:
260
+ """Construct the full argparse surface.
261
+
262
+ Split out of `main()` so the registered subcommands can be introspected directly. `--help` is
263
+ routed to `render_help()`, so argparse's own listing is no longer reachable from the CLI — and
264
+ that listing was how the help-honesty test proved every advertised command is really wired up.
265
+ Reading the parser is a stronger source of truth than scraping either screen's text."""
122
266
  parser = argparse.ArgumentParser(prog="codeintel")
123
267
  parser.add_argument("--version", action="version", version=f"codeintel {__version__}")
124
268
  subparsers = parser.add_subparsers(dest="command")
@@ -133,7 +277,7 @@ def main() -> None:
133
277
  # index subcommand
134
278
  index_parser = subparsers.add_parser(
135
279
  "index", parents=[color_parent],
136
- help="Index a project for semantic search")
280
+ help="Build the index every other command reads (semantic + graph)")
137
281
  index_parser.add_argument(
138
282
  "project_root",
139
283
  nargs="?",
@@ -205,7 +349,8 @@ def main() -> None:
205
349
 
206
350
  # map subcommand
207
351
  map_parser = subparsers.add_parser("map", help="Generate CODE_INTEL.md orientation file")
208
- map_parser.add_argument("project_root", nargs="?", default=None)
352
+ map_parser.add_argument("project_root", nargs="?", default=None,
353
+ help="Project root (default: cwd)")
209
354
  map_parser.add_argument("--inject", action="store_true", help="Inject reference block into CLAUDE.md/AGENTS.md")
210
355
  map_parser.add_argument("--budget", type=int, default=32768, help="Byte budget for CODE_INTEL.md (default: 32768)")
211
356
 
@@ -219,6 +364,27 @@ def main() -> None:
219
364
  graph_parser.add_argument("--out", default=None, help="Output path for --html (default: codeintel-graph.html)")
220
365
  graph_parser.add_argument("--limit", type=int, default=220, help="Max call edges to include (default: 220)")
221
366
 
367
+ # c4 subcommand — a LikeC4 model of the Folder/File + IMPORTS slice
368
+ c4_parser = subparsers.add_parser(
369
+ "c4", help="Generate a LikeC4 architecture model (.c4) from the import graph")
370
+ c4_parser.add_argument("project_root", nargs="?", default=None, help="Project root (default: cwd)")
371
+ c4_parser.add_argument("--out", default=None,
372
+ help="Output DIRECTORY (default: codeintel-c4/). LikeC4 merges every "
373
+ ".c4 in a directory into one project, so the model gets its own.")
374
+ c4_parser.add_argument("--depth", type=int, default=None,
375
+ help="Directory roll-up depth for elements (default: auto-fit to the "
376
+ "100-element view cap; the chosen depth is always reported)")
377
+ c4_parser.add_argument("--scope", action="append", default=None,
378
+ help="Limit the model to this path prefix; repeatable. A scope matching "
379
+ "no indexed file is an error, not a silent empty model.")
380
+ c4_parser.add_argument("--include-tests", action="store_true",
381
+ help="Model test directories too (excluded by default — they are not "
382
+ "architecture and outnumber source in some repos)")
383
+ c4_parser.add_argument("--no-index", action="store_true",
384
+ help="Fail instead of indexing an un-indexed repo (default: index it "
385
+ "first, so one command always produces a model)")
386
+ c4_parser.add_argument("--json", action="store_true", help="Print the payload; write nothing")
387
+
222
388
  # doctor subcommand
223
389
  doctor_parser = subparsers.add_parser("doctor", parents=[color_parent],
224
390
  help="Diagnose engine health + index status for a repo")
@@ -268,13 +434,33 @@ def main() -> None:
268
434
  subparsers.add_parser("gen-token", help="Print a secure random bearer token (for serve-http / RBAC auth.toml)")
269
435
  subparsers.add_parser("help", help="Show every command, grouped, with examples")
270
436
 
437
+ # Applied in one pass over the built parser rather than at each `add_parser` call site: an
438
+ # epilog is presentation, and threading two extra kwargs through fourteen construction sites
439
+ # would bury the flags that actually define each command. RawDescriptionHelpFormatter goes with
440
+ # it — argparse otherwise re-wraps the epilog and destroys the aligned example columns.
441
+ for _name, _sub in (subparsers.choices or {}).items():
442
+ _epilog = _EPILOGS.get(_name)
443
+ if _epilog:
444
+ _sub.epilog = _epilog
445
+ _sub.formatter_class = argparse.RawDescriptionHelpFormatter
446
+
447
+ return parser
448
+
449
+
450
+ def main() -> None:
451
+ parser = build_parser()
452
+
271
453
  # Intercept an unrecognized command BEFORE argparse, whose error prints the full choice list and
272
454
  # stops — a dead end for a one-character typo. Only a bare word is claimed here; anything
273
- # starting with `-` (--version, --help) still goes to argparse.
455
+ # starting with `-` (--version) still goes to argparse.
274
456
  argv = sys.argv[1:]
275
457
  if argv and not argv[0].startswith("-") and argv[0] not in [*_COMMANDS, "help"]:
276
458
  sys.exit(_unknown_command(argv[0]))
277
- if not argv or argv[0] == "help":
459
+ # `-h`/`--help` only counts as top-level when it is the FIRST token: `codeintel query --help`
460
+ # has argv[0] == "query", so it still falls through to argparse and gets that command's own
461
+ # screen. Without this, the command a new user actually types was the ONE path that missed
462
+ # render_help() and fell back to stock argparse.
463
+ if not argv or argv[0] in ("help", "-h", "--help"):
278
464
  from codeintel import term
279
465
  term.configure(no_color=False, ascii_mode=None)
280
466
  print(render_help())