trellis 2.0.10 → 2.1.1
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 +279 -116
- package/dist/core/index.js +2 -1
- package/package.json +6 -2
- package/src/core/agents/harness.ts +66 -22
package/README.md
CHANGED
|
@@ -1,62 +1,79 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="https://trentbrew.pockethost.io/api/files/swvnum16u65or8w/75p6fv4xnwa3mq7/logomark_alpha_green_T3C3ypsMwI.png?token=" alt="Trellis" width="128" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# Trellis
|
|
6
6
|
|
|
7
|
-
> **
|
|
8
|
-
>
|
|
9
|
-
|
|
10
|
-
Trellis is a from-scratch VCS built on five pillars:
|
|
11
|
-
|
|
12
|
-
1. **Causal Stream** — an immutable, content-addressed log of ops where every change is causally chained to its predecessor.
|
|
13
|
-
2. **Semantic Patching** — changes are recorded as structured AST patches (`symbolRename`, `symbolModify`, …) rather than line diffs, enabling conflict-free merges.
|
|
14
|
-
3. **Narrative Milestones** — you never "commit". Instead you carve checkpoints and milestones out of a continuous stream of work.
|
|
15
|
-
4. **Governance Subgraph** — op signing, identity management, and policy rules enforced before ops enter the stream.
|
|
16
|
-
5. **Idea Garden** — abandoned work clusters are automatically detected and surfaced for later revival.
|
|
7
|
+
> **A comprehensive graph-native platform for code, knowledge, and collaboration.**
|
|
8
|
+
> Trellis combines version control, semantic analysis, knowledge graphs, and intelligent automation into a unified system that treats every action as a first-class graph entity.
|
|
17
9
|
|
|
18
10
|
---
|
|
19
11
|
|
|
20
12
|
## Table of Contents
|
|
21
13
|
|
|
22
|
-
- [
|
|
14
|
+
- [Platform Overview](#platform-overview)
|
|
15
|
+
- [Core Capabilities](#core-capabilities)
|
|
23
16
|
- [Project Surfaces](#project-surfaces)
|
|
24
17
|
- [Quick Start](#quick-start)
|
|
18
|
+
- [Package Architecture](#package-architecture)
|
|
25
19
|
- [CLI Overview](#cli-overview)
|
|
26
20
|
- [VS Code Extension](#vs-code-extension)
|
|
27
21
|
- [Module & Subpath Guide](#module--subpath-guide)
|
|
28
|
-
- [Architecture Overview](#architecture-overview)
|
|
29
22
|
- [Development & Releases](#development--releases)
|
|
30
23
|
- [Roadmap](#roadmap)
|
|
31
24
|
|
|
32
25
|
---
|
|
33
26
|
|
|
34
|
-
##
|
|
27
|
+
## Platform Overview
|
|
28
|
+
|
|
29
|
+
Trellis is a comprehensive platform that unifies version control, knowledge management, semantic analysis, and intelligent automation. Built on a graph-native foundation, it treats every action—code changes, decisions, links, and embeddings—as first-class entities in a causal graph.
|
|
30
|
+
|
|
31
|
+
The platform consists of multiple integrated surfaces:
|
|
32
|
+
|
|
33
|
+
- **`trellis` npm package** — the published package exposing all platform APIs through modular subpaths
|
|
34
|
+
- **`trellis` CLI** — command-line interface for repository management, semantic tooling, knowledge operations, and automation
|
|
35
|
+
- **VS Code extension** — visual interface for timeline exploration, issue management, knowledge navigation, and collaborative features
|
|
36
|
+
- **Core platform modules** — reusable building blocks for graph storage, semantic analysis, sync, knowledge graphs, embeddings, and decision traces
|
|
37
|
+
|
|
38
|
+
### Core Capabilities
|
|
39
|
+
|
|
40
|
+
#### 1. **Graph-Native Foundation**
|
|
41
|
+
|
|
42
|
+
- **EAV Store** — Entity-Attribute-Value graph database with SQLite backend
|
|
43
|
+
- **TrellisKernel** — Generic graph CRUD operations independent of VCS
|
|
44
|
+
- **Query Engine** — EQL-S query language and Datalog evaluator
|
|
45
|
+
- **Ontology System** — Schema validation and built-in ontologies
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
#### 2. **Advanced Version Control**
|
|
37
48
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
49
|
+
- **Causal Stream** — Immutable, content-addressed operations with causal chaining
|
|
50
|
+
- **Semantic Patching** — AST-aware changes enabling conflict-free merges
|
|
51
|
+
- **Narrative Milestones** — Human-readable checkpoints over continuous work streams
|
|
52
|
+
- **Governance** — Op signing, identity management, and policy enforcement
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
#### 3. **Knowledge & Intelligence**
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
- **Wiki-Links** — Bidirectional reference system for cross-linking entities
|
|
57
|
+
- **Embeddings** — Semantic indexing and vector search for intelligent discovery
|
|
58
|
+
- **Decision Traces** — Automated capture and querying of agent decisions
|
|
59
|
+
- **Idea Garden** — Detection and revival of abandoned work clusters
|
|
60
|
+
|
|
61
|
+
#### 4. **Collaboration & Sync**
|
|
62
|
+
|
|
63
|
+
- **Peer Sync** — CRDT-based reconciliation with HTTP/WebSocket transports
|
|
64
|
+
- **Multi-Repo Federation** — Cross-repository entity references and linking
|
|
65
|
+
- **Agent System** — Tool registry and decision trace capture
|
|
66
|
+
- **Plugin Architecture** — Extensible system with event bus and workspace config
|
|
50
67
|
|
|
51
68
|
## Project Surfaces
|
|
52
69
|
|
|
53
|
-
| Surface | Location | Purpose
|
|
54
|
-
| :-------------------- | :------------------------------------------------------------ |
|
|
55
|
-
| **npm package** | `package.json`, `src/`, `dist/` | Published as `trellis`; exposes
|
|
56
|
-
| **CLI** | `src/cli/index.ts` | Repository lifecycle,
|
|
57
|
-
| **VS Code extension** | `vscode-extension/` | Timeline,
|
|
58
|
-
| **Core
|
|
59
|
-
| **Platform modules** | `src/vcs/`, `src/links/`, `src/embeddings/`, `src/decisions/` |
|
|
70
|
+
| Surface | Location | Purpose |
|
|
71
|
+
| :-------------------- | :------------------------------------------------------------ | :----------------------------------------------------------------------- |
|
|
72
|
+
| **npm package** | `package.json`, `src/`, `dist/` | Published as `trellis`; exposes all platform APIs via subpaths |
|
|
73
|
+
| **CLI** | `src/cli/index.ts` | Repository lifecycle, semantic tooling, knowledge operations, automation |
|
|
74
|
+
| **VS Code extension** | `vscode-extension/` | Timeline, knowledge navigation, issue management, collaborative UX |
|
|
75
|
+
| **Core platform** | `src/core/` | EAV store, kernel, ontology, query, agents, plugins |
|
|
76
|
+
| **Platform modules** | `src/vcs/`, `src/links/`, `src/embeddings/`, `src/decisions/` | Specialized subsystems for version control, knowledge, and intelligence |
|
|
60
77
|
|
|
61
78
|
---
|
|
62
79
|
|
|
@@ -96,27 +113,35 @@ bun run src/cli/index.ts import --from /path/to/git-repo
|
|
|
96
113
|
|
|
97
114
|
---
|
|
98
115
|
|
|
99
|
-
## Architecture
|
|
116
|
+
## Package Architecture
|
|
100
117
|
|
|
101
118
|
```
|
|
102
119
|
trellis/
|
|
103
120
|
├── src/
|
|
104
|
-
│ ├── core/ # EAV store
|
|
105
|
-
│ ├──
|
|
106
|
-
│ ├──
|
|
121
|
+
│ ├── core/ # EAV store, kernel, ontology, query, agents, plugins
|
|
122
|
+
│ │ ├── kernel/ # TrellisKernel with entity CRUD and middleware
|
|
123
|
+
│ │ ├── ontology/ # Schema registry, validation, built-in ontologies
|
|
124
|
+
│ │ ├── query/ # EQL-S query engine and Datalog evaluator
|
|
125
|
+
│ │ ├── agents/ # Agent harness, tool registry, decision traces
|
|
126
|
+
│ │ └── plugins/ # Plugin registry, event bus, workspace config
|
|
127
|
+
│ ├── vcs/ # Version control: ops, branches, milestones, diff, merge
|
|
107
128
|
│ ├── links/ # Wiki-link parsing, resolution, backlink index
|
|
108
|
-
│ ├── embeddings/ # Semantic indexing
|
|
129
|
+
│ ├── embeddings/ # Semantic indexing, vector search, auto-embed, RAG
|
|
109
130
|
│ ├── decisions/ # Decision trace capture and querying
|
|
110
131
|
│ ├── semantic/ # AST parsers, semantic diff, semantic merge
|
|
111
|
-
│ ├── sync/ # Peer sync
|
|
132
|
+
│ ├── sync/ # Peer sync, CRDT reconciler, HTTP/WebSocket transports, multi-repo
|
|
133
|
+
│ ├── garden/ # Idea Garden: abandoned work detection and revival
|
|
134
|
+
│ ├── git/ # Git import/export bridge
|
|
135
|
+
│ ├── identity/ # Ed25519 identity management and governance
|
|
112
136
|
│ ├── watcher/ # File watching + ingestion pipeline
|
|
137
|
+
│ ├── mcp/ # Model Context Protocol server integration
|
|
113
138
|
│ ├── cli/ # CLI entrypoint
|
|
114
139
|
│ ├── engine.ts # Composition root
|
|
115
140
|
│ └── index.ts # Main package entrypoint
|
|
116
141
|
├── vscode-extension/ # VS Code extension surface
|
|
117
|
-
├── test/ #
|
|
118
|
-
├── DESIGN.md # Detailed architecture
|
|
119
|
-
└── justfile # Local build
|
|
142
|
+
├── test/ # Comprehensive test suites
|
|
143
|
+
├── DESIGN.md # Detailed architecture specification
|
|
144
|
+
└── justfile # Local build and development recipes
|
|
120
145
|
```
|
|
121
146
|
|
|
122
147
|
### The Op Stream
|
|
@@ -218,20 +243,61 @@ trellis garden stats # Garden statistics
|
|
|
218
243
|
```bash
|
|
219
244
|
trellis sync status # Local sync state
|
|
220
245
|
trellis sync reconcile --remote <path> # Reconcile with another local repo
|
|
246
|
+
trellis sync push --peer <id> # Push ops to a peer (HTTP/WebSocket)
|
|
247
|
+
trellis sync pull --peer <id> # Pull ops from a peer
|
|
248
|
+
trellis link-repo <alias> <location> # Link a remote repo for cross-repo refs
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Query & Search
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
trellis query "find Project where status = 'active'" # EQL-S structured query
|
|
255
|
+
trellis query-repl # Interactive query REPL
|
|
256
|
+
trellis ask "authentication code" # Natural language semantic search
|
|
257
|
+
trellis ask "show me auth code" --rag # Output as RAG context for LLMs
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Ontology
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
trellis ontology list # List registered ontologies
|
|
264
|
+
trellis ontology show <id> # Show ontology details
|
|
265
|
+
trellis ontology validate # Validate store against ontologies
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Agents
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
trellis agent list # List registered agents
|
|
272
|
+
trellis agent create <name> # Create a new agent definition
|
|
273
|
+
trellis agent run <id> --input "task" # Execute an agent run
|
|
274
|
+
trellis agent inspect <run-id> # View run details and decision traces
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Plugins
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
trellis plugin list # List loaded plugins
|
|
281
|
+
trellis plugin add <id> # Register and load a plugin
|
|
282
|
+
trellis plugin remove <id> # Unload and unregister a plugin
|
|
221
283
|
```
|
|
222
284
|
|
|
223
285
|
---
|
|
224
286
|
|
|
225
287
|
## VS Code Extension
|
|
226
288
|
|
|
227
|
-
The VS Code extension
|
|
289
|
+
The VS Code extension provides a rich visual interface for the entire Trellis platform, making knowledge navigation, collaboration, and project management intuitive and efficient.
|
|
290
|
+
|
|
291
|
+
### Core Features
|
|
228
292
|
|
|
229
|
-
- **Status
|
|
230
|
-
- **Causal
|
|
231
|
-
- **
|
|
232
|
-
- **
|
|
233
|
-
- **
|
|
234
|
-
- **
|
|
293
|
+
- **Status Dashboard** — Real-time view of repository health, activity metrics, and system state
|
|
294
|
+
- **Causal Timeline** — Interactive exploration of the complete operation history with filtering and search
|
|
295
|
+
- **Knowledge Navigator** — Browse and traverse the wiki-link graph with visual connections
|
|
296
|
+
- **Issue Management** — Full lifecycle issue management with drag-and-drop workflow automation
|
|
297
|
+
- **Semantic Explorer** — Navigate code structure and relationships through AST-aware visualizations
|
|
298
|
+
- **Decision Inspector** — Review decision traces and understand the reasoning behind changes
|
|
299
|
+
- **Collaboration Hub** — Multi-repo federation view and cross-project relationship mapping
|
|
300
|
+
- **Intelligence Panel** — Semantic search, RAG context, and AI-powered insights
|
|
235
301
|
|
|
236
302
|
The extension lives in [`vscode-extension/`](./vscode-extension) and publishes separately from the npm package.
|
|
237
303
|
|
|
@@ -239,7 +305,7 @@ The extension lives in [`vscode-extension/`](./vscode-extension) and publishes s
|
|
|
239
305
|
|
|
240
306
|
## Module & Subpath Guide
|
|
241
307
|
|
|
242
|
-
The `trellis` package is intentionally split into subpaths so consumers can depend on the
|
|
308
|
+
The `trellis` package is intentionally split into subpaths so consumers can depend on the specific capabilities they need, from core graph operations to advanced AI features.
|
|
243
309
|
|
|
244
310
|
### Published Package Surface
|
|
245
311
|
|
|
@@ -248,88 +314,104 @@ bun add trellis
|
|
|
248
314
|
```
|
|
249
315
|
|
|
250
316
|
```typescript
|
|
251
|
-
// Main entry —
|
|
317
|
+
// Main entry — full platform engine
|
|
252
318
|
import { TrellisVcsEngine } from 'trellis';
|
|
253
319
|
|
|
254
|
-
// Core
|
|
255
|
-
import { EAVStore } from 'trellis/core';
|
|
256
|
-
import type { Fact, Link } from 'trellis/core';
|
|
320
|
+
// Core graph foundation (independent of VCS)
|
|
321
|
+
import { EAVStore, TrellisKernel } from 'trellis/core';
|
|
322
|
+
import type { Fact, Link, EntityRecord } from 'trellis/core';
|
|
257
323
|
|
|
258
|
-
//
|
|
324
|
+
// Version control primitives
|
|
259
325
|
import type { VcsOp, VcsOpKind } from 'trellis/vcs';
|
|
260
326
|
|
|
261
|
-
//
|
|
327
|
+
// Knowledge and intelligence
|
|
262
328
|
import { EmbeddingManager } from 'trellis/ai';
|
|
263
|
-
|
|
264
|
-
// Wiki-linking
|
|
265
329
|
import { parseFileRefs, resolveRef, RefIndex } from 'trellis/links';
|
|
266
|
-
|
|
267
|
-
// Decision traces
|
|
268
330
|
import { recordDecision, queryDecisions } from 'trellis/decisions';
|
|
269
331
|
```
|
|
270
332
|
|
|
271
|
-
### `trellis` — Engine
|
|
333
|
+
### `trellis` — Platform Engine (`TrellisVcsEngine`)
|
|
272
334
|
|
|
273
|
-
The main entry point
|
|
335
|
+
The main entry point that orchestrates the entire platform: kernel, file watcher, version control, knowledge graphs, semantic analysis, and collaboration features.
|
|
274
336
|
|
|
275
337
|
```typescript
|
|
276
338
|
import { TrellisVcsEngine } from 'trellis';
|
|
277
339
|
|
|
278
340
|
const engine = new TrellisVcsEngine({ rootPath: '/my/project' });
|
|
279
341
|
|
|
280
|
-
//
|
|
342
|
+
// Repository management
|
|
281
343
|
await engine.initRepo();
|
|
282
|
-
|
|
283
|
-
// Existing repo
|
|
284
344
|
engine.open();
|
|
285
345
|
|
|
286
|
-
// Core
|
|
287
|
-
engine.getOps(); // All
|
|
346
|
+
// Core operations
|
|
347
|
+
engine.getOps(); // All operations
|
|
288
348
|
engine.status(); // Branch, op count, files
|
|
289
|
-
engine.log(); // Formatted
|
|
349
|
+
engine.log(); // Formatted history
|
|
290
350
|
engine.getFiles(); // Currently tracked files
|
|
291
351
|
|
|
292
|
-
//
|
|
352
|
+
// Version control
|
|
293
353
|
await engine.createBranch('feature/x');
|
|
294
|
-
engine.switchBranch('feature/x');
|
|
295
|
-
engine.listBranches();
|
|
296
|
-
await engine.deleteBranch('feature/x');
|
|
297
|
-
|
|
298
|
-
// Milestones
|
|
299
354
|
await engine.createMilestone('Implement auth', { fromOpHash, toOpHash });
|
|
300
|
-
engine.listMilestones();
|
|
301
355
|
|
|
302
|
-
// Semantic
|
|
356
|
+
// Semantic analysis
|
|
303
357
|
engine.parseFile(content, 'src/auth.ts');
|
|
304
358
|
engine.semanticDiff(oldContent, newContent, 'src/auth.ts');
|
|
305
359
|
|
|
306
|
-
//
|
|
360
|
+
// Knowledge operations
|
|
307
361
|
const garden = engine.garden();
|
|
308
362
|
garden.listClusters();
|
|
309
363
|
garden.search({ keyword: 'auth' });
|
|
310
|
-
|
|
364
|
+
|
|
365
|
+
// Intelligence features
|
|
366
|
+
const embeddings = engine.embeddings();
|
|
367
|
+
await embeddings.search('authentication flow');
|
|
311
368
|
```
|
|
312
369
|
|
|
313
|
-
### `trellis/core` —
|
|
370
|
+
### `trellis/core` — Graph Foundation
|
|
314
371
|
|
|
315
|
-
The
|
|
372
|
+
The core graph layer exposes EAV primitives, ontology schemas, query engine, agent harness, and plugin system. This module is independent of version control and can be used for any graph-based application.
|
|
316
373
|
|
|
317
374
|
```typescript
|
|
318
|
-
import { EAVStore } from 'trellis/core';
|
|
319
|
-
import
|
|
375
|
+
import { EAVStore, TrellisKernel } from 'trellis/core';
|
|
376
|
+
import { OntologyRegistry, builtinOntologies } from 'trellis/core';
|
|
377
|
+
import { QueryEngine, parseQuery } from 'trellis/core';
|
|
378
|
+
import { AgentHarness } from 'trellis/core';
|
|
379
|
+
import { PluginRegistry, EventBus } from 'trellis/core';
|
|
380
|
+
import type { Fact, Link, KernelOp, AgentDef, PluginDef } from 'trellis/core';
|
|
381
|
+
|
|
382
|
+
// Graph operations
|
|
383
|
+
const kernel = new TrellisKernel({ backend, agentId: 'me' });
|
|
384
|
+
await kernel.createEntity('proj:1', 'Project', {
|
|
385
|
+
name: 'Trellis',
|
|
386
|
+
status: 'active',
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Ontology validation
|
|
390
|
+
const registry = new OntologyRegistry();
|
|
391
|
+
for (const o of builtinOntologies) registry.register(o);
|
|
320
392
|
|
|
321
|
-
|
|
393
|
+
// Graph queries
|
|
394
|
+
const query = parseQuery('find Project where status = "active"');
|
|
395
|
+
const results = new QueryEngine(store).eval(query);
|
|
396
|
+
|
|
397
|
+
// Agent orchestration
|
|
398
|
+
const harness = new AgentHarness(kernel);
|
|
399
|
+
await harness.createAgent({ name: 'Reviewer', status: 'active' });
|
|
400
|
+
|
|
401
|
+
// Plugin system
|
|
402
|
+
const plugins = new PluginRegistry();
|
|
403
|
+
plugins.register({ id: 'my:plugin', name: 'My Plugin', version: '1.0.0' });
|
|
322
404
|
```
|
|
323
405
|
|
|
324
|
-
### `trellis/vcs` —
|
|
406
|
+
### `trellis/vcs` — Version Control Model
|
|
325
407
|
|
|
326
|
-
The VCS subpath exposes operation types and domain-level building blocks for branches, milestones, checkpoints, issues, diffing, merging, and blob storage.
|
|
408
|
+
The VCS subpath exposes operation types and domain-level building blocks for version control: branches, milestones, checkpoints, issues, diffing, merging, and blob storage.
|
|
327
409
|
|
|
328
|
-
Use this subpath when you want Trellis
|
|
410
|
+
Use this subpath when you want Trellis version control capabilities without the full platform surface.
|
|
329
411
|
|
|
330
|
-
### `trellis/links` — Wiki-
|
|
412
|
+
### `trellis/links` — Knowledge Graph & Wiki-Links
|
|
331
413
|
|
|
332
|
-
Parse `[[wiki-links]]` from markdown, doc-comments, and source files. Resolve references to issues, files, symbols, milestones, and decisions. Build a bidirectional
|
|
414
|
+
Parse `[[wiki-links]]` from markdown, doc-comments, and source files. Resolve references to issues, files, symbols, milestones, and decisions. Build a bidirectional knowledge graph that connects all entities in your workspace.
|
|
333
415
|
|
|
334
416
|
```typescript
|
|
335
417
|
import { parseFileRefs, resolveRef, RefIndex } from 'trellis/links';
|
|
@@ -339,11 +421,14 @@ const resolved = resolveRef(ref, context);
|
|
|
339
421
|
const index = new RefIndex();
|
|
340
422
|
index.indexFile('README.md', refs, context);
|
|
341
423
|
index.getIncoming('issue:TRL-5');
|
|
424
|
+
|
|
425
|
+
// Cross-repository references
|
|
426
|
+
index.addCrossRepoLink('frontend', 'proj:app', 'backend', 'api:users');
|
|
342
427
|
```
|
|
343
428
|
|
|
344
|
-
### `trellis/ai` —
|
|
429
|
+
### `trellis/ai` — Semantic Intelligence
|
|
345
430
|
|
|
346
|
-
|
|
431
|
+
Advanced semantic capabilities: embed issues, milestones, files, code entities, and decisions into a vector store for intelligent search, discovery, and RAG (Retrieval-Augmented Generation).
|
|
347
432
|
|
|
348
433
|
```typescript
|
|
349
434
|
import { EmbeddingManager } from 'trellis/ai';
|
|
@@ -351,11 +436,14 @@ import { EmbeddingManager } from 'trellis/ai';
|
|
|
351
436
|
const manager = new EmbeddingManager(engine, { dbPath: 'embeddings.db' });
|
|
352
437
|
await manager.reindex();
|
|
353
438
|
const results = await manager.search('auth flow');
|
|
439
|
+
|
|
440
|
+
// RAG context for LLMs
|
|
441
|
+
const context = await manager.getRAGContext('authentication implementation');
|
|
354
442
|
```
|
|
355
443
|
|
|
356
|
-
### `trellis/decisions` — Decision
|
|
444
|
+
### `trellis/decisions` — Decision Intelligence
|
|
357
445
|
|
|
358
|
-
Automatically capture tool invocations as auditable
|
|
446
|
+
Automatically capture tool invocations and agent decisions as auditable traces. Enrich them with rationale via hooks and query them later by tool, entity, or decision chain. Perfect for understanding how and why decisions were made.
|
|
359
447
|
|
|
360
448
|
```typescript
|
|
361
449
|
import { recordDecision, queryDecisions } from 'trellis/decisions';
|
|
@@ -364,9 +452,12 @@ const dec = await recordDecision(ctx, {
|
|
|
364
452
|
toolName: 'trellis_issue_create',
|
|
365
453
|
input: { title: 'Add parser' },
|
|
366
454
|
output: { id: 'TRL-5' },
|
|
455
|
+
rationale: 'Needed for TypeScript support',
|
|
367
456
|
});
|
|
368
457
|
|
|
458
|
+
// Query decision patterns
|
|
369
459
|
const decs = queryDecisions(ctx, { tool: 'trellis_issue_*' });
|
|
460
|
+
const chain = queryDecisions(ctx, { entity: 'TRL-5' }); // Full decision chain
|
|
370
461
|
```
|
|
371
462
|
|
|
372
463
|
### `src/garden/` — Idea Garden
|
|
@@ -384,6 +475,14 @@ garden.stats(); // { total, abandoned, draft, revived, totalOps, totalFiles }
|
|
|
384
475
|
garden.revive('cluster:abc');
|
|
385
476
|
```
|
|
386
477
|
|
|
478
|
+
### Platform Subsystem Guides
|
|
479
|
+
|
|
480
|
+
The following sections provide detailed guides for Trellis' internal subsystems. These are useful if you're contributing to the platform or extending its capabilities.
|
|
481
|
+
|
|
482
|
+
#### Knowledge Graph & Wiki-Links (`src/links/`)
|
|
483
|
+
|
|
484
|
+
Builds a bidirectional knowledge graph from wiki-link references across all workspace entities.
|
|
485
|
+
|
|
387
486
|
**Detection heuristics:**
|
|
388
487
|
|
|
389
488
|
| Heuristic | Trigger | Signal |
|
|
@@ -392,29 +491,29 @@ garden.revive('cluster:abc');
|
|
|
392
491
|
| `revertDetector` | Complementary ops | Ops undone by a subsequent inverse op (add→delete, modify→revert) |
|
|
393
492
|
| `staleBranchDetector` | Time + no milestone | Ops on non-`main` branches untouched >7 days without a milestone |
|
|
394
493
|
|
|
395
|
-
|
|
494
|
+
#### Semantic Intelligence (`src/semantic/`)
|
|
396
495
|
|
|
397
|
-
|
|
496
|
+
Advanced TypeScript/JavaScript analysis with structural entity extraction and semantic diffing for intelligent code understanding.
|
|
398
497
|
|
|
399
498
|
```typescript
|
|
400
499
|
import { typescriptParser, semanticMerge } from './src/semantic/index.js';
|
|
401
500
|
|
|
402
|
-
// Parse
|
|
501
|
+
// Parse code into structural entities
|
|
403
502
|
const result = typescriptParser.parse(source, 'file.ts');
|
|
404
503
|
result.declarations; // ASTEntity[] — functions, classes, interfaces, enums, …
|
|
405
504
|
result.imports; // ImportRelation[]
|
|
406
505
|
result.exports; // ExportRelation[]
|
|
407
506
|
|
|
408
|
-
//
|
|
507
|
+
// Compute semantic differences
|
|
409
508
|
const patches = typescriptParser.diff(oldResult, newResult);
|
|
410
509
|
// SemanticPatch[] — symbolAdd | symbolRemove | symbolModify |
|
|
411
510
|
// symbolRename | importAdd | importRemove | …
|
|
412
511
|
|
|
413
|
-
//
|
|
512
|
+
// Intelligent merging with conflict resolution
|
|
414
513
|
const merged = semanticMerge(ourPatches, theirPatches, 'file.ts');
|
|
415
514
|
merged.clean; // true if no conflicts
|
|
416
515
|
merged.patches; // Merged patch list
|
|
417
|
-
merged.conflicts; // SemanticMergeConflict[] — entity-level, with
|
|
516
|
+
merged.conflicts; // SemanticMergeConflict[] — entity-level, with suggestions
|
|
418
517
|
```
|
|
419
518
|
|
|
420
519
|
### `src/sync/` — Peer Sync
|
|
@@ -447,7 +546,55 @@ await engine.pullFrom('peer-b');
|
|
|
447
546
|
engine.reconcileWith(remoteOps);
|
|
448
547
|
```
|
|
449
548
|
|
|
450
|
-
|
|
549
|
+
#### Collaboration & Sync (`src/sync/`)
|
|
550
|
+
|
|
551
|
+
Enterprise-grade synchronization with CRDT reconciliation, multiple transport protocols, and multi-repo federation for distributed teams.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import {
|
|
555
|
+
SyncEngine,
|
|
556
|
+
MemoryTransport,
|
|
557
|
+
reconcile,
|
|
558
|
+
HttpSyncTransport,
|
|
559
|
+
WebSocketSyncTransport,
|
|
560
|
+
MultiRepoManager,
|
|
561
|
+
formatCrossRepoRef,
|
|
562
|
+
} from './src/sync/index.js';
|
|
563
|
+
|
|
564
|
+
// CRDT reconciler for conflict-free merging
|
|
565
|
+
const result = reconcile(localOps, remoteOps);
|
|
566
|
+
result.merged; // Interleaved by timestamp
|
|
567
|
+
result.forkPoint; // Last common op hash
|
|
568
|
+
result.conflicts; // File-level conflicts
|
|
569
|
+
|
|
570
|
+
// HTTP transport for network sync
|
|
571
|
+
const httpTransport = new HttpSyncTransport('peer-a');
|
|
572
|
+
httpTransport.addPeer('peer-b', 'http://192.168.1.10:4200');
|
|
573
|
+
|
|
574
|
+
// WebSocket transport for real-time collaboration
|
|
575
|
+
const wsTransport = new WebSocketSyncTransport('peer-a');
|
|
576
|
+
await wsTransport.connect('peer-b', 'ws://192.168.1.10:4201');
|
|
577
|
+
|
|
578
|
+
// Multi-repo federation — connect knowledge across repositories
|
|
579
|
+
const repoManager = new MultiRepoManager(kernel);
|
|
580
|
+
await repoManager.linkRepo(
|
|
581
|
+
'backend',
|
|
582
|
+
'/path/to/backend-api',
|
|
583
|
+
'Backend service',
|
|
584
|
+
);
|
|
585
|
+
await repoManager.addCrossRepoLink(
|
|
586
|
+
'proj:frontend',
|
|
587
|
+
'dependsOn',
|
|
588
|
+
'backend',
|
|
589
|
+
'lib:api-client',
|
|
590
|
+
);
|
|
591
|
+
// Creates: proj:frontend --dependsOn--> @backend:lib:api-client
|
|
592
|
+
|
|
593
|
+
// Discover cross-repository relationships
|
|
594
|
+
const refs = repoManager.findReferencesTo('backend', 'lib:api-client');
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
These subsystem guides provide insight into Trellis' internal architecture. They're particularly useful for platform contributors, extenders, or those building custom integrations.
|
|
451
598
|
|
|
452
599
|
---
|
|
453
600
|
|
|
@@ -513,14 +660,20 @@ Engine A sends 'have' { heads: { main: 'h42' }, opCount: 42 }
|
|
|
513
660
|
|
|
514
661
|
## Design Doc
|
|
515
662
|
|
|
516
|
-
See [`DESIGN.md`](./DESIGN.md) for the
|
|
663
|
+
See [`DESIGN.md`](./DESIGN.md) for the complete platform architecture specification, including:
|
|
517
664
|
|
|
518
665
|
- §3 — Causal stream and branch concurrency model
|
|
519
|
-
- §4 — Semantic patching: parser adapter interface, patch types, commutativity
|
|
666
|
+
- §4 — Semantic patching: parser adapter interface, patch types, commutativity
|
|
520
667
|
- §5 — Milestone narrative model
|
|
521
668
|
- §6 — Identity, signing, and governance
|
|
522
669
|
- §7 — Idea Garden cluster detection heuristics
|
|
523
|
-
- §
|
|
670
|
+
- §8 — Knowledge graph and wiki-link system
|
|
671
|
+
- §9 — Embeddings and semantic search architecture
|
|
672
|
+
- §10 — Decision trace capture and querying
|
|
673
|
+
- §11 — Sync protocols and multi-repo federation
|
|
674
|
+
- §12 — Core graph kernel and ontology system
|
|
675
|
+
- §13 — Agent harness and plugin architecture
|
|
676
|
+
- §14 — Open questions and architectural trade-offs
|
|
524
677
|
|
|
525
678
|
---
|
|
526
679
|
|
|
@@ -575,23 +728,33 @@ just publish-dry-run
|
|
|
575
728
|
just publish
|
|
576
729
|
```
|
|
577
730
|
|
|
578
|
-
> **Requires [Bun](https://bun.sh) ≥ 1.0** — Trellis uses `bun:sqlite` for the vector store and Bun's native
|
|
731
|
+
> **Requires [Bun](https://bun.sh) ≥ 1.0** — Trellis uses `bun:sqlite` for the vector store and Bun's native TypeScript support for optimal performance.
|
|
579
732
|
|
|
580
733
|
---
|
|
581
734
|
|
|
582
735
|
## Roadmap
|
|
583
736
|
|
|
584
|
-
| Phase | Deliverable
|
|
585
|
-
| :---- |
|
|
586
|
-
| P0 | Causal stream + CLI
|
|
587
|
-
| P0.5 | VS Code extension / visual timeline
|
|
588
|
-
| P1 | Git import bridge
|
|
589
|
-
| P2 | Branches, milestones, checkpoints
|
|
590
|
-
| P2.5 | Blob store, engine modularization, git exporter
|
|
591
|
-
| P3 | File-level diff + text-based merge
|
|
592
|
-
| P4 | Identity + op signing + governance
|
|
593
|
-
| P5 | Idea Garden — cluster detection + query
|
|
594
|
-
| P6 | Semantic patching — parser adapter + diff/merge
|
|
595
|
-
| P7 | Peer sync + CRDT reconciler
|
|
596
|
-
| P8 | Wiki-links, embeddings, decision traces
|
|
597
|
-
| P9 | npm package + VSCode extension publish
|
|
737
|
+
| Phase | Deliverable | Status | Commit |
|
|
738
|
+
| :---- | :------------------------------------------------- | :----- | :---------- |
|
|
739
|
+
| P0 | Causal stream + CLI | ✅ | `51475d3` |
|
|
740
|
+
| P0.5 | VS Code extension / visual timeline | ✅ | `947d5a1` |
|
|
741
|
+
| P1 | Git import bridge | ✅ | `f4cc4a6` |
|
|
742
|
+
| P2 | Branches, milestones, checkpoints | ✅ | `3f91e9a` |
|
|
743
|
+
| P2.5 | Blob store, engine modularization, git exporter | ✅ | `5c43a31` |
|
|
744
|
+
| P3 | File-level diff + text-based merge | ✅ | `c953654` |
|
|
745
|
+
| P4 | Identity + op signing + governance | ✅ | `3acddda` |
|
|
746
|
+
| P5 | Idea Garden — cluster detection + query | ✅ | `105a207` |
|
|
747
|
+
| P6 | Semantic patching — parser adapter + diff/merge | ✅ | `22192ae` |
|
|
748
|
+
| P7 | Peer sync + CRDT reconciler | ✅ | `d02f3f7` |
|
|
749
|
+
| P8 | Wiki-links, embeddings, decision traces | ✅ | `b9cd5b7` |
|
|
750
|
+
| P9 | npm package + VSCode extension publish | ✅ | `57bad37` |
|
|
751
|
+
| P10 | Graph kernel + SQLite backend + entity CRUD | ✅ | |
|
|
752
|
+
| P11 | EQL-S query engine + Datalog evaluator | ✅ | |
|
|
753
|
+
| P12 | Ontology system + validation middleware | ✅ | |
|
|
754
|
+
| P13 | Transformers.js upgrade + auto-embed + RAG | ✅ | |
|
|
755
|
+
| P14 | Agent harness + tool registry + decision traces | ✅ | |
|
|
756
|
+
| P15 | Plugin system + event bus + workspace config | ✅ | |
|
|
757
|
+
| P16 | Sync federation + multi-repo linking | ✅ | |
|
|
758
|
+
| P17 | Advanced knowledge graph + cross-repo intelligence | 🚧 | In Progress |
|
|
759
|
+
| P18 | Enterprise collaboration features + RBAC | 📋 | Planned |
|
|
760
|
+
| P19 | Advanced AI capabilities + intelligent automation | 📋 | Planned |
|
package/dist/core/index.js
CHANGED
|
@@ -32,6 +32,7 @@ class AgentHarness {
|
|
|
32
32
|
kernel;
|
|
33
33
|
toolHandlers = new Map;
|
|
34
34
|
config;
|
|
35
|
+
runCounter = 0;
|
|
35
36
|
constructor(kernel, config) {
|
|
36
37
|
this.kernel = kernel;
|
|
37
38
|
this.config = {
|
|
@@ -114,7 +115,7 @@ class AgentHarness {
|
|
|
114
115
|
const agent = this.getAgent(agentId);
|
|
115
116
|
if (!agent)
|
|
116
117
|
throw new Error(`Agent "${agentId}" not found.`);
|
|
117
|
-
const runId = `run:${agentId.replace("agent:", "")}:${Date.now()}`;
|
|
118
|
+
const runId = `run:${agentId.replace("agent:", "")}:${Date.now()}:${++this.runCounter}`;
|
|
118
119
|
await this.kernel.createEntity(runId, "AgentRun", {
|
|
119
120
|
startedAt: new Date().toISOString(),
|
|
120
121
|
status: "running",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trellis",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "A comprehensive graph-native platform for code, knowledge, and collaboration — version control, semantic analysis, knowledge graphs, embeddings, and decision traces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./src/index.ts",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"ast",
|
|
22
22
|
"wiki-links",
|
|
23
23
|
"embeddings",
|
|
24
|
+
"knowledge-graph",
|
|
25
|
+
"decision-traces",
|
|
26
|
+
"collaboration",
|
|
27
|
+
"sync",
|
|
24
28
|
"causal",
|
|
25
29
|
"bun"
|
|
26
30
|
],
|
|
@@ -27,6 +27,7 @@ export class AgentHarness {
|
|
|
27
27
|
private kernel: TrellisKernel;
|
|
28
28
|
private toolHandlers: Map<string, ToolHandler> = new Map();
|
|
29
29
|
private config: AgentHarnessConfig;
|
|
30
|
+
private runCounter: number = 0;
|
|
30
31
|
|
|
31
32
|
constructor(kernel: TrellisKernel, config?: AgentHarnessConfig) {
|
|
32
33
|
this.kernel = kernel;
|
|
@@ -41,11 +42,13 @@ export class AgentHarness {
|
|
|
41
42
|
// Agent CRUD (via kernel entities)
|
|
42
43
|
// -------------------------------------------------------------------------
|
|
43
44
|
|
|
44
|
-
async createAgent(
|
|
45
|
-
id
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
async createAgent(
|
|
46
|
+
def: Omit<AgentDef, 'id' | 'capabilities' | 'tools'> & {
|
|
47
|
+
id?: string;
|
|
48
|
+
capabilities?: string[];
|
|
49
|
+
tools?: string[];
|
|
50
|
+
},
|
|
51
|
+
): Promise<AgentDef> {
|
|
49
52
|
const id = def.id ?? `agent:${def.name.toLowerCase().replace(/\s+/g, '-')}`;
|
|
50
53
|
await this.kernel.createEntity(id, 'Agent', {
|
|
51
54
|
name: def.name,
|
|
@@ -84,18 +87,29 @@ export class AgentHarness {
|
|
|
84
87
|
return {
|
|
85
88
|
id: entity.id,
|
|
86
89
|
name: String(entity.facts.find((f) => f.a === 'name')?.v ?? ''),
|
|
87
|
-
description: entity.facts.find((f) => f.a === 'description')?.v as
|
|
90
|
+
description: entity.facts.find((f) => f.a === 'description')?.v as
|
|
91
|
+
| string
|
|
92
|
+
| undefined,
|
|
88
93
|
model: entity.facts.find((f) => f.a === 'model')?.v as string | undefined,
|
|
89
|
-
provider: entity.facts.find((f) => f.a === 'provider')?.v as
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
provider: entity.facts.find((f) => f.a === 'provider')?.v as
|
|
95
|
+
| string
|
|
96
|
+
| undefined,
|
|
97
|
+
systemPrompt: entity.facts.find((f) => f.a === 'systemPrompt')?.v as
|
|
98
|
+
| string
|
|
99
|
+
| undefined,
|
|
100
|
+
status:
|
|
101
|
+
(entity.facts.find((f) => f.a === 'status')?.v as AgentDef['status']) ??
|
|
102
|
+
'active',
|
|
92
103
|
capabilities: capLinks.map((l) => l.e2),
|
|
93
104
|
tools: toolLinks.map((l) => l.e2),
|
|
94
105
|
};
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
listAgents(status?: AgentDef['status']): AgentDef[] {
|
|
98
|
-
const entities = this.kernel.listEntities(
|
|
109
|
+
const entities = this.kernel.listEntities(
|
|
110
|
+
'Agent',
|
|
111
|
+
status ? { status } : undefined,
|
|
112
|
+
);
|
|
99
113
|
return entities
|
|
100
114
|
.map((e) => this.getAgent(e.id))
|
|
101
115
|
.filter((a): a is AgentDef => a !== null);
|
|
@@ -105,7 +119,10 @@ export class AgentHarness {
|
|
|
105
119
|
// Tool registration
|
|
106
120
|
// -------------------------------------------------------------------------
|
|
107
121
|
|
|
108
|
-
async registerTool(
|
|
122
|
+
async registerTool(
|
|
123
|
+
def: Omit<ToolDef, 'id'> & { id?: string },
|
|
124
|
+
handler: ToolHandler,
|
|
125
|
+
): Promise<string> {
|
|
109
126
|
const id = def.id ?? `tool:${def.name.toLowerCase().replace(/\s+/g, '-')}`;
|
|
110
127
|
|
|
111
128
|
// Create tool entity if it doesn't exist
|
|
@@ -130,9 +147,13 @@ export class AgentHarness {
|
|
|
130
147
|
return this.kernel.listEntities('Tool').map((e) => ({
|
|
131
148
|
id: e.id,
|
|
132
149
|
name: String(e.facts.find((f) => f.a === 'name')?.v ?? ''),
|
|
133
|
-
description: e.facts.find((f) => f.a === 'description')?.v as
|
|
150
|
+
description: e.facts.find((f) => f.a === 'description')?.v as
|
|
151
|
+
| string
|
|
152
|
+
| undefined,
|
|
134
153
|
schema: e.facts.find((f) => f.a === 'schema')?.v as string | undefined,
|
|
135
|
-
endpoint: e.facts.find((f) => f.a === 'endpoint')?.v as
|
|
154
|
+
endpoint: e.facts.find((f) => f.a === 'endpoint')?.v as
|
|
155
|
+
| string
|
|
156
|
+
| undefined,
|
|
136
157
|
}));
|
|
137
158
|
}
|
|
138
159
|
|
|
@@ -144,7 +165,8 @@ export class AgentHarness {
|
|
|
144
165
|
const agent = this.getAgent(agentId);
|
|
145
166
|
if (!agent) throw new Error(`Agent "${agentId}" not found.`);
|
|
146
167
|
|
|
147
|
-
|
|
168
|
+
// Ensure unique run IDs even when called in rapid succession
|
|
169
|
+
const runId = `run:${agentId.replace('agent:', '')}:${Date.now()}:${++this.runCounter}`;
|
|
148
170
|
await this.kernel.createEntity(runId, 'AgentRun', {
|
|
149
171
|
startedAt: new Date().toISOString(),
|
|
150
172
|
status: 'running',
|
|
@@ -155,7 +177,11 @@ export class AgentHarness {
|
|
|
155
177
|
return runId;
|
|
156
178
|
}
|
|
157
179
|
|
|
158
|
-
async completeRun(
|
|
180
|
+
async completeRun(
|
|
181
|
+
runId: string,
|
|
182
|
+
output?: string,
|
|
183
|
+
tokenCount?: number,
|
|
184
|
+
): Promise<void> {
|
|
159
185
|
const updates: Record<string, any> = {
|
|
160
186
|
status: 'completed',
|
|
161
187
|
completedAt: new Date().toISOString(),
|
|
@@ -183,8 +209,12 @@ export class AgentHarness {
|
|
|
183
209
|
|
|
184
210
|
// Get decisions for this run
|
|
185
211
|
const decisionLinks = store.getLinksByAttribute('belongsToRun');
|
|
186
|
-
const decisionIds = decisionLinks
|
|
187
|
-
|
|
212
|
+
const decisionIds = decisionLinks
|
|
213
|
+
.filter((l) => l.e2 === runId)
|
|
214
|
+
.map((l) => l.e1);
|
|
215
|
+
const decisions = decisionIds
|
|
216
|
+
.map((did) => this._buildDecisionTrace(did))
|
|
217
|
+
.filter(Boolean) as DecisionTrace[];
|
|
188
218
|
|
|
189
219
|
const get = (a: string) => entity.facts.find((f) => f.a === a)?.v;
|
|
190
220
|
|
|
@@ -207,7 +237,10 @@ export class AgentHarness {
|
|
|
207
237
|
.map((e) => this.getRun(e.id))
|
|
208
238
|
.filter((r): r is AgentRun => r !== null)
|
|
209
239
|
.filter((r) => !agentId || r.agentId === agentId)
|
|
210
|
-
.sort(
|
|
240
|
+
.sort(
|
|
241
|
+
(a, b) =>
|
|
242
|
+
new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(),
|
|
243
|
+
);
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
// -------------------------------------------------------------------------
|
|
@@ -235,7 +268,9 @@ export class AgentHarness {
|
|
|
235
268
|
...(input ? { input: JSON.stringify(input) } : {}),
|
|
236
269
|
...(output ? { output } : {}),
|
|
237
270
|
...(opts?.rationale ? { rationale: opts.rationale } : {}),
|
|
238
|
-
...(opts?.alternatives
|
|
271
|
+
...(opts?.alternatives
|
|
272
|
+
? { alternatives: JSON.stringify(opts.alternatives) }
|
|
273
|
+
: {}),
|
|
239
274
|
});
|
|
240
275
|
|
|
241
276
|
// Link decision to run and agent
|
|
@@ -262,7 +297,8 @@ export class AgentHarness {
|
|
|
262
297
|
opts?: { rationale?: string; relatedEntities?: string[] },
|
|
263
298
|
): Promise<ToolResult> {
|
|
264
299
|
const handler = this.toolHandlers.get(toolId);
|
|
265
|
-
if (!handler)
|
|
300
|
+
if (!handler)
|
|
301
|
+
throw new Error(`No handler registered for tool "${toolId}".`);
|
|
266
302
|
|
|
267
303
|
const result = await handler(input);
|
|
268
304
|
|
|
@@ -311,13 +347,21 @@ export class AgentHarness {
|
|
|
311
347
|
let inputParsed: Record<string, unknown> | undefined;
|
|
312
348
|
const inputRaw = get('input') as string | undefined;
|
|
313
349
|
if (inputRaw) {
|
|
314
|
-
try {
|
|
350
|
+
try {
|
|
351
|
+
inputParsed = JSON.parse(inputRaw);
|
|
352
|
+
} catch {
|
|
353
|
+
inputParsed = { raw: inputRaw };
|
|
354
|
+
}
|
|
315
355
|
}
|
|
316
356
|
|
|
317
357
|
let alternatives: string[] | undefined;
|
|
318
358
|
const altRaw = get('alternatives') as string | undefined;
|
|
319
359
|
if (altRaw) {
|
|
320
|
-
try {
|
|
360
|
+
try {
|
|
361
|
+
alternatives = JSON.parse(altRaw);
|
|
362
|
+
} catch {
|
|
363
|
+
alternatives = [altRaw];
|
|
364
|
+
}
|
|
321
365
|
}
|
|
322
366
|
|
|
323
367
|
return {
|