Skip to content

Commit 328d03b

Browse files
committed
feat(map): promote codebase map as primary first-call surface (Phase 7)
- Add CodebaseMapSummary type and buildCodebaseMap() builder in src/core/codebase-map.ts - Add renderMapMarkdown() and renderMapPretty() renderers (deterministic, snapshot-locked) - Add map CLI subcommand (src/cli-map.ts) with --json, --pretty, --help modes - Rewrite codebase://context MCP resource to use shared map builder; preserve generateCodebaseIntelligence() signature for eval harness - Remove dead _generateCodebaseContext() function and its exclusive imports from src/index.ts - Update docs/capabilities.md, README.md (First Use section), and cli-init.ts next-steps - Add 20 unit/integration tests and synthetic fixture; 44/44 tests pass
1 parent 3210d26 commit 328d03b

13 files changed

Lines changed: 990 additions & 386 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ Copy-pasteable templates: [`templates/mcp/stdio/.mcp.json`](./templates/mcp/stdi
6767

6868
Full per-client setup, HTTP server instructions, and local build testing: [`docs/client-setup.md`](./docs/client-setup.md).
6969

70+
## First Use
71+
72+
Get a conventions map of your codebase before exploring or searching:
73+
74+
```bash
75+
# See your codebase conventions — architecture layers, patterns, golden files
76+
npx -y codebase-context map
77+
78+
# Then search for what you need
79+
npx -y codebase-context search --query "auth middleware"
80+
```
81+
82+
Your AI agent uses the same map via the `codebase://context` MCP resource on first call.
83+
7084
## Common First Commands
7185

7286
Three commands to get what usually takes a new developer weeks to piece together:

docs/capabilities.md

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ Technical reference for what `codebase-context` ships today. For the user-facing
66

77
The server supports two transport modes:
88

9-
| Mode | Command | MCP endpoint |
10-
| ---- | ------- | ------------ |
11-
| **stdio** (default) | `npx -y codebase-context` | Spawned process stdin/stdout |
12-
| **HTTP** | `npx -y codebase-context --http [--port N]` | `http://127.0.0.1:3100/mcp` |
9+
| Mode | Command | MCP endpoint |
10+
| ------------------- | ------------------------------------------- | ---------------------------- |
11+
| **stdio** (default) | `npx -y codebase-context` | Spawned process stdin/stdout |
12+
| **HTTP** | `npx -y codebase-context --http [--port N]` | `http://127.0.0.1:3100/mcp` |
1313

1414
HTTP defaults to `127.0.0.1:3100`. Override with `--port`, `CODEBASE_CONTEXT_PORT`, or `server.port` in `~/.codebase-context/config.json`.
1515

@@ -21,7 +21,6 @@ Per-project config overrides supported today:
2121
- `projects[].analyzerHints.analyzer`: prefers a registered analyzer by name for that project and falls back safely when the name is missing or invalid
2222
- `projects[].analyzerHints.extensions`: adds project-local source extensions for indexing and auto-refresh watching without changing defaults for other projects
2323

24-
2524
Copy-pasteable client config templates are shipped in the package:
2625

2726
- `templates/mcp/stdio/.mcp.json` — stdio setup for `.mcp.json`-style clients
@@ -35,19 +34,20 @@ Repo-scoped capabilities are available locally via the CLI (human-readable by de
3534
Multi-project selection is MCP-only because the CLI already targets one root per invocation.
3635
For a command gallery with examples, see `docs/cli.md`.
3736

38-
| Command | Flags | Maps to |
39-
|---|---|---|
40-
| `search --query <q>` | `--intent explore\|edit\|refactor\|migrate`, `--limit <n>`, `--lang <l>`, `--framework <f>`, `--layer <l>` | `search_codebase` |
41-
| `metadata` || `get_codebase_metadata` |
42-
| `status` || `get_indexing_status` |
43-
| `reindex` | `--incremental`, `--reason <r>` | equivalent to `refresh_index` |
44-
| `style-guide` | `--query <q>`, `--category <c>` | `get_style_guide` |
45-
| `patterns` | `--category all\|di\|state\|testing\|libraries` | `get_team_patterns` |
46-
| `refs --symbol <name>` | `--limit <n>` | `get_symbol_references` |
47-
| `cycles` | `--scope <path>` | `detect_circular_dependencies` |
48-
| `memory list` | `--category`, `--type`, `--query`, `--json` ||
49-
| `memory add` | `--type`, `--category`, `--memory`, `--reason` | `remember` |
50-
| `memory remove <id>` |||
37+
| Command | Flags | Maps to |
38+
| ---------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------- |
39+
| `map` | `--json`, `--pretty` | `codebase://context` (conventions map) |
40+
| `search --query <q>` | `--intent explore\|edit\|refactor\|migrate`, `--limit <n>`, `--lang <l>`, `--framework <f>`, `--layer <l>` | `search_codebase` |
41+
| `metadata` || `get_codebase_metadata` |
42+
| `status` || `get_indexing_status` |
43+
| `reindex` | `--incremental`, `--reason <r>` | equivalent to `refresh_index` |
44+
| `style-guide` | `--query <q>`, `--category <c>` | `get_style_guide` |
45+
| `patterns` | `--category all\|di\|state\|testing\|libraries` | `get_team_patterns` |
46+
| `refs --symbol <name>` | `--limit <n>` | `get_symbol_references` |
47+
| `cycles` | `--scope <path>` | `detect_circular_dependencies` |
48+
| `memory list` | `--category`, `--type`, `--query`, `--json` ||
49+
| `memory add` | `--type`, `--category`, `--memory`, `--reason` | `remember` |
50+
| `memory remove <id>` |||
5151

5252
All commands accept `--json` for raw JSON output. Errors go to stderr with exit code 1.
5353

@@ -73,13 +73,13 @@ Shared selector inputs:
7373

7474
### Core Tools
7575

76-
| Tool | Input | Output |
77-
| ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78-
| `search_codebase` | `query`, optional `intent`, `limit`, `filters`, `includeSnippets`, shared `project`/`project_directory` | Ranked results (`file`, `summary`, `score`, `type`, `trend`, `patternWarning`, `relationships`, `hints`) + `searchQuality` + decision card (`ready`, `nextAction`, `patterns`, `bestExample`, `impact`, `whatWouldHelp`) when `intent="edit"`. Hints capped at 3 per category. |
79-
| `get_team_patterns` | optional `category`, shared `project`/`project_directory` | Pattern frequencies, trends, golden files, conflicts |
80-
| `get_symbol_references` | `symbol`, optional `limit`, shared `project`/`project_directory` | Concrete symbol usage evidence: `usageCount` + top usage snippets + `confidence` + `isComplete`. `confidence: "syntactic"` means static/source-based only (no runtime or dynamic dispatch). When Tree-sitter + file content are available, comments and string literals are excluded from the scan — the count reflects real identifier nodes only. Replaces the removed `get_component_usage`. |
81-
| `remember` | `type`, `category`, `memory`, `reason`, shared `project`/`project_directory` | Persists to `.codebase-context/memory.json` |
82-
| `get_memory` | optional `category`, `type`, `query`, `limit`, shared `project`/`project_directory` | Memories with confidence decay scoring |
76+
| Tool | Input | Output |
77+
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78+
| `search_codebase` | `query`, optional `intent`, `limit`, `filters`, `includeSnippets`, shared `project`/`project_directory` | Ranked results (`file`, `summary`, `score`, `type`, `trend`, `patternWarning`, `relationships`, `hints`) + `searchQuality` + decision card (`ready`, `nextAction`, `patterns`, `bestExample`, `impact`, `whatWouldHelp`) when `intent="edit"`. Hints capped at 3 per category. |
79+
| `get_team_patterns` | optional `category`, shared `project`/`project_directory` | Pattern frequencies, trends, golden files, conflicts |
80+
| `get_symbol_references` | `symbol`, optional `limit`, shared `project`/`project_directory` | Concrete symbol usage evidence: `usageCount` + top usage snippets + `confidence` + `isComplete`. `confidence: "syntactic"` means static/source-based only (no runtime or dynamic dispatch). When Tree-sitter + file content are available, comments and string literals are excluded from the scan — the count reflects real identifier nodes only. Replaces the removed `get_component_usage`. |
81+
| `remember` | `type`, `category`, `memory`, `reason`, shared `project`/`project_directory` | Persists to `.codebase-context/memory.json` |
82+
| `get_memory` | optional `category`, `type`, `query`, `limit`, shared `project`/`project_directory` | Memories with confidence decay scoring |
8383

8484
### Utility Tools
8585

@@ -95,12 +95,12 @@ Shared selector inputs:
9595

9696
Behavior matrix:
9797

98-
| Situation | Server behavior |
99-
| --- | --- |
100-
| One known project | Automatic routing |
101-
| Multiple known projects + active project already set | Automatic routing to the active project |
102-
| Multiple known projects + no active project | `selection_required` |
103-
| No workspace context and no bootstrap path | `selection_required` until the caller passes `project` |
98+
| Situation | Server behavior |
99+
| ---------------------------------------------------- | ------------------------------------------------------ |
100+
| One known project | Automatic routing |
101+
| Multiple known projects + active project already set | Automatic routing to the active project |
102+
| Multiple known projects + no active project | `selection_required` |
103+
| No workspace context and no bootstrap path | `selection_required` until the caller passes `project` |
104104

105105
Rules:
106106

@@ -271,7 +271,7 @@ Impact is 2-hop transitive: direct importers (hop 1) and their importers (hop 2)
271271
- **Angular**: signals, standalone components, control flow syntax, lifecycle hooks, DI patterns, component metadata
272272
- **React**: function/class components, custom hooks, context usage, memoization, Suspense, ecosystem signal extraction
273273
- **Next.js**: App Router and Pages Router detection, route/API classification, route paths, `"use client"`, metadata exports
274-
- **Generic**: 30+ have indexing/retrieval coverage including PHP, Ruby, Swift, Scala, Shell, config/markup., 10 languages have full symbol extraction (Tree-sitter: TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust).
274+
- **Generic**: 30+ have indexing/retrieval coverage including PHP, Ruby, Swift, Scala, Shell, config/markup., 10 languages have full symbol extraction (Tree-sitter: TypeScript, JavaScript, Python, Java, Kotlin, C, C++, C#, Go, Rust).
275275

276276
Notes:
277277

src/cli-init.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,5 +283,9 @@ export async function handleInitCli(_argv: string[]): Promise<void> {
283283
}
284284
}
285285

286-
console.log('\nNext steps:\n Start the HTTP server: npx codebase-context --http\n');
286+
console.log(
287+
'\nNext steps:\n' +
288+
' Run `npx codebase-context map` to see your codebase conventions\n' +
289+
' Start the HTTP server: npx codebase-context --http\n'
290+
);
287291
}

src/cli-map.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* CLI handler for `codebase-context map`.
3+
*
4+
* Self-contained module — does not import from cli.ts.
5+
* Resolves root path, builds the CodebaseMapSummary, and renders per flag:
6+
* (no flags) → markdown (pipeable)
7+
* --json → JSON.stringify(map)
8+
* --pretty → terminal box layout
9+
* --help → usage string
10+
*/
11+
12+
import path from 'path';
13+
import { promises as fs } from 'fs';
14+
import {
15+
CODEBASE_CONTEXT_DIRNAME,
16+
INTELLIGENCE_FILENAME,
17+
KEYWORD_INDEX_FILENAME,
18+
VECTOR_DB_DIRNAME,
19+
MEMORY_FILENAME
20+
} from './constants/codebase-context.js';
21+
import { createProjectState } from './project-state.js';
22+
import { buildCodebaseMap, renderMapMarkdown, renderMapPretty } from './core/codebase-map.js';
23+
import type { IndexState } from './tools/types.js';
24+
25+
function resolveMapRootPath(): string {
26+
return path.resolve(process.env.CODEBASE_ROOT ?? process.cwd());
27+
}
28+
29+
function printMapUsage(): void {
30+
console.log('codebase-context map [options]');
31+
console.log('');
32+
console.log('Output the conventions map for the current codebase.');
33+
console.log('');
34+
console.log('Options:');
35+
console.log(' --json Output raw JSON (CodebaseMapSummary)');
36+
console.log(' --pretty Terminal-friendly box layout');
37+
console.log(' --help Show this help');
38+
console.log('');
39+
console.log('Default output is markdown (pipeable).');
40+
}
41+
42+
export async function handleMapCli(args: string[]): Promise<void> {
43+
const useJson = args.includes('--json');
44+
const usePretty = args.includes('--pretty');
45+
const showHelp = args.includes('--help') || args.includes('-h');
46+
47+
if (showHelp) {
48+
printMapUsage();
49+
return;
50+
}
51+
52+
const rootPath = resolveMapRootPath();
53+
54+
// Build a minimal project state — same pattern as the MCP resource path.
55+
const baseDir = path.join(rootPath, CODEBASE_CONTEXT_DIRNAME);
56+
const paths = {
57+
baseDir,
58+
memory: path.join(baseDir, MEMORY_FILENAME),
59+
intelligence: path.join(baseDir, INTELLIGENCE_FILENAME),
60+
keywordIndex: path.join(baseDir, KEYWORD_INDEX_FILENAME),
61+
vectorDb: path.join(baseDir, VECTOR_DB_DIRNAME)
62+
};
63+
64+
let indexExists = false;
65+
try {
66+
await fs.access(paths.keywordIndex);
67+
indexExists = true;
68+
} catch {
69+
// no index yet
70+
}
71+
72+
const indexState: IndexState = { status: indexExists ? 'ready' : 'idle' };
73+
74+
const project = createProjectState(rootPath);
75+
project.indexState = indexState;
76+
77+
try {
78+
const map = await buildCodebaseMap(project);
79+
80+
if (useJson) {
81+
console.log(JSON.stringify(map, null, 2));
82+
} else if (usePretty) {
83+
console.log(renderMapPretty(map));
84+
} else {
85+
console.log(renderMapMarkdown(map));
86+
}
87+
} catch (error) {
88+
console.error(
89+
'Error building codebase map:',
90+
error instanceof Error ? error.message : String(error)
91+
);
92+
process.exit(1);
93+
}
94+
}

src/cli.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { formatJson } from './cli-formatters.js';
2626
import { handleMemoryCli } from './cli-memory.js';
2727
export { handleMemoryCli } from './cli-memory.js';
2828
import { handleInitCli } from './cli-init.js';
29+
import { handleMapCli } from './cli-map.js';
2930

3031
analyzerRegistry.register(new AngularAnalyzer());
3132
analyzerRegistry.register(new NextJsAnalyzer());
@@ -42,7 +43,8 @@ const _CLI_COMMANDS = [
4243
'patterns',
4344
'refs',
4445
'cycles',
45-
'init'
46+
'init',
47+
'map'
4648
] as const;
4749

4850
type CliCommand = (typeof _CLI_COMMANDS)[number];
@@ -82,6 +84,7 @@ function printUsage(): void {
8284
console.log(' refs --symbol <name> [--limit <n>] Symbol references');
8385
console.log(' cycles [--scope <path>] Circular dependency detection');
8486
console.log(' init Interactive setup wizard for AI clients');
87+
console.log(' map [--json] [--pretty] Conventions map');
8588
console.log('');
8689
console.log('Global flags:');
8790
console.log(' --json Output raw JSON (default: human-readable)');
@@ -268,6 +271,10 @@ export async function handleCliCommand(argv: string[]): Promise<void> {
268271
return handleInitCli(argv.slice(1));
269272
}
270273

274+
if (command === 'map') {
275+
return handleMapCli(argv.slice(1));
276+
}
277+
271278
const useJson = argv.includes('--json');
272279

273280
const flags = parseFlags(argv);

0 commit comments

Comments
 (0)