Skip to content

Commit 4c9006b

Browse files
rushikeshmoreclaude
andcommitted
feat: v0.6.0 — inline context injection, 13→5 tools, MCP resources + prompts
Replace indirect 3-line CLAUDE.md pointer with rich inline context injection (architecture, risk map, directives) between <!-- codecortex:start/end --> markers. Reduces MCP tools from 13 to 5 proven, irreplaceable ones. Adds 3 MCP resources and 2 MCP prompts. Includes coupling noise filter, static fallback paths, and new `inject` CLI command. Changes: - NEW: src/core/context-injection.ts — generates ~60-80 line inline Markdown - NEW: src/cli/commands/inject.ts — standalone CLI command - NEW: src/mcp/resources.ts — 3 MCP resources (overview, hotspots, module/{name}) - NEW: src/mcp/prompts.ts — 2 MCP prompts (start_session, before_editing) - NEW: generateHotspotsMarkdown() in temporal.ts — static hotspots.md generation - NEW: coupling noise filter (go.mod↔go.sum, Cargo pairs, golden clusters) - REMOVED: 8 MCP tools (5 read + 3 write) — agents use static files instead - REMOVED: src/mcp/tools/write.ts (0 organic usage) - 279 tests passing, tsc clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 66d89a7 commit 4c9006b

24 files changed

Lines changed: 1140 additions & 754 deletions

CLAUDE.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Persistent codebase knowledge layer for AI agents. Pre-builds architecture, depe
66
- TypeScript, ESM (`"type": "module"`)
77
- tree-sitter (native N-API) + 27 language grammar packages
88
- @modelcontextprotocol/sdk - MCP server (stdio transport)
9-
- commander - CLI (init, serve, update, status, symbols, search, modules, hotspots, hook, upgrade)
9+
- commander - CLI (init, serve, update, inject, status, symbols, search, modules, hotspots, hook, upgrade)
1010
- simple-git - git integration + temporal analysis
1111
- zod - schema validation for LLM analysis results
1212
- yaml - cortex.yaml manifest
@@ -45,16 +45,25 @@ Hybrid extraction:
4545
- `codecortex symbols [query]` - browse and filter the symbol index
4646
- `codecortex search <query>` - search across all knowledge files
4747
- `codecortex modules [name]` - list modules or deep-dive into one
48+
- `codecortex inject` - regenerate inline context in CLAUDE.md and agent config files
4849
- `codecortex hotspots` - files ranked by risk (churn + coupling + bugs)
4950
- `codecortex hook install|uninstall|status` - manage git hooks for auto-update
5051
- `codecortex upgrade` - check for and install latest version
5152

52-
## MCP Tools (13)
53-
Read (10): get_project_overview, get_module_context, get_session_briefing, search_knowledge, get_decision_history, get_dependency_graph, lookup_symbol, get_change_coupling, get_hotspots, get_edit_briefing
54-
Write (3): record_decision, update_patterns, record_observation
53+
## MCP Tools (5)
54+
get_project_overview, get_dependency_graph, lookup_symbol, get_change_coupling, get_edit_briefing
5555

56-
All read tools include `_freshness` metadata (status, lastAnalyzed, filesChangedSince, changedFiles, message).
57-
All read tools return context-safe responses (<10K chars) via truncation utilities in `src/utils/truncate.ts`.
56+
## MCP Resources (3)
57+
- `codecortex://project/overview` — constitution (architecture, risk map)
58+
- `codecortex://project/hotspots` — risk-ranked files
59+
- `codecortex://module/{name}` — module documentation (template)
60+
61+
## MCP Prompts (2)
62+
- `start_session` — constitution + latest session for context
63+
- `before_editing` — risk assessment for files you plan to edit
64+
65+
All tools include `_freshness` metadata (status, lastAnalyzed, filesChangedSince, changedFiles, message).
66+
All tools return context-safe responses (<10K chars) via truncation utilities in `src/utils/truncate.ts`.
5867

5968
## Pre-Publish Checklist
6069
Run ALL of these before `npm publish`. Do not skip any step.
@@ -72,7 +81,7 @@ Run ALL of these before `npm publish`. Do not skip any step.
7281
- **Grammar smoke test** (`parser.test.ts`): Loads every language in `LANGUAGE_LOADERS` via `parseSource()`. Catches missing packages, broken native builds, wrong require paths. This is what would have caught the tree-sitter-liquid issue.
7382
- **Version-check tests**: Update notification, cache lifecycle, PM detection, upgrade commands.
7483
- **Hook tests**: Git hook install/uninstall/status integration tests.
75-
- **MCP tests**: All 13 tools (read + write), simulation tests.
84+
- **MCP tests**: All 5 tools, resources, prompts, simulation tests.
7685

7786
### Known limitations
7887
- tree-sitter native bindings don't compile on Node 24 yet (upstream issue)
@@ -91,7 +100,7 @@ Run ALL of these before `npm publish`. Do not skip any step.
91100
src/
92101
cli/ - commander CLI (init, serve, update, status)
93102
mcp/ - MCP server + tools
94-
core/ - knowledge store (graph, modules, decisions, sessions, patterns, constitution, search, agent-instructions, freshness)
103+
core/ - knowledge store (graph, modules, decisions, sessions, patterns, constitution, search, agent-instructions, context-injection, freshness)
95104
extraction/ - tree-sitter native N-API (parser, symbols, imports, calls)
96105
git/ - git diff, history, temporal analysis
97106
types/ - TypeScript types + Zod schemas

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codecortex-ai",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"description": "Persistent codebase knowledge layer for AI agents — architecture, dependencies, coupling, and risk served via MCP",
55
"type": "module",
66
"bin": {

src/cli/commands/init.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { writeFile, writeJsonStream, ensureDir, cortexPath } from '../../utils/f
1414
import { readFile } from 'node:fs/promises'
1515
import { generateStructuralModuleDocs } from '../../core/module-gen.js'
1616
import { generateAgentInstructions } from '../../core/agent-instructions.js'
17+
import { generateHotspotsMarkdown } from '../../git/temporal.js'
1718
import { createDecision, writeDecision, listDecisions } from '../../core/decisions.js'
1819
import type { SymbolRecord, ImportEdge, CallEdge, SymbolIndex, ProjectInfo } from '../../types/index.js'
1920

@@ -40,6 +41,7 @@ export async function initCommand(opts: { root: string; days: string }): Promise
4041
const allImports: ImportEdge[] = []
4142
const allCalls: CallEdge[] = []
4243
let extractionErrors = 0
44+
const langStats = new Map<string, { files: number; symbols: number }>()
4345

4446
let parsed = 0
4547
const parseable = project.files.filter(f => languageFromPath(f.path)).length
@@ -49,6 +51,9 @@ export async function initCommand(opts: { root: string; days: string }): Promise
4951
const lang = languageFromPath(file.path)
5052
if (!lang) continue
5153

54+
const stats = langStats.get(lang) || { files: 0, symbols: 0 }
55+
stats.files++
56+
5257
try {
5358
const tree = await parseFile(file.absolutePath, lang)
5459
const source = await readFile(file.absolutePath, 'utf-8')
@@ -57,12 +62,14 @@ export async function initCommand(opts: { root: string; days: string }): Promise
5762
const imports = extractImports(tree, file.path, lang)
5863
const calls = extractCalls(tree, file.path, lang)
5964

65+
stats.symbols += symbols.length
6066
allSymbols.push(...symbols)
6167
allImports.push(...imports)
6268
allCalls.push(...calls)
6369
} catch {
6470
extractionErrors++
6571
}
72+
langStats.set(lang, stats)
6673
parsed++
6774
if (showProgress && parsed % 5000 === 0) {
6875
process.stdout.write(`\r Progress: ${parsed}/${parseable} files (${allSymbols.length} symbols)`)
@@ -74,6 +81,13 @@ export async function initCommand(opts: { root: string; days: string }): Promise
7481
if (extractionErrors > 0) {
7582
console.log(` (${extractionErrors} files skipped due to parse errors)`)
7683
}
84+
85+
// Warn about languages with 0 symbols extracted
86+
for (const [lang, stats] of langStats) {
87+
if (stats.files > 0 && stats.symbols === 0) {
88+
console.log(` \u26a0 Warning: ${lang} \u2014 ${stats.files} files parsed, 0 symbols extracted. Grammar may not support this language.`)
89+
}
90+
}
7791
console.log('')
7892

7993
// Step 3: Build dependency graph
@@ -140,9 +154,10 @@ export async function initCommand(opts: { root: string; days: string }): Promise
140154
// Write graph.json
141155
await writeGraph(root, graph)
142156

143-
// Write temporal.json
157+
// Write temporal.json + hotspots.md
144158
if (temporalData) {
145159
await writeFile(cortexPath(root, 'temporal.json'), JSON.stringify(temporalData, null, 2))
160+
await writeFile(cortexPath(root, 'hotspots.md'), generateHotspotsMarkdown(temporalData))
146161
}
147162

148163
// Write overview.md — compact summary only (no raw file listing)
@@ -162,7 +177,7 @@ export async function initCommand(opts: { root: string; days: string }): Promise
162177
await writeManifest(root, manifest)
163178

164179
// Write patterns.md (empty template)
165-
await writeFile(cortexPath(root, 'patterns.md'), '# Coding Patterns\n\nNo patterns recorded yet. Use `update_patterns` to add patterns.\n')
180+
await writeFile(cortexPath(root, 'patterns.md'), '# Coding Patterns\n\nNo patterns recorded yet. Edit this file directly to add patterns.\n')
166181

167182
// Generate structural module docs
168183
const moduleDocsGenerated = await generateStructuralModuleDocs(root, {
@@ -185,8 +200,8 @@ export async function initCommand(opts: { root: string; days: string }): Promise
185200
console.log(' Written: constitution.md')
186201
console.log('')
187202

188-
// Step 7: Agent onboarding
189-
console.log('Step 7/7: Generating agent instructions...')
203+
// Step 7: Agent onboarding + inline context injection
204+
console.log('Step 7/7: Generating inline context...')
190205
const updatedFiles = await generateAgentInstructions(root)
191206

192207
// Seed a starter decision (skip if decisions already exist)

src/cli/commands/inject.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { resolve } from 'node:path'
2+
import { existsSync } from 'node:fs'
3+
import { cortexPath } from '../../utils/files.js'
4+
import { injectAllAgentFiles } from '../../core/context-injection.js'
5+
6+
export async function injectCommand(opts: { root: string }): Promise<void> {
7+
const root = resolve(opts.root)
8+
9+
if (!existsSync(cortexPath(root, 'cortex.yaml'))) {
10+
console.error('Error: No CodeCortex knowledge found. Run `codecortex init` first.')
11+
process.exitCode = 1
12+
return
13+
}
14+
15+
console.log('Regenerating inline context...')
16+
const updated = await injectAllAgentFiles(root)
17+
18+
if (updated.length === 0) {
19+
console.log(' All agent config files are already up to date.')
20+
} else {
21+
for (const file of updated) {
22+
console.log(` Updated: ${file}`)
23+
}
24+
}
25+
console.log('')
26+
console.log('Done. Agent config files now contain inline project knowledge.')
27+
}

src/cli/commands/update.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { generateConstitution } from '../../core/constitution.js'
1616
import { createSession, writeSession, getLatestSession } from '../../core/sessions.js'
1717
import { readFile as fsRead } from 'node:fs/promises'
1818
import { generateStructuralModuleDocs } from '../../core/module-gen.js'
19+
import { generateHotspotsMarkdown } from '../../git/temporal.js'
20+
import { injectAllAgentFiles } from '../../core/context-injection.js'
1921
import type { SymbolRecord, ImportEdge, CallEdge, SymbolIndex } from '../../types/index.js'
2022

2123
export async function updateCommand(opts: { root: string; days: string }): Promise<void> {
@@ -100,6 +102,7 @@ export async function updateCommand(opts: { root: string; days: string }): Promi
100102
await writeGraph(root, graph)
101103
if (temporalData) {
102104
await writeFile(cortexPath(root, 'temporal.json'), JSON.stringify(temporalData, null, 2))
105+
await writeFile(cortexPath(root, 'hotspots.md'), generateHotspotsMarkdown(temporalData))
103106
}
104107

105108
// Generate structural module docs (skip existing)
@@ -125,6 +128,9 @@ export async function updateCommand(opts: { root: string; days: string }): Promi
125128
temporal: temporalData,
126129
})
127130

131+
// Refresh inline context in agent config files
132+
await injectAllAgentFiles(root)
133+
128134
// Create session log
129135
const diff = await getUncommittedDiff(root).catch(() => ({ filesChanged: [], summary: 'no changes' }))
130136
const previousSession = await getLatestSession(root)

src/cli/grouped-help.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Command, Help } from 'commander'
22

33
const COMMAND_GROUPS: Array<{ title: string; commands: string[] }> = [
4-
{ title: 'Core', commands: ['init', 'serve', 'update', 'status'] },
4+
{ title: 'Core', commands: ['init', 'serve', 'update', 'inject', 'status'] },
55
{ title: 'Query', commands: ['symbols', 'search', 'modules', 'hotspots'] },
66
{ title: 'Utility', commands: ['hook', 'upgrade'] },
77
]

src/cli/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Command } from 'commander'
55
import { initCommand } from './commands/init.js'
66
import { serveCommand } from './commands/serve.js'
77
import { updateCommand } from './commands/update.js'
8+
import { injectCommand } from './commands/inject.js'
89
import { statusCommand } from './commands/status.js'
910
import { symbolsCommand } from './commands/symbols.js'
1011
import { searchCommand } from './commands/search.js'
@@ -51,6 +52,12 @@ program
5152
.option('-d, --days <number>', 'Days of git history to re-analyze', '90')
5253
.action(updateCommand)
5354

55+
program
56+
.command('inject')
57+
.description('Regenerate inline context in CLAUDE.md and agent config files')
58+
.option('-r, --root <path>', 'Project root directory', process.cwd())
59+
.action(injectCommand)
60+
5461
program
5562
.command('status')
5663
.description('Show knowledge freshness and symbol counts')

src/core/agent-instructions.ts

Lines changed: 12 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import { existsSync } from 'node:fs'
2-
import { join } from 'node:path'
3-
import { readFile, writeFile as writeFileFs } from 'node:fs/promises'
41
import { writeFile, ensureDir, cortexPath } from '../utils/files.js'
5-
6-
const CODECORTEX_SECTION_MARKER = '## CodeCortex'
2+
import { injectAllAgentFiles } from './context-injection.js'
73

84
export const AGENT_INSTRUCTIONS = `# CodeCortex — Codebase Navigation & Risk Tools
95
@@ -13,95 +9,35 @@ This project uses CodeCortex. It gives you a pre-built map of the codebase — a
139
1410
## Navigation (start here)
1511
- \`get_project_overview\` — architecture, modules, risk map. Call this first.
16-
- \`search_knowledge\` — find where a function/class/type is DEFINED by name. Ranked results: exported definitions first. NOT for content search — use grep for that.
1712
- \`lookup_symbol\` — precise symbol lookup with kind + file path filters. Use when you know exactly what you're looking for (e.g., "all interfaces in gateway/").
18-
- \`get_module_context\` — what files, symbols, and deps are in a specific module.
1913
- \`get_dependency_graph\` — import/export graph filtered by file or module.
20-
- \`get_session_briefing\` — what changed since the last session.
2114
2215
## When to use grep instead
2316
- "How does X work?" → grep (searches file contents)
2417
- "Find all usage of X" → grep (finds every occurrence)
25-
- "Where is X defined?" → \`search_knowledge\` or \`lookup_symbol\` (finds definitions, ranked)
18+
- "Where is X defined?" → \`lookup_symbol\` (finds definitions with filters)
2619
2720
## Before Editing (ALWAYS call these)
2821
- \`get_edit_briefing\` — co-change risks, hidden dependencies, bug history for files you plan to edit. Prevents bugs from files that secretly change together.
2922
- \`get_change_coupling\` — files that historically change together. Missing one causes bugs.
30-
- \`get_hotspots\` — files ranked by risk (churn + coupling + bugs).
23+
24+
## Static Knowledge (read directly, no tool needed)
25+
- \`.codecortex/modules/*.md\` — module docs (purpose, deps, API)
26+
- \`.codecortex/hotspots.md\` — files ranked by risk (churn + coupling + bugs)
27+
- \`.codecortex/patterns.md\` — coding conventions
28+
- \`.codecortex/decisions/*.md\` — architectural decision records
3129
3230
## Response Detail Control
3331
Most tools accept \`detail: "brief"\` (default) or \`"full"\`. Use brief for exploration, full only when you need exhaustive data.
34-
35-
## Building Knowledge (call as you work)
36-
- \`record_decision\` — when you make a non-obvious technical choice, record WHY.
37-
- \`update_patterns\` — when you discover a coding convention, document it.
38-
- \`record_observation\` — record anything you learned (gotchas, undocumented deps, env requirements).
39-
- \`get_decision_history\` — check what decisions were already made and why.
4032
`
4133

42-
const CLAUDEMD_POINTER = `
43-
${CODECORTEX_SECTION_MARKER}
44-
This project uses CodeCortex for codebase knowledge. See \`.codecortex/AGENT.md\` for available MCP tools and when to use them.
45-
`
46-
47-
// All known agent instruction files across AI coding tools
48-
const AGENT_CONFIG_FILES = [
49-
'CLAUDE.md', // Claude Code, Claude Desktop
50-
'.cursorrules', // Cursor
51-
'.windsurfrules', // Windsurf
52-
'AGENTS.md', // Generic / multi-agent convention
53-
'.github/copilot-instructions.md', // GitHub Copilot
54-
]
55-
5634
export async function generateAgentInstructions(projectRoot: string): Promise<string[]> {
57-
// 1. Write .codecortex/AGENT.md (canonical source of truth)
35+
// 1. Write .codecortex/AGENT.md (compact tool reference)
5836
await ensureDir(cortexPath(projectRoot))
5937
await writeFile(cortexPath(projectRoot, 'AGENT.md'), AGENT_INSTRUCTIONS)
6038

61-
// 2. Append pointer to every agent config file that exists
62-
// If NONE exist, create CLAUDE.md as default
63-
const updated: string[] = ['AGENT.md']
64-
let foundAny = false
65-
66-
for (const file of AGENT_CONFIG_FILES) {
67-
const filePath = join(projectRoot, file)
68-
if (existsSync(filePath)) {
69-
foundAny = true
70-
const wasUpdated = await appendPointerToFile(filePath)
71-
if (wasUpdated) updated.push(file)
72-
}
73-
}
74-
75-
// If no agent config files exist at all, create CLAUDE.md as default
76-
if (!foundAny) {
77-
await appendPointerToFile(join(projectRoot, 'CLAUDE.md'))
78-
updated.push('CLAUDE.md')
79-
}
80-
81-
return updated
82-
}
83-
84-
async function appendPointerToFile(filePath: string): Promise<boolean> {
85-
// Ensure parent directory exists (for .github/copilot-instructions.md)
86-
const dir = join(filePath, '..')
87-
if (!existsSync(dir)) {
88-
const { mkdir } = await import('node:fs/promises')
89-
await mkdir(dir, { recursive: true })
90-
}
91-
92-
if (existsSync(filePath)) {
93-
const content = await readFileFs(filePath, 'utf-8')
94-
// Don't duplicate — check if CodeCortex section already exists
95-
if (content.includes(CODECORTEX_SECTION_MARKER)) return false
96-
await writeFileFs(filePath, content + CLAUDEMD_POINTER, 'utf-8')
97-
return true
98-
} else {
99-
// Create new file with just the pointer
100-
await writeFileFs(filePath, CLAUDEMD_POINTER.trimStart(), 'utf-8')
101-
return true
102-
}
103-
}
39+
// 2. Inject rich inline context into all agent config files
40+
const injected = await injectAllAgentFiles(projectRoot)
10441

105-
async function readFileFs(path: string, encoding: BufferEncoding): Promise<string> {
106-
return readFile(path, encoding)
42+
return ['AGENT.md', ...injected]
10743
}

src/core/constitution.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export async function generateConstitution(projectRoot: string, data?: Constitut
148148
}
149149
lines.push(
150150
``,
151-
`Use \`get_module_context\` to deep-dive into any module.`,
151+
`Read \`.codecortex/modules/*.md\` directly for module deep-dives.`,
152152
`Use \`get_change_coupling\` before editing a file to check what else must change.`,
153153
`Use \`lookup_symbol\` to find any function, type, or class.`,
154154
)

0 commit comments

Comments
 (0)