trellis 2.0.8 → 2.0.13

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 (42) hide show
  1. package/README.md +279 -116
  2. package/dist/cli/index.js +655 -4
  3. package/dist/core/index.js +471 -2
  4. package/dist/embeddings/index.js +5 -1
  5. package/dist/{index-s603ev6w.js → index-5b01h414.js} +1 -1
  6. package/dist/index-5m0g9r0y.js +1100 -0
  7. package/dist/{index-zf6htvnm.js → index-7gvjxt27.js} +166 -2
  8. package/dist/index-hybgxe40.js +1174 -0
  9. package/dist/index.js +7 -2
  10. package/dist/transformers.node-bx3q9d7k.js +33130 -0
  11. package/package.json +9 -4
  12. package/src/cli/index.ts +939 -0
  13. package/src/core/agents/harness.ts +380 -0
  14. package/src/core/agents/index.ts +18 -0
  15. package/src/core/agents/types.ts +90 -0
  16. package/src/core/index.ts +85 -2
  17. package/src/core/kernel/trellis-kernel.ts +593 -0
  18. package/src/core/ontology/builtins.ts +248 -0
  19. package/src/core/ontology/index.ts +34 -0
  20. package/src/core/ontology/registry.ts +209 -0
  21. package/src/core/ontology/types.ts +124 -0
  22. package/src/core/ontology/validator.ts +382 -0
  23. package/src/core/persist/backend.ts +10 -0
  24. package/src/core/persist/sqlite-backend.ts +298 -0
  25. package/src/core/plugins/index.ts +17 -0
  26. package/src/core/plugins/registry.ts +322 -0
  27. package/src/core/plugins/types.ts +126 -0
  28. package/src/core/query/datalog.ts +188 -0
  29. package/src/core/query/engine.ts +370 -0
  30. package/src/core/query/index.ts +34 -0
  31. package/src/core/query/parser.ts +481 -0
  32. package/src/core/query/types.ts +200 -0
  33. package/src/embeddings/auto-embed.ts +248 -0
  34. package/src/embeddings/index.ts +7 -0
  35. package/src/embeddings/model.ts +21 -4
  36. package/src/embeddings/types.ts +8 -1
  37. package/src/index.ts +9 -0
  38. package/src/sync/http-transport.ts +144 -0
  39. package/src/sync/index.ts +11 -0
  40. package/src/sync/multi-repo.ts +200 -0
  41. package/src/sync/ws-transport.ts +145 -0
  42. package/dist/index-5bhe57y9.js +0 -326
@@ -0,0 +1,322 @@
1
+ /**
2
+ * Plugin Registry — Load, register, and manage plugins.
3
+ *
4
+ * Handles plugin lifecycle, dependency resolution, event dispatching,
5
+ * and workspace configuration.
6
+ *
7
+ * @module trellis/core/plugins
8
+ */
9
+
10
+ import type { TrellisKernel } from '../kernel/trellis-kernel.js';
11
+ import type { OntologyRegistry } from '../ontology/registry.js';
12
+ import type { QueryEngine } from '../query/engine.js';
13
+ import type {
14
+ PluginDef,
15
+ PluginContext,
16
+ PluginManifest,
17
+ EventCallback,
18
+ EventHandler,
19
+ WorkspaceConfig,
20
+ } from './types.js';
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Event Bus
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export class EventBus {
27
+ private handlers: Map<string, Set<EventCallback>> = new Map();
28
+
29
+ on(event: string, handler: EventCallback): void {
30
+ const set = this.handlers.get(event) ?? new Set();
31
+ set.add(handler);
32
+ this.handlers.set(event, set);
33
+ }
34
+
35
+ off(event: string, handler: EventCallback): void {
36
+ const set = this.handlers.get(event);
37
+ if (set) {
38
+ set.delete(handler);
39
+ if (set.size === 0) this.handlers.delete(event);
40
+ }
41
+ }
42
+
43
+ async emit(event: string, data?: unknown): Promise<void> {
44
+ // Exact match
45
+ const exact = this.handlers.get(event);
46
+ if (exact) {
47
+ for (const h of exact) await h(data);
48
+ }
49
+
50
+ // Wildcard match (e.g. "op:*" matches "op:applied")
51
+ for (const [pattern, handlers] of this.handlers) {
52
+ if (pattern === event) continue; // Already handled
53
+ if (pattern.endsWith('*') && event.startsWith(pattern.slice(0, -1))) {
54
+ for (const h of handlers) await h(data);
55
+ }
56
+ }
57
+ }
58
+
59
+ listEvents(): string[] {
60
+ return [...this.handlers.keys()];
61
+ }
62
+
63
+ clear(): void {
64
+ this.handlers.clear();
65
+ }
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Plugin Registry
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export class PluginRegistry {
73
+ private plugins: Map<string, { def: PluginDef; loaded: boolean }> = new Map();
74
+ private eventBus: EventBus = new EventBus();
75
+ private workspaceConfig: WorkspaceConfig = {};
76
+ private logs: Array<{ pluginId: string; message: string; timestamp: string }> = [];
77
+
78
+ /**
79
+ * Register a plugin definition. Does not load it yet.
80
+ */
81
+ register(def: PluginDef): void {
82
+ if (this.plugins.has(def.id)) {
83
+ throw new Error(`Plugin "${def.id}" is already registered.`);
84
+ }
85
+ this.plugins.set(def.id, { def, loaded: false });
86
+ }
87
+
88
+ /**
89
+ * Unregister a plugin. Unloads it first if loaded.
90
+ */
91
+ async unregister(id: string): Promise<void> {
92
+ const entry = this.plugins.get(id);
93
+ if (!entry) return;
94
+ if (entry.loaded) await this.unload(id);
95
+ this.plugins.delete(id);
96
+ }
97
+
98
+ /**
99
+ * Load a plugin (call onLoad, register middleware/ontologies/rules/events).
100
+ * Resolves dependencies first.
101
+ */
102
+ async load(
103
+ id: string,
104
+ kernel?: TrellisKernel,
105
+ ontologyRegistry?: OntologyRegistry,
106
+ queryEngine?: QueryEngine,
107
+ ): Promise<void> {
108
+ const entry = this.plugins.get(id);
109
+ if (!entry) throw new Error(`Plugin "${id}" is not registered.`);
110
+ if (entry.loaded) return;
111
+
112
+ // Check dependencies
113
+ if (entry.def.dependencies) {
114
+ for (const dep of entry.def.dependencies) {
115
+ const depEntry = this.plugins.get(dep);
116
+ if (!depEntry) {
117
+ throw new Error(`Plugin "${id}" depends on "${dep}" which is not registered.`);
118
+ }
119
+ if (!depEntry.loaded) {
120
+ await this.load(dep, kernel, ontologyRegistry, queryEngine);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Register middleware
126
+ if (entry.def.middleware && kernel) {
127
+ for (const mw of entry.def.middleware) {
128
+ kernel.addMiddleware(mw);
129
+ }
130
+ }
131
+
132
+ // Register ontologies
133
+ if (entry.def.ontologies && ontologyRegistry) {
134
+ for (const schema of entry.def.ontologies) {
135
+ try { ontologyRegistry.register(schema); } catch {}
136
+ }
137
+ }
138
+
139
+ // Register rules
140
+ if (entry.def.rules && queryEngine) {
141
+ for (const rule of entry.def.rules) {
142
+ queryEngine.addRule(rule);
143
+ }
144
+ }
145
+
146
+ // Register event handlers
147
+ if (entry.def.eventHandlers) {
148
+ for (const eh of entry.def.eventHandlers) {
149
+ this.eventBus.on(eh.event, eh.handler);
150
+ }
151
+ }
152
+
153
+ // Build plugin context
154
+ const ctx = this._buildContext(id);
155
+
156
+ // Call onLoad
157
+ if (entry.def.onLoad) {
158
+ await entry.def.onLoad(ctx);
159
+ }
160
+
161
+ entry.loaded = true;
162
+ await this.eventBus.emit('plugin:loaded', { pluginId: id });
163
+ }
164
+
165
+ /**
166
+ * Unload a plugin (call onUnload, remove middleware/events).
167
+ */
168
+ async unload(id: string): Promise<void> {
169
+ const entry = this.plugins.get(id);
170
+ if (!entry || !entry.loaded) return;
171
+
172
+ const ctx = this._buildContext(id);
173
+ if (entry.def.onUnload) {
174
+ await entry.def.onUnload(ctx);
175
+ }
176
+
177
+ // Remove event handlers
178
+ if (entry.def.eventHandlers) {
179
+ for (const eh of entry.def.eventHandlers) {
180
+ this.eventBus.off(eh.event, eh.handler);
181
+ }
182
+ }
183
+
184
+ entry.loaded = false;
185
+ await this.eventBus.emit('plugin:unloaded', { pluginId: id });
186
+ }
187
+
188
+ /**
189
+ * Load all registered plugins in dependency order.
190
+ */
191
+ async loadAll(
192
+ kernel?: TrellisKernel,
193
+ ontologyRegistry?: OntologyRegistry,
194
+ queryEngine?: QueryEngine,
195
+ ): Promise<void> {
196
+ const order = this._resolveDependencyOrder();
197
+ for (const id of order) {
198
+ await this.load(id, kernel, ontologyRegistry, queryEngine);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Unload all plugins in reverse order.
204
+ */
205
+ async unloadAll(): Promise<void> {
206
+ const order = this._resolveDependencyOrder().reverse();
207
+ for (const id of order) {
208
+ await this.unload(id);
209
+ }
210
+ }
211
+
212
+ // -------------------------------------------------------------------------
213
+ // Queries
214
+ // -------------------------------------------------------------------------
215
+
216
+ get(id: string): PluginDef | undefined {
217
+ return this.plugins.get(id)?.def;
218
+ }
219
+
220
+ isLoaded(id: string): boolean {
221
+ return this.plugins.get(id)?.loaded ?? false;
222
+ }
223
+
224
+ list(): Array<{ def: PluginDef; loaded: boolean }> {
225
+ return [...this.plugins.values()];
226
+ }
227
+
228
+ listLoaded(): PluginDef[] {
229
+ return [...this.plugins.values()]
230
+ .filter((e) => e.loaded)
231
+ .map((e) => e.def);
232
+ }
233
+
234
+ // -------------------------------------------------------------------------
235
+ // Event bus access
236
+ // -------------------------------------------------------------------------
237
+
238
+ getEventBus(): EventBus {
239
+ return this.eventBus;
240
+ }
241
+
242
+ async emit(event: string, data?: unknown): Promise<void> {
243
+ await this.eventBus.emit(event, data);
244
+ }
245
+
246
+ on(event: string, handler: EventCallback): void {
247
+ this.eventBus.on(event, handler);
248
+ }
249
+
250
+ // -------------------------------------------------------------------------
251
+ // Workspace config
252
+ // -------------------------------------------------------------------------
253
+
254
+ getWorkspaceConfig(): WorkspaceConfig {
255
+ return this.workspaceConfig;
256
+ }
257
+
258
+ setWorkspaceConfig(config: WorkspaceConfig): void {
259
+ this.workspaceConfig = config;
260
+ }
261
+
262
+ getConfigValue(key: string): unknown {
263
+ return this.workspaceConfig.settings?.[key];
264
+ }
265
+
266
+ setConfigValue(key: string, value: unknown): void {
267
+ if (!this.workspaceConfig.settings) this.workspaceConfig.settings = {};
268
+ this.workspaceConfig.settings[key] = value;
269
+ }
270
+
271
+ // -------------------------------------------------------------------------
272
+ // Logs
273
+ // -------------------------------------------------------------------------
274
+
275
+ getLogs(pluginId?: string): typeof this.logs {
276
+ if (pluginId) return this.logs.filter((l) => l.pluginId === pluginId);
277
+ return [...this.logs];
278
+ }
279
+
280
+ // -------------------------------------------------------------------------
281
+ // Internal
282
+ // -------------------------------------------------------------------------
283
+
284
+ private _buildContext(pluginId: string): PluginContext {
285
+ return {
286
+ pluginId,
287
+ on: (event, handler) => this.eventBus.on(event, handler),
288
+ emit: (event, data) => { this.eventBus.emit(event, data); },
289
+ getConfig: (key) => this.getConfigValue(key),
290
+ log: (message) => {
291
+ this.logs.push({ pluginId, message, timestamp: new Date().toISOString() });
292
+ },
293
+ };
294
+ }
295
+
296
+ private _resolveDependencyOrder(): string[] {
297
+ const visited = new Set<string>();
298
+ const order: string[] = [];
299
+
300
+ const visit = (id: string, stack: Set<string>) => {
301
+ if (visited.has(id)) return;
302
+ if (stack.has(id)) throw new Error(`Circular dependency detected: ${[...stack, id].join(' → ')}`);
303
+
304
+ stack.add(id);
305
+ const entry = this.plugins.get(id);
306
+ if (entry?.def.dependencies) {
307
+ for (const dep of entry.def.dependencies) {
308
+ visit(dep, stack);
309
+ }
310
+ }
311
+ stack.delete(id);
312
+ visited.add(id);
313
+ order.push(id);
314
+ };
315
+
316
+ for (const id of this.plugins.keys()) {
317
+ visit(id, new Set());
318
+ }
319
+
320
+ return order;
321
+ }
322
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Plugin System Types
3
+ *
4
+ * Defines the plugin interface, manifest format, lifecycle hooks,
5
+ * and event system for extensibility.
6
+ *
7
+ * @module trellis/core/plugins
8
+ */
9
+
10
+ import type { KernelMiddleware } from '../kernel/middleware.js';
11
+ import type { OntologySchema } from '../ontology/types.js';
12
+ import type { DatalogRule } from '../query/types.js';
13
+ import type { KernelOp } from '../persist/backend.js';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Plugin definition
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export interface PluginDef {
20
+ /** Unique plugin identifier (e.g. "trellis:security", "my-org:custom"). */
21
+ id: string;
22
+ /** Human-readable name. */
23
+ name: string;
24
+ /** Semantic version. */
25
+ version: string;
26
+ /** Description. */
27
+ description?: string;
28
+ /** Plugin dependencies (other plugin IDs). */
29
+ dependencies?: string[];
30
+
31
+ /** Kernel middleware provided by this plugin. */
32
+ middleware?: KernelMiddleware[];
33
+ /** Ontology schemas provided by this plugin. */
34
+ ontologies?: OntologySchema[];
35
+ /** Datalog rules provided by this plugin. */
36
+ rules?: DatalogRule[];
37
+ /** Event listeners provided by this plugin. */
38
+ eventHandlers?: EventHandler[];
39
+
40
+ /** Called when the plugin is loaded. */
41
+ onLoad?: (ctx: PluginContext) => void | Promise<void>;
42
+ /** Called when the plugin is unloaded. */
43
+ onUnload?: (ctx: PluginContext) => void | Promise<void>;
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Plugin context — what the plugin receives during lifecycle
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export interface PluginContext {
51
+ /** The plugin's own ID. */
52
+ pluginId: string;
53
+ /** Subscribe to events. */
54
+ on: (event: string, handler: EventCallback) => void;
55
+ /** Emit an event. */
56
+ emit: (event: string, data?: unknown) => void;
57
+ /** Get workspace config value. */
58
+ getConfig: (key: string) => unknown;
59
+ /** Log a message. */
60
+ log: (message: string) => void;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Event system
65
+ // ---------------------------------------------------------------------------
66
+
67
+ export type EventCallback = (data: unknown) => void | Promise<void>;
68
+
69
+ export interface EventHandler {
70
+ /** Event name pattern (e.g. "op:*", "entity:created", "milestone:created"). */
71
+ event: string;
72
+ /** Handler function. */
73
+ handler: EventCallback;
74
+ }
75
+
76
+ /** Well-known event names. */
77
+ export type WellKnownEvent =
78
+ | 'op:applied'
79
+ | 'entity:created'
80
+ | 'entity:updated'
81
+ | 'entity:deleted'
82
+ | 'link:added'
83
+ | 'link:removed'
84
+ | 'milestone:created'
85
+ | 'issue:created'
86
+ | 'issue:closed'
87
+ | 'plugin:loaded'
88
+ | 'plugin:unloaded';
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Workspace configuration
92
+ // ---------------------------------------------------------------------------
93
+
94
+ export interface WorkspaceConfig {
95
+ /** Active ontology IDs. */
96
+ ontologies?: string[];
97
+ /** Active plugin IDs. */
98
+ plugins?: string[];
99
+ /** Tracked file patterns (globs). */
100
+ trackedPaths?: string[];
101
+ /** Ignore patterns. */
102
+ ignorePaths?: string[];
103
+ /** Branch policies. */
104
+ branchPolicies?: Record<string, { linear?: boolean }>;
105
+ /** Embedding model override. */
106
+ embeddingModel?: string;
107
+ /** Snapshot threshold (ops between auto-snapshots). */
108
+ snapshotThreshold?: number;
109
+ /** Custom key-value settings. */
110
+ settings?: Record<string, unknown>;
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Plugin manifest (for on-disk .trellis/plugins.json)
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export interface PluginManifest {
118
+ /** Plugin ID. */
119
+ id: string;
120
+ /** Installed version. */
121
+ version: string;
122
+ /** Whether the plugin is enabled. */
123
+ enabled: boolean;
124
+ /** Plugin-specific configuration. */
125
+ config?: Record<string, unknown>;
126
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Datalog Evaluator — Rule-based recursive queries.
3
+ *
4
+ * Provides transitive closure, reachability, and other recursive
5
+ * graph queries via Datalog-style rules evaluated over the EAV store.
6
+ *
7
+ * Built-in rules:
8
+ * - `reachable(?src, ?tgt)` via a named link attribute
9
+ * - `ancestor(?x, ?y)` (generic transitive closure over any link)
10
+ *
11
+ * Custom rules can be registered via `addRule()`.
12
+ *
13
+ * @module trellis/core/query
14
+ */
15
+
16
+ import type { EAVStore } from '../store/eav-store.js';
17
+ import type { DatalogRule } from './types.js';
18
+ import { variable, literal } from './types.js';
19
+ import { QueryEngine } from './engine.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Built-in rule constructors
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Creates a transitive closure rule over a specific link attribute.
27
+ *
28
+ * `reachable(?x, ?y) :- (x attr y)`
29
+ * `reachable(?x, ?y) :- (x attr ?z), reachable(?z, ?y)`
30
+ */
31
+ export function transitiveClosureRules(ruleName: string, linkAttribute: string): DatalogRule[] {
32
+ return [
33
+ // Base case: direct link
34
+ {
35
+ name: ruleName,
36
+ params: ['x', 'y'],
37
+ body: [
38
+ {
39
+ kind: 'link',
40
+ source: variable('x'),
41
+ attribute: literal(linkAttribute),
42
+ target: variable('y'),
43
+ },
44
+ ],
45
+ filters: [],
46
+ },
47
+ // Recursive case: indirect via intermediate
48
+ {
49
+ name: ruleName,
50
+ params: ['x', 'y'],
51
+ body: [
52
+ {
53
+ kind: 'link',
54
+ source: variable('x'),
55
+ attribute: literal(linkAttribute),
56
+ target: variable('z'),
57
+ },
58
+ {
59
+ kind: 'rule',
60
+ name: ruleName,
61
+ args: [variable('z'), variable('y')],
62
+ },
63
+ ],
64
+ filters: [],
65
+ },
66
+ ];
67
+ }
68
+
69
+ /**
70
+ * Creates a reverse reachability rule (follows links backwards).
71
+ */
72
+ export function reverseReachabilityRules(ruleName: string, linkAttribute: string): DatalogRule[] {
73
+ return [
74
+ {
75
+ name: ruleName,
76
+ params: ['x', 'y'],
77
+ body: [
78
+ {
79
+ kind: 'link',
80
+ source: variable('y'),
81
+ attribute: literal(linkAttribute),
82
+ target: variable('x'),
83
+ },
84
+ ],
85
+ filters: [],
86
+ },
87
+ {
88
+ name: ruleName,
89
+ params: ['x', 'y'],
90
+ body: [
91
+ {
92
+ kind: 'link',
93
+ source: variable('z'),
94
+ attribute: literal(linkAttribute),
95
+ target: variable('x'),
96
+ },
97
+ {
98
+ kind: 'rule',
99
+ name: ruleName,
100
+ args: [variable('z'), variable('y')],
101
+ },
102
+ ],
103
+ filters: [],
104
+ },
105
+ ];
106
+ }
107
+
108
+ /**
109
+ * Creates a "sibling" rule — entities that share a common parent via a link attribute.
110
+ *
111
+ * `sibling(?a, ?b) :- (?a attr ?parent), (?b attr ?parent)`
112
+ * FILTER ?a != ?b
113
+ */
114
+ export function siblingRules(ruleName: string, linkAttribute: string): DatalogRule[] {
115
+ return [
116
+ {
117
+ name: ruleName,
118
+ params: ['a', 'b'],
119
+ body: [
120
+ {
121
+ kind: 'link',
122
+ source: variable('a'),
123
+ attribute: literal(linkAttribute),
124
+ target: variable('parent'),
125
+ },
126
+ {
127
+ kind: 'link',
128
+ source: variable('b'),
129
+ attribute: literal(linkAttribute),
130
+ target: variable('parent'),
131
+ },
132
+ ],
133
+ filters: [
134
+ {
135
+ kind: 'filter',
136
+ left: variable('a'),
137
+ op: '!=',
138
+ right: variable('b'),
139
+ },
140
+ ],
141
+ },
142
+ ];
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // Datalog Runtime — convenience wrapper around QueryEngine + rules
147
+ // ---------------------------------------------------------------------------
148
+
149
+ export class DatalogRuntime {
150
+ private engine: QueryEngine;
151
+
152
+ constructor(store: EAVStore) {
153
+ this.engine = new QueryEngine(store);
154
+ }
155
+
156
+ /** Register a Datalog rule (or multiple). */
157
+ addRule(rule: DatalogRule): void {
158
+ this.engine.addRule(rule);
159
+ }
160
+
161
+ addRules(rules: DatalogRule[]): void {
162
+ for (const r of rules) this.engine.addRule(r);
163
+ }
164
+
165
+ removeRule(name: string): void {
166
+ this.engine.removeRule(name);
167
+ }
168
+
169
+ /** Register built-in transitive closure for a link attribute. */
170
+ registerTransitiveClosure(ruleName: string, linkAttribute: string): void {
171
+ this.addRules(transitiveClosureRules(ruleName, linkAttribute));
172
+ }
173
+
174
+ /** Register built-in reverse reachability for a link attribute. */
175
+ registerReverseReachability(ruleName: string, linkAttribute: string): void {
176
+ this.addRules(reverseReachabilityRules(ruleName, linkAttribute));
177
+ }
178
+
179
+ /** Register built-in sibling rule for a link attribute. */
180
+ registerSiblings(ruleName: string, linkAttribute: string): void {
181
+ this.addRules(siblingRules(ruleName, linkAttribute));
182
+ }
183
+
184
+ /** Get the underlying QueryEngine for direct query execution. */
185
+ getEngine(): QueryEngine {
186
+ return this.engine;
187
+ }
188
+ }