@getmikk/ai-context 1.7.1 → 1.8.0

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.
package/README.md CHANGED
@@ -1,329 +1,100 @@
1
- # @getmikk/ai-context
1
+ # @getmikk/ai-context
2
2
 
3
- > Token-budgeted, graph-traced AI context the difference between an LLM that guesses your architecture and one that actually knows it.
3
+ > Token-budgeted AI context builder and claude.md / AGENTS.md generator.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@getmikk/ai-context)](https://www.npmjs.com/package/@getmikk/ai-context)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](../../LICENSE)
7
7
 
8
- `@getmikk/ai-context` solves the AI context window problem at the architectural level. Instead of dumping your entire codebase into a prompt and hoping the LLM figures it out, it walks the dependency graph from task-relevant seed functions, scores every reachable function by relevance, and packs the highest-signal functions into a token budget giving your AI exactly what it needs and nothing it doesn't.
8
+ Two things: a graph-traced context builder that packs the most relevant functions into a token budget for any given task, and a documentation generator that produces always-accurate `claude.md` and `AGENTS.md` files from the lock file.
9
9
 
10
- It also generates `claude.md` and `AGENTS.md` tiered architecture summaries that let AI agents understand your entire project structure from a single file.
11
-
12
- > Part of [Mikk](../../README.md) — the codebase nervous system for AI-assisted development.
10
+ > Part of [Mikk](../../README.md)live architectural context for your AI agent.
13
11
 
14
12
  ---
15
13
 
16
- ## Installation
17
-
18
- ```bash
19
- npm install @getmikk/ai-context
20
- # or
21
- bun add @getmikk/ai-context
22
- ```
14
+ ## Context Builder
23
15
 
24
- **Peer dependencies:** `@getmikk/core`, `@getmikk/intent-engine`
16
+ Given a task description, BFS-traces the dependency graph from matched seed functions and returns the most relevant context within a configurable token budget.
25
17
 
26
- ---
27
-
28
- ## Quick Start
29
-
30
- ### Context Queries
18
+ ### Usage
31
19
 
32
20
  ```typescript
33
21
  import { ContextBuilder, getProvider } from '@getmikk/ai-context'
34
- import { ContractReader, LockReader } from '@getmikk/core'
35
-
36
- const contract = await new ContractReader().read('./mikk.json')
37
- const lock = await new LockReader().read('./mikk.lock.json')
38
22
 
39
23
  const builder = new ContextBuilder(contract, lock)
40
- const context = builder.build({
41
- task: 'Add rate limiting to the authentication endpoints',
42
- tokenBudget: 8000,
43
- maxHops: 3,
24
+
25
+ const ctx = builder.build({
26
+ task: 'Add rate limiting to all API routes',
27
+ maxHops: 4,
28
+ tokenBudget: 6000,
29
+ focusModules: ['api-gateway'],
44
30
  includeCallGraph: true,
31
+ includeBodies: true,
32
+ projectRoot: '/path/to/project',
45
33
  })
46
34
 
47
- // Format for a specific AI provider
48
- const provider = getProvider('claude')
49
- const formatted = provider.formatContext(context)
50
- // Send `formatted` as part of your AI prompt
51
- ```
52
-
53
- ### Claude.md / AGENTS.md Generation
54
-
55
- ```typescript
56
- import { ClaudeMdGenerator } from '@getmikk/ai-context'
57
-
58
- const generator = new ClaudeMdGenerator(contract, lock, /* tokenBudget */ 4000)
59
- const markdown = generator.generate()
35
+ // Format for your AI client
36
+ const formatter = getProvider('claude') // XML tags
37
+ // const formatter = getProvider('generic') // plain text
38
+ // const formatter = getProvider('compact') // minimal tokens
60
39
 
61
- // Write to project root
62
- await writeFile('./claude.md', markdown)
63
- await writeFile('./AGENTS.md', markdown)
40
+ const output = formatter.formatContext(ctx)
64
41
  ```
65
42
 
66
- ---
67
-
68
- ## How Context Building Works
69
-
70
- The `ContextBuilder` uses a 6-step algorithm:
71
-
72
- ```
73
- 1. Resolve Seeds
74
- └─ Parse task → extract keywords → match against lock file functions/modules
43
+ ### How context is built
75
44
 
76
- 2. BFS Proximity Walk
77
- └─ Walk the call graph outward from seed functions (up to maxHops)
45
+ 1. **Seed** match task keywords against function names, module descriptions, and file paths
46
+ 2. **Walk** BFS from seed nodes, following call graph edges outward up to `maxHops` depth
47
+ 3. **Score** — each function scored by: proximity to seed (depth penalty), keyword match bonus, entry-point bonus
48
+ 4. **Budget** — greedy knapsack: highest-scoring functions added until token budget is consumed
49
+ 5. **Format** — serialized with function bodies, params, return types, call graph, and file locations
78
50
 
79
- 3. Score Functions
80
- └─ Each function gets a relevance score:
81
- • Proximity score (closer to seed = higher)
82
- • Keyword match score (task keywords in function name)
83
- • Entry-point bonus (exported functions score higher)
84
-
85
- 4. Sort by Score
86
- └─ Descending relevance
87
-
88
- 5. Fill Token Budget
89
- └─ Greedily add functions until budget is exhausted
90
- (each function's token cost ≈ line count × 4)
91
-
92
- 6. Group by Module
93
- └─ Organize selected functions into module-level context
94
- ```
51
+ The result is surgical — agents get exactly the functions relevant to their task, not the entire codebase.
95
52
 
96
53
  ---
97
54
 
98
- ## API Reference
55
+ ## claude.md / AGENTS.md Generator
99
56
 
100
- ### ContextBuilder
57
+ Generates `claude.md` and `AGENTS.md` automatically during `mikk init` and `mikk analyze`. Every piece of content is derived from the AST-parsed lock file — never hand-authored, never stale.
101
58
 
102
- The main entry point for building task-specific context.
103
-
104
- ```typescript
105
- import { ContextBuilder } from '@getmikk/ai-context'
106
-
107
- const builder = new ContextBuilder(contract, lock)
108
- const context = builder.build(query)
109
- ```
110
-
111
- **`ContextQuery`:**
112
-
113
- | Field | Type | Default | Description |
114
- |-------|------|---------|-------------|
115
- | `task` | `string` | — | Natural-language description of the task |
116
- | `focusFiles` | `string[]` | `[]` | Specific files to prioritize |
117
- | `focusModules` | `string[]` | `[]` | Specific modules to prioritize |
118
- | `maxFunctions` | `number` | `50` | Maximum functions to include |
119
- | `maxHops` | `number` | `3` | BFS depth limit from seed functions |
120
- | `tokenBudget` | `number` | `8000` | Maximum estimated tokens |
121
- | `includeCallGraph` | `boolean` | `true` | Include `calls[]` and `calledBy[]` per function |
122
-
123
- **`AIContext` (returned):**
124
-
125
- ```typescript
126
- type AIContext = {
127
- project: string // Project name from contract
128
- modules: ContextModule[] // Relevant modules with their functions
129
- constraints: string[] // Active architectural constraints
130
- decisions: string[] // Relevant ADRs
131
- prompt: string // Original task
132
- meta: {
133
- seedCount: number // How many seed functions were found
134
- totalFunctionsConsidered: number
135
- selectedFunctions: number // Functions that fit in the budget
136
- estimatedTokens: number // Approximate token count
137
- keywords: string[] // Extracted keywords from the task
138
- }
139
- }
140
- ```
141
-
142
- **`ContextModule`:**
143
-
144
- ```typescript
145
- type ContextModule = {
146
- id: string
147
- name: string
148
- description?: string
149
- intent?: string // Module's purpose from mikk.json
150
- functions: ContextFunction[]
151
- files: string[]
152
- }
153
- ```
154
-
155
- **`ContextFunction`:**
156
-
157
- ```typescript
158
- type ContextFunction = {
159
- name: string
160
- file: string
161
- startLine: number
162
- endLine: number
163
- calls: string[] // Functions this one calls
164
- calledBy: string[] // Functions that call this one
165
- purpose?: string // Inferred purpose
166
- errorHandling?: string // Error patterns detected
167
- edgeCases?: string // Edge cases noted
168
- }
169
- ```
170
-
171
- ---
172
-
173
- ### ClaudeMdGenerator
174
-
175
- Generates tiered markdown documentation files for AI agents.
59
+ ### Usage
176
60
 
177
61
  ```typescript
178
62
  import { ClaudeMdGenerator } from '@getmikk/ai-context'
179
63
 
180
- const generator = new ClaudeMdGenerator(contract, lock, tokenBudget)
181
- const markdown = generator.generate()
182
- ```
183
-
184
- **Tiered output structure:**
185
-
186
- | Tier | Content | Budget |
187
- |------|---------|--------|
188
- | **Tier 1** | Project summary — name, module count, total functions, architecture overview | ~500 tokens |
189
- | **Tier 2** | Module details — each module's intent, public API, file list, key functions | ~300 tokens/module |
190
- | **Tier 3** | Constraints & decisions — all architectural rules and ADRs | Remaining budget |
191
-
192
- **All data is sourced from the AST-derived lock file** — no hallucinated descriptions. Module intents come from `mikk.json`, function lists and call graphs come from `mikk.lock.json`.
64
+ const generator = new ClaudeMdGenerator(
65
+ contract,
66
+ lock,
67
+ 12000, // token budget
68
+ { // from package.json
69
+ description: 'Multi-channel AI gateway',
70
+ scripts: { build: 'bun run build', test: 'vitest' },
71
+ dependencies: { ... },
72
+ },
73
+ projectRoot
74
+ )
193
75
 
194
- **Example output:**
195
-
196
- ```markdown
197
- # Project: my-app
198
-
199
- ## Architecture Overview
200
- - **Modules:** 5
201
- - **Total Functions:** 87
202
- - **Total Files:** 23
203
-
204
- ## Modules
205
-
206
- ### auth
207
- **Intent:** Handle user authentication and session management
208
- **Public API:** `login`, `logout`, `validateToken`, `refreshSession`
209
- **Files:** auth/login.ts, auth/session.ts, auth/middleware.ts
210
- **Key Functions:**
211
- - `validateToken` (auth/middleware.ts:15-42) → calls: `decodeJWT`, `checkExpiry`
212
- - `login` (auth/login.ts:8-35) → calls: `validateCredentials`, `createSession`
213
-
214
- ### payments
215
- ...
216
-
217
- ## Constraints
218
- - auth: no-import from payments
219
- - payments: must-use stripe-sdk
220
-
221
- ## Decisions
222
- - ADR-001: Use JWT for stateless auth (2024-01-15)
223
- ```
224
-
225
- ---
226
-
227
- ### Providers
228
-
229
- Providers format the `AIContext` object into a string optimized for a specific AI model.
230
-
231
- ```typescript
232
- import { getProvider, ClaudeProvider, GenericProvider } from '@getmikk/ai-context'
233
-
234
- // Factory
235
- const provider = getProvider('claude') // or 'generic'
236
-
237
- // Format context for the provider
238
- const formatted = provider.formatContext(context)
239
- ```
240
-
241
- #### ClaudeProvider
242
-
243
- Formats with structured XML tags optimized for Anthropic Claude:
244
-
245
- ```xml
246
- <architecture>
247
- <module name="auth" intent="Handle authentication">
248
- <function name="validateToken" file="auth/middleware.ts" lines="15-42">
249
- <calls>decodeJWT, checkExpiry</calls>
250
- <calledBy>authMiddleware</calledBy>
251
- </function>
252
- </module>
253
- </architecture>
254
- <constraints>
255
- auth: no-import from payments
256
- </constraints>
257
- ```
258
-
259
- #### GenericProvider
260
-
261
- Clean plain-text format for any AI model:
262
-
263
- ```
264
- ## Module: auth
265
- Intent: Handle authentication
266
-
267
- ### validateToken (auth/middleware.ts:15-42)
268
- Calls: decodeJWT, checkExpiry
269
- Called by: authMiddleware
76
+ const content = generator.generate()
77
+ // Write to claude.md and AGENTS.md
270
78
  ```
271
79
 
272
- ---
273
-
274
- ## Usage Examples
275
-
276
- ### "What breaks if I change this file?"
80
+ ### Tiered output
277
81
 
278
- ```typescript
279
- const context = builder.build({
280
- task: `Impact analysis for changes to src/auth/login.ts`,
281
- focusFiles: ['src/auth/login.ts'],
282
- maxHops: 5,
283
- tokenBudget: 4000,
284
- })
285
- // Returns all functions transitively affected by login.ts
286
- ```
287
-
288
- ### "Get context for adding a feature"
289
-
290
- ```typescript
291
- const context = builder.build({
292
- task: 'Add two-factor authentication to the login flow',
293
- focusModules: ['auth'],
294
- tokenBudget: 12000,
295
- includeCallGraph: true,
296
- })
297
- // Returns auth module functions + related cross-module calls
298
- ```
82
+ Content is generated in priority order until the token budget (default 12,000) is consumed:
299
83
 
300
- ### "Focused context for a specific module"
84
+ | Tier | Content | Always included? |
85
+ |------|---------|-----------------|
86
+ | 1 | Project summary — name, description, module list with function counts, stats, critical constraints | Yes |
87
+ | — | Tech stack — detected frameworks, runtime, build tool | If detectable |
88
+ | — | Build/test/run commands — from package.json scripts | If present |
89
+ | 2 | Per-module detail — exported API, key functions, internal call summary (~300 tokens/module) | Until budget |
90
+ | — | Context files — discovered schemas, configs, data models | If budget allows |
91
+ | — | Import graph — cross-file import relationships per module | If budget allows |
92
+ | — | HTTP routes — detected routes with method, path, handler | If budget allows |
93
+ | 3 | Constraints — all declared constraint rules | If budget allows |
94
+ | — | ADR decisions — architectural decisions with reasons | If budget allows |
301
95
 
302
- ```typescript
303
- const context = builder.build({
304
- task: 'Refactor the payment processing pipeline',
305
- focusModules: ['payments'],
306
- maxFunctions: 30,
307
- tokenBudget: 6000,
308
- })
309
- ```
310
-
311
- ---
312
-
313
- ## Types
314
-
315
- ```typescript
316
- import type {
317
- AIContext,
318
- ContextModule,
319
- ContextFunction,
320
- ContextQuery,
321
- ContextProvider,
322
- } from '@getmikk/ai-context'
323
- ```
324
-
325
- ---
96
+ Modules with zero functions are skipped entirely.
326
97
 
327
- ## License
98
+ ### Token estimation
328
99
 
329
- [Apache-2.0](../../LICENSE)
100
+ Uses a ~4 chars/token approximation. The generator stops adding sections the moment adding the next section would exceed the budget, so the output always fits within the specified window.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikk/ai-context",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,8 +21,8 @@
21
21
  "dev": "tsc --watch"
22
22
  },
23
23
  "dependencies": {
24
- "@getmikk/core": "^1.7.1",
25
- "@getmikk/intent-engine": "^1.7.1"
24
+ "@getmikk/core": "^1.8.0",
25
+ "@getmikk/intent-engine": "^1.8.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^5.7.0",
@@ -46,26 +46,26 @@ export class ClaudeMdGenerator {
46
46
  const sections: string[] = []
47
47
  let usedTokens = 0
48
48
 
49
- // ── Tier 1: Summary (always included) ──────────────────────
49
+ // --- Tier 1: Summary (always included) ----------------------
50
50
  const summary = this.generateSummary()
51
51
  sections.push(summary)
52
52
  usedTokens += estimateTokens(summary)
53
53
 
54
- // ── Tech stack & conventions (always included if detectable) ──
54
+ // --- Tech stack & conventions (always included if detectable) ---
55
55
  const techSection = this.generateTechStackSection()
56
56
  if (techSection) {
57
57
  sections.push(techSection)
58
58
  usedTokens += estimateTokens(techSection)
59
59
  }
60
60
 
61
- // ── Build / test / run commands ─────────────────────────────
61
+ // --- Build / test / run commands -----------------------------
62
62
  const commandsSection = this.generateCommandsSection()
63
63
  if (commandsSection) {
64
64
  sections.push(commandsSection)
65
65
  usedTokens += estimateTokens(commandsSection)
66
66
  }
67
67
 
68
- // ── Tier 2: Module details (if budget allows) ──────────────
68
+ // --- Tier 2: Module details (if budget allows) --------------
69
69
  // Skip modules with zero functions — they waste AI tokens
70
70
  const modules = this.getModulesSortedByDependencyOrder()
71
71
  .filter(m => {
@@ -78,14 +78,16 @@ export class ClaudeMdGenerator {
78
78
  const moduleSection = this.generateModuleSection(module.id)
79
79
  const tokens = estimateTokens(moduleSection)
80
80
  if (usedTokens + tokens > this.tokenBudget) {
81
- sections.push('\n> Full details available in `mikk.lock.json`\n')
81
+ sections.push('\n <!-- Full details truncated due to context budget -->\n')
82
82
  break
83
83
  }
84
84
  sections.push(moduleSection)
85
85
  usedTokens += tokens
86
86
  }
87
87
 
88
- // ── Context files: schemas, data models, config ─────────
88
+ sections.push('</modules>\n')
89
+
90
+ // --- Context files: schemas, data models, config ---------
89
91
  const contextSection = this.generateContextFilesSection()
90
92
  if (contextSection) {
91
93
  const ctxTokens = estimateTokens(contextSection)
@@ -95,7 +97,7 @@ export class ClaudeMdGenerator {
95
97
  }
96
98
  }
97
99
 
98
- // ── File import graph per module ────────────────────────────
100
+ // --- File import graph per module ----------------------------
99
101
  const importSection = this.generateImportGraphSection()
100
102
  if (importSection) {
101
103
  const impTokens = estimateTokens(importSection)
@@ -105,7 +107,7 @@ export class ClaudeMdGenerator {
105
107
  }
106
108
  }
107
109
 
108
- // ── HTTP Routes (Express + Next.js) ─────────────────────────
110
+ // --- HTTP Routes (Express + Next.js) -------------------------
109
111
  const routesSection = this.generateRoutesSection()
110
112
  if (routesSection) {
111
113
  const routeTokens = estimateTokens(routesSection)
@@ -115,7 +117,7 @@ export class ClaudeMdGenerator {
115
117
  }
116
118
  }
117
119
 
118
- // ── Tier 3: Constraints & decisions ────────────────────────
120
+ // --- Tier 3: Constraints & decisions ------------------------
119
121
  const constraintsSection = this.generateConstraintsSection()
120
122
  const constraintTokens = estimateTokens(constraintsSection)
121
123
  if (usedTokens + constraintTokens <= this.tokenBudget) {
@@ -141,15 +143,13 @@ export class ClaudeMdGenerator {
141
143
  const functionCount = Object.keys(this.lock.functions).length
142
144
  const fileCount = Object.keys(this.lock.files).length
143
145
 
144
- lines.push(`# ${this.contract.project.name} — Architecture Overview`)
145
- lines.push('')
146
+ lines.push('<repository_context>')
147
+ lines.push(` <name>${this.contract.project.name}</name>`)
146
148
 
147
149
  // Project description: prefer contract, fall back to package.json
148
150
  const description = this.contract.project.description || this.meta.description
149
151
  if (description) {
150
- lines.push('## What this project does')
151
- lines.push(description)
152
- lines.push('')
152
+ lines.push(` <description>${description}</description>`)
153
153
  }
154
154
 
155
155
  // Only list modules that have functions (skip empty ones)
@@ -159,32 +159,26 @@ export class ClaudeMdGenerator {
159
159
  return fnCount > 0
160
160
  })
161
161
 
162
- lines.push('## Modules')
163
- for (const module of nonEmptyModules) {
164
- const fnCount = Object.values(this.lock.functions)
165
- .filter(f => f.moduleId === module.id).length
166
- const desc = module.intent || module.description || ''
167
- // Strip leading "N functions — " from auto-generated descriptions to avoid double-counting
168
- const cleanDesc = desc.replace(/^\d+ functions\s*—\s*/, '')
169
- const descStr = cleanDesc ? ` — ${cleanDesc}` : ''
170
- lines.push(`- **${module.name}** (\`${module.id}\`): ${fnCount} functions${descStr}`)
171
- }
172
- lines.push('')
173
-
174
- lines.push(`## Stats`)
175
- lines.push(`- ${fileCount} files, ${functionCount} functions, ${nonEmptyModules.length} modules`)
176
- lines.push(`- Language: ${this.contract.project.language}`)
177
- lines.push('')
162
+ lines.push(` <stats>`)
163
+ lines.push(` <files>${fileCount}</files>`)
164
+ lines.push(` <functions>${functionCount}</functions>`)
165
+ lines.push(` <modules>${nonEmptyModules.length}</modules>`)
166
+ lines.push(` <language>${this.contract.project.language}</language>`)
167
+ lines.push(` </stats>`)
178
168
 
179
169
  // Critical constraints summary
180
170
  if (this.contract.declared.constraints.length > 0) {
181
- lines.push('## Critical Constraints')
171
+ lines.push(' <critical_constraints>')
182
172
  for (const c of this.contract.declared.constraints) {
183
- lines.push(`- ${c}`)
173
+ lines.push(` <constraint>${c}</constraint>`)
184
174
  }
185
- lines.push('')
175
+ lines.push(' </critical_constraints>')
186
176
  }
187
177
 
178
+ lines.push('</repository_context>')
179
+ lines.push('')
180
+ lines.push('<modules>')
181
+
188
182
  return lines.join('\n')
189
183
  }
190
184
 
@@ -198,23 +192,22 @@ export class ClaudeMdGenerator {
198
192
  const moduleFunctions = Object.values(this.lock.functions)
199
193
  .filter(f => f.moduleId === moduleId)
200
194
 
201
- lines.push(`## ${module.name} module`)
195
+ lines.push(` <module id="${moduleId}">`)
196
+ lines.push(` <name>${module.name}</name>`)
202
197
 
203
198
  // Location — collapse to common prefix when many paths share a root
204
199
  if (module.paths.length > 0) {
205
200
  const collapsed = this.collapsePaths(module.paths)
206
- lines.push(`**Location:** ${collapsed}`)
201
+ lines.push(` <location>${collapsed}</location>`)
207
202
  }
208
203
 
209
204
  // Intent
210
205
  if (module.intent) {
211
- lines.push(`**Purpose:** ${module.intent}`)
206
+ lines.push(` <purpose>${module.intent}</purpose>`)
212
207
  } else if (module.description) {
213
- lines.push(`**Purpose:** ${module.description}`)
208
+ lines.push(` <purpose>${module.description}</purpose>`)
214
209
  }
215
210
 
216
- lines.push('')
217
-
218
211
  // Entry points: functions with no calledBy (likely public API surface)
219
212
  const entryPoints = moduleFunctions
220
213
  .filter(fn => fn.calledBy.length === 0)
@@ -222,13 +215,13 @@ export class ClaudeMdGenerator {
222
215
  .slice(0, 5)
223
216
 
224
217
  if (entryPoints.length > 0) {
225
- lines.push('**Entry points:**')
218
+ lines.push(' <entry_points>')
226
219
  for (const fn of entryPoints) {
227
220
  const sig = this.formatSignature(fn)
228
- const purpose = fn.purpose ? ` — ${this.oneLine(fn.purpose)}` : ''
229
- lines.push(` - \`${sig}\`${purpose}`)
221
+ const purpose = fn.purpose ? `${this.oneLine(fn.purpose)}` : ''
222
+ lines.push(` <function signature="${sig.replace(/"/g, '&quot;')}" purpose="${purpose.replace(/"/g, '&quot;')}" />`)
230
223
  }
231
- lines.push('')
224
+ lines.push(' </entry_points>')
232
225
  }
233
226
 
234
227
  // Key functions: top 5 by calledBy count (most depended upon)
@@ -238,13 +231,13 @@ export class ClaudeMdGenerator {
238
231
  .slice(0, 5)
239
232
 
240
233
  if (keyFunctions.length > 0) {
241
- lines.push('**Key internal functions:**')
234
+ lines.push(' <key_internal_functions>')
242
235
  for (const fn of keyFunctions) {
243
236
  const callerCount = fn.calledBy.length
244
- const purpose = fn.purpose ? ` — ${this.oneLine(fn.purpose)}` : ''
245
- lines.push(` - \`${fn.name}\` (called by ${callerCount})${purpose}`)
237
+ const purpose = fn.purpose ? `${this.oneLine(fn.purpose)}` : ''
238
+ lines.push(` <function name="${fn.name.replace(/"/g, '&quot;')}" callers="${callerCount}" purpose="${purpose.replace(/"/g, '&quot;')}" />`)
246
239
  }
247
- lines.push('')
240
+ lines.push(' </key_internal_functions>')
248
241
  }
249
242
 
250
243
  // Dependencies: other modules this module imports from
@@ -263,8 +256,7 @@ export class ClaudeMdGenerator {
263
256
  const mod = this.contract.declared.modules.find(m => m.id === id)
264
257
  return mod?.name || id
265
258
  })
266
- lines.push(`**Depends on:** ${depNames.join(', ')}`)
267
- lines.push('')
259
+ lines.push(` <depends_on>${depNames.join(', ')}</depends_on>`)
268
260
  }
269
261
 
270
262
  // Module-specific constraints
@@ -273,13 +265,14 @@ export class ClaudeMdGenerator {
273
265
  c.toLowerCase().includes(module.name.toLowerCase())
274
266
  )
275
267
  if (moduleConstraints.length > 0) {
276
- lines.push('**Constraints:**')
268
+ lines.push(' <module_constraints>')
277
269
  for (const c of moduleConstraints) {
278
- lines.push(` - ${c}`)
270
+ lines.push(` <constraint>${c}</constraint>`)
279
271
  }
280
- lines.push('')
272
+ lines.push(' </module_constraints>')
281
273
  }
282
274
 
275
+ lines.push(` </module>`)
283
276
  return lines.join('\n')
284
277
  }
285
278
 
@@ -520,9 +513,11 @@ export class ClaudeMdGenerator {
520
513
  if (detected.length === 0) return null
521
514
 
522
515
  const lines: string[] = []
523
- lines.push('## Tech Stack')
524
- lines.push(detected.join(' · '))
525
- lines.push('')
516
+ lines.push('<tech_stack>')
517
+ for (const d of detected) {
518
+ lines.push(` <technology>${d}</technology>`)
519
+ }
520
+ lines.push('</tech_stack>')
526
521
  return lines.join('\n')
527
522
  }
528
523
 
@@ -554,12 +549,14 @@ export class ClaudeMdGenerator {
554
549
  if (useful.length === 0) return null
555
550
 
556
551
  const lines: string[] = []
557
- lines.push('## Commands')
552
+ lines.push('<commands>')
558
553
  for (const [key, cmd] of useful) {
559
- lines.push(`- \`${pm} ${key}\` \u2014 \`${cmd}\``)
554
+ lines.push(` <command>`)
555
+ lines.push(` <run>${pm} ${key}</run>`)
556
+ lines.push(` <executes>${cmd.replace(/"/g, '&quot;')}</executes>`)
557
+ lines.push(` </command>`)
560
558
  }
561
-
562
- lines.push('')
559
+ lines.push('</commands>')
563
560
  return lines.join('\n')
564
561
  }
565
562
 
@@ -64,8 +64,8 @@ describe('ClaudeMdGenerator', () => {
64
64
  test('generates valid markdown', () => {
65
65
  const gen = new ClaudeMdGenerator(mockContract, mockLock)
66
66
  const md = gen.generate()
67
- expect(md).toContain('# TestProject')
68
- expect(md).toContain('Architecture Overview')
67
+ expect(md).toContain('<name>TestProject</name>')
68
+ expect(md).toContain('<repository_context>')
69
69
  })
70
70
 
71
71
  test('includes project description', () => {
@@ -77,8 +77,8 @@ describe('ClaudeMdGenerator', () => {
77
77
  test('includes module sections', () => {
78
78
  const gen = new ClaudeMdGenerator(mockContract, mockLock)
79
79
  const md = gen.generate()
80
- expect(md).toContain('Authentication module')
81
- expect(md).toContain('API module')
80
+ expect(md).toContain('<module id="auth">')
81
+ expect(md).toContain('<module id="api">')
82
82
  })
83
83
 
84
84
  test('includes function names', () => {
@@ -105,7 +105,7 @@ describe('ClaudeMdGenerator', () => {
105
105
  const gen = new ClaudeMdGenerator(mockContract, mockLock)
106
106
  const md = gen.generate()
107
107
  // API depends on Auth (handleLogin calls verifyToken)
108
- expect(md).toContain('Depends on')
108
+ expect(md).toContain('<depends_on>Authentication</depends_on>')
109
109
  })
110
110
 
111
111
  test('respects token budget', () => {
@@ -119,8 +119,8 @@ describe('ClaudeMdGenerator', () => {
119
119
  test('includes stats', () => {
120
120
  const gen = new ClaudeMdGenerator(mockContract, mockLock)
121
121
  const md = gen.generate()
122
- expect(md).toContain('3 functions')
123
- expect(md).toContain('2 modules')
122
+ expect(md).toContain('<functions>3</functions>')
123
+ expect(md).toContain('<modules>2</modules>')
124
124
  })
125
125
 
126
126
  test('shows purpose when available', () => {
@@ -132,6 +132,86 @@ describe('ClaudeMdGenerator', () => {
132
132
  test('shows calledBy count for key functions', () => {
133
133
  const gen = new ClaudeMdGenerator(mockContract, mockLock)
134
134
  const md = gen.generate()
135
- expect(md).toContain('called by 1')
135
+ expect(md).toContain('callers="1"')
136
+ })
137
+
138
+ describe('Edge Cases and Fault Tolerance', () => {
139
+ test('handles completely empty lock and contract without throwing', () => {
140
+ const emptyContract: MikkContract = {
141
+ version: '1',
142
+ project: { name: 'Empty', language: 'TS', description: '', entryPoints: [] },
143
+ declared: { modules: [], constraints: [], decisions: [] },
144
+ overwrite: { mode: 'never', requireConfirmation: false }
145
+ }
146
+ const emptyLock: MikkLock = {
147
+ version: '1', generatedAt: new Date().toISOString(), generatorVersion: '1', projectRoot: '', syncState: { status: 'clean', lastSyncAt: '', lockHash: '', contractHash: '' },
148
+ files: {}, functions: {}, classes: {}, modules: {}, graph: { nodes: 0, edges: 0, rootHash: '' }
149
+ }
150
+ const gen = new ClaudeMdGenerator(emptyContract, emptyLock)
151
+ const md = gen.generate()
152
+ expect(md).toContain('<name>Empty</name>')
153
+ expect(md).toContain('<modules>0</modules>')
154
+ expect(md).toContain('<functions>0</functions>')
155
+ })
156
+
157
+ test('handles missing optional fields gracefully', () => {
158
+ const partialContract: MikkContract = {
159
+ version: '1',
160
+ project: { name: 'Partial', language: 'TS', description: '', entryPoints: [] },
161
+ declared: { modules: [{ id: 'core', name: 'Core', description: '', paths: [] }], constraints: [], decisions: [] },
162
+ overwrite: { mode: 'never', requireConfirmation: false }
163
+ }
164
+ const partialLock: MikkLock = {
165
+ version: '1', generatedAt: new Date().toISOString(), generatorVersion: '1', projectRoot: '', syncState: { status: 'clean', lastSyncAt: '', lockHash: '', contractHash: '' },
166
+ files: {},
167
+ functions: {
168
+ 'f1': { id: 'f1', name: 'func', file: 'a.ts', startLine: 1, endLine: 2, hash: 'h', calls: [], calledBy: [], moduleId: 'core' }
169
+ },
170
+ classes: {},
171
+ modules: {
172
+ 'core': { id: 'core', files: [], hash: 'h', fragmentPath: 'p' }
173
+ },
174
+ graph: { nodes: 0, edges: 0, rootHash: '' }
175
+ }
176
+ const gen = new ClaudeMdGenerator(partialContract, partialLock)
177
+ const md = gen.generate()
178
+ expect(md).toContain('<name>Core</name>')
179
+ expect(md).toContain('func')
180
+ })
181
+
182
+ test('handles circular dependencies between functions gracefully', () => {
183
+ const circLock: MikkLock = {
184
+ ...mockLock,
185
+ functions: {
186
+ 'fn:a': { id: 'fn:a', name: 'A', file: 'a.ts', startLine: 1, endLine: 2, hash: 'h', calls: ['fn:b'], calledBy: ['fn:b'], moduleId: 'auth' },
187
+ 'fn:b': { id: 'fn:b', name: 'B', file: 'b.ts', startLine: 1, endLine: 2, hash: 'h', calls: ['fn:a'], calledBy: ['fn:a'], moduleId: 'auth' },
188
+ }
189
+ }
190
+ const gen = new ClaudeMdGenerator(mockContract, circLock)
191
+ const md = gen.generate()
192
+ expect(md).toContain('A')
193
+ expect(md).toContain('B')
194
+ expect(md.length).toBeLessThan(10000)
195
+ })
196
+
197
+ test('truncates extremely strict token budgets without losing core layout', () => {
198
+ const gen = new ClaudeMdGenerator(mockContract, mockLock, 50)
199
+ const md = gen.generate()
200
+ expect(md).toContain('<repository_context>')
201
+ expect(md).not.toContain('Use JWT')
202
+ })
203
+
204
+ test('handles foreign or orphaned modules robustly', () => {
205
+ const orphanedLock: MikkLock = {
206
+ ...mockLock,
207
+ functions: {
208
+ 'fn:orphan': { id: 'fn:orphan', name: 'OrphanFn', file: 'o.ts', startLine: 1, endLine: 2, hash: 'h', calls: [], calledBy: [], moduleId: 'unknown-module' }
209
+ }
210
+ }
211
+ const gen = new ClaudeMdGenerator(mockContract, orphanedLock)
212
+ const md = gen.generate()
213
+ // Unknown module functions are typically skipped entirely. The generator should not crash.
214
+ expect(md).not.toContain('OrphanFn')
215
+ })
136
216
  })
137
217
  })