dekko 0.7.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.
dekko/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """dekko: static code map generator (MAP.md + map.json)."""
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "dekko",
3
+ "owner": {
4
+ "name": "Austin Ahlijian",
5
+ "email": "aahlijia@gmail.com"
6
+ },
7
+ "metadata": {
8
+ "description": "dekko — static code map generator for Claude Code",
9
+ "version": "0.7.0"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "dekko",
14
+ "description": "Adds /map: maps every file, function, type, and call edge without spending model tokens on parsing.",
15
+ "source": "./"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "dekko",
3
+ "description": "Adds a /map command that programmatically scans the repository: every code file, function, parameter, type, return type, and the call relationships between functions. Writes MAP.md and map.json without spending model tokens on parsing.",
4
+ "version": "0.7.0",
5
+ "author": {
6
+ "name": "Austin Ahlijian"
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "dekko": {
4
+ "command": "dekko",
5
+ "args": ["serve", "--mcp"],
6
+ "cwd": "${CLAUDE_PROJECT_DIR}"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Generate MAP.md — a relational map of every file, function, signature, and call in the repo
3
+ argument-hint: "[subpath]"
4
+ allowed-tools: Bash(dekko:*)
5
+ ---
6
+
7
+ ## Code map results
8
+
9
+ The mapping tool has already run; its summary is below.
10
+
11
+ !`dekko map --if-stale . $ARGUMENTS`
12
+
13
+ ## Your task
14
+
15
+ The repository map was generated programmatically by the tool above — do
16
+ NOT parse any source files yourself.
17
+
18
+ 1. If the summary above shows an error, explain the problem to the user
19
+ and how to fix it. In particular, if the `dekko` command was not
20
+ found, tell them to install it with `pip install dekko` (or
21
+ `uv tool install dekko`). Otherwise:
22
+ 2. Relay the summary to the user: how many files were mapped, in which
23
+ languages, how many functions and call relationships were found, and
24
+ anything skipped. If the summary says "map fresh", tell the user the
25
+ existing MAP.md is already up to date and nothing was regenerated.
26
+ 3. Tell the user the map was written to `MAP.md` (human-readable) and
27
+ `map.json` (machine-readable) at the repo root.
28
+ 4. Do not read MAP.md back into context unless the user asks a question
29
+ that requires it.
dekko/cache.py ADDED
@@ -0,0 +1,167 @@
1
+ """Per-file extraction cache stored under ``.dekko/``.
2
+
3
+ Parsing every file with tree-sitter dominates a map run. The cache
4
+ keys each file's extracted ``FileMap`` on the same content hash used
5
+ for provenance: on the next run, files whose hash is unchanged reuse
6
+ their cached ``FileMap`` and skip re-parsing. Resolution still runs
7
+ repo-wide (it is cheap relative to parsing).
8
+
9
+ The cache lives in ``<root>/.dekko/cache.json``. On first creation the
10
+ directory is made self-ignoring (``.dekko/.gitignore`` of ``*``) and
11
+ ``.dekko/`` is appended to the repository ``.gitignore``.
12
+ """
13
+
14
+ import json
15
+ from dataclasses import asdict
16
+ from importlib.metadata import version as _pkg_version
17
+ from pathlib import Path
18
+
19
+ from .mapfile import _file_hash, _symbol_from_dict
20
+ from .model import FileMap, Import, RawCall
21
+
22
+ CACHE_VERSION = 1
23
+ CACHE_DIR = ".dekko"
24
+ CACHE_FILE = "cache.json"
25
+
26
+
27
+ def _tool_version() -> str:
28
+ """Current dekko version, used to invalidate stale extractions."""
29
+ return _pkg_version("dekko")
30
+
31
+
32
+ def _filemap_to_dict(fm: FileMap) -> dict:
33
+ """Serialize a ``FileMap`` for the cache."""
34
+ return asdict(fm)
35
+
36
+
37
+ def _filemap_from_dict(d: dict) -> FileMap:
38
+ """Rebuild a ``FileMap`` from its cached dict."""
39
+ return FileMap(
40
+ path=d["path"],
41
+ language=d["language"],
42
+ symbols=[_symbol_from_dict(s) for s in d.get("symbols", [])],
43
+ calls=[RawCall(**c) for c in d.get("calls", [])],
44
+ imports=[Import(**i) for i in d.get("imports", [])],
45
+ error=d.get("error"),
46
+ )
47
+
48
+
49
+ class IncrementalCache:
50
+ """A read-old / write-new view over the per-file extraction cache.
51
+
52
+ Attributes:
53
+ entries: Cache entries to persist after the run — populated by
54
+ both reused and freshly extracted files.
55
+ """
56
+
57
+ def __init__(self, old: dict[str, dict]) -> None:
58
+ """Initialize with the entries loaded from a prior run.
59
+
60
+ Args:
61
+ old: Previous ``path -> {"hash", "file"}`` entries, or an
62
+ empty dict to force every file to re-parse.
63
+ """
64
+ self._old = old
65
+ self.entries: dict[str, dict] = {}
66
+
67
+ def reuse(self, root: Path, rel: str) -> FileMap | None:
68
+ """Return the cached ``FileMap`` for an unchanged file.
69
+
70
+ Args:
71
+ root: Repository root.
72
+ rel: Repo-relative path of the file.
73
+
74
+ Returns:
75
+ The cached ``FileMap`` when a prior entry's hash matches the
76
+ current file, else ``None``.
77
+ """
78
+ entry = self._old.get(rel)
79
+ if entry is None or entry.get("hash") != _file_hash(root / rel):
80
+ return None
81
+ self.entries[rel] = entry
82
+ return _filemap_from_dict(entry["file"])
83
+
84
+ def store(self, root: Path, rel: str, fm: FileMap) -> None:
85
+ """Record a freshly extracted ``FileMap`` for persistence."""
86
+ self.entries[rel] = {
87
+ "hash": _file_hash(root / rel),
88
+ "file": _filemap_to_dict(fm),
89
+ }
90
+
91
+
92
+ def load(root: Path) -> dict[str, dict]:
93
+ """Load the prior cache entries for a repository.
94
+
95
+ Args:
96
+ root: Repository root.
97
+
98
+ A cache written by a different dekko version is discarded, so
99
+ extractor changes always take effect on the next run without a
100
+ manual ``--full``.
101
+
102
+ Returns:
103
+ ``path -> entry`` mapping, or an empty dict when no usable
104
+ cache exists.
105
+ """
106
+ path = root / CACHE_DIR / CACHE_FILE
107
+ try:
108
+ doc = json.loads(path.read_text())
109
+ except (OSError, json.JSONDecodeError):
110
+ return {}
111
+ if doc.get("version") != CACHE_VERSION:
112
+ return {}
113
+ if doc.get("tool_version") != _tool_version():
114
+ return {}
115
+ files = doc.get("files")
116
+ return files if isinstance(files, dict) else {}
117
+
118
+
119
+ def save(root: Path, cache: IncrementalCache) -> None:
120
+ """Persist a cache and ensure ``.dekko/`` is git-ignored.
121
+
122
+ Args:
123
+ root: Repository root.
124
+ cache: The cache whose ``entries`` should be written.
125
+ """
126
+ cache_dir = root / CACHE_DIR
127
+ cache_dir.mkdir(parents=True, exist_ok=True)
128
+ _ensure_ignored(root, cache_dir)
129
+ doc = {
130
+ "version": CACHE_VERSION,
131
+ "tool_version": _tool_version(),
132
+ "files": cache.entries,
133
+ }
134
+ (cache_dir / CACHE_FILE).write_text(json.dumps(doc) + "\n")
135
+
136
+
137
+ def ensure_dir(root: Path) -> Path:
138
+ """Create ``.dekko/`` and set up gitignore entries.
139
+
140
+ Idempotent — safe to call on every map run. Returns the cache dir.
141
+
142
+ Args:
143
+ root: Repository root.
144
+
145
+ Returns:
146
+ Path to the ``.dekko/`` directory.
147
+ """
148
+ cache_dir = root / CACHE_DIR
149
+ cache_dir.mkdir(parents=True, exist_ok=True)
150
+ _ensure_ignored(root, cache_dir)
151
+ return cache_dir
152
+
153
+
154
+ def _ensure_ignored(root: Path, cache_dir: Path) -> None:
155
+ """Make ``.dekko/`` self-ignoring and ignored by the repo."""
156
+ inner = cache_dir / ".gitignore"
157
+ if not inner.exists():
158
+ inner.write_text("*\n")
159
+
160
+ gitignore = root / ".gitignore"
161
+ entry = f"{CACHE_DIR}/"
162
+ text = gitignore.read_text() if gitignore.exists() else ""
163
+ if entry in text.splitlines():
164
+ return
165
+ if text and not text.endswith("\n"):
166
+ text += "\n"
167
+ gitignore.write_text(text + entry + "\n")