create-universal-ai-context 2.4.0 → 2.6.0-final
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/LICENSE +21 -21
- package/README.md +331 -294
- package/bin/create-ai-context.js +1507 -764
- package/lib/adapters/aider.js +131 -131
- package/lib/adapters/antigravity.js +205 -205
- package/lib/adapters/claude.js +397 -397
- package/lib/adapters/cline.js +125 -125
- package/lib/adapters/continue.js +138 -138
- package/lib/adapters/copilot.js +131 -131
- package/lib/adapters/index.js +78 -78
- package/lib/adapters/windsurf.js +138 -138
- package/lib/ai-context-generator.js +234 -234
- package/lib/ai-orchestrator.js +432 -432
- package/lib/call-tracer.js +444 -444
- package/lib/content-preservation.js +243 -243
- package/lib/cross-tool-sync/file-watcher.js +274 -274
- package/lib/cross-tool-sync/index.js +41 -40
- package/lib/cross-tool-sync/sync-manager.js +540 -512
- package/lib/cross-tool-sync/sync-service.js +297 -297
- package/lib/detector.js +726 -726
- package/lib/doc-discovery.js +741 -741
- package/lib/drift-checker.js +920 -920
- package/lib/environment-detector.js +239 -239
- package/lib/index.js +399 -399
- package/lib/install-hooks.js +82 -82
- package/lib/installer.js +419 -419
- package/lib/migrate.js +328 -328
- package/lib/placeholder.js +632 -632
- package/lib/prompts.js +341 -341
- package/lib/smart-merge.js +540 -540
- package/lib/spinner.js +60 -60
- package/lib/static-analyzer.js +729 -729
- package/lib/template-coordination.js +148 -148
- package/lib/template-populator.js +843 -843
- package/lib/template-renderer.js +392 -392
- package/lib/utils/fs-wrapper.js +79 -79
- package/lib/utils/path-utils.js +60 -60
- package/lib/validate.js +155 -155
- package/package.json +1 -1
- package/templates/AI_CONTEXT.md.template +245 -245
- package/templates/base/README.md +260 -257
- package/templates/base/RPI_WORKFLOW_PLAN.md +325 -320
- package/templates/base/agents/api-developer.md +76 -76
- package/templates/base/agents/context-engineer.md +525 -525
- package/templates/base/agents/core-architect.md +76 -76
- package/templates/base/agents/database-ops.md +76 -76
- package/templates/base/agents/deployment-ops.md +76 -76
- package/templates/base/agents/integration-hub.md +76 -76
- package/templates/base/analytics/README.md +114 -114
- package/templates/base/automation/config.json +58 -58
- package/templates/base/automation/generators/code-mapper.js +308 -308
- package/templates/base/automation/generators/index-builder.js +321 -321
- package/templates/base/automation/hooks/post-commit.sh +83 -83
- package/templates/base/automation/hooks/pre-commit.sh +103 -103
- package/templates/base/ci-templates/README.md +108 -108
- package/templates/base/ci-templates/github-actions/context-check.yml +144 -144
- package/templates/base/ci-templates/github-actions/validate-docs.yml +105 -105
- package/templates/base/commands/analytics.md +238 -238
- package/templates/base/commands/auto-sync.md +172 -172
- package/templates/base/commands/collab.md +194 -194
- package/templates/base/commands/context-optimize.md +226 -0
- package/templates/base/commands/help.md +485 -450
- package/templates/base/commands/rpi-implement.md +164 -115
- package/templates/base/commands/rpi-plan.md +147 -93
- package/templates/base/commands/rpi-research.md +145 -88
- package/templates/base/commands/session-resume.md +144 -144
- package/templates/base/commands/session-save.md +112 -112
- package/templates/base/commands/validate-all.md +77 -77
- package/templates/base/commands/verify-docs-current.md +86 -86
- package/templates/base/config/base.json +57 -57
- package/templates/base/config/environments/development.json +13 -13
- package/templates/base/config/environments/production.json +17 -17
- package/templates/base/config/environments/staging.json +13 -13
- package/templates/base/config/local.json.example +21 -21
- package/templates/base/context/.meta/generated-at.json +18 -18
- package/templates/base/context/ARCHITECTURE_SNAPSHOT.md +156 -156
- package/templates/base/context/CODE_TO_WORKFLOW_MAP.md +94 -94
- package/templates/base/context/FILE_OWNERSHIP.md +57 -57
- package/templates/base/context/INTEGRATION_POINTS.md +92 -92
- package/templates/base/context/KNOWN_GOTCHAS.md +195 -195
- package/templates/base/context/TESTING_MAP.md +95 -95
- package/templates/base/context/WORKFLOW_INDEX.md +129 -129
- package/templates/base/context/workflows/WORKFLOW_TEMPLATE.md +294 -294
- package/templates/base/indexes/agents/CAPABILITY_MATRIX.md +255 -255
- package/templates/base/indexes/agents/CATEGORY_INDEX.md +44 -44
- package/templates/base/indexes/code/CATEGORY_INDEX.md +38 -38
- package/templates/base/indexes/routing/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/search/CATEGORY_INDEX.md +39 -39
- package/templates/base/indexes/workflows/CATEGORY_INDEX.md +38 -38
- package/templates/base/knowledge/README.md +98 -98
- package/templates/base/knowledge/sessions/README.md +88 -88
- package/templates/base/knowledge/sessions/TEMPLATE.md +150 -150
- package/templates/base/knowledge/shared/decisions/0001-adopt-context-engineering.md +144 -144
- package/templates/base/knowledge/shared/decisions/README.md +49 -49
- package/templates/base/knowledge/shared/decisions/TEMPLATE.md +123 -123
- package/templates/base/knowledge/shared/patterns/README.md +62 -62
- package/templates/base/knowledge/shared/patterns/TEMPLATE.md +120 -120
- package/templates/base/plans/PLAN_TEMPLATE.md +316 -250
- package/templates/base/research/RESEARCH_TEMPLATE.md +245 -153
- package/templates/base/schemas/agent.schema.json +141 -141
- package/templates/base/schemas/anchors.schema.json +54 -54
- package/templates/base/schemas/automation.schema.json +93 -93
- package/templates/base/schemas/command.schema.json +134 -134
- package/templates/base/schemas/hashes.schema.json +40 -40
- package/templates/base/schemas/manifest.schema.json +117 -117
- package/templates/base/schemas/plan.schema.json +136 -136
- package/templates/base/schemas/research.schema.json +115 -115
- package/templates/base/schemas/roles.schema.json +34 -34
- package/templates/base/schemas/session.schema.json +77 -77
- package/templates/base/schemas/settings.schema.json +244 -244
- package/templates/base/schemas/staleness.schema.json +53 -53
- package/templates/base/schemas/team-config.schema.json +42 -42
- package/templates/base/schemas/workflow.schema.json +126 -126
- package/templates/base/session/checkpoints/.gitkeep +2 -2
- package/templates/base/session/current/state.json +20 -20
- package/templates/base/session/history/.gitkeep +2 -2
- package/templates/base/settings.json +3 -3
- package/templates/base/standards/COMPATIBILITY.md +219 -219
- package/templates/base/standards/EXTENSION_GUIDELINES.md +280 -280
- package/templates/base/standards/QUALITY_CHECKLIST.md +211 -211
- package/templates/base/standards/README.md +66 -66
- package/templates/base/sync/anchors.json +6 -6
- package/templates/base/sync/hashes.json +6 -6
- package/templates/base/sync/staleness.json +10 -10
- package/templates/base/team/README.md +168 -168
- package/templates/base/team/config.json +79 -79
- package/templates/base/team/roles.json +145 -145
- package/templates/base/tools/bin/claude-context.js +151 -151
- package/templates/base/tools/lib/anchor-resolver.js +276 -276
- package/templates/base/tools/lib/config-loader.js +363 -363
- package/templates/base/tools/lib/detector.js +350 -350
- package/templates/base/tools/lib/diagnose.js +206 -206
- package/templates/base/tools/lib/drift-detector.js +373 -373
- package/templates/base/tools/lib/errors.js +199 -199
- package/templates/base/tools/lib/index.js +36 -36
- package/templates/base/tools/lib/init.js +192 -192
- package/templates/base/tools/lib/logger.js +230 -230
- package/templates/base/tools/lib/placeholder.js +201 -201
- package/templates/base/tools/lib/session-manager.js +354 -354
- package/templates/base/tools/lib/validate.js +521 -521
- package/templates/base/tools/package.json +49 -49
- package/templates/handlebars/aider-config.hbs +146 -80
- package/templates/handlebars/antigravity.hbs +377 -377
- package/templates/handlebars/claude.hbs +183 -183
- package/templates/handlebars/cline.hbs +62 -62
- package/templates/handlebars/continue-config.hbs +116 -116
- package/templates/handlebars/copilot.hbs +130 -130
- package/templates/handlebars/partials/gotcha-list.hbs +11 -11
- package/templates/handlebars/partials/header.hbs +3 -3
- package/templates/handlebars/partials/workflow-summary.hbs +16 -16
- package/templates/handlebars/windsurf-rules.hbs +69 -69
- package/templates/hooks/post-commit.hbs +28 -29
- package/templates/hooks/pre-commit.hbs +46 -46
|
@@ -1,274 +1,274 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Watcher for Cross-Tool Sync
|
|
3
|
-
*
|
|
4
|
-
* Monitors AI tool context files for changes and triggers synchronization.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const { EventEmitter } = require('events');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Simple file watcher implementation (no chokidar dependency)
|
|
13
|
-
* Uses polling to detect file changes
|
|
14
|
-
*/
|
|
15
|
-
class FileWatcher extends EventEmitter {
|
|
16
|
-
constructor(options = {}) {
|
|
17
|
-
super();
|
|
18
|
-
this.watchPaths = new Map();
|
|
19
|
-
this.fileStates = new Map();
|
|
20
|
-
this.pollInterval = options.pollInterval || 1000; // 1 second default
|
|
21
|
-
this.intervalId = null;
|
|
22
|
-
this.running = false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Add a file or directory to watch
|
|
27
|
-
*/
|
|
28
|
-
watch(watchPath, projectRoot) {
|
|
29
|
-
const fullPath = path.isAbsolute(watchPath)
|
|
30
|
-
? watchPath
|
|
31
|
-
: path.join(projectRoot, watchPath);
|
|
32
|
-
|
|
33
|
-
const key = fullPath.toLowerCase();
|
|
34
|
-
|
|
35
|
-
if (this.watchPaths.has(key)) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Store initial state
|
|
40
|
-
this.recordFileState(fullPath, key);
|
|
41
|
-
|
|
42
|
-
this.watchPaths.set(key, {
|
|
43
|
-
path: fullPath,
|
|
44
|
-
projectRoot,
|
|
45
|
-
isDirectory: this.isDirectory(fullPath)
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Record current state of a file/directory
|
|
51
|
-
*/
|
|
52
|
-
recordFileState(filePath, key) {
|
|
53
|
-
try {
|
|
54
|
-
if (fs.existsSync(filePath)) {
|
|
55
|
-
const stats = fs.statSync(filePath);
|
|
56
|
-
|
|
57
|
-
if (stats.isDirectory()) {
|
|
58
|
-
// For directories, hash all files
|
|
59
|
-
this.fileStates.set(key, {
|
|
60
|
-
mtimeMs: stats.mtimeMs,
|
|
61
|
-
hash: this.hashDirectory(filePath),
|
|
62
|
-
exists: true
|
|
63
|
-
});
|
|
64
|
-
} else {
|
|
65
|
-
// For files, use content hash
|
|
66
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
-
const crypto = require('crypto');
|
|
68
|
-
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
69
|
-
|
|
70
|
-
this.fileStates.set(key, {
|
|
71
|
-
mtimeMs: stats.mtimeMs,
|
|
72
|
-
hash,
|
|
73
|
-
exists: true
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
this.fileStates.set(key, { exists: false });
|
|
78
|
-
}
|
|
79
|
-
} catch (error) {
|
|
80
|
-
this.fileStates.set(key, { exists: false, error: error.message });
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if path is a directory
|
|
86
|
-
*/
|
|
87
|
-
isDirectory(filePath) {
|
|
88
|
-
try {
|
|
89
|
-
return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory();
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Hash all files in a directory
|
|
97
|
-
*/
|
|
98
|
-
hashDirectory(dirPath) {
|
|
99
|
-
const crypto = require('crypto');
|
|
100
|
-
const hash = crypto.createHash('sha256');
|
|
101
|
-
const files = this.getAllFiles(dirPath);
|
|
102
|
-
|
|
103
|
-
for (const file of files.sort()) {
|
|
104
|
-
try {
|
|
105
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
106
|
-
hash.update(content);
|
|
107
|
-
} catch {
|
|
108
|
-
// Skip files that can't be read
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return hash.digest('hex');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Get all files in directory recursively
|
|
117
|
-
*/
|
|
118
|
-
getAllFiles(dirPath) {
|
|
119
|
-
const files = [];
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
123
|
-
|
|
124
|
-
for (const entry of entries) {
|
|
125
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
126
|
-
|
|
127
|
-
if (entry.isDirectory()) {
|
|
128
|
-
files.push(...this.getAllFiles(fullPath));
|
|
129
|
-
} else {
|
|
130
|
-
files.push(fullPath);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
} catch {
|
|
134
|
-
// Skip directories that can't be read
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return files;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Start watching
|
|
142
|
-
*/
|
|
143
|
-
start() {
|
|
144
|
-
if (this.running) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this.running = true;
|
|
149
|
-
this.intervalId = setInterval(() => {
|
|
150
|
-
this.checkForChanges();
|
|
151
|
-
}, this.pollInterval);
|
|
152
|
-
|
|
153
|
-
this.emit('ready');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Stop watching
|
|
158
|
-
*/
|
|
159
|
-
stop() {
|
|
160
|
-
if (!this.running) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
this.running = false;
|
|
165
|
-
|
|
166
|
-
if (this.intervalId) {
|
|
167
|
-
clearInterval(this.intervalId);
|
|
168
|
-
this.intervalId = null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
this.emit('stopped');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check all watched paths for changes
|
|
176
|
-
*/
|
|
177
|
-
checkForChanges() {
|
|
178
|
-
for (const [key, watchInfo] of this.watchPaths.entries()) {
|
|
179
|
-
this.checkPath(key, watchInfo);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Check a single path for changes
|
|
185
|
-
*/
|
|
186
|
-
checkPath(key, watchInfo) {
|
|
187
|
-
const { path: filePath } = watchInfo;
|
|
188
|
-
const oldState = this.fileStates.get(key);
|
|
189
|
-
|
|
190
|
-
if (!oldState) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Record new state
|
|
195
|
-
this.recordFileState(filePath, key);
|
|
196
|
-
const newState = this.fileStates.get(key);
|
|
197
|
-
|
|
198
|
-
// Detect changes
|
|
199
|
-
if (!oldState.exists && newState.exists) {
|
|
200
|
-
// File/directory was created
|
|
201
|
-
this.emit('created', {
|
|
202
|
-
path: filePath,
|
|
203
|
-
...watchInfo
|
|
204
|
-
});
|
|
205
|
-
} else if (oldState.exists && !newState.exists) {
|
|
206
|
-
// File/directory was deleted
|
|
207
|
-
this.emit('deleted', {
|
|
208
|
-
path: filePath,
|
|
209
|
-
...watchInfo
|
|
210
|
-
});
|
|
211
|
-
} else if (oldState.exists && newState.exists) {
|
|
212
|
-
// Check for modifications
|
|
213
|
-
if (oldState.hash !== newState.hash) {
|
|
214
|
-
this.emit('changed', {
|
|
215
|
-
path: filePath,
|
|
216
|
-
...watchInfo,
|
|
217
|
-
previousHash: oldState.hash,
|
|
218
|
-
currentHash: newState.hash
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Get current watch list
|
|
226
|
-
*/
|
|
227
|
-
getWatchedPaths() {
|
|
228
|
-
return Array.from(this.watchPaths.values()).map(w => w.path);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Unwatch a specific path
|
|
233
|
-
*/
|
|
234
|
-
unwatch(watchPath) {
|
|
235
|
-
const fullPath = path.isAbsolute(watchPath)
|
|
236
|
-
? watchPath
|
|
237
|
-
: path.join(process.cwd(), watchPath);
|
|
238
|
-
|
|
239
|
-
const key = fullPath.toLowerCase();
|
|
240
|
-
this.watchPaths.delete(key);
|
|
241
|
-
this.fileStates.delete(key);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Unwatch all paths
|
|
246
|
-
*/
|
|
247
|
-
unwatchAll() {
|
|
248
|
-
this.watchPaths.clear();
|
|
249
|
-
this.fileStates.clear();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Create a file watcher for AI tool contexts
|
|
255
|
-
*/
|
|
256
|
-
function createToolContextWatcher(projectRoot, options = {}) {
|
|
257
|
-
const watcher = new FileWatcher(options);
|
|
258
|
-
|
|
259
|
-
// Add context files for all tools
|
|
260
|
-
const { TOOL_CONTEXT_FILES } = require('./sync-manager');
|
|
261
|
-
|
|
262
|
-
for (const [toolName, files] of Object.entries(TOOL_CONTEXT_FILES)) {
|
|
263
|
-
for (const file of files) {
|
|
264
|
-
watcher.watch(file, projectRoot);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return watcher;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
module.exports = {
|
|
272
|
-
FileWatcher,
|
|
273
|
-
createToolContextWatcher
|
|
274
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* File Watcher for Cross-Tool Sync
|
|
3
|
+
*
|
|
4
|
+
* Monitors AI tool context files for changes and triggers synchronization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { EventEmitter } = require('events');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Simple file watcher implementation (no chokidar dependency)
|
|
13
|
+
* Uses polling to detect file changes
|
|
14
|
+
*/
|
|
15
|
+
class FileWatcher extends EventEmitter {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
this.watchPaths = new Map();
|
|
19
|
+
this.fileStates = new Map();
|
|
20
|
+
this.pollInterval = options.pollInterval || 1000; // 1 second default
|
|
21
|
+
this.intervalId = null;
|
|
22
|
+
this.running = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Add a file or directory to watch
|
|
27
|
+
*/
|
|
28
|
+
watch(watchPath, projectRoot) {
|
|
29
|
+
const fullPath = path.isAbsolute(watchPath)
|
|
30
|
+
? watchPath
|
|
31
|
+
: path.join(projectRoot, watchPath);
|
|
32
|
+
|
|
33
|
+
const key = fullPath.toLowerCase();
|
|
34
|
+
|
|
35
|
+
if (this.watchPaths.has(key)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Store initial state
|
|
40
|
+
this.recordFileState(fullPath, key);
|
|
41
|
+
|
|
42
|
+
this.watchPaths.set(key, {
|
|
43
|
+
path: fullPath,
|
|
44
|
+
projectRoot,
|
|
45
|
+
isDirectory: this.isDirectory(fullPath)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Record current state of a file/directory
|
|
51
|
+
*/
|
|
52
|
+
recordFileState(filePath, key) {
|
|
53
|
+
try {
|
|
54
|
+
if (fs.existsSync(filePath)) {
|
|
55
|
+
const stats = fs.statSync(filePath);
|
|
56
|
+
|
|
57
|
+
if (stats.isDirectory()) {
|
|
58
|
+
// For directories, hash all files
|
|
59
|
+
this.fileStates.set(key, {
|
|
60
|
+
mtimeMs: stats.mtimeMs,
|
|
61
|
+
hash: this.hashDirectory(filePath),
|
|
62
|
+
exists: true
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
// For files, use content hash
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
67
|
+
const crypto = require('crypto');
|
|
68
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
69
|
+
|
|
70
|
+
this.fileStates.set(key, {
|
|
71
|
+
mtimeMs: stats.mtimeMs,
|
|
72
|
+
hash,
|
|
73
|
+
exists: true
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
this.fileStates.set(key, { exists: false });
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
this.fileStates.set(key, { exists: false, error: error.message });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if path is a directory
|
|
86
|
+
*/
|
|
87
|
+
isDirectory(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory();
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Hash all files in a directory
|
|
97
|
+
*/
|
|
98
|
+
hashDirectory(dirPath) {
|
|
99
|
+
const crypto = require('crypto');
|
|
100
|
+
const hash = crypto.createHash('sha256');
|
|
101
|
+
const files = this.getAllFiles(dirPath);
|
|
102
|
+
|
|
103
|
+
for (const file of files.sort()) {
|
|
104
|
+
try {
|
|
105
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
106
|
+
hash.update(content);
|
|
107
|
+
} catch {
|
|
108
|
+
// Skip files that can't be read
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return hash.digest('hex');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all files in directory recursively
|
|
117
|
+
*/
|
|
118
|
+
getAllFiles(dirPath) {
|
|
119
|
+
const files = [];
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
123
|
+
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
126
|
+
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
files.push(...this.getAllFiles(fullPath));
|
|
129
|
+
} else {
|
|
130
|
+
files.push(fullPath);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// Skip directories that can't be read
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return files;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Start watching
|
|
142
|
+
*/
|
|
143
|
+
start() {
|
|
144
|
+
if (this.running) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.running = true;
|
|
149
|
+
this.intervalId = setInterval(() => {
|
|
150
|
+
this.checkForChanges();
|
|
151
|
+
}, this.pollInterval);
|
|
152
|
+
|
|
153
|
+
this.emit('ready');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Stop watching
|
|
158
|
+
*/
|
|
159
|
+
stop() {
|
|
160
|
+
if (!this.running) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this.running = false;
|
|
165
|
+
|
|
166
|
+
if (this.intervalId) {
|
|
167
|
+
clearInterval(this.intervalId);
|
|
168
|
+
this.intervalId = null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.emit('stopped');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check all watched paths for changes
|
|
176
|
+
*/
|
|
177
|
+
checkForChanges() {
|
|
178
|
+
for (const [key, watchInfo] of this.watchPaths.entries()) {
|
|
179
|
+
this.checkPath(key, watchInfo);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check a single path for changes
|
|
185
|
+
*/
|
|
186
|
+
checkPath(key, watchInfo) {
|
|
187
|
+
const { path: filePath } = watchInfo;
|
|
188
|
+
const oldState = this.fileStates.get(key);
|
|
189
|
+
|
|
190
|
+
if (!oldState) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Record new state
|
|
195
|
+
this.recordFileState(filePath, key);
|
|
196
|
+
const newState = this.fileStates.get(key);
|
|
197
|
+
|
|
198
|
+
// Detect changes
|
|
199
|
+
if (!oldState.exists && newState.exists) {
|
|
200
|
+
// File/directory was created
|
|
201
|
+
this.emit('created', {
|
|
202
|
+
path: filePath,
|
|
203
|
+
...watchInfo
|
|
204
|
+
});
|
|
205
|
+
} else if (oldState.exists && !newState.exists) {
|
|
206
|
+
// File/directory was deleted
|
|
207
|
+
this.emit('deleted', {
|
|
208
|
+
path: filePath,
|
|
209
|
+
...watchInfo
|
|
210
|
+
});
|
|
211
|
+
} else if (oldState.exists && newState.exists) {
|
|
212
|
+
// Check for modifications
|
|
213
|
+
if (oldState.hash !== newState.hash) {
|
|
214
|
+
this.emit('changed', {
|
|
215
|
+
path: filePath,
|
|
216
|
+
...watchInfo,
|
|
217
|
+
previousHash: oldState.hash,
|
|
218
|
+
currentHash: newState.hash
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get current watch list
|
|
226
|
+
*/
|
|
227
|
+
getWatchedPaths() {
|
|
228
|
+
return Array.from(this.watchPaths.values()).map(w => w.path);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Unwatch a specific path
|
|
233
|
+
*/
|
|
234
|
+
unwatch(watchPath) {
|
|
235
|
+
const fullPath = path.isAbsolute(watchPath)
|
|
236
|
+
? watchPath
|
|
237
|
+
: path.join(process.cwd(), watchPath);
|
|
238
|
+
|
|
239
|
+
const key = fullPath.toLowerCase();
|
|
240
|
+
this.watchPaths.delete(key);
|
|
241
|
+
this.fileStates.delete(key);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Unwatch all paths
|
|
246
|
+
*/
|
|
247
|
+
unwatchAll() {
|
|
248
|
+
this.watchPaths.clear();
|
|
249
|
+
this.fileStates.clear();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create a file watcher for AI tool contexts
|
|
255
|
+
*/
|
|
256
|
+
function createToolContextWatcher(projectRoot, options = {}) {
|
|
257
|
+
const watcher = new FileWatcher(options);
|
|
258
|
+
|
|
259
|
+
// Add context files for all tools
|
|
260
|
+
const { TOOL_CONTEXT_FILES } = require('./sync-manager');
|
|
261
|
+
|
|
262
|
+
for (const [toolName, files] of Object.entries(TOOL_CONTEXT_FILES)) {
|
|
263
|
+
for (const file of files) {
|
|
264
|
+
watcher.watch(file, projectRoot);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return watcher;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = {
|
|
272
|
+
FileWatcher,
|
|
273
|
+
createToolContextWatcher
|
|
274
|
+
};
|
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-Tool Sync Module
|
|
3
|
-
*
|
|
4
|
-
* Exports all synchronization functionality for automatic cross-tool
|
|
5
|
-
* context synchronization.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const SyncManager = require('./sync-manager');
|
|
9
|
-
const { FileWatcher, createToolContextWatcher } = require('./file-watcher');
|
|
10
|
-
const { SyncService, createSyncService, DEFAULT_CONFIG } = require('./sync-service');
|
|
11
|
-
|
|
12
|
-
module.exports = {
|
|
13
|
-
// Sync Manager - Core sync logic
|
|
14
|
-
...SyncManager,
|
|
15
|
-
|
|
16
|
-
// File Watcher - Change detection
|
|
17
|
-
FileWatcher,
|
|
18
|
-
createToolContextWatcher,
|
|
19
|
-
|
|
20
|
-
// Sync Service - Background service
|
|
21
|
-
SyncService,
|
|
22
|
-
createSyncService,
|
|
23
|
-
DEFAULT_CONFIG
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Also export individual functions for named imports
|
|
27
|
-
module.exports.detectChangedTool = SyncManager.detectChangedTool;
|
|
28
|
-
module.exports.propagateContextChange = SyncManager.propagateContextChange;
|
|
29
|
-
module.exports.checkSyncStatus = SyncManager.checkSyncStatus;
|
|
30
|
-
module.exports.syncAllFromCodebase = SyncManager.syncAllFromCodebase;
|
|
31
|
-
module.exports.
|
|
32
|
-
module.exports.
|
|
33
|
-
module.exports.
|
|
34
|
-
module.exports.
|
|
35
|
-
module.exports.
|
|
36
|
-
module.exports.
|
|
37
|
-
module.exports.
|
|
38
|
-
module.exports.
|
|
39
|
-
module.exports.
|
|
40
|
-
module.exports.
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Tool Sync Module
|
|
3
|
+
*
|
|
4
|
+
* Exports all synchronization functionality for automatic cross-tool
|
|
5
|
+
* context synchronization.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const SyncManager = require('./sync-manager');
|
|
9
|
+
const { FileWatcher, createToolContextWatcher } = require('./file-watcher');
|
|
10
|
+
const { SyncService, createSyncService, DEFAULT_CONFIG } = require('./sync-service');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
// Sync Manager - Core sync logic
|
|
14
|
+
...SyncManager,
|
|
15
|
+
|
|
16
|
+
// File Watcher - Change detection
|
|
17
|
+
FileWatcher,
|
|
18
|
+
createToolContextWatcher,
|
|
19
|
+
|
|
20
|
+
// Sync Service - Background service
|
|
21
|
+
SyncService,
|
|
22
|
+
createSyncService,
|
|
23
|
+
DEFAULT_CONFIG
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Also export individual functions for named imports
|
|
27
|
+
module.exports.detectChangedTool = SyncManager.detectChangedTool;
|
|
28
|
+
module.exports.propagateContextChange = SyncManager.propagateContextChange;
|
|
29
|
+
module.exports.checkSyncStatus = SyncManager.checkSyncStatus;
|
|
30
|
+
module.exports.syncAllFromCodebase = SyncManager.syncAllFromCodebase;
|
|
31
|
+
module.exports.updateSyncStateOnly = SyncManager.updateSyncStateOnly;
|
|
32
|
+
module.exports.resolveConflict = SyncManager.resolveConflict;
|
|
33
|
+
module.exports.getSyncHistory = SyncManager.getSyncHistory;
|
|
34
|
+
module.exports.initSyncState = SyncManager.initSyncState;
|
|
35
|
+
module.exports.loadSyncState = SyncManager.loadSyncState;
|
|
36
|
+
module.exports.saveSyncState = SyncManager.saveSyncState;
|
|
37
|
+
module.exports.calculateFileHash = SyncManager.calculateFileHash;
|
|
38
|
+
module.exports.getToolContextFiles = SyncManager.getToolContextFiles;
|
|
39
|
+
module.exports.formatSyncStatus = SyncManager.formatSyncStatus;
|
|
40
|
+
module.exports.CONFLICT_STRATEGY = SyncManager.CONFLICT_STRATEGY;
|
|
41
|
+
module.exports.TOOL_CONTEXT_FILES = SyncManager.TOOL_CONTEXT_FILES;
|