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
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Signing Middleware
|
|
3
|
-
*
|
|
4
|
-
* DESIGN.md §6.2 — Every op can be cryptographically signed by its author.
|
|
5
|
-
*
|
|
6
|
-
* This module provides:
|
|
7
|
-
* - `signOp`: Sign a VcsOp with a local identity's private key.
|
|
8
|
-
* - `verifyOp`: Verify the signature on a VcsOp.
|
|
9
|
-
* - `SignatureVerificationMiddleware`: Middleware that rejects ops with
|
|
10
|
-
* invalid signatures on remote ops.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { VcsOp } from '../vcs/types.js';
|
|
14
|
-
import { signMessage, verifySignature } from './identity.js';
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Op signing
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Sign a VcsOp in-place using the given private key.
|
|
22
|
-
* Sets `vcs.signature` and `vcs.signedBy` on the op.
|
|
23
|
-
*/
|
|
24
|
-
export function signOp(
|
|
25
|
-
op: VcsOp,
|
|
26
|
-
privateKeyBase64: string,
|
|
27
|
-
identityEntityId: string,
|
|
28
|
-
): VcsOp {
|
|
29
|
-
if (!op.vcs) {
|
|
30
|
-
op.vcs = {};
|
|
31
|
-
}
|
|
32
|
-
op.vcs.signature = signMessage(op.hash, privateKeyBase64);
|
|
33
|
-
op.vcs.signedBy = identityEntityId;
|
|
34
|
-
return op;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Verify the signature on a VcsOp.
|
|
39
|
-
* Returns true if the op has a valid signature, false if invalid.
|
|
40
|
-
* Returns null if the op has no signature (unsigned).
|
|
41
|
-
*/
|
|
42
|
-
export function verifyOp(
|
|
43
|
-
op: VcsOp,
|
|
44
|
-
publicKeyBase64: string,
|
|
45
|
-
): boolean | null {
|
|
46
|
-
if (!op.vcs?.signature) return null;
|
|
47
|
-
return verifySignature(op.hash, op.vcs.signature, publicKeyBase64);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Middleware interface
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
export interface IdentityResolver {
|
|
55
|
-
/** Resolve an identity entity ID to its public key (base64). */
|
|
56
|
-
resolvePublicKey(entityId: string): string | null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface SignatureVerificationResult {
|
|
60
|
-
valid: boolean;
|
|
61
|
-
op: VcsOp;
|
|
62
|
-
reason?: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Verify all signatures on a batch of ops.
|
|
67
|
-
* Returns results for ops that have signatures.
|
|
68
|
-
*/
|
|
69
|
-
export function verifyOpBatch(
|
|
70
|
-
ops: VcsOp[],
|
|
71
|
-
resolver: IdentityResolver,
|
|
72
|
-
): SignatureVerificationResult[] {
|
|
73
|
-
const results: SignatureVerificationResult[] = [];
|
|
74
|
-
|
|
75
|
-
for (const op of ops) {
|
|
76
|
-
if (!op.vcs?.signature || !op.vcs?.signedBy) continue;
|
|
77
|
-
|
|
78
|
-
const publicKey = resolver.resolvePublicKey(op.vcs.signedBy);
|
|
79
|
-
if (!publicKey) {
|
|
80
|
-
results.push({
|
|
81
|
-
valid: false,
|
|
82
|
-
op,
|
|
83
|
-
reason: `Unknown identity: ${op.vcs.signedBy}`,
|
|
84
|
-
});
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const valid = verifySignature(op.hash, op.vcs.signature, publicKey);
|
|
89
|
-
results.push({
|
|
90
|
-
valid,
|
|
91
|
-
op,
|
|
92
|
-
reason: valid ? undefined : `Invalid signature on op ${op.hash}`,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return results;
|
|
97
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TrellisVCS — Graph-native, code-first version control
|
|
3
|
-
*
|
|
4
|
-
* @module trellisvcs
|
|
5
|
-
*
|
|
6
|
-
* Public API surface. Import {@link TrellisVcsEngine} as the main entry point,
|
|
7
|
-
* plus core VCS types, the {@link FileWatcher}, and the {@link Ingestion}
|
|
8
|
-
* pipeline.
|
|
9
|
-
*
|
|
10
|
-
* For sub-modules, import directly from:
|
|
11
|
-
* - `./garden/index.js` — Idea Garden cluster detection + query
|
|
12
|
-
* - `./semantic/index.js` — Semantic parsing + diff/merge
|
|
13
|
-
* - `./sync/index.js` — Peer sync + CRDT reconciler
|
|
14
|
-
* - `./identity/index.js` — Ed25519 identity + governance
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export { TrellisVcsEngine } from './engine.js';
|
|
18
|
-
export * from './vcs/index.js';
|
|
19
|
-
export { FileWatcher } from './watcher/fs-watcher.js';
|
|
20
|
-
export { Ingestion } from './watcher/ingestion.js';
|
|
21
|
-
|
|
22
|
-
// Core kernel (generic graph CRUD, independent of VCS)
|
|
23
|
-
export { TrellisKernel } from './core/kernel/trellis-kernel.js';
|
|
24
|
-
export { SqliteKernelBackend } from './core/persist/sqlite-backend.js';
|
|
25
|
-
export type {
|
|
26
|
-
KernelConfig,
|
|
27
|
-
MutateResult,
|
|
28
|
-
EntityRecord,
|
|
29
|
-
} from './core/kernel/trellis-kernel.js';
|
package/src/links/index.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Linked Markdown
|
|
3
|
-
*
|
|
4
|
-
* Wiki-link parser, entity reference resolver, and bidirectional
|
|
5
|
-
* reference index for [[...]] syntax in markdown and doc-comments.
|
|
6
|
-
*
|
|
7
|
-
* @see TRL-11
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
parseFileRefs,
|
|
12
|
-
parseMarkdownRefs,
|
|
13
|
-
parseDocCommentRefs,
|
|
14
|
-
parseRefContent,
|
|
15
|
-
inferNamespace,
|
|
16
|
-
} from './parser.js';
|
|
17
|
-
export { resolveRef, resolveRefs, createResolverContext } from './resolver.js';
|
|
18
|
-
export type { ResolverContext, Enginelike } from './resolver.js';
|
|
19
|
-
export {
|
|
20
|
-
buildRefIndex,
|
|
21
|
-
updateFileInIndex,
|
|
22
|
-
removeFileFromIndex,
|
|
23
|
-
getOutgoingRefs,
|
|
24
|
-
getBacklinks,
|
|
25
|
-
getReferencedEntities,
|
|
26
|
-
getFilesWithRefs,
|
|
27
|
-
getIndexStats,
|
|
28
|
-
} from './ref-index.js';
|
|
29
|
-
export {
|
|
30
|
-
StaleRefRegistry,
|
|
31
|
-
buildRenameProposal,
|
|
32
|
-
applyRenameProposal,
|
|
33
|
-
handleSymbolDeletion,
|
|
34
|
-
handleFileDeletion,
|
|
35
|
-
getDiagnostics,
|
|
36
|
-
processSemanticPatches,
|
|
37
|
-
} from './lifecycle.js';
|
|
38
|
-
export type { StaleRef, RefDiagnostic, LifecycleEvent } from './lifecycle.js';
|
|
39
|
-
export type {
|
|
40
|
-
EntityRef,
|
|
41
|
-
RefSource,
|
|
42
|
-
RefContext,
|
|
43
|
-
RefNamespace,
|
|
44
|
-
ResolvedRef,
|
|
45
|
-
RefState,
|
|
46
|
-
RefIndex,
|
|
47
|
-
RefUpdateProposal,
|
|
48
|
-
RefRewrite,
|
|
49
|
-
} from './types.js';
|
package/src/links/lifecycle.ts
DELETED
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ref Lifecycle — Rename Prompts & Stale Detection
|
|
3
|
-
*
|
|
4
|
-
* Handles the three-tier diagnostic model for wiki-link references:
|
|
5
|
-
* - Resolved: target exists and resolves
|
|
6
|
-
* - Stale (warning): target was renamed or deleted, known provenance
|
|
7
|
-
* - Broken (error): target never existed
|
|
8
|
-
*
|
|
9
|
-
* On symbol rename → build a RefUpdateProposal for user confirmation.
|
|
10
|
-
* On symbol delete → mark affected refs as stale (no file modification).
|
|
11
|
-
*
|
|
12
|
-
* @see TRL-14
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type {
|
|
16
|
-
EntityRef,
|
|
17
|
-
RefIndex,
|
|
18
|
-
RefSource,
|
|
19
|
-
RefUpdateProposal,
|
|
20
|
-
RefRewrite,
|
|
21
|
-
RefState,
|
|
22
|
-
} from './types.js';
|
|
23
|
-
import { getBacklinks } from './ref-index.js';
|
|
24
|
-
import { readFileSync } from 'fs';
|
|
25
|
-
import { join } from 'path';
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Stale Ref Tracking
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
export interface StaleRef {
|
|
32
|
-
/** The entity ID that became stale */
|
|
33
|
-
entityId: string;
|
|
34
|
-
/** Why it became stale */
|
|
35
|
-
reason: 'renamed' | 'deleted';
|
|
36
|
-
/** The op hash that caused the staleness */
|
|
37
|
-
causeOpHash?: string;
|
|
38
|
-
/** For renames: the new name */
|
|
39
|
-
newTarget?: string;
|
|
40
|
-
/** Timestamp when it became stale */
|
|
41
|
-
timestamp: string;
|
|
42
|
-
/** Sources that reference this stale entity */
|
|
43
|
-
sources: RefSource[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* In-memory stale ref registry.
|
|
48
|
-
* Maps entity ID → StaleRef.
|
|
49
|
-
*/
|
|
50
|
-
export class StaleRefRegistry {
|
|
51
|
-
private staleRefs: Map<string, StaleRef> = new Map();
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Mark an entity as stale due to rename or deletion.
|
|
55
|
-
*/
|
|
56
|
-
markStale(
|
|
57
|
-
entityId: string,
|
|
58
|
-
reason: 'renamed' | 'deleted',
|
|
59
|
-
sources: RefSource[],
|
|
60
|
-
opts?: { causeOpHash?: string; newTarget?: string },
|
|
61
|
-
): StaleRef {
|
|
62
|
-
const entry: StaleRef = {
|
|
63
|
-
entityId,
|
|
64
|
-
reason,
|
|
65
|
-
causeOpHash: opts?.causeOpHash,
|
|
66
|
-
newTarget: opts?.newTarget,
|
|
67
|
-
timestamp: new Date().toISOString(),
|
|
68
|
-
sources,
|
|
69
|
-
};
|
|
70
|
-
this.staleRefs.set(entityId, entry);
|
|
71
|
-
return entry;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Remove stale status (e.g. after user accepts rename update).
|
|
76
|
-
*/
|
|
77
|
-
clearStale(entityId: string): void {
|
|
78
|
-
this.staleRefs.delete(entityId);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check if an entity is stale.
|
|
83
|
-
*/
|
|
84
|
-
isStale(entityId: string): boolean {
|
|
85
|
-
return this.staleRefs.has(entityId);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get stale info for an entity.
|
|
90
|
-
*/
|
|
91
|
-
getStale(entityId: string): StaleRef | undefined {
|
|
92
|
-
return this.staleRefs.get(entityId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get all stale refs.
|
|
97
|
-
*/
|
|
98
|
-
getAllStale(): StaleRef[] {
|
|
99
|
-
return [...this.staleRefs.values()];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get stale refs filtered by reason.
|
|
104
|
-
*/
|
|
105
|
-
getByReason(reason: 'renamed' | 'deleted'): StaleRef[] {
|
|
106
|
-
return this.getAllStale().filter((s) => s.reason === reason);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Rename → RefUpdateProposal
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Build a RefUpdateProposal when a symbol is renamed.
|
|
116
|
-
* Scans the ref index for all references to the old symbol name
|
|
117
|
-
* and produces a list of rewrites.
|
|
118
|
-
*
|
|
119
|
-
* Does NOT modify any files — returns a proposal for user confirmation.
|
|
120
|
-
*/
|
|
121
|
-
export function buildRenameProposal(
|
|
122
|
-
index: RefIndex,
|
|
123
|
-
filePath: string,
|
|
124
|
-
oldName: string,
|
|
125
|
-
newName: string,
|
|
126
|
-
): RefUpdateProposal {
|
|
127
|
-
// Entity ID for the old symbol
|
|
128
|
-
const oldEntityId = `symbol:${filePath}#${oldName}`;
|
|
129
|
-
const newEntityId = `symbol:${filePath}#${newName}`;
|
|
130
|
-
|
|
131
|
-
// Find all backlinks to the old symbol
|
|
132
|
-
const sources = getBacklinks(index, oldEntityId);
|
|
133
|
-
|
|
134
|
-
// Also check for refs using the fallback entity ID pattern
|
|
135
|
-
const altEntityId = `symbol:${filePath}#${oldName}`;
|
|
136
|
-
const altSources = getBacklinks(index, altEntityId);
|
|
137
|
-
const allSources = deduplicateSources([...sources, ...altSources]);
|
|
138
|
-
|
|
139
|
-
const rewrites: RefRewrite[] = [];
|
|
140
|
-
const affectedFiles = new Set<string>();
|
|
141
|
-
|
|
142
|
-
for (const source of allSources) {
|
|
143
|
-
affectedFiles.add(source.filePath);
|
|
144
|
-
|
|
145
|
-
// Build the old and new text for the rewrite
|
|
146
|
-
const oldText = buildRefText(filePath, oldName);
|
|
147
|
-
const newText = buildRefText(filePath, newName);
|
|
148
|
-
|
|
149
|
-
rewrites.push({
|
|
150
|
-
filePath: source.filePath,
|
|
151
|
-
line: source.line,
|
|
152
|
-
col: source.col,
|
|
153
|
-
oldText,
|
|
154
|
-
newText,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
oldTarget: oldEntityId,
|
|
160
|
-
newTarget: newEntityId,
|
|
161
|
-
affectedFiles: [...affectedFiles],
|
|
162
|
-
rewrites,
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Apply a RefUpdateProposal by rewriting files.
|
|
168
|
-
* Reads each affected file, applies the rewrites, and writes back.
|
|
169
|
-
*
|
|
170
|
-
* Returns the list of files that were actually modified.
|
|
171
|
-
*/
|
|
172
|
-
export function applyRenameProposal(
|
|
173
|
-
proposal: RefUpdateProposal,
|
|
174
|
-
rootPath: string,
|
|
175
|
-
): string[] {
|
|
176
|
-
const modifiedFiles: string[] = [];
|
|
177
|
-
|
|
178
|
-
// Group rewrites by file
|
|
179
|
-
const byFile = new Map<string, RefRewrite[]>();
|
|
180
|
-
for (const rw of proposal.rewrites) {
|
|
181
|
-
const existing = byFile.get(rw.filePath) ?? [];
|
|
182
|
-
existing.push(rw);
|
|
183
|
-
byFile.set(rw.filePath, existing);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
for (const [filePath, rewrites] of byFile) {
|
|
187
|
-
try {
|
|
188
|
-
const absPath = join(rootPath, filePath);
|
|
189
|
-
let content = readFileSync(absPath, 'utf-8');
|
|
190
|
-
let modified = false;
|
|
191
|
-
|
|
192
|
-
for (const rw of rewrites) {
|
|
193
|
-
// Replace all occurrences of the old ref text with the new one
|
|
194
|
-
if (content.includes(rw.oldText)) {
|
|
195
|
-
content = content.replaceAll(rw.oldText, rw.newText);
|
|
196
|
-
modified = true;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (modified) {
|
|
201
|
-
const { writeFileSync } = require('fs');
|
|
202
|
-
writeFileSync(absPath, content);
|
|
203
|
-
modifiedFiles.push(filePath);
|
|
204
|
-
}
|
|
205
|
-
} catch {
|
|
206
|
-
// File may have been deleted or moved — skip
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return modifiedFiles;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
// Deletion → Stale marking
|
|
215
|
-
// ---------------------------------------------------------------------------
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Handle a symbol deletion by marking all referencing refs as stale.
|
|
219
|
-
* Does NOT modify any files.
|
|
220
|
-
*
|
|
221
|
-
* Returns the StaleRef entry for diagnostic display.
|
|
222
|
-
*/
|
|
223
|
-
export function handleSymbolDeletion(
|
|
224
|
-
index: RefIndex,
|
|
225
|
-
registry: StaleRefRegistry,
|
|
226
|
-
filePath: string,
|
|
227
|
-
symbolName: string,
|
|
228
|
-
causeOpHash?: string,
|
|
229
|
-
): StaleRef | null {
|
|
230
|
-
const entityId = `symbol:${filePath}#${symbolName}`;
|
|
231
|
-
const sources = getBacklinks(index, entityId);
|
|
232
|
-
|
|
233
|
-
if (sources.length === 0) return null;
|
|
234
|
-
|
|
235
|
-
return registry.markStale(entityId, 'deleted', sources, { causeOpHash });
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Handle a file deletion by marking all refs to that file (and its symbols) as stale.
|
|
240
|
-
*/
|
|
241
|
-
export function handleFileDeletion(
|
|
242
|
-
index: RefIndex,
|
|
243
|
-
registry: StaleRefRegistry,
|
|
244
|
-
filePath: string,
|
|
245
|
-
causeOpHash?: string,
|
|
246
|
-
): StaleRef | null {
|
|
247
|
-
const entityId = `file:${filePath}`;
|
|
248
|
-
const sources = getBacklinks(index, entityId);
|
|
249
|
-
|
|
250
|
-
if (sources.length === 0) return null;
|
|
251
|
-
|
|
252
|
-
return registry.markStale(entityId, 'deleted', sources, { causeOpHash });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
// Diagnostic queries
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
|
|
259
|
-
export type RefDiagnostic = {
|
|
260
|
-
entityId: string;
|
|
261
|
-
state: RefState;
|
|
262
|
-
source: RefSource;
|
|
263
|
-
message: string;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Produce diagnostics for all refs in the index.
|
|
268
|
-
* Combines stale registry info with broken ref detection.
|
|
269
|
-
*/
|
|
270
|
-
export function getDiagnostics(
|
|
271
|
-
index: RefIndex,
|
|
272
|
-
registry: StaleRefRegistry,
|
|
273
|
-
resolvedEntityIds: Set<string>,
|
|
274
|
-
): RefDiagnostic[] {
|
|
275
|
-
const diagnostics: RefDiagnostic[] = [];
|
|
276
|
-
|
|
277
|
-
// Walk all outgoing refs
|
|
278
|
-
for (const [filePath, refs] of index.outgoing) {
|
|
279
|
-
for (const ref of refs) {
|
|
280
|
-
const entityId = buildEntityIdFromRef(ref);
|
|
281
|
-
|
|
282
|
-
// Check stale first
|
|
283
|
-
const staleInfo = registry.getStale(entityId);
|
|
284
|
-
if (staleInfo) {
|
|
285
|
-
const reason =
|
|
286
|
-
staleInfo.reason === 'renamed'
|
|
287
|
-
? `renamed to ${staleInfo.newTarget}`
|
|
288
|
-
: 'removed';
|
|
289
|
-
diagnostics.push({
|
|
290
|
-
entityId,
|
|
291
|
-
state: 'stale',
|
|
292
|
-
source: ref.source,
|
|
293
|
-
message: `Reference to '${ref.target}${ref.anchor ? '#' + ref.anchor : ''}' is stale: target was ${reason}`,
|
|
294
|
-
});
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Check if resolved
|
|
299
|
-
if (!resolvedEntityIds.has(entityId)) {
|
|
300
|
-
diagnostics.push({
|
|
301
|
-
entityId,
|
|
302
|
-
state: 'broken',
|
|
303
|
-
source: ref.source,
|
|
304
|
-
message: `Cannot resolve reference: '${ref.target}${ref.anchor ? '#' + ref.anchor : ''}' does not exist`,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return diagnostics;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
// SemanticPatch integration
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
import type { SemanticPatch } from '../semantic/types.js';
|
|
318
|
-
|
|
319
|
-
export interface LifecycleEvent {
|
|
320
|
-
type: 'rename-proposal' | 'stale-detected';
|
|
321
|
-
filePath: string;
|
|
322
|
-
proposal?: RefUpdateProposal;
|
|
323
|
-
staleRef?: StaleRef;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Process semantic patches and produce lifecycle events.
|
|
328
|
-
* This is the main integration point — call this when sdiff detects changes.
|
|
329
|
-
*/
|
|
330
|
-
export function processSemanticPatches(
|
|
331
|
-
patches: SemanticPatch[],
|
|
332
|
-
filePath: string,
|
|
333
|
-
index: RefIndex,
|
|
334
|
-
registry: StaleRefRegistry,
|
|
335
|
-
causeOpHash?: string,
|
|
336
|
-
): LifecycleEvent[] {
|
|
337
|
-
const events: LifecycleEvent[] = [];
|
|
338
|
-
|
|
339
|
-
for (const patch of patches) {
|
|
340
|
-
if (patch.kind === 'symbolRename') {
|
|
341
|
-
const proposal = buildRenameProposal(
|
|
342
|
-
index,
|
|
343
|
-
filePath,
|
|
344
|
-
patch.oldName,
|
|
345
|
-
patch.newName,
|
|
346
|
-
);
|
|
347
|
-
if (proposal.rewrites.length > 0) {
|
|
348
|
-
events.push({
|
|
349
|
-
type: 'rename-proposal',
|
|
350
|
-
filePath,
|
|
351
|
-
proposal,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (patch.kind === 'symbolRemove') {
|
|
357
|
-
const staleRef = handleSymbolDeletion(
|
|
358
|
-
index,
|
|
359
|
-
registry,
|
|
360
|
-
filePath,
|
|
361
|
-
patch.entityName,
|
|
362
|
-
causeOpHash,
|
|
363
|
-
);
|
|
364
|
-
if (staleRef) {
|
|
365
|
-
events.push({
|
|
366
|
-
type: 'stale-detected',
|
|
367
|
-
filePath,
|
|
368
|
-
staleRef,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return events;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// ---------------------------------------------------------------------------
|
|
378
|
-
// Helpers
|
|
379
|
-
// ---------------------------------------------------------------------------
|
|
380
|
-
|
|
381
|
-
function buildRefText(filePath: string, symbolName: string): string {
|
|
382
|
-
return `[[${filePath}#${symbolName}]]`;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function buildEntityIdFromRef(ref: EntityRef): string {
|
|
386
|
-
if (ref.anchor) {
|
|
387
|
-
return `${ref.namespace}:${ref.target}#${ref.anchor}`;
|
|
388
|
-
}
|
|
389
|
-
return `${ref.namespace}:${ref.target}`;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function deduplicateSources(sources: RefSource[]): RefSource[] {
|
|
393
|
-
const seen = new Set<string>();
|
|
394
|
-
return sources.filter((s) => {
|
|
395
|
-
const key = `${s.filePath}:${s.line}:${s.col}`;
|
|
396
|
-
if (seen.has(key)) return false;
|
|
397
|
-
seen.add(key);
|
|
398
|
-
return true;
|
|
399
|
-
});
|
|
400
|
-
}
|