Skip to content

Commit 707319a

Browse files
author
Евгений Балякин
committed
initial codebone CLI and MCP server
0 parents  commit 707319a

86 files changed

Lines changed: 7586 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
node-version: [18, 20, 22]
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: ${{ matrix.node-version }}
19+
cache: npm
20+
- run: npm ci
21+
- run: npm run typecheck
22+
- run: npm run lint
23+
- run: npm test
24+
- run: npm run test:contracts
25+
- run: npm run test:security
26+
- run: npm run build
27+
- run: npm run bench:smoke
28+
- run: npm audit --audit-level=moderate
29+
- run: npm run package:check
30+
- run: npm run pack:smoke

.github/workflows/release.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
release:
6+
types: [published]
7+
8+
permissions:
9+
contents: read
10+
id-token: write
11+
12+
jobs:
13+
publish:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: 22
20+
registry-url: https://registry.npmjs.org
21+
cache: npm
22+
- run: npm ci
23+
- run: npm run release:check
24+
- run: npm publish --provenance --access public
25+
env:
26+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
dist/
3+
.codebone/
4+
coverage/
5+
.DS_Store
6+
.idea/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) Evgeny Balyakin
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# codebone
2+
3+
Agent-native CLI and MCP server for compact, read-only code context.
4+
5+
## Quick Start
6+
7+
```bash
8+
npm install
9+
npm run build
10+
npx codebone doctor
11+
npx codebone skeleton src --format json
12+
npx codebone mcp --root .
13+
```
14+
15+
## MCP Configuration
16+
17+
Claude Code `.claude/mcp.json`:
18+
19+
```json
20+
{
21+
"mcpServers": {
22+
"codebone": {
23+
"command": "npx",
24+
"args": ["-y", "codebone", "mcp", "--root", "."]
25+
}
26+
}
27+
}
28+
```
29+
30+
Cline settings:
31+
32+
```json
33+
{
34+
"cline.mcpServers": {
35+
"codebone": {
36+
"command": "npx",
37+
"args": ["-y", "codebone", "mcp", "--root", "."]
38+
}
39+
}
40+
}
41+
```
42+
43+
OpenCode example:
44+
45+
```json
46+
{
47+
"mcp": {
48+
"codebone": {
49+
"command": "npx",
50+
"args": ["-y", "codebone", "mcp", "--root", "."]
51+
}
52+
}
53+
}
54+
```
55+
56+
## Commands
57+
58+
- `codebone map [directory]`
59+
- `codebone skeleton <path>`
60+
- `codebone symbols <path> --query <name>`
61+
- `codebone read <path> --symbol-id <id>`
62+
- `codebone read <path> --symbol <name>`
63+
- `codebone read <path> --lines 10:30`
64+
- `codebone context --goal "task"`
65+
- `codebone index [directory]`
66+
- `codebone batch`
67+
- `codebone doctor`
68+
- `codebone mcp`
69+
70+
The current implementation is read-only and zero external CLI. It supports TypeScript, JavaScript, Python, Go, Rust, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, and Lua with syntax-aware extraction rules.
71+
72+
## Language Tiers
73+
74+
- Gold: TypeScript, Python, Go, Rust tree-sitter WASM parser smoke coverage.
75+
- Fallback: JavaScript, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Lua syntax-rule extraction.
76+
- References are AST-first where queries expose `@reference`, with text fallback otherwise.
77+
78+
## Release Check
79+
80+
```bash
81+
npm run release:check
82+
```
83+
84+
## MCP Inspector
85+
86+
After `npm run build`, run:
87+
88+
```bash
89+
npm run mcp:inspector
90+
```

benches/smoke.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import fs from 'node:fs/promises';
2+
import os from 'node:os';
3+
import path from 'node:path';
4+
import { performance } from 'node:perf_hooks';
5+
import { buildContext } from '../src/core/context.js';
6+
import { buildIndex } from '../src/core/indexer.js';
7+
import { projectMap } from '../src/core/map.js';
8+
import { readCode } from '../src/core/reader.js';
9+
import { renderSkeleton, skeletonPath } from '../src/core/skeleton.js';
10+
11+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'codebone-bench-'));
12+
await fs.mkdir(path.join(root, 'src'));
13+
const file = 'src/server.ts';
14+
await fs.writeFile(path.join(root, file), `
15+
import { Router } from './router';
16+
17+
export class Server {
18+
private router: Router;
19+
20+
constructor(router: Router) {
21+
this.router = router;
22+
}
23+
24+
async start(): Promise<void> {
25+
${Array.from({ length: 80 }, (_, index) => ` await this.router.handleRequest('/${index}');`).join('\n')}
26+
}
27+
}
28+
29+
export function createServer(router: Router): Server {
30+
${Array.from({ length: 30 }, () => ' const server = new Server(router);').join('\n')}
31+
return new Server(router);
32+
}
33+
`);
34+
await fs.writeFile(path.join(root, 'src/router.ts'), 'export class Router { async handleRequest(path: string): Promise<string> { return path; } }\n');
35+
for (let index = 0; index < 150; index += 1) {
36+
await fs.writeFile(path.join(root, 'src', `feature-${index}.ts`), `import { Router } from './router';\nexport function feature${index}(router: Router): Promise<string> {\n return router.handleRequest('/feature-${index}');\n}\n`);
37+
}
38+
39+
const skeleton = await measure('skeleton file', 250, () => skeletonPath(root, file));
40+
await measure('read symbol', 150, () => readCode(root, file, { symbol: 'createServer' }));
41+
await measure('context pack', 3000, () => buildContext(root, { goal: 'server request handling', budget: 4000 }));
42+
await measure('project map 150 files', 2500, () => projectMap(root, '.', 1200));
43+
await measure('index 150 files', 4000, () => buildIndex(root, '.'));
44+
45+
const fullSource = await fs.readFile(path.join(root, file), 'utf8');
46+
const compression = fullSource.length / Math.max(1, renderSkeleton(skeleton.result).length);
47+
if (compression < 1.2) throw new Error(`Token compression smoke failed: ${compression.toFixed(2)}x`);
48+
49+
process.stdout.write(`Smoke benchmarks ok\n files: 152\n skeleton: ${skeleton.elapsedMs.toFixed(1)}ms\n compression: ${compression.toFixed(2)}x\n`);
50+
51+
async function measure<T>(name: string, maxMs: number, fn: () => Promise<T>): Promise<{ result: T; elapsedMs: number }> {
52+
const started = performance.now();
53+
const result = await fn();
54+
const elapsedMs = performance.now() - started;
55+
if (elapsedMs > maxMs) throw new Error(`${name} SLO failed: ${elapsedMs.toFixed(1)}ms > ${maxMs}ms`);
56+
return { result, elapsedMs };
57+
}

0 commit comments

Comments
 (0)