clew-trace 1.0.0__py3-none-any.whl
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.
- clew/__init__.py +96 -0
- clew/__main__.py +14 -0
- clew/_common.py +173 -0
- clew/ast_symbols.py +1781 -0
- clew/buildoptions.py +696 -0
- clew/call_edges.py +1306 -0
- clew/callback_edges.py +1386 -0
- clew/cli.py +2464 -0
- clew/coverage.py +663 -0
- clew/critical_sections.py +597 -0
- clew/datamodel.py +1077 -0
- clew/declaration.py +616 -0
- clew/diagnostics.py +252 -0
- clew/dispatch.py +472 -0
- clew/dispatch_edges.py +765 -0
- clew/dominated_edges.py +356 -0
- clew/doxygen.py +1263 -0
- clew/enrichment.py +105 -0
- clew/errors.py +72 -0
- clew/event_edges.py +358 -0
- clew/export_command.py +183 -0
- clew/external.py +280 -0
- clew/filedocs.py +433 -0
- clew/gitenv.py +69 -0
- clew/guardconfig.py +512 -0
- clew/harvest.py +614 -0
- clew/harvest_plan.py +186 -0
- clew/indexcache.py +538 -0
- clew/init_command.py +613 -0
- clew/kconfig.py +796 -0
- clew/kconfig_gates.py +567 -0
- clew/locks.py +1068 -0
- clew/mcp_config.py +615 -0
- clew/mcp_server/__init__.py +32 -0
- clew/mcp_server/__main__.py +13 -0
- clew/mcp_server/_sdk.py +105 -0
- clew/mcp_server/descriptions/_provenance.md +134 -0
- clew/mcp_server/descriptions/_templates/ambiguous.json +33 -0
- clew/mcp_server/descriptions/_templates/disambiguate.json +40 -0
- clew/mcp_server/descriptions/_templates/provenance.json +39 -0
- clew/mcp_server/descriptions/_templates/rows.json +14 -0
- clew/mcp_server/descriptions/dossier.json +17 -0
- clew/mcp_server/descriptions/index.json +13 -0
- clew/mcp_server/descriptions/propose_declaration.json +8 -0
- clew/mcp_server/descriptions/search.json +17 -0
- clew/mcp_server/descriptions.py +239 -0
- clew/mcp_server/emptiness.py +449 -0
- clew/mcp_server/freshness.py +417 -0
- clew/mcp_server/server.py +1723 -0
- clew/mcp_server/state.py +905 -0
- clew/mcp_server/tools_query.py +1938 -0
- clew/precommit.py +386 -0
- clew/preprocessor.py +834 -0
- clew/propose/__init__.py +83 -0
- clew/propose/astdefs.py +432 -0
- clew/propose/command.py +250 -0
- clew/propose/context.py +56 -0
- clew/propose/dryrun.py +259 -0
- clew/propose/model.py +179 -0
- clew/propose/notindexed.py +322 -0
- clew/propose/registry.py +488 -0
- clew/propose/render.py +416 -0
- clew/propose/scanning.py +428 -0
- clew/propose/sharedkey_detect.py +319 -0
- clew/propose/sharedkey_report.py +409 -0
- clew/propose/threads_detect.py +443 -0
- clew/propose/threads_report.py +350 -0
- clew/prose.py +168 -0
- clew/py_entrypoints.py +405 -0
- clew/pyast.py +805 -0
- clew/query/__init__.py +239 -0
- clew/query/_common.py +679 -0
- clew/query/corpus.py +961 -0
- clew/query/dossier.py +638 -0
- clew/query/externcalls.py +242 -0
- clew/query/graph.py +487 -0
- clew/query/kconfig.py +519 -0
- clew/query/locks.py +731 -0
- clew/query/macros.py +214 -0
- clew/query/models.py +1931 -0
- clew/query/source.py +387 -0
- clew/query/subject.py +798 -0
- clew/query/symbols.py +1973 -0
- clew/query/traversal.py +412 -0
- clew/reachability.py +239 -0
- clew/requirements.py +780 -0
- clew/scope.py +729 -0
- clew/shared_key_edges.py +2452 -0
- clew/signature.py +353 -0
- clew/stagetimer.py +141 -0
- clew/threads.py +1516 -0
- clew/tiers.py +564 -0
- clew/tomlcompat.py +94 -0
- clew/treescan.py +371 -0
- clew/vocabulary.py +1080 -0
- clew/wire.py +193 -0
- clew_trace-1.0.0.dist-info/METADATA +284 -0
- clew_trace-1.0.0.dist-info/RECORD +101 -0
- clew_trace-1.0.0.dist-info/WHEEL +4 -0
- clew_trace-1.0.0.dist-info/entry_points.txt +3 -0
- clew_trace-1.0.0.dist-info/licenses/LICENSE +21 -0
clew/__init__.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Build a doxygen SQLite knowledge database from source code.
|
|
3
|
+
|
|
4
|
+
Public entry point: `python -m clew --doxyfile <path>
|
|
5
|
+
--output <path> [--enrich ...] [--repo-root ...]`.
|
|
6
|
+
|
|
7
|
+
The pipeline:
|
|
8
|
+
|
|
9
|
+
1. doxygen runs with GENERATE_SQLITE3 forced on
|
|
10
|
+
(see `doxygen.run_doxygen`).
|
|
11
|
+
2. Generated DB copied into place; doxygen STRIP_FROM_PATH paths
|
|
12
|
+
are restored to repo-root-relative form
|
|
13
|
+
(`doxygen.fix_doxygen_paths`).
|
|
14
|
+
3. README/CHANGELOG/docs/*.md ingested into FTS5
|
|
15
|
+
(`prose.ingest_supplementary_docs`).
|
|
16
|
+
4. Two layers of call-edge import populate `call_edges`:
|
|
17
|
+
`call_edges.build_call_edges` (doxygen sqlite3 inline xrefs),
|
|
18
|
+
`call_edges.import_ast_call_edges` (tree-sitter AST walk).
|
|
19
|
+
5. Reachability BFS marks every function `live` or `orphan`
|
|
20
|
+
(`reachability.mark_reachability`).
|
|
21
|
+
6. Stats printed (`cli.report_stats`).
|
|
22
|
+
|
|
23
|
+
Each module is independently testable; previously this was one
|
|
24
|
+
~1200-line file. Split landed in Phase C PR-2.
|
|
25
|
+
|
|
26
|
+
@brief Doxygen → SQLite knowledge-database build pipeline.
|
|
27
|
+
@version 3
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
# Re-export the public functions so the flat-module API keeps working
|
|
33
|
+
# for callers importing `clew.X`, in a checkout and from the
|
|
34
|
+
# wheel alike. Relative imports work in both contexts.
|
|
35
|
+
from .call_edges import (
|
|
36
|
+
build_call_edges,
|
|
37
|
+
import_ast_call_edges,
|
|
38
|
+
)
|
|
39
|
+
from .cli import main, report_stats
|
|
40
|
+
from .dispatch import load_dispatch_manifest, shared_key_document
|
|
41
|
+
from .dispatch_edges import import_declared_dispatch_edges
|
|
42
|
+
from .doxygen import (
|
|
43
|
+
copy_database,
|
|
44
|
+
fix_doxygen_paths,
|
|
45
|
+
run_doxygen,
|
|
46
|
+
)
|
|
47
|
+
from .enrichment import enrich_database
|
|
48
|
+
from .filedocs import extract_file_doc, ingest_file_docs
|
|
49
|
+
from .kconfig import import_kconfig
|
|
50
|
+
from .kconfig_gates import import_kconfig_gates
|
|
51
|
+
from .prose import ingest_supplementary_docs
|
|
52
|
+
from .reachability import (
|
|
53
|
+
DEFAULT_ENTRY_PATTERNS,
|
|
54
|
+
ENTRY_PATTERN_FACTS,
|
|
55
|
+
ENTRY_PATTERN_HEURISTICS,
|
|
56
|
+
mark_reachability,
|
|
57
|
+
)
|
|
58
|
+
from .shared_key_edges import (
|
|
59
|
+
import_mqtt_dispatch_edges,
|
|
60
|
+
import_shared_key_edges_declared,
|
|
61
|
+
import_shared_key_edges_inferred,
|
|
62
|
+
)
|
|
63
|
+
from .threads import (
|
|
64
|
+
DEFAULT_SPAWN_PATTERNS,
|
|
65
|
+
annotate_thread_boundaries,
|
|
66
|
+
extract_threads,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"DEFAULT_ENTRY_PATTERNS",
|
|
71
|
+
"DEFAULT_SPAWN_PATTERNS",
|
|
72
|
+
"ENTRY_PATTERN_FACTS",
|
|
73
|
+
"ENTRY_PATTERN_HEURISTICS",
|
|
74
|
+
"annotate_thread_boundaries",
|
|
75
|
+
"build_call_edges",
|
|
76
|
+
"copy_database",
|
|
77
|
+
"enrich_database",
|
|
78
|
+
"extract_file_doc",
|
|
79
|
+
"extract_threads",
|
|
80
|
+
"fix_doxygen_paths",
|
|
81
|
+
"import_ast_call_edges",
|
|
82
|
+
"import_declared_dispatch_edges",
|
|
83
|
+
"import_kconfig",
|
|
84
|
+
"import_kconfig_gates",
|
|
85
|
+
"import_mqtt_dispatch_edges",
|
|
86
|
+
"import_shared_key_edges_declared",
|
|
87
|
+
"import_shared_key_edges_inferred",
|
|
88
|
+
"ingest_file_docs",
|
|
89
|
+
"ingest_supplementary_docs",
|
|
90
|
+
"load_dispatch_manifest",
|
|
91
|
+
"main",
|
|
92
|
+
"mark_reachability",
|
|
93
|
+
"report_stats",
|
|
94
|
+
"run_doxygen",
|
|
95
|
+
"shared_key_document",
|
|
96
|
+
]
|
clew/__main__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Module entry point: `python -m clew`.
|
|
3
|
+
|
|
4
|
+
Defers to `cli.main`. Exists so a caller can run the package via
|
|
5
|
+
`python -m clew ...` without needing a wrapper script.
|
|
6
|
+
|
|
7
|
+
@brief Module entry-point shim.
|
|
8
|
+
@version 1
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .cli import main
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main()
|
clew/_common.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Shared helpers for the clew package.
|
|
3
|
+
|
|
4
|
+
The Rich Progress factory, the module logger, and the one seam that decides
|
|
5
|
+
WHERE pipeline output goes. Kept tiny; if more shared state grows, move those
|
|
6
|
+
helpers here in their own modules.
|
|
7
|
+
|
|
8
|
+
@brief Shared helpers for clew submodules.
|
|
9
|
+
@version 2
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import io
|
|
15
|
+
import logging
|
|
16
|
+
import os
|
|
17
|
+
import threading
|
|
18
|
+
from collections.abc import Iterator
|
|
19
|
+
from contextlib import contextmanager
|
|
20
|
+
from contextvars import ContextVar
|
|
21
|
+
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.progress import (
|
|
24
|
+
BarColumn,
|
|
25
|
+
MofNCompleteColumn,
|
|
26
|
+
Progress,
|
|
27
|
+
SpinnerColumn,
|
|
28
|
+
TaskProgressColumn,
|
|
29
|
+
TextColumn,
|
|
30
|
+
TimeElapsedColumn,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("clew")
|
|
34
|
+
console = Console()
|
|
35
|
+
|
|
36
|
+
## The console pipeline output is rendered to for the duration of ONE call.
|
|
37
|
+
##
|
|
38
|
+
## A ContextVar rather than a module-level rebind, because a caller silencing its
|
|
39
|
+
## own build must not silence anyone else's: a build dispatched to a worker thread
|
|
40
|
+
## runs beside a thread that may be writing to the real stdout, and each thread
|
|
41
|
+
## carries its own context, so a value set inside the worker is invisible there.
|
|
42
|
+
## None means "the process console", which is what a plain CLI build wants.
|
|
43
|
+
_render_target: ContextVar[Console | None] = ContextVar("clew_render_target", default=None)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## @brief os.environ + NO_COLOR/TERM=dumb so pipeline subprocesses emit no ANSI.
|
|
47
|
+
## @version 2
|
|
48
|
+
## @req REQ-DDB-PIPE-006
|
|
49
|
+
## @return Copy of os.environ with NO_COLOR=1, TERM=dumb, and CLICOLOR=0 set.
|
|
50
|
+
def clean_subprocess_env() -> dict[str, str]:
|
|
51
|
+
"""Return a copy of the environment with color/interactive TTY hints forced
|
|
52
|
+
off (NO_COLOR=1, TERM=dumb, CLICOLOR=0). Passed to every pipeline subprocess
|
|
53
|
+
(e.g. doxygen) so captured build output carries no SGR escape sequences.
|
|
54
|
+
|
|
55
|
+
@brief Build a color-suppressing environment for pipeline subprocesses.
|
|
56
|
+
@version 2
|
|
57
|
+
"""
|
|
58
|
+
env = dict(os.environ)
|
|
59
|
+
env["NO_COLOR"] = "1"
|
|
60
|
+
env["TERM"] = "dumb"
|
|
61
|
+
env["CLICOLOR"] = "0"
|
|
62
|
+
return env
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## @brief The console every pipeline writer renders to right now.
|
|
66
|
+
## @return The console injected by `captured_output`, else the process console.
|
|
67
|
+
## @version 1
|
|
68
|
+
## @utility
|
|
69
|
+
def active_console() -> Console:
|
|
70
|
+
"""Read at write time rather than captured at import, so a caller can redirect
|
|
71
|
+
the whole pipeline for one call without swapping `sys.stdout` — which is
|
|
72
|
+
process-global and would take the frames of any other thread with it.
|
|
73
|
+
|
|
74
|
+
@brief Resolve the current render target for pipeline output.
|
|
75
|
+
@return The console to write to.
|
|
76
|
+
@version 1
|
|
77
|
+
"""
|
|
78
|
+
return _render_target.get() or console
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
## @brief A log handler that only accepts records raised on the thread that made it.
|
|
82
|
+
## @version 1
|
|
83
|
+
## @utility
|
|
84
|
+
class _OwnThreadHandler(logging.StreamHandler):
|
|
85
|
+
"""The render target is context-local, so two concurrent runs cannot see each
|
|
86
|
+
other's bars. A logger is not: it is one object shared by every thread, and a
|
|
87
|
+
handler added to it would collect the other run's records too. Matching on the
|
|
88
|
+
creating thread restores the same isolation for the log half.
|
|
89
|
+
|
|
90
|
+
@brief Stream handler scoped to one thread's records.
|
|
91
|
+
@version 1
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
## @brief Bind the handler to a stream and to the creating thread.
|
|
95
|
+
## @param stream Stream the records are written to.
|
|
96
|
+
## @version 1
|
|
97
|
+
## @dg_internal
|
|
98
|
+
def __init__(self, stream: io.StringIO) -> None:
|
|
99
|
+
"""@brief Record which thread's output this handler accepts."""
|
|
100
|
+
super().__init__(stream)
|
|
101
|
+
self.owner = threading.get_ident()
|
|
102
|
+
|
|
103
|
+
## @brief Emit only when the record came from the owning thread.
|
|
104
|
+
## @param record The record being handled.
|
|
105
|
+
## @version 1
|
|
106
|
+
## @dg_internal
|
|
107
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
108
|
+
"""@brief Drop records raised on any other thread."""
|
|
109
|
+
if record.thread == self.owner:
|
|
110
|
+
super().emit(record)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## @brief Send every pipeline writer into a buffer instead of the process stdout.
|
|
114
|
+
## @return Context manager yielding the buffer the output lands in.
|
|
115
|
+
## @version 2
|
|
116
|
+
## @utility
|
|
117
|
+
@contextmanager
|
|
118
|
+
def captured_output() -> Iterator[io.StringIO]:
|
|
119
|
+
"""The seam a caller uses when its `sys.stdout` is a protocol transport: the
|
|
120
|
+
progress bars, the doxygen warning lines, the build summary and the pipeline's
|
|
121
|
+
own log records all land in the yielded buffer, and stdout is untouched for
|
|
122
|
+
anyone else.
|
|
123
|
+
|
|
124
|
+
The LOG half is here because the log is where the pipeline explains itself —
|
|
125
|
+
which Doxyfile it resolved, which scope tier it fell back to, what it refused.
|
|
126
|
+
Capturing the rendered output alone would hand a caller a table of row counts
|
|
127
|
+
and drop the sentence saying why the counts are what they are.
|
|
128
|
+
|
|
129
|
+
The buffer stays readable after the block ends, so the partial output of a run
|
|
130
|
+
that raised is still available to report.
|
|
131
|
+
|
|
132
|
+
@brief Capture pipeline output and logging for the duration of a call.
|
|
133
|
+
@return The buffer receiving everything the pipeline emits.
|
|
134
|
+
@version 2
|
|
135
|
+
"""
|
|
136
|
+
buffer = io.StringIO()
|
|
137
|
+
handler = _OwnThreadHandler(buffer)
|
|
138
|
+
handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
|
|
139
|
+
level = logger.level
|
|
140
|
+
token = _render_target.set(Console(file=buffer, no_color=True, width=100))
|
|
141
|
+
logger.addHandler(handler)
|
|
142
|
+
logger.setLevel(logging.INFO)
|
|
143
|
+
try:
|
|
144
|
+
yield buffer
|
|
145
|
+
finally:
|
|
146
|
+
logger.setLevel(level)
|
|
147
|
+
logger.removeHandler(handler)
|
|
148
|
+
_render_target.reset(token)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
## @brief Single consistent Progress factory across all DB-build stages.
|
|
152
|
+
## @version 2
|
|
153
|
+
## @return A configured rich Progress instance with the standard column layout.
|
|
154
|
+
## @utility
|
|
155
|
+
def make_progress(known_total: bool = True) -> Progress:
|
|
156
|
+
"""Single consistent Progress factory across all DB-build stages, bound to
|
|
157
|
+
whichever console is active when the bar is created.
|
|
158
|
+
|
|
159
|
+
@brief Progress factory used by doxygen, AST, and XML stages.
|
|
160
|
+
@version 2
|
|
161
|
+
"""
|
|
162
|
+
cols = [
|
|
163
|
+
SpinnerColumn(),
|
|
164
|
+
TextColumn(" [bold]{task.description:<14}"),
|
|
165
|
+
BarColumn(bar_width=28),
|
|
166
|
+
]
|
|
167
|
+
if known_total:
|
|
168
|
+
cols.append(TaskProgressColumn())
|
|
169
|
+
cols.append(MofNCompleteColumn())
|
|
170
|
+
else:
|
|
171
|
+
cols.append(TextColumn("[cyan]{task.completed}[/] items"))
|
|
172
|
+
cols.append(TimeElapsedColumn())
|
|
173
|
+
return Progress(*cols, console=active_console(), transient=False)
|