@aprimediet/codewalker 1.0.0 → 1.2.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 +44 -50
- package/index.ts +6 -42
- package/package.json +20 -39
- package/prompts/codewalker.md +7 -0
- package/skills/codewalker/SKILL.md +43 -0
- package/src/cards.test.ts +88 -0
- package/src/cards.ts +87 -0
- package/src/db.test.ts +343 -0
- package/src/db.ts +363 -0
- package/src/extract/ctags-parse.test.ts +108 -0
- package/src/extract/ctags-parse.ts +112 -0
- package/src/extract/ctags.ts +51 -0
- package/src/extract/docs.test.ts +81 -0
- package/src/extract/docs.ts +169 -0
- package/src/extract/regex.test.ts +202 -0
- package/src/extract/regex.ts +192 -0
- package/src/format.test.ts +123 -0
- package/src/format.ts +69 -0
- package/src/git.test.ts +75 -0
- package/src/git.ts +62 -0
- package/src/index.contract.test.ts +145 -0
- package/src/index.ts +173 -0
- package/src/indexer.test.ts +138 -0
- package/src/indexer.ts +352 -0
- package/src/libs/cards.test.ts +86 -0
- package/src/libs/cards.ts +53 -0
- package/src/libs/dts.test.ts +269 -0
- package/src/libs/dts.ts +213 -0
- package/src/libs/indexer.test.ts +236 -0
- package/src/libs/indexer.ts +291 -0
- package/src/libs/resolve.test.ts +218 -0
- package/src/libs/resolve.ts +120 -0
- package/src/project.test.ts +115 -0
- package/src/project.ts +206 -0
- package/src/query.test.ts +169 -0
- package/src/query.ts +89 -0
- package/src/sync.test.ts +116 -0
- package/src/types.ts +117 -0
- package/vitest.config.ts +28 -0
- package/LICENSE +0 -21
- package/agents.ts +0 -126
- package/compat.ts +0 -217
- package/detect.ts +0 -188
- package/docs/PRD.md +0 -78
- package/prd.ts +0 -106
- package/skills/learn-this/SKILL.md +0 -325
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import { openDb, upsertSymbol, setMeta, getMeta, upsertLibSymbol, searchLibSymbols } from './db.ts';
|
|
6
|
+
import { runQuery } from './query.ts';
|
|
7
|
+
|
|
8
|
+
describe('query.ts', () => {
|
|
9
|
+
let tmpDir: string;
|
|
10
|
+
let dbPath: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cw-query-'));
|
|
14
|
+
dbPath = path.join(tmpDir, 'test.db');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns compact rows with file:line for a text query', () => {
|
|
22
|
+
const db = openDb(dbPath);
|
|
23
|
+
upsertSymbol(db, {
|
|
24
|
+
name: 'probeCompat', kind: 'function', file_path: 'src/compat.ts',
|
|
25
|
+
line_start: 201, line_end: 243, signature: '(cwd: string) => CompatResult',
|
|
26
|
+
doc: 'Detect integration status', summary: '', card_path: '',
|
|
27
|
+
});
|
|
28
|
+
upsertSymbol(db, {
|
|
29
|
+
name: 'detectStack', kind: 'function', file_path: 'src/detect.ts',
|
|
30
|
+
line_start: 10, line_end: 30, signature: '() => string[]',
|
|
31
|
+
doc: 'Detect tech stack', summary: '', card_path: '',
|
|
32
|
+
});
|
|
33
|
+
db.close();
|
|
34
|
+
|
|
35
|
+
const result = runQuery(dbPath, { query: 'probeCompat' });
|
|
36
|
+
expect(result.rows).toHaveLength(1);
|
|
37
|
+
expect(result.rows[0]!.name).toBe('probeCompat');
|
|
38
|
+
expect(result.rows[0]!.file_path).toBe('src/compat.ts');
|
|
39
|
+
expect(result.staleness).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('kind filter narrows results', () => {
|
|
43
|
+
const db = openDb(dbPath);
|
|
44
|
+
upsertSymbol(db, {
|
|
45
|
+
name: 'MyClass', kind: 'class', file_path: 'src/a.ts',
|
|
46
|
+
line_start: 1, line_end: 10, signature: '', doc: '', summary: '', card_path: '',
|
|
47
|
+
});
|
|
48
|
+
upsertSymbol(db, {
|
|
49
|
+
name: 'myFunc', kind: 'function', file_path: 'src/b.ts',
|
|
50
|
+
line_start: 1, line_end: 5, signature: '', doc: '', summary: '', card_path: '',
|
|
51
|
+
});
|
|
52
|
+
db.close();
|
|
53
|
+
|
|
54
|
+
const result = runQuery(dbPath, { query: 'MyClass', kind: 'class' });
|
|
55
|
+
expect(result.rows).toHaveLength(1);
|
|
56
|
+
expect(result.rows[0]!.kind).toBe('class');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('limit defaults to 10', () => {
|
|
60
|
+
const db = openDb(dbPath);
|
|
61
|
+
for (let i = 0; i < 15; i++) {
|
|
62
|
+
upsertSymbol(db, {
|
|
63
|
+
name: `sym${i}`, kind: 'function', file_path: 'src/a.ts',
|
|
64
|
+
line_start: i, line_end: i, signature: '', doc: '', summary: '', card_path: '',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
db.close();
|
|
68
|
+
|
|
69
|
+
const result = runQuery(dbPath, { query: '' });
|
|
70
|
+
expect(result.rows.length).toBeLessThanOrEqual(10);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('limit parameter caps results', () => {
|
|
74
|
+
const db = openDb(dbPath);
|
|
75
|
+
for (let i = 0; i < 15; i++) {
|
|
76
|
+
upsertSymbol(db, {
|
|
77
|
+
name: `sym${i}`, kind: 'function', file_path: 'src/a.ts',
|
|
78
|
+
line_start: i, line_end: i, signature: '', doc: '', summary: '', card_path: '',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
db.close();
|
|
82
|
+
|
|
83
|
+
const result = runQuery(dbPath, { query: '', limit: 3 });
|
|
84
|
+
expect(result.rows).toHaveLength(3);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('reports staleness when last_indexed_commit differs from HEAD', () => {
|
|
88
|
+
// Without git, staleness will be null (no commit to compare)
|
|
89
|
+
const db = openDb(dbPath);
|
|
90
|
+
setMeta(db, 'last_indexed_commit', 'abc123');
|
|
91
|
+
db.close();
|
|
92
|
+
|
|
93
|
+
const result = runQuery(dbPath, { query: '' });
|
|
94
|
+
// The staleness signal depends on git context
|
|
95
|
+
// In a non-git dir, staleness should be null
|
|
96
|
+
expect(result.staleness).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ── source filter ───────────────────────────────────────────
|
|
100
|
+
it('source="code" returns only code symbols (default)', () => {
|
|
101
|
+
const db = openDb(dbPath);
|
|
102
|
+
upsertSymbol(db, {
|
|
103
|
+
name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
|
|
104
|
+
line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
|
|
105
|
+
});
|
|
106
|
+
upsertLibSymbol(db, {
|
|
107
|
+
lib: 'hono', version: '4.6.3', name: 'honoFunc',
|
|
108
|
+
kind: 'function', signature: '', doc: '', summary: '', card_path: '',
|
|
109
|
+
});
|
|
110
|
+
db.close();
|
|
111
|
+
|
|
112
|
+
const result = runQuery(dbPath, { query: '', source: 'code' });
|
|
113
|
+
expect(result.rows).toHaveLength(1);
|
|
114
|
+
expect(result.rows[0]!.name).toBe('myFunc');
|
|
115
|
+
expect(result.rows[0]!.source).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('source="libs" returns only lib symbols', () => {
|
|
119
|
+
const db = openDb(dbPath);
|
|
120
|
+
upsertSymbol(db, {
|
|
121
|
+
name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
|
|
122
|
+
line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
|
|
123
|
+
});
|
|
124
|
+
upsertLibSymbol(db, {
|
|
125
|
+
lib: 'hono', version: '4.6.3', name: 'honoFunc',
|
|
126
|
+
kind: 'function', signature: '', doc: '', summary: '', card_path: '',
|
|
127
|
+
});
|
|
128
|
+
db.close();
|
|
129
|
+
|
|
130
|
+
const result = runQuery(dbPath, { query: '', source: 'libs' });
|
|
131
|
+
expect(result.rows).toHaveLength(1);
|
|
132
|
+
expect(result.rows[0]!.name).toBe('honoFunc');
|
|
133
|
+
expect(result.rows[0]!.source).toBe('lib');
|
|
134
|
+
expect(result.rows[0]!.lib).toBe('hono');
|
|
135
|
+
expect(result.rows[0]!.version).toBe('4.6.3');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('source="all" returns merged code and lib symbols', () => {
|
|
139
|
+
const db = openDb(dbPath);
|
|
140
|
+
upsertSymbol(db, {
|
|
141
|
+
name: 'myFunc', kind: 'function', file_path: 'src/a.ts',
|
|
142
|
+
line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
|
|
143
|
+
});
|
|
144
|
+
upsertLibSymbol(db, {
|
|
145
|
+
lib: 'hono', version: '4.6.3', name: 'honoFunc',
|
|
146
|
+
kind: 'function', signature: '', doc: '', summary: '', card_path: '',
|
|
147
|
+
});
|
|
148
|
+
db.close();
|
|
149
|
+
|
|
150
|
+
const result = runQuery(dbPath, { query: '', source: 'all' });
|
|
151
|
+
expect(result.rows).toHaveLength(2);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('source=all respects limit across merged set', () => {
|
|
155
|
+
const db = openDb(dbPath);
|
|
156
|
+
upsertSymbol(db, {
|
|
157
|
+
name: 'aCode', kind: 'function', file_path: 'src/a.ts',
|
|
158
|
+
line_start: 1, line_end: 1, signature: '', doc: '', summary: '', card_path: '',
|
|
159
|
+
});
|
|
160
|
+
upsertLibSymbol(db, {
|
|
161
|
+
lib: 'pkg', version: '1.0.0', name: 'bLib',
|
|
162
|
+
kind: 'function', signature: '', doc: '', summary: '', card_path: '',
|
|
163
|
+
});
|
|
164
|
+
db.close();
|
|
165
|
+
|
|
166
|
+
const result = runQuery(dbPath, { query: '', source: 'all', limit: 1 });
|
|
167
|
+
expect(result.rows).toHaveLength(1);
|
|
168
|
+
});
|
|
169
|
+
});
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query orchestration: wraps DB search with staleness detection.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { QueryResult, QueryResultRow, StalenessInfo } from "./types.ts";
|
|
6
|
+
import { openDb, searchSymbols, searchLibSymbols, getMeta } from "./db.ts";
|
|
7
|
+
import { getHeadSha, changedFilesSince } from "./git.ts";
|
|
8
|
+
|
|
9
|
+
export interface QueryParams {
|
|
10
|
+
query: string;
|
|
11
|
+
kind?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
/** Source scope: "code" (default, only code symbols), "libs" (only lib symbols), or "all" (both). */
|
|
14
|
+
source?: "code" | "libs" | "all";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run a query against a codewalker DB.
|
|
19
|
+
*
|
|
20
|
+
* @param dbPath - Path to the SQLite DB file.
|
|
21
|
+
* @param params - Query parameters.
|
|
22
|
+
* @param repoDir - Optional repo directory for staleness detection.
|
|
23
|
+
*/
|
|
24
|
+
export function runQuery(
|
|
25
|
+
dbPath: string,
|
|
26
|
+
params: QueryParams,
|
|
27
|
+
repoDir?: string,
|
|
28
|
+
): QueryResult {
|
|
29
|
+
const db = openDb(dbPath);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const source = params.source ?? "code";
|
|
33
|
+
const limit = params.limit ?? 10;
|
|
34
|
+
|
|
35
|
+
let rows: QueryResultRow[];
|
|
36
|
+
|
|
37
|
+
if (source === "libs") {
|
|
38
|
+
rows = searchLibSymbols(db, params.query, params.kind, limit) as unknown as QueryResultRow[];
|
|
39
|
+
} else if (source === "all") {
|
|
40
|
+
// Run both code and lib searches, merge, sort by score, apply limit
|
|
41
|
+
const codeRows = searchSymbols(db, params.query, params.kind, limit);
|
|
42
|
+
const libRows = searchLibSymbols(db, params.query, params.kind, limit) as unknown as QueryResultRow[];
|
|
43
|
+
|
|
44
|
+
// Merge and sort by score ascending (lower bm25 = better match)
|
|
45
|
+
const merged: QueryResultRow[] = [...codeRows, ...libRows];
|
|
46
|
+
merged.sort((a, b) => a.score - b.score);
|
|
47
|
+
rows = merged.slice(0, limit);
|
|
48
|
+
} else {
|
|
49
|
+
// "code" — default, existing behavior
|
|
50
|
+
rows = searchSymbols(db, params.query, params.kind, limit);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const staleness = detectStaleness(db, repoDir);
|
|
54
|
+
|
|
55
|
+
return { rows, staleness };
|
|
56
|
+
} finally {
|
|
57
|
+
db.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect whether the index is stale compared to the current git HEAD.
|
|
63
|
+
*/
|
|
64
|
+
function detectStaleness(
|
|
65
|
+
db: Database,
|
|
66
|
+
repoDir?: string,
|
|
67
|
+
): StalenessInfo | null {
|
|
68
|
+
if (!repoDir) return null;
|
|
69
|
+
|
|
70
|
+
const indexedCommit = getMeta(db, "last_indexed_commit");
|
|
71
|
+
if (!indexedCommit) return null;
|
|
72
|
+
|
|
73
|
+
const headCommit = getHeadSha(repoDir);
|
|
74
|
+
if (!headCommit) return null;
|
|
75
|
+
|
|
76
|
+
if (indexedCommit === headCommit) return null;
|
|
77
|
+
|
|
78
|
+
const changedFiles = changedFilesSince(repoDir, indexedCommit);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
indexedCommit,
|
|
82
|
+
headCommit,
|
|
83
|
+
changedFiles: changedFiles.length,
|
|
84
|
+
message: `Index is stale (${changedFiles.length} file(s) changed since last index)`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Re-export for test convenience
|
|
89
|
+
export { detectStaleness };
|
package/src/sync.test.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { sync } from './indexer.ts';
|
|
7
|
+
import { openDb, getMeta, searchSymbols } from './db.ts';
|
|
8
|
+
|
|
9
|
+
describe('sync', () => {
|
|
10
|
+
let tmpDir: string;
|
|
11
|
+
let globalDir: string;
|
|
12
|
+
let cardsDir: string;
|
|
13
|
+
let symbolsDir: string;
|
|
14
|
+
let dbPath: string;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cw-sync-'));
|
|
18
|
+
globalDir = path.join(tmpDir, '.pi-global', 'projects', 'test-project', 'codewalker');
|
|
19
|
+
cardsDir = path.join(globalDir, 'entries');
|
|
20
|
+
symbolsDir = path.join(cardsDir, 'symbols');
|
|
21
|
+
dbPath = path.join(globalDir, 'index.db');
|
|
22
|
+
fs.mkdirSync(symbolsDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
// Init git repo
|
|
25
|
+
execSync('git init', { cwd: tmpDir, stdio: 'ignore' });
|
|
26
|
+
execSync('git config user.email test@test.com', { cwd: tmpDir, stdio: 'ignore' });
|
|
27
|
+
execSync('git config user.name Test', { cwd: tmpDir, stdio: 'ignore' });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function writeFile(name: string, content: string): void {
|
|
35
|
+
const p = path.join(tmpDir, name);
|
|
36
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
37
|
+
fs.writeFileSync(p, content, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
it('sync reindexes only changed files after an edit', async () => {
|
|
41
|
+
writeFile('src/a.ts', 'function alpha() {}');
|
|
42
|
+
writeFile('src/b.ts', 'function beta() {}');
|
|
43
|
+
execSync('git add . && git commit -m "initial"', { cwd: tmpDir, stdio: 'ignore' });
|
|
44
|
+
|
|
45
|
+
// First full scan
|
|
46
|
+
await sync({
|
|
47
|
+
projectRoot: tmpDir,
|
|
48
|
+
globalCodewalkerDir: globalDir,
|
|
49
|
+
dbPath,
|
|
50
|
+
entriesDir: cardsDir,
|
|
51
|
+
symbolsDir,
|
|
52
|
+
useCtags: false,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const db = openDb(dbPath);
|
|
56
|
+
expect(getMeta(db, 'last_indexed_commit')).toBeTruthy();
|
|
57
|
+
const beforeEdit = searchSymbols(db, '', undefined, 100);
|
|
58
|
+
const beforeCount = beforeEdit.length;
|
|
59
|
+
db.close();
|
|
60
|
+
|
|
61
|
+
// Edit one file
|
|
62
|
+
writeFile('src/b.ts', 'function beta() {}\nfunction beta2() {}');
|
|
63
|
+
execSync('git add . && git commit -m "edit b"', { cwd: tmpDir, stdio: 'ignore' });
|
|
64
|
+
|
|
65
|
+
// Sync
|
|
66
|
+
await sync({
|
|
67
|
+
projectRoot: tmpDir,
|
|
68
|
+
globalCodewalkerDir: globalDir,
|
|
69
|
+
dbPath,
|
|
70
|
+
entriesDir: cardsDir,
|
|
71
|
+
symbolsDir,
|
|
72
|
+
useCtags: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const db2 = openDb(dbPath);
|
|
76
|
+
const afterEdit = searchSymbols(db2, '', undefined, 100);
|
|
77
|
+
// Should have one more symbol (beta2 added)
|
|
78
|
+
expect(afterEdit.length).toBe(beforeCount + 1);
|
|
79
|
+
expect(afterEdit.some(s => s.name === 'beta2')).toBe(true);
|
|
80
|
+
db2.close();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('sync removes symbols for deleted files', async () => {
|
|
84
|
+
writeFile('src/keep.ts', 'function keep() {}');
|
|
85
|
+
writeFile('src/remove.ts', 'function removeMe() {}');
|
|
86
|
+
execSync('git add . && git commit -m "initial"', { cwd: tmpDir, stdio: 'ignore' });
|
|
87
|
+
|
|
88
|
+
await sync({
|
|
89
|
+
projectRoot: tmpDir,
|
|
90
|
+
globalCodewalkerDir: globalDir,
|
|
91
|
+
dbPath,
|
|
92
|
+
entriesDir: cardsDir,
|
|
93
|
+
symbolsDir,
|
|
94
|
+
useCtags: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Delete one file
|
|
98
|
+
fs.rmSync(path.join(tmpDir, 'src/remove.ts'));
|
|
99
|
+
execSync('git add . && git commit -m "delete remove"', { cwd: tmpDir, stdio: 'ignore' });
|
|
100
|
+
|
|
101
|
+
await sync({
|
|
102
|
+
projectRoot: tmpDir,
|
|
103
|
+
globalCodewalkerDir: globalDir,
|
|
104
|
+
dbPath,
|
|
105
|
+
entriesDir: cardsDir,
|
|
106
|
+
symbolsDir,
|
|
107
|
+
useCtags: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const db = openDb(dbPath);
|
|
111
|
+
const all = searchSymbols(db, '', undefined, 100);
|
|
112
|
+
expect(all.some(s => s.name === 'keep')).toBe(true);
|
|
113
|
+
expect(all.some(s => s.name === 'removeMe')).toBe(false);
|
|
114
|
+
db.close();
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the codewalker code index.
|
|
3
|
+
*
|
|
4
|
+
* These types are shared across all layers (extraction, cards, DB, query).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Library symbol kind — extends SymbolKind with reexport + namespace. */
|
|
8
|
+
export type LibSymbolKind =
|
|
9
|
+
| "function"
|
|
10
|
+
| "const"
|
|
11
|
+
| "class"
|
|
12
|
+
| "interface"
|
|
13
|
+
| "type"
|
|
14
|
+
| "enum"
|
|
15
|
+
| "namespace"
|
|
16
|
+
| "reexport"
|
|
17
|
+
| "module";
|
|
18
|
+
|
|
19
|
+
/** The kind of a code symbol. */
|
|
20
|
+
export type SymbolKind =
|
|
21
|
+
| "function"
|
|
22
|
+
| "const"
|
|
23
|
+
| "class"
|
|
24
|
+
| "type"
|
|
25
|
+
| "method"
|
|
26
|
+
| "enum"
|
|
27
|
+
| "variable"
|
|
28
|
+
| "interface"
|
|
29
|
+
| "namespace"
|
|
30
|
+
| "module";
|
|
31
|
+
|
|
32
|
+
/** A single symbol extracted from a library's .d.ts file. */
|
|
33
|
+
export interface LibSymbol {
|
|
34
|
+
lib: string;
|
|
35
|
+
version: string;
|
|
36
|
+
name: string;
|
|
37
|
+
kind: LibSymbolKind;
|
|
38
|
+
signature: string;
|
|
39
|
+
doc: string;
|
|
40
|
+
summary: string;
|
|
41
|
+
card_path: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A single symbol extracted from source code. */
|
|
45
|
+
export interface Symbol {
|
|
46
|
+
name: string;
|
|
47
|
+
kind: SymbolKind;
|
|
48
|
+
file_path: string;
|
|
49
|
+
line_start: number;
|
|
50
|
+
line_end: number;
|
|
51
|
+
signature: string;
|
|
52
|
+
doc: string;
|
|
53
|
+
summary: string; // reserved for v1.3; empty in v1.1
|
|
54
|
+
card_path: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The frontmatter head of a markdown card — what `query` returns (compact, token-cheap). */
|
|
58
|
+
export interface CardHead {
|
|
59
|
+
name: string;
|
|
60
|
+
kind: string;
|
|
61
|
+
signature: string;
|
|
62
|
+
location: string; // "file.ts:10-42"
|
|
63
|
+
tags: string[];
|
|
64
|
+
summary: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A single row returned from a query. */
|
|
68
|
+
export interface QueryResultRow {
|
|
69
|
+
name: string;
|
|
70
|
+
kind: string;
|
|
71
|
+
file_path: string;
|
|
72
|
+
line_start: number;
|
|
73
|
+
line_end: number;
|
|
74
|
+
signature: string;
|
|
75
|
+
summary: string;
|
|
76
|
+
score: number;
|
|
77
|
+
id: number;
|
|
78
|
+
/** Origin fields — code rows omit these; lib rows set them. */
|
|
79
|
+
source?: "code" | "lib";
|
|
80
|
+
lib?: string;
|
|
81
|
+
version?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The full result of a query, including staleness info. */
|
|
85
|
+
export interface QueryResult {
|
|
86
|
+
rows: QueryResultRow[];
|
|
87
|
+
staleness: StalenessInfo | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Git-anchored staleness signal attached to every query result. */
|
|
91
|
+
export interface StalenessInfo {
|
|
92
|
+
indexedCommit: string;
|
|
93
|
+
headCommit: string;
|
|
94
|
+
changedFiles: number;
|
|
95
|
+
message: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Metadata about the index stored in meta.json. */
|
|
99
|
+
export interface IndexMeta {
|
|
100
|
+
schemaVersion: number;
|
|
101
|
+
lastIndexedCommit: string;
|
|
102
|
+
lastFullScan: string; // ISO date
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Configuration for the codebase indexer. */
|
|
106
|
+
export interface IndexerConfig {
|
|
107
|
+
language: "ts" | "js" | "py" | "go" | "rs";
|
|
108
|
+
extensions: string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Map of language → file extension list. */
|
|
112
|
+
export const SUPPORTED_LANGUAGES: Record<string, string[]> = {
|
|
113
|
+
ts: [".ts", ".tsx"],
|
|
114
|
+
js: [".js", ".jsx", ".mjs", ".cjs"],
|
|
115
|
+
py: [".py"],
|
|
116
|
+
go: [".go"],
|
|
117
|
+
};
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
resolve: {
|
|
6
|
+
alias: {
|
|
7
|
+
'@': path.resolve(__dirname, 'src'),
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
environment: 'node',
|
|
13
|
+
include: ['src/**/*.test.ts'],
|
|
14
|
+
coverage: {
|
|
15
|
+
provider: 'v8',
|
|
16
|
+
reporter: ['text', 'lcov'],
|
|
17
|
+
exclude: [
|
|
18
|
+
'src/**/*.test.ts',
|
|
19
|
+
'**/node_modules/**',
|
|
20
|
+
'**/dist/**',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
sequence: {
|
|
24
|
+
concurrent: false,
|
|
25
|
+
},
|
|
26
|
+
testTimeout: 15_000,
|
|
27
|
+
},
|
|
28
|
+
});
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 aprimediet
|
|
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.
|
package/agents.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
|
|
4
|
-
// Agent-facing guide lives at the repo root by convention (AGENTS.md), with a thin
|
|
5
|
-
// CLAUDE.md pointer next to it so Claude Code auto-imports the same source of truth.
|
|
6
|
-
const AGENTS_CANDIDATES = ["AGENTS.md", ".agents/AGENTS.md", "docs/AGENTS.md"];
|
|
7
|
-
const CLAUDE_CANDIDATES = ["CLAUDE.md", ".claude/CLAUDE.md"];
|
|
8
|
-
|
|
9
|
-
export function findExistingAgentsMd(root: string): string | null {
|
|
10
|
-
for (const p of AGENTS_CANDIDATES) {
|
|
11
|
-
const filePath = path.join(root, p);
|
|
12
|
-
if (fs.existsSync(filePath)) {
|
|
13
|
-
return filePath;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function findExistingClaudeMd(root: string): string | null {
|
|
20
|
-
for (const p of CLAUDE_CANDIDATES) {
|
|
21
|
-
const filePath = path.join(root, p);
|
|
22
|
-
if (fs.existsSync(filePath)) {
|
|
23
|
-
return filePath;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createAgentsMd(root: string, content: string): string {
|
|
30
|
-
const outPath = path.join(root, "AGENTS.md");
|
|
31
|
-
fs.writeFileSync(outPath, content, "utf-8");
|
|
32
|
-
return outPath;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function createClaudeMd(root: string, content: string): string {
|
|
36
|
-
const outPath = path.join(root, "CLAUDE.md");
|
|
37
|
-
fs.writeFileSync(outPath, content, "utf-8");
|
|
38
|
-
return outPath;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The agent-facing engineering guide. This is the "how to work in this repo" document:
|
|
43
|
-
* tech stack, structure, commands, conventions, technical boundaries, gotchas, and how
|
|
44
|
-
* the agent should use the minion/memory companion extensions. Product "what & why"
|
|
45
|
-
* (goals, users, features, metrics) belongs in the PRD, not here.
|
|
46
|
-
*/
|
|
47
|
-
export function agentsMdTemplate(p: {
|
|
48
|
-
projectName: string;
|
|
49
|
-
summary: string;
|
|
50
|
-
techStack: string;
|
|
51
|
-
structure: string;
|
|
52
|
-
commands: { setup?: string; build?: string; test?: string; run?: string; lint?: string };
|
|
53
|
-
conventions: string;
|
|
54
|
-
boundaries: string;
|
|
55
|
-
knownIssues: string[];
|
|
56
|
-
integration: string;
|
|
57
|
-
currentFocus: string;
|
|
58
|
-
prdPath: string | null;
|
|
59
|
-
}): string {
|
|
60
|
-
const cmd = p.commands;
|
|
61
|
-
const commandRows = [
|
|
62
|
-
cmd.setup ? `- **Setup:** \`${cmd.setup}\`` : null,
|
|
63
|
-
cmd.build ? `- **Build:** \`${cmd.build}\`` : null,
|
|
64
|
-
cmd.test ? `- **Test:** \`${cmd.test}\`` : null,
|
|
65
|
-
cmd.run ? `- **Run:** \`${cmd.run}\`` : null,
|
|
66
|
-
cmd.lint ? `- **Lint:** \`${cmd.lint}\`` : null,
|
|
67
|
-
]
|
|
68
|
-
.filter(Boolean)
|
|
69
|
-
.join("\n");
|
|
70
|
-
const commandsSection = commandRows || "_(none detected — ask the user)_";
|
|
71
|
-
const issuesSection = p.knownIssues.length
|
|
72
|
-
? p.knownIssues.map((i) => `- ${i}`).join("\n")
|
|
73
|
-
: "_(none known)_";
|
|
74
|
-
const productLink = p.prdPath
|
|
75
|
-
? `Product context (goals, users, features, success metrics): see [${p.prdPath}](${p.prdPath}).`
|
|
76
|
-
: "Product context: no PRD found.";
|
|
77
|
-
|
|
78
|
-
return `# AGENTS.md — ${p.projectName}
|
|
79
|
-
|
|
80
|
-
Guide for coding agents working in this repository. ${productLink}
|
|
81
|
-
|
|
82
|
-
## Summary
|
|
83
|
-
${p.summary}
|
|
84
|
-
|
|
85
|
-
## Tech Stack
|
|
86
|
-
${p.techStack}
|
|
87
|
-
|
|
88
|
-
## Project Structure
|
|
89
|
-
${p.structure}
|
|
90
|
-
|
|
91
|
-
## Commands
|
|
92
|
-
${commandsSection}
|
|
93
|
-
|
|
94
|
-
## Conventions
|
|
95
|
-
${p.conventions}
|
|
96
|
-
|
|
97
|
-
## Boundaries (technical)
|
|
98
|
-
${p.boundaries}
|
|
99
|
-
|
|
100
|
-
## Known Issues & Gotchas
|
|
101
|
-
${issuesSection}
|
|
102
|
-
|
|
103
|
-
## Companion Extensions
|
|
104
|
-
${p.integration}
|
|
105
|
-
|
|
106
|
-
## Current Focus
|
|
107
|
-
${p.currentFocus}
|
|
108
|
-
`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* A thin CLAUDE.md that points Claude Code at AGENTS.md as the single source of truth.
|
|
113
|
-
* The `@AGENTS.md` line triggers Claude Code's file-import so the content is pulled in.
|
|
114
|
-
*/
|
|
115
|
-
export function claudeMdTemplate(projectName: string): string {
|
|
116
|
-
return `# CLAUDE.md — ${projectName}
|
|
117
|
-
|
|
118
|
-
Guidance for Claude Code in this repository.
|
|
119
|
-
|
|
120
|
-
All project conventions, architecture, build/test commands, and boundaries live in
|
|
121
|
-
**[AGENTS.md](./AGENTS.md)** — the shared guide for every coding agent. Keep that file
|
|
122
|
-
as the single source of truth; do not duplicate its content here.
|
|
123
|
-
|
|
124
|
-
@AGENTS.md
|
|
125
|
-
`;
|
|
126
|
-
}
|