trellis 1.0.8 → 2.0.6

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +564 -83
  3. package/bin/trellis.mjs +2 -0
  4. package/dist/cli/index.js +4718 -0
  5. package/dist/core/index.js +12 -0
  6. package/dist/decisions/index.js +19 -0
  7. package/dist/embeddings/index.js +43 -0
  8. package/dist/index-1j1anhmr.js +4038 -0
  9. package/dist/index-3s0eak0p.js +1556 -0
  10. package/dist/index-8pce39mh.js +272 -0
  11. package/dist/index-a76rekgs.js +67 -0
  12. package/dist/index-cy9k1g6v.js +684 -0
  13. package/dist/index-fd4e26s4.js +69 -0
  14. package/dist/{store/eav-store.js → index-gkvhzm9f.js} +4 -6
  15. package/dist/index-gnw8d7d6.js +51 -0
  16. package/dist/index-vkpkfwhq.js +817 -0
  17. package/dist/index.js +118 -2876
  18. package/dist/links/index.js +55 -0
  19. package/dist/transformers-m9je15kg.js +32491 -0
  20. package/dist/vcs/index.js +110 -0
  21. package/logo.png +0 -0
  22. package/logo.svg +9 -0
  23. package/package.json +79 -76
  24. package/src/cli/index.ts +2340 -0
  25. package/src/core/index.ts +35 -0
  26. package/src/core/kernel/middleware.ts +44 -0
  27. package/src/core/persist/backend.ts +64 -0
  28. package/src/core/store/eav-store.ts +467 -0
  29. package/src/decisions/auto-capture.ts +136 -0
  30. package/src/decisions/hooks.ts +163 -0
  31. package/src/decisions/index.ts +261 -0
  32. package/src/decisions/types.ts +103 -0
  33. package/src/embeddings/chunker.ts +327 -0
  34. package/src/embeddings/index.ts +41 -0
  35. package/src/embeddings/model.ts +95 -0
  36. package/src/embeddings/search.ts +305 -0
  37. package/src/embeddings/store.ts +313 -0
  38. package/src/embeddings/types.ts +85 -0
  39. package/src/engine.ts +1083 -0
  40. package/src/garden/cluster.ts +330 -0
  41. package/src/garden/garden.ts +306 -0
  42. package/src/garden/index.ts +29 -0
  43. package/src/git/git-exporter.ts +286 -0
  44. package/src/git/git-importer.ts +329 -0
  45. package/src/git/git-reader.ts +189 -0
  46. package/src/git/index.ts +22 -0
  47. package/src/identity/governance.ts +211 -0
  48. package/src/identity/identity.ts +224 -0
  49. package/src/identity/index.ts +30 -0
  50. package/src/identity/signing-middleware.ts +97 -0
  51. package/src/index.ts +20 -0
  52. package/src/links/index.ts +49 -0
  53. package/src/links/lifecycle.ts +400 -0
  54. package/src/links/parser.ts +484 -0
  55. package/src/links/ref-index.ts +186 -0
  56. package/src/links/resolver.ts +314 -0
  57. package/src/links/types.ts +108 -0
  58. package/src/mcp/index.ts +22 -0
  59. package/src/mcp/server.ts +1278 -0
  60. package/src/semantic/csharp-parser.ts +493 -0
  61. package/src/semantic/go-parser.ts +585 -0
  62. package/src/semantic/index.ts +34 -0
  63. package/src/semantic/java-parser.ts +456 -0
  64. package/src/semantic/python-parser.ts +659 -0
  65. package/src/semantic/ruby-parser.ts +446 -0
  66. package/src/semantic/rust-parser.ts +784 -0
  67. package/src/semantic/semantic-merge.ts +210 -0
  68. package/src/semantic/ts-parser.ts +681 -0
  69. package/src/semantic/types.ts +175 -0
  70. package/src/sync/index.ts +32 -0
  71. package/src/sync/memory-transport.ts +66 -0
  72. package/src/sync/reconciler.ts +237 -0
  73. package/src/sync/sync-engine.ts +258 -0
  74. package/src/sync/types.ts +104 -0
  75. package/src/vcs/blob-store.ts +124 -0
  76. package/src/vcs/branch.ts +150 -0
  77. package/src/vcs/checkpoint.ts +64 -0
  78. package/src/vcs/decompose.ts +469 -0
  79. package/src/vcs/diff.ts +409 -0
  80. package/src/vcs/engine-context.ts +26 -0
  81. package/src/vcs/index.ts +23 -0
  82. package/src/vcs/issue.ts +800 -0
  83. package/src/vcs/merge.ts +425 -0
  84. package/src/vcs/milestone.ts +124 -0
  85. package/src/vcs/ops.ts +59 -0
  86. package/src/vcs/types.ts +213 -0
  87. package/src/vcs/vcs-middleware.ts +81 -0
  88. package/src/watcher/fs-watcher.ts +217 -0
  89. package/src/watcher/index.ts +9 -0
  90. package/src/watcher/ingestion.ts +116 -0
  91. package/dist/ai/index.js +0 -688
  92. package/dist/cli/server.js +0 -3321
  93. package/dist/cli/tql.js +0 -5282
  94. package/dist/client/tql-client.js +0 -108
  95. package/dist/graph/index.js +0 -2248
  96. package/dist/kernel/logic-middleware.js +0 -179
  97. package/dist/kernel/middleware.js +0 -0
  98. package/dist/kernel/operations.js +0 -32
  99. package/dist/kernel/schema-middleware.js +0 -34
  100. package/dist/kernel/security-middleware.js +0 -53
  101. package/dist/kernel/trellis-kernel.js +0 -2239
  102. package/dist/kernel/workspace.js +0 -91
  103. package/dist/persist/backend.js +0 -0
  104. package/dist/persist/sqlite-backend.js +0 -123
  105. package/dist/query/index.js +0 -1643
  106. package/dist/server/index.js +0 -3309
  107. package/dist/workflows/index.js +0 -3160
@@ -0,0 +1,446 @@
1
+ /**
2
+ * Ruby Parser Adapter
3
+ *
4
+ * Tier 1 regex-based parser for Ruby source files.
5
+ * Extracts classes, modules, methods, constants, attributes,
6
+ * require/include statements, and module_function exports.
7
+ *
8
+ * @see TRL-8
9
+ */
10
+
11
+ import type {
12
+ ParserAdapter,
13
+ ParseResult,
14
+ ASTEntity,
15
+ ASTEntityKind,
16
+ ImportRelation,
17
+ ExportRelation,
18
+ SemanticPatch,
19
+ } from './types.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Parser Adapter
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export const rubyParser: ParserAdapter = {
26
+ languages: ['ruby'],
27
+
28
+ parse(content: string, filePath: string): ParseResult {
29
+ const fileEntityId = `file:${filePath}`;
30
+
31
+ return {
32
+ fileEntityId,
33
+ filePath,
34
+ language: 'ruby',
35
+ declarations: extractDeclarations(content, filePath),
36
+ imports: extractImports(content),
37
+ exports: extractExports(content),
38
+ };
39
+ },
40
+
41
+ diff(oldResult: ParseResult, newResult: ParseResult): SemanticPatch[] {
42
+ return computeSemanticDiff(oldResult, newResult);
43
+ },
44
+ };
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Declaration extraction
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function extractDeclarations(content: string, filePath: string): ASTEntity[] {
51
+ const declarations: ASTEntity[] = [];
52
+ const lines = content.split('\n');
53
+ const scopeStack: string[] = [];
54
+
55
+ let i = 0;
56
+ while (i < lines.length) {
57
+ const line = lines[i];
58
+ const trimmed = line.trim();
59
+
60
+ // Skip empty lines, comments
61
+ if (!trimmed || trimmed.startsWith('#')) {
62
+ i++;
63
+ continue;
64
+ }
65
+
66
+ // Class: class Name [< Parent]
67
+ let match = trimmed.match(/^class\s+([A-Z]\w*(?:::\w+)*)/);
68
+ if (match) {
69
+ const name = match[1];
70
+ scopeStack.push(name);
71
+ const result = extractEndBlock(name, 'ClassDef', lines, i, filePath, scopeStack);
72
+ result.entity.children = extractClassMembers(lines, i, result.endLine, name, filePath);
73
+ declarations.push(result.entity);
74
+ scopeStack.pop();
75
+ i = result.endLine + 1;
76
+ continue;
77
+ }
78
+
79
+ // Module: module Name
80
+ match = trimmed.match(/^module\s+([A-Z]\w*(?:::\w+)*)/);
81
+ if (match) {
82
+ const name = match[1];
83
+ scopeStack.push(name);
84
+ const result = extractEndBlock(name, 'ClassDef', lines, i, filePath, scopeStack);
85
+ result.entity.children = extractClassMembers(lines, i, result.endLine, name, filePath);
86
+ declarations.push(result.entity);
87
+ scopeStack.pop();
88
+ i = result.endLine + 1;
89
+ continue;
90
+ }
91
+
92
+ // Top-level method: def name or def self.name
93
+ match = trimmed.match(/^def\s+(self\.)?(\w+[?!=]?)/);
94
+ if (match) {
95
+ const name = match[1] ? `self.${match[2]}` : match[2];
96
+ const result = extractEndBlock(name, 'FunctionDef', lines, i, filePath, scopeStack);
97
+ declarations.push(result.entity);
98
+ i = result.endLine + 1;
99
+ continue;
100
+ }
101
+
102
+ // Constant: NAME = value (top-level, all caps or CamelCase starting with uppercase)
103
+ match = trimmed.match(/^([A-Z][A-Z0-9_]*)\s*=/);
104
+ if (match) {
105
+ declarations.push({
106
+ id: makeEntityId(filePath, 'VariableDecl', match[1]),
107
+ kind: 'VariableDecl',
108
+ name: match[1],
109
+ scopePath: match[1],
110
+ span: [0, 0],
111
+ rawText: trimmed,
112
+ signature: normalizeSignature(trimmed),
113
+ children: [],
114
+ });
115
+ i++;
116
+ continue;
117
+ }
118
+
119
+ i++;
120
+ }
121
+
122
+ return declarations;
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // Block extraction
127
+ // ---------------------------------------------------------------------------
128
+
129
+ interface ExtractionResult {
130
+ entity: ASTEntity;
131
+ endLine: number;
132
+ }
133
+
134
+ function extractEndBlock(
135
+ name: string,
136
+ kind: ASTEntityKind,
137
+ lines: string[],
138
+ startLine: number,
139
+ filePath: string,
140
+ scopeStack: string[],
141
+ ): ExtractionResult {
142
+ let depth = 0;
143
+ let endLine = startLine;
144
+
145
+ for (let i = startLine; i < lines.length; i++) {
146
+ const trimmed = lines[i].trim();
147
+
148
+ // Count block openers
149
+ if (isBlockOpener(trimmed)) depth++;
150
+ // Count block closers
151
+ if (trimmed === 'end' || trimmed.startsWith('end ') || trimmed.startsWith('end#')) depth--;
152
+
153
+ if (depth <= 0 && i > startLine) { endLine = i; break; }
154
+ endLine = i;
155
+ }
156
+
157
+ const rawText = lines.slice(startLine, endLine + 1).join('\n');
158
+ const startOffset = lines.slice(0, startLine).join('\n').length + (startLine > 0 ? 1 : 0);
159
+
160
+ return {
161
+ entity: {
162
+ id: makeEntityId(filePath, kind, name),
163
+ kind,
164
+ name,
165
+ scopePath: name,
166
+ span: [startOffset, startOffset + rawText.length],
167
+ rawText,
168
+ signature: normalizeSignature(rawText),
169
+ children: [],
170
+ },
171
+ endLine,
172
+ };
173
+ }
174
+
175
+ function isBlockOpener(trimmed: string): boolean {
176
+ // Match: class, module, def, do, if/unless/while/until/for/case/begin (as statements, not modifiers)
177
+ if (/^(class|module|def|do|begin|case)\b/.test(trimmed)) return true;
178
+ if (/^(if|unless|while|until|for)\b/.test(trimmed) && !trimmed.includes(' then ')) return true;
179
+ return false;
180
+ }
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Class member extraction
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function extractClassMembers(
187
+ lines: string[],
188
+ startLine: number,
189
+ endLine: number,
190
+ parentName: string,
191
+ filePath: string,
192
+ ): ASTEntity[] {
193
+ const children: ASTEntity[] = [];
194
+ let depth = 0;
195
+
196
+ for (let i = startLine; i <= endLine; i++) {
197
+ const trimmed = lines[i].trim();
198
+ const depthBefore = depth;
199
+
200
+ if (isBlockOpener(trimmed)) depth++;
201
+ if (trimmed === 'end' || trimmed.startsWith('end ') || trimmed.startsWith('end#')) depth--;
202
+
203
+ // Only look at depth 1 (direct children)
204
+ if (depthBefore === 1) {
205
+ // Method: def name or def self.name
206
+ const match = trimmed.match(/^def\s+(self\.)?(\w+[?!=]?)/);
207
+ if (match) {
208
+ const methodName = match[1] ? `self.${match[2]}` : match[2];
209
+ const kind: ASTEntityKind = methodName === 'initialize' ? 'Constructor' : 'MethodDef';
210
+ children.push({
211
+ id: makeEntityId(filePath, kind, `${parentName}.${methodName}`),
212
+ kind,
213
+ name: methodName,
214
+ scopePath: `${parentName}.${methodName}`,
215
+ span: [0, 0],
216
+ rawText: trimmed,
217
+ signature: normalizeSignature(trimmed),
218
+ children: [],
219
+ });
220
+ }
221
+
222
+ // attr_accessor / attr_reader / attr_writer
223
+ const attrMatch = trimmed.match(/^attr_(accessor|reader|writer)\s+(.*)/);
224
+ if (attrMatch) {
225
+ const attrs = attrMatch[2].split(',').map(a => a.trim().replace(/^:/, ''));
226
+ for (const attr of attrs) {
227
+ if (attr) {
228
+ children.push({
229
+ id: makeEntityId(filePath, 'PropertyDef', `${parentName}.${attr}`),
230
+ kind: 'PropertyDef',
231
+ name: attr,
232
+ scopePath: `${parentName}.${attr}`,
233
+ span: [0, 0],
234
+ rawText: trimmed,
235
+ signature: normalizeSignature(trimmed),
236
+ children: [],
237
+ });
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return children;
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // Import extraction
248
+ // ---------------------------------------------------------------------------
249
+
250
+ function extractImports(content: string): ImportRelation[] {
251
+ const imports: ImportRelation[] = [];
252
+ const lines = content.split('\n');
253
+
254
+ for (const line of lines) {
255
+ const trimmed = line.trim();
256
+
257
+ // require 'name' or require "name"
258
+ let match = trimmed.match(/^require\s+['"]([^'"]+)['"]/);
259
+ if (match) {
260
+ imports.push({
261
+ source: match[1],
262
+ specifiers: [match[1].split('/').pop()!],
263
+ isDefault: true,
264
+ isNamespace: false,
265
+ rawText: trimmed,
266
+ span: [0, trimmed.length],
267
+ });
268
+ continue;
269
+ }
270
+
271
+ // require_relative 'name'
272
+ match = trimmed.match(/^require_relative\s+['"]([^'"]+)['"]/);
273
+ if (match) {
274
+ imports.push({
275
+ source: match[1],
276
+ specifiers: [match[1].split('/').pop()!],
277
+ isDefault: true,
278
+ isNamespace: false,
279
+ rawText: trimmed,
280
+ span: [0, trimmed.length],
281
+ });
282
+ continue;
283
+ }
284
+
285
+ // include ModuleName / extend ModuleName / prepend ModuleName
286
+ match = trimmed.match(/^(include|extend|prepend)\s+([A-Z]\w*(?:::\w+)*)/);
287
+ if (match) {
288
+ imports.push({
289
+ source: match[2],
290
+ specifiers: [match[2]],
291
+ isDefault: false,
292
+ isNamespace: true,
293
+ rawText: trimmed,
294
+ span: [0, trimmed.length],
295
+ });
296
+ }
297
+ }
298
+
299
+ return imports;
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // Export extraction
304
+ // ---------------------------------------------------------------------------
305
+
306
+ function extractExports(content: string): ExportRelation[] {
307
+ const exports: ExportRelation[] = [];
308
+ const lines = content.split('\n');
309
+
310
+ for (const line of lines) {
311
+ const trimmed = line.trim();
312
+
313
+ // module_function :name
314
+ const match = trimmed.match(/^module_function\s+:(\w+)/);
315
+ if (match) {
316
+ exports.push({
317
+ name: match[1],
318
+ isDefault: false,
319
+ rawText: trimmed,
320
+ span: [0, 0],
321
+ });
322
+ }
323
+
324
+ // public :name (explicit public declaration)
325
+ const pubMatch = trimmed.match(/^public\s+:(\w+)/);
326
+ if (pubMatch) {
327
+ exports.push({
328
+ name: pubMatch[1],
329
+ isDefault: false,
330
+ rawText: trimmed,
331
+ span: [0, 0],
332
+ });
333
+ }
334
+ }
335
+
336
+ return exports;
337
+ }
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Semantic diff
341
+ // ---------------------------------------------------------------------------
342
+
343
+ function computeSemanticDiff(
344
+ oldResult: ParseResult,
345
+ newResult: ParseResult,
346
+ ): SemanticPatch[] {
347
+ const patches: SemanticPatch[] = [];
348
+ const fileId = newResult.fileEntityId;
349
+
350
+ const oldDecls = new Map(oldResult.declarations.map(d => [d.id, d]));
351
+ const newDecls = new Map(newResult.declarations.map(d => [d.id, d]));
352
+
353
+ for (const [id, entity] of newDecls) {
354
+ if (!oldDecls.has(id)) {
355
+ const oldEntity = findRenamedEntity(entity, oldResult.declarations, newDecls);
356
+ if (oldEntity) {
357
+ patches.push({ kind: 'symbolRename', entityId: oldEntity.id, oldName: oldEntity.name, newName: entity.name });
358
+ } else {
359
+ patches.push({ kind: 'symbolAdd', entity });
360
+ }
361
+ }
362
+ }
363
+
364
+ for (const [id, entity] of oldDecls) {
365
+ if (!newDecls.has(id)) {
366
+ const wasRenamed = findRenamedEntity(entity, newResult.declarations, oldDecls);
367
+ if (!wasRenamed) {
368
+ patches.push({ kind: 'symbolRemove', entityId: id, entityName: entity.name });
369
+ }
370
+ }
371
+ }
372
+
373
+ for (const [id, newEntity] of newDecls) {
374
+ const oldEntity = oldDecls.get(id);
375
+ if (oldEntity && oldEntity.signature !== newEntity.signature) {
376
+ patches.push({
377
+ kind: 'symbolModify', entityId: id, entityName: newEntity.name,
378
+ oldSignature: oldEntity.signature, newSignature: newEntity.signature,
379
+ oldRawText: oldEntity.rawText, newRawText: newEntity.rawText,
380
+ });
381
+ }
382
+ }
383
+
384
+ const oldImports = new Map(oldResult.imports.map(imp => [imp.source, imp]));
385
+ const newImports = new Map(newResult.imports.map(imp => [imp.source, imp]));
386
+
387
+ for (const [source, imp] of newImports) {
388
+ const oldImp = oldImports.get(source);
389
+ if (!oldImp) {
390
+ patches.push({ kind: 'importAdd', fileId, source, specifiers: imp.specifiers, rawText: imp.rawText });
391
+ } else if (JSON.stringify(oldImp.specifiers.sort()) !== JSON.stringify(imp.specifiers.sort())) {
392
+ patches.push({ kind: 'importModify', fileId, source, oldSpecifiers: oldImp.specifiers, newSpecifiers: imp.specifiers });
393
+ }
394
+ }
395
+ for (const [source] of oldImports) {
396
+ if (!newImports.has(source)) {
397
+ patches.push({ kind: 'importRemove', fileId, source });
398
+ }
399
+ }
400
+
401
+ const oldExports = new Map(oldResult.exports.map(exp => [exp.name, exp]));
402
+ const newExports = new Map(newResult.exports.map(exp => [exp.name, exp]));
403
+ for (const [name, exp] of newExports) {
404
+ if (!oldExports.has(name)) {
405
+ patches.push({ kind: 'exportAdd', fileId, name, rawText: exp.rawText });
406
+ }
407
+ }
408
+ for (const [name] of oldExports) {
409
+ if (!newExports.has(name)) {
410
+ patches.push({ kind: 'exportRemove', fileId, name });
411
+ }
412
+ }
413
+
414
+ return patches;
415
+ }
416
+
417
+ // ---------------------------------------------------------------------------
418
+ // Helpers
419
+ // ---------------------------------------------------------------------------
420
+
421
+ function findRenamedEntity(
422
+ entity: ASTEntity,
423
+ candidates: ASTEntity[],
424
+ existingIds: Map<string, ASTEntity>,
425
+ ): ASTEntity | null {
426
+ for (const candidate of candidates) {
427
+ if (candidate.kind !== entity.kind) continue;
428
+ if (candidate.name === entity.name) continue;
429
+ if (existingIds.has(candidate.id)) continue;
430
+ const normalizedOld = candidate.signature.replace(new RegExp(candidate.name, 'g'), '___');
431
+ const normalizedNew = entity.signature.replace(new RegExp(entity.name, 'g'), '___');
432
+ if (normalizedOld === normalizedNew) return candidate;
433
+ }
434
+ return null;
435
+ }
436
+
437
+ function makeEntityId(filePath: string, kind: string, name: string): string {
438
+ return `${kind}:${filePath}:${name}`;
439
+ }
440
+
441
+ function normalizeSignature(text: string): string {
442
+ return text
443
+ .replace(/#[^\n]*/g, '')
444
+ .replace(/\s+/g, ' ')
445
+ .trim();
446
+ }