logicstamp-context 0.8.3 → 0.8.5
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/LLM_CONTEXT.md +703 -703
- package/README.md +412 -403
- package/dist/cli/commands/clean.d.ts.map +1 -1
- package/dist/cli/commands/clean.js +1 -6
- package/dist/cli/commands/clean.js.map +1 -1
- package/dist/cli/commands/compare/core.js +1 -1
- package/dist/cli/commands/compare/core.js.map +1 -1
- package/dist/cli/commands/context/fileWriter.js +2 -2
- package/dist/cli/commands/context/fileWriter.js.map +1 -1
- package/dist/cli/commands/context/statsCalculator.d.ts +3 -2
- package/dist/cli/commands/context/statsCalculator.d.ts.map +1 -1
- package/dist/cli/commands/context/statsCalculator.js +45 -104
- package/dist/cli/commands/context/statsCalculator.js.map +1 -1
- package/dist/cli/commands/context/watchMode/watchMode.d.ts.map +1 -1
- package/dist/cli/commands/context/watchMode/watchMode.js +252 -256
- package/dist/cli/commands/context/watchMode/watchMode.js.map +1 -1
- package/dist/cli/commands/init.js +20 -20
- package/dist/cli/commands/security.js +3 -3
- package/dist/cli/commands/security.js.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/cli/commands/validate.js +1 -6
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/handlers/initHandler.js +5 -5
- package/dist/cli/index.d.ts +6 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +100 -96
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/parser/helpText.js +570 -570
- package/dist/cli/validate-index.js +26 -26
- package/dist/core/astParser/detectors.d.ts.map +1 -1
- package/dist/core/astParser/detectors.js +3 -2
- package/dist/core/astParser/detectors.js.map +1 -1
- package/dist/core/pack/loader.js +3 -3
- package/dist/core/pack/loader.js.map +1 -1
- package/dist/utils/codeSanitizer.d.ts.map +1 -1
- package/dist/utils/codeSanitizer.js +3 -2
- package/dist/utils/codeSanitizer.js.map +1 -1
- package/dist/utils/fileLock.d.ts.map +1 -1
- package/dist/utils/fileLock.js +70 -6
- package/dist/utils/fileLock.js.map +1 -1
- package/dist/utils/fsx.d.ts +5 -0
- package/dist/utils/fsx.d.ts.map +1 -1
- package/dist/utils/fsx.js +9 -2
- package/dist/utils/fsx.js.map +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +2 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/gitignore.d.ts.map +1 -1
- package/dist/utils/gitignore.js +2 -1
- package/dist/utils/gitignore.js.map +1 -1
- package/dist/utils/secretDetector.d.ts.map +1 -1
- package/dist/utils/secretDetector.js +11 -6
- package/dist/utils/secretDetector.js.map +1 -1
- package/package.json +85 -84
- package/schema/logicstamp.context.schema.json +1250 -1250
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { resolve, dirname, join, relative } from 'node:path';
|
|
5
5
|
import { readFile } from 'node:fs/promises';
|
|
6
6
|
import chokidar from 'chokidar';
|
|
7
|
-
import { globFiles } from '../../../../utils/fsx.js';
|
|
7
|
+
import { globFiles, toForwardSlashes } from '../../../../utils/fsx.js';
|
|
8
8
|
import { readStampignore, filterIgnoredFiles } from '../../../../utils/stampignore.js';
|
|
9
9
|
import { buildDependencyGraph } from '../../../../core/manifest.js';
|
|
10
10
|
import { normalizeEntryId } from '../../../../utils/fsx.js';
|
|
@@ -69,7 +69,7 @@ export async function startWatchMode(options, projectRoot, initialCache = null)
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
let debounceTimer = null;
|
|
72
|
-
let
|
|
72
|
+
let isRegenerating = false; // Flag-based lock to prevent race conditions
|
|
73
73
|
let changedFiles = new Set();
|
|
74
74
|
/** Snapshot for `getChanges` / strict-watch: set on first load; reset when watch cache is recovered after an error (new stable tree). */
|
|
75
75
|
let baselineBundles = null;
|
|
@@ -180,179 +180,193 @@ export async function startWatchMode(options, projectRoot, initialCache = null)
|
|
|
180
180
|
}
|
|
181
181
|
};
|
|
182
182
|
const regenerate = async () => {
|
|
183
|
-
//
|
|
184
|
-
// If already regenerating,
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
// After waiting, if no new changes accumulated, skip
|
|
188
|
-
if (changedFiles.size === 0) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// Otherwise, fall through to process new changes
|
|
183
|
+
// Synchronous flag check - safe in JS single-threaded event loop
|
|
184
|
+
// If already regenerating, return - the while loop will pick up new changes
|
|
185
|
+
if (isRegenerating) {
|
|
186
|
+
return;
|
|
192
187
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const fileList = changedFileList.length > 3
|
|
210
|
-
? `${changedFileList.slice(0, 3).join(', ')}, ... (+${changedFileList.length - 3} more)`
|
|
211
|
-
: changedFileList.join(', ');
|
|
212
|
-
console.log(`\n🔄 Recompiling (${changedFileList.length} file${changedFileList.length > 1 ? 's' : ''} changed)...`);
|
|
213
|
-
}
|
|
214
|
-
let newBundles;
|
|
215
|
-
// Use incremental rebuild if cache exists, otherwise do full rebuild (fallback)
|
|
216
|
-
if (watchCache) {
|
|
217
|
-
// Incremental rebuild - only rebuild affected bundles
|
|
218
|
-
const result = await incrementalRebuild(changedFileList, watchCache, options, projectRoot);
|
|
219
|
-
newBundles = result.bundles;
|
|
220
|
-
// Write only changed context files
|
|
221
|
-
const bundlesByFolder = groupBundlesByFolder(newBundles);
|
|
222
|
-
const changedFolders = new Set();
|
|
223
|
-
for (const bundleId of result.updatedBundles) {
|
|
224
|
-
const bundle = newBundles.find(b => b.entryId === bundleId);
|
|
225
|
-
if (bundle) {
|
|
226
|
-
const folderPath = bundle.entryId.substring(0, bundle.entryId.lastIndexOf('/') || bundle.entryId.length);
|
|
227
|
-
changedFolders.add(folderPath);
|
|
228
|
-
}
|
|
188
|
+
isRegenerating = true;
|
|
189
|
+
try {
|
|
190
|
+
// Loop to process any changes that arrive during regeneration
|
|
191
|
+
while (changedFiles.size > 0) {
|
|
192
|
+
const changedFileList = Array.from(changedFiles);
|
|
193
|
+
changedFiles.clear(); // Clear for next batch
|
|
194
|
+
const startTime = Date.now();
|
|
195
|
+
try {
|
|
196
|
+
// Determine output directory
|
|
197
|
+
const outPath = resolve(options.out);
|
|
198
|
+
const outputDir = outPath.endsWith('.json') ? dirname(outPath) : outPath;
|
|
199
|
+
// Load baseline bundles for state-based comparison (like git diff).
|
|
200
|
+
// Set on first successful load; also updated after error recovery (full rebuild) so diffs stay meaningful.
|
|
201
|
+
if (!baselineBundles) {
|
|
202
|
+
baselineBundles = await loadAllBundles(outputDir);
|
|
203
|
+
previousBundles = baselineBundles;
|
|
229
204
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
const { contracts } = await buildContractsFromFiles(filteredFiles, projectRoot, {
|
|
263
|
-
includeStyle: options.includeStyle,
|
|
264
|
-
styleMode: options.styleMode,
|
|
265
|
-
predictBehavior: options.predictBehavior,
|
|
205
|
+
if (!options.quiet) {
|
|
206
|
+
const fileList = changedFileList.length > 3
|
|
207
|
+
? `${changedFileList.slice(0, 3).join(', ')}, ... (+${changedFileList.length - 3} more)`
|
|
208
|
+
: changedFileList.join(', ');
|
|
209
|
+
console.log(`\n🔄 Recompiling (${changedFileList.length} file${changedFileList.length > 1 ? 's' : ''} changed)...`);
|
|
210
|
+
}
|
|
211
|
+
let newBundles;
|
|
212
|
+
// Use incremental rebuild if cache exists, otherwise do full rebuild (fallback)
|
|
213
|
+
if (watchCache) {
|
|
214
|
+
// Incremental rebuild - only rebuild affected bundles
|
|
215
|
+
const result = await incrementalRebuild(changedFileList, watchCache, options, projectRoot);
|
|
216
|
+
newBundles = result.bundles;
|
|
217
|
+
// Write only changed context files
|
|
218
|
+
const bundlesByFolder = groupBundlesByFolder(newBundles);
|
|
219
|
+
const changedFolders = new Set();
|
|
220
|
+
for (const bundleId of result.updatedBundles) {
|
|
221
|
+
const bundle = newBundles.find(b => b.entryId === bundleId);
|
|
222
|
+
if (bundle) {
|
|
223
|
+
const folderPath = bundle.entryId.substring(0, bundle.entryId.lastIndexOf('/') || bundle.entryId.length);
|
|
224
|
+
changedFolders.add(folderPath);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Write context files for changed folders only
|
|
228
|
+
// (This is a simplified version - full implementation would write individual files)
|
|
229
|
+
// For now, we'll write all files but this can be optimized further
|
|
230
|
+
const { folderInfos, totalTokenEstimate } = await writeContextFiles(newBundles, outputDir, projectRoot, {
|
|
231
|
+
format: options.format,
|
|
232
|
+
quiet: true,
|
|
233
|
+
});
|
|
234
|
+
// Get contracts for main index (from cache if available)
|
|
235
|
+
const allContracts = watchCache ? Array.from(watchCache.contracts.values()) : [];
|
|
236
|
+
await writeMainIndex(outputDir, folderInfos, allContracts, newBundles, bundlesByFolder.size, totalTokenEstimate, projectRoot, {
|
|
266
237
|
quiet: true,
|
|
238
|
+
suppressSuccessIndicator: true,
|
|
267
239
|
});
|
|
268
|
-
const manifest = buildDependencyGraph(contracts);
|
|
269
|
-
watchCache = await initializeWatchCache(filteredFiles, contracts, manifest, newBundles, projectRoot);
|
|
270
240
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
241
|
+
else {
|
|
242
|
+
// Fallback: Full rebuild if cache not available (shouldn't happen normally)
|
|
243
|
+
const regenerateOptions = {
|
|
244
|
+
...options,
|
|
245
|
+
watch: false,
|
|
246
|
+
strictMissing: false,
|
|
247
|
+
quiet: true,
|
|
248
|
+
suppressSuccessIndicator: true,
|
|
249
|
+
};
|
|
250
|
+
await contextCommand(regenerateOptions);
|
|
251
|
+
// Load bundles and initialize cache
|
|
252
|
+
newBundles = await loadAllBundles(outputDir);
|
|
253
|
+
// Initialize cache for next incremental rebuild
|
|
254
|
+
if (newBundles.length > 0) {
|
|
255
|
+
// We need contracts and manifest - reload them from the build
|
|
256
|
+
const files = await globFiles(projectRoot);
|
|
257
|
+
const stampignore = await readStampignore(projectRoot);
|
|
258
|
+
const filteredFiles = stampignore ? filterIgnoredFiles(files, stampignore.ignore, projectRoot) : files;
|
|
259
|
+
const { contracts } = await buildContractsFromFiles(filteredFiles, projectRoot, {
|
|
260
|
+
includeStyle: options.includeStyle,
|
|
261
|
+
styleMode: options.styleMode,
|
|
262
|
+
predictBehavior: options.predictBehavior,
|
|
263
|
+
quiet: true,
|
|
264
|
+
});
|
|
265
|
+
const manifest = buildDependencyGraph(contracts);
|
|
266
|
+
watchCache = await initializeWatchCache(filteredFiles, contracts, manifest, newBundles, projectRoot);
|
|
267
|
+
}
|
|
281
268
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
269
|
+
const durationMs = Date.now() - startTime;
|
|
270
|
+
// Compare against BASELINE (starting state), not previous state
|
|
271
|
+
// This gives us cumulative diff like `git diff` - if changes are reverted, diff is empty
|
|
272
|
+
const changes = baselineBundles && baselineBundles.length > 0
|
|
273
|
+
? getChanges(baselineBundles, newBundles)
|
|
274
|
+
: null;
|
|
275
|
+
if (!options.quiet) {
|
|
276
|
+
if (changes && (changes.changed.length > 0 || changes.added.length > 0 || changes.removed.length > 0 || changes.bundleChanged.length > 0)) {
|
|
277
|
+
showChanges(baselineBundles, newBundles, changedFileList[0] || 'unknown', { debug: options.debug });
|
|
278
|
+
}
|
|
279
|
+
console.log(`\n✅ Recompiled\n`);
|
|
280
|
+
}
|
|
281
|
+
// Update previousBundles for incremental tracking (still useful for other operations)
|
|
282
|
+
previousBundles = newBundles;
|
|
283
|
+
// Strict watch mode: detect and report violations (state-based, like git diff)
|
|
284
|
+
// Violations are calculated from current state vs baseline, not accumulated
|
|
285
|
+
if (options.strictWatch && strictWatchStatus) {
|
|
286
|
+
const hasChanges = changes && (changes.changed.length > 0 ||
|
|
287
|
+
changes.added.length > 0 ||
|
|
288
|
+
changes.removed.length > 0 ||
|
|
289
|
+
changes.bundleChanged.length > 0);
|
|
290
|
+
// Track previous state to detect new violations and resolution
|
|
291
|
+
const previousActiveErrors = strictWatchStatus.lastCheck?.errors ?? 0;
|
|
292
|
+
const previousActiveWarnings = strictWatchStatus.lastCheck?.warnings ?? 0;
|
|
293
|
+
const hadActiveViolations = previousActiveErrors > 0 || previousActiveWarnings > 0;
|
|
294
|
+
if (hasChanges) {
|
|
295
|
+
const violations = detectViolations({ type: 'watch', changes });
|
|
296
|
+
const errors = violations.filter(v => v.severity === 'error');
|
|
297
|
+
const warnings = violations.filter(v => v.severity === 'warning');
|
|
298
|
+
strictWatchStatus.regenerationCount++;
|
|
299
|
+
if (violations.length > 0) {
|
|
300
|
+
// Only increment totals if these are NEW violations (first time seeing violations, or count increased)
|
|
301
|
+
const isNewViolation = !hadActiveViolations;
|
|
302
|
+
const violationCountIncreased = errors.length > previousActiveErrors || warnings.length > previousActiveWarnings;
|
|
303
|
+
if (isNewViolation || violationCountIncreased) {
|
|
304
|
+
// Calculate new violations (only count increases, not existing ones)
|
|
305
|
+
if (isNewViolation) {
|
|
306
|
+
// First time violations appear - count all of them
|
|
307
|
+
strictWatchStatus.totalErrorsDetected += errors.length;
|
|
308
|
+
strictWatchStatus.totalWarningsDetected += warnings.length;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// Violations increased - only count the new ones
|
|
312
|
+
const newErrors = Math.max(0, errors.length - previousActiveErrors);
|
|
313
|
+
const newWarnings = Math.max(0, warnings.length - previousActiveWarnings);
|
|
314
|
+
strictWatchStatus.totalErrorsDetected += newErrors;
|
|
315
|
+
strictWatchStatus.totalWarningsDetected += newWarnings;
|
|
316
|
+
}
|
|
319
317
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
318
|
+
// Update state-based counts (current state, not cumulative)
|
|
319
|
+
strictWatchStatus.cumulativeViolations = violations.length;
|
|
320
|
+
strictWatchStatus.cumulativeErrors = errors.length;
|
|
321
|
+
strictWatchStatus.cumulativeWarnings = warnings.length;
|
|
322
|
+
// Store current violations
|
|
323
|
+
strictWatchStatus.lastCheck = {
|
|
324
|
+
timestamp: new Date().toISOString(),
|
|
325
|
+
totalViolations: violations.length,
|
|
326
|
+
errors: errors.length,
|
|
327
|
+
warnings: warnings.length,
|
|
328
|
+
violations,
|
|
329
|
+
changedFiles: changedFileList,
|
|
330
|
+
};
|
|
331
|
+
// Display violations to console (only if status changed)
|
|
332
|
+
if (!options.quiet) {
|
|
333
|
+
const statusChanged = errors.length !== previousActiveErrors || warnings.length !== previousActiveWarnings;
|
|
334
|
+
if (statusChanged) {
|
|
335
|
+
if (isNewViolation || violationCountIncreased) {
|
|
336
|
+
// Show appropriate emoji based on severity: ❌ for errors, ⚠️ for warnings only
|
|
337
|
+
if (errors.length > 0) {
|
|
338
|
+
console.log(`\n❌ Breaking change detected`);
|
|
339
|
+
}
|
|
340
|
+
else if (warnings.length > 0) {
|
|
341
|
+
console.log(`\n⚠️ Warning detected`);
|
|
342
|
+
}
|
|
345
343
|
}
|
|
344
|
+
displayViolations(violations, { quiet: options.quiet });
|
|
345
|
+
displaySessionStatus(strictWatchStatus, { quiet: options.quiet });
|
|
346
346
|
}
|
|
347
|
-
|
|
347
|
+
}
|
|
348
|
+
// Write current violations state to disk
|
|
349
|
+
await writeStrictWatchStatus(projectRoot, strictWatchStatus);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
// Changes exist but no violations - violations were resolved
|
|
353
|
+
if (hadActiveViolations) {
|
|
354
|
+
strictWatchStatus.resolvedCount++;
|
|
355
|
+
}
|
|
356
|
+
strictWatchStatus.cumulativeViolations = 0;
|
|
357
|
+
strictWatchStatus.cumulativeErrors = 0;
|
|
358
|
+
strictWatchStatus.cumulativeWarnings = 0;
|
|
359
|
+
strictWatchStatus.lastCheck = undefined;
|
|
360
|
+
// Display resolution message and status
|
|
361
|
+
if (!options.quiet && hadActiveViolations) {
|
|
362
|
+
console.log(`\n✅ Violation resolved`);
|
|
348
363
|
displaySessionStatus(strictWatchStatus, { quiet: options.quiet });
|
|
349
364
|
}
|
|
365
|
+
await deleteStrictWatchStatus(projectRoot);
|
|
350
366
|
}
|
|
351
|
-
// Write current violations state to disk
|
|
352
|
-
await writeStrictWatchStatus(projectRoot, strictWatchStatus);
|
|
353
367
|
}
|
|
354
368
|
else {
|
|
355
|
-
//
|
|
369
|
+
// No changes from baseline - clear violations (state reverted)
|
|
356
370
|
if (hadActiveViolations) {
|
|
357
371
|
strictWatchStatus.resolvedCount++;
|
|
358
372
|
}
|
|
@@ -368,111 +382,93 @@ export async function startWatchMode(options, projectRoot, initialCache = null)
|
|
|
368
382
|
await deleteStrictWatchStatus(projectRoot);
|
|
369
383
|
}
|
|
370
384
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
385
|
+
// Log structured data for MCP server (only if --log-file flag is set)
|
|
386
|
+
// Appends each event to the log file for event history
|
|
387
|
+
if (options.logFile) {
|
|
388
|
+
if (changes) {
|
|
389
|
+
const logEntry = {
|
|
390
|
+
timestamp: new Date().toISOString(),
|
|
391
|
+
changedFiles: changedFileList,
|
|
392
|
+
fileCount: changedFileList.length,
|
|
393
|
+
durationMs,
|
|
394
|
+
modifiedContracts: changes.changed.map(c => ({
|
|
395
|
+
entryId: c.entryId,
|
|
396
|
+
semanticHashChanged: !!c.semanticHash,
|
|
397
|
+
fileHashChanged: !!c.fileHash,
|
|
398
|
+
semanticHash: c.semanticHash,
|
|
399
|
+
fileHash: c.fileHash,
|
|
400
|
+
})),
|
|
401
|
+
modifiedBundles: changes.bundleChanged.map(b => ({
|
|
402
|
+
entryId: b.entryId,
|
|
403
|
+
bundleHash: { old: b.oldHash, new: b.newHash },
|
|
404
|
+
})),
|
|
405
|
+
addedContracts: changes.added.length > 0 ? changes.added : undefined,
|
|
406
|
+
removedContracts: changes.removed.length > 0 ? changes.removed : undefined,
|
|
407
|
+
summary: {
|
|
408
|
+
modifiedContractsCount: changes.changed.length,
|
|
409
|
+
modifiedBundlesCount: changes.bundleChanged.length,
|
|
410
|
+
addedContractsCount: changes.added.length,
|
|
411
|
+
removedContractsCount: changes.removed.length,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
await appendWatchLog(projectRoot, logEntry);
|
|
375
415
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
416
|
+
else if (changedFileList.length > 0) {
|
|
417
|
+
// Log even if no changes detected (file changed but no contract changes)
|
|
418
|
+
const logEntry = {
|
|
419
|
+
timestamp: new Date().toISOString(),
|
|
420
|
+
changedFiles: changedFileList,
|
|
421
|
+
fileCount: changedFileList.length,
|
|
422
|
+
durationMs,
|
|
423
|
+
};
|
|
424
|
+
await appendWatchLog(projectRoot, logEntry);
|
|
384
425
|
}
|
|
385
|
-
await deleteStrictWatchStatus(projectRoot);
|
|
386
426
|
}
|
|
387
427
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
timestamp: new Date().toISOString(),
|
|
394
|
-
changedFiles: changedFileList,
|
|
395
|
-
fileCount: changedFileList.length,
|
|
396
|
-
durationMs,
|
|
397
|
-
modifiedContracts: changes.changed.map(c => ({
|
|
398
|
-
entryId: c.entryId,
|
|
399
|
-
semanticHashChanged: !!c.semanticHash,
|
|
400
|
-
fileHashChanged: !!c.fileHash,
|
|
401
|
-
semanticHash: c.semanticHash,
|
|
402
|
-
fileHash: c.fileHash,
|
|
403
|
-
})),
|
|
404
|
-
modifiedBundles: changes.bundleChanged.map(b => ({
|
|
405
|
-
entryId: b.entryId,
|
|
406
|
-
bundleHash: { old: b.oldHash, new: b.newHash },
|
|
407
|
-
})),
|
|
408
|
-
addedContracts: changes.added.length > 0 ? changes.added : undefined,
|
|
409
|
-
removedContracts: changes.removed.length > 0 ? changes.removed : undefined,
|
|
410
|
-
summary: {
|
|
411
|
-
modifiedContractsCount: changes.changed.length,
|
|
412
|
-
modifiedBundlesCount: changes.bundleChanged.length,
|
|
413
|
-
addedContractsCount: changes.added.length,
|
|
414
|
-
removedContractsCount: changes.removed.length,
|
|
415
|
-
},
|
|
416
|
-
};
|
|
417
|
-
await appendWatchLog(projectRoot, logEntry);
|
|
418
|
-
}
|
|
419
|
-
else if (changedFileList.length > 0) {
|
|
420
|
-
// Log even if no changes detected (file changed but no contract changes)
|
|
421
|
-
const logEntry = {
|
|
422
|
-
timestamp: new Date().toISOString(),
|
|
423
|
-
changedFiles: changedFileList,
|
|
424
|
-
fileCount: changedFileList.length,
|
|
425
|
-
durationMs,
|
|
426
|
-
};
|
|
427
|
-
await appendWatchLog(projectRoot, logEntry);
|
|
428
|
+
catch (error) {
|
|
429
|
+
const durationMs = Date.now() - startTime;
|
|
430
|
+
const errorMessage = formatErrorMessage(error);
|
|
431
|
+
if (!options.quiet) {
|
|
432
|
+
console.error(` ❌ Error: ${errorMessage}\n`);
|
|
428
433
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
};
|
|
446
|
-
await appendWatchLog(projectRoot, logEntry);
|
|
434
|
+
if (options.logFile) {
|
|
435
|
+
try {
|
|
436
|
+
const logEntry = {
|
|
437
|
+
timestamp: new Date().toISOString(),
|
|
438
|
+
changedFiles: changedFileList,
|
|
439
|
+
fileCount: changedFileList.length,
|
|
440
|
+
durationMs,
|
|
441
|
+
error: errorMessage,
|
|
442
|
+
};
|
|
443
|
+
await appendWatchLog(projectRoot, logEntry);
|
|
444
|
+
}
|
|
445
|
+
catch (logError) {
|
|
446
|
+
if (!options.quiet) {
|
|
447
|
+
console.warn(` ⚠️ Watch: could not append watch log: ${formatErrorMessage(logError)}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
447
450
|
}
|
|
448
|
-
|
|
451
|
+
const recovery = await attemptWatchCacheRecovery();
|
|
452
|
+
if (recovery != null) {
|
|
453
|
+
watchCache = recovery.cache;
|
|
454
|
+
baselineBundles = recovery.bundles;
|
|
455
|
+
previousBundles = recovery.bundles;
|
|
449
456
|
if (!options.quiet) {
|
|
450
|
-
console.
|
|
457
|
+
console.log(` ✅ Watch cache restored after full rebuild.\n`);
|
|
451
458
|
}
|
|
452
459
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
previousBundles = recovery.bundles;
|
|
459
|
-
if (!options.quiet) {
|
|
460
|
-
console.log(` ✅ Watch cache restored after full rebuild.\n`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
watchCache = null;
|
|
465
|
-
if (!options.quiet) {
|
|
466
|
-
console.warn(' ⚠️ Each file change will run a full context rebuild until a rebuild succeeds.\n');
|
|
460
|
+
else {
|
|
461
|
+
watchCache = null;
|
|
462
|
+
if (!options.quiet) {
|
|
463
|
+
console.warn(' ⚠️ Each file change will run a full context rebuild until a rebuild succeeds.\n');
|
|
464
|
+
}
|
|
467
465
|
}
|
|
468
466
|
}
|
|
469
467
|
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
});
|
|
475
|
-
await regenerationPromise;
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
isRegenerating = false;
|
|
471
|
+
}
|
|
476
472
|
};
|
|
477
473
|
const debouncedRegenerate = () => {
|
|
478
474
|
if (debounceTimer) {
|
|
@@ -512,7 +508,7 @@ export async function startWatchMode(options, projectRoot, initialCache = null)
|
|
|
512
508
|
/context_main\.json$/,
|
|
513
509
|
/context_compare_modes\.json$/,
|
|
514
510
|
// Ignore output directory if different from project root
|
|
515
|
-
...(outputDir !== projectRoot ? [new RegExp('^' + relative(projectRoot, normalizedOutputDir)
|
|
511
|
+
...(outputDir !== projectRoot ? [new RegExp('^' + toForwardSlashes(relative(projectRoot, normalizedOutputDir)))] : []),
|
|
516
512
|
// Ignore common build/dependency directories
|
|
517
513
|
/node_modules/,
|
|
518
514
|
/dist/,
|
|
@@ -531,7 +527,7 @@ export async function startWatchMode(options, projectRoot, initialCache = null)
|
|
|
531
527
|
// Helper to check if a file should trigger regeneration
|
|
532
528
|
const shouldTrigger = (filePath) => {
|
|
533
529
|
// Normalize path separators
|
|
534
|
-
const normalizedPath = filePath
|
|
530
|
+
const normalizedPath = toForwardSlashes(filePath);
|
|
535
531
|
// Debug logging (can be enabled with LOGICSTAMP_DEBUG=1)
|
|
536
532
|
if (process.env.LOGICSTAMP_DEBUG === '1' && !options.quiet) {
|
|
537
533
|
console.log(`[DEBUG] Watch event for: ${normalizedPath}`);
|