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,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Registry — Layer 0: Session Continuity Engine
|
|
3
|
+
*
|
|
4
|
+
* Stores and retrieves coding patterns learned during sessions.
|
|
5
|
+
* Patterns are project-scoped and persist across sessions.
|
|
6
|
+
*
|
|
7
|
+
* Example pattern:
|
|
8
|
+
* {
|
|
9
|
+
* id: 'p_1707856123456_a1b2c3d4',
|
|
10
|
+
* rule: 'Always normalize phone numbers to 10 digits',
|
|
11
|
+
* context: 'auth module',
|
|
12
|
+
* example: 'phone.replace(/\D/g, "").slice(-10)',
|
|
13
|
+
* tags: ['validation', 'auth'],
|
|
14
|
+
* createdAt: '2024-02-13T12:34:56.789Z'
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
|
|
23
|
+
// These are helper modules already in the codebase
|
|
24
|
+
const { atomicWriteJson, readJsonSafe } = require('./atomic-io');
|
|
25
|
+
const { acquireLock, releaseLock } = require('./file-lock');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* PatternRegistry class for managing coding patterns.
|
|
29
|
+
*
|
|
30
|
+
* This class stores patterns (rules, examples, context) that help maintain
|
|
31
|
+
* consistency across sessions. Each pattern has a unique ID and metadata.
|
|
32
|
+
*/
|
|
33
|
+
class PatternRegistry {
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new PatternRegistry instance.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} projectPath - Absolute path to the project directory
|
|
38
|
+
*
|
|
39
|
+
* The registry creates a project-specific storage directory using a hash
|
|
40
|
+
* of the project path, so different projects have separate pattern stores.
|
|
41
|
+
*/
|
|
42
|
+
constructor(projectPath) {
|
|
43
|
+
// Create a short hash of the project path to identify this project
|
|
44
|
+
const projectHash = crypto
|
|
45
|
+
.createHash('sha256')
|
|
46
|
+
.update(projectPath)
|
|
47
|
+
.digest('hex')
|
|
48
|
+
.slice(0, 16);
|
|
49
|
+
|
|
50
|
+
// Storage directory for this project's patterns
|
|
51
|
+
this.storageDir = path.join(
|
|
52
|
+
os.homedir(),
|
|
53
|
+
'.clearctx',
|
|
54
|
+
'continuity',
|
|
55
|
+
projectHash
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// File that holds all patterns as JSON
|
|
59
|
+
this.patternsPath = path.join(this.storageDir, 'patterns.json');
|
|
60
|
+
|
|
61
|
+
// Directory for lock files (prevents race conditions)
|
|
62
|
+
this.locksDir = path.join(this.storageDir, 'locks');
|
|
63
|
+
|
|
64
|
+
// Create directories if they don't exist yet
|
|
65
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
66
|
+
fs.mkdirSync(this.locksDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Adds a new pattern to the registry.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} options - Pattern details
|
|
73
|
+
* @param {string} options.rule - The pattern rule (required)
|
|
74
|
+
* @param {string} [options.context] - Where this pattern applies
|
|
75
|
+
* @param {string} [options.example] - Code example or explanation
|
|
76
|
+
* @param {string[]} [options.tags] - Tags for categorization
|
|
77
|
+
* @returns {Object} The created pattern entry
|
|
78
|
+
*
|
|
79
|
+
* Example:
|
|
80
|
+
* registry.add({
|
|
81
|
+
* rule: 'Always validate email format',
|
|
82
|
+
* context: 'user registration',
|
|
83
|
+
* example: '/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)',
|
|
84
|
+
* tags: ['validation', 'user']
|
|
85
|
+
* });
|
|
86
|
+
*/
|
|
87
|
+
add({ rule, context, example, tags }) {
|
|
88
|
+
// Generate a unique ID for this pattern
|
|
89
|
+
// Format: 'p_' + timestamp + '_' + random hex
|
|
90
|
+
const id = 'p_' + Date.now() + '_' + crypto.randomBytes(4).toString('hex');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Lock the patterns file to prevent conflicts from other sessions
|
|
94
|
+
acquireLock(this.locksDir, 'patterns');
|
|
95
|
+
|
|
96
|
+
// Read existing patterns (returns {} if file doesn't exist)
|
|
97
|
+
const patterns = readJsonSafe(this.patternsPath, {});
|
|
98
|
+
|
|
99
|
+
// Build the new pattern entry
|
|
100
|
+
const entry = {
|
|
101
|
+
id,
|
|
102
|
+
rule,
|
|
103
|
+
context: context || '',
|
|
104
|
+
example: example || '',
|
|
105
|
+
tags: tags || [],
|
|
106
|
+
createdAt: new Date().toISOString()
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Add the pattern to the collection
|
|
110
|
+
patterns[id] = entry;
|
|
111
|
+
|
|
112
|
+
// Write back to disk atomically (safe even if interrupted)
|
|
113
|
+
atomicWriteJson(this.patternsPath, patterns);
|
|
114
|
+
|
|
115
|
+
return entry;
|
|
116
|
+
} finally {
|
|
117
|
+
// Always release the lock, even if an error occurred
|
|
118
|
+
releaseLock(this.locksDir, 'patterns');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Lists all patterns, optionally filtered by tag or context.
|
|
124
|
+
*
|
|
125
|
+
* @param {Object} [options] - Filter options
|
|
126
|
+
* @param {string} [options.tag] - Filter by tag (exact match)
|
|
127
|
+
* @param {string} [options.context] - Filter by context (substring match, case-insensitive)
|
|
128
|
+
* @returns {Array} Array of pattern entries, sorted by creation time (newest first)
|
|
129
|
+
*
|
|
130
|
+
* Example:
|
|
131
|
+
* registry.list({ tag: 'validation' })
|
|
132
|
+
* registry.list({ context: 'auth' })
|
|
133
|
+
* registry.list() // all patterns
|
|
134
|
+
*/
|
|
135
|
+
list({ tag, context } = {}) {
|
|
136
|
+
// Read all patterns from disk
|
|
137
|
+
const patterns = readJsonSafe(this.patternsPath, {});
|
|
138
|
+
|
|
139
|
+
// Convert from object to array
|
|
140
|
+
let entries = Object.values(patterns);
|
|
141
|
+
|
|
142
|
+
// Apply tag filter if provided
|
|
143
|
+
if (tag) {
|
|
144
|
+
entries = entries.filter(entry => entry.tags.includes(tag));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Apply context filter if provided (case-insensitive substring match)
|
|
148
|
+
if (context) {
|
|
149
|
+
const contextLower = context.toLowerCase();
|
|
150
|
+
entries = entries.filter(entry =>
|
|
151
|
+
entry.context.toLowerCase().includes(contextLower)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Sort by creation time, newest first
|
|
156
|
+
entries.sort((a, b) => {
|
|
157
|
+
return new Date(b.createdAt) - new Date(a.createdAt);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return entries;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Removes a pattern from the registry.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} patternId - The ID of the pattern to remove
|
|
167
|
+
* @returns {Object} Confirmation object with removed ID
|
|
168
|
+
* @throws {Error} If pattern ID doesn't exist
|
|
169
|
+
*
|
|
170
|
+
* Example:
|
|
171
|
+
* registry.remove('p_1707856123456_a1b2c3d4')
|
|
172
|
+
*/
|
|
173
|
+
remove(patternId) {
|
|
174
|
+
try {
|
|
175
|
+
// Lock the patterns file
|
|
176
|
+
acquireLock(this.locksDir, 'patterns');
|
|
177
|
+
|
|
178
|
+
// Read current patterns
|
|
179
|
+
const patterns = readJsonSafe(this.patternsPath, {});
|
|
180
|
+
|
|
181
|
+
// Check if the pattern exists
|
|
182
|
+
if (!patterns[patternId]) {
|
|
183
|
+
throw new Error(`Pattern not found: ${patternId}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Delete the pattern
|
|
187
|
+
delete patterns[patternId];
|
|
188
|
+
|
|
189
|
+
// Write back to disk
|
|
190
|
+
atomicWriteJson(this.patternsPath, patterns);
|
|
191
|
+
|
|
192
|
+
return { removed: patternId };
|
|
193
|
+
} finally {
|
|
194
|
+
// Always release the lock
|
|
195
|
+
releaseLock(this.locksDir, 'patterns');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gets a specific pattern by ID.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} patternId - The ID of the pattern to retrieve
|
|
203
|
+
* @returns {Object|null} The pattern entry, or null if not found
|
|
204
|
+
*
|
|
205
|
+
* Example:
|
|
206
|
+
* const pattern = registry.get('p_1707856123456_a1b2c3d4');
|
|
207
|
+
* if (pattern) {
|
|
208
|
+
* console.log(pattern.rule);
|
|
209
|
+
* }
|
|
210
|
+
*/
|
|
211
|
+
get(patternId) {
|
|
212
|
+
// Read patterns (no lock needed for simple reads)
|
|
213
|
+
const patterns = readJsonSafe(this.patternsPath, {});
|
|
214
|
+
|
|
215
|
+
// Return the pattern or null if not found
|
|
216
|
+
return patterns[patternId] || null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Export the class so other modules can use it
|
|
221
|
+
module.exports = PatternRegistry;
|