trellis 2.0.13 → 2.1.2
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/dist/cli/index.js +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
- package/package.json +2 -10
- package/dist/transformers.node-bx3q9d7k.js +0 -33130
- package/src/cli/index.ts +0 -3356
- package/src/core/agents/harness.ts +0 -380
- package/src/core/agents/index.ts +0 -18
- package/src/core/agents/types.ts +0 -90
- package/src/core/index.ts +0 -118
- package/src/core/kernel/middleware.ts +0 -44
- package/src/core/kernel/trellis-kernel.ts +0 -593
- package/src/core/ontology/builtins.ts +0 -248
- package/src/core/ontology/index.ts +0 -34
- package/src/core/ontology/registry.ts +0 -209
- package/src/core/ontology/types.ts +0 -124
- package/src/core/ontology/validator.ts +0 -382
- package/src/core/persist/backend.ts +0 -74
- package/src/core/persist/sqlite-backend.ts +0 -298
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/registry.ts +0 -322
- package/src/core/plugins/types.ts +0 -126
- package/src/core/query/datalog.ts +0 -188
- package/src/core/query/engine.ts +0 -370
- package/src/core/query/index.ts +0 -34
- package/src/core/query/parser.ts +0 -481
- package/src/core/query/types.ts +0 -200
- package/src/core/store/eav-store.ts +0 -467
- package/src/decisions/auto-capture.ts +0 -136
- package/src/decisions/hooks.ts +0 -163
- package/src/decisions/index.ts +0 -261
- package/src/decisions/types.ts +0 -103
- package/src/embeddings/auto-embed.ts +0 -248
- package/src/embeddings/chunker.ts +0 -327
- package/src/embeddings/index.ts +0 -48
- package/src/embeddings/model.ts +0 -112
- package/src/embeddings/search.ts +0 -305
- package/src/embeddings/store.ts +0 -313
- package/src/embeddings/types.ts +0 -92
- package/src/engine.ts +0 -1125
- package/src/garden/cluster.ts +0 -330
- package/src/garden/garden.ts +0 -306
- package/src/garden/index.ts +0 -29
- package/src/git/git-exporter.ts +0 -286
- package/src/git/git-importer.ts +0 -329
- package/src/git/git-reader.ts +0 -189
- package/src/git/index.ts +0 -22
- package/src/identity/governance.ts +0 -211
- package/src/identity/identity.ts +0 -224
- package/src/identity/index.ts +0 -30
- package/src/identity/signing-middleware.ts +0 -97
- package/src/index.ts +0 -29
- package/src/links/index.ts +0 -49
- package/src/links/lifecycle.ts +0 -400
- package/src/links/parser.ts +0 -484
- package/src/links/ref-index.ts +0 -186
- package/src/links/resolver.ts +0 -314
- package/src/links/types.ts +0 -108
- package/src/mcp/index.ts +0 -22
- package/src/mcp/server.ts +0 -1278
- package/src/semantic/csharp-parser.ts +0 -493
- package/src/semantic/go-parser.ts +0 -585
- package/src/semantic/index.ts +0 -34
- package/src/semantic/java-parser.ts +0 -456
- package/src/semantic/python-parser.ts +0 -659
- package/src/semantic/ruby-parser.ts +0 -446
- package/src/semantic/rust-parser.ts +0 -784
- package/src/semantic/semantic-merge.ts +0 -210
- package/src/semantic/ts-parser.ts +0 -681
- package/src/semantic/types.ts +0 -175
- package/src/sync/http-transport.ts +0 -144
- package/src/sync/index.ts +0 -43
- package/src/sync/memory-transport.ts +0 -66
- package/src/sync/multi-repo.ts +0 -200
- package/src/sync/reconciler.ts +0 -237
- package/src/sync/sync-engine.ts +0 -258
- package/src/sync/types.ts +0 -104
- package/src/sync/ws-transport.ts +0 -145
- package/src/ui/client.html +0 -695
- package/src/ui/server.ts +0 -419
- package/src/vcs/blob-store.ts +0 -124
- package/src/vcs/branch.ts +0 -150
- package/src/vcs/checkpoint.ts +0 -64
- package/src/vcs/decompose.ts +0 -469
- package/src/vcs/diff.ts +0 -409
- package/src/vcs/engine-context.ts +0 -26
- package/src/vcs/index.ts +0 -23
- package/src/vcs/issue.ts +0 -800
- package/src/vcs/merge.ts +0 -425
- package/src/vcs/milestone.ts +0 -124
- package/src/vcs/ops.ts +0 -59
- package/src/vcs/types.ts +0 -213
- package/src/vcs/vcs-middleware.ts +0 -81
- package/src/watcher/fs-watcher.ts +0 -255
- package/src/watcher/index.ts +0 -9
- package/src/watcher/ingestion.ts +0 -116
package/src/vcs/types.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TrellisVCS Type Definitions
|
|
3
|
-
*
|
|
4
|
-
* VCS-specific operation kinds, payloads, and entity types
|
|
5
|
-
* that extend the trellis-core kernel primitives.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { KernelOp } from '../core/persist/backend.js';
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// VCS Operation Kinds
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
export type VcsOpKind =
|
|
15
|
-
// Tier 0: File-level operations
|
|
16
|
-
| 'vcs:fileAdd'
|
|
17
|
-
| 'vcs:fileModify'
|
|
18
|
-
| 'vcs:fileDelete'
|
|
19
|
-
| 'vcs:fileRename'
|
|
20
|
-
// Tier 1: Structural operations
|
|
21
|
-
| 'vcs:dirAdd'
|
|
22
|
-
| 'vcs:dirDelete'
|
|
23
|
-
// VCS control operations
|
|
24
|
-
| 'vcs:branchCreate'
|
|
25
|
-
| 'vcs:branchDelete'
|
|
26
|
-
| 'vcs:branchAdvance'
|
|
27
|
-
| 'vcs:milestoneCreate'
|
|
28
|
-
| 'vcs:checkpointCreate'
|
|
29
|
-
| 'vcs:merge'
|
|
30
|
-
// Tier 2: AST-level semantic patches (future)
|
|
31
|
-
| 'vcs:symbolRename'
|
|
32
|
-
| 'vcs:symbolMove'
|
|
33
|
-
| 'vcs:symbolExtract'
|
|
34
|
-
| 'vcs:signatureChange'
|
|
35
|
-
// Issue tracking
|
|
36
|
-
| 'vcs:issueCreate'
|
|
37
|
-
| 'vcs:issueUpdate'
|
|
38
|
-
| 'vcs:issueStart'
|
|
39
|
-
| 'vcs:issuePause'
|
|
40
|
-
| 'vcs:issueResume'
|
|
41
|
-
| 'vcs:issueClose'
|
|
42
|
-
| 'vcs:issueReopen'
|
|
43
|
-
| 'vcs:criterionAdd'
|
|
44
|
-
| 'vcs:criterionUpdate'
|
|
45
|
-
// Issue blocking
|
|
46
|
-
| 'vcs:issueBlock'
|
|
47
|
-
| 'vcs:issueUnblock'
|
|
48
|
-
// Decision traces
|
|
49
|
-
| 'vcs:decisionRecord';
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// VCS Operation Payload
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
export interface VcsPayload {
|
|
56
|
-
// File operations
|
|
57
|
-
filePath?: string;
|
|
58
|
-
oldFilePath?: string;
|
|
59
|
-
contentHash?: string;
|
|
60
|
-
oldContentHash?: string;
|
|
61
|
-
size?: number;
|
|
62
|
-
language?: string;
|
|
63
|
-
|
|
64
|
-
// Branch operations
|
|
65
|
-
branchName?: string;
|
|
66
|
-
targetOpHash?: string;
|
|
67
|
-
sourceBranch?: string;
|
|
68
|
-
baseBranch?: string;
|
|
69
|
-
|
|
70
|
-
// Milestone operations
|
|
71
|
-
milestoneId?: string;
|
|
72
|
-
message?: string;
|
|
73
|
-
fromOpHash?: string;
|
|
74
|
-
toOpHash?: string;
|
|
75
|
-
|
|
76
|
-
// Checkpoint operations
|
|
77
|
-
trigger?: 'green-build' | 'interval' | 'op-count' | 'manual';
|
|
78
|
-
|
|
79
|
-
// Signature
|
|
80
|
-
signature?: string;
|
|
81
|
-
signedBy?: string;
|
|
82
|
-
|
|
83
|
-
// Issue tracking
|
|
84
|
-
issueId?: string;
|
|
85
|
-
issueTitle?: string;
|
|
86
|
-
issueStatus?: 'backlog' | 'queue' | 'in_progress' | 'paused' | 'closed';
|
|
87
|
-
issuePriority?: 'critical' | 'high' | 'medium' | 'low';
|
|
88
|
-
issueLabels?: string[];
|
|
89
|
-
parentIssueId?: string;
|
|
90
|
-
issueDescription?: string;
|
|
91
|
-
issueAssignee?: string;
|
|
92
|
-
pauseNote?: string;
|
|
93
|
-
blockedByIssueId?: string;
|
|
94
|
-
|
|
95
|
-
// Decision traces
|
|
96
|
-
decisionId?: string;
|
|
97
|
-
decisionContext?: string;
|
|
98
|
-
decisionRationale?: string;
|
|
99
|
-
decisionAlternatives?: string;
|
|
100
|
-
decisionToolName?: string;
|
|
101
|
-
decisionToolInput?: string;
|
|
102
|
-
decisionToolOutput?: string;
|
|
103
|
-
|
|
104
|
-
// Acceptance criteria
|
|
105
|
-
criterionId?: string;
|
|
106
|
-
criterionDescription?: string;
|
|
107
|
-
criterionCommand?: string;
|
|
108
|
-
criterionStatus?: 'pending' | 'passed' | 'failed';
|
|
109
|
-
criterionOutput?: string;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* A VcsOp mirrors KernelOp but widens `kind` to accept VCS-specific strings.
|
|
114
|
-
* We don't extend KernelOp directly because the kernel types `kind` as a
|
|
115
|
-
* narrow union; our VCS kinds are a superset.
|
|
116
|
-
*/
|
|
117
|
-
export interface VcsOp {
|
|
118
|
-
hash: string;
|
|
119
|
-
kind: VcsOpKind | string;
|
|
120
|
-
timestamp: string;
|
|
121
|
-
agentId: string;
|
|
122
|
-
previousHash?: string;
|
|
123
|
-
facts?: import('../core/store/eav-store.js').Fact[];
|
|
124
|
-
links?: import('../core/store/eav-store.js').Link[];
|
|
125
|
-
vcs?: VcsPayload;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// File Change Events (from watcher)
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
export interface FileChangeEvent {
|
|
133
|
-
type: 'add' | 'modify' | 'delete' | 'rename';
|
|
134
|
-
path: string;
|
|
135
|
-
oldPath?: string;
|
|
136
|
-
contentHash?: string;
|
|
137
|
-
oldContentHash?: string;
|
|
138
|
-
size?: number;
|
|
139
|
-
timestamp: string;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// Entity ID Helpers
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
export function fileEntityId(path: string): string {
|
|
147
|
-
return `file:${path}`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function dirEntityId(path: string): string {
|
|
151
|
-
return `dir:${path}`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function branchEntityId(name: string): string {
|
|
155
|
-
return `branch:${name}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function milestoneEntityId(hash: string): string {
|
|
159
|
-
return `milestone:${hash}`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function checkpointEntityId(hash: string): string {
|
|
163
|
-
return `checkpoint:${hash}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function issueEntityId(id: string): string {
|
|
167
|
-
return id.startsWith('issue:') ? id : `issue:${id}`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function criterionEntityId(issueId: string, index: number): string {
|
|
171
|
-
const bare = issueId.replace(/^issue:/, '');
|
|
172
|
-
return `criterion:${bare}:ac-${index}`;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export function decisionEntityId(id: string): string {
|
|
176
|
-
return id.startsWith('decision:') ? id : `decision:${id}`;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// Repository Config
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
|
|
183
|
-
export interface TrellisVcsConfig {
|
|
184
|
-
/** Absolute path to the repository root. */
|
|
185
|
-
rootPath: string;
|
|
186
|
-
|
|
187
|
-
/** Glob patterns to ignore (e.g. ['node_modules', '.git', '*.log']). */
|
|
188
|
-
ignorePatterns: string[];
|
|
189
|
-
|
|
190
|
-
/** Debounce interval for file watcher in ms. */
|
|
191
|
-
debounceMs: number;
|
|
192
|
-
|
|
193
|
-
/** Name of the default branch. */
|
|
194
|
-
defaultBranch: string;
|
|
195
|
-
|
|
196
|
-
/** Path to the .trellis database file. */
|
|
197
|
-
dbPath: string;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export const DEFAULT_CONFIG: Omit<TrellisVcsConfig, 'rootPath'> = {
|
|
201
|
-
ignorePatterns: [
|
|
202
|
-
'node_modules',
|
|
203
|
-
'.git',
|
|
204
|
-
'.trellis',
|
|
205
|
-
'dist',
|
|
206
|
-
'build',
|
|
207
|
-
'.DS_Store',
|
|
208
|
-
'*.log',
|
|
209
|
-
],
|
|
210
|
-
debounceMs: 300,
|
|
211
|
-
defaultBranch: 'main',
|
|
212
|
-
dbPath: '.trellis/trellis.db',
|
|
213
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VCS Middleware
|
|
3
|
-
*
|
|
4
|
-
* Intercepts VcsOps in the kernel middleware chain and decomposes
|
|
5
|
-
* them into primitive EAV store operations before passing to the
|
|
6
|
-
* next middleware (or the store).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
KernelMiddleware,
|
|
11
|
-
MiddlewareContext,
|
|
12
|
-
OpMiddlewareNext,
|
|
13
|
-
} from '../core/kernel/middleware.js';
|
|
14
|
-
import type { KernelOp } from '../core/persist/backend.js';
|
|
15
|
-
import { decompose } from './decompose.js';
|
|
16
|
-
import { isVcsOp } from './ops.js';
|
|
17
|
-
import type { VcsOp } from './types.js';
|
|
18
|
-
|
|
19
|
-
export class VcsMiddleware implements KernelMiddleware {
|
|
20
|
-
name = 'vcs';
|
|
21
|
-
|
|
22
|
-
async handleOp(
|
|
23
|
-
op: KernelOp,
|
|
24
|
-
ctx: MiddlewareContext,
|
|
25
|
-
next: OpMiddlewareNext,
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
if (!isVcsOp(op as any)) {
|
|
28
|
-
return next(op, ctx);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const vcsOp = op as unknown as VcsOp;
|
|
32
|
-
const decomposed = decompose(vcsOp);
|
|
33
|
-
|
|
34
|
-
// Apply the decomposed primitive operations to the store.
|
|
35
|
-
// We create synthetic KernelOps for each batch and pass them through.
|
|
36
|
-
if (decomposed.deleteFacts.length > 0) {
|
|
37
|
-
await next(
|
|
38
|
-
{
|
|
39
|
-
...op,
|
|
40
|
-
kind: 'deleteFacts' as any,
|
|
41
|
-
facts: decomposed.deleteFacts,
|
|
42
|
-
links: undefined,
|
|
43
|
-
} as KernelOp,
|
|
44
|
-
ctx,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
if (decomposed.deleteLinks.length > 0) {
|
|
48
|
-
await next(
|
|
49
|
-
{
|
|
50
|
-
...op,
|
|
51
|
-
kind: 'deleteLinks' as any,
|
|
52
|
-
facts: undefined,
|
|
53
|
-
links: decomposed.deleteLinks,
|
|
54
|
-
} as KernelOp,
|
|
55
|
-
ctx,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
if (decomposed.addFacts.length > 0) {
|
|
59
|
-
await next(
|
|
60
|
-
{
|
|
61
|
-
...op,
|
|
62
|
-
kind: 'addFacts' as any,
|
|
63
|
-
facts: decomposed.addFacts,
|
|
64
|
-
links: undefined,
|
|
65
|
-
} as KernelOp,
|
|
66
|
-
ctx,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
if (decomposed.addLinks.length > 0) {
|
|
70
|
-
await next(
|
|
71
|
-
{
|
|
72
|
-
...op,
|
|
73
|
-
kind: 'addLinks' as any,
|
|
74
|
-
facts: undefined,
|
|
75
|
-
links: decomposed.addLinks,
|
|
76
|
-
} as KernelOp,
|
|
77
|
-
ctx,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filesystem Watcher
|
|
3
|
-
*
|
|
4
|
-
* Monitors a directory tree for changes using Bun's fs.watch.
|
|
5
|
-
* Debounces rapid events and filters ignored paths.
|
|
6
|
-
* Emits FileChangeEvents to a callback.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { watch, type FSWatcher } from 'fs';
|
|
10
|
-
import { readdir, stat, readFile } from 'fs/promises';
|
|
11
|
-
import { join, relative } from 'path';
|
|
12
|
-
import type { FileChangeEvent } from '../vcs/types.js';
|
|
13
|
-
|
|
14
|
-
export interface FileWatcherConfig {
|
|
15
|
-
rootPath: string;
|
|
16
|
-
ignorePatterns: string[];
|
|
17
|
-
debounceMs: number;
|
|
18
|
-
onEvent: (event: FileChangeEvent) => void | Promise<void>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface ScanProgress {
|
|
22
|
-
phase: 'discovering' | 'hashing' | 'done';
|
|
23
|
-
current: number;
|
|
24
|
-
total: number;
|
|
25
|
-
message: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Computes SHA-256 content hash for a file.
|
|
30
|
-
*/
|
|
31
|
-
async function hashFile(filePath: string): Promise<string> {
|
|
32
|
-
const content = await readFile(filePath);
|
|
33
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', content);
|
|
34
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
35
|
-
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Checks if a path matches any ignore pattern (simple glob-like matching).
|
|
40
|
-
*/
|
|
41
|
-
function shouldIgnore(relPath: string, patterns: string[]): boolean {
|
|
42
|
-
for (const pattern of patterns) {
|
|
43
|
-
// Simple matching: exact segment match or extension glob
|
|
44
|
-
if (pattern.startsWith('*.')) {
|
|
45
|
-
const ext = pattern.slice(1); // e.g. '.log'
|
|
46
|
-
if (relPath.endsWith(ext)) return true;
|
|
47
|
-
} else if (relPath.includes(pattern)) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class FileWatcher {
|
|
55
|
-
private config: FileWatcherConfig;
|
|
56
|
-
private watchers: FSWatcher[] = [];
|
|
57
|
-
private debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
58
|
-
private knownFiles = new Map<string, string>(); // relPath → contentHash
|
|
59
|
-
private running = false;
|
|
60
|
-
|
|
61
|
-
constructor(config: FileWatcherConfig) {
|
|
62
|
-
this.config = config;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Scans the directory tree and builds an initial map of all tracked files.
|
|
67
|
-
* Returns the list of FileChangeEvents for the initial state (all adds).
|
|
68
|
-
*/
|
|
69
|
-
async scan(opts?: {
|
|
70
|
-
onProgress?: (progress: ScanProgress) => void;
|
|
71
|
-
}): Promise<FileChangeEvent[]> {
|
|
72
|
-
const events: FileChangeEvent[] = [];
|
|
73
|
-
opts?.onProgress?.({
|
|
74
|
-
phase: 'discovering',
|
|
75
|
-
current: 0,
|
|
76
|
-
total: 0,
|
|
77
|
-
message: 'Discovering existing files…',
|
|
78
|
-
});
|
|
79
|
-
const entries = await this.walkDir(this.config.rootPath);
|
|
80
|
-
opts?.onProgress?.({
|
|
81
|
-
phase: 'hashing',
|
|
82
|
-
current: 0,
|
|
83
|
-
total: entries.length,
|
|
84
|
-
message: `Hashing ${entries.length} existing files…`,
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < entries.length; i++) {
|
|
88
|
-
const absPath = entries[i];
|
|
89
|
-
const relPath = relative(this.config.rootPath, absPath);
|
|
90
|
-
if (shouldIgnore(relPath, this.config.ignorePatterns)) continue;
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const hash = await hashFile(absPath);
|
|
94
|
-
const stats = await stat(absPath);
|
|
95
|
-
this.knownFiles.set(relPath, hash);
|
|
96
|
-
events.push({
|
|
97
|
-
type: 'add',
|
|
98
|
-
path: relPath,
|
|
99
|
-
contentHash: hash,
|
|
100
|
-
size: stats.size,
|
|
101
|
-
timestamp: new Date().toISOString(),
|
|
102
|
-
});
|
|
103
|
-
} catch {
|
|
104
|
-
// File may have been deleted between scan and hash
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if ((i + 1) % 25 === 0 || i === entries.length - 1) {
|
|
108
|
-
opts?.onProgress?.({
|
|
109
|
-
phase: 'hashing',
|
|
110
|
-
current: i + 1,
|
|
111
|
-
total: entries.length,
|
|
112
|
-
message: `Hashed ${i + 1}/${entries.length} files`,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
opts?.onProgress?.({
|
|
118
|
-
phase: 'done',
|
|
119
|
-
current: events.length,
|
|
120
|
-
total: events.length,
|
|
121
|
-
message: `Discovered ${events.length} trackable files`,
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return events;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Starts watching the directory tree for changes.
|
|
129
|
-
*/
|
|
130
|
-
start(): void {
|
|
131
|
-
if (this.running) return;
|
|
132
|
-
this.running = true;
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const watcher = watch(
|
|
136
|
-
this.config.rootPath,
|
|
137
|
-
{ recursive: true },
|
|
138
|
-
(eventType, filename) => {
|
|
139
|
-
if (!filename) return;
|
|
140
|
-
const relPath = filename.toString();
|
|
141
|
-
if (shouldIgnore(relPath, this.config.ignorePatterns)) return;
|
|
142
|
-
this.debouncedHandle(relPath);
|
|
143
|
-
},
|
|
144
|
-
);
|
|
145
|
-
this.watchers.push(watcher);
|
|
146
|
-
} catch {
|
|
147
|
-
// recursive watch not supported on all platforms; fall back gracefully
|
|
148
|
-
console.warn('Recursive watch not supported; using scan-based polling.');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Stops all watchers.
|
|
154
|
-
*/
|
|
155
|
-
stop(): void {
|
|
156
|
-
this.running = false;
|
|
157
|
-
for (const w of this.watchers) {
|
|
158
|
-
w.close();
|
|
159
|
-
}
|
|
160
|
-
this.watchers = [];
|
|
161
|
-
for (const timer of this.debounceTimers.values()) {
|
|
162
|
-
clearTimeout(timer);
|
|
163
|
-
}
|
|
164
|
-
this.debounceTimers.clear();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Returns the current known file map (path → contentHash).
|
|
169
|
-
*/
|
|
170
|
-
getKnownFiles(): Map<string, string> {
|
|
171
|
-
return new Map(this.knownFiles);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private debouncedHandle(relPath: string): void {
|
|
175
|
-
const existing = this.debounceTimers.get(relPath);
|
|
176
|
-
if (existing) clearTimeout(existing);
|
|
177
|
-
|
|
178
|
-
const timer = setTimeout(async () => {
|
|
179
|
-
this.debounceTimers.delete(relPath);
|
|
180
|
-
await this.handleChange(relPath);
|
|
181
|
-
}, this.config.debounceMs);
|
|
182
|
-
|
|
183
|
-
this.debounceTimers.set(relPath, timer);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private async handleChange(relPath: string): Promise<void> {
|
|
187
|
-
const absPath = join(this.config.rootPath, relPath);
|
|
188
|
-
const known = this.knownFiles.get(relPath);
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
const stats = await stat(absPath);
|
|
192
|
-
if (!stats.isFile()) return;
|
|
193
|
-
|
|
194
|
-
const hash = await hashFile(absPath);
|
|
195
|
-
|
|
196
|
-
if (!known) {
|
|
197
|
-
// New file
|
|
198
|
-
this.knownFiles.set(relPath, hash);
|
|
199
|
-
await this.config.onEvent({
|
|
200
|
-
type: 'add',
|
|
201
|
-
path: relPath,
|
|
202
|
-
contentHash: hash,
|
|
203
|
-
size: stats.size,
|
|
204
|
-
timestamp: new Date().toISOString(),
|
|
205
|
-
});
|
|
206
|
-
} else if (known !== hash) {
|
|
207
|
-
// Modified file
|
|
208
|
-
const oldHash = known;
|
|
209
|
-
this.knownFiles.set(relPath, hash);
|
|
210
|
-
await this.config.onEvent({
|
|
211
|
-
type: 'modify',
|
|
212
|
-
path: relPath,
|
|
213
|
-
contentHash: hash,
|
|
214
|
-
oldContentHash: oldHash,
|
|
215
|
-
size: stats.size,
|
|
216
|
-
timestamp: new Date().toISOString(),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
// If hash is the same, no event (unchanged)
|
|
220
|
-
} catch {
|
|
221
|
-
// File doesn't exist → it was deleted
|
|
222
|
-
if (known) {
|
|
223
|
-
this.knownFiles.delete(relPath);
|
|
224
|
-
await this.config.onEvent({
|
|
225
|
-
type: 'delete',
|
|
226
|
-
path: relPath,
|
|
227
|
-
contentHash: known,
|
|
228
|
-
timestamp: new Date().toISOString(),
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private async walkDir(dir: string): Promise<string[]> {
|
|
235
|
-
const results: string[] = [];
|
|
236
|
-
try {
|
|
237
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
238
|
-
for (const entry of entries) {
|
|
239
|
-
const fullPath = join(dir, entry.name);
|
|
240
|
-
const relFromRoot = relative(this.config.rootPath, fullPath);
|
|
241
|
-
if (shouldIgnore(relFromRoot, this.config.ignorePatterns)) continue;
|
|
242
|
-
|
|
243
|
-
if (entry.isDirectory()) {
|
|
244
|
-
const sub = await this.walkDir(fullPath);
|
|
245
|
-
results.push(...sub);
|
|
246
|
-
} else if (entry.isFile()) {
|
|
247
|
-
results.push(fullPath);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} catch {
|
|
251
|
-
// Permission error or deleted dir
|
|
252
|
-
}
|
|
253
|
-
return results;
|
|
254
|
-
}
|
|
255
|
-
}
|
package/src/watcher/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Watcher
|
|
3
|
-
*
|
|
4
|
-
* Monitors a directory for filesystem changes and emits FileChangeEvents.
|
|
5
|
-
* Uses Bun's built-in fs.watch for zero-dependency file monitoring.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { FileWatcher } from './fs-watcher.js';
|
|
9
|
-
export { Ingestion } from './ingestion.js';
|
package/src/watcher/ingestion.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ingestion Pipeline
|
|
3
|
-
*
|
|
4
|
-
* Bridges the file watcher and the kernel: converts FileChangeEvents
|
|
5
|
-
* into VcsOps and applies them to the kernel via mutate().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { FileChangeEvent, VcsOpKind } from '../vcs/types.js';
|
|
9
|
-
import { createVcsOp } from '../vcs/ops.js';
|
|
10
|
-
import type { VcsOp } from '../vcs/types.js';
|
|
11
|
-
import { extname } from 'path';
|
|
12
|
-
|
|
13
|
-
// Simple language detection from file extension
|
|
14
|
-
const EXT_LANGUAGE: Record<string, string> = {
|
|
15
|
-
'.ts': 'typescript',
|
|
16
|
-
'.tsx': 'typescript',
|
|
17
|
-
'.js': 'javascript',
|
|
18
|
-
'.jsx': 'javascript',
|
|
19
|
-
'.py': 'python',
|
|
20
|
-
'.rs': 'rust',
|
|
21
|
-
'.go': 'go',
|
|
22
|
-
'.rb': 'ruby',
|
|
23
|
-
'.java': 'java',
|
|
24
|
-
'.c': 'c',
|
|
25
|
-
'.cpp': 'cpp',
|
|
26
|
-
'.h': 'c',
|
|
27
|
-
'.hpp': 'cpp',
|
|
28
|
-
'.cs': 'csharp',
|
|
29
|
-
'.swift': 'swift',
|
|
30
|
-
'.kt': 'kotlin',
|
|
31
|
-
'.md': 'markdown',
|
|
32
|
-
'.json': 'json',
|
|
33
|
-
'.yaml': 'yaml',
|
|
34
|
-
'.yml': 'yaml',
|
|
35
|
-
'.toml': 'toml',
|
|
36
|
-
'.html': 'html',
|
|
37
|
-
'.css': 'css',
|
|
38
|
-
'.scss': 'scss',
|
|
39
|
-
'.vue': 'vue',
|
|
40
|
-
'.svelte': 'svelte',
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
function detectLanguage(filePath: string): string | undefined {
|
|
44
|
-
const ext = extname(filePath).toLowerCase();
|
|
45
|
-
return EXT_LANGUAGE[ext];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class Ingestion {
|
|
49
|
-
private agentId: string;
|
|
50
|
-
private lastOpHash: string | undefined;
|
|
51
|
-
private onOp: (op: VcsOp) => void | Promise<void>;
|
|
52
|
-
|
|
53
|
-
constructor(opts: {
|
|
54
|
-
agentId: string;
|
|
55
|
-
lastOpHash?: string;
|
|
56
|
-
onOp: (op: VcsOp) => void | Promise<void>;
|
|
57
|
-
}) {
|
|
58
|
-
this.agentId = opts.agentId;
|
|
59
|
-
this.lastOpHash = opts.lastOpHash;
|
|
60
|
-
this.onOp = opts.onOp;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Processes a single FileChangeEvent, producing and emitting a VcsOp.
|
|
65
|
-
*/
|
|
66
|
-
async process(event: FileChangeEvent): Promise<VcsOp> {
|
|
67
|
-
let kind: VcsOpKind;
|
|
68
|
-
|
|
69
|
-
switch (event.type) {
|
|
70
|
-
case 'add':
|
|
71
|
-
kind = 'vcs:fileAdd';
|
|
72
|
-
break;
|
|
73
|
-
case 'modify':
|
|
74
|
-
kind = 'vcs:fileModify';
|
|
75
|
-
break;
|
|
76
|
-
case 'delete':
|
|
77
|
-
kind = 'vcs:fileDelete';
|
|
78
|
-
break;
|
|
79
|
-
case 'rename':
|
|
80
|
-
kind = 'vcs:fileRename';
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const op = await createVcsOp(kind, {
|
|
85
|
-
agentId: this.agentId,
|
|
86
|
-
previousHash: this.lastOpHash,
|
|
87
|
-
vcs: {
|
|
88
|
-
filePath: event.path,
|
|
89
|
-
oldFilePath: event.oldPath,
|
|
90
|
-
contentHash: event.contentHash,
|
|
91
|
-
oldContentHash: event.oldContentHash,
|
|
92
|
-
size: event.size,
|
|
93
|
-
language: detectLanguage(event.path),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
this.lastOpHash = op.hash;
|
|
98
|
-
await this.onOp(op);
|
|
99
|
-
return op;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Processes a batch of FileChangeEvents in order.
|
|
104
|
-
*/
|
|
105
|
-
async processBatch(events: FileChangeEvent[]): Promise<VcsOp[]> {
|
|
106
|
-
const ops: VcsOp[] = [];
|
|
107
|
-
for (const event of events) {
|
|
108
|
-
ops.push(await this.process(event));
|
|
109
|
-
}
|
|
110
|
-
return ops;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getLastOpHash(): string | undefined {
|
|
114
|
-
return this.lastOpHash;
|
|
115
|
-
}
|
|
116
|
-
}
|