bluera-knowledge 0.10.0 → 0.11.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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +98 -2
  4. package/commands/sync.md +96 -0
  5. package/dist/{chunk-ITH6FWQY.js → chunk-2WBITQWZ.js} +24 -3
  6. package/dist/{chunk-ITH6FWQY.js.map → chunk-2WBITQWZ.js.map} +1 -1
  7. package/dist/{chunk-CUHYSPRV.js → chunk-565OVW3C.js} +999 -2
  8. package/dist/chunk-565OVW3C.js.map +1 -0
  9. package/dist/{chunk-DWAIT2OD.js → chunk-TRDMYKGC.js} +190 -5
  10. package/dist/chunk-TRDMYKGC.js.map +1 -0
  11. package/dist/index.js +217 -5
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/server.js +2 -2
  14. package/dist/workers/background-worker-cli.js +2 -2
  15. package/package.json +1 -1
  16. package/src/analysis/adapter-registry.test.ts +211 -0
  17. package/src/analysis/adapter-registry.ts +155 -0
  18. package/src/analysis/language-adapter.ts +127 -0
  19. package/src/analysis/parser-factory.test.ts +79 -1
  20. package/src/analysis/parser-factory.ts +8 -0
  21. package/src/analysis/zil/index.ts +34 -0
  22. package/src/analysis/zil/zil-adapter.test.ts +187 -0
  23. package/src/analysis/zil/zil-adapter.ts +121 -0
  24. package/src/analysis/zil/zil-lexer.test.ts +222 -0
  25. package/src/analysis/zil/zil-lexer.ts +239 -0
  26. package/src/analysis/zil/zil-parser.test.ts +210 -0
  27. package/src/analysis/zil/zil-parser.ts +360 -0
  28. package/src/analysis/zil/zil-special-forms.ts +193 -0
  29. package/src/cli/commands/sync.test.ts +54 -0
  30. package/src/cli/commands/sync.ts +264 -0
  31. package/src/cli/index.ts +1 -0
  32. package/src/crawl/claude-client.test.ts +56 -0
  33. package/src/crawl/claude-client.ts +27 -1
  34. package/src/index.ts +8 -0
  35. package/src/mcp/commands/index.ts +2 -0
  36. package/src/mcp/commands/sync.commands.test.ts +283 -0
  37. package/src/mcp/commands/sync.commands.ts +233 -0
  38. package/src/mcp/server.ts +9 -1
  39. package/src/services/gitignore.service.test.ts +157 -0
  40. package/src/services/gitignore.service.ts +132 -0
  41. package/src/services/store-definition.service.test.ts +440 -0
  42. package/src/services/store-definition.service.ts +198 -0
  43. package/src/services/store.service.test.ts +279 -1
  44. package/src/services/store.service.ts +101 -4
  45. package/src/types/index.ts +18 -0
  46. package/src/types/store-definition.test.ts +492 -0
  47. package/src/types/store-definition.ts +129 -0
  48. package/dist/chunk-CUHYSPRV.js.map +0 -1
  49. package/dist/chunk-DWAIT2OD.js.map +0 -1
@@ -0,0 +1,360 @@
1
+ /**
2
+ * ZIL Parser
3
+ *
4
+ * Parses ZIL source code into an AST-like structure and extracts:
5
+ * - Symbols (ROUTINE, OBJECT, ROOM, GLOBAL, CONSTANT, SYNTAX)
6
+ * - Imports (INSERT-FILE)
7
+ * - Call relationships (filtering out special forms)
8
+ */
9
+
10
+ import { ZilLexer, TokenType, type Token } from './zil-lexer.js';
11
+ import { isSpecialForm, isDefinitionForm } from './zil-special-forms.js';
12
+ import type { ImportInfo } from '../ast-parser.js';
13
+
14
+ /**
15
+ * A ZIL form node (angle-bracket expression)
16
+ */
17
+ export interface ZilForm {
18
+ /** The head/operator of the form (first atom after <) */
19
+ head: string;
20
+ /** Child nodes (atoms, strings, numbers, nested forms) */
21
+ children: ZilNode[];
22
+ /** Starting line number */
23
+ startLine: number;
24
+ /** Ending line number */
25
+ endLine: number;
26
+ }
27
+
28
+ /**
29
+ * A parenthesized group (used for args lists)
30
+ */
31
+ export interface ZilGroup {
32
+ type: 'group';
33
+ children: ZilNode[];
34
+ startLine: number;
35
+ endLine: number;
36
+ }
37
+
38
+ /**
39
+ * A leaf node (atom, string, or number)
40
+ */
41
+ export interface ZilLeaf {
42
+ type: 'atom' | 'string' | 'number';
43
+ value: string;
44
+ line: number;
45
+ }
46
+
47
+ export type ZilNode = ZilForm | ZilGroup | ZilLeaf;
48
+
49
+ /**
50
+ * Extracted symbol from ZIL code
51
+ */
52
+ export interface ZilSymbol {
53
+ name: string;
54
+ kind: 'routine' | 'object' | 'room' | 'global' | 'constant' | 'syntax' | 'verb';
55
+ startLine: number;
56
+ endLine: number;
57
+ signature?: string;
58
+ }
59
+
60
+ /**
61
+ * A function call extracted from ZIL code
62
+ */
63
+ export interface ZilCall {
64
+ caller: string;
65
+ callee: string;
66
+ line: number;
67
+ }
68
+
69
+ /**
70
+ * Result of parsing a ZIL file
71
+ */
72
+ export interface ZilParseResult {
73
+ forms: ZilForm[];
74
+ symbols: ZilSymbol[];
75
+ imports: ImportInfo[];
76
+ calls: ZilCall[];
77
+ }
78
+
79
+ /**
80
+ * Parser for ZIL source code
81
+ */
82
+ export class ZilParser {
83
+ private readonly lexer = new ZilLexer();
84
+ private tokens: Token[] = [];
85
+ private pos = 0;
86
+
87
+ /**
88
+ * Parse ZIL source code
89
+ */
90
+ parse(input: string): ZilParseResult {
91
+ this.tokens = this.lexer.tokenize(input);
92
+ this.pos = 0;
93
+
94
+ const forms: ZilForm[] = [];
95
+ const symbols: ZilSymbol[] = [];
96
+ const imports: ImportInfo[] = [];
97
+ const calls: ZilCall[] = [];
98
+
99
+ // Parse all top-level forms
100
+ while (!this.isAtEnd()) {
101
+ if (this.check(TokenType.LANGLE)) {
102
+ const form = this.parseForm();
103
+ if (form !== undefined) {
104
+ forms.push(form);
105
+
106
+ // Extract symbols from definition forms
107
+ const symbol = this.extractSymbol(form);
108
+ if (symbol !== undefined) {
109
+ symbols.push(symbol);
110
+ }
111
+
112
+ // Extract imports from INSERT-FILE
113
+ const imp = this.extractImport(form);
114
+ if (imp !== undefined) {
115
+ imports.push(imp);
116
+ }
117
+
118
+ // Extract calls from routines
119
+ if (form.head.toUpperCase() === 'ROUTINE') {
120
+ const routineName = this.getRoutineName(form);
121
+ if (routineName !== undefined) {
122
+ this.extractCalls(form, routineName, calls);
123
+ }
124
+ }
125
+ }
126
+ } else {
127
+ // Skip non-form tokens at top level
128
+ this.advance();
129
+ }
130
+ }
131
+
132
+ return { forms, symbols, imports, calls };
133
+ }
134
+
135
+ private isAtEnd(): boolean {
136
+ return this.pos >= this.tokens.length;
137
+ }
138
+
139
+ private peek(): Token | undefined {
140
+ return this.tokens[this.pos];
141
+ }
142
+
143
+ private check(type: TokenType): boolean {
144
+ if (this.isAtEnd()) return false;
145
+ return this.peek()?.type === type;
146
+ }
147
+
148
+ private advance(): Token | undefined {
149
+ if (!this.isAtEnd()) {
150
+ const token = this.tokens[this.pos];
151
+ this.pos++;
152
+ return token;
153
+ }
154
+ return undefined;
155
+ }
156
+
157
+ private parseForm(): ZilForm | undefined {
158
+ if (!this.check(TokenType.LANGLE)) return undefined;
159
+
160
+ const startToken = this.advance(); // consume <
161
+ const startLine = startToken?.line ?? 1;
162
+ let endLine = startLine;
163
+
164
+ // Get the head (first element)
165
+ let head = '';
166
+ if (this.check(TokenType.ATOM)) {
167
+ head = this.advance()?.value ?? '';
168
+ }
169
+
170
+ const children: ZilNode[] = [];
171
+
172
+ // Parse children until >
173
+ while (!this.isAtEnd() && !this.check(TokenType.RANGLE)) {
174
+ const child = this.parseNode();
175
+ if (child !== undefined) {
176
+ children.push(child);
177
+ endLine = this.getNodeEndLine(child);
178
+ } else {
179
+ // Skip unexpected tokens
180
+ this.advance();
181
+ }
182
+ }
183
+
184
+ if (this.check(TokenType.RANGLE)) {
185
+ const closeToken = this.advance();
186
+ endLine = closeToken?.line ?? endLine;
187
+ }
188
+
189
+ return { head, children, startLine, endLine };
190
+ }
191
+
192
+ private parseGroup(): ZilGroup | undefined {
193
+ if (!this.check(TokenType.LPAREN)) return undefined;
194
+
195
+ const startToken = this.advance(); // consume (
196
+ const startLine = startToken?.line ?? 1;
197
+ let endLine = startLine;
198
+
199
+ const children: ZilNode[] = [];
200
+
201
+ while (!this.isAtEnd() && !this.check(TokenType.RPAREN)) {
202
+ const child = this.parseNode();
203
+ if (child !== undefined) {
204
+ children.push(child);
205
+ endLine = this.getNodeEndLine(child);
206
+ } else {
207
+ this.advance();
208
+ }
209
+ }
210
+
211
+ if (this.check(TokenType.RPAREN)) {
212
+ const closeToken = this.advance();
213
+ endLine = closeToken?.line ?? endLine;
214
+ }
215
+
216
+ return { type: 'group', children, startLine, endLine };
217
+ }
218
+
219
+ private parseNode(): ZilNode | undefined {
220
+ const token = this.peek();
221
+ if (token === undefined) return undefined;
222
+
223
+ switch (token.type) {
224
+ case TokenType.LANGLE:
225
+ return this.parseForm();
226
+ case TokenType.LPAREN:
227
+ return this.parseGroup();
228
+ case TokenType.ATOM:
229
+ this.advance();
230
+ return { type: 'atom', value: token.value, line: token.line };
231
+ case TokenType.STRING:
232
+ this.advance();
233
+ return { type: 'string', value: token.value, line: token.line };
234
+ case TokenType.NUMBER:
235
+ this.advance();
236
+ return { type: 'number', value: token.value, line: token.line };
237
+ default:
238
+ return undefined;
239
+ }
240
+ }
241
+
242
+ private getNodeEndLine(node: ZilNode): number {
243
+ if ('endLine' in node) {
244
+ return node.endLine;
245
+ }
246
+ return node.line;
247
+ }
248
+
249
+ private extractSymbol(form: ZilForm): ZilSymbol | undefined {
250
+ const headUpper = form.head.toUpperCase();
251
+
252
+ if (!isDefinitionForm(headUpper)) {
253
+ return undefined;
254
+ }
255
+
256
+ // Get the name (first child that's an atom)
257
+ const nameNode = form.children.find((c): c is ZilLeaf => 'type' in c && c.type === 'atom');
258
+
259
+ if (nameNode === undefined) {
260
+ return undefined;
261
+ }
262
+
263
+ const kindMap: Record<string, ZilSymbol['kind']> = {
264
+ ROUTINE: 'routine',
265
+ OBJECT: 'object',
266
+ ROOM: 'room',
267
+ GLOBAL: 'global',
268
+ CONSTANT: 'constant',
269
+ SYNTAX: 'syntax',
270
+ VERB: 'verb',
271
+ DEFINE: 'routine',
272
+ DEFMAC: 'routine',
273
+ };
274
+
275
+ const kind = kindMap[headUpper];
276
+ if (kind === undefined) {
277
+ return undefined;
278
+ }
279
+
280
+ const result: ZilSymbol = {
281
+ name: nameNode.value,
282
+ kind,
283
+ startLine: form.startLine,
284
+ endLine: form.endLine,
285
+ };
286
+
287
+ if (headUpper === 'ROUTINE' || headUpper === 'DEFINE' || headUpper === 'DEFMAC') {
288
+ result.signature = this.extractRoutineSignature(form, nameNode.value);
289
+ }
290
+
291
+ return result;
292
+ }
293
+
294
+ private extractRoutineSignature(form: ZilForm, name: string): string {
295
+ // Find args group (first parenthesized group after name)
296
+ const argsGroup = form.children.find((c): c is ZilGroup => 'type' in c && c.type === 'group');
297
+
298
+ if (argsGroup === undefined) {
299
+ return `ROUTINE ${name} ()`;
300
+ }
301
+
302
+ const args = argsGroup.children
303
+ .filter((c): c is ZilLeaf => 'type' in c && c.type === 'atom')
304
+ .map((c) => c.value)
305
+ .join(' ');
306
+
307
+ return `ROUTINE ${name} (${args})`;
308
+ }
309
+
310
+ private extractImport(form: ZilForm): ImportInfo | undefined {
311
+ if (form.head.toUpperCase() !== 'INSERT-FILE') {
312
+ return undefined;
313
+ }
314
+
315
+ // Get the file name (first string child)
316
+ const fileNode = form.children.find((c): c is ZilLeaf => 'type' in c && c.type === 'string');
317
+
318
+ if (fileNode === undefined) {
319
+ return undefined;
320
+ }
321
+
322
+ return {
323
+ source: fileNode.value,
324
+ specifiers: [],
325
+ isType: false,
326
+ };
327
+ }
328
+
329
+ private getRoutineName(form: ZilForm): string | undefined {
330
+ const nameNode = form.children.find((c): c is ZilLeaf => 'type' in c && c.type === 'atom');
331
+ return nameNode?.value;
332
+ }
333
+
334
+ private extractCalls(node: ZilNode, caller: string, calls: ZilCall[]): void {
335
+ if ('head' in node) {
336
+ // It's a form
337
+ const headUpper = node.head.toUpperCase();
338
+
339
+ // If not a special form and not empty, it's a call
340
+ if (node.head !== '' && !isSpecialForm(headUpper)) {
341
+ calls.push({
342
+ caller,
343
+ callee: node.head,
344
+ line: node.startLine,
345
+ });
346
+ }
347
+
348
+ // Recurse into children
349
+ for (const child of node.children) {
350
+ this.extractCalls(child, caller, calls);
351
+ }
352
+ } else if ('type' in node && node.type === 'group') {
353
+ // Recurse into group children
354
+ for (const child of node.children) {
355
+ this.extractCalls(child, caller, calls);
356
+ }
357
+ }
358
+ // Leaf nodes don't contain calls
359
+ }
360
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * ZIL Special Forms and Builtins
3
+ *
4
+ * These are NOT treated as function calls when building the call graph.
5
+ * Special forms are control flow, assignment, and predicates.
6
+ * Builtins are runtime primitives.
7
+ */
8
+
9
+ /**
10
+ * Special forms - language-level constructs, not function calls
11
+ */
12
+ export const ZIL_SPECIAL_FORMS = new Set([
13
+ // Conditionals
14
+ 'COND',
15
+ 'AND',
16
+ 'OR',
17
+ 'NOT',
18
+ 'IF',
19
+ 'ELSE',
20
+
21
+ // Assignment
22
+ 'SET',
23
+ 'SETG',
24
+ 'BIND',
25
+ 'PROG',
26
+
27
+ // Loops
28
+ 'REPEAT',
29
+ 'DO',
30
+ 'MAP',
31
+ 'MAPF',
32
+ 'MAPR',
33
+ 'MAPRET',
34
+ 'MAPLEAVE',
35
+
36
+ // Output
37
+ 'TELL',
38
+ 'PRINT',
39
+ 'PRINTN',
40
+ 'PRINTD',
41
+ 'PRINTC',
42
+ 'PRINTR',
43
+ 'CRLF',
44
+
45
+ // Control flow
46
+ 'RETURN',
47
+ 'AGAIN',
48
+ 'RTRUE',
49
+ 'RFALSE',
50
+ 'QUIT',
51
+
52
+ // Predicates (end with ?)
53
+ 'EQUAL?',
54
+ 'ZERO?',
55
+ 'LESS?',
56
+ 'GRTR?',
57
+ 'FSET?',
58
+ 'IN?',
59
+ 'VERB?',
60
+ 'PRSO?',
61
+ 'PRSI?',
62
+ 'HELD?',
63
+ 'HERE?',
64
+ 'ACCESSIBLE?',
65
+ 'VISIBLE?',
66
+ 'FIRST?',
67
+ 'NEXT?',
68
+ 'PROB?',
69
+ 'RANDOM',
70
+
71
+ // Property/flag manipulation
72
+ 'FSET',
73
+ 'FCLEAR',
74
+ 'GETP',
75
+ 'PUTP',
76
+ 'GETPT',
77
+ 'PTSIZE',
78
+
79
+ // Object manipulation
80
+ 'MOVE',
81
+ 'REMOVE',
82
+ 'LOC',
83
+ 'FIRST',
84
+ 'NEXT',
85
+
86
+ // Arithmetic
87
+ 'ADD',
88
+ 'SUB',
89
+ 'MUL',
90
+ 'DIV',
91
+ 'MOD',
92
+ 'BAND',
93
+ 'BOR',
94
+ 'BCOM',
95
+ 'LSH',
96
+
97
+ // Table operations
98
+ 'GET',
99
+ 'PUT',
100
+ 'GETB',
101
+ 'PUTB',
102
+ 'TABLE',
103
+ 'ITABLE',
104
+ 'LTABLE',
105
+ 'PTABLE',
106
+
107
+ // Stack operations
108
+ 'PUSH',
109
+ 'POP',
110
+ 'FSTACK',
111
+
112
+ // Input
113
+ 'READ',
114
+ 'INPUT',
115
+ 'READLINE',
116
+
117
+ // Definition forms (handled separately for symbol extraction)
118
+ 'ROUTINE',
119
+ 'OBJECT',
120
+ 'ROOM',
121
+ 'GLOBAL',
122
+ 'CONSTANT',
123
+ 'SYNTAX',
124
+ 'INSERT-FILE',
125
+
126
+ // Misc builtins
127
+ 'VERSION?',
128
+ 'ASCII',
129
+ 'USL',
130
+ 'APPLY',
131
+ 'EVAL',
132
+ 'FORM',
133
+ 'REST',
134
+ 'LENGTH',
135
+ 'NTH',
136
+ 'ZGET',
137
+ 'ZPUT',
138
+ 'ZWSTR',
139
+ 'DIROUT',
140
+ 'DIRIN',
141
+ 'BUFOUT',
142
+ 'HLIGHT',
143
+ 'COLOR',
144
+ 'FONT',
145
+ 'SPLIT',
146
+ 'SCREEN',
147
+ 'WINGET',
148
+ 'WINPUT',
149
+ 'WINATTR',
150
+ 'PICINF',
151
+ 'DISPLAY',
152
+ 'DCLEAR',
153
+ 'SOUND',
154
+ 'INTBL?',
155
+ 'CATCH',
156
+ 'THROW',
157
+ 'LEGAL?',
158
+ 'COPYT',
159
+ 'VALUE',
160
+ 'GASSIGNED?',
161
+ 'ASSIGNED?',
162
+ 'DEFINE',
163
+ 'DEFMAC',
164
+ ]);
165
+
166
+ /**
167
+ * Check if a form head is a special form or builtin
168
+ */
169
+ export function isSpecialForm(name: string): boolean {
170
+ return ZIL_SPECIAL_FORMS.has(name.toUpperCase());
171
+ }
172
+
173
+ /**
174
+ * Definition forms that create symbols
175
+ */
176
+ export const ZIL_DEFINITION_FORMS = new Set([
177
+ 'ROUTINE',
178
+ 'OBJECT',
179
+ 'ROOM',
180
+ 'GLOBAL',
181
+ 'CONSTANT',
182
+ 'SYNTAX',
183
+ 'VERB',
184
+ 'DEFINE',
185
+ 'DEFMAC',
186
+ ]);
187
+
188
+ /**
189
+ * Check if a form head is a definition form
190
+ */
191
+ export function isDefinitionForm(name: string): boolean {
192
+ return ZIL_DEFINITION_FORMS.has(name.toUpperCase());
193
+ }
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createSyncCommand } from './sync.js';
3
+ import type { GlobalOptions } from '../program.js';
4
+
5
+ describe('createSyncCommand', () => {
6
+ function createTestOptions(): GlobalOptions {
7
+ return {
8
+ dataDir: '/tmp/test-data',
9
+ projectRoot: '/tmp/test-project',
10
+ format: 'table',
11
+ };
12
+ }
13
+
14
+ it('creates sync command with correct name and description', () => {
15
+ const cmd = createSyncCommand(createTestOptions);
16
+ expect(cmd.name()).toBe('sync');
17
+ expect(cmd.description()).toContain('Sync');
18
+ });
19
+
20
+ it('has --dry-run option', () => {
21
+ const cmd = createSyncCommand(createTestOptions);
22
+ const options = cmd.options;
23
+ const dryRunOpt = options.find((o) => o.long === '--dry-run');
24
+ expect(dryRunOpt).toBeDefined();
25
+ });
26
+
27
+ it('has --prune option', () => {
28
+ const cmd = createSyncCommand(createTestOptions);
29
+ const options = cmd.options;
30
+ const pruneOpt = options.find((o) => o.long === '--prune');
31
+ expect(pruneOpt).toBeDefined();
32
+ });
33
+
34
+ it('has --reindex option', () => {
35
+ const cmd = createSyncCommand(createTestOptions);
36
+ const options = cmd.options;
37
+ const reindexOpt = options.find((o) => o.long === '--reindex');
38
+ expect(reindexOpt).toBeDefined();
39
+ });
40
+
41
+ it('has correct option descriptions', () => {
42
+ const cmd = createSyncCommand(createTestOptions);
43
+ const options = cmd.options;
44
+
45
+ const dryRunOpt = options.find((o) => o.long === '--dry-run');
46
+ expect(dryRunOpt?.description).toContain('without making changes');
47
+
48
+ const pruneOpt = options.find((o) => o.long === '--prune');
49
+ expect(pruneOpt?.description).toContain('Remove');
50
+
51
+ const reindexOpt = options.find((o) => o.long === '--reindex');
52
+ expect(reindexOpt?.description).toContain('index');
53
+ });
54
+ });