clew-trace 1.0.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 (222) hide show
  1. clew_trace-1.0.0/.doxygen-guard.yaml +62 -0
  2. clew_trace-1.0.0/.gitignore +69 -0
  3. clew_trace-1.0.0/.pre-commit-config.yaml +58 -0
  4. clew_trace-1.0.0/LICENSE +21 -0
  5. clew_trace-1.0.0/PKG-INFO +284 -0
  6. clew_trace-1.0.0/README.md +245 -0
  7. clew_trace-1.0.0/clew/__init__.py +96 -0
  8. clew_trace-1.0.0/clew/__main__.py +14 -0
  9. clew_trace-1.0.0/clew/_common.py +173 -0
  10. clew_trace-1.0.0/clew/ast_symbols.py +1781 -0
  11. clew_trace-1.0.0/clew/buildoptions.py +696 -0
  12. clew_trace-1.0.0/clew/call_edges.py +1306 -0
  13. clew_trace-1.0.0/clew/callback_edges.py +1386 -0
  14. clew_trace-1.0.0/clew/cli.py +2464 -0
  15. clew_trace-1.0.0/clew/coverage.py +663 -0
  16. clew_trace-1.0.0/clew/critical_sections.py +597 -0
  17. clew_trace-1.0.0/clew/datamodel.py +1077 -0
  18. clew_trace-1.0.0/clew/declaration.py +616 -0
  19. clew_trace-1.0.0/clew/diagnostics.py +252 -0
  20. clew_trace-1.0.0/clew/dispatch.py +472 -0
  21. clew_trace-1.0.0/clew/dispatch_edges.py +765 -0
  22. clew_trace-1.0.0/clew/dominated_edges.py +356 -0
  23. clew_trace-1.0.0/clew/doxygen.py +1263 -0
  24. clew_trace-1.0.0/clew/enrichment.py +105 -0
  25. clew_trace-1.0.0/clew/errors.py +72 -0
  26. clew_trace-1.0.0/clew/event_edges.py +358 -0
  27. clew_trace-1.0.0/clew/export_command.py +183 -0
  28. clew_trace-1.0.0/clew/external.py +280 -0
  29. clew_trace-1.0.0/clew/filedocs.py +433 -0
  30. clew_trace-1.0.0/clew/gitenv.py +69 -0
  31. clew_trace-1.0.0/clew/guardconfig.py +512 -0
  32. clew_trace-1.0.0/clew/harvest.py +614 -0
  33. clew_trace-1.0.0/clew/harvest_plan.py +186 -0
  34. clew_trace-1.0.0/clew/indexcache.py +538 -0
  35. clew_trace-1.0.0/clew/init_command.py +613 -0
  36. clew_trace-1.0.0/clew/kconfig.py +796 -0
  37. clew_trace-1.0.0/clew/kconfig_gates.py +567 -0
  38. clew_trace-1.0.0/clew/locks.py +1068 -0
  39. clew_trace-1.0.0/clew/mcp_config.py +615 -0
  40. clew_trace-1.0.0/clew/mcp_server/__init__.py +32 -0
  41. clew_trace-1.0.0/clew/mcp_server/__main__.py +13 -0
  42. clew_trace-1.0.0/clew/mcp_server/_sdk.py +105 -0
  43. clew_trace-1.0.0/clew/mcp_server/descriptions/_provenance.md +134 -0
  44. clew_trace-1.0.0/clew/mcp_server/descriptions/_templates/ambiguous.json +33 -0
  45. clew_trace-1.0.0/clew/mcp_server/descriptions/_templates/disambiguate.json +40 -0
  46. clew_trace-1.0.0/clew/mcp_server/descriptions/_templates/provenance.json +39 -0
  47. clew_trace-1.0.0/clew/mcp_server/descriptions/_templates/rows.json +14 -0
  48. clew_trace-1.0.0/clew/mcp_server/descriptions/dossier.json +17 -0
  49. clew_trace-1.0.0/clew/mcp_server/descriptions/index.json +13 -0
  50. clew_trace-1.0.0/clew/mcp_server/descriptions/propose_declaration.json +8 -0
  51. clew_trace-1.0.0/clew/mcp_server/descriptions/search.json +17 -0
  52. clew_trace-1.0.0/clew/mcp_server/descriptions.py +239 -0
  53. clew_trace-1.0.0/clew/mcp_server/emptiness.py +449 -0
  54. clew_trace-1.0.0/clew/mcp_server/freshness.py +417 -0
  55. clew_trace-1.0.0/clew/mcp_server/server.py +1723 -0
  56. clew_trace-1.0.0/clew/mcp_server/state.py +905 -0
  57. clew_trace-1.0.0/clew/mcp_server/tools_query.py +1938 -0
  58. clew_trace-1.0.0/clew/precommit.py +386 -0
  59. clew_trace-1.0.0/clew/preprocessor.py +834 -0
  60. clew_trace-1.0.0/clew/propose/__init__.py +83 -0
  61. clew_trace-1.0.0/clew/propose/astdefs.py +432 -0
  62. clew_trace-1.0.0/clew/propose/command.py +250 -0
  63. clew_trace-1.0.0/clew/propose/context.py +56 -0
  64. clew_trace-1.0.0/clew/propose/dryrun.py +259 -0
  65. clew_trace-1.0.0/clew/propose/model.py +179 -0
  66. clew_trace-1.0.0/clew/propose/notindexed.py +322 -0
  67. clew_trace-1.0.0/clew/propose/registry.py +488 -0
  68. clew_trace-1.0.0/clew/propose/render.py +416 -0
  69. clew_trace-1.0.0/clew/propose/scanning.py +428 -0
  70. clew_trace-1.0.0/clew/propose/sharedkey_detect.py +319 -0
  71. clew_trace-1.0.0/clew/propose/sharedkey_report.py +409 -0
  72. clew_trace-1.0.0/clew/propose/threads_detect.py +443 -0
  73. clew_trace-1.0.0/clew/propose/threads_report.py +350 -0
  74. clew_trace-1.0.0/clew/prose.py +168 -0
  75. clew_trace-1.0.0/clew/py_entrypoints.py +405 -0
  76. clew_trace-1.0.0/clew/pyast.py +805 -0
  77. clew_trace-1.0.0/clew/query/__init__.py +239 -0
  78. clew_trace-1.0.0/clew/query/_common.py +679 -0
  79. clew_trace-1.0.0/clew/query/corpus.py +961 -0
  80. clew_trace-1.0.0/clew/query/dossier.py +638 -0
  81. clew_trace-1.0.0/clew/query/externcalls.py +242 -0
  82. clew_trace-1.0.0/clew/query/graph.py +487 -0
  83. clew_trace-1.0.0/clew/query/kconfig.py +519 -0
  84. clew_trace-1.0.0/clew/query/locks.py +731 -0
  85. clew_trace-1.0.0/clew/query/macros.py +214 -0
  86. clew_trace-1.0.0/clew/query/models.py +1931 -0
  87. clew_trace-1.0.0/clew/query/source.py +387 -0
  88. clew_trace-1.0.0/clew/query/subject.py +798 -0
  89. clew_trace-1.0.0/clew/query/symbols.py +1973 -0
  90. clew_trace-1.0.0/clew/query/traversal.py +412 -0
  91. clew_trace-1.0.0/clew/reachability.py +239 -0
  92. clew_trace-1.0.0/clew/requirements.py +780 -0
  93. clew_trace-1.0.0/clew/scope.py +729 -0
  94. clew_trace-1.0.0/clew/shared_key_edges.py +2452 -0
  95. clew_trace-1.0.0/clew/signature.py +353 -0
  96. clew_trace-1.0.0/clew/stagetimer.py +141 -0
  97. clew_trace-1.0.0/clew/threads.py +1516 -0
  98. clew_trace-1.0.0/clew/tiers.py +564 -0
  99. clew_trace-1.0.0/clew/tomlcompat.py +94 -0
  100. clew_trace-1.0.0/clew/treescan.py +371 -0
  101. clew_trace-1.0.0/clew/vocabulary.py +1080 -0
  102. clew_trace-1.0.0/clew/wire.py +193 -0
  103. clew_trace-1.0.0/docs/CORE_HYPOTHESIS.md +53 -0
  104. clew_trace-1.0.0/pyproject.toml +101 -0
  105. clew_trace-1.0.0/requirements.yaml +945 -0
  106. clew_trace-1.0.0/tests/conftest.py +270 -0
  107. clew_trace-1.0.0/tests/data/csample/.doxygen-guard.yaml +28 -0
  108. clew_trace-1.0.0/tests/data/csample/.pre-commit-config.yaml +10 -0
  109. clew_trace-1.0.0/tests/data/csample/README.md +25 -0
  110. clew_trace-1.0.0/tests/data/csample/data_model_keys.yaml +16 -0
  111. clew_trace-1.0.0/tests/data/csample/docs/architecture.md +56 -0
  112. clew_trace-1.0.0/tests/data/csample/gen/ingot/dm.c +60 -0
  113. clew_trace-1.0.0/tests/data/csample/gen/ingot/dm.h +25 -0
  114. clew_trace-1.0.0/tests/data/csample/gen/ingot/dm_helpers.h +39 -0
  115. clew_trace-1.0.0/tests/data/csample/gen/ingot/dm_key.h +10 -0
  116. clew_trace-1.0.0/tests/data/csample/gen/ingot/integer_storage.c +39 -0
  117. clew_trace-1.0.0/tests/data/csample/gen/ingot/integer_storage.h +11 -0
  118. clew_trace-1.0.0/tests/data/csample/requirements.yaml +40 -0
  119. clew_trace-1.0.0/tests/data/csample/src/command/command_handler.c +40 -0
  120. clew_trace-1.0.0/tests/data/csample/src/command/command_handler.h +6 -0
  121. clew_trace-1.0.0/tests/data/csample/src/dispatch/dm_event_dispatch.c +37 -0
  122. clew_trace-1.0.0/tests/data/csample/src/dispatch/dm_event_dispatch.h +6 -0
  123. clew_trace-1.0.0/tests/data/csample/src/event_bus/event_bus.c +78 -0
  124. clew_trace-1.0.0/tests/data/csample/src/event_bus/event_bus.h +24 -0
  125. clew_trace-1.0.0/tests/data/csample/src/legacy/legacy_calibration.c +48 -0
  126. clew_trace-1.0.0/tests/data/csample/src/main.c +47 -0
  127. clew_trace-1.0.0/tests/data/csample/src/sensor/sensor_driver.c +42 -0
  128. clew_trace-1.0.0/tests/data/csample/src/sensor/sensor_driver.h +7 -0
  129. clew_trace-1.0.0/tests/data/csample/src/sound/sound_service.c +18 -0
  130. clew_trace-1.0.0/tests/data/csample/src/sound/sound_service.h +12 -0
  131. clew_trace-1.0.0/tests/data/csample/src/telemetry/telemetry.c +30 -0
  132. clew_trace-1.0.0/tests/data/csample/src/telemetry/telemetry.h +7 -0
  133. clew_trace-1.0.0/tests/data/csample/test/test_sound.c +29 -0
  134. clew_trace-1.0.0/tests/data/doxygen_schema.sql +357 -0
  135. clew_trace-1.0.0/tests/data/kconfigsample/Kconfig +20 -0
  136. clew_trace-1.0.0/tests/data/kconfigsample/Kconfig.motor +22 -0
  137. clew_trace-1.0.0/tests/data/kconfigsample/src/motor.c +32 -0
  138. clew_trace-1.0.0/tests/data/pysample/__init__.py +8 -0
  139. clew_trace-1.0.0/tests/data/pysample/models.py +33 -0
  140. clew_trace-1.0.0/tests/data/pysample/spawner.py +230 -0
  141. clew_trace-1.0.0/tests/data/schema_vocabulary.json +176 -0
  142. clew_trace-1.0.0/tests/gitfixture.py +173 -0
  143. clew_trace-1.0.0/tests/integration/conftest.py +243 -0
  144. clew_trace-1.0.0/tests/integration/test_export_roundtrip.py +170 -0
  145. clew_trace-1.0.0/tests/integration/test_fixture_fidelity.py +150 -0
  146. clew_trace-1.0.0/tests/integration/test_gate_coverage_integration.py +304 -0
  147. clew_trace-1.0.0/tests/integration/test_indexcache_integration.py +616 -0
  148. clew_trace-1.0.0/tests/integration/test_manifest_replay_integration.py +625 -0
  149. clew_trace-1.0.0/tests/integration/test_richness.py +230 -0
  150. clew_trace-1.0.0/tests/integration/test_robustness_integration.py +141 -0
  151. clew_trace-1.0.0/tests/integration/test_self_index.py +346 -0
  152. clew_trace-1.0.0/tests/richdb.py +1159 -0
  153. clew_trace-1.0.0/tests/schema_snapshot.py +161 -0
  154. clew_trace-1.0.0/tests/test_ast_recovery.py +308 -0
  155. clew_trace-1.0.0/tests/test_ast_symbols.py +1218 -0
  156. clew_trace-1.0.0/tests/test_binding_edges.py +565 -0
  157. clew_trace-1.0.0/tests/test_build_docs_db.py +2210 -0
  158. clew_trace-1.0.0/tests/test_buildoptions.py +656 -0
  159. clew_trace-1.0.0/tests/test_cli_surface.py +484 -0
  160. clew_trace-1.0.0/tests/test_conceptual_search.py +116 -0
  161. clew_trace-1.0.0/tests/test_coverage.py +413 -0
  162. clew_trace-1.0.0/tests/test_critical_sections.py +728 -0
  163. clew_trace-1.0.0/tests/test_datamodel.py +939 -0
  164. clew_trace-1.0.0/tests/test_declaration.py +439 -0
  165. clew_trace-1.0.0/tests/test_descriptions.py +418 -0
  166. clew_trace-1.0.0/tests/test_diagnostics.py +530 -0
  167. clew_trace-1.0.0/tests/test_disambiguation.py +378 -0
  168. clew_trace-1.0.0/tests/test_dispatch.py +895 -0
  169. clew_trace-1.0.0/tests/test_doc_links.py +113 -0
  170. clew_trace-1.0.0/tests/test_dominated_edges.py +373 -0
  171. clew_trace-1.0.0/tests/test_dossier_batch.py +352 -0
  172. clew_trace-1.0.0/tests/test_dossier_one_shot.py +531 -0
  173. clew_trace-1.0.0/tests/test_doxyfile_refusal_messages.py +218 -0
  174. clew_trace-1.0.0/tests/test_emptiness.py +480 -0
  175. clew_trace-1.0.0/tests/test_enrichment.py +152 -0
  176. clew_trace-1.0.0/tests/test_event_edges.py +402 -0
  177. clew_trace-1.0.0/tests/test_external_provenance.py +514 -0
  178. clew_trace-1.0.0/tests/test_extra_input_patterns.py +370 -0
  179. clew_trace-1.0.0/tests/test_filedocs.py +295 -0
  180. clew_trace-1.0.0/tests/test_freshness.py +659 -0
  181. clew_trace-1.0.0/tests/test_git_env_isolation.py +121 -0
  182. clew_trace-1.0.0/tests/test_gitenv.py +148 -0
  183. clew_trace-1.0.0/tests/test_graph_stats.py +379 -0
  184. clew_trace-1.0.0/tests/test_guard_config_discovery.py +279 -0
  185. clew_trace-1.0.0/tests/test_guard_config_skew.py +343 -0
  186. clew_trace-1.0.0/tests/test_index_scope_inheritance.py +395 -0
  187. clew_trace-1.0.0/tests/test_indexcache_doxygen_cache.py +150 -0
  188. clew_trace-1.0.0/tests/test_init.py +1337 -0
  189. clew_trace-1.0.0/tests/test_kconfig.py +1125 -0
  190. clew_trace-1.0.0/tests/test_locks.py +444 -0
  191. clew_trace-1.0.0/tests/test_macro_query.py +463 -0
  192. clew_trace-1.0.0/tests/test_macro_recovery.py +371 -0
  193. clew_trace-1.0.0/tests/test_mcp_routing.py +515 -0
  194. clew_trace-1.0.0/tests/test_mcp_server.py +3250 -0
  195. clew_trace-1.0.0/tests/test_operator_excludes.py +292 -0
  196. clew_trace-1.0.0/tests/test_overrides.py +257 -0
  197. clew_trace-1.0.0/tests/test_packaging.py +243 -0
  198. clew_trace-1.0.0/tests/test_payload_completeness.py +266 -0
  199. clew_trace-1.0.0/tests/test_plugin_manifest.py +233 -0
  200. clew_trace-1.0.0/tests/test_preprocessor.py +810 -0
  201. clew_trace-1.0.0/tests/test_propose.py +868 -0
  202. clew_trace-1.0.0/tests/test_py310_compat.py +438 -0
  203. clew_trace-1.0.0/tests/test_python_ast.py +821 -0
  204. clew_trace-1.0.0/tests/test_query.py +1862 -0
  205. clew_trace-1.0.0/tests/test_r1_richness.py +892 -0
  206. clew_trace-1.0.0/tests/test_repair_attribute.py +103 -0
  207. clew_trace-1.0.0/tests/test_requirements_declared.py +419 -0
  208. clew_trace-1.0.0/tests/test_robustness.py +86 -0
  209. clew_trace-1.0.0/tests/test_roster_origin_split.py +642 -0
  210. clew_trace-1.0.0/tests/test_scope.py +507 -0
  211. clew_trace-1.0.0/tests/test_search_type_corpora.py +296 -0
  212. clew_trace-1.0.0/tests/test_self_edges.py +562 -0
  213. clew_trace-1.0.0/tests/test_shared_parse.py +353 -0
  214. clew_trace-1.0.0/tests/test_stage_timings.py +219 -0
  215. clew_trace-1.0.0/tests/test_subject_surface.py +590 -0
  216. clew_trace-1.0.0/tests/test_thread_spawns.py +542 -0
  217. clew_trace-1.0.0/tests/test_tiers.py +799 -0
  218. clew_trace-1.0.0/tests/test_treescan_pruning.py +248 -0
  219. clew_trace-1.0.0/tests/test_vocabulary.py +449 -0
  220. clew_trace-1.0.0/tests/test_wire.py +197 -0
  221. clew_trace-1.0.0/tests/vocab_dump.py +51 -0
  222. clew_trace-1.0.0/tests/wire_expect.py +90 -0
@@ -0,0 +1,62 @@
1
+ ---
2
+ # doxygen-guard configuration for clew (dogfooding — we enforce on ourselves).
3
+ #
4
+ # Every function/class in the shipped Python package carries:
5
+ # ## @brief one-line summary
6
+ # ## @version bumped on body change
7
+ # ## @return on non-void functions
8
+ #
9
+ # @req COVERAGE IS NOT GATED. It was, by scripts/req_coverage_audit.py, which is
10
+ # DELETED. Nothing now checks that a requirement claiming `implemented` resolves to
11
+ # an implementing function, or that one claiming `planned` resolves to none.
12
+ # Last measured by hand: 30 of 34 resolve; the 4 that do not are untagged
13
+ # deliberately (two satisfied by configuration and human review, two unbuilt).
14
+ #
15
+ # Also not gated: whether a TEST covers a requirement. req_test_edges is sparse.
16
+
17
+ output_dir: docs/generated/
18
+
19
+ # Declare where our catalog lives, so the INDEX finds it without a CLI flag.
20
+ # Not cosmetic dogfooding: `bd2d34e` taught clew to honour this field
21
+ # precisely because a repo that declares its catalog had the declaration read by
22
+ # the gate and ignored by the index — and we then shipped that fix without
23
+ # applying it to ourselves. A bare `--scope from-guard` build of this repo
24
+ # ingested ZERO catalog rows, so `req_trace` returned implementers with no title,
25
+ # status or acceptance, and the "30 of 34" figure was reproducible only with an
26
+ # undocumented `--requirements` argument. Through the MCP server, which passes no
27
+ # flags, it could not be reproduced at all.
28
+ impact:
29
+ requirements:
30
+ file: requirements.yaml
31
+
32
+ validate:
33
+ exclude:
34
+ - "^\\.venv/"
35
+ - "^\\.claude/"
36
+ - "^workspace/"
37
+ - "^tests/" # tests are exempt (fixtures, asserts)
38
+ - "^scripts/" # dev helpers
39
+ - "^acceptance/" # benchmark harness + evidence, not shipped code
40
+ - "^docs/generated/"
41
+ # NOTE: this key was DUPLICATED — an empty `tags:` sat directly above the real
42
+ # one. YAML takes the last, so the real block won and nothing broke, which is
43
+ # why it survived. The trap is that a reorder or an insertion between them
44
+ # would silently hand the win to the empty mapping, dropping the declared @req
45
+ # id pattern and disabling tag validation with no error anywhere.
46
+ tags:
47
+ # clew's own requirement id shape. Declared so @req tags validate
48
+ # against it — NOT hardcoded anywhere in the pipeline.
49
+ #
50
+ # A BARE ID, nothing else. This pattern used to carry an optional
51
+ # `( \[inferred\])?` suffix, because the project had a convention where a tag
52
+ # could mark its own mapping as inferred rather than asserted, and
53
+ # doxygen-guard validates the WHOLE remainder of the `@req` line rather than
54
+ # just the first token.
55
+ #
56
+ # The convention was RETIRED 2026-07-30 by the owner: "I would rather not put
57
+ # requirements as inferred or not — they should simply be the stated ID,
58
+ # inferred is noise." The marker is gone from the tags, the parser and the
59
+ # schema — `req_edges.confidence` no longer exists, because a column that can
60
+ # hold only one value states nothing.
61
+ req:
62
+ pattern: "^REQ-DDB-[A-Z]+-[0-9]{3}$"
@@ -0,0 +1,69 @@
1
+ .venv/
2
+ # BOTH spellings, because `.venv/` with the trailing slash matches a DIRECTORY only and so
3
+ # does not match a SYMLINK named `.venv`. `git add -A` would then stage a tracked symlink
4
+ # pointing at an absolute machine path. Same hazard as the two `doxygen_out` spellings below:
5
+ # one spelling of an ignore silently covers less than a reader assumes.
6
+ .venv
7
+ __pycache__/
8
+ *.pyc
9
+ *.egg-info/
10
+ .ipynb_checkpoints/
11
+ workspace/
12
+
13
+ # Tool caches. `.ruff_cache/` is the one that matters: ruff writes its own
14
+ # `.ruff_cache/.gitignore` containing `*`, so the directory self-hides and looks handled —
15
+ # but that is ruff's file, not this repo's guarantee. Delete the cache and the protection
16
+ # goes with it.
17
+ .cache/
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+ .mypy_cache/
21
+ .tox/
22
+ htmlcov/
23
+ .coverage
24
+
25
+ # Build outputs. `python -m build` runs in CI, so anyone reproducing it locally creates these.
26
+ dist/
27
+ build/
28
+
29
+ # Working-instructions and scratch. `.claude/CLAUDE.md` lives here deliberately: how someone
30
+ # instructs their own agent is theirs to maintain, not a shipped asset.
31
+ .claude/
32
+
33
+ # The doxygen-guard output directory the guard's own config declares
34
+ # (`output_dir: docs/generated/`), so any guard run recreates it.
35
+ docs/generated/
36
+
37
+ # Built indexes and their caches. DEFENSE IN DEPTH, not convenience: clew is pointed at
38
+ # third-party repos, so a stray database in the tree is that repo's source graph — symbol
39
+ # names, file paths, call structure — in a single file. It is the highest-value thing here to
40
+ # leak by accident, and a multi-megabyte binary is exactly what a hurried `git add -A`
41
+ # swallows without anyone reading the diff. `--output` accepts any path, so these patterns
42
+ # exist to keep that true when someone builds an index somewhere new.
43
+ *.db
44
+ *.db-journal
45
+ *.db-wal
46
+ *.idxcache
47
+
48
+ # Doxygen scratch from a HAND-RUN doxygen. The pipeline forces OUTPUT_DIRECTORY away from the
49
+ # target repo, so this only catches manual invocations.
50
+ #
51
+ # TWO SPELLINGS, not a glob. The dot-prefixed form is the one the pipeline produces and
52
+ # `doxygen_out/` never matched it — a leading dot makes it a non-match, which once staged 47
53
+ # files of doxygen XML. `*doxygen_out/` would also swallow a deliberately-named directory, and
54
+ # an over-broad ignore that hides a real file is a worse hazard than the mess it prevents.
55
+ #
56
+ # Deliberately NOT ignoring bare `html/` or `xml/` for the same reason: those are ordinary
57
+ # directory names.
58
+ doxygen_out/
59
+ .doxygen_out/
60
+
61
+ # Private-target acceptance evidence. A cell transcript quotes the target's source verbatim,
62
+ # so committing one publishes that source. THIS IS THE ONLY PROTECTION — there is no scrub
63
+ # gate, so a `git add -f` here is unguarded. Both spellings, per the rule at the top.
64
+ acceptance/targets/internal
65
+ acceptance/targets/internal/
66
+
67
+ # In-flux findings. A committed workbook becomes a citable claim before it has been measured;
68
+ # measured results go in `acceptance/targets/*/` beside the transcripts that produced them.
69
+ acceptance/docs/
@@ -0,0 +1,58 @@
1
+ ---
2
+ exclude: |
3
+ (?x)^(
4
+ \.venv/.*|
5
+ \.claude/.*|
6
+ workspace/.*|
7
+ docs/generated/.*|
8
+ acceptance/targets/.*|
9
+ tests/data/.*|
10
+ .*\.egg-info/.*
11
+ )$
12
+
13
+ repos:
14
+ - repo: https://github.com/brandon-arrendondo/knots
15
+ rev: v1.15.1
16
+ hooks:
17
+ - id: knots
18
+
19
+ - repo: https://github.com/tvanfossen/doxygen-guard
20
+ rev: 1.4.2
21
+ hooks:
22
+ - id: doxygen-guard
23
+ args: [validate]
24
+ files: ^(clew|acceptance|tests)/.*\.py$
25
+
26
+ - repo: https://github.com/astral-sh/ruff-pre-commit
27
+ rev: v0.16.1
28
+ hooks:
29
+ - id: ruff
30
+ args: ["--fix", "--select=E4,E7,E9,F", "--target-version=py310", "--cache-dir=.cache/ruff"]
31
+ - id: ruff-format
32
+ args: ["--line-length=100", "--target-version=py310", "--cache-dir=.cache/ruff"]
33
+
34
+ - repo: https://github.com/pre-commit/pre-commit-hooks
35
+ rev: v6.0.0
36
+ hooks:
37
+ - id: trailing-whitespace
38
+ - id: end-of-file-fixer
39
+ - id: check-yaml
40
+ - id: check-json
41
+ - id: check-toml
42
+ - id: check-added-large-files
43
+ args: ['--maxkb=512']
44
+ - id: check-merge-conflict
45
+ - id: check-symlinks
46
+ - id: mixed-line-ending
47
+
48
+ - repo: local
49
+ hooks:
50
+ - id: pytest
51
+ name: pytest
52
+ ## `cache_dir` is set once, in pyproject's `[tool.pytest.ini_options]`. It used to be
53
+ ## overridden here as well, which is two places for one setting and therefore one place
54
+ ## to forget when it changes.
55
+ entry: .venv/bin/python -m pytest tests/ -q
56
+ language: system
57
+ pass_filenames: false
58
+ always_run: true
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tristan Van Fossen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,284 @@
1
+ Metadata-Version: 2.5
2
+ Name: clew-trace
3
+ Version: 1.0.0
4
+ Summary: Queryable, layered symbol database for C, C++ and Python codebases: doxygen + tree-sitter call graph, threads, locks and dataflow edges, served to AI agents over MCP.
5
+ Project-URL: Homepage, https://github.com/tvanfossen/clew
6
+ Project-URL: Repository, https://github.com/tvanfossen/clew
7
+ Project-URL: Issues, https://github.com/tvanfossen/clew/issues
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: call-graph,documentation,doxygen,embedded,mcp,static-analysis,traceability,tree-sitter
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Documentation
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: anyio>=4.0
22
+ Requires-Dist: doxygen-guard<2,>=1.4.2
23
+ Requires-Dist: kconfiglib<15,>=14
24
+ Requires-Dist: mcp-types<3,>=2
25
+ Requires-Dist: mcp<3,>=2
26
+ Requires-Dist: pyyaml<8,>=6.0
27
+ Requires-Dist: rich>=13.0
28
+ Requires-Dist: tomli>=2; python_version < '3.11'
29
+ Requires-Dist: tree-sitter-c<0.23.5,>=0.23
30
+ Requires-Dist: tree-sitter-cpp<0.23.5,>=0.23
31
+ Requires-Dist: tree-sitter-python<0.23.5,>=0.23
32
+ Requires-Dist: tree-sitter<0.24,>=0.23
33
+ Provides-Extra: dev
34
+ Requires-Dist: build>=1.0; extra == 'dev'
35
+ Requires-Dist: pre-commit>=3.6; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0; extra == 'dev'
37
+ Requires-Dist: twine>=5.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # clew
41
+
42
+ > *A **clew** is the ball of thread Ariadne gave Theseus — the thing you follow to find your way
43
+ > out of the labyrinth. It is the archaic spelling, and the direct ancestor, of the word "clue".*
44
+
45
+ **Give an agent the thread, not a map of the whole maze.** `clew` runs doxygen and tree-sitter
46
+ over a C, C++ or Python repository and compiles the result into a SQLite graph — symbols, call
47
+ edges, callbacks, threads, locks, dataflow, requirement links, file inventory, prose — then
48
+ serves it over MCP. Pull on one symbol and you get its callers, its locks, the thread it runs on
49
+ and the requirement it satisfies.
50
+
51
+ **What it is for is aggregation, not capability.** Nothing here is a question `grep` cannot
52
+ answer. The difference is that "who calls this, transitively, across a function-pointer
53
+ boundary, and which of those run on another thread" is one query instead of a dozen searches
54
+ and a mental model you rebuild every session. It is a cache for work you would otherwise redo.
55
+
56
+ **doxygen-guard is optional.** [It](https://github.com/tvanfossen/doxygen-guard) is a
57
+ pre-commit gate that keeps a repo's doxygen accurate, and a repo that uses it gets a richer
58
+ index — briefs, requirement tags, versioned comments. But `clew` needs neither the gate nor a
59
+ Doxyfile: a repo that declares nothing gets its whole tree indexed, and three of the four repos
60
+ in `acceptance/targets/` are measured that way. If you want the gate, it is a separate tool with
61
+ one job; if you do not, this still works.
62
+
63
+ ## The four tools
64
+
65
+ An agent is the user, and these are the whole of what the server exposes. A human-facing browser
66
+ over the index is deliberately deferred: repository searchability for a person is what an IDE
67
+ already is, so building a second one is not where the value is.
68
+
69
+ | tool | for |
70
+ |---|---|
71
+ | **`dossier`** | Everything about one named symbol in ONE reply: body excerpt, brief, both edge directions, locks held, the thread it runs on, requirements and covering tests, liveness. There is no separate `callers` or `chain_trace` tool — this is why. |
72
+ | **`search`** | Find the name when you do not have one, or enumerate a whole layer. `corpus=` picks: symbols, prose, files, config, locks, threads. An empty result is graded and says what it did *not* read. |
73
+ | **`index(action=…)`** | Administration, not questions: `refresh` builds, `status` diagnoses, `targets` lists every indexed repo, `stats` grades one, `cull` deletes aged-out databases. |
74
+ | **`propose_declaration`** | Reads the repo's evidence and proposes a `.clew.yaml`, entirely as comments, for when a causal layer looks emptier than the code warrants. |
75
+
76
+ Every reply names the `target` it answered from, and carries a `staleness` block only when that
77
+ index is actually stale — so a current index costs nothing to establish.
78
+
79
+ Reads are live: query tools open the database per call. Writes are refused when the server
80
+ process predates its own source, because a build through stale logic can silently drop whole
81
+ layers and then report success.
82
+
83
+ ## What's in the database
84
+
85
+ | Layer | Answers |
86
+ |---|---|
87
+ | Structure | "what is this symbol?" — doxygen symbol tables, briefs, extents |
88
+ | Prose | "*why* does this subsystem exist?" — README/docs markdown, FTS5-indexed |
89
+ | Call graph | "who calls this, and how sure are we?" — doxygen ×2 + tree-sitter, every edge carrying `source` + `confidence` |
90
+ | Callbacks | "who reaches it through a function pointer?" — registration→dispatch resolution |
91
+ | Shared-key dataflow | "writing this key affects whom?" — data-model / queue edges the call graph cannot see |
92
+ | **Threads** | "which thread does this run on?" — spawn-site harvest + membership closure |
93
+ | **Dispatch semantics** | inline vs queued vs keyed; edge-triggered; **which hops cross a thread boundary** |
94
+ | **Terminus** | where a chain leaves the repo through an externally-registered callback |
95
+ | Requirements | "what implements / tests REQ-X?" — `@req` traceability |
96
+ | Liveness | "does this code even run?" — reachability over the non-fuzzy call graph |
97
+
98
+ The point of the semantic layers (threads, dispatch mode, terminus) is **causal
99
+ chains**: reconstructing "message arrives → who handles it → where it goes →
100
+ which thread hops → where it leaves the library → what requirements it touches"
101
+ as a single query, not a manual archaeology session.
102
+
103
+ ## Measured
104
+
105
+ Answering from the index is compared against an agent with only `Read`/`Grep`/`Glob`/`Bash` —
106
+ same repository, same model, same sitting, same frozen questions. Four repositories, both arms,
107
+ two model tiers. Every transcript, metric and per-mark grade is committed beside its rubric in
108
+ [`acceptance/targets/`](https://github.com/tvanfossen/clew/tree/main/acceptance/targets); the results are in each target's `result.md`.
109
+
110
+ Run it on yours: [`acceptance/targets/TEMPLATE/`](https://github.com/tvanfossen/clew/tree/main/acceptance/targets/TEMPLATE) has the rubric
111
+ template and the runbook.
112
+
113
+ ## What semver covers
114
+
115
+ **Covered** — a breaking change here means a major bump:
116
+
117
+ - the `clew` CLI verbs and their flags (`init`, `propose`, `export`, and the
118
+ build invocation)
119
+ - the MCP **tool names and their wire keys** — a consumer reads `edge_class`,
120
+ `confidence`, `source` and friends by name
121
+ - `clew.query`'s public functions and the dataclasses they return
122
+
123
+ **Not covered**, and deliberately so:
124
+
125
+ - **the SQLite schema.** `CLEW_BUILD_VERSION` exists precisely because the
126
+ schema moves; a consumer detects a stale index by comparing it, not by assuming
127
+ stability. Query through `clew.query`, not with your own SQL.
128
+
129
+ `CLEW_BUILD_VERSION`, `read_build_signature` and `index_unusable_reason` are
130
+ re-exported from `clew.query` and are therefore **covered**, so the documented
131
+ way to check freshness does not reach into an internal:
132
+
133
+ ```python
134
+ from clew.query import CLEW_BUILD_VERSION, read_build_signature
135
+
136
+ if read_build_signature("clew.db") != CLEW_BUILD_VERSION:
137
+ ... # rebuild: this index predates the current pipeline
138
+ ```
139
+ - `clew.*` internals — anything not re-exported from `query`
140
+ - the `.idxcache` format
141
+
142
+ ## Install
143
+
144
+ The package is always required — the server *is* the `clew-mcp` command it installs, and no
145
+ plugin or registration step can supply that. Install it so the command is on PATH whatever
146
+ virtualenv happens to be active:
147
+
148
+ ```bash
149
+ pipx install clew-trace # or: pip install --user clew-trace
150
+ sudo apt install doxygen # REQUIRED, and not pip-installable
151
+ ```
152
+
153
+ A plain `pip install` inside a project venv also works, but only while that venv is on the PATH
154
+ your editor launches the server with — which is the usual reason a correctly-registered server
155
+ shows as failed to connect.
156
+
157
+ Then register it, EITHER as a Claude Code plugin:
158
+
159
+ ```
160
+ /plugin marketplace add tvanfossen/clew
161
+ /plugin install clew@clew
162
+ ```
163
+
164
+ or by hand, which also doctors the environment and names what is missing:
165
+
166
+ ```bash
167
+ clew init # writes ./.mcp.json (--scope global for user-level)
168
+ ```
169
+
170
+ Pick one. Both register a server named `clew`, so doing both leaves two sources for one entry —
171
+ run `claude mcp remove clew` first if you have already used `clew init`.
172
+
173
+ `doxygen` is a C++ binary, so it cannot be a Python dependency — `clew init` checks for it and
174
+ tells you if it is missing. Everything else, including the MCP SDK, comes with the package.
175
+
176
+ ### Working on clew itself
177
+
178
+ ```bash
179
+ python3 -m venv .venv
180
+ .venv/bin/pip install -e .[dev] # pipeline + doxygen-guard + gates
181
+ sudo apt install doxygen # external binary (plantuml optional)
182
+ ```
183
+
184
+ ## Build a database
185
+
186
+ ```bash
187
+ .venv/bin/python -m clew \
188
+ --output clew.db \
189
+ --repo-root /path/to/repo
190
+ ```
191
+
192
+ That is the whole common case. The Doxyfile is discovered, and a repo that ships none gets
193
+ one synthesized over its declared scope.
194
+
195
+ **Six arguments, and only six:**
196
+
197
+ | Flag | For |
198
+ |---|---|
199
+ | `--output` | where the database is written |
200
+ | `--repo-root` | the repository to index |
201
+ | `--declare FILE` | a declaration document stated for this build (see below) |
202
+ | `--exclude PATH…` | paths to leave out; recorded into the index and replayed by later builds |
203
+ | `--rebuild` | ignore every cached entry, then re-warm the cache |
204
+ | `--verbose` | more on stderr; changes nothing that reaches the index |
205
+
206
+ Everything a target repo can *declare* lives in one place — its own
207
+ `.clew.yaml` — and `--declare` states the same document for a repository you do
208
+ not own and must leave byte-identical:
209
+
210
+ ```yaml
211
+ # a --declare document, or <repo>/.clew.yaml
212
+ index_scope: {roots: [src, include], excludes: [src/generated]}
213
+ preprocessor: {predefined: [MY_FEATURE_C], config_header: auto}
214
+ requirements: docs/requirements.yaml
215
+ thread_patterns: {spawns: [{name: osThreadNew, entry_arg_index: 0}]}
216
+ shared_key_patterns: {writers: [{name_prefix: Store_Set}], readers: [{name_prefix: Store_Get}]}
217
+ locks: {locks: [{name: MyGuard, form: raii}]}
218
+ dispatch: {interfaces: [...]}
219
+ mqtt_dispatch: {subscribe_functions: [...]}
220
+ data_model: tools/dm_full.toml
221
+ kconfig: {path: Kconfig}
222
+ event_tags: {emits: produce, handles: consume}
223
+ entry_patterns: ["%trampoline%", "on_%"]
224
+ enrich: docs/architecture_topics.yaml
225
+ ```
226
+
227
+ Nothing about a target repo is hardcoded: every section above is a declared override of
228
+ a built-in default. The identical mapping is the `options` argument of `build_index()` and
229
+ of the MCP `index(action="refresh")` tool, so an agent with no shell can state any of it.
230
+
231
+ The repo's `.doxygen-guard.yaml` — which supplies the declared `@req` id pattern and the
232
+ catalog column mapping — is **discovered** from `--repo-root`: its root, then the path the
233
+ doxygen-guard pre-commit hook names in its own `--config` argument, then
234
+ `conf/ | config/ | .config/`.
235
+
236
+ ### Commit what you stated
237
+
238
+ `--declare` reads a document in; `export` writes one out, so a discovery made against a
239
+ repository you do not own can be committed to one you do:
240
+
241
+ ```bash
242
+ .venv/bin/python -m clew export --index clew.db # to stdout
243
+ .venv/bin/python -m clew export --index clew.db --output .clew.yaml
244
+ ```
245
+
246
+ It emits **only what was stated** — never the built-in defaults. That is deliberate: a file
247
+ asserting you declared every default would freeze those defaults into your repo, so a later
248
+ improvement to one would be shadowed by a value nobody remembers committing. An index that
249
+ recorded no statement exports a document that says so.
250
+
251
+ ## Without an agent
252
+
253
+ The same four capabilities are importable, returning JSON-serializable dataclasses. This is what
254
+ the MCP server is a view over, so no query logic exists twice.
255
+
256
+ ```python
257
+ from clew.query import dossier, search
258
+
259
+ dossier("clew.db", "sensor_poll") # identity, reqs, both edge directions, locks, threads, liveness
260
+ search("clew.db", "retry backoff") # find the name when you do not have one
261
+ ```
262
+
263
+ ## Layout
264
+
265
+ | Path | What |
266
+ |---|---|
267
+ | `clew/` | The pipeline (`python -m clew`) |
268
+ | `clew/query/` | The stable query API the MCP server is a view over |
269
+ | `tests/` | `.venv/bin/python -m pytest tests/ -q` (add `--integration` for the tier that builds real repos) |
270
+ | `acceptance/targets/<t>/<version>/` | Frozen rubrics and the committed results of every grid run against them |
271
+ | `acceptance/bench/` | The harness. Acceptance-only by design — deliberately NOT in the pre-commit gate |
272
+
273
+ ## Dogfooding
274
+
275
+ clew runs its own gate: `.venv/bin/pre-commit run --all-files` → ruff (lint +
276
+ format), doxygen-guard (`@brief`/`@version`/`@return` presence on the shipped
277
+ package), and the full test suite. Its own catalog lives in `requirements.yaml`.
278
+
279
+ ## doxygen-guard
280
+
281
+ [doxygen-guard](https://github.com/tvanfossen/doxygen-guard) is a separate tool, consumed here as
282
+ a pip dependency and pinned by `rev:` in `.pre-commit-config.yaml`. Its scope is
283
+ validation, traceability and change impact; nothing here extends it, and `clew` does not require
284
+ it — see the top of this file.