clearctx 3.0.0
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/CHANGELOG.md +71 -0
- package/LICENSE +21 -0
- package/README.md +1006 -0
- package/STRATEGY.md +485 -0
- package/bin/cli.js +1756 -0
- package/bin/continuity-hook.js +118 -0
- package/bin/mcp.js +27 -0
- package/bin/setup.js +929 -0
- package/package.json +56 -0
- package/src/artifact-store.js +710 -0
- package/src/atomic-io.js +99 -0
- package/src/briefing-generator.js +451 -0
- package/src/continuity-hooks.js +253 -0
- package/src/contract-store.js +525 -0
- package/src/decision-journal.js +229 -0
- package/src/delegate.js +348 -0
- package/src/dependency-resolver.js +453 -0
- package/src/diff-engine.js +473 -0
- package/src/file-lock.js +161 -0
- package/src/index.js +61 -0
- package/src/lineage-graph.js +402 -0
- package/src/manager.js +510 -0
- package/src/mcp-server.js +3501 -0
- package/src/pattern-registry.js +221 -0
- package/src/pipeline-engine.js +618 -0
- package/src/prompts.js +1217 -0
- package/src/safety-net.js +170 -0
- package/src/session-snapshot.js +508 -0
- package/src/snapshot-engine.js +490 -0
- package/src/stale-detector.js +169 -0
- package/src/store.js +131 -0
- package/src/stream-session.js +463 -0
- package/src/team-hub.js +615 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lineage-graph.js
|
|
3
|
+
* Layer 3: Artifact Lineage Graph - Track data provenance and impact analysis
|
|
4
|
+
*
|
|
5
|
+
* This module provides methods to traverse the lineage graph embedded in artifact version files.
|
|
6
|
+
* The lineage graph is NOT a separate index - it's stored directly in each version file's
|
|
7
|
+
* lineage field (producedBy and derivedFrom).
|
|
8
|
+
*
|
|
9
|
+
* Key concepts:
|
|
10
|
+
* - Upstream: What artifacts was this derived from? (follow derivedFrom)
|
|
11
|
+
* - Downstream: What artifacts depend on this? (scan all artifacts for references)
|
|
12
|
+
* - Impact: What would be affected if this artifact changes?
|
|
13
|
+
* - Stale: Which artifacts reference outdated versions?
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// Import Layer 2 ArtifactStore
|
|
20
|
+
const ArtifactStore = require('./artifact-store');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* LineageGraph provides methods to traverse artifact dependencies and analyze impact
|
|
24
|
+
*
|
|
25
|
+
* The lineage graph is stored in each artifact version file:
|
|
26
|
+
* {
|
|
27
|
+
* lineage: {
|
|
28
|
+
* producedBy: { contractId, session } | null,
|
|
29
|
+
* derivedFrom: ["other-artifact@v1", ...] // format: "artifactId@vN"
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
class LineageGraph {
|
|
34
|
+
/**
|
|
35
|
+
* Create a new LineageGraph instance
|
|
36
|
+
* @param {string} teamName - Name of the team (default: 'default')
|
|
37
|
+
*/
|
|
38
|
+
constructor(teamName = 'default') {
|
|
39
|
+
// Create an ArtifactStore instance to access artifact data
|
|
40
|
+
this.artifacts = new ArtifactStore(teamName);
|
|
41
|
+
|
|
42
|
+
// Store team name for reference
|
|
43
|
+
this.teamName = teamName;
|
|
44
|
+
|
|
45
|
+
// Store the data directory path for scanning operations
|
|
46
|
+
this.dataDir = this.artifacts.dataDir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parse a lineage reference like "artifactId@vN" into components
|
|
51
|
+
* @param {string} ref - Reference string (format: "artifactId@vN" or just "artifactId")
|
|
52
|
+
* @returns {Object} { artifactId, version } where version is number or null for "latest"
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
_parseRef(ref) {
|
|
56
|
+
// Check if the reference includes a version suffix
|
|
57
|
+
const atIndex = ref.lastIndexOf('@v');
|
|
58
|
+
|
|
59
|
+
if (atIndex === -1) {
|
|
60
|
+
// No version specified - means "latest"
|
|
61
|
+
return { artifactId: ref, version: null };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Split into artifact ID and version
|
|
65
|
+
const artifactId = ref.substring(0, atIndex);
|
|
66
|
+
const versionStr = ref.substring(atIndex + 2); // Skip "@v"
|
|
67
|
+
const version = parseInt(versionStr, 10);
|
|
68
|
+
|
|
69
|
+
return { artifactId, version };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scan all artifact directories and collect latest version files
|
|
74
|
+
* @returns {Map<string, Object>} Map of artifactId -> latest version data
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
_scanAllVersionFiles() {
|
|
78
|
+
const results = new Map();
|
|
79
|
+
|
|
80
|
+
// Check if data directory exists
|
|
81
|
+
if (!fs.existsSync(this.dataDir)) {
|
|
82
|
+
return results; // Empty map
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Get all artifact directories
|
|
86
|
+
const artifactDirs = fs.readdirSync(this.dataDir);
|
|
87
|
+
|
|
88
|
+
// Process each artifact directory
|
|
89
|
+
for (const artifactId of artifactDirs) {
|
|
90
|
+
const artifactDir = path.join(this.dataDir, artifactId);
|
|
91
|
+
|
|
92
|
+
// Skip if not a directory
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.statSync(artifactDir).isDirectory()) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
continue; // Skip if can't access
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Get the latest version from the artifact store
|
|
102
|
+
const latestVersion = this.artifacts.get(artifactId);
|
|
103
|
+
|
|
104
|
+
if (latestVersion) {
|
|
105
|
+
results.set(artifactId, latestVersion);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the upstream dependency tree for an artifact
|
|
114
|
+
* Shows what artifacts this one was derived from, recursively
|
|
115
|
+
* @param {string} artifactId - The artifact identifier
|
|
116
|
+
* @param {number|null} [version=null] - Version number (null = latest)
|
|
117
|
+
* @returns {Object|null} Tree: { artifactId, version, parents: [...] } or null if not found
|
|
118
|
+
*/
|
|
119
|
+
getUpstream(artifactId, version = null) {
|
|
120
|
+
// Use a Set to track visited nodes and prevent cycles
|
|
121
|
+
const visited = new Set();
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Recursive helper to build upstream tree
|
|
125
|
+
* @param {string} id - Artifact ID
|
|
126
|
+
* @param {number|null} ver - Version number
|
|
127
|
+
* @returns {Object|null} Tree node
|
|
128
|
+
*/
|
|
129
|
+
const buildUpstreamTree = (id, ver) => {
|
|
130
|
+
// Get the artifact version
|
|
131
|
+
const versionData = this.artifacts.get(id, ver);
|
|
132
|
+
|
|
133
|
+
if (!versionData) {
|
|
134
|
+
return null; // Artifact not found
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Create a unique key for cycle detection
|
|
138
|
+
const nodeKey = `${id}@v${versionData.version}`;
|
|
139
|
+
|
|
140
|
+
// Check if we've already visited this node (cycle detection)
|
|
141
|
+
if (visited.has(nodeKey)) {
|
|
142
|
+
return { artifactId: id, version: versionData.version, parents: [], cycleDetected: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Mark this node as visited
|
|
146
|
+
visited.add(nodeKey);
|
|
147
|
+
|
|
148
|
+
// Get the derivedFrom array from lineage
|
|
149
|
+
const derivedFrom = versionData.lineage?.derivedFrom || [];
|
|
150
|
+
|
|
151
|
+
// Build parent nodes recursively
|
|
152
|
+
const parents = [];
|
|
153
|
+
for (const ref of derivedFrom) {
|
|
154
|
+
const { artifactId: parentId, version: parentVer } = this._parseRef(ref);
|
|
155
|
+
const parentTree = buildUpstreamTree(parentId, parentVer);
|
|
156
|
+
|
|
157
|
+
if (parentTree) {
|
|
158
|
+
parents.push(parentTree);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Return the tree node
|
|
163
|
+
return {
|
|
164
|
+
artifactId: id,
|
|
165
|
+
version: versionData.version,
|
|
166
|
+
parents
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Build and return the tree
|
|
171
|
+
return buildUpstreamTree(artifactId, version);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get the downstream dependency tree for an artifact
|
|
176
|
+
* Shows what artifacts depend on this one, recursively
|
|
177
|
+
* @param {string} artifactId - The artifact identifier
|
|
178
|
+
* @param {number|null} [version=null] - Version number (null = latest)
|
|
179
|
+
* @returns {Object|null} Tree: { artifactId, version, dependents: [...] } or null if not found
|
|
180
|
+
*/
|
|
181
|
+
getDownstream(artifactId, version = null) {
|
|
182
|
+
// Get the artifact version to find its actual version number
|
|
183
|
+
const versionData = this.artifacts.get(artifactId, version);
|
|
184
|
+
|
|
185
|
+
if (!versionData) {
|
|
186
|
+
return null; // Artifact not found
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const targetVersion = versionData.version;
|
|
190
|
+
|
|
191
|
+
// Use a Set to track visited nodes and prevent cycles
|
|
192
|
+
const visited = new Set();
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Recursive helper to build downstream tree
|
|
196
|
+
* @param {string} id - Artifact ID
|
|
197
|
+
* @param {number} ver - Version number
|
|
198
|
+
* @returns {Object} Tree node
|
|
199
|
+
*/
|
|
200
|
+
const buildDownstreamTree = (id, ver) => {
|
|
201
|
+
// Create a unique key for cycle detection
|
|
202
|
+
const nodeKey = `${id}@v${ver}`;
|
|
203
|
+
|
|
204
|
+
// Check if we've already visited this node (cycle detection)
|
|
205
|
+
if (visited.has(nodeKey)) {
|
|
206
|
+
return { artifactId: id, version: ver, dependents: [], cycleDetected: true };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Mark this node as visited
|
|
210
|
+
visited.add(nodeKey);
|
|
211
|
+
|
|
212
|
+
// Scan all artifacts to find which ones reference this artifact
|
|
213
|
+
const allArtifacts = this._scanAllVersionFiles();
|
|
214
|
+
const dependents = [];
|
|
215
|
+
|
|
216
|
+
for (const [depId, depData] of allArtifacts.entries()) {
|
|
217
|
+
// Get the derivedFrom array
|
|
218
|
+
const derivedFrom = depData.lineage?.derivedFrom || [];
|
|
219
|
+
|
|
220
|
+
// Check if this artifact is in the derivedFrom list
|
|
221
|
+
for (const ref of derivedFrom) {
|
|
222
|
+
const { artifactId: refId, version: refVer } = this._parseRef(ref);
|
|
223
|
+
|
|
224
|
+
// Check if this reference matches our target artifact
|
|
225
|
+
// Match if: same artifactId AND (version matches OR ref has no version specified)
|
|
226
|
+
if (refId === id && (refVer === ver || refVer === null)) {
|
|
227
|
+
// This artifact depends on our target
|
|
228
|
+
const depTree = buildDownstreamTree(depId, depData.version);
|
|
229
|
+
dependents.push(depTree);
|
|
230
|
+
break; // Don't check other refs in this artifact's derivedFrom
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Return the tree node
|
|
236
|
+
return {
|
|
237
|
+
artifactId: id,
|
|
238
|
+
version: ver,
|
|
239
|
+
dependents
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Build and return the tree
|
|
244
|
+
return buildDownstreamTree(artifactId, targetVersion);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Calculate the impact radius of an artifact
|
|
249
|
+
* Shows how many artifacts would be affected if this artifact changes
|
|
250
|
+
* @param {string} artifactId - The artifact identifier
|
|
251
|
+
* @returns {Object} { artifactId, impactRadius, affectedArtifacts, affectedContracts }
|
|
252
|
+
*/
|
|
253
|
+
getImpact(artifactId) {
|
|
254
|
+
// Get the downstream tree
|
|
255
|
+
const downstreamTree = this.getDownstream(artifactId);
|
|
256
|
+
|
|
257
|
+
if (!downstreamTree) {
|
|
258
|
+
return {
|
|
259
|
+
artifactId,
|
|
260
|
+
impactRadius: 0,
|
|
261
|
+
affectedArtifacts: [],
|
|
262
|
+
affectedContracts: []
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Flatten the tree to get all affected artifact IDs
|
|
267
|
+
const affectedArtifacts = new Set();
|
|
268
|
+
let maxDepth = 0;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Recursive helper to traverse the tree
|
|
272
|
+
* @param {Object} node - Tree node
|
|
273
|
+
* @param {number} depth - Current depth
|
|
274
|
+
*/
|
|
275
|
+
const traverseTree = (node, depth) => {
|
|
276
|
+
// Add this artifact to the affected set
|
|
277
|
+
if (node.artifactId !== artifactId) {
|
|
278
|
+
affectedArtifacts.add(node.artifactId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Update max depth
|
|
282
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
283
|
+
|
|
284
|
+
// Traverse dependents
|
|
285
|
+
for (const dependent of node.dependents || []) {
|
|
286
|
+
// Skip cycles
|
|
287
|
+
if (!dependent.cycleDetected) {
|
|
288
|
+
traverseTree(dependent, depth + 1);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Traverse the downstream tree
|
|
294
|
+
traverseTree(downstreamTree, 0);
|
|
295
|
+
|
|
296
|
+
// TODO: Check contracts for references to this artifact
|
|
297
|
+
// This requires the ContractStore to be implemented
|
|
298
|
+
// For now, return an empty array
|
|
299
|
+
const affectedContracts = [];
|
|
300
|
+
|
|
301
|
+
// Return the impact analysis
|
|
302
|
+
return {
|
|
303
|
+
artifactId,
|
|
304
|
+
impactRadius: maxDepth,
|
|
305
|
+
affectedArtifacts: Array.from(affectedArtifacts),
|
|
306
|
+
affectedContracts
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Find artifacts that reference outdated versions of their dependencies
|
|
312
|
+
* @returns {Array} Array of { artifactId, version, staleReason, staleSourceId, referencedVersion, latestVersion }
|
|
313
|
+
*/
|
|
314
|
+
findStale() {
|
|
315
|
+
const staleArtifacts = [];
|
|
316
|
+
|
|
317
|
+
// Scan all artifacts
|
|
318
|
+
const allArtifacts = this._scanAllVersionFiles();
|
|
319
|
+
|
|
320
|
+
for (const [artifactId, versionData] of allArtifacts.entries()) {
|
|
321
|
+
// Get the derivedFrom array
|
|
322
|
+
const derivedFrom = versionData.lineage?.derivedFrom || [];
|
|
323
|
+
|
|
324
|
+
// Check each dependency
|
|
325
|
+
for (const ref of derivedFrom) {
|
|
326
|
+
const { artifactId: sourceId, version: referencedVersion } = this._parseRef(ref);
|
|
327
|
+
|
|
328
|
+
// Get the source artifact's latest version
|
|
329
|
+
const sourceLatest = this.artifacts.get(sourceId);
|
|
330
|
+
|
|
331
|
+
if (!sourceLatest) {
|
|
332
|
+
// Source artifact doesn't exist anymore
|
|
333
|
+
staleArtifacts.push({
|
|
334
|
+
artifactId,
|
|
335
|
+
version: versionData.version,
|
|
336
|
+
staleReason: 'Source artifact not found',
|
|
337
|
+
staleSourceId: sourceId,
|
|
338
|
+
referencedVersion,
|
|
339
|
+
latestVersion: null
|
|
340
|
+
});
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// If referencedVersion is null, it means "latest" - not stale
|
|
345
|
+
if (referencedVersion === null) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check if the referenced version is outdated
|
|
350
|
+
if (sourceLatest.version > referencedVersion) {
|
|
351
|
+
staleArtifacts.push({
|
|
352
|
+
artifactId,
|
|
353
|
+
version: versionData.version,
|
|
354
|
+
staleReason: 'References outdated version',
|
|
355
|
+
staleSourceId: sourceId,
|
|
356
|
+
referencedVersion,
|
|
357
|
+
latestVersion: sourceLatest.version
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return staleArtifacts;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get the complete audit trail for an artifact
|
|
368
|
+
* Shows full provenance: who published it, what contract produced it, what inputs it used
|
|
369
|
+
* @param {string} artifactId - The artifact identifier
|
|
370
|
+
* @param {number|null} [version=null] - Version number (null = latest)
|
|
371
|
+
* @returns {Object|null} Audit trail with full provenance, or null if not found
|
|
372
|
+
*/
|
|
373
|
+
getAuditTrail(artifactId, version = null) {
|
|
374
|
+
// Get the artifact version
|
|
375
|
+
const versionData = this.artifacts.get(artifactId, version);
|
|
376
|
+
|
|
377
|
+
if (!versionData) {
|
|
378
|
+
return null; // Artifact not found
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Get upstream chain
|
|
382
|
+
const upstreamChain = this.getUpstream(artifactId, versionData.version);
|
|
383
|
+
|
|
384
|
+
// Build the audit trail
|
|
385
|
+
const auditTrail = {
|
|
386
|
+
artifactId,
|
|
387
|
+
version: versionData.version,
|
|
388
|
+
type: versionData.type,
|
|
389
|
+
publisher: versionData.publisher,
|
|
390
|
+
publishedAt: versionData.publishedAt,
|
|
391
|
+
summary: versionData.summary,
|
|
392
|
+
contract: versionData.lineage?.producedBy || null,
|
|
393
|
+
inputs: versionData.lineage?.derivedFrom || [],
|
|
394
|
+
upstreamChain
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
return auditTrail;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Export the LineageGraph class
|
|
402
|
+
module.exports = LineageGraph;
|