Skip to main content

hologram

hologram reads your codebase and writes one compact map of it — public callables, type field names, relationships, project-internal calls, private identifiers, and the test files/classes that cover the project — directly into the context files your coding agents already read. The map is in context from turn zero, before any exploration begins.

It ships as a pip package and as a single runnable file (hologram.pyz). It installs its own parsers the first time it needs them, and git hooks keep the map up to date after every commit. Generation uses no LLM and is deterministic under a fixed Python/parser toolchain: the same sources, Hologram version, settings, runtime, and grammar versions produce the same map. Under those fixed inputs, a map diff means the code changed.

The name: like a hologram, every fragment of the output carries the shape of the whole. Token cost stays low by choosing compact facts instead of truncating them.

What it's for

  • Feature planning — plan against the real surface of the code: what already exists, which module the new thing belongs in, which family of types it should extend. Plans written this way survive contact with the codebase.
  • Implementation — the agent (or you) finds the existing helper before writing a second one, follows the house conventions, and places code where it belongs.
  • Code reviewhologram diff shows a pull request's API drift on one screen; hologram review goes further and names the near-duplicate helpers, re-covered test paths, and misplaced additions that sneak in quietly — from the post-commit hook, straight into the committing agent's context.
  • Refactoring×0 flags functions and classes with no statically observed project references, and the call chains show which modules are coupled, before you start pulling threads.
  • Debugging — call chains, private-name lists, and ~N body-size marks point at the right file before you open a single one.
  • Onboarding — a new teammate, human or agent, reads one block and knows the territory: the modules, the vocabulary, the patterns.

What the output looks like

The map of a small Java fixture:

# hologram · 186 LOC · state 2c3a5cf0b580
· C/R/I{fields} E{values} · f(args):Ret > project calls · ?=tests · ×0=no static use · !E=throws · p{a,b}=pa,pb · :T=supers · sealed:A|B · ←A|B=implementors · Self=own type
src
 App(C) ×0
  main(args) ×0 > PricingEngine,evaluate,OrderId.of,ItemId.of
 engine
  OrderStatus(E{NEW,PAID,SHIPPED})
   isTerminal():boolean ×0
  PricePort(I) ←PricingEngine
   quoteFor(order):Quote ×0
   supports(order):boolean ×0
  PricingEngine(C{basePrices})
   PricingEngine(basePrices)
   quoteFor(order):Quote ×0 > evaluate
   supports(order):boolean ×0
   evaluate(order,items):Quote !UnknownItem > UnknownItemException,Quote
  Quote(R{order,totalCents})
  UnknownItemException(C) : RuntimeException
   UnknownItemException(item)
 ids
  ItemId,OrderId,UserId(R{value})
   of(raw):Self > Self
 transport
  Bicycle,Scooter(R{serial})
   wheels():int ×0
  Vehicle(I) sealed:Bicycle|Scooter
   wheels():int ×0
? tests ·.java
 src/test
  PricingEngineTest{PricingEngineTest,BulkDiscounts}

Reading it is easier than it looks, and the legend on line 2 teaches the notation to any LLM:

  • The tree mirrors your directory layout, shared path prefixes stated once.
  • The legend on line 2 lists only the notation this particular map uses, so small maps carry a small legend.
  • Types expose field names rather than redundant field types. PricingEngine(C{basePrices}) is a class with a basePrices field. Records/interfaces use the same braces, enums list values, aliases retain their target, and sealed interfaces retain permitted types. Python @dataclass renders as a record (R).
  • Interface relations are stated once, on the interface: PricePort(I) ←PricingEngine names the implementors, so the domain's variation points read off one line. Non-interface supers keep the : T suffix.
  • Functions show parameter names and return types: evaluate(order,items):Quote. Types appear beside names only when overloads would otherwise collide.
  • Routes and annotations that carry business meaning render after the signature: find(id):User @GET/users/{id} (Spring, JAX-RS, Flask/FastAPI, NestJS), @app-user-list (Angular selector), @Transactional-style markers. Noise annotations (@Override, Lombok, …) never appear. Angular route configs render as routes=/users→UserListComponent lines; in React/TSX, JSX usage becomes call edges, so the component render tree is the call graph.
  • Constants are business rules: = config.py: MAX_RETRIES=3,BASE_URL lists UPPER_SNAKE/static-final constants, with scalar literal values inline.
  • Call chains follow the >: what a function calls, in order. Variables resolve to their declared types (PricingEngine.evaluate, not engine.evaluate), standard library calls are dropped, and chains are transitively reduced — if a > b and b > c, then a's line doesn't repeat c.
  • Same-shape types group. ItemId,OrderId,UserId(R{value}) is a family in one entry; Self stands for each member's own name in the methods they share.
  • Markers: = resolved call from a test · ~120 = the body is 120 lines · ×0 = no statically observed project reference to a function/class/method (external entry points — route handlers, schedulers, listeners, Angular lifecycle hooks, and Make targets — are exempt) · !UnknownItem = throws (Exception suffix implied) · no :Ret = returns void · » index.ts: A,B = barrel re-exports.
  • Private members always appear as names. Repeated prefixes and suffixes factor losslessly: _extract_{java,python,typescript} and {TaskLoader,Workspace}Test each mean those exact identifiers.
  • Tests list every detected test file and its classes, each class carrying a coverage edge to the first non-obvious production symbol it exercises ({WorkspaceTest>make_workspace+1,…}; +N = more targets). Test functions are omitted because their names cost tokens without improving placement guidance. When every test file shares one extension it is stated once in the header (? tests ·.java), and a file whose only test class matches its name folds to one token (ThemeTest>loadTheme+2 — no braces).
  • Test helpers — reusable drivers/builders/shared bases — render with a * sigil and, when referenced by other test files, their full public signatures: the reuse targets agents otherwise re-invent. Helpers living under directories named fixtures, testdata, or resources are never scanned (denylist).
  • state hashes the exact sources plus the generator, so source or Hologram extraction/rendering changes make old maps stale. It does not fingerprint the Python runtime or optional parser package versions; rebuild after upgrading that toolchain even when check reports fresh.

Languages

Language What you get
Java, C#, TypeScript/JS, TSX/JSX types with named fields, name-based signatures, relations, resolved calls, privates, aliases, object APIs, re-exports; Java additionally annotations/routes and static-final constants; C# additionally attributes/routes (ASP.NET) and constants
TypeScript (Angular) @Component selectors, @Injectable, constructor DI receiver resolution, @Input/@Output fields, route configs (routes=/path→Component), template→component usage edges (inline template: and templateUrl)
TSX/JSX (React) JSX usage as call edges (the render tree is the call graph), memo/forwardRef-wrapped components, React.FC<Props> prop types
Python same as Java tier, via the standard library's ast — zero dependencies; decorators/routes (Flask, FastAPI), module constants, @dataclass as record
Kotlin classes, data classes, enums, interfaces, named fields, supers, calls, local-variable receiver bindings, @Throws/throw extraction, annotations/routes (Spring), const val constants
Go structs, interfaces, signatures, calls, receiver bindings, constants
Rust structs, traits (with supertraits), enums, signatures, calls, receiver bindings, attribute macros/routes (actix), constants
C, C++ types, structs, signatures, calls, receiver bindings; C++ additionally throw extraction
PHP classes, interfaces, traits, enums, typed params, fields, supers, $x = new T() bindings, throw extraction, PHP 8 attributes/routes (Symfony), class constants
Swift classes, structs, enums, protocols, inheritance, typed params, fields, let x = T() bindings, throw extraction
Scala classes, case classes, traits, objects, extends, typed params, fields, val x = new T() bindings, throw extraction
Ruby classes, modules, methods with param names and call chains, attr_*/@ivar fields; private/protected sections respected (untyped — no receiver resolution)
Vue, Svelte the component plus everything in its <script> block
Lua functions and methods with call chains (params by name — it's untyped)
Bash/zsh (.sh, .bash, .zsh) one node per script with its functions nested under it, command-call chains, variables with literal values (secret-redacted); _name = private
HTML element ids and custom-element tags, plus nested <script>/<style> blocks run through the JS/CSS extractors (when those grammars are installed)
CSS class/id selectors, custom properties (--x), @keyframes names — names only
Helm template define names, values.yaml keys, chart name
Makefile (Makefile, *.mk) targets as external commands with caller-settable recipe variables (deploy(ENV,MANIFEST)) and prerequisite call edges (deploy > build); ordinary =, :=, +=, and ?= values are overridable, explicit override values are internal; repeated/double-colon rules merge; .PHONY/pattern rules skipped; _name = private

Getting started

Install from PyPI (the grammars extra pulls in every tree-sitter parser up front):

pip install "hologram-map[grammars]"
hologram init --root /path/to/repo

Or skip installation entirely — download the single-file hologram.pyz from the latest release (or clone the repo and use hologram.py) and point it at a repo:

python3 hologram.pyz init --root /path/to/repo

That installs git hooks and embeds the map in every agent context file the repo already has. From then on the hooks refresh it after every commit, merge, and checkout. You never touch them again.

The first time it meets a language it has no parser for, it offers to set one up: it creates a .venv next to itself and pip-installs the right tree-sitter grammar. You type y once. Every later run finds that venv on its own, so plain python3 hologram.py … always works. Python-only repos skip all of this — the standard library is enough.

Everything it can do:

hologram build --root .                    # refresh the embedded map
hologram build --root . --lang java        # limit to one or more languages;
                                           # the filter is stamped into the map and
                                           # reused by every later rebuild/check
                                           # (clear with --lang all)
hologram build --root . --if-stale         # rebuild only if the code changed
hologram check --root .                    # is every context file current? exit 0 yes / 1 no
hologram diff HEAD~3 --root .              # how did the API change since then?
hologram review --root .                   # map-level drift in tracked/staged work
hologram review HEAD~3 --root .            # …or of the last three commits
hologram review --root . --json            # stable finding IDs for automation
hologram print --root .                    # stdout only; context/source files stay untouched
hologram build --root . --budget 8000      # fit the map into a token budget
hologram stats --root . --budget 8000      # inspect that decision; add --json for tooling
hologram uninstall --root .                # remove the hooks and embedded blocks

(Substitute python3 hologram.pyz or python3 hologram.py for hologram when running the single-file form.)

A successful build prints the map's estimated token cost and where it went:

hologram: 1193 tokens embedded in CLAUDE.md, AGENTS.md

Which agents get the map

init/build detect the context files a repo already uses and attach the map to each one — the same map, everywhere, so Claude Code and Codex and Cursor can't drift apart:

Agent File it reads
Claude Code CLAUDE.md
Codex, opencode, Jules, Zed AGENTS.md
Amp AGENT.md
Gemini CLI GEMINI.md
Qwen Code QWEN.md
Aider CONVENTIONS.md
GitHub Copilot .github/copilot-instructions.md, .github/instructions/
Cline .clinerules (file or directory)
Cursor .cursorrules, .cursor/rules/
Windsurf .windsurfrules, .windsurf/rules/
Roo Code .roorules, .roo/rules/
JetBrains Junie .junie/guidelines.md
Continue .continue/rules/
Kiro .kiro/steering/

Existing files are attached to, never invented: hologram only writes a context file that already exists. Rule directories get one managed file of hologram's own (.cursor/rules/hologram.mdc, .clinerules/hologram.md, …), created with whatever front matter that agent needs to load it. A repo with none of these gets a CLAUDE.md.

Inside each file the map lives between two HTML-comment markers, and the block opens with a short note telling the agent what it is looking at. Everything you wrote around the block is preserved on every rebuild — the map is a block in your instructions file, not a replacement for it.

Staying fresh

A stale map is worse than none — an agent trusting a description of deleted code is confidently wrong. Three commands make freshness a non-issue:

  • check recomputes the state hash in milliseconds, without parsing anything, and compares it against the stamp in every context file. Any target lagging means exit
    1. Wire it into CI or an agent harness.
  • build --if-stale uses the same probe, so "rebuild just in case" costs nothing when nothing changed.
  • diff <rev> points the same machinery backwards: it rebuilds the map as it looked at an older revision and prints the difference — a pull request's API drift on one screen.

Reviewing changes — the map talks back

hologram review [REV] compares the map-level facts of your working tree against a revision (default HEAD) and reports what drifted:

  • near-duplicates — a new function whose name is suspiciously close to an existing one in another file, with the original's map line as the pointer (delegating to the original doesn't count — that's reuse);
  • re-covered paths — a new test edge to a production symbol some other test class already covers;
  • dead on arrival — a new public symbol with zero static references;
  • orphaned tests — a test still naming production code this change deleted;
  • API drift+added −removed ~changed in one line across public symbol kinds, overloads, callable signatures, fields, relationships, mapped routes/annotations, throws, constructors, and constants (--brief K prints just this for the last K commits);
  • placement — a new symbol whose calls point overwhelmingly at a different module than the one it landed in.

Findings are advisory and do not change a successful invocation's exit status; invalid revisions or setup failures still exit nonzero. --quiet-if-clean prints nothing when there's nothing to say, and --json exposes stable finding IDs and structured metadata for harnesses that verify whether a finding remains in the final state. Review JSON includes source paths, subjects, details, and corpus-derived IDs; do not publish it without explicit authorization from the corpus owner. init wires review into the post-commit hook, which is the interesting part: when a coding agent commits, the findings print into the agent's own session — the map answers back at exactly the moment the mistake is cheapest to undo. Findings are heuristic (name similarity, call affinity), so expect the occasional false positive; they point, you decide. Foreign hook scripts that exec another tool will skip an appended hologram line — a limitation every appended hook line has.

Review output is never embedded into context files — under a fixed runtime/parser toolchain, the embedded map stays a pure function of tracked sources plus the Hologram version and settings. Review scans Git-indexed files; use git add -N path/to/new-file before reviewing a wholly untracked addition.

Fitting a token budget

The map is already compact (facts are chosen, never truncated), but --budget N can target an estimated ceiling with a deterministic degradation ladder. Budget, fit, and utilization use the dependency-free ceil(characters / 4) estimate; they are deterministic planning values, not a hard limit from a model tokenizer. Hologram compares the complete candidates — stamp, legend, disclosure, and facts — and selects the least-degraded one that fits:

level drops
L1 test-index coverage edges
L2 test-helper method signatures
L3 private-name inventories
L4 call chains of untested functions
L5 methods of types with zero static fan-in, except external entrypoints
L6 all remaining call chains
L7 non-entrypoint method lines and const values — the skeleton: type headers with fields, external route/listener/Make commands, top-level signatures, const names, test file names

The applied level is stamped in the header (· budget 8000 L2) and reused by every later rebuild until you clear it with --budget 0; the same code and budget always produce the same map, and the legend only explains notation that survived. Facts degrade in usefulness order — untested paths lose chains before tested ones, cold types lose methods before referenced ones, and externally invoked routes, listeners, and Make targets survive every level. High-value scalar values ride through L6 and their names remain in the skeleton. Every degraded map carries a disclosure line naming exactly which fact classes were dropped, with an instruction to read the source instead of guessing. Disclosure text can make a deeper candidate larger on tiny maps; selection therefore uses total size, not level order. If no complete candidate fits, the smallest candidate is emitted with a warning suggesting --lang filters — hologram never cuts a fact in half.

When a complete level fits, Hologram uses remaining room to restore whole facts from the next quality boundary. It tries smaller rendered payloads first within each semantic category and interleaves categories deterministically, so a long high-priority fact cannot consume the search cap without testing small facts in the other categories. Adaptive output is stamped A<level> (for example, · budget 8000 A3), while stats reports its exact selection and effective detail. Selection stops once the complete map is within one percent of the target or reaches its fixed trial bound, keeping post-commit rebuild time predictable on large repositories. hologram stats --budget N explains the choice without modifying context or source files (missing-parser bootstrap may still create its managed environment); --json includes the policy version, full/selected/skeleton estimates, fit and utilization, effective detail, retained/dropped bundle IDs, trial count, search truncation, and stop reason. Bundle IDs contain source paths and symbols, so treat JSON statistics as corpus-derived data rather than publication-safe output.

Evaluating effectiveness

Hologram targets a common failure mode: an agent develops a partial picture of a repository and writes code that already exists. The map gives duplication a counterweight and makes project structure available without a full exploratory scan. It remains context, not enforcement: agents can ignore it, bodies stay invisible, means a test references a symbol rather than proving correctness, and extraction depth varies by language.

The benchmark harness supports matched map/control experiments with immutable artifacts and revision-aware reports. Private-corpus prompts, transcripts, results, and derived aggregates are not published. See benchmark/README.md for the privacy boundary and for instructions on running an authorized evaluation.

How it works

One file, one pipeline: scan (only git-tracked files when inside a repo), extract, render, embed. Each language has its own small extractor and they all produce the same Symbol records, so everything downstream — receiver resolution, transitive reduction, shape grouping, the final tree — is language-neutral and written once. Formatting decisions were measured with a real tokenizer (o200k), not guessed.

Tests

python3 tools/run_tests.py --profile core       # no optional grammars required
.venv/bin/python tools/run_tests.py --profile full  # every grammar, zero skips

The full profile preflights every registered parser and fails on any unexpected skip, so a broken grammar install cannot silently turn CI green.

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hologram_map-0.10.0.tar.gz (208.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hologram_map-0.10.0-py3-none-any.whl (104.1 kB view details)

Uploaded Python 3

File details

Details for the file hologram_map-0.10.0.tar.gz.

File metadata

  • Download URL: hologram_map-0.10.0.tar.gz
  • Upload date:
  • Size: 208.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for hologram_map-0.10.0.tar.gz
Algorithm Hash digest
SHA256 954b0065d3883e490ebf4efd23ee039a1e4bba9dea306800e59a4e2ad0c1202a
MD5 21ed653b5345b99cdaf4fbcb0776909f
BLAKE2b-256 480518b080a15681d58bc3dffd7cc8ea724cfcba47beb6a49e96d2d86bdcfcc3

See more details on using hashes here.

Provenance

The following attestation bundles were made for hologram_map-0.10.0.tar.gz:

Publisher: release.yml on lazymaniac/hologram

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hologram_map-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: hologram_map-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 104.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for hologram_map-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4e1003ee5f67199053577d891fedc44999cdef5f925e46842439b6ee1bb5e976
MD5 312ed089c7bec252f4af05cf55d968c0
BLAKE2b-256 8938d38d648f5742e82166869b4bd00f097a480188a8b74eee2fd53408b36c5c

See more details on using hashes here.

Provenance

The following attestation bundles were made for hologram_map-0.10.0-py3-none-any.whl:

Publisher: release.yml on lazymaniac/hologram

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

This release

0.10.0 This release

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page