|
| 1 | +# TLDRGraph Agent Contract |
| 2 | + |
| 3 | +**Audience: the coding agent with this repository open** (Claude Code, Cursor, Antigravity). |
| 4 | + |
| 5 | +TLDRGraph builds an architectural graph from the graphify AST export. The layer set |
| 6 | +itself is designed by you, reading this repository. TLDRGraph ships no layer templates. |
| 7 | +Structure *within* a layer, and the high-volume deterministic seams between layers, are |
| 8 | +extracted automatically. What cannot be extracted automatically is: |
| 9 | + |
| 10 | +- indirect dispatch, queue / event hops, dynamically-built routes; |
| 11 | +- the natural-language **intent** that makes semantic search work at all. |
| 12 | + |
| 13 | +That is your job. You are not a fallback for a hosted model — you are the primary |
| 14 | +enrichment path, because **you can open the files**. The API path only ever sees a label |
| 15 | +and a path (`snippet` is never populated), so it guesses. You do not have to guess. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Start here: `tldrgraph init` |
| 20 | + |
| 21 | +One command does everything, and it is resumable: |
| 22 | + |
| 23 | +```bash |
| 24 | +tldrgraph init |
| 25 | +``` |
| 26 | + |
| 27 | +It runs every deterministic step — extraction, classification, indexing, applying whatever |
| 28 | +you last wrote — then stops with a `NEXT ACTION` block the moment it needs judgement only |
| 29 | +you can supply. Do what the block says and run it again. Repeat until `status: done`. |
| 30 | + |
| 31 | +| status | what it wants | |
| 32 | +| --- | --- | |
| 33 | +| `needs_layers` | Read the code and design this repository's architecture. **TLDRGraph ships no layer templates**; nothing will be applied for you. The request carries sketches of how other kinds of codebase divide — for shape only, never to copy. | |
| 34 | +| `needs_confirmation` | Enrichment costs the user tokens. Show them the estimate and ask. Then `tldrgraph init --yes`. | |
| 35 | +| `needs_enrichment` | A batch to open, read and describe, per the schema below. | |
| 36 | +| `done` | Nothing left. Use `query` / `trace` / `layers`. | |
| 37 | + |
| 38 | +`--json` gives you the same thing machine-readably. The sections below document the file |
| 39 | +formats `init` reads and writes; the underlying `queue-enrichment` / `apply-enrichment` |
| 40 | +commands remain available for scripting. |
| 41 | + |
| 42 | +**Copy every `id` verbatim from the request.** A constructed id matches nothing, is |
| 43 | +dropped, and will be reported back to you — but the work is wasted. |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## The loop |
| 48 | + |
| 49 | +```bash |
| 50 | +tldrgraph queue-enrichment --limit 50 # 1. writes .tldrgraph/enrichment_request.yaml |
| 51 | +# 2. you read it, read the SOURCE, and write |
| 52 | +# .tldrgraph/enrichment_response.yaml |
| 53 | +tldrgraph apply-enrichment # 3. merges into the graph, cache and index |
| 54 | +tldrgraph queue-enrichment --limit 50 # 4. repeat -- the queue advances automatically |
| 55 | +``` |
| 56 | + |
| 57 | +Request and response are **separate files**. Never write your answer back into |
| 58 | +`enrichment_request.yaml`; it is regenerated on every run and your work would be lost. |
| 59 | + |
| 60 | +| File | Written by | Read by | |
| 61 | +| --- | --- | --- | |
| 62 | +| `.tldrgraph/enrichment_request.yaml` (or `enrichment_request.json`) | `queue-enrichment` | you | |
| 63 | +| `.tldrgraph/enrichment_response.yaml` (or `enrichment_response.json`) | **you** | `apply-enrichment` | |
| 64 | +| `.tldrgraph/enrichment_cursor.json` | both commands | both commands | |
| 65 | +| `.tldrgraph/pending_enrichment.json` | *(legacy)* | `apply-enrichment`, only if no response file exists | |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## Request schema (`enrichment_request.yaml`) |
| 70 | + |
| 71 | +```yaml |
| 72 | +schema: codechakra/enrichment-request@1 |
| 73 | +generated_at: "2026-08-19T00:00:00+00:00" |
| 74 | +response_file: .tldrgraph/enrichment_response.yaml |
| 75 | +contract: .tldrgraph/AGENT_CONTRACT.md |
| 76 | +progress: |
| 77 | + total_candidates: 1873 # un-enriched, non-utility nodes |
| 78 | + already_enriched: 12 # nodes that already carry an intent |
| 79 | + queued_now: 50 # entries in "nodes" below |
| 80 | + remaining_after: 1823 # still waiting after this batch is applied |
| 81 | +nodes: |
| 82 | + - id: backend_src_applications_applications_controller_applicationscontroller |
| 83 | + label: ApplicationsController |
| 84 | + layer_id: api |
| 85 | + layer: "Layer 2: API Gateway" |
| 86 | + file: backend/src/applications/applications.controller.ts |
| 87 | + source_location: L31 |
| 88 | + degree: 41 # in + out edges in the AST graph |
| 89 | + cross_layer_degree: 17 # of those, how many cross a layer boundary |
| 90 | + rank: 1 # 1 = highest priority in this batch |
| 91 | + existing_intent_source: heuristic # "" when the node has no intent at all |
| 92 | +``` |
| 93 | +
|
| 94 | +`file` is repo-relative. `source_location` is graphify's line hint and may be `null`. |
| 95 | +`layer_id` is the stable machine key (e.g. `cli`, `engine`, `storage`, `api`, `ui`). |
| 96 | + |
| 97 | +`existing_intent_source` is `"heuristic"` when the node already carries an intent written |
| 98 | +by the offline template enricher. That text was generated from the label and layer alone |
| 99 | +— it has not read a line of source — so the node is still a candidate and your answer |
| 100 | +should overwrite it. Applied answers are stamped `"agent"` and are never re-queued. |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +## Response schema (`enrichment_response.yaml` or `enrichment_response.json`) |
| 105 | + |
| 106 | +A **YAML list** (preferred) or **JSON array** of objects: |
| 107 | + |
| 108 | +```yaml |
| 109 | +- id: backend_src_applications_applications_controller_applicationscontroller |
| 110 | + intent: | |
| 111 | + ### Pension Application Lifecycle Gateway |
| 112 | + REST gateway for the pension application lifecycle. Authorizes DEO/AAO/AO/DAG roles, |
| 113 | + dispatches cases to ApplicationsService and records status transitions. |
| 114 | + input_fields: |
| 115 | + - caseId |
| 116 | + - transitionPayload |
| 117 | + - remarks |
| 118 | + - sanctionOrderNo |
| 119 | + output_fields: |
| 120 | + - applicationStatus |
| 121 | + - disposition |
| 122 | + calls: |
| 123 | + - ApplicationsService |
| 124 | + - JwtAuthGuard |
| 125 | + - RolesGuard |
| 126 | + - pension_cases |
| 127 | +``` |
| 128 | + |
| 129 | +Equivalent JSON format (also accepted from `.tldrgraph/enrichment_response.json` or `.tldrgraph/pending_enrichment.json`): |
| 130 | +```json |
| 131 | +[ |
| 132 | + { |
| 133 | + "id": "backend_src_applications_applications_controller_applicationscontroller", |
| 134 | + "intent": "### Pension Application Lifecycle Gateway\nREST gateway for the pension application lifecycle.", |
| 135 | + "input_fields": ["caseId", "transitionPayload", "remarks", "sanctionOrderNo"], |
| 136 | + "output_fields": ["applicationStatus", "disposition"], |
| 137 | + "calls": ["ApplicationsService", "JwtAuthGuard", "RolesGuard", "pension_cases"] |
| 138 | + } |
| 139 | +] |
| 140 | +``` |
| 141 | + |
| 142 | +| Key | Type | Meaning | |
| 143 | +| --- | --- | --- | |
| 144 | +| `id` | string, **required** | The node id, copied **verbatim** from the request. An id that is not in the graph is skipped silently. | |
| 145 | +| `intent` | string (Markdown) | Markdown formatted explanation: what this symbol does, its role, and why it exists. AI decides how much depth is needed. This is the text semantic search matches against. | |
| 146 | +| `input_fields` | array of strings | Input parameters, arguments, request body payload attributes, query filters. | |
| 147 | +| `output_fields` | array of strings | Return types, response models, emitted event names, or mutated state attributes. | |
| 148 | +| `fields` | array of strings (legacy) | Supported for backwards compatibility (maps to input fields). | |
| 149 | +| `calls` | array of strings or objects | Downstream symbols, files (`file:symbol`), or node IDs this symbol calls. Cross-layer bridges are created with 100% confidence. | |
| 150 | +| `layer_id` | string (optional) | Explicitly reassign the architectural layer ID if the AST classification miscategorized it. | |
| 151 | + |
| 152 | +`input_fields`, `output_fields`, and `calls` may be omitted or empty. An object with only `id` and `intent` is |
| 153 | +valid and useful. |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +## Hard rules |
| 158 | + |
| 159 | +1. **Open and read the actual source file before writing an intent.** You have the repo |
| 160 | + checked out; that is the entire reason this path exists. Read `file` (use |
| 161 | + `source_location` to find the symbol), and read enough of its imports and callees to |
| 162 | + describe what it really does. An intent paraphrased from the label is worse than no |
| 163 | + intent, because it poisons search with confident-sounding noise. |
| 164 | + |
| 165 | +2. **Do not invent fields or calls. Omit what you cannot verify in the code.** If you |
| 166 | + read the file and it handles three params, list three. Do not pad the list with what a |
| 167 | + symbol of that name "usually" has. `"fields": []` is a correct, honest answer. |
| 168 | + A wrong `calls` entry creates a real, wrong edge in the graph that later queries will |
| 169 | + follow. |
| 170 | + |
| 171 | +3. **`calls` entries are resolved with 2-tier high precision.** |
| 172 | + - **Tier 1 (Exact Match, 100% confidence):** Exact symbol names (`ApplicationsService`), |
| 173 | + function names, node IDs, file paths (`calc.ts`), or database table names (`pension_cases`). |
| 174 | + - **Tier 2 (Vector Fallback):** Semantic search with a calibrated 0.35 score floor. |
| 175 | + |
| 176 | + | Good | Bad | |
| 177 | + | --- | --- | |
| 178 | + | `ApplicationsService` | `the application service` | |
| 179 | + | `calc.ts` | `some calculation helper` | |
| 180 | + | `pension_cases` | `the database` | |
| 181 | + | `JwtAuthGuard` | `auth stuff` | |
| 182 | + |
| 183 | + Prefer the exact symbol name, file name, or table/model name as it appears in the source. |
| 184 | + |
| 185 | +4. **Copy `id` verbatim.** Do not normalize, shorten or re-case it. |
| 186 | + |
| 187 | +5. **Answer only the nodes in the request.** Extra ids are ignored; missing ids just come |
| 188 | + back in a later batch. |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## Priority order in the queue |
| 193 | + |
| 194 | +The queue is not arbitrary — a node that many things depend on is worth more of your |
| 195 | +attention than a leaf. A node is a **candidate** when it sits outside `General / Utility` |
| 196 | +and either has no intent at all, or has one that came from the offline template heuristic |
| 197 | +(`enrichment_source: "heuristic"`, i.e. nobody read the source). Candidates are sorted by: |
| 198 | +
|
| 199 | +1. **`cross_layer_degree` descending** — neighbours that sit in a *different* layer. |
| 200 | + These are the seams TLDRGraph exists to describe, and they are exactly where the AST |
| 201 | + alone is weakest. |
| 202 | +2. **`degree` descending** — total in + out edges. Hub nodes first. |
| 203 | +3. **node id ascending** — only to make the ordering deterministic. |
| 204 | + |
| 205 | +Both degrees are computed from the live graph. (The `degree` key that graphify emits is |
| 206 | +absent, so anything reading `node["degree"]` from the raw export sees `0`; TLDRGraph |
| 207 | +recomputes it and stamps it back into `.tldrgraph/graph.json`.) |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Paging and progress |
| 212 | + |
| 213 | +`queue-enrichment` remembers what it has handed out in `.tldrgraph/enrichment_cursor.json`: |
| 214 | + |
| 215 | +- `applied` — ids successfully merged by `apply-enrichment`. Never re-queued. |
| 216 | +- `queued` — ids handed out but not yet applied ("in flight"). Skipped by default. |
| 217 | + |
| 218 | +So running `queue-enrichment` twice in a row **advances** to the next batch instead of |
| 219 | +repeating. Two escape hatches: |
| 220 | + |
| 221 | +- `--requeue` — also hand out in-flight ids again (use when a batch was abandoned). |
| 222 | +- `--reset` — clear all progress and start again from the highest-priority node. |
| 223 | +- `--limit 0` — no cap; queue every remaining candidate at once. |
| 224 | + |
| 225 | +--- |
| 226 | + |
| 227 | +## What `apply-enrichment` does with your answer |
| 228 | + |
| 229 | +For each object it can match to a node: |
| 230 | + |
| 231 | +1. sets `intent`, rewrites `summary` to `"<layer>: <label> - <intent>"`, sets `fields`; |
| 232 | +2. writes the node into the SQLite hash-gate cache, keyed by a content signature, so the |
| 233 | + work survives re-scans and is not redone until the file actually changes; |
| 234 | +3. resolves every `calls` entry through the vector index and, above the `0.35` floor, |
| 235 | + adds a `cross_layer_link` edge; |
| 236 | +4. re-indexes and persists `.tldrgraph/graph.json` plus `.tldrgraph/layers.yaml`. |
| 237 | + |
| 238 | +It then records the ids in the cursor so the next `queue-enrichment` moves on. |
| 239 | + |
| 240 | +--- |
| 241 | + |
| 242 | +## Related commands |
| 243 | + |
| 244 | +```bash |
| 245 | +tldrgraph init # everything, resumable (scan/enrich are aliases) |
| 246 | +tldrgraph query "pension approval" # semantic search + end-to-end flow trace |
| 247 | +tldrgraph trace AaoDeskView pension_cases |
| 248 | +tldrgraph layers # node counts per layer |
| 249 | +tldrgraph dead-code --status candidate # nodes worth a human/agent review |
| 250 | +``` |
| 251 | + |
| 252 | +`query`, `trace`, `layers` and `dead-code` are read commands: they never trigger |
| 253 | +enrichment. |
| 254 | + |
| 255 | +### `dead-code` is a review list, not a delete list |
| 256 | + |
| 257 | +`dead-code` reports `dead_code_status` per node: |
| 258 | + |
| 259 | +| Status | Means | |
| 260 | +| --- | --- | |
| 261 | +| `live` | Reached by something. | |
| 262 | +| `entry_point` | A root: route handler, CLI entry, cron job, exported public API. | |
| 263 | +| `candidate` | **Worth reviewing.** Nothing observed reaches it — which is evidence, not proof. | |
| 264 | +| `unreviewed` | **Not enough evidence to conclude anything.** Never treat as removable. | |
| 265 | + |
| 266 | +TLDRGraph has no delete capability and will not gain one. Reflection, DI containers, |
| 267 | +string-built routes, template references and test-only entry points all produce nodes the |
| 268 | +static graph cannot see. Confirm with the source before removing anything. |
0 commit comments