hologram-map 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sebastian Fabisz
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,298 @@
1
+ Metadata-Version: 2.4
2
+ Name: hologram-map
3
+ Version: 0.1.0
4
+ Summary: Compress a codebase into a compact, deterministic markdown map embedded in your coding agent's context files
5
+ Author: Sebastian Fabisz
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/lazymaniac/hologram
8
+ Project-URL: Repository, https://github.com/lazymaniac/hologram
9
+ Project-URL: Changelog, https://github.com/lazymaniac/hologram/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/lazymaniac/hologram/issues
11
+ Keywords: llm,agent,context,codebase-map,tree-sitter,claude,digest
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: grammars
26
+ Requires-Dist: tree-sitter; extra == "grammars"
27
+ Requires-Dist: tree-sitter-bash; extra == "grammars"
28
+ Requires-Dist: tree-sitter-c; extra == "grammars"
29
+ Requires-Dist: tree-sitter-c-sharp; extra == "grammars"
30
+ Requires-Dist: tree-sitter-cpp; extra == "grammars"
31
+ Requires-Dist: tree-sitter-css; extra == "grammars"
32
+ Requires-Dist: tree-sitter-go; extra == "grammars"
33
+ Requires-Dist: tree-sitter-html; extra == "grammars"
34
+ Requires-Dist: tree-sitter-java; extra == "grammars"
35
+ Requires-Dist: tree-sitter-kotlin; extra == "grammars"
36
+ Requires-Dist: tree-sitter-lua; extra == "grammars"
37
+ Requires-Dist: tree-sitter-rust; extra == "grammars"
38
+ Requires-Dist: tree-sitter-typescript; extra == "grammars"
39
+ Dynamic: license-file
40
+
41
+ # hologram
42
+
43
+ hologram reads your codebase and writes one compact map of it — public callables, type
44
+ field names, relationships, project-internal calls, private identifiers, and the test
45
+ files/classes that cover the project — directly into the context files your coding
46
+ agents already read. The map is in context from turn zero, before any exploration
47
+ begins.
48
+
49
+ It's a single Python file. It installs its own parsers the first time it needs them,
50
+ and git hooks keep the map up to date after every commit. Generation is fully
51
+ deterministic — no LLM involved — so the same code always produces the same map,
52
+ and a map diff always means the code changed.
53
+
54
+ The name: like a hologram, every fragment of the output carries the shape of the
55
+ whole. Token cost stays low by choosing compact facts instead of truncating them.
56
+
57
+ ## What it's for
58
+
59
+ - **Feature planning** — plan against the real surface of the code: what already
60
+ exists, which module the new thing belongs in, which family of types it should
61
+ extend. Plans written this way survive contact with the codebase.
62
+ - **Implementation** — the agent (or you) finds the existing helper before writing a
63
+ second one, follows the house conventions, and places code where it belongs.
64
+ - **Code review** — `hologram diff` shows a pull request's API drift on one screen,
65
+ including the near-duplicate helpers that sneak in quietly.
66
+ - **Refactoring** — `×0` flags functions and classes with no statically observed
67
+ project references, and the `· deps` lines show which modules are coupled, before
68
+ you start pulling threads.
69
+ - **Debugging** — call chains, private-name lists, and `⋮N` body-size marks point at
70
+ the right file before you open a single one.
71
+ - **Onboarding** — a new teammate, human or agent, reads one block and knows the
72
+ territory: the modules, the vocabulary, the patterns.
73
+
74
+ ## What the output looks like
75
+
76
+ The map of a small Java fixture:
77
+
78
+ ```
79
+ # hologram · 186 LOC · state 817a0445a77f
80
+ · C/R/I{fields} E{values} T:target · f(args):Ret > project calls · -=private · ?=tests · ×0=no static use · ✓=tested · ⋮N=lines · !E=throws · p{a,b}=pa,pb
81
+ · deps .→ids | engine→ids
82
+ src
83
+ App(C) ×0
84
+ main(args) ×0 > PricingEngine,evaluate,OrderId.of,ItemId.of
85
+ delta
86
+ AddOp,RemoveOp(R{nodeId}) : DeltaOp
87
+ weight():int ×0
88
+ DeltaOp(I) sealed:AddOp|RemoveOp
89
+ weight():int ×0
90
+ engine
91
+ OrderStatus(E{NEW,PAID,SHIPPED})
92
+ isTerminal():boolean ×0
93
+ PricePort(I)
94
+ quoteFor(order):Quote ×0
95
+ supports(order):boolean ×0
96
+ PricingEngine(C{basePrices}) : PricePort
97
+ quoteFor(order):Quote ×0 > evaluate
98
+ supports(order):boolean ×0
99
+ evaluate(order,items):Quote !UnknownItem > UnknownItemException,Quote
100
+ Quote(R{order,totalCents})
101
+ UnknownItemException(C) : RuntimeException
102
+ ids
103
+ ItemId,OrderId,UserId(R{value})
104
+ of(raw):⟨X⟩ > ⟨X⟩
105
+ ? tests
106
+ src/test
107
+ PricingEngineTest.java{PricingEngineTest,BulkDiscounts}
108
+ ```
109
+
110
+ Reading it is easier than it looks, and the legend on line 2 teaches the notation to
111
+ any LLM:
112
+
113
+ - **The tree** mirrors your directory layout, shared path prefixes stated once.
114
+ - **Types** expose field names rather than redundant field types.
115
+ `PricingEngine(C{basePrices}) : PricePort` is a class with a `basePrices` field
116
+ implementing `PricePort`. Records/interfaces use the same braces, enums list
117
+ values, aliases retain their target, and sealed interfaces retain permitted types.
118
+ - **Functions** show parameter names and return types: `evaluate(order,items):Quote`.
119
+ Types appear beside names only when overloads would otherwise collide.
120
+ - **Call chains** follow the `>`: what a function calls, in order. Variables resolve
121
+ to their declared types (`PricingEngine.evaluate`, not `engine.evaluate`), standard
122
+ library calls are dropped, and chains are transitively reduced — if `a > b` and
123
+ `b > c`, then `a`'s line doesn't repeat `c`.
124
+ - **Same-shape types group.** `ItemId,OrderId,UserId(R{value})` is a family in one
125
+ entry; `⟨X⟩` stands for each member's own name in the methods they share.
126
+ - **Markers**: `✓` = resolved call from a test · `⋮120` = the body is 120 lines ·
127
+ `×0` = no statically observed project reference to a function/class/method ·
128
+ `!UnknownItem` = throws (`Exception` suffix implied) · no `:Ret` = returns void ·
129
+ `» index.ts: A,B` = barrel re-exports.
130
+ - **Private members** always appear as names. Repeated prefixes factor losslessly:
131
+ `_extract_{java,python,typescript}` means those three exact identifiers.
132
+ - **Tests** list every detected test file and its classes. Test functions are omitted
133
+ because their names cost tokens without improving placement guidance.
134
+ - **`· deps a→b`** = module `a` uses types from module `b`: the import architecture
135
+ without reading imports.
136
+ - **`state`** hashes the exact sources plus the generator, so source or extraction/
137
+ rendering changes make old maps stale.
138
+
139
+ ## Languages
140
+
141
+ | Language | What you get |
142
+ |---|---|
143
+ | Java, C#, TypeScript/JS, TSX/JSX | types with named fields, name-based signatures, relations, resolved calls, privates, aliases, object APIs, re-exports |
144
+ | Python | same, via the standard library's `ast` — zero dependencies |
145
+ | Kotlin | classes, data classes, enums, interfaces, named fields, supers, calls |
146
+ | Go, Rust, C, C++ | types, traits, structs, signatures, calls, receiver bindings |
147
+ | Vue, Svelte | the component plus everything in its `<script>` block |
148
+ | Lua | functions and methods with call chains (params by name — it's untyped) |
149
+ | Bash/zsh (`.sh`, `.bash`, `.zsh`) | functions (both definition forms) with command-call chains; `_name` = private |
150
+ | HTML | element ids and custom-element tags, plus nested `<script>`/`<style>` blocks run through the JS/CSS extractors (when those grammars are installed) |
151
+ | CSS | class/id selectors, custom properties (`--x`), `@keyframes` names — names only |
152
+ | Helm | template `define` names, `values.yaml` keys, chart name |
153
+
154
+ ## Getting started
155
+
156
+ Install from PyPI (the `grammars` extra pulls in every tree-sitter parser up front):
157
+
158
+ ```bash
159
+ pip install "hologram-map[grammars]"
160
+ ```
161
+
162
+ ```bash
163
+ hologram init --root /path/to/repo
164
+ ```
165
+
166
+ Or skip installation entirely — it's a single file. Clone it anywhere and point it
167
+ at a repo:
168
+
169
+ ```bash
170
+ python3 ~/workspace/hologram/hologram.py init --root /path/to/repo
171
+ ```
172
+
173
+ That installs git hooks and embeds the map in every agent context file the repo
174
+ already has. From then on the hooks refresh it after every commit, merge, and
175
+ checkout. You never touch them again.
176
+
177
+ The first time it meets a language it has no parser for, it offers to set one up: it
178
+ creates a `.venv` next to itself and pip-installs the right tree-sitter grammar. You
179
+ type `y` once. Every later run finds that venv on its own, so plain
180
+ `python3 hologram.py …` always works. Python-only repos skip all of this — the
181
+ standard library is enough.
182
+
183
+ Everything it can do:
184
+
185
+ ```bash
186
+ hologram.py build --root . # refresh the embedded map
187
+ hologram.py build --root . --lang java # limit to one or more languages
188
+ hologram.py build --root . --if-stale # rebuild only if the code changed
189
+ hologram.py check --root . # is every context file current? exit 0 yes / 1 no
190
+ hologram.py diff HEAD~3 --root . # how did the API change since then?
191
+ ```
192
+
193
+ A successful build prints the map's token cost and where it went:
194
+
195
+ ```
196
+ hologram: 1193 tokens embedded in CLAUDE.md, AGENTS.md
197
+ ```
198
+
199
+ ## Which agents get the map
200
+
201
+ `init`/`build` detect the context files a repo already uses and attach the map to each
202
+ one — the same map, everywhere, so Claude Code and Codex and Cursor can't drift apart:
203
+
204
+ | Agent | File it reads |
205
+ |---|---|
206
+ | Claude Code | `CLAUDE.md` |
207
+ | Codex, opencode, Amp, Jules, Zed | `AGENTS.md` |
208
+ | Gemini CLI | `GEMINI.md` |
209
+ | Qwen Code | `QWEN.md` |
210
+ | GitHub Copilot | `.github/copilot-instructions.md`, `.github/instructions/` |
211
+ | Cline | `.clinerules` (file or directory) |
212
+ | Cursor | `.cursorrules`, `.cursor/rules/` |
213
+ | Windsurf | `.windsurfrules`, `.windsurf/rules/` |
214
+ | Roo Code | `.roorules`, `.roo/rules/` |
215
+
216
+ Existing files are attached to, never invented: hologram only writes a context file
217
+ that already exists. Rule *directories* get one managed file of hologram's own
218
+ (`.cursor/rules/hologram.mdc`, `.clinerules/hologram.md`, …), created with whatever
219
+ front matter that agent needs to load it. A repo with none of these gets a `CLAUDE.md`.
220
+
221
+ Inside each file the map lives between two HTML-comment markers, and the block opens
222
+ with a short note telling the agent what it is looking at. Everything you wrote around
223
+ the block is preserved on every rebuild — the map is a block in your instructions
224
+ file, not a replacement for it.
225
+
226
+ ## Staying fresh
227
+
228
+ A stale map is worse than none — an agent trusting a description of deleted code
229
+ is confidently wrong. Three commands make freshness a non-issue:
230
+
231
+ - `check` recomputes the `state` hash in milliseconds, without parsing anything, and
232
+ compares it against the stamp in every context file. Any target lagging means exit
233
+ 1. Wire it into CI or an agent harness.
234
+ - `build --if-stale` uses the same probe, so "rebuild just in case" costs nothing
235
+ when nothing changed.
236
+ - `diff <rev>` points the same machinery backwards: it rebuilds the map as it
237
+ looked at an older revision and prints the difference — a pull request's API drift
238
+ on one screen.
239
+
240
+ ## Does it actually help? An honest take
241
+
242
+ hologram exists because of one specific failure: an agent lands in a repo with no
243
+ map, greps its way to a partial picture, and writes code that already exists.
244
+
245
+ **The good.** An agent normally burns thousands of tokens re-discovering project
246
+ structure every single session, and most of what it reads gets discarded. The map
247
+ replaces that exploration. Duplication gets a real counterweight: "does this already
248
+ exist?" becomes something the agent can see rather than something it only catches by
249
+ grepping the exact right word. And because the map shows your conventions — all your
250
+ ID types are one-field records, your services take dependencies through constructors —
251
+ a model tends to extend the patterns it sees rather than invent parallel ones. Factored
252
+ private names, concise call lines, and the test index tell it which file to open first
253
+ without a raw symbol dump.
254
+
255
+ **The caveats.** None of this is enforced. The map competes for the model's
256
+ attention like everything else in context, and an agent can ignore it and reimplement
257
+ a helper anyway — it shifts the odds, it is not a guardrail. Function bodies stay
258
+ invisible: a 500-line algorithm and a one-liner expose the same signature, so the
259
+ map tells an agent what exists, never how well it's built. `✓` means a test
260
+ mentions the function, not that the function is correct. If your naming is misleading,
261
+ the map compresses and transmits the misleading names with perfect fidelity. Depth
262
+ varies by language — the table above is honest about which ones get the full
263
+ treatment.
264
+
265
+ **What's been measured.** The map in context, against the same agent without it, on a
266
+ private 133k-LOC codebase the model had never seen (10 headless sonnet sessions vs
267
+ matched baselines, transcripts reviewed by hand): **outcomes stayed equal while effort
268
+ dropped ~36% in turns and ~55% in searches, with navigation tasks 40% faster — one
269
+ answered in 4 turns with zero file reads, straight from the embedded map. Total tokens
270
+ came out level: the per-turn cost of the map was fully offset by fewer turns.** That is
271
+ the thesis doing what it was supposed to do — the map in context replaces exploration.
272
+
273
+ Caveats stay honest: n=1 per cell, one model, one corpus (results withheld — private);
274
+ duplication was zero in every condition, so the measured win is orientation speed, not
275
+ duplication prevention; and larger repos, weaker models, and chat-only contexts remain
276
+ unmeasured. On a famous OSS corpus the model has largely memorized, expect no benefit
277
+ at all — a control agent walks straight to the right API from training memory.
278
+
279
+ ## How it works
280
+
281
+ One file, one pipeline: scan (only git-tracked files when inside a repo), extract,
282
+ render, embed. Each language has its own small extractor and they all produce the same
283
+ `Symbol` records, so everything downstream — receiver resolution, transitive
284
+ reduction, shape grouping, the final tree — is language-neutral and written once.
285
+ Formatting decisions were measured with a real tokenizer (o200k), not guessed.
286
+
287
+ ## Tests
288
+
289
+ ```bash
290
+ .venv/bin/python -m unittest discover -s tests
291
+ ```
292
+
293
+ Runs under plain `python3` too — tests for languages whose grammar isn't installed
294
+ just skip.
295
+
296
+ ## License
297
+
298
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,258 @@
1
+ # hologram
2
+
3
+ hologram reads your codebase and writes one compact map of it — public callables, type
4
+ field names, relationships, project-internal calls, private identifiers, and the test
5
+ files/classes that cover the project — directly into the context files your coding
6
+ agents already read. The map is in context from turn zero, before any exploration
7
+ begins.
8
+
9
+ It's a single Python file. It installs its own parsers the first time it needs them,
10
+ and git hooks keep the map up to date after every commit. Generation is fully
11
+ deterministic — no LLM involved — so the same code always produces the same map,
12
+ and a map diff always means the code changed.
13
+
14
+ The name: like a hologram, every fragment of the output carries the shape of the
15
+ whole. Token cost stays low by choosing compact facts instead of truncating them.
16
+
17
+ ## What it's for
18
+
19
+ - **Feature planning** — plan against the real surface of the code: what already
20
+ exists, which module the new thing belongs in, which family of types it should
21
+ extend. Plans written this way survive contact with the codebase.
22
+ - **Implementation** — the agent (or you) finds the existing helper before writing a
23
+ second one, follows the house conventions, and places code where it belongs.
24
+ - **Code review** — `hologram diff` shows a pull request's API drift on one screen,
25
+ including the near-duplicate helpers that sneak in quietly.
26
+ - **Refactoring** — `×0` flags functions and classes with no statically observed
27
+ project references, and the `· deps` lines show which modules are coupled, before
28
+ you start pulling threads.
29
+ - **Debugging** — call chains, private-name lists, and `⋮N` body-size marks point at
30
+ the right file before you open a single one.
31
+ - **Onboarding** — a new teammate, human or agent, reads one block and knows the
32
+ territory: the modules, the vocabulary, the patterns.
33
+
34
+ ## What the output looks like
35
+
36
+ The map of a small Java fixture:
37
+
38
+ ```
39
+ # hologram · 186 LOC · state 817a0445a77f
40
+ · C/R/I{fields} E{values} T:target · f(args):Ret > project calls · -=private · ?=tests · ×0=no static use · ✓=tested · ⋮N=lines · !E=throws · p{a,b}=pa,pb
41
+ · deps .→ids | engine→ids
42
+ src
43
+ App(C) ×0
44
+ main(args) ×0 > PricingEngine,evaluate,OrderId.of,ItemId.of
45
+ delta
46
+ AddOp,RemoveOp(R{nodeId}) : DeltaOp
47
+ weight():int ×0
48
+ DeltaOp(I) sealed:AddOp|RemoveOp
49
+ weight():int ×0
50
+ engine
51
+ OrderStatus(E{NEW,PAID,SHIPPED})
52
+ isTerminal():boolean ×0
53
+ PricePort(I)
54
+ quoteFor(order):Quote ×0
55
+ supports(order):boolean ×0
56
+ PricingEngine(C{basePrices}) : PricePort
57
+ quoteFor(order):Quote ×0 > evaluate
58
+ supports(order):boolean ×0
59
+ evaluate(order,items):Quote !UnknownItem > UnknownItemException,Quote
60
+ Quote(R{order,totalCents})
61
+ UnknownItemException(C) : RuntimeException
62
+ ids
63
+ ItemId,OrderId,UserId(R{value})
64
+ of(raw):⟨X⟩ > ⟨X⟩
65
+ ? tests
66
+ src/test
67
+ PricingEngineTest.java{PricingEngineTest,BulkDiscounts}
68
+ ```
69
+
70
+ Reading it is easier than it looks, and the legend on line 2 teaches the notation to
71
+ any LLM:
72
+
73
+ - **The tree** mirrors your directory layout, shared path prefixes stated once.
74
+ - **Types** expose field names rather than redundant field types.
75
+ `PricingEngine(C{basePrices}) : PricePort` is a class with a `basePrices` field
76
+ implementing `PricePort`. Records/interfaces use the same braces, enums list
77
+ values, aliases retain their target, and sealed interfaces retain permitted types.
78
+ - **Functions** show parameter names and return types: `evaluate(order,items):Quote`.
79
+ Types appear beside names only when overloads would otherwise collide.
80
+ - **Call chains** follow the `>`: what a function calls, in order. Variables resolve
81
+ to their declared types (`PricingEngine.evaluate`, not `engine.evaluate`), standard
82
+ library calls are dropped, and chains are transitively reduced — if `a > b` and
83
+ `b > c`, then `a`'s line doesn't repeat `c`.
84
+ - **Same-shape types group.** `ItemId,OrderId,UserId(R{value})` is a family in one
85
+ entry; `⟨X⟩` stands for each member's own name in the methods they share.
86
+ - **Markers**: `✓` = resolved call from a test · `⋮120` = the body is 120 lines ·
87
+ `×0` = no statically observed project reference to a function/class/method ·
88
+ `!UnknownItem` = throws (`Exception` suffix implied) · no `:Ret` = returns void ·
89
+ `» index.ts: A,B` = barrel re-exports.
90
+ - **Private members** always appear as names. Repeated prefixes factor losslessly:
91
+ `_extract_{java,python,typescript}` means those three exact identifiers.
92
+ - **Tests** list every detected test file and its classes. Test functions are omitted
93
+ because their names cost tokens without improving placement guidance.
94
+ - **`· deps a→b`** = module `a` uses types from module `b`: the import architecture
95
+ without reading imports.
96
+ - **`state`** hashes the exact sources plus the generator, so source or extraction/
97
+ rendering changes make old maps stale.
98
+
99
+ ## Languages
100
+
101
+ | Language | What you get |
102
+ |---|---|
103
+ | Java, C#, TypeScript/JS, TSX/JSX | types with named fields, name-based signatures, relations, resolved calls, privates, aliases, object APIs, re-exports |
104
+ | Python | same, via the standard library's `ast` — zero dependencies |
105
+ | Kotlin | classes, data classes, enums, interfaces, named fields, supers, calls |
106
+ | Go, Rust, C, C++ | types, traits, structs, signatures, calls, receiver bindings |
107
+ | Vue, Svelte | the component plus everything in its `<script>` block |
108
+ | Lua | functions and methods with call chains (params by name — it's untyped) |
109
+ | Bash/zsh (`.sh`, `.bash`, `.zsh`) | functions (both definition forms) with command-call chains; `_name` = private |
110
+ | HTML | element ids and custom-element tags, plus nested `<script>`/`<style>` blocks run through the JS/CSS extractors (when those grammars are installed) |
111
+ | CSS | class/id selectors, custom properties (`--x`), `@keyframes` names — names only |
112
+ | Helm | template `define` names, `values.yaml` keys, chart name |
113
+
114
+ ## Getting started
115
+
116
+ Install from PyPI (the `grammars` extra pulls in every tree-sitter parser up front):
117
+
118
+ ```bash
119
+ pip install "hologram-map[grammars]"
120
+ ```
121
+
122
+ ```bash
123
+ hologram init --root /path/to/repo
124
+ ```
125
+
126
+ Or skip installation entirely — it's a single file. Clone it anywhere and point it
127
+ at a repo:
128
+
129
+ ```bash
130
+ python3 ~/workspace/hologram/hologram.py init --root /path/to/repo
131
+ ```
132
+
133
+ That installs git hooks and embeds the map in every agent context file the repo
134
+ already has. From then on the hooks refresh it after every commit, merge, and
135
+ checkout. You never touch them again.
136
+
137
+ The first time it meets a language it has no parser for, it offers to set one up: it
138
+ creates a `.venv` next to itself and pip-installs the right tree-sitter grammar. You
139
+ type `y` once. Every later run finds that venv on its own, so plain
140
+ `python3 hologram.py …` always works. Python-only repos skip all of this — the
141
+ standard library is enough.
142
+
143
+ Everything it can do:
144
+
145
+ ```bash
146
+ hologram.py build --root . # refresh the embedded map
147
+ hologram.py build --root . --lang java # limit to one or more languages
148
+ hologram.py build --root . --if-stale # rebuild only if the code changed
149
+ hologram.py check --root . # is every context file current? exit 0 yes / 1 no
150
+ hologram.py diff HEAD~3 --root . # how did the API change since then?
151
+ ```
152
+
153
+ A successful build prints the map's token cost and where it went:
154
+
155
+ ```
156
+ hologram: 1193 tokens embedded in CLAUDE.md, AGENTS.md
157
+ ```
158
+
159
+ ## Which agents get the map
160
+
161
+ `init`/`build` detect the context files a repo already uses and attach the map to each
162
+ one — the same map, everywhere, so Claude Code and Codex and Cursor can't drift apart:
163
+
164
+ | Agent | File it reads |
165
+ |---|---|
166
+ | Claude Code | `CLAUDE.md` |
167
+ | Codex, opencode, Amp, Jules, Zed | `AGENTS.md` |
168
+ | Gemini CLI | `GEMINI.md` |
169
+ | Qwen Code | `QWEN.md` |
170
+ | GitHub Copilot | `.github/copilot-instructions.md`, `.github/instructions/` |
171
+ | Cline | `.clinerules` (file or directory) |
172
+ | Cursor | `.cursorrules`, `.cursor/rules/` |
173
+ | Windsurf | `.windsurfrules`, `.windsurf/rules/` |
174
+ | Roo Code | `.roorules`, `.roo/rules/` |
175
+
176
+ Existing files are attached to, never invented: hologram only writes a context file
177
+ that already exists. Rule *directories* get one managed file of hologram's own
178
+ (`.cursor/rules/hologram.mdc`, `.clinerules/hologram.md`, …), created with whatever
179
+ front matter that agent needs to load it. A repo with none of these gets a `CLAUDE.md`.
180
+
181
+ Inside each file the map lives between two HTML-comment markers, and the block opens
182
+ with a short note telling the agent what it is looking at. Everything you wrote around
183
+ the block is preserved on every rebuild — the map is a block in your instructions
184
+ file, not a replacement for it.
185
+
186
+ ## Staying fresh
187
+
188
+ A stale map is worse than none — an agent trusting a description of deleted code
189
+ is confidently wrong. Three commands make freshness a non-issue:
190
+
191
+ - `check` recomputes the `state` hash in milliseconds, without parsing anything, and
192
+ compares it against the stamp in every context file. Any target lagging means exit
193
+ 1. Wire it into CI or an agent harness.
194
+ - `build --if-stale` uses the same probe, so "rebuild just in case" costs nothing
195
+ when nothing changed.
196
+ - `diff <rev>` points the same machinery backwards: it rebuilds the map as it
197
+ looked at an older revision and prints the difference — a pull request's API drift
198
+ on one screen.
199
+
200
+ ## Does it actually help? An honest take
201
+
202
+ hologram exists because of one specific failure: an agent lands in a repo with no
203
+ map, greps its way to a partial picture, and writes code that already exists.
204
+
205
+ **The good.** An agent normally burns thousands of tokens re-discovering project
206
+ structure every single session, and most of what it reads gets discarded. The map
207
+ replaces that exploration. Duplication gets a real counterweight: "does this already
208
+ exist?" becomes something the agent can see rather than something it only catches by
209
+ grepping the exact right word. And because the map shows your conventions — all your
210
+ ID types are one-field records, your services take dependencies through constructors —
211
+ a model tends to extend the patterns it sees rather than invent parallel ones. Factored
212
+ private names, concise call lines, and the test index tell it which file to open first
213
+ without a raw symbol dump.
214
+
215
+ **The caveats.** None of this is enforced. The map competes for the model's
216
+ attention like everything else in context, and an agent can ignore it and reimplement
217
+ a helper anyway — it shifts the odds, it is not a guardrail. Function bodies stay
218
+ invisible: a 500-line algorithm and a one-liner expose the same signature, so the
219
+ map tells an agent what exists, never how well it's built. `✓` means a test
220
+ mentions the function, not that the function is correct. If your naming is misleading,
221
+ the map compresses and transmits the misleading names with perfect fidelity. Depth
222
+ varies by language — the table above is honest about which ones get the full
223
+ treatment.
224
+
225
+ **What's been measured.** The map in context, against the same agent without it, on a
226
+ private 133k-LOC codebase the model had never seen (10 headless sonnet sessions vs
227
+ matched baselines, transcripts reviewed by hand): **outcomes stayed equal while effort
228
+ dropped ~36% in turns and ~55% in searches, with navigation tasks 40% faster — one
229
+ answered in 4 turns with zero file reads, straight from the embedded map. Total tokens
230
+ came out level: the per-turn cost of the map was fully offset by fewer turns.** That is
231
+ the thesis doing what it was supposed to do — the map in context replaces exploration.
232
+
233
+ Caveats stay honest: n=1 per cell, one model, one corpus (results withheld — private);
234
+ duplication was zero in every condition, so the measured win is orientation speed, not
235
+ duplication prevention; and larger repos, weaker models, and chat-only contexts remain
236
+ unmeasured. On a famous OSS corpus the model has largely memorized, expect no benefit
237
+ at all — a control agent walks straight to the right API from training memory.
238
+
239
+ ## How it works
240
+
241
+ One file, one pipeline: scan (only git-tracked files when inside a repo), extract,
242
+ render, embed. Each language has its own small extractor and they all produce the same
243
+ `Symbol` records, so everything downstream — receiver resolution, transitive
244
+ reduction, shape grouping, the final tree — is language-neutral and written once.
245
+ Formatting decisions were measured with a real tokenizer (o200k), not guessed.
246
+
247
+ ## Tests
248
+
249
+ ```bash
250
+ .venv/bin/python -m unittest discover -s tests
251
+ ```
252
+
253
+ Runs under plain `python3` too — tests for languages whose grammar isn't installed
254
+ just skip.
255
+
256
+ ## License
257
+
258
+ MIT — see [LICENSE](LICENSE).